plotnine 0.15.0a3__py3-none-any.whl → 0.15.0a5__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/_utils/__init__.py +5 -103
- plotnine/doctools.py +3 -3
- plotnine/geoms/annotate.py +10 -10
- plotnine/geoms/geom.py +41 -20
- plotnine/layer.py +2 -0
- plotnine/mapping/_atomic.py +178 -0
- plotnine/mapping/aes.py +75 -45
- plotnine/scales/scale_continuous.py +1 -1
- plotnine/scales/scale_discrete.py +1 -1
- plotnine/stats/stat_density_2d.py +1 -1
- {plotnine-0.15.0a3.dist-info → plotnine-0.15.0a5.dist-info}/METADATA +2 -2
- {plotnine-0.15.0a3.dist-info → plotnine-0.15.0a5.dist-info}/RECORD +15 -14
- {plotnine-0.15.0a3.dist-info → plotnine-0.15.0a5.dist-info}/WHEEL +0 -0
- {plotnine-0.15.0a3.dist-info → plotnine-0.15.0a5.dist-info}/licenses/LICENSE +0 -0
- {plotnine-0.15.0a3.dist-info → plotnine-0.15.0a5.dist-info}/top_level.txt +0 -0
plotnine/_utils/__init__.py
CHANGED
|
@@ -12,9 +12,10 @@ from collections.abc import Iterable, Sequence
|
|
|
12
12
|
from contextlib import suppress
|
|
13
13
|
from copy import deepcopy
|
|
14
14
|
from dataclasses import field
|
|
15
|
-
from typing import TYPE_CHECKING
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
16
|
from warnings import warn
|
|
17
17
|
|
|
18
|
+
import mizani._colors.utils as color_utils
|
|
18
19
|
import numpy as np
|
|
19
20
|
import pandas as pd
|
|
20
21
|
from pandas.core.groupby import DataFrameGroupBy
|
|
@@ -26,12 +27,10 @@ if TYPE_CHECKING:
|
|
|
26
27
|
from typing import Any, Callable, Literal, TypeVar
|
|
27
28
|
|
|
28
29
|
import numpy.typing as npt
|
|
29
|
-
from matplotlib.typing import ColorType
|
|
30
30
|
from typing_extensions import TypeGuard
|
|
31
31
|
|
|
32
32
|
from plotnine.typing import (
|
|
33
33
|
AnyArrayLike,
|
|
34
|
-
AnySeries,
|
|
35
34
|
DataLike,
|
|
36
35
|
FloatArray,
|
|
37
36
|
FloatArrayLike,
|
|
@@ -60,6 +59,8 @@ BOX_LOCATIONS: dict[str, tuple[float, float]] = {
|
|
|
60
59
|
"centre": (0.5, 0.5),
|
|
61
60
|
}
|
|
62
61
|
|
|
62
|
+
to_rgba = color_utils.to_rgba
|
|
63
|
+
|
|
63
64
|
|
|
64
65
|
def is_scalar(val):
|
|
65
66
|
"""
|
|
@@ -361,7 +362,7 @@ def _id_var(x: AnyArrayLike, drop: bool = False) -> list[int]:
|
|
|
361
362
|
lst = match(x, levels)
|
|
362
363
|
lst = [item + 1 for item in lst]
|
|
363
364
|
|
|
364
|
-
return lst
|
|
365
|
+
return lst
|
|
365
366
|
|
|
366
367
|
|
|
367
368
|
def join_keys(x, y, by=None):
|
|
@@ -530,105 +531,6 @@ def remove_missing(
|
|
|
530
531
|
return data
|
|
531
532
|
|
|
532
533
|
|
|
533
|
-
@overload
|
|
534
|
-
def to_rgba(colors: ColorType, alpha: float) -> ColorType: ...
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
@overload
|
|
538
|
-
def to_rgba(
|
|
539
|
-
colors: Sequence[ColorType], alpha: float
|
|
540
|
-
) -> Sequence[ColorType] | ColorType: ...
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
@overload
|
|
544
|
-
def to_rgba(
|
|
545
|
-
colors: AnySeries, alpha: AnySeries
|
|
546
|
-
) -> Sequence[ColorType] | ColorType: ...
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
def to_rgba(
|
|
550
|
-
colors: Sequence[ColorType] | AnySeries | ColorType,
|
|
551
|
-
alpha: float | Sequence[float] | AnySeries,
|
|
552
|
-
) -> Sequence[ColorType] | ColorType:
|
|
553
|
-
"""
|
|
554
|
-
Convert hex colors to rgba values.
|
|
555
|
-
|
|
556
|
-
Parameters
|
|
557
|
-
----------
|
|
558
|
-
colors : iterable | str
|
|
559
|
-
colors to convert
|
|
560
|
-
alphas : iterable | float
|
|
561
|
-
alpha values
|
|
562
|
-
|
|
563
|
-
Returns
|
|
564
|
-
-------
|
|
565
|
-
out : ndarray | tuple
|
|
566
|
-
rgba color(s)
|
|
567
|
-
|
|
568
|
-
Notes
|
|
569
|
-
-----
|
|
570
|
-
Matplotlib plotting functions only accept scalar
|
|
571
|
-
alpha values. Hence no two objects with different
|
|
572
|
-
alpha values may be plotted in one call. This would
|
|
573
|
-
make plots with continuous alpha values innefficient.
|
|
574
|
-
However :), the colors can be rgba hex values or
|
|
575
|
-
list-likes and the alpha dimension will be respected.
|
|
576
|
-
"""
|
|
577
|
-
|
|
578
|
-
def is_iterable(var):
|
|
579
|
-
return np.iterable(var) and not isinstance(var, str)
|
|
580
|
-
|
|
581
|
-
def has_alpha(c):
|
|
582
|
-
return (isinstance(c, tuple) and len(c) == 4) or (
|
|
583
|
-
isinstance(c, str) and len(c) == 9 and c[0] == "#"
|
|
584
|
-
)
|
|
585
|
-
|
|
586
|
-
def no_color(c):
|
|
587
|
-
return c is None or c.lower() in ("none", "")
|
|
588
|
-
|
|
589
|
-
def to_rgba_hex(c: ColorType, a: float) -> str:
|
|
590
|
-
"""
|
|
591
|
-
Convert rgb color to rgba hex value
|
|
592
|
-
|
|
593
|
-
If color c has an alpha channel, then alpha value
|
|
594
|
-
a is ignored
|
|
595
|
-
"""
|
|
596
|
-
from matplotlib.colors import colorConverter, to_hex
|
|
597
|
-
|
|
598
|
-
if c in ("None", "none"):
|
|
599
|
-
return c
|
|
600
|
-
|
|
601
|
-
_has_alpha = has_alpha(c)
|
|
602
|
-
c = to_hex(c, keep_alpha=_has_alpha)
|
|
603
|
-
|
|
604
|
-
if not _has_alpha:
|
|
605
|
-
arr = colorConverter.to_rgba(c, a)
|
|
606
|
-
return to_hex(arr, keep_alpha=True)
|
|
607
|
-
|
|
608
|
-
return c
|
|
609
|
-
|
|
610
|
-
if is_iterable(colors):
|
|
611
|
-
colors = cast("Sequence[ColorType]", colors)
|
|
612
|
-
|
|
613
|
-
if all(no_color(c) for c in colors):
|
|
614
|
-
return "none"
|
|
615
|
-
|
|
616
|
-
if isinstance(alpha, (Sequence, pd.Series)):
|
|
617
|
-
return [to_rgba_hex(c, a) for c, a in zip(colors, alpha)]
|
|
618
|
-
else:
|
|
619
|
-
return [to_rgba_hex(c, alpha) for c in colors]
|
|
620
|
-
else:
|
|
621
|
-
colors = cast("ColorType", colors)
|
|
622
|
-
|
|
623
|
-
if no_color(colors):
|
|
624
|
-
return colors
|
|
625
|
-
|
|
626
|
-
if isinstance(alpha, (Sequence, pd.Series)):
|
|
627
|
-
return [to_rgba_hex(colors, a) for a in alpha]
|
|
628
|
-
else:
|
|
629
|
-
return to_rgba_hex(colors, alpha)
|
|
630
|
-
|
|
631
|
-
|
|
632
534
|
def groupby_apply(
|
|
633
535
|
df: pd.DataFrame,
|
|
634
536
|
cols: str | list[str],
|
plotnine/doctools.py
CHANGED
|
@@ -71,7 +71,7 @@ STAT_SIGNATURE_TPL = """
|
|
|
71
71
|
|
|
72
72
|
common_params_doc = {
|
|
73
73
|
"mapping": """\
|
|
74
|
-
Aesthetic mappings created with [aes](:class:`plotnine.mapping.aes`). If \
|
|
74
|
+
Aesthetic mappings created with [aes](:class:`plotnine.mapping.aes.aes`). If \
|
|
75
75
|
specified and `inherit_aes=True`{.py}, it is combined with the default \
|
|
76
76
|
mapping for the plot. You must supply mapping if there is no plot mapping.""",
|
|
77
77
|
"data": """\
|
|
@@ -103,7 +103,7 @@ the final image is in vector format.""",
|
|
|
103
103
|
|
|
104
104
|
|
|
105
105
|
GEOM_PARAMS_TPL = """
|
|
106
|
-
mapping : ~plotnine.mapping.aes, default=None
|
|
106
|
+
mapping : ~plotnine.mapping.aes.aes, default=None
|
|
107
107
|
{mapping}
|
|
108
108
|
{_aesthetics_doc}
|
|
109
109
|
data : ~pandas.DataFrame, default=None
|
|
@@ -124,7 +124,7 @@ raster : bool, default={default_raster}
|
|
|
124
124
|
"""
|
|
125
125
|
|
|
126
126
|
STAT_PARAMS_TPL = """
|
|
127
|
-
mapping : ~plotnine.mapping.aes, default=None
|
|
127
|
+
mapping : ~plotnine.mapping.aes.aes, default=None
|
|
128
128
|
{mapping}
|
|
129
129
|
{_aesthetics_doc}
|
|
130
130
|
data : ~pandas.DataFrame, default=None
|
plotnine/geoms/annotate.py
CHANGED
|
@@ -64,16 +64,16 @@ class annotate:
|
|
|
64
64
|
def __init__(
|
|
65
65
|
self,
|
|
66
66
|
geom: str | type[geom_base_class],
|
|
67
|
-
x: float | None = None,
|
|
68
|
-
y: float | None = None,
|
|
69
|
-
xmin: float | None = None,
|
|
70
|
-
xmax: float | None = None,
|
|
71
|
-
xend: float | None = None,
|
|
72
|
-
xintercept: float | None = None,
|
|
73
|
-
ymin: float | None = None,
|
|
74
|
-
ymax: float | None = None,
|
|
75
|
-
yend: float | None = None,
|
|
76
|
-
yintercept: float | None = None,
|
|
67
|
+
x: float | list[float] | None = None,
|
|
68
|
+
y: float | list[float] | None = None,
|
|
69
|
+
xmin: float | list[float] | None = None,
|
|
70
|
+
xmax: float | list[float] | None = None,
|
|
71
|
+
xend: float | list[float] | None = None,
|
|
72
|
+
xintercept: float | list[float] | None = None,
|
|
73
|
+
ymin: float | list[float] | None = None,
|
|
74
|
+
ymax: float | list[float] | None = None,
|
|
75
|
+
yend: float | list[float] | None = None,
|
|
76
|
+
yintercept: float | list[float] | None = None,
|
|
77
77
|
**kwargs: Any,
|
|
78
78
|
):
|
|
79
79
|
variables = locals()
|
plotnine/geoms/geom.py
CHANGED
|
@@ -2,18 +2,20 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import typing
|
|
4
4
|
from abc import ABC
|
|
5
|
+
from contextlib import suppress
|
|
5
6
|
from copy import deepcopy
|
|
6
7
|
from itertools import chain, repeat
|
|
7
8
|
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
8
11
|
from .._utils import (
|
|
9
12
|
data_mapping_as_kwargs,
|
|
10
|
-
is_list_like,
|
|
11
13
|
remove_missing,
|
|
12
14
|
)
|
|
13
15
|
from .._utils.registry import Register, Registry
|
|
14
16
|
from ..exceptions import PlotnineError
|
|
15
17
|
from ..layer import layer
|
|
16
|
-
from ..mapping.aes import
|
|
18
|
+
from ..mapping.aes import rename_aesthetics
|
|
17
19
|
from ..mapping.evaluation import evaluate
|
|
18
20
|
from ..positions.position import position
|
|
19
21
|
from ..stats.stat import stat
|
|
@@ -179,11 +181,16 @@ class geom(ABC, metaclass=Register):
|
|
|
179
181
|
----------
|
|
180
182
|
data :
|
|
181
183
|
Data
|
|
184
|
+
"""
|
|
182
185
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
186
|
+
def setup_aes_params(self, data: pd.DataFrame):
|
|
187
|
+
"""
|
|
188
|
+
Override this method to verify and/or adjust aesthetic parameters
|
|
189
|
+
|
|
190
|
+
Parameters
|
|
191
|
+
----------
|
|
192
|
+
data :
|
|
193
|
+
Data
|
|
187
194
|
"""
|
|
188
195
|
|
|
189
196
|
def setup_data(self, data: pd.DataFrame) -> pd.DataFrame:
|
|
@@ -239,6 +246,9 @@ class geom(ABC, metaclass=Register):
|
|
|
239
246
|
:
|
|
240
247
|
Data used for drawing the geom.
|
|
241
248
|
"""
|
|
249
|
+
from plotnine.mapping import _atomic as atomic
|
|
250
|
+
from plotnine.mapping._atomic import ae_value
|
|
251
|
+
|
|
242
252
|
missing_aes = (
|
|
243
253
|
self.DEFAULT_AES.keys()
|
|
244
254
|
- self.aes_params.keys()
|
|
@@ -255,23 +265,34 @@ class geom(ABC, metaclass=Register):
|
|
|
255
265
|
data[ae] = evaled[ae]
|
|
256
266
|
|
|
257
267
|
num_panels = len(data["PANEL"].unique()) if "PANEL" in data else 1
|
|
268
|
+
across_panels = num_panels > 1 and not self.params["inherit_aes"]
|
|
258
269
|
|
|
259
|
-
# Aesthetics set as parameters
|
|
270
|
+
# Aesthetics set as parameters in the geom/stat
|
|
260
271
|
for ae, value in self.aes_params.items():
|
|
261
|
-
|
|
272
|
+
if isinstance(value, (str, int, float, np.integer, np.floating)):
|
|
273
|
+
data[ae] = value
|
|
274
|
+
elif isinstance(value, ae_value):
|
|
275
|
+
data[ae] = value * len(data)
|
|
276
|
+
elif across_panels:
|
|
277
|
+
value = list(chain(*repeat(value, num_panels)))
|
|
262
278
|
data[ae] = value
|
|
263
|
-
|
|
264
|
-
#
|
|
265
|
-
#
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
279
|
+
else:
|
|
280
|
+
# Try to make sense of aesthetics whose values can be tuples
|
|
281
|
+
# or sequences of sorts.
|
|
282
|
+
ae_value_cls: type[ae_value] | None = getattr(atomic, ae, None)
|
|
283
|
+
if ae_value_cls:
|
|
284
|
+
with suppress(ValueError):
|
|
285
|
+
data[ae] = ae_value_cls(value) * len(data)
|
|
286
|
+
continue
|
|
287
|
+
|
|
288
|
+
# This should catch the aesthetic assignments to
|
|
289
|
+
# non-numeric or non-string values or sequence of values.
|
|
290
|
+
# e.g. x=datetime, x=Sequence[datetime],
|
|
291
|
+
# x=Sequence[float], shape=Sequence[str]
|
|
292
|
+
try:
|
|
293
|
+
data[ae] = value
|
|
294
|
+
except ValueError as e:
|
|
295
|
+
msg = f"'{ae}={value}' does not look like a valid value"
|
|
275
296
|
raise PlotnineError(msg) from e
|
|
276
297
|
|
|
277
298
|
return data
|
plotnine/layer.py
CHANGED
|
@@ -257,6 +257,7 @@ class layer:
|
|
|
257
257
|
"""
|
|
258
258
|
self.geom.params["zorder"] = self.zorder
|
|
259
259
|
self.geom.params["raster"] = self.raster
|
|
260
|
+
self.geom.params["inherit_aes"] = self.inherit_aes
|
|
260
261
|
|
|
261
262
|
def compute_aesthetics(self, plot: ggplot):
|
|
262
263
|
"""
|
|
@@ -330,6 +331,7 @@ class layer:
|
|
|
330
331
|
|
|
331
332
|
self.geom.params.update(self.stat.params)
|
|
332
333
|
self.geom.setup_params(data)
|
|
334
|
+
self.geom.setup_aes_params(data)
|
|
333
335
|
data = self.geom.setup_data(data)
|
|
334
336
|
|
|
335
337
|
check_required_aesthetics(
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from contextlib import suppress
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import (
|
|
6
|
+
Any,
|
|
7
|
+
Generic,
|
|
8
|
+
Literal,
|
|
9
|
+
Sequence,
|
|
10
|
+
TypeAlias,
|
|
11
|
+
TypeVar,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
from mizani._colors.utils import is_color_tuple
|
|
16
|
+
|
|
17
|
+
# NOTE:For now we shall use these class privately and not list them
|
|
18
|
+
# in documentation. We can't deal with assigning Sequence[ae_value]
|
|
19
|
+
# to an aesthetic.
|
|
20
|
+
|
|
21
|
+
__all__ = (
|
|
22
|
+
"linetype",
|
|
23
|
+
"color",
|
|
24
|
+
"colour",
|
|
25
|
+
"fill",
|
|
26
|
+
"shape",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
T = TypeVar("T")
|
|
30
|
+
|
|
31
|
+
ShapeType: TypeAlias = (
|
|
32
|
+
str | tuple[int, Literal[0, 1, 2], float] | Sequence[tuple[float, float]]
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class ae_value(Generic[T]):
|
|
38
|
+
"""
|
|
39
|
+
Atomic aesthetic value
|
|
40
|
+
|
|
41
|
+
The goal of this base class is simplify working with the more complex
|
|
42
|
+
aesthetic values. e.g. if a value is a tuple, we don't want it to be
|
|
43
|
+
seen as a sequence of values when assigning it to a dataframe column.
|
|
44
|
+
The subclasses should be able to recognise valid aesthetic values and
|
|
45
|
+
repeat (using multiplication) the value any number of times.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
value: T
|
|
49
|
+
|
|
50
|
+
def __mul__(self, n: int) -> Sequence[T]:
|
|
51
|
+
"""
|
|
52
|
+
Repeat value n times
|
|
53
|
+
"""
|
|
54
|
+
return [self.value] * n
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class linetype(ae_value[str | tuple]):
|
|
59
|
+
"""
|
|
60
|
+
A single linetype value
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __post_init__(self):
|
|
64
|
+
value = self.value
|
|
65
|
+
named = {
|
|
66
|
+
" ",
|
|
67
|
+
"",
|
|
68
|
+
"-",
|
|
69
|
+
"--",
|
|
70
|
+
"-.",
|
|
71
|
+
":",
|
|
72
|
+
"None",
|
|
73
|
+
"none",
|
|
74
|
+
"dashdot",
|
|
75
|
+
"dashed",
|
|
76
|
+
"dotted",
|
|
77
|
+
"solid",
|
|
78
|
+
}
|
|
79
|
+
if self.value in named:
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
# tuple of the form (offset, (on, off, on, off, ...))
|
|
83
|
+
# e.g (0, (1, 2))
|
|
84
|
+
if (
|
|
85
|
+
isinstance(value, tuple)
|
|
86
|
+
and isinstance(value[0], int)
|
|
87
|
+
and isinstance(value[1], tuple)
|
|
88
|
+
and len(value[1]) % 2 == 0
|
|
89
|
+
and all(isinstance(x, int) for x in value[1])
|
|
90
|
+
):
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
raise ValueError(f"{value} is not a known linetype.")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class color(ae_value[str | tuple]):
|
|
98
|
+
"""
|
|
99
|
+
A single color value
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def __post_init__(self):
|
|
103
|
+
if isinstance(self.value, str):
|
|
104
|
+
return
|
|
105
|
+
elif is_color_tuple(self.value):
|
|
106
|
+
self.value = tuple(self.value)
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
raise ValueError(f"{self.value} is not a known color.")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
colour = color
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass
|
|
116
|
+
class fill(color):
|
|
117
|
+
"""
|
|
118
|
+
A single color value
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass
|
|
123
|
+
class shape(ae_value[ShapeType]):
|
|
124
|
+
"""
|
|
125
|
+
A single shape value
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
def __post_init__(self):
|
|
129
|
+
from matplotlib.path import Path
|
|
130
|
+
|
|
131
|
+
from ..scales.scale_shape import FILLED_SHAPES, UNFILLED_SHAPES
|
|
132
|
+
|
|
133
|
+
value = self.value
|
|
134
|
+
|
|
135
|
+
with suppress(TypeError):
|
|
136
|
+
if value in (FILLED_SHAPES | UNFILLED_SHAPES):
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
if isinstance(value, Path):
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
# tuple of the form (numsides, style, angle)
|
|
143
|
+
# where style is in the range [0, 3]
|
|
144
|
+
# e.g (4, 1, 45)
|
|
145
|
+
if (
|
|
146
|
+
isinstance(value, tuple)
|
|
147
|
+
and len(value) == 3
|
|
148
|
+
and isinstance(value[0], int)
|
|
149
|
+
and value[1] in (0, 1, 2)
|
|
150
|
+
and isinstance(value[2], (float, int))
|
|
151
|
+
):
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
if is_shape_points(value):
|
|
155
|
+
self.value = tuple(value) # pyright: ignore[reportAttributeAccessIssue]
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
raise ValueError(f"{value} is not a known shape.")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def is_shape_points(obj: Any) -> bool:
|
|
162
|
+
"""
|
|
163
|
+
Return True if obj is like Sequence[tuple[float, float]]
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
def is_numeric(obj) -> bool:
|
|
167
|
+
"""
|
|
168
|
+
Return True if obj is a python or numpy float or integer
|
|
169
|
+
"""
|
|
170
|
+
return isinstance(obj, (float, int, np.floating, np.integer))
|
|
171
|
+
|
|
172
|
+
if not iter(obj):
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
return all(is_numeric(a) and is_numeric(b) for a, b in obj)
|
|
177
|
+
except (ValueError, TypeError):
|
|
178
|
+
return False
|
plotnine/mapping/aes.py
CHANGED
|
@@ -8,7 +8,9 @@ from dataclasses import fields
|
|
|
8
8
|
from functools import cached_property
|
|
9
9
|
from typing import TYPE_CHECKING, Any, Dict
|
|
10
10
|
|
|
11
|
+
import numpy as np
|
|
11
12
|
import pandas as pd
|
|
13
|
+
from mizani._colors.utils import is_color_tuple
|
|
12
14
|
|
|
13
15
|
from ..iapi import labels_view
|
|
14
16
|
from .evaluation import after_stat, stage
|
|
@@ -538,23 +540,23 @@ def make_labels(mapping: dict[str, Any] | aes) -> labels_view:
|
|
|
538
540
|
)
|
|
539
541
|
|
|
540
542
|
|
|
541
|
-
|
|
543
|
+
class RepeatAesthetic:
|
|
542
544
|
"""
|
|
543
|
-
|
|
545
|
+
Repeat an Aeshetic a given number of times
|
|
544
546
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
value :
|
|
548
|
-
Value to check
|
|
549
|
-
ae :
|
|
550
|
-
Aesthetic name
|
|
547
|
+
The methods in this class know how to create sequences of aesthetics
|
|
548
|
+
whose values may not be scalar.
|
|
551
549
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
valid.
|
|
550
|
+
Some aesthetics may have valid values that are not scalar. e.g.
|
|
551
|
+
sequences. Inserting one of such a value in a dataframe as a column
|
|
552
|
+
would either lead to the wrong input or fail. The s
|
|
556
553
|
"""
|
|
557
|
-
|
|
554
|
+
|
|
555
|
+
@staticmethod
|
|
556
|
+
def linetype(value: Any, n: int) -> Sequence[Any]:
|
|
557
|
+
"""
|
|
558
|
+
Repeat linetypes
|
|
559
|
+
"""
|
|
558
560
|
named = {
|
|
559
561
|
"solid",
|
|
560
562
|
"dashed",
|
|
@@ -569,47 +571,75 @@ def is_valid_aesthetic(value: Any, ae: str) -> bool:
|
|
|
569
571
|
"",
|
|
570
572
|
}
|
|
571
573
|
if value in named:
|
|
572
|
-
return
|
|
574
|
+
return [value] * n
|
|
573
575
|
|
|
574
576
|
# tuple of the form (offset, (on, off, on, off, ...))
|
|
575
577
|
# e.g (0, (1, 2))
|
|
576
|
-
|
|
577
|
-
isinstance(value, tuple)
|
|
578
|
-
isinstance(value[0], int)
|
|
579
|
-
isinstance(value[1], tuple)
|
|
580
|
-
len(value[1]) % 2 == 0
|
|
581
|
-
all(isinstance(x, int) for x in value[1])
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
578
|
+
if (
|
|
579
|
+
isinstance(value, tuple)
|
|
580
|
+
and isinstance(value[0], int)
|
|
581
|
+
and isinstance(value[1], tuple)
|
|
582
|
+
and len(value[1]) % 2 == 0
|
|
583
|
+
and all(isinstance(x, int) for x in value[1])
|
|
584
|
+
):
|
|
585
|
+
return [value] * n
|
|
586
|
+
|
|
587
|
+
raise ValueError(f"{value} is not a known linetype.")
|
|
588
|
+
|
|
589
|
+
@staticmethod
|
|
590
|
+
def color(value: Any, n: int) -> Sequence[Any]:
|
|
591
|
+
"""
|
|
592
|
+
Repeat colors
|
|
593
|
+
"""
|
|
586
594
|
if isinstance(value, str):
|
|
587
|
-
return
|
|
595
|
+
return [value] * n
|
|
596
|
+
if is_color_tuple(value):
|
|
597
|
+
return [tuple(value)] * n
|
|
598
|
+
|
|
599
|
+
raise ValueError(f"{value} is not a known color.")
|
|
600
|
+
|
|
601
|
+
fill = color
|
|
588
602
|
|
|
603
|
+
@staticmethod
|
|
604
|
+
def shape(value: Any, n: int) -> Any:
|
|
605
|
+
"""
|
|
606
|
+
Repeat shapes
|
|
607
|
+
"""
|
|
608
|
+
if isinstance(value, str):
|
|
609
|
+
return [value] * n
|
|
589
610
|
# tuple of the form (numsides, style, angle)
|
|
590
611
|
# where style is in the range [0, 3]
|
|
591
612
|
# e.g (4, 1, 45)
|
|
592
|
-
|
|
593
|
-
isinstance(value, tuple)
|
|
594
|
-
all(isinstance(x, int) for x in value)
|
|
595
|
-
0 <= value[1] < 3
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
if isinstance(value, (tuple, list)) and all(
|
|
604
|
-
0 <= x <= 1 for x in value
|
|
605
|
-
):
|
|
606
|
-
return True
|
|
607
|
-
return False
|
|
613
|
+
if (
|
|
614
|
+
isinstance(value, tuple)
|
|
615
|
+
and all(isinstance(x, int) for x in value)
|
|
616
|
+
and 0 <= value[1] < 3
|
|
617
|
+
):
|
|
618
|
+
return [value] * n
|
|
619
|
+
|
|
620
|
+
if is_shape_points(value):
|
|
621
|
+
return [tuple(value)] * n
|
|
622
|
+
|
|
623
|
+
raise ValueError(f"{value} is not a know shape.")
|
|
608
624
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
625
|
+
|
|
626
|
+
def is_shape_points(obj: Any) -> bool:
|
|
627
|
+
"""
|
|
628
|
+
Return True if obj is like Sequence[tuple[float, float]]
|
|
629
|
+
"""
|
|
630
|
+
|
|
631
|
+
def is_numeric(obj) -> bool:
|
|
632
|
+
"""
|
|
633
|
+
Return True if obj is a python or numpy float or integer
|
|
634
|
+
"""
|
|
635
|
+
return isinstance(obj, (float, int, np.floating, np.integer))
|
|
636
|
+
|
|
637
|
+
if not iter(obj):
|
|
638
|
+
return False
|
|
639
|
+
try:
|
|
640
|
+
return all(is_numeric(a) and is_numeric(b) for a, b in obj)
|
|
641
|
+
except TypeError:
|
|
642
|
+
return False
|
|
613
643
|
|
|
614
644
|
|
|
615
645
|
def has_groups(data: pd.DataFrame) -> bool:
|
|
@@ -396,7 +396,7 @@ class scale_continuous(
|
|
|
396
396
|
scaled = [na_value if x == "nan" else x for x in scaled]
|
|
397
397
|
else:
|
|
398
398
|
scaled[pd.isna(scaled)] = na_value
|
|
399
|
-
return scaled
|
|
399
|
+
return scaled
|
|
400
400
|
|
|
401
401
|
def get_breaks(
|
|
402
402
|
self, limits: Optional[tuple[float, float]] = None
|
|
@@ -206,7 +206,7 @@ class scale_discrete(
|
|
|
206
206
|
pal = np.asarray(pal, dtype=object)
|
|
207
207
|
idx = np.asarray(match(x, limits))
|
|
208
208
|
try:
|
|
209
|
-
pal_match = [pal[i] if i >= 0 else None for i in idx]
|
|
209
|
+
pal_match = [pal[i] if i >= 0 else None for i in idx]
|
|
210
210
|
except IndexError:
|
|
211
211
|
# Deal with missing data
|
|
212
212
|
# - Insert NaN where there is no match
|
|
@@ -155,7 +155,7 @@ def contour_lines(X, Y, Z, levels: int | FloatArrayLike):
|
|
|
155
155
|
level_values = []
|
|
156
156
|
start_pid = 1
|
|
157
157
|
for level in levels:
|
|
158
|
-
vertices, *_ = cgen.create_contour(level)
|
|
158
|
+
vertices, *_ = cgen.create_contour(level)
|
|
159
159
|
for pid, piece in enumerate(vertices, start=start_pid):
|
|
160
160
|
n = len(piece) # pyright: ignore
|
|
161
161
|
segments.append(piece)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plotnine
|
|
3
|
-
Version: 0.15.
|
|
3
|
+
Version: 0.15.0a5
|
|
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)
|
|
@@ -48,7 +48,7 @@ Requires-Dist: matplotlib>=3.8.0
|
|
|
48
48
|
Requires-Dist: pandas>=2.2.0
|
|
49
49
|
Requires-Dist: mizani~=0.14.0
|
|
50
50
|
Requires-Dist: numpy>=1.23.5
|
|
51
|
-
Requires-Dist: scipy
|
|
51
|
+
Requires-Dist: scipy<1.16.0,>=1.8.0
|
|
52
52
|
Requires-Dist: statsmodels>=0.14.0
|
|
53
53
|
Provides-Extra: all
|
|
54
54
|
Requires-Dist: plotnine[extra]; extra == "all"
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
plotnine/__init__.py,sha256=HrJhd65bnny1t-TawUgvApVj4p-gDZ0ftpr2NKZeW_s,10316
|
|
2
2
|
plotnine/animation.py,sha256=izJZ4Gy0cBHEBc8ehofsWSWOzZW8UEroy1Uvw86Igb0,7521
|
|
3
|
-
plotnine/doctools.py,sha256=
|
|
3
|
+
plotnine/doctools.py,sha256=JBF55q1MX2fXYQcGDpVrGPdlKf5OiQ5gyTdWhnM_IzU,14558
|
|
4
4
|
plotnine/exceptions.py,sha256=SgTxBHkV65HjGI3aFy2q1_lHP9HAdiuxVLN3U-PJWSQ,1616
|
|
5
5
|
plotnine/ggplot.py,sha256=xFj9iWAyBvnhitCrpgdNonQIqqjBQ2aDgkqpvHbH364,24823
|
|
6
6
|
plotnine/helpers.py,sha256=4R3KZmtGH46-kRNSGOA0JxZaLKBo0ge8Vnx1cDQ8_gI,966
|
|
7
7
|
plotnine/iapi.py,sha256=jNLmUSoh5g9kNdhOoXSqNcqOdd2-6xdWAmst-YGU41U,9095
|
|
8
8
|
plotnine/labels.py,sha256=3pOXth2Xma_qCqB_xXAGIkPQ9gcaUaaFEAsa5if1iR0,2830
|
|
9
|
-
plotnine/layer.py,sha256=
|
|
9
|
+
plotnine/layer.py,sha256=sUtzKTPnvkMuVFsNUFPkK9HUEcX7ohqv1EQV2KryS_c,16982
|
|
10
10
|
plotnine/options.py,sha256=j3zXv4wc3J4nOI_TqJ5s_abuifodt_UN8MR8M4i8UVA,3108
|
|
11
11
|
plotnine/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
plotnine/qplot.py,sha256=BSAb4u5I7PaPGofkAgx7brdCNTjMZvC_TDGHVUZ35xM,7384
|
|
@@ -25,7 +25,7 @@ plotnine/_mpl/layout_manager/_engine.py,sha256=ESUvbLAlZApFbBi6w7gZA7S4guS0Rmrj-
|
|
|
25
25
|
plotnine/_mpl/layout_manager/_layout_items.py,sha256=3XRBl7xEdBKdhrexRmnVe7k919po6nkyE0q5Hx7j5cQ,29642
|
|
26
26
|
plotnine/_mpl/layout_manager/_layout_tree.py,sha256=O6U78CYOjSwS4lt56YF3YiOCXdxmV_oy3lYS4gMmzSc,26447
|
|
27
27
|
plotnine/_mpl/layout_manager/_spaces.py,sha256=ahBpKt-q1kVOTGiGliwl_DNB6pTEekAzA_7-GXwFlBk,35789
|
|
28
|
-
plotnine/_utils/__init__.py,sha256=
|
|
28
|
+
plotnine/_utils/__init__.py,sha256=czHi-uv4eIBCDf6td11bGA41PMXaJ5j7t-avx_JbYgY,30636
|
|
29
29
|
plotnine/_utils/context.py,sha256=HPQy_uyNXdS0s9URD7ZePyuc5hFU2XrRBLDTqRDLJzY,1708
|
|
30
30
|
plotnine/_utils/dev.py,sha256=0qgRbMhcd4dfuLuYxx0skocKAtfwHF02ntyILRBogbg,1629
|
|
31
31
|
plotnine/_utils/ipython.py,sha256=5Obr73xJ-4dzJEdBrFA8z9TXuxY7pIjKmzdTzWwnxNk,1884
|
|
@@ -66,10 +66,10 @@ plotnine/facets/labelling.py,sha256=JEuwERTK0IfmxTWHbl2nsGgxZ6xi0n2TTWT4_OSfQcQ,
|
|
|
66
66
|
plotnine/facets/layout.py,sha256=TIkMChA0wJWLKN31PH0czS6CN4pw3o--PF49LakJ2h4,8967
|
|
67
67
|
plotnine/facets/strips.py,sha256=-SWFaxqdzn-dnjx_Hxkuwd3kO-u_yreMLcMurm_IHqU,5688
|
|
68
68
|
plotnine/geoms/__init__.py,sha256=HEfhNmmNH4xm4rpXnFRXY4eLkJha3XPM72IIwVjv5Lc,2697
|
|
69
|
-
plotnine/geoms/annotate.py,sha256=
|
|
69
|
+
plotnine/geoms/annotate.py,sha256=T5RxepV55HVNzPfkq43BWxduNIZPslRfPD1yx4bJtoo,4165
|
|
70
70
|
plotnine/geoms/annotation_logticks.py,sha256=6iGdo5szck0_nXdHnvMaRMZuRbH8Tg87tJ_aan_frqg,8969
|
|
71
71
|
plotnine/geoms/annotation_stripes.py,sha256=4Cw7TJ4SZChm_ioqfiiku0cPNnLruGuAP-4vyRao-9Y,6080
|
|
72
|
-
plotnine/geoms/geom.py,sha256=
|
|
72
|
+
plotnine/geoms/geom.py,sha256=ayhBEoPc-9MLpu18HkwLoby4NIKC68ED4Pq0ioa4I9c,17687
|
|
73
73
|
plotnine/geoms/geom_abline.py,sha256=6oxAJl_yFKKmf7OTHvACw6fg6kgJEN54hGKkyWOLr6o,3188
|
|
74
74
|
plotnine/geoms/geom_area.py,sha256=wvQ4nNvhJNN3nfn6Bv1gCARC6IWTjOjOfHPfSmg6Sxc,818
|
|
75
75
|
plotnine/geoms/geom_bar.py,sha256=SnqS4hPTfqXzdPh1U-kNuBg0LNX9_tQC9OKhIlB7cy0,1732
|
|
@@ -120,9 +120,10 @@ plotnine/guides/guide_colorbar.py,sha256=gL0218k3iJPNEpj6hWKxau3R8qRpT2bPkS1q9Pt
|
|
|
120
120
|
plotnine/guides/guide_legend.py,sha256=CrcV3iCAcEfUnSbkGtsS31c3OFQMWKiHqZZXejxOdno,14212
|
|
121
121
|
plotnine/guides/guides.py,sha256=cV7CwoYNrjkeaDHZ2AGcS2Dij5RpPovSiB-v47E7vhQ,15471
|
|
122
122
|
plotnine/mapping/__init__.py,sha256=DLu9E0kwwuHxzTUenoVjCNTTdkWMwIDtkExLleBq1MI,205
|
|
123
|
+
plotnine/mapping/_atomic.py,sha256=TbobHVJlHRoSHibi6OOWMVM2J1r_kKQJMS6G5zvEhrg,4029
|
|
123
124
|
plotnine/mapping/_env.py,sha256=ZXlTt2THRIcWb2WGk9fCpCMdVynlUX_BpG0Uwj7axi0,6072
|
|
124
125
|
plotnine/mapping/_eval_environment.py,sha256=PTrnnqrxMXqjt23t2NGRcU9i8Jie3ZaMe6W5aKtI7bI,2502
|
|
125
|
-
plotnine/mapping/aes.py,sha256=
|
|
126
|
+
plotnine/mapping/aes.py,sha256=eqNTBHqFnSBPoVNdrUB7pYM-ShlUTYvmwdQRXh9beV4,16717
|
|
126
127
|
plotnine/mapping/evaluation.py,sha256=kblTxVv3M4xIGnHyReUU0RtmmIN77Or2JBcci0nGGfE,5913
|
|
127
128
|
plotnine/plot_composition/__init__.py,sha256=ZJYpfVF158cQZ1zREXy6wHNJ4FbSmqWxIkHWZwX3QT8,148
|
|
128
129
|
plotnine/plot_composition/_compose.py,sha256=6UgXs6VBH0LIXW2uQlBQy-URh_mM948F5GOwN6IV734,12167
|
|
@@ -146,9 +147,9 @@ plotnine/scales/range.py,sha256=xBlFdAhthH1zKIJZDdkEyAA2kMZtyDorDFHKBMHKyAQ,1379
|
|
|
146
147
|
plotnine/scales/scale.py,sha256=T7oMfiXA2xOL_LQQIEKY_06VvXVfF-norNpc2gdTNNw,8078
|
|
147
148
|
plotnine/scales/scale_alpha.py,sha256=lYVZeaCC2pk-0-BoXnOeP0RdIbHYk6qo3DRClJrxTKo,2350
|
|
148
149
|
plotnine/scales/scale_color.py,sha256=6Cyjxml8Jn3EFiDY8IGvYM9_qacHvBQZFvbFh5Gyg8Q,14514
|
|
149
|
-
plotnine/scales/scale_continuous.py,sha256
|
|
150
|
+
plotnine/scales/scale_continuous.py,sha256=YMRjGh7QS644ajx5dVPJ9QTrWbTjmKt62MvVSFl3Y7U,16500
|
|
150
151
|
plotnine/scales/scale_datetime.py,sha256=OM9gfHKGkQIBrgqCEE00zUV0cMXXxTIS7wc9dYQZ6sE,3983
|
|
151
|
-
plotnine/scales/scale_discrete.py,sha256=
|
|
152
|
+
plotnine/scales/scale_discrete.py,sha256=UwAB0icMljH-eW6mK3g0YWAVzuE4P_91ZMYJhoMoMV4,9431
|
|
152
153
|
plotnine/scales/scale_identity.py,sha256=-PL9vJn0C_wOgrOQpqYSQbTWFSALoRPFzGXzebABTv8,2211
|
|
153
154
|
plotnine/scales/scale_linetype.py,sha256=pwLTmglN6d4bnChbuBi6HWbDP_nsE4viXD8CK6XKBPw,1373
|
|
154
155
|
plotnine/scales/scale_manual.py,sha256=oMnIfQNfxO4qw-gkBG3ikyuXdeoMgstRSdiibLc_DAA,4525
|
|
@@ -169,7 +170,7 @@ plotnine/stats/stat_bindot.py,sha256=FS-Axqhb-nMR9HK77BD-ElDHpsS8NaJKLMIff4-lDSU
|
|
|
169
170
|
plotnine/stats/stat_boxplot.py,sha256=5PvTTig5kAZQjenYL-158tjnzRnRUcnJpJL5qOdv9WI,5997
|
|
170
171
|
plotnine/stats/stat_count.py,sha256=P560-9Lm6FWUvL5bGASIBxY3vQnbcZrU4CYlBdZpE3Y,1928
|
|
171
172
|
plotnine/stats/stat_density.py,sha256=1TyOIiAaOaZesPcqIsnaVk14by0cKu6b7d7r-a5ZZSI,10346
|
|
172
|
-
plotnine/stats/stat_density_2d.py,sha256=
|
|
173
|
+
plotnine/stats/stat_density_2d.py,sha256=XSyE78IJwLLH4G5H7UD7ZrmknmaGCEMUHwvwNYmYVLQ,5647
|
|
173
174
|
plotnine/stats/stat_ecdf.py,sha256=BdeisCWzDnuPMc3vBgZqpDsXCKy8ZfoSRwFPOQlgksM,1630
|
|
174
175
|
plotnine/stats/stat_ellipse.py,sha256=NMr22TQMfVDZejIKvw1UWcB1NJptTlg6-qRf8d8EzJE,7570
|
|
175
176
|
plotnine/stats/stat_function.py,sha256=AcP_wUu063fA8RAjc7AoFuVQmLdlsuEvP6m8AuTm9HY,3123
|
|
@@ -211,8 +212,8 @@ plotnine/themes/elements/element_line.py,sha256=xF6xW-iA66YEP_fN7ooqaYry8_8qZT-e
|
|
|
211
212
|
plotnine/themes/elements/element_rect.py,sha256=w5cLH-Sr4cTRXVdkRiu8kBqFt3TXHhIb1MUITfi89gE,1767
|
|
212
213
|
plotnine/themes/elements/element_text.py,sha256=8yhwBa9s9JKCtBcqcBNybbCGK6ieDnZv4SHiC4Sy2qc,6255
|
|
213
214
|
plotnine/themes/elements/margin.py,sha256=jMHe-UKHHer_VYwAVDC-Tz2-AP_4YDuXPTWAuacoqgU,4080
|
|
214
|
-
plotnine-0.15.
|
|
215
|
-
plotnine-0.15.
|
|
216
|
-
plotnine-0.15.
|
|
217
|
-
plotnine-0.15.
|
|
218
|
-
plotnine-0.15.
|
|
215
|
+
plotnine-0.15.0a5.dist-info/licenses/LICENSE,sha256=GY4tQiUd17Tq3wWR42Zs9MRTFOTf6ahIXhZTcwAdOeU,1082
|
|
216
|
+
plotnine-0.15.0a5.dist-info/METADATA,sha256=HJHt2oWR1vudsK80OUWXBGCkU_d_Q6TWUlvo2d9w-nQ,9407
|
|
217
|
+
plotnine-0.15.0a5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
218
|
+
plotnine-0.15.0a5.dist-info/top_level.txt,sha256=t340Mbko1ZbmvYPkQ81dIiPHcaQdTUszYz-bWUpr8ys,9
|
|
219
|
+
plotnine-0.15.0a5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|