plotnine 0.15.0a4__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/geoms/geom.py CHANGED
@@ -2,9 +2,12 @@ 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
13
  remove_missing,
@@ -12,7 +15,7 @@ from .._utils import (
12
15
  from .._utils.registry import Register, Registry
13
16
  from ..exceptions import PlotnineError
14
17
  from ..layer import layer
15
- from ..mapping.aes import RepeatAesthetic, rename_aesthetics
18
+ from ..mapping.aes import rename_aesthetics
16
19
  from ..mapping.evaluation import evaluate
17
20
  from ..positions.position import position
18
21
  from ..stats.stat import stat
@@ -243,6 +246,9 @@ class geom(ABC, metaclass=Register):
243
246
  :
244
247
  Data used for drawing the geom.
245
248
  """
249
+ from plotnine.mapping import _atomic as atomic
250
+ from plotnine.mapping._atomic import ae_value
251
+
246
252
  missing_aes = (
247
253
  self.DEFAULT_AES.keys()
248
254
  - self.aes_params.keys()
@@ -261,25 +267,31 @@ class geom(ABC, metaclass=Register):
261
267
  num_panels = len(data["PANEL"].unique()) if "PANEL" in data else 1
262
268
  across_panels = num_panels > 1 and not self.params["inherit_aes"]
263
269
 
264
- # Aesthetics set as parameters to the geom/stat
270
+ # Aesthetics set as parameters in the geom/stat
265
271
  for ae, value in self.aes_params.items():
266
- try:
272
+ if isinstance(value, (str, int, float, np.integer, np.floating)):
267
273
  data[ae] = value
268
- except ValueError as e:
269
- # NOTE: Handling of the edgecases in this exception is not
270
- # foolproof.
271
- repeat_ae = getattr(RepeatAesthetic, ae, None)
272
- if across_panels:
273
- # Adding an annotation/abline/hline/vhline with multiple
274
- # items across to more than 1 panel
275
- value = list(chain(*repeat(value, num_panels)))
274
+ elif isinstance(value, ae_value):
275
+ data[ae] = value * len(data)
276
+ elif across_panels:
277
+ value = list(chain(*repeat(value, num_panels)))
278
+ data[ae] = value
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:
276
293
  data[ae] = value
277
- elif repeat_ae:
278
- # Some aesthetics may have valid values that are not
279
- # scalar. e.g. sequences. For such case, we need to
280
- # insert a sequence of the same value.
281
- data[ae] = repeat_ae(value, len(data))
282
- else:
294
+ except ValueError as e:
283
295
  msg = f"'{ae}={value}' does not look like a valid value"
284
296
  raise PlotnineError(msg) from e
285
297
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plotnine
3
- Version: 0.15.0a4
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)
@@ -69,7 +69,7 @@ plotnine/geoms/__init__.py,sha256=HEfhNmmNH4xm4rpXnFRXY4eLkJha3XPM72IIwVjv5Lc,26
69
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=cjxzWTZiAMop9FuXWg5kqgo1lBEG2Uu6DzARjHOpBY4,17300
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,6 +120,7 @@ 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
126
  plotnine/mapping/aes.py,sha256=eqNTBHqFnSBPoVNdrUB7pYM-ShlUTYvmwdQRXh9beV4,16717
@@ -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.0a4.dist-info/licenses/LICENSE,sha256=GY4tQiUd17Tq3wWR42Zs9MRTFOTf6ahIXhZTcwAdOeU,1082
215
- plotnine-0.15.0a4.dist-info/METADATA,sha256=MCVzMIfRSdwYyq2UrgdS74Zc8QiMo9pp8GiwCVFZXCo,9407
216
- plotnine-0.15.0a4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
217
- plotnine-0.15.0a4.dist-info/top_level.txt,sha256=t340Mbko1ZbmvYPkQ81dIiPHcaQdTUszYz-bWUpr8ys,9
218
- plotnine-0.15.0a4.dist-info/RECORD,,
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,,