dataplot 0.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dataplot/__init__.py +34 -0
- dataplot/_typing.py +211 -0
- dataplot/artist/__init__.py +24 -0
- dataplot/artist/base.py +78 -0
- dataplot/artist/corrmap.py +57 -0
- dataplot/artist/histogram.py +89 -0
- dataplot/artist/ksplot.py +42 -0
- dataplot/artist/linechart.py +62 -0
- dataplot/artist/ppplot.py +41 -0
- dataplot/artist/qqplot.py +92 -0
- dataplot/artist/scatterchart.py +59 -0
- dataplot/container.py +203 -0
- dataplot/core.py +202 -0
- dataplot/dataset.py +968 -0
- dataplot/setting.py +232 -0
- dataplot/utils/__init__.py +6 -0
- dataplot/utils/math.py +80 -0
- dataplot/utils/multi.py +269 -0
- dataplot-0.1.6.dist-info/METADATA +100 -0
- dataplot-0.1.6.dist-info/RECORD +22 -0
- dataplot-0.1.6.dist-info/WHEEL +4 -0
- dataplot-0.1.6.dist-info/licenses/LICENSE +28 -0
dataplot/setting.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Contains dataclasses: PlotSettings, PlotSettable.
|
|
3
|
+
|
|
4
|
+
NOTE: this module is private. All functions and objects are available in the main
|
|
5
|
+
`dataplot` namespace - use that instead.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import asdict
|
|
10
|
+
from typing import Any, Optional, Self, Unpack
|
|
11
|
+
|
|
12
|
+
from validating import attr, dataclass
|
|
13
|
+
|
|
14
|
+
from ._typing import (
|
|
15
|
+
DefaultVar,
|
|
16
|
+
FontDict,
|
|
17
|
+
PlotSettableVar,
|
|
18
|
+
SettingDict,
|
|
19
|
+
SettingKey,
|
|
20
|
+
StyleName,
|
|
21
|
+
SubplotDict,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__all__ = ["PlotSettings", "PlotSettable"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(validate_methods=True)
|
|
28
|
+
class PlotSettings:
|
|
29
|
+
"""Stores and manages settings for plotting."""
|
|
30
|
+
|
|
31
|
+
title: Optional[str] = None
|
|
32
|
+
xlabel: Optional[str] = None
|
|
33
|
+
ylabel: Optional[str] = None
|
|
34
|
+
alpha: Optional[float] = None
|
|
35
|
+
dpi: Optional[float] = None
|
|
36
|
+
grid: Optional[bool] = None
|
|
37
|
+
grid_alpha: Optional[float] = None
|
|
38
|
+
style: Optional[StyleName] = None
|
|
39
|
+
figsize: Optional[tuple[int, int]] = None
|
|
40
|
+
fontdict: Optional[FontDict] = None
|
|
41
|
+
legend_loc: Optional[str] = None
|
|
42
|
+
subplots_adjust: Optional[SubplotDict] = None
|
|
43
|
+
|
|
44
|
+
def __getitem__(self, __key: SettingKey) -> Any:
|
|
45
|
+
return getattr(self, __key)
|
|
46
|
+
|
|
47
|
+
def __setitem__(self, __key: SettingKey, __value: Any) -> None:
|
|
48
|
+
setattr(self, __key, __value)
|
|
49
|
+
|
|
50
|
+
def __repr__(self) -> str:
|
|
51
|
+
return self.__class__.__name__ + "(" + self.repr_not_none() + ")"
|
|
52
|
+
|
|
53
|
+
def repr_not_none(self) -> str:
|
|
54
|
+
"""
|
|
55
|
+
Returns a string representation of attributes with not-None values.
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
str
|
|
60
|
+
String representation.
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
diff = [f"{k}={repr(v)}" for k, v in asdict(self).items() if v is not None]
|
|
64
|
+
return ", ".join(diff)
|
|
65
|
+
|
|
66
|
+
def keys(self) -> list[SettingKey]:
|
|
67
|
+
"""
|
|
68
|
+
Keys of settings.
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
list[SettingKey]
|
|
73
|
+
Keys of the settings.
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
return getattr(self, "__match_args__")
|
|
77
|
+
|
|
78
|
+
def reset(self) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Reset all the settings to None.
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
for k in self.keys():
|
|
84
|
+
self[k] = None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass(init=False)
|
|
88
|
+
class PlotSettable:
|
|
89
|
+
"""Contains an attribute of plot settings, and provides methods for
|
|
90
|
+
handling these settings.
|
|
91
|
+
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
settings: PlotSettings = attr(default_factory=PlotSettings, init=False)
|
|
95
|
+
|
|
96
|
+
def _set(
|
|
97
|
+
self, *, inplace: bool = False, **kwargs: Unpack[SettingDict]
|
|
98
|
+
) -> Self | None:
|
|
99
|
+
obj = self if inplace else self.copy()
|
|
100
|
+
keys = obj.settings.keys()
|
|
101
|
+
for k, v in kwargs.items():
|
|
102
|
+
if v is None or k not in keys:
|
|
103
|
+
continue
|
|
104
|
+
obj.setting_check(k, v)
|
|
105
|
+
if isinstance(v, dict) and isinstance(d := obj.settings[k], dict):
|
|
106
|
+
d.update(v)
|
|
107
|
+
else:
|
|
108
|
+
obj.settings[k] = v
|
|
109
|
+
if not inplace:
|
|
110
|
+
return obj
|
|
111
|
+
|
|
112
|
+
def setting_check(self, key: SettingKey, value: Any) -> None:
|
|
113
|
+
"""
|
|
114
|
+
Checks if a new setting is legal.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
key : SettingKey
|
|
119
|
+
Key of the setting.
|
|
120
|
+
value : Any
|
|
121
|
+
Value of the setting.
|
|
122
|
+
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
def set_default(self, **kwargs: Unpack[SettingDict]) -> None:
|
|
126
|
+
"""
|
|
127
|
+
Sets the default settings.
|
|
128
|
+
|
|
129
|
+
Parameters
|
|
130
|
+
----------
|
|
131
|
+
**kwargs : Unpack[SettingDict]
|
|
132
|
+
Specifies the settings.
|
|
133
|
+
|
|
134
|
+
"""
|
|
135
|
+
keys = self.settings.keys()
|
|
136
|
+
for k, v in kwargs.items():
|
|
137
|
+
if k not in keys:
|
|
138
|
+
continue
|
|
139
|
+
if self.settings[k] is None:
|
|
140
|
+
self.settings[k] = v
|
|
141
|
+
elif isinstance(d := self.settings[k], dict):
|
|
142
|
+
self.settings[k] = {**v, **d}
|
|
143
|
+
|
|
144
|
+
def load(self, settings: "PlotSettings | SettingDict") -> None:
|
|
145
|
+
"""
|
|
146
|
+
Load in the settings.
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
settings : PlotSettings | SettingDict
|
|
151
|
+
An instance of `PlotSettings` or a dict.
|
|
152
|
+
|
|
153
|
+
"""
|
|
154
|
+
if isinstance(settings, PlotSettings):
|
|
155
|
+
settings = asdict(settings)
|
|
156
|
+
self._set(inplace=True, **settings)
|
|
157
|
+
|
|
158
|
+
def get_setting(
|
|
159
|
+
self, key: SettingKey, default: Optional[DefaultVar] = None
|
|
160
|
+
) -> "DefaultVar | Any":
|
|
161
|
+
"""
|
|
162
|
+
Returns the value of a setting if it is not None, otherwise returns the
|
|
163
|
+
default value.
|
|
164
|
+
|
|
165
|
+
Parameters
|
|
166
|
+
----------
|
|
167
|
+
key : SettingKey
|
|
168
|
+
Key of the setting.
|
|
169
|
+
default : DefaultVar, optional
|
|
170
|
+
Specifies the default value to be returned if the requested value
|
|
171
|
+
is None, by default None.
|
|
172
|
+
|
|
173
|
+
Returns
|
|
174
|
+
-------
|
|
175
|
+
DefaultVar | Any
|
|
176
|
+
Value of the setting.
|
|
177
|
+
|
|
178
|
+
"""
|
|
179
|
+
return default if (value := self.settings[key]) is None else value
|
|
180
|
+
|
|
181
|
+
def customize(
|
|
182
|
+
self, cls: type["PlotSettableVar"], *args, **kwargs
|
|
183
|
+
) -> "PlotSettableVar":
|
|
184
|
+
"""
|
|
185
|
+
Initialize another instance with the same settings as `self`.
|
|
186
|
+
|
|
187
|
+
Parameters
|
|
188
|
+
----------
|
|
189
|
+
cls : type[PlotSetableVar]
|
|
190
|
+
Type of the new instance.
|
|
191
|
+
*args :
|
|
192
|
+
Positional arguments.
|
|
193
|
+
**kwargs :
|
|
194
|
+
Keyword arguments.
|
|
195
|
+
|
|
196
|
+
Returns
|
|
197
|
+
-------
|
|
198
|
+
PlotSetableVar
|
|
199
|
+
The new instance.
|
|
200
|
+
|
|
201
|
+
Raises
|
|
202
|
+
------
|
|
203
|
+
ValueError
|
|
204
|
+
Raised when `cls` cannot be customized.
|
|
205
|
+
|
|
206
|
+
"""
|
|
207
|
+
if not issubclass(cls, PlotSettable):
|
|
208
|
+
raise ValueError(f"type {cls} cannot be customized")
|
|
209
|
+
matched: dict[str, Any] = {}
|
|
210
|
+
unmatched: dict[str, Any] = {}
|
|
211
|
+
for k, v in kwargs.items():
|
|
212
|
+
if k in cls.__init__.__code__.co_varnames[1:]:
|
|
213
|
+
matched[k] = v
|
|
214
|
+
else:
|
|
215
|
+
unmatched[k] = v
|
|
216
|
+
obj = cls(*args, **matched)
|
|
217
|
+
obj.settings = PlotSettings(**asdict(self.settings))
|
|
218
|
+
for k, v in unmatched.items():
|
|
219
|
+
setattr(obj, k, v)
|
|
220
|
+
return obj
|
|
221
|
+
|
|
222
|
+
def copy(self) -> Self:
|
|
223
|
+
"""
|
|
224
|
+
Copy the instance of self (but not deepcopy).
|
|
225
|
+
|
|
226
|
+
Returns
|
|
227
|
+
-------
|
|
228
|
+
Self
|
|
229
|
+
A new instance of self.
|
|
230
|
+
|
|
231
|
+
"""
|
|
232
|
+
raise TypeError(f"cannot copy instance of {self.__class__.__name__!r}")
|
dataplot/utils/math.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The core of math: linear_regression_1d(), get_quantile(), get_prob(), etc.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
__all__ = ["linear_regression_1d", "get_quantile", "get_prob"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def linear_regression_1d(y: np.ndarray, x: np.ndarray) -> tuple[float, float]:
|
|
12
|
+
"""
|
|
13
|
+
Implements a 1-demensional linear regression of y on x (y = ax + b), and
|
|
14
|
+
returns the regression coefficients (a, b). Nan-values and inf-values are
|
|
15
|
+
handled smartly.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
y : np.ndarray
|
|
20
|
+
The dependent variable.
|
|
21
|
+
x : np.ndarray
|
|
22
|
+
The independent variable.
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
tuple[float, float]
|
|
27
|
+
The regression coefficients (a, b).
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
x, y = (
|
|
31
|
+
np.nan_to_num(x, posinf=np.nan, neginf=np.nan),
|
|
32
|
+
np.nan_to_num(y, posinf=np.nan, neginf=np.nan),
|
|
33
|
+
)
|
|
34
|
+
xy_mean = np.nanmean(x * y)
|
|
35
|
+
x_mean = np.nanmean(x)
|
|
36
|
+
y_mean = np.nanmean(y)
|
|
37
|
+
b = (xy_mean - x_mean * y_mean) / np.nanvar(x)
|
|
38
|
+
a = y_mean - x_mean * b
|
|
39
|
+
return a, b
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_quantile(data: np.ndarray, p: np.ndarray) -> np.ndarray:
|
|
43
|
+
"""
|
|
44
|
+
Get quantiles from cummulative probabilities. Nan-values and inf-values
|
|
45
|
+
are handled smartly.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
data : np.ndarray
|
|
50
|
+
Original data.
|
|
51
|
+
p : np.ndarray
|
|
52
|
+
Cummulative probabilities.
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
np.ndarray
|
|
57
|
+
Quantiles.
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
return np.nanquantile(np.nan_to_num(data, posinf=np.nan, neginf=np.nan), p)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_prob(data: np.ndarray, q: np.ndarray) -> np.ndarray:
|
|
64
|
+
"""
|
|
65
|
+
Get cummulative probabilities from quantiles.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
data : np.ndarray
|
|
70
|
+
Original data.
|
|
71
|
+
q : np.ndarray
|
|
72
|
+
Quantiles.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
np.ndarray
|
|
77
|
+
Cummulative probabilities.
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
return np.array([np.sum(data <= x) for x in q]) / np.isfinite(data).sum()
|
dataplot/utils/multi.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The core of multi: multi(), multipartial(), etc.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Callable, Generic, Iterable, LiteralString, Optional, TypeVar
|
|
7
|
+
|
|
8
|
+
from validating import dataclass
|
|
9
|
+
|
|
10
|
+
T = TypeVar("T")
|
|
11
|
+
S = TypeVar("S", bound=LiteralString)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"MultiObject",
|
|
16
|
+
"multipartial",
|
|
17
|
+
"single",
|
|
18
|
+
"multiple",
|
|
19
|
+
"REMAIN",
|
|
20
|
+
"UNSUBSCRIPTABLE",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MultiObject(Generic[T]):
|
|
25
|
+
"""
|
|
26
|
+
A basic object that enables multi-element attribute-getting,
|
|
27
|
+
attribute-setting, calling, etc. This object maintains a list of items,
|
|
28
|
+
and if a method (including some magic methods, see below) is called, each
|
|
29
|
+
item's method with the same name will be called instead, and the results
|
|
30
|
+
come as a new MultiObject.
|
|
31
|
+
|
|
32
|
+
Here are the methods that will be overloaded:
|
|
33
|
+
* __getattr__()
|
|
34
|
+
* __setattr__()
|
|
35
|
+
* __call__()
|
|
36
|
+
* __getitem__()
|
|
37
|
+
* __setitem__()
|
|
38
|
+
* All public methods
|
|
39
|
+
* All private methods that starts with only one "_"
|
|
40
|
+
|
|
41
|
+
And here is the only property that is exposed outside:
|
|
42
|
+
* __multiobjects__ : returns the items
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
__iterable : Iterable, optional
|
|
47
|
+
If not given, the constructor creates a new empty MultiObject. If
|
|
48
|
+
specified, the argument must be an iterable (the same as what is needed
|
|
49
|
+
for creating a list). By default None.
|
|
50
|
+
call_reducer : Callable[[list], Any], optional
|
|
51
|
+
Specifies a reducer for the return values of `__call__()`. If specified,
|
|
52
|
+
should be a callable that receives the list of original returns, and
|
|
53
|
+
gives back a reduced value. If None, the reduced value will always be a
|
|
54
|
+
new MultiObject. By default None.
|
|
55
|
+
call_reflex : bool, optional
|
|
56
|
+
If True, the return values of a previous element's `__call__()` will be
|
|
57
|
+
provided to the next element as a keyword argument named
|
|
58
|
+
'__multi_prev_returned__', by default False.
|
|
59
|
+
attr_reducer: Callable[[str], Callable[[list], Any]], optional
|
|
60
|
+
Specifies a reducer for the return values of `__getattr__()`. If
|
|
61
|
+
specified, should be a callable that receives the attribute name, and
|
|
62
|
+
gives back a new callable. The new callable will receive the list of
|
|
63
|
+
original return values, and gives back a reduced value. If None, the
|
|
64
|
+
reduced value will always be a new MultiObject. By default None.
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
__iterable: Optional[Iterable] = None,
|
|
71
|
+
*,
|
|
72
|
+
call_reducer: Optional[Callable[[list], Any]] = None,
|
|
73
|
+
call_reflex: bool = False,
|
|
74
|
+
attr_reducer: Optional[Callable[[str], Callable[[list], Any]]] = None,
|
|
75
|
+
) -> None:
|
|
76
|
+
self.__call_reducer = call_reducer
|
|
77
|
+
self.__call_reflex = call_reflex
|
|
78
|
+
self.__attr_reducer = attr_reducer
|
|
79
|
+
self.__items: list[S] = [] if __iterable is None else list(__iterable)
|
|
80
|
+
|
|
81
|
+
def __getattr__(self, __name: str) -> "MultiObject | Any":
|
|
82
|
+
if __name.startswith("__"):
|
|
83
|
+
raise AttributeError(f"cannot reach attribute '{__name}'")
|
|
84
|
+
attrs = [getattr(x, __name) for x in self.__items]
|
|
85
|
+
if self.__attr_reducer:
|
|
86
|
+
reduced = self.__attr_reducer(__name)(attrs)
|
|
87
|
+
if reduced != REMAIN:
|
|
88
|
+
return reduced
|
|
89
|
+
return MultiObject(attrs)
|
|
90
|
+
|
|
91
|
+
def __setattr__(self, __name: Any, __value: Any) -> None:
|
|
92
|
+
if isinstance(__name, str) and __name.startswith("_"):
|
|
93
|
+
super().__setattr__(__name, __value)
|
|
94
|
+
else:
|
|
95
|
+
for i, obj in enumerate(self.__items):
|
|
96
|
+
setattr(obj, single(__name, n=i), single(__value, n=i))
|
|
97
|
+
|
|
98
|
+
def __call__(self, *args: Any, **kwargs: Any) -> "MultiObject | Any":
|
|
99
|
+
returns = []
|
|
100
|
+
len_items = len(self.__items)
|
|
101
|
+
r = None
|
|
102
|
+
for i, obj in enumerate(self.__items):
|
|
103
|
+
a = [single(x, n=i) for x in args]
|
|
104
|
+
kwd = {k: single(v, n=i) for k, v in kwargs.items()}
|
|
105
|
+
if self.__call_reflex:
|
|
106
|
+
kwd["__multi_is_final__"] = i == len_items - 1
|
|
107
|
+
kwd["__multi_prev_returned__"] = r if i > 0 else None
|
|
108
|
+
returns.append(r := obj(*a, **kwd))
|
|
109
|
+
if self.__call_reducer:
|
|
110
|
+
reduced = self.__call_reducer(returns)
|
|
111
|
+
if reduced != REMAIN:
|
|
112
|
+
return reduced
|
|
113
|
+
return MultiObject(returns)
|
|
114
|
+
|
|
115
|
+
def __getitem__(self, __key: Any) -> T | "MultiObject":
|
|
116
|
+
items = [x[__key] for x in self.__items]
|
|
117
|
+
if isinstance(__key, int) and UNSUBSCRIPTABLE in items:
|
|
118
|
+
return self.__items[__key]
|
|
119
|
+
return MultiObject(items)
|
|
120
|
+
|
|
121
|
+
def __setitem__(self, __key: Any, __value: Any) -> None:
|
|
122
|
+
for i, obj in enumerate(self.__items):
|
|
123
|
+
obj[single(__key, n=i)] = single(__value, n=i)
|
|
124
|
+
|
|
125
|
+
def __repr__(self) -> str:
|
|
126
|
+
return ("\n").join("- " + repr(x).replace("\n", "\n ") for x in self.__items)
|
|
127
|
+
|
|
128
|
+
def __str__(self) -> str:
|
|
129
|
+
signature = self.__class__.__name__ + repr_not_none(self)
|
|
130
|
+
return f"{signature}"
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def __multiobjects__(self) -> list[T]:
|
|
134
|
+
return self.__items
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def repr_not_none(x: MultiObject) -> str:
|
|
138
|
+
"""
|
|
139
|
+
Returns a string representation of the MultiObject's attributes with
|
|
140
|
+
not-None values. Attributes with values of None are ignored.
|
|
141
|
+
|
|
142
|
+
Parameters
|
|
143
|
+
----------
|
|
144
|
+
x : MultiObject
|
|
145
|
+
Any object.
|
|
146
|
+
|
|
147
|
+
Returns
|
|
148
|
+
-------
|
|
149
|
+
str
|
|
150
|
+
String representation.
|
|
151
|
+
|
|
152
|
+
"""
|
|
153
|
+
namelist = [n for n in x.__init__.__code__.co_varnames[1:] if not n.startswith("_")]
|
|
154
|
+
not_nones: list[str] = []
|
|
155
|
+
for n in namelist:
|
|
156
|
+
if not hasattr(x, p := f"_{type(x).__name__}__{n}"):
|
|
157
|
+
continue
|
|
158
|
+
if (v := getattr(x, p)) is None:
|
|
159
|
+
continue
|
|
160
|
+
if isinstance(v, Callable):
|
|
161
|
+
v = v.__name__
|
|
162
|
+
not_nones.append(f"{n}={v}")
|
|
163
|
+
return "" if len(not_nones) == 0 else "(" + ", ".join(not_nones) + ")"
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@dataclass(validate_methods=True)
|
|
167
|
+
class MultiFlag:
|
|
168
|
+
"""Flag for MultiObjects."""
|
|
169
|
+
|
|
170
|
+
flag: int
|
|
171
|
+
name: str
|
|
172
|
+
err: type | None = None
|
|
173
|
+
errmsg: str = ""
|
|
174
|
+
|
|
175
|
+
def __repr__(self) -> str:
|
|
176
|
+
if self.err is not None:
|
|
177
|
+
raise self.err(self.errmsg)
|
|
178
|
+
return self.name
|
|
179
|
+
|
|
180
|
+
def __eq__(self, __value: Any) -> bool:
|
|
181
|
+
if isinstance(__value, MultiFlag):
|
|
182
|
+
return self.flag == __value.flag
|
|
183
|
+
return False
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
REMAIN = MultiFlag(0, "REMAIN")
|
|
187
|
+
UNSUBSCRIPTABLE = MultiFlag(
|
|
188
|
+
-1, "UNSUBSCRIPTABLE", TypeError, "object is not subscriptable"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def multipartial(**kwargs) -> Callable[[list], MultiObject]:
|
|
193
|
+
"""
|
|
194
|
+
Returns a MultiObject constructor with partial application of the
|
|
195
|
+
given arguments and keywords.
|
|
196
|
+
|
|
197
|
+
Returns
|
|
198
|
+
-------
|
|
199
|
+
Callable[[list], MultiObject]
|
|
200
|
+
A MultiObject constructor.
|
|
201
|
+
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
def multi_constructor(x: list):
|
|
205
|
+
return MultiObject(x, **kwargs)
|
|
206
|
+
|
|
207
|
+
return multi_constructor
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def single(x: T, n: int = -1) -> T:
|
|
211
|
+
"""
|
|
212
|
+
If a MultiObject is provided, return its n-th element, otherwise return
|
|
213
|
+
the input itself.
|
|
214
|
+
|
|
215
|
+
Parameters
|
|
216
|
+
----------
|
|
217
|
+
x : T
|
|
218
|
+
Can be a MultiObject or anything else.
|
|
219
|
+
n : int, optional
|
|
220
|
+
Specifies which element to return if a MultiObject is provided, by
|
|
221
|
+
default -1.
|
|
222
|
+
|
|
223
|
+
Returns
|
|
224
|
+
-------
|
|
225
|
+
T
|
|
226
|
+
A single object.
|
|
227
|
+
|
|
228
|
+
"""
|
|
229
|
+
return x.__multiobjects__[n] if isinstance(x, MultiObject) else x
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def multiple(x: T) -> list[T]:
|
|
233
|
+
"""
|
|
234
|
+
If a MultiObject is provided, return a list of its elements, otherwise
|
|
235
|
+
return `[x]`.
|
|
236
|
+
|
|
237
|
+
Parameters
|
|
238
|
+
----------
|
|
239
|
+
x : T
|
|
240
|
+
Can be a MultiObject or anything else.
|
|
241
|
+
|
|
242
|
+
Returns
|
|
243
|
+
-------
|
|
244
|
+
list[T]
|
|
245
|
+
List of elements.
|
|
246
|
+
|
|
247
|
+
"""
|
|
248
|
+
return x.__multiobjects__ if isinstance(x, MultiObject) else [x]
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def cleaner(x: list) -> MultiObject | None:
|
|
252
|
+
"""
|
|
253
|
+
If the list is consist of None's only, return None, otherwise return
|
|
254
|
+
a MultiObject instantiated by the list.
|
|
255
|
+
|
|
256
|
+
Parameters
|
|
257
|
+
----------
|
|
258
|
+
x : list
|
|
259
|
+
List of objects.
|
|
260
|
+
|
|
261
|
+
Returns
|
|
262
|
+
-------
|
|
263
|
+
MultiObject | None
|
|
264
|
+
May be a MultiObject instantiated by the list or None.
|
|
265
|
+
|
|
266
|
+
"""
|
|
267
|
+
if all(i is None for i in x):
|
|
268
|
+
return None
|
|
269
|
+
return MultiObject(x, call_reducer=cleaner, attr_reducer=lambda x: cleaner)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dataplot
|
|
3
|
+
Version: 0.1.6
|
|
4
|
+
Summary: Provides plotting tools useful in datascience.
|
|
5
|
+
Project-URL: Documentation, https://github.com/Chitaoji/dataplot/blob/main/README.md
|
|
6
|
+
Project-URL: Repository, https://github.com/Chitaoji/dataplot/
|
|
7
|
+
Author-email: Chitaoji <2360742040@qq.com>
|
|
8
|
+
Maintainer-email: Chitaoji <2360742040@qq.com>
|
|
9
|
+
License-Expression: BSD-3-Clause
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: plot
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Python: >=3.13
|
|
15
|
+
Requires-Dist: lazyr
|
|
16
|
+
Requires-Dist: matplotlib
|
|
17
|
+
Requires-Dist: numpy
|
|
18
|
+
Requires-Dist: pandas
|
|
19
|
+
Requires-Dist: scipy
|
|
20
|
+
Requires-Dist: seaborn
|
|
21
|
+
Requires-Dist: validating
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# dataplot
|
|
25
|
+
Provides plotting tools useful in datascience.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
```sh
|
|
29
|
+
$ pip install dataplot
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Requirements
|
|
33
|
+
```txt
|
|
34
|
+
validating
|
|
35
|
+
lazyr
|
|
36
|
+
matplotlib
|
|
37
|
+
numpy
|
|
38
|
+
pandas
|
|
39
|
+
scipy
|
|
40
|
+
seaborn
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## See Also
|
|
44
|
+
### Github repository
|
|
45
|
+
* https://github.com/Chitaoji/dataplot/
|
|
46
|
+
|
|
47
|
+
### PyPI project
|
|
48
|
+
* https://pypi.org/project/dataplot/
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
This project falls under the BSD 3-Clause License.
|
|
52
|
+
|
|
53
|
+
## History
|
|
54
|
+
### v0.1.6
|
|
55
|
+
* New method `PlotDataSet.scatter()` to draw true scatter charts while keeping `PlotDataSet.plot()` as line chart behavior.
|
|
56
|
+
* Improved automatic label inference for `dp.data(...)`, plotting labels, and x-axis labels in interactive contexts.
|
|
57
|
+
* Plot builders now use deferred drawing; removed `dp.show()` and improved axis/figure rendering in object representations.
|
|
58
|
+
* Refined rendering stability with fixes for empty-axis cleanup and figure re-rendering in `FigWrapper.__repr__`.
|
|
59
|
+
* Updated minimum required Python version to >=3.13.
|
|
60
|
+
|
|
61
|
+
### v0.1.5
|
|
62
|
+
* Fixed issue: unworking figure settings in the artist methods.
|
|
63
|
+
|
|
64
|
+
### v0.1.4
|
|
65
|
+
* Fixed issue: incorrectly displayed histogram statistics when the x-label had been modified by the user.
|
|
66
|
+
|
|
67
|
+
### v0.1.3
|
|
68
|
+
* Allowed users to set the plot-settings by kwargs in artist methods like `PlotDataSet.hist()`, `PlotDataSet.plot()`, etc.
|
|
69
|
+
* New operation methods `PlotDataSet.signedpow()` and `PlotDataSet.log10()`.
|
|
70
|
+
* Renamed `PlotDataSet.signlog()` to `.signedlog()`; renamed `PlotDataSet.opclear()` to `.undo_all()`; removed `PlotDataSet.opclear_records_only()`.
|
|
71
|
+
* New optional parameter `format_label=` for `PlotDataSet.set_plot()` to decide whether to format the label when painting on the axes.
|
|
72
|
+
* When defining the data classes, used *dataclasses* instead of *attrs* for a faster import.
|
|
73
|
+
|
|
74
|
+
### v0.1.2
|
|
75
|
+
* New methods `PlotDataSet.corrmap()`, `PlotDataSet.ppplot()`, and `PlotDataSet.resample()`.
|
|
76
|
+
* New optional parameter `fmt=` for `PlotDataSet.plot()`, `PlotDataSet.qqplot()`, `PlotDataSet.ppplot()`, and `PlotDataSet.ksplot()`.
|
|
77
|
+
* Bugfix.
|
|
78
|
+
|
|
79
|
+
### v0.1.1
|
|
80
|
+
* New module-level function `dp.show()`.
|
|
81
|
+
* New methods `PlotDataSet.qqplot()`, `PlotDataSet.ksplot()` and `PlotDataSet.abs()`.
|
|
82
|
+
* All the plotting method (e.g., `.hist()`) will now return an `Artist` object instead of None.
|
|
83
|
+
* New plot settings: `grid` and `grid_alpha`.
|
|
84
|
+
* Parameters of `FigWrapper.set_figure()`, `AxesWrapper.set_axes()` and `PlotDataSet.set_plot()` are keyword-only now.
|
|
85
|
+
* The returns of `.set_figure()` and `.set_axes()` will be None (instead of `self`) to avoid misunderstandings.
|
|
86
|
+
* New optional parameter `inplace=` for `PlotDataSet.set_plot()` to decide whether the changes will happen in-place (which is the only option before) or in a new copy.
|
|
87
|
+
* Parameter `ticks=` for `PlotDataSet.plot()` can be set to a `PlotDataSet` object now.
|
|
88
|
+
|
|
89
|
+
### v0.1.0
|
|
90
|
+
* `PlotDataSet` now supports binary operations including +, -, *, /, and **.
|
|
91
|
+
* New methods `FigWrapper.set_figure()` and `AxesWrapper.set_axes()` - use them instead of `*.set_plot()`.
|
|
92
|
+
* Simplified the usage of `AxesWrapper`.
|
|
93
|
+
* New plot settings: `subplots_adjust=`, `fontdict=` and `dpi=`.
|
|
94
|
+
* After this version, the required Python version is updated to >=3.11.9. Download and install v0.0.2 if the user is under lower Python version (>=3.8.13).
|
|
95
|
+
|
|
96
|
+
### v0.0.2
|
|
97
|
+
* Updated the meta-data.
|
|
98
|
+
|
|
99
|
+
### v0.0.1
|
|
100
|
+
* Initial release.
|