plotnine 0.15.3__py3-none-any.whl → 0.16.0a1__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.
- plotnine/_mpl/gridspec.py +50 -6
- plotnine/_mpl/layout_manager/__init__.py +2 -5
- plotnine/_mpl/layout_manager/_composition_layout_items.py +98 -0
- plotnine/_mpl/layout_manager/_composition_side_space.py +461 -0
- plotnine/_mpl/layout_manager/_engine.py +19 -58
- plotnine/_mpl/layout_manager/_grid.py +94 -0
- plotnine/_mpl/layout_manager/_layout_tree.py +402 -817
- plotnine/_mpl/layout_manager/{_layout_items.py → _plot_layout_items.py} +55 -278
- plotnine/_mpl/layout_manager/{_spaces.py → _plot_side_space.py} +111 -291
- plotnine/_mpl/layout_manager/_side_space.py +176 -0
- plotnine/_mpl/utils.py +259 -1
- plotnine/_utils/__init__.py +23 -3
- plotnine/_utils/context.py +9 -13
- plotnine/_utils/dataclasses.py +24 -0
- plotnine/animation.py +13 -12
- plotnine/composition/__init__.py +6 -0
- plotnine/composition/_beside.py +13 -11
- plotnine/composition/_compose.py +263 -99
- plotnine/composition/_plot_annotation.py +75 -0
- plotnine/composition/_plot_layout.py +143 -0
- plotnine/composition/_plot_spacer.py +1 -1
- plotnine/composition/_stack.py +13 -11
- plotnine/composition/_types.py +28 -0
- plotnine/composition/_wrap.py +60 -0
- plotnine/facets/facet.py +9 -12
- plotnine/facets/facet_grid.py +2 -2
- plotnine/facets/facet_wrap.py +1 -1
- plotnine/geoms/geom.py +2 -2
- plotnine/geoms/geom_map.py +4 -5
- plotnine/geoms/geom_path.py +8 -7
- plotnine/geoms/geom_rug.py +6 -10
- plotnine/geoms/geom_text.py +5 -5
- plotnine/ggplot.py +63 -9
- plotnine/guides/guide.py +24 -6
- plotnine/guides/guide_colorbar.py +88 -46
- plotnine/guides/guide_legend.py +47 -20
- plotnine/guides/guides.py +2 -2
- plotnine/iapi.py +17 -1
- plotnine/scales/scale.py +1 -1
- plotnine/stats/binning.py +15 -43
- plotnine/stats/smoothers.py +7 -3
- plotnine/stats/stat.py +2 -2
- plotnine/stats/stat_density_2d.py +10 -6
- plotnine/stats/stat_pointdensity.py +8 -1
- plotnine/stats/stat_qq.py +5 -5
- plotnine/stats/stat_qq_line.py +6 -1
- plotnine/stats/stat_sina.py +19 -20
- plotnine/stats/stat_summary.py +4 -2
- plotnine/stats/stat_summary_bin.py +7 -1
- plotnine/themes/elements/element_line.py +2 -0
- plotnine/themes/elements/element_text.py +12 -1
- plotnine/themes/theme.py +18 -24
- plotnine/themes/themeable.py +17 -3
- plotnine/typing.py +6 -1
- {plotnine-0.15.3.dist-info → plotnine-0.16.0a1.dist-info}/METADATA +3 -3
- {plotnine-0.15.3.dist-info → plotnine-0.16.0a1.dist-info}/RECORD +59 -51
- {plotnine-0.15.3.dist-info → plotnine-0.16.0a1.dist-info}/WHEEL +1 -1
- plotnine/composition/_plotspec.py +0 -50
- {plotnine-0.15.3.dist-info → plotnine-0.16.0a1.dist-info}/licenses/LICENSE +0 -0
- {plotnine-0.15.3.dist-info → plotnine-0.16.0a1.dist-info}/top_level.txt +0 -0
plotnine/iapi.py
CHANGED
|
@@ -14,7 +14,7 @@ from functools import cached_property
|
|
|
14
14
|
from typing import TYPE_CHECKING
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
|
-
from typing import Any, Iterator, Optional, Sequence
|
|
17
|
+
from typing import Any, Iterator, Literal, Optional, Sequence
|
|
18
18
|
|
|
19
19
|
from matplotlib.axes import Axes
|
|
20
20
|
from matplotlib.figure import Figure
|
|
@@ -26,8 +26,10 @@ if TYPE_CHECKING:
|
|
|
26
26
|
FloatArrayLike,
|
|
27
27
|
HorizontalJustification,
|
|
28
28
|
ScaledAestheticsName,
|
|
29
|
+
Side,
|
|
29
30
|
StripPosition,
|
|
30
31
|
VerticalJustification,
|
|
32
|
+
VerticalTextJustification,
|
|
31
33
|
)
|
|
32
34
|
|
|
33
35
|
from ._mpl.offsetbox import FlexibleAnchoredOffsetbox
|
|
@@ -385,3 +387,17 @@ class legend_artists:
|
|
|
385
387
|
)
|
|
386
388
|
inside = (l.box for l in self.inside)
|
|
387
389
|
return list(itertools.chain([*lrtb, *inside]))
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
@dataclass
|
|
393
|
+
class guide_text:
|
|
394
|
+
"""
|
|
395
|
+
Processed guide text
|
|
396
|
+
"""
|
|
397
|
+
|
|
398
|
+
margins: Sequence[float]
|
|
399
|
+
aligns: Sequence[Side | Literal["center"]]
|
|
400
|
+
fontsize: float
|
|
401
|
+
has: Sequence[HorizontalJustification]
|
|
402
|
+
vas: Sequence[VerticalTextJustification]
|
|
403
|
+
is_blank: bool
|
plotnine/scales/scale.py
CHANGED
plotnine/stats/binning.py
CHANGED
|
@@ -47,7 +47,7 @@ def breaks_from_binwidth(
|
|
|
47
47
|
binwidth: float,
|
|
48
48
|
center: Optional[float] = None,
|
|
49
49
|
boundary: Optional[float] = None,
|
|
50
|
-
):
|
|
50
|
+
) -> FloatArray:
|
|
51
51
|
"""
|
|
52
52
|
Calculate breaks given binwidth
|
|
53
53
|
|
|
@@ -82,13 +82,12 @@ def breaks_from_binwidth(
|
|
|
82
82
|
if center is not None:
|
|
83
83
|
boundary = center - boundary
|
|
84
84
|
|
|
85
|
-
epsilon = np.finfo(float).eps
|
|
86
85
|
shift = np.floor((x_range[0] - boundary) / binwidth)
|
|
87
86
|
origin = boundary + shift * binwidth
|
|
88
|
-
# The
|
|
87
|
+
# The nextafter reduction prevents numerical roundoff in the
|
|
89
88
|
# binwidth from creating an extra break beyond the one that
|
|
90
89
|
# includes x_range[1].
|
|
91
|
-
max_x = x_range[1] + binwidth
|
|
90
|
+
max_x = np.nextafter(x_range[1] + binwidth, -np.inf)
|
|
92
91
|
breaks = np.arange(origin, max_x, binwidth)
|
|
93
92
|
return breaks
|
|
94
93
|
|
|
@@ -98,7 +97,7 @@ def breaks_from_bins(
|
|
|
98
97
|
bins: int = 30,
|
|
99
98
|
center: Optional[float] = None,
|
|
100
99
|
boundary: Optional[float] = None,
|
|
101
|
-
):
|
|
100
|
+
) -> FloatArray:
|
|
102
101
|
"""
|
|
103
102
|
Calculate breaks given binwidth
|
|
104
103
|
|
|
@@ -303,48 +302,21 @@ def fuzzybreaks(
|
|
|
303
302
|
|
|
304
303
|
# To minimise precision errors, we do not pass the boundary and
|
|
305
304
|
# binwidth into np.arange as params. The resulting breaks
|
|
306
|
-
# can then be adjusted
|
|
307
|
-
# some arbitrary small number) precision.
|
|
305
|
+
# can then be adjusted to the next floating point number.
|
|
308
306
|
breaks = np.arange(boundary, srange[1] + binwidth, binwidth)
|
|
309
307
|
return _adjust_breaks(breaks, right)
|
|
310
308
|
|
|
311
309
|
|
|
312
310
|
def _adjust_breaks(breaks: FloatArray, right: bool) -> FloatArray:
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
minus = 1 - epsilon
|
|
316
|
-
|
|
317
|
-
sign = np.sign(breaks)
|
|
318
|
-
pos_idx = np.where(sign == 1)[0]
|
|
319
|
-
neg_idx = np.where(sign == -1)[0]
|
|
320
|
-
zero_idx = np.where(sign == 0)[0]
|
|
321
|
-
|
|
322
|
-
fuzzy = breaks.copy()
|
|
323
|
-
if right:
|
|
324
|
-
# [_](_](_](_]
|
|
325
|
-
lbreak = breaks[0]
|
|
326
|
-
fuzzy[pos_idx] *= plus
|
|
327
|
-
fuzzy[neg_idx] *= minus
|
|
328
|
-
fuzzy[zero_idx] = epsilon
|
|
329
|
-
# Left closing break
|
|
330
|
-
if lbreak == 0:
|
|
331
|
-
fuzzy[0] = -epsilon
|
|
332
|
-
elif lbreak < 0:
|
|
333
|
-
fuzzy[0] = lbreak * plus
|
|
334
|
-
else:
|
|
335
|
-
fuzzy[0] = lbreak * minus
|
|
336
|
-
else:
|
|
337
|
-
# [_)[_)[_)[_]
|
|
338
|
-
rbreak = breaks[-1]
|
|
339
|
-
fuzzy[pos_idx] *= minus
|
|
340
|
-
fuzzy[neg_idx] *= plus
|
|
341
|
-
fuzzy[zero_idx] = -epsilon
|
|
342
|
-
# Right closing break
|
|
343
|
-
if rbreak == 0:
|
|
344
|
-
fuzzy[-1] = epsilon
|
|
345
|
-
elif rbreak > 0:
|
|
346
|
-
fuzzy[-1] = rbreak * plus
|
|
347
|
-
else:
|
|
348
|
-
fuzzy[-1] = rbreak * minus
|
|
311
|
+
"""
|
|
312
|
+
Adjust breaks to include/exclude every right break
|
|
349
313
|
|
|
314
|
+
If right=True, the breaks create intervals closed on right
|
|
315
|
+
i.e. [_] (_] (_] (_]
|
|
316
|
+
If right=False, the breaks create intervals closed on the left
|
|
317
|
+
i.e. [_) [_) [_) [_]
|
|
318
|
+
"""
|
|
319
|
+
limit, idx = (np.inf, 0) if right else (-np.inf, -1)
|
|
320
|
+
fuzzy = np.nextafter(breaks, limit)
|
|
321
|
+
fuzzy[idx] = np.nextafter(breaks[idx], -limit)
|
|
350
322
|
return fuzzy
|
plotnine/stats/smoothers.py
CHANGED
|
@@ -13,6 +13,8 @@ from ..exceptions import PlotnineError, PlotnineWarning
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
14
|
import statsmodels.api as sm
|
|
15
15
|
|
|
16
|
+
from plotnine.typing import FloatArray
|
|
17
|
+
|
|
16
18
|
|
|
17
19
|
def predictdf(data, xseq, params) -> pd.DataFrame:
|
|
18
20
|
"""
|
|
@@ -64,7 +66,7 @@ def lm(data, xseq, params) -> pd.DataFrame:
|
|
|
64
66
|
|
|
65
67
|
X = sm.add_constant(data["x"])
|
|
66
68
|
Xseq = sm.add_constant(xseq)
|
|
67
|
-
weights = data.get("
|
|
69
|
+
weights = data.get("weight", None)
|
|
68
70
|
|
|
69
71
|
if weights is None:
|
|
70
72
|
init_kwargs, fit_kwargs = separate_method_kwargs(
|
|
@@ -454,12 +456,14 @@ def gpr(data, xseq, params):
|
|
|
454
456
|
if params["se"]:
|
|
455
457
|
y, stderr = regressor.predict(Xseq, return_std=True)
|
|
456
458
|
data["y"] = y
|
|
457
|
-
data["se"] = stderr
|
|
459
|
+
data["se"] = cast("FloatArray", stderr)
|
|
458
460
|
data["ymin"], data["ymax"] = tdist_ci(
|
|
459
461
|
y, n - 1, stderr, params["level"]
|
|
460
462
|
)
|
|
461
463
|
else:
|
|
462
|
-
data["y"] =
|
|
464
|
+
data["y"] = cast(
|
|
465
|
+
"FloatArray", regressor.predict(Xseq, return_std=False)
|
|
466
|
+
)
|
|
463
467
|
|
|
464
468
|
return data
|
|
465
469
|
|
plotnine/stats/stat.py
CHANGED
|
@@ -144,10 +144,10 @@ class stat(ABC, metaclass=Register):
|
|
|
144
144
|
shallow = {"_kwargs"}
|
|
145
145
|
for key, item in old.items():
|
|
146
146
|
if key in shallow:
|
|
147
|
-
new[key] = item
|
|
147
|
+
new[key] = item # pyright: ignore[reportIndexIssue]
|
|
148
148
|
memo[id(new[key])] = new[key]
|
|
149
149
|
else:
|
|
150
|
-
new[key] = deepcopy(item, memo)
|
|
150
|
+
new[key] = deepcopy(item, memo) # pyright: ignore[reportIndexIssue]
|
|
151
151
|
|
|
152
152
|
return result
|
|
153
153
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING, cast
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import pandas as pd
|
|
7
7
|
|
|
8
|
+
from ..doctools import document
|
|
8
9
|
from .density import get_var_type, kde
|
|
9
10
|
from .stat import stat
|
|
10
11
|
|
|
@@ -12,6 +13,7 @@ if TYPE_CHECKING:
|
|
|
12
13
|
from plotnine.typing import FloatArrayLike
|
|
13
14
|
|
|
14
15
|
|
|
16
|
+
@document
|
|
15
17
|
class stat_density_2d(stat):
|
|
16
18
|
"""
|
|
17
19
|
Compute 2D kernel density estimation
|
|
@@ -92,17 +94,19 @@ class stat_density_2d(stat):
|
|
|
92
94
|
group = data["group"].iloc[0]
|
|
93
95
|
range_x = scales.x.dimension()
|
|
94
96
|
range_y = scales.y.dimension()
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
_x = np.linspace(range_x[0], range_x[1], params["n"])
|
|
98
|
+
_y = np.linspace(range_y[0], range_y[1], params["n"])
|
|
97
99
|
|
|
98
100
|
# The grid must have a "similar" shape (n, p) to the var_data
|
|
99
|
-
X, Y = np.meshgrid(
|
|
100
|
-
|
|
101
|
+
X, Y = np.meshgrid(_x, _y)
|
|
102
|
+
x = cast("FloatArrayLike", data["x"].to_numpy())
|
|
103
|
+
y = cast("FloatArrayLike", data["y"].to_numpy())
|
|
104
|
+
var_data = np.array([x, y]).T
|
|
101
105
|
grid = np.array([X.flatten(), Y.flatten()]).T
|
|
102
106
|
density = kde(var_data, grid, package, **kde_params)
|
|
103
107
|
|
|
104
108
|
if params["contour"]:
|
|
105
|
-
Z = density.reshape(len(
|
|
109
|
+
Z = density.reshape(len(_x), len(_y))
|
|
106
110
|
data = contour_lines(X, Y, Z, params["levels"])
|
|
107
111
|
# Each piece should have a distinct group
|
|
108
112
|
groups = str(group) + "-00" + data["piece"].astype(str)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, cast
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
4
|
import pandas as pd
|
|
3
5
|
|
|
@@ -6,6 +8,9 @@ from ..mapping.evaluation import after_stat
|
|
|
6
8
|
from .density import get_var_type, kde
|
|
7
9
|
from .stat import stat
|
|
8
10
|
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from plotnine.typing import FloatArray
|
|
13
|
+
|
|
9
14
|
|
|
10
15
|
@document
|
|
11
16
|
class stat_pointdensity(stat):
|
|
@@ -67,8 +72,10 @@ class stat_pointdensity(stat):
|
|
|
67
72
|
def compute_group(self, data, scales):
|
|
68
73
|
package = self.params["package"]
|
|
69
74
|
kde_params = self.params["kde_params"]
|
|
75
|
+
x = cast("FloatArray", data["x"].to_numpy())
|
|
76
|
+
y = cast("FloatArray", data["y"].to_numpy())
|
|
70
77
|
|
|
71
|
-
var_data = np.array([
|
|
78
|
+
var_data = np.array([x, y]).T
|
|
72
79
|
density = kde(var_data, var_data, package, **kde_params)
|
|
73
80
|
|
|
74
81
|
data = pd.DataFrame(
|
plotnine/stats/stat_qq.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING, cast
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import pandas as pd
|
|
@@ -11,9 +11,9 @@ from ..mapping.evaluation import after_stat
|
|
|
11
11
|
from .stat import stat
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
|
-
from typing import Any
|
|
14
|
+
from typing import Any
|
|
15
15
|
|
|
16
|
-
from plotnine.typing import FloatArray
|
|
16
|
+
from plotnine.typing import FloatArray, FloatArrayLike
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
# Note: distribution should be a name from scipy.stat.distribution
|
|
@@ -76,7 +76,7 @@ class stat_qq(stat):
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
def compute_group(self, data, scales):
|
|
79
|
-
sample = data["sample"].sort_values().to_numpy()
|
|
79
|
+
sample = cast("FloatArray", data["sample"].sort_values().to_numpy())
|
|
80
80
|
theoretical = theoretical_qq(
|
|
81
81
|
sample,
|
|
82
82
|
self.params["distribution"],
|
|
@@ -93,7 +93,7 @@ def theoretical_qq(
|
|
|
93
93
|
distribution: str,
|
|
94
94
|
alpha: float,
|
|
95
95
|
beta: float,
|
|
96
|
-
quantiles:
|
|
96
|
+
quantiles: FloatArrayLike | None,
|
|
97
97
|
distribution_params: dict[str, Any],
|
|
98
98
|
) -> FloatArray:
|
|
99
99
|
"""
|
plotnine/stats/stat_qq_line.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, cast
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
4
|
import pandas as pd
|
|
3
5
|
|
|
@@ -6,6 +8,9 @@ from ..exceptions import PlotnineError
|
|
|
6
8
|
from .stat import stat
|
|
7
9
|
from .stat_qq import theoretical_qq
|
|
8
10
|
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from plotnine.typing import FloatArray
|
|
13
|
+
|
|
9
14
|
|
|
10
15
|
@document
|
|
11
16
|
class stat_qq_line(stat):
|
|
@@ -75,7 +80,7 @@ class stat_qq_line(stat):
|
|
|
75
80
|
dparams = self.params["dparams"]
|
|
76
81
|
|
|
77
82
|
# Compute theoretical values
|
|
78
|
-
sample = data["sample"].sort_values().to_numpy()
|
|
83
|
+
sample = cast("FloatArray", data["sample"].sort_values().to_numpy())
|
|
79
84
|
theoretical = theoretical_qq(
|
|
80
85
|
sample,
|
|
81
86
|
self.params["distribution"],
|
plotnine/stats/stat_sina.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, cast
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
4
|
import pandas as pd
|
|
3
5
|
|
|
4
|
-
from .._utils import array_kind, jitter, resolution
|
|
6
|
+
from .._utils import array_kind, jitter, nextafter_range, resolution
|
|
5
7
|
from ..doctools import document
|
|
6
8
|
from ..exceptions import PlotnineError
|
|
7
9
|
from ..mapping.aes import has_groups
|
|
@@ -9,6 +11,9 @@ from .binning import breaks_from_bins, breaks_from_binwidth
|
|
|
9
11
|
from .stat import stat
|
|
10
12
|
from .stat_density import compute_density
|
|
11
13
|
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from plotnine.typing import FloatArray, IntArray
|
|
16
|
+
|
|
12
17
|
|
|
13
18
|
@document
|
|
14
19
|
class stat_sina(stat):
|
|
@@ -142,17 +147,6 @@ class stat_sina(stat):
|
|
|
142
147
|
params = self.params
|
|
143
148
|
maxwidth = params["maxwidth"]
|
|
144
149
|
random_state = params["random_state"]
|
|
145
|
-
fuzz = 1e-8
|
|
146
|
-
y_dim = scales.y.dimension()
|
|
147
|
-
y_dim_fuzzed = (y_dim[0] - fuzz, y_dim[1] + fuzz)
|
|
148
|
-
|
|
149
|
-
if params["binwidth"] is not None:
|
|
150
|
-
params["bins"] = breaks_from_binwidth(
|
|
151
|
-
y_dim_fuzzed, params["binwidth"]
|
|
152
|
-
)
|
|
153
|
-
else:
|
|
154
|
-
params["bins"] = breaks_from_bins(y_dim_fuzzed, params["bins"])
|
|
155
|
-
|
|
156
150
|
data = super().compute_panel(data, scales)
|
|
157
151
|
|
|
158
152
|
if not len(data):
|
|
@@ -198,8 +192,8 @@ class stat_sina(stat):
|
|
|
198
192
|
return data
|
|
199
193
|
|
|
200
194
|
def compute_group(self, data, scales):
|
|
195
|
+
binwidth = self.params["binwidth"]
|
|
201
196
|
maxwidth = self.params["maxwidth"]
|
|
202
|
-
bins = self.params["bins"]
|
|
203
197
|
bin_limit = self.params["bin_limit"]
|
|
204
198
|
weight = None
|
|
205
199
|
y = data["y"]
|
|
@@ -228,8 +222,14 @@ class stat_sina(stat):
|
|
|
228
222
|
data["density"] = densf(y)
|
|
229
223
|
data["scaled"] = data["density"] / dens["density"].max()
|
|
230
224
|
else:
|
|
225
|
+
expanded_y_range = nextafter_range(scales.y.dimension())
|
|
226
|
+
if binwidth is not None:
|
|
227
|
+
bins = breaks_from_binwidth(expanded_y_range, binwidth)
|
|
228
|
+
else:
|
|
229
|
+
bins = breaks_from_bins(expanded_y_range, self.params["bins"])
|
|
230
|
+
|
|
231
231
|
# bin based estimation
|
|
232
|
-
bin_index = pd.cut(y, bins, include_lowest=True, labels=False)
|
|
232
|
+
bin_index = pd.cut(y, bins, include_lowest=True, labels=False) # pyright: ignore[reportCallIssue,reportArgumentType]
|
|
233
233
|
data["density"] = (
|
|
234
234
|
pd.Series(bin_index)
|
|
235
235
|
.groupby(bin_index)
|
|
@@ -254,19 +254,18 @@ class stat_sina(stat):
|
|
|
254
254
|
def finish_layer(self, data):
|
|
255
255
|
# Rescale x in case positions have been adjusted
|
|
256
256
|
style = self.params["style"]
|
|
257
|
-
x_mean = data["x"].to_numpy()
|
|
257
|
+
x_mean = cast("FloatArray", data["x"].to_numpy())
|
|
258
258
|
x_mod = (data["xmax"] - data["xmin"]) / data["width"]
|
|
259
259
|
data["x"] = data["x"] + data["x_diff"] * x_mod
|
|
260
|
-
|
|
261
|
-
|
|
260
|
+
group = cast("IntArray", data["group"].to_numpy())
|
|
261
|
+
x = cast("FloatArray", data["x"].to_numpy())
|
|
262
|
+
even = group % 2 == 0
|
|
262
263
|
|
|
263
264
|
def mirror_x(bool_idx):
|
|
264
265
|
"""
|
|
265
266
|
Mirror x locations along the mean value
|
|
266
267
|
"""
|
|
267
|
-
data.loc[bool_idx, "x"] =
|
|
268
|
-
2 * x_mean[bool_idx] - data.loc[bool_idx, "x"]
|
|
269
|
-
)
|
|
268
|
+
data.loc[bool_idx, "x"] = 2 * x_mean[bool_idx] - x[bool_idx]
|
|
270
269
|
|
|
271
270
|
match style:
|
|
272
271
|
case "left":
|
plotnine/stats/stat_summary.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import cast
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
4
|
import pandas as pd
|
|
3
5
|
|
|
@@ -314,8 +316,8 @@ class stat_summary(stat):
|
|
|
314
316
|
summaries = []
|
|
315
317
|
for (group, x), df in data.groupby(["group", "x"]):
|
|
316
318
|
summary = func(df)
|
|
317
|
-
summary["x"] = x
|
|
318
|
-
summary["group"] = group
|
|
319
|
+
summary["x"] = x # pyright: ignore[reportCallIssue,reportArgumentType]
|
|
320
|
+
summary["group"] = cast("int", group)
|
|
319
321
|
summary["n"] = len(df)
|
|
320
322
|
unique = uniquecols(df)
|
|
321
323
|
if "y" in unique:
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, cast
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
4
|
import pandas as pd
|
|
3
5
|
|
|
@@ -9,6 +11,9 @@ from .binning import fuzzybreaks
|
|
|
9
11
|
from .stat import stat
|
|
10
12
|
from .stat_summary import make_summary_fun
|
|
11
13
|
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from plotnine.typing import IntArray
|
|
16
|
+
|
|
12
17
|
|
|
13
18
|
@document
|
|
14
19
|
class stat_summary_bin(stat):
|
|
@@ -157,7 +162,8 @@ class stat_summary_bin(stat):
|
|
|
157
162
|
# This is a plyr::ddply
|
|
158
163
|
out = groupby_apply(data, "bin", func_wrapper)
|
|
159
164
|
centers = (breaks[:-1] + breaks[1:]) * 0.5
|
|
160
|
-
|
|
165
|
+
bin = cast("IntArray", out["bin"].to_numpy())
|
|
166
|
+
bin_centers = centers[bin]
|
|
161
167
|
out["x"] = bin_centers
|
|
162
168
|
out["bin"] += 1
|
|
163
169
|
if isinstance(scales.x, scale_discrete):
|
|
@@ -37,6 +37,7 @@ class element_line(element_base):
|
|
|
37
37
|
*,
|
|
38
38
|
color: (
|
|
39
39
|
str
|
|
40
|
+
| Sequence[str]
|
|
40
41
|
| tuple[float, float, float]
|
|
41
42
|
| tuple[float, float, float, float]
|
|
42
43
|
| None
|
|
@@ -46,6 +47,7 @@ class element_line(element_base):
|
|
|
46
47
|
lineend: Literal["butt", "projecting", "round"] | None = None,
|
|
47
48
|
colour: (
|
|
48
49
|
str
|
|
50
|
+
| Sequence[str]
|
|
49
51
|
| tuple[float, float, float]
|
|
50
52
|
| tuple[float, float, float, float]
|
|
51
53
|
| None
|
|
@@ -86,10 +86,21 @@ class element_text(element_base):
|
|
|
86
86
|
| None
|
|
87
87
|
) = None,
|
|
88
88
|
size: float | Sequence[float] | None = None,
|
|
89
|
-
ha:
|
|
89
|
+
ha: (
|
|
90
|
+
Literal["center", "left", "right"]
|
|
91
|
+
| float
|
|
92
|
+
| Sequence[Literal["center", "left", "right"] | float]
|
|
93
|
+
| None
|
|
94
|
+
) = None,
|
|
90
95
|
va: (
|
|
91
96
|
Literal["center", "top", "bottom", "baseline", "center_baseline"]
|
|
92
97
|
| float
|
|
98
|
+
| Sequence[
|
|
99
|
+
Literal[
|
|
100
|
+
"center", "top", "bottom", "baseline", "center_baseline"
|
|
101
|
+
]
|
|
102
|
+
| float
|
|
103
|
+
]
|
|
93
104
|
| None
|
|
94
105
|
) = None,
|
|
95
106
|
ma: Literal["center", "left", "right"] | float | None = None,
|
plotnine/themes/theme.py
CHANGED
|
@@ -295,7 +295,13 @@ class theme:
|
|
|
295
295
|
for th in self.T.values():
|
|
296
296
|
th.apply(self)
|
|
297
297
|
|
|
298
|
-
def
|
|
298
|
+
def _setup(
|
|
299
|
+
self,
|
|
300
|
+
figure: Figure,
|
|
301
|
+
axs: list[Axes] | None = None,
|
|
302
|
+
title: str | None = None,
|
|
303
|
+
subtitle: str | None = None,
|
|
304
|
+
):
|
|
299
305
|
"""
|
|
300
306
|
Setup theme for applying
|
|
301
307
|
|
|
@@ -306,24 +312,14 @@ class theme:
|
|
|
306
312
|
|
|
307
313
|
It also initialises where the artists to be themed will be stored.
|
|
308
314
|
"""
|
|
309
|
-
self.
|
|
310
|
-
self.
|
|
311
|
-
self.axs = plot.axs
|
|
312
|
-
self.targets = ThemeTargets()
|
|
313
|
-
self._add_default_themeable_properties()
|
|
314
|
-
self.T.setup(self)
|
|
315
|
-
|
|
316
|
-
def _add_default_themeable_properties(self):
|
|
317
|
-
"""
|
|
318
|
-
Add default themeable properties that depend depend on the plot
|
|
315
|
+
self.figure = figure
|
|
316
|
+
self.axs = axs if axs is not None else []
|
|
319
317
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
before the themeables are applied.
|
|
318
|
+
if title or subtitle:
|
|
319
|
+
self._smart_title_and_subtitle_ha(title, subtitle)
|
|
323
320
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
self._smart_title_and_subtitle_ha()
|
|
321
|
+
self.targets = ThemeTargets()
|
|
322
|
+
self.T.setup(self)
|
|
327
323
|
|
|
328
324
|
@property
|
|
329
325
|
def rcParams(self):
|
|
@@ -466,18 +462,16 @@ class theme:
|
|
|
466
462
|
dpi = self.getp("dpi")
|
|
467
463
|
return self + theme(dpi=dpi * 2)
|
|
468
464
|
|
|
469
|
-
def _smart_title_and_subtitle_ha(
|
|
465
|
+
def _smart_title_and_subtitle_ha(
|
|
466
|
+
self, title: str | None, subtitle: str | None
|
|
467
|
+
):
|
|
470
468
|
"""
|
|
471
469
|
Smartly add the horizontal alignment for the title and subtitle
|
|
472
470
|
"""
|
|
473
471
|
from .elements import element_text
|
|
474
472
|
|
|
475
|
-
has_title = bool(
|
|
476
|
-
|
|
477
|
-
) and not self.T.is_blank("plot_title")
|
|
478
|
-
has_subtitle = bool(
|
|
479
|
-
self.plot.labels.get("subtitle", "")
|
|
480
|
-
) and not self.T.is_blank("plot_subtitle")
|
|
473
|
+
has_title = bool(title) and not self.T.is_blank("plot_title")
|
|
474
|
+
has_subtitle = bool(subtitle) and not self.T.is_blank("plot_subtitle")
|
|
481
475
|
|
|
482
476
|
title_ha = self.getp(("plot_title", "ha"))
|
|
483
477
|
subtitle_ha = self.getp(("plot_subtitle", "ha"))
|
plotnine/themes/themeable.py
CHANGED
|
@@ -2368,9 +2368,23 @@ class legend_text_position(themeable):
|
|
|
2368
2368
|
|
|
2369
2369
|
Parameters
|
|
2370
2370
|
----------
|
|
2371
|
-
theme_element : Literal["top", "bottom", "left", "right"] |
|
|
2372
|
-
|
|
2373
|
-
|
|
2371
|
+
theme_element : Literal["top", "bottom", "left", "right"] | \
|
|
2372
|
+
Sequence[Literal["top", "bottom"]] | \
|
|
2373
|
+
Sequence[Literal["left", "right"]] | \
|
|
2374
|
+
Literal["top-bottom", "bottom-top"] | \
|
|
2375
|
+
Literal["left-right", "right-left"] | \
|
|
2376
|
+
None
|
|
2377
|
+
Position of the legend key text.
|
|
2378
|
+
It must be compatible with the position of the legend e.g.
|
|
2379
|
+
when the legend is at the top or bottom, text can only be top
|
|
2380
|
+
or bottom as well.
|
|
2381
|
+
The default depends on the position of the legend.
|
|
2382
|
+
Use a sequence to specify the position of each text, or
|
|
2383
|
+
hyphenated values like `"left-right"` to alternate the position.
|
|
2384
|
+
|
|
2385
|
+
Notes
|
|
2386
|
+
-----
|
|
2387
|
+
Sequences and alternation only works well for colorbars.
|
|
2374
2388
|
"""
|
|
2375
2389
|
|
|
2376
2390
|
|
plotnine/typing.py
CHANGED
|
@@ -122,8 +122,13 @@ GuideKind: TypeAlias = Literal["legend", "colorbar", "colourbar"]
|
|
|
122
122
|
NoGuide: TypeAlias = Literal["none", False]
|
|
123
123
|
VerticalJustification: TypeAlias = Literal["bottom", "center", "top"]
|
|
124
124
|
HorizontalJustification: TypeAlias = Literal["left", "center", "right"]
|
|
125
|
+
Justification: TypeAlias = HorizontalJustification | VerticalJustification
|
|
126
|
+
HorizontalTextJustification: TypeAlias = HorizontalJustification
|
|
127
|
+
VerticalTextJustification: TypeAlias = (
|
|
128
|
+
VerticalJustification | Literal["baseline", "center_baseline"]
|
|
129
|
+
)
|
|
125
130
|
TextJustification: TypeAlias = (
|
|
126
|
-
|
|
131
|
+
HorizontalTextJustification | VerticalTextJustification
|
|
127
132
|
)
|
|
128
133
|
|
|
129
134
|
# Type Variables
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plotnine
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.0a1
|
|
4
4
|
Summary: A Grammar of Graphics for Python
|
|
5
5
|
Author-email: Hassan Kibirige <has2k1@gmail.com>
|
|
6
6
|
License: The MIT License (MIT)
|
|
@@ -49,7 +49,7 @@ Requires-Dist: pandas>=2.2.0
|
|
|
49
49
|
Requires-Dist: mizani~=0.14.0
|
|
50
50
|
Requires-Dist: numpy>=1.23.5
|
|
51
51
|
Requires-Dist: scipy>=1.8.0
|
|
52
|
-
Requires-Dist: statsmodels>=0.14.
|
|
52
|
+
Requires-Dist: statsmodels>=0.14.5
|
|
53
53
|
Provides-Extra: all
|
|
54
54
|
Requires-Dist: plotnine[extra]; extra == "all"
|
|
55
55
|
Requires-Dist: plotnine[doc]; extra == "all"
|
|
@@ -82,7 +82,7 @@ Requires-Dist: twine; extra == "dev"
|
|
|
82
82
|
Requires-Dist: plotnine[typing]; extra == "dev"
|
|
83
83
|
Requires-Dist: pre-commit; extra == "dev"
|
|
84
84
|
Provides-Extra: typing
|
|
85
|
-
Requires-Dist: pyright==1.1.
|
|
85
|
+
Requires-Dist: pyright==1.1.408; extra == "typing"
|
|
86
86
|
Requires-Dist: ipython; extra == "typing"
|
|
87
87
|
Requires-Dist: pandas-stubs; extra == "typing"
|
|
88
88
|
Dynamic: license-file
|