plotnine 0.14.5__py3-none-any.whl → 0.15.0a2__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.
Files changed (92) hide show
  1. plotnine/__init__.py +31 -37
  2. plotnine/_mpl/gridspec.py +265 -0
  3. plotnine/_mpl/layout_manager/__init__.py +6 -0
  4. plotnine/_mpl/layout_manager/_engine.py +87 -0
  5. plotnine/_mpl/layout_manager/_layout_items.py +957 -0
  6. plotnine/_mpl/layout_manager/_layout_tree.py +905 -0
  7. plotnine/_mpl/layout_manager/_spaces.py +1154 -0
  8. plotnine/_mpl/patches.py +70 -34
  9. plotnine/_mpl/text.py +159 -37
  10. plotnine/_mpl/utils.py +78 -10
  11. plotnine/_utils/__init__.py +35 -9
  12. plotnine/_utils/dev.py +45 -27
  13. plotnine/_utils/yippie.py +115 -0
  14. plotnine/animation.py +1 -1
  15. plotnine/coords/coord.py +3 -3
  16. plotnine/coords/coord_trans.py +1 -1
  17. plotnine/data/__init__.py +43 -8
  18. plotnine/data/anscombe-quartet.csv +45 -0
  19. plotnine/doctools.py +2 -2
  20. plotnine/facets/facet.py +34 -43
  21. plotnine/facets/facet_grid.py +14 -6
  22. plotnine/facets/facet_wrap.py +3 -5
  23. plotnine/facets/strips.py +20 -33
  24. plotnine/geoms/annotate.py +3 -3
  25. plotnine/geoms/annotation_logticks.py +2 -0
  26. plotnine/geoms/annotation_stripes.py +2 -0
  27. plotnine/geoms/geom.py +3 -3
  28. plotnine/geoms/geom_bar.py +10 -2
  29. plotnine/geoms/geom_col.py +6 -0
  30. plotnine/geoms/geom_crossbar.py +2 -3
  31. plotnine/geoms/geom_path.py +2 -2
  32. plotnine/geoms/geom_violin.py +24 -7
  33. plotnine/ggplot.py +95 -66
  34. plotnine/guides/guide.py +19 -20
  35. plotnine/guides/guide_colorbar.py +6 -6
  36. plotnine/guides/guide_legend.py +15 -16
  37. plotnine/guides/guides.py +8 -8
  38. plotnine/helpers.py +49 -0
  39. plotnine/iapi.py +33 -7
  40. plotnine/labels.py +8 -3
  41. plotnine/layer.py +4 -4
  42. plotnine/mapping/_env.py +2 -2
  43. plotnine/mapping/_eval_environment.py +85 -0
  44. plotnine/mapping/aes.py +14 -30
  45. plotnine/mapping/evaluation.py +7 -65
  46. plotnine/options.py +14 -7
  47. plotnine/plot_composition/__init__.py +10 -0
  48. plotnine/plot_composition/_compose.py +462 -0
  49. plotnine/plot_composition/_plotspec.py +50 -0
  50. plotnine/plot_composition/_spacer.py +32 -0
  51. plotnine/positions/position_dodge.py +1 -1
  52. plotnine/positions/position_dodge2.py +1 -1
  53. plotnine/positions/position_stack.py +1 -2
  54. plotnine/qplot.py +1 -2
  55. plotnine/scales/__init__.py +0 -6
  56. plotnine/scales/limits.py +7 -7
  57. plotnine/scales/scale.py +4 -4
  58. plotnine/scales/scale_continuous.py +2 -1
  59. plotnine/scales/scale_identity.py +10 -2
  60. plotnine/scales/scale_manual.py +6 -2
  61. plotnine/stats/binning.py +5 -2
  62. plotnine/stats/smoothers.py +3 -5
  63. plotnine/stats/stat.py +3 -3
  64. plotnine/stats/stat_bindot.py +1 -3
  65. plotnine/stats/stat_density.py +2 -2
  66. plotnine/stats/stat_qq_line.py +1 -1
  67. plotnine/stats/stat_sina.py +34 -1
  68. plotnine/themes/elements/__init__.py +3 -0
  69. plotnine/themes/elements/element_text.py +35 -24
  70. plotnine/themes/elements/margin.py +137 -61
  71. plotnine/themes/targets.py +3 -1
  72. plotnine/themes/theme.py +21 -7
  73. plotnine/themes/theme_538.py +0 -1
  74. plotnine/themes/theme_bw.py +0 -1
  75. plotnine/themes/theme_dark.py +0 -1
  76. plotnine/themes/theme_gray.py +32 -34
  77. plotnine/themes/theme_light.py +1 -1
  78. plotnine/themes/theme_matplotlib.py +28 -31
  79. plotnine/themes/theme_seaborn.py +36 -36
  80. plotnine/themes/theme_void.py +25 -27
  81. plotnine/themes/theme_xkcd.py +0 -1
  82. plotnine/themes/themeable.py +369 -169
  83. plotnine/typing.py +3 -3
  84. plotnine/watermark.py +3 -3
  85. {plotnine-0.14.5.dist-info → plotnine-0.15.0a2.dist-info}/METADATA +8 -5
  86. {plotnine-0.14.5.dist-info → plotnine-0.15.0a2.dist-info}/RECORD +89 -78
  87. {plotnine-0.14.5.dist-info → plotnine-0.15.0a2.dist-info}/WHEEL +1 -1
  88. plotnine/_mpl/_plot_side_space.py +0 -888
  89. plotnine/_mpl/_plotnine_tight_layout.py +0 -293
  90. plotnine/_mpl/layout_engine.py +0 -110
  91. {plotnine-0.14.5.dist-info → plotnine-0.15.0a2.dist-info/licenses}/LICENSE +0 -0
  92. {plotnine-0.14.5.dist-info → plotnine-0.15.0a2.dist-info}/top_level.txt +0 -0
@@ -25,7 +25,7 @@ if TYPE_CHECKING:
25
25
 
26
26
  from plotnine.geoms.geom import geom
27
27
  from plotnine.layer import layer
28
- from plotnine.typing import SidePosition
28
+ from plotnine.typing import Side
29
29
 
30
30
 
31
31
  # See guides.py for terminology
@@ -171,21 +171,20 @@ class guide_legend(guide):
171
171
  # Modify aesthetics
172
172
 
173
173
  # When doing after_scale evaluations, we only consider those
174
- # for the aesthetics of this legend. The reduces the spurious
175
- # warnings where an evaluation of another aesthetic failed yet
176
- # it is not needed.
174
+ # for the aesthetics that are valid for this layer/geom.
177
175
  aes_modifiers = {
178
- ae: expr
179
- for ae, expr in l.mapping._scaled.items()
180
- if ae in matched_set
176
+ ae: l.mapping._scaled[ae]
177
+ for ae in l.geom.aesthetics() & l.mapping._scaled.keys()
181
178
  }
182
179
 
183
180
  try:
184
181
  data = l.use_defaults(data, aes_modifiers)
185
182
  except PlotnineError:
186
183
  warn(
187
- "Failed to apply `after_scale` modifications "
188
- "to the legend.",
184
+ "Failed to apply `after_scale` modifications to the "
185
+ "legend. This probably should not happen. Help us "
186
+ "discover why, please open and issue at "
187
+ "https://github.com/has2k1/plotnine/issues",
189
188
  PlotnineWarning,
190
189
  )
191
190
  data = l.use_defaults(data, {})
@@ -226,10 +225,10 @@ class guide_legend(guide):
226
225
  ncol = int(np.ceil(nbreak / 15))
227
226
 
228
227
  if nrow is None:
229
- ncol = cast(int, ncol)
228
+ ncol = cast("int", ncol)
230
229
  nrow = int(np.ceil(nbreak / ncol))
231
230
  elif ncol is None:
232
- nrow = cast(int, nrow)
231
+ nrow = cast("int", nrow)
233
232
  ncol = int(np.ceil(nbreak / nrow))
234
233
 
235
234
  return nrow, ncol
@@ -255,7 +254,7 @@ class guide_legend(guide):
255
254
  elements = self.elements
256
255
 
257
256
  # title
258
- title = cast(str, self.title)
257
+ title = cast("str", self.title)
259
258
  title_box = TextArea(title)
260
259
  targets.legend_title = title_box._text # type: ignore
261
260
 
@@ -282,7 +281,7 @@ class guide_legend(guide):
282
281
  targets.legend_key = drawings
283
282
 
284
283
  # Match Drawings with labels to create the entries
285
- lookup: dict[SidePosition, tuple[type[PackerBase], slice]] = {
284
+ lookup: dict[Side, tuple[type[PackerBase], slice]] = {
286
285
  "right": (HPacker, reverse),
287
286
  "left": (HPacker, obverse),
288
287
  "bottom": (VPacker, reverse),
@@ -381,7 +380,7 @@ class GuideElementsLegend(GuideElements):
381
380
  )
382
381
 
383
382
  @cached_property
384
- def text_position(self) -> SidePosition:
383
+ def text_position(self) -> Side:
385
384
  if not (pos := self.theme.getp("legend_text_position")):
386
385
  pos = "right"
387
386
  return pos
@@ -403,7 +402,7 @@ class GuideElementsLegend(GuideElements):
403
402
  dimensions are big enough.
404
403
  """
405
404
  # Note the different height sizes for the entries
406
- guide = cast(guide_legend, self.guide)
405
+ guide = cast("guide_legend", self.guide)
407
406
  min_size = (
408
407
  self.theme.getp("legend_key_width"),
409
408
  self.theme.getp("legend_key_height"),
@@ -452,7 +451,7 @@ class GuideElementsLegend(GuideElements):
452
451
  If legend is horizontal, then key heights must be equal, so we
453
452
  use the maximum
454
453
  """
455
- hs = [h for h, _ in self._key_dimensions]
454
+ hs = [h for _, h in self._key_dimensions]
456
455
  if self.is_horizontal:
457
456
  return [max(hs)] * len(hs)
458
457
  return hs
plotnine/guides/guides.py CHANGED
@@ -35,7 +35,7 @@ if TYPE_CHECKING:
35
35
  NoGuide,
36
36
  Orientation,
37
37
  ScaledAestheticsName,
38
- SidePosition,
38
+ Side,
39
39
  TextJustification,
40
40
  )
41
41
 
@@ -104,7 +104,7 @@ class guides:
104
104
  raise ValueError("Got a guide for color and colour, choose one.")
105
105
  rename_aesthetics(self)
106
106
 
107
- def __radd__(self, plot: ggplot):
107
+ def __radd__(self, other: ggplot):
108
108
  """
109
109
  Add guides to the plot
110
110
 
@@ -120,9 +120,9 @@ class guides:
120
120
  """
121
121
  for f in fields(self):
122
122
  if (g := getattr(self, f.name)) is not None:
123
- setattr(plot.guides, f.name, g)
123
+ setattr(other.guides, f.name, g)
124
124
 
125
- return plot
125
+ return other
126
126
 
127
127
  def _build(self) -> Sequence[guide]:
128
128
  """
@@ -326,7 +326,7 @@ class guides:
326
326
 
327
327
  # Group together guides for each position
328
328
  groups: dict[
329
- tuple[SidePosition, float]
329
+ tuple[Side, float]
330
330
  | tuple[tuple[float, float], tuple[float, float]],
331
331
  list[PackerBase],
332
332
  ] = defaultdict(list)
@@ -342,8 +342,8 @@ class guides:
342
342
  if isinstance(position, str) and isinstance(just, (float, int)):
343
343
  setattr(legends, position, outside_legend(aob, just))
344
344
  else:
345
- position = cast(tuple[float, float], position)
346
- just = cast(tuple[float, float], just)
345
+ position = cast("tuple[float, float]", position)
346
+ just = cast("tuple[float, float]", just)
347
347
  legends.inside.append(inside_legend(aob, just, position))
348
348
 
349
349
  return legends
@@ -467,7 +467,7 @@ class GuidesElements:
467
467
  if just is None:
468
468
  just = (0.5, 0.5)
469
469
  elif just in VALID_JUSTIFICATION_WORDS:
470
- just = ensure_xy_location(just) # pyright: ignore[reportArgumentType]
470
+ just = ensure_xy_location(just)
471
471
  elif isinstance(just, (float, int)):
472
472
  just = (just, just)
473
473
  return just[idx]
plotnine/helpers.py ADDED
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ from copy import deepcopy
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from typing import Sequence
8
+
9
+ from plotnine import ggplot
10
+
11
+ __all__ = ("get_aesthetic_limits",)
12
+
13
+
14
+ def get_aesthetic_limits(
15
+ plot: ggplot,
16
+ ae: str,
17
+ ) -> (
18
+ tuple[float, float]
19
+ | Sequence[str]
20
+ | list[tuple[float]]
21
+ | list[Sequence[str]]
22
+ ):
23
+ """
24
+ Get the limits of an aesthetic
25
+
26
+ These are the limits before they are expanded.
27
+
28
+ Parameters
29
+ ----------
30
+ plot :
31
+ ggplot object
32
+
33
+ ae :
34
+ Name of aesthetic
35
+
36
+ Returns
37
+ -------
38
+ out :
39
+ The limits of the aesthetic. If the plot is facetted, (has many
40
+ panels), it is a sequence of limits, one for each panel.
41
+ """
42
+ plot = deepcopy(plot)
43
+ plot._build()
44
+ limits = [
45
+ getattr(panel, ae).limits
46
+ for panel in plot._build_objs.layout.panel_params
47
+ ]
48
+
49
+ return limits[0] if len(limits) == 1 else limits
plotnine/iapi.py CHANGED
@@ -10,6 +10,7 @@ from __future__ import annotations
10
10
  import itertools
11
11
  from copy import copy
12
12
  from dataclasses import dataclass, field, fields
13
+ from functools import cached_property
13
14
  from typing import TYPE_CHECKING
14
15
 
15
16
  if TYPE_CHECKING:
@@ -19,11 +20,14 @@ if TYPE_CHECKING:
19
20
  from matplotlib.figure import Figure
20
21
 
21
22
  from plotnine.scales.scale import scale
23
+ from plotnine.themes.elements.margin import margin
22
24
  from plotnine.typing import (
23
25
  CoordRange,
24
26
  FloatArrayLike,
27
+ HorizontalJustification,
25
28
  ScaledAestheticsName,
26
29
  StripPosition,
30
+ VerticalJustification,
27
31
  )
28
32
 
29
33
  from ._mpl.offsetbox import FlexibleAnchoredOffsetbox
@@ -76,6 +80,7 @@ class labels_view:
76
80
  title: Optional[str] = None
77
81
  caption: Optional[str] = None
78
82
  subtitle: Optional[str] = None
83
+ tag: Optional[str] = None
79
84
 
80
85
  def update(self, other: labels_view):
81
86
  """
@@ -228,13 +233,27 @@ class strip_draw_info:
228
233
  Information required to draw strips
229
234
  """
230
235
 
231
- x: float
232
- y: float
233
- ha: str
234
- va: str
235
- box_width: float
236
- box_height: float
237
- strip_text_margin: float
236
+ bg_x: float
237
+ """Left of the strip background in transAxes"""
238
+
239
+ bg_y: float
240
+ """Bottom of the strip background in transAxes"""
241
+
242
+ ha: HorizontalJustification | float
243
+ """Horizontal justification of strip text within the background"""
244
+
245
+ va: VerticalJustification | float
246
+ """Vertical justification of strip text within the background"""
247
+
248
+ bg_width: float
249
+ """Width of the strip background in transAxes"""
250
+
251
+ bg_height: float
252
+ """Height of the strip background in transAxes"""
253
+
254
+ margin: margin
255
+ """Strip text margin with the units in lines"""
256
+
238
257
  strip_align: float
239
258
  position: StripPosition
240
259
  label: str
@@ -242,6 +261,13 @@ class strip_draw_info:
242
261
  rotation: float
243
262
  layout: layout_details
244
263
 
264
+ @cached_property
265
+ def is_oneline(self) -> bool:
266
+ """
267
+ Whether the strip text is a single line
268
+ """
269
+ return len(self.label.split("\n")) == 1
270
+
245
271
 
246
272
  @dataclass
247
273
  class strip_label_details:
plotnine/labels.py CHANGED
@@ -90,6 +90,11 @@ class labs:
90
90
  The caption at the bottom of the plot.
91
91
  """
92
92
 
93
+ tag: str | None = None
94
+ """
95
+ A plot tag
96
+ """
97
+
93
98
  def __post_init__(self):
94
99
  kwargs: dict[str, str] = {
95
100
  f.name: value
@@ -98,12 +103,12 @@ class labs:
98
103
  }
99
104
  self.labels = labels_view(**rename_aesthetics(kwargs))
100
105
 
101
- def __radd__(self, plot: p9.ggplot) -> p9.ggplot:
106
+ def __radd__(self, other: p9.ggplot) -> p9.ggplot:
102
107
  """
103
108
  Add labels to ggplot object
104
109
  """
105
- plot.labels.update(self.labels)
106
- return plot
110
+ other.labels.update(self.labels)
111
+ return other
107
112
 
108
113
 
109
114
  class xlab(labs):
plotnine/layer.py CHANGED
@@ -125,16 +125,16 @@ class layer:
125
125
  lkwargs[param] = geom.DEFAULT_PARAMS[param]
126
126
  return layer(**lkwargs)
127
127
 
128
- def __radd__(self, plot: ggplot) -> ggplot:
128
+ def __radd__(self, other: ggplot) -> ggplot:
129
129
  """
130
130
  Add layer to ggplot object
131
131
  """
132
132
  try:
133
- plot.layers.append(self)
133
+ other.layers.append(self)
134
134
  except AttributeError as e:
135
- msg = f"Cannot add layer to object of type {type(plot)!r}"
135
+ msg = f"Cannot add layer to object of type {type(other)!r}"
136
136
  raise PlotnineError(msg) from e
137
- return plot
137
+ return other
138
138
 
139
139
  def __deepcopy__(self, memo: dict[Any, Any]) -> layer:
140
140
  """
plotnine/mapping/_env.py CHANGED
@@ -122,7 +122,7 @@ class Environment:
122
122
  def __hash__(self):
123
123
  return hash((Environment, tuple(self._namespace_ids())))
124
124
 
125
- def __getstate__(*args, **kwargs):
125
+ def __getstate__(self):
126
126
  """
127
127
  Return state with no namespaces
128
128
  """
@@ -198,7 +198,7 @@ class StackedLookup(MutableMapping):
198
198
  def __repr__(self):
199
199
  return f"{self.__class__.__name__}({self.stack})"
200
200
 
201
- def __getstate__(*args, **kwargs):
201
+ def __getstate__(self):
202
202
  """
203
203
  Return state with no namespace
204
204
  """
@@ -0,0 +1,85 @@
1
+ """
2
+ These are functions that can be called by the user inside the aes()
3
+ mapping. This is meant to make it easy to transform column-variables
4
+ as easily as is possible in ggplot2.
5
+
6
+ We only implement the most common functions.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import TYPE_CHECKING
12
+
13
+ import numpy as np
14
+ import pandas as pd
15
+
16
+ if TYPE_CHECKING:
17
+ from typing import Any, Sequence
18
+
19
+ __all__ = (
20
+ "factor",
21
+ "reorder",
22
+ )
23
+
24
+
25
+ def factor(
26
+ values: Sequence[Any],
27
+ categories: Sequence[Any] | None = None,
28
+ ordered: bool | None = None,
29
+ ) -> pd.Categorical:
30
+ """
31
+ Turn x in to a categorical (factor) variable
32
+
33
+ It is just an alias to `pandas.Categorical`
34
+
35
+ Parameters
36
+ ----------
37
+ values :
38
+ The values of the categorical. If categories are given, values not in
39
+ categories will be replaced with NaN.
40
+ categories :
41
+ The unique categories for this categorical. If not given, the
42
+ categories are assumed to be the unique values of `values`
43
+ (sorted, if possible, otherwise in the order in which they appear).
44
+ ordered :
45
+ Whether or not this categorical is treated as a ordered categorical.
46
+ If True, the resulting categorical will be ordered.
47
+ An ordered categorical respects, when sorted, the order of its
48
+ `categories` attribute (which in turn is the `categories` argument, if
49
+ provided).
50
+ """
51
+ return pd.Categorical(values, categories=categories, ordered=None)
52
+
53
+
54
+ def reorder(x, y, fun=np.median, ascending=True):
55
+ """
56
+ Reorder categorical by sorting along another variable
57
+
58
+ It is the order of the categories that changes. Values in x
59
+ are grouped by categories and summarised to determine the
60
+ new order.
61
+
62
+ Credit: Copied from plydata
63
+
64
+ Parameters
65
+ ----------
66
+ x : list-like
67
+ Values that will make up the categorical.
68
+ y : list-like
69
+ Values by which `c` will be ordered.
70
+ fun : callable
71
+ Summarising function to `x` for each category in `c`.
72
+ Default is the *median*.
73
+ ascending : bool
74
+ If `True`, the `c` is ordered in ascending order of `x`.
75
+ """
76
+ if len(x) != len(y):
77
+ raise ValueError(f"Lengths are not equal. {len(x)=}, {len(x)=}")
78
+ summary = (
79
+ pd.Series(y)
80
+ .groupby(x, observed=True)
81
+ .apply(fun)
82
+ .sort_values(ascending=ascending)
83
+ )
84
+ cats = summary.index.to_list()
85
+ return pd.Categorical(x, categories=cats)
plotnine/mapping/aes.py CHANGED
@@ -1,19 +1,19 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import re
4
- import typing
5
4
  from collections.abc import Iterable, Sequence
6
5
  from contextlib import suppress
7
6
  from copy import deepcopy
8
7
  from dataclasses import fields
9
- from typing import Any, Dict
8
+ from functools import cached_property
9
+ from typing import TYPE_CHECKING, Any, Dict
10
10
 
11
11
  import pandas as pd
12
12
 
13
13
  from ..iapi import labels_view
14
14
  from .evaluation import after_stat, stage
15
15
 
16
- if typing.TYPE_CHECKING:
16
+ if TYPE_CHECKING:
17
17
  from typing import Protocol, TypeVar
18
18
 
19
19
  class ColorOrColour(Protocol):
@@ -171,27 +171,11 @@ class aes(Dict[str, Any]):
171
171
  ggplot(df, aes(x="df.index", y="np.sin(gam ma)"))
172
172
  ```
173
173
 
174
- `aes` has 2 internal methods you can use to transform variables being
175
- mapped.
174
+ `aes` has 2 internal functions that you can use in your expressions
175
+ when transforming the variables.
176
176
 
177
- 1. `factor` - This function turns the variable into a factor.
178
- It is just an alias to `pandas.Categorical`:
179
-
180
- ```python
181
- ggplot(mtcars, aes(x="factor(cyl)")) + geom_bar()
182
- ```
183
-
184
- 2. `reorder` - This function changes the order of first variable
185
- based on values of the second variable:
186
-
187
- ```python
188
- df = pd.DataFrame({
189
- "x": ["b", "d", "c", "a"],
190
- "y": [1, 2, 3, 4]
191
- })
192
-
193
- ggplot(df, aes("reorder(x, y)", "y")) + geom_col()
194
- ```
177
+ 1. [](:func:`~plotnine.mapping._eval_environment.factor`)
178
+ 1. [](:func:`~plotnine.mapping._eval_environment.reorder`)
195
179
 
196
180
  **The group aesthetic**
197
181
 
@@ -237,7 +221,7 @@ class aes(Dict[str, Any]):
237
221
  kwargs[name] = after_stat(_after_stat)
238
222
  return kwargs
239
223
 
240
- @property
224
+ @cached_property
241
225
  def _starting(self) -> dict[str, Any]:
242
226
  """
243
227
  Return the subset of aesthetics mapped from the layer data
@@ -254,7 +238,7 @@ class aes(Dict[str, Any]):
254
238
 
255
239
  return d
256
240
 
257
- @property
241
+ @cached_property
258
242
  def _calculated(self) -> dict[str, Any]:
259
243
  """
260
244
  Return only the aesthetics mapped to calculated statistics
@@ -269,7 +253,7 @@ class aes(Dict[str, Any]):
269
253
 
270
254
  return d
271
255
 
272
- @property
256
+ @cached_property
273
257
  def _scaled(self) -> dict[str, Any]:
274
258
  """
275
259
  Return only the aesthetics mapped to after scaling
@@ -298,14 +282,14 @@ class aes(Dict[str, Any]):
298
282
 
299
283
  return result
300
284
 
301
- def __radd__(self, plot):
285
+ def __radd__(self, other):
302
286
  """
303
287
  Add aesthetic mappings to ggplot
304
288
  """
305
289
  self = deepcopy(self)
306
- plot.mapping.update(self)
307
- plot.labels.update(make_labels(self))
308
- return plot
290
+ other.mapping.update(self)
291
+ other.labels.update(make_labels(self))
292
+ return other
309
293
 
310
294
  def copy(self):
311
295
  return aes(**self)
@@ -1,15 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import numbers
4
- import typing
4
+ from typing import TYPE_CHECKING
5
5
 
6
6
  import numpy as np
7
7
  import pandas as pd
8
8
  import pandas.api.types as pdtypes
9
9
 
10
10
  from ..exceptions import PlotnineError
11
+ from ._eval_environment import factor, reorder
11
12
 
12
- if typing.TYPE_CHECKING:
13
+ if TYPE_CHECKING:
13
14
  from typing import Any
14
15
 
15
16
  from . import aes
@@ -18,6 +19,9 @@ if typing.TYPE_CHECKING:
18
19
 
19
20
  __all__ = ("after_stat", "after_scale", "stage")
20
21
 
22
+
23
+ EVAL_ENVIRONMENT = {"factor": factor, "reorder": reorder}
24
+
21
25
  _TPL_EVAL_FAIL = """\
22
26
  Could not evaluate the '{}' mapping: '{}' \
23
27
  (original error: {})"""
@@ -108,68 +112,6 @@ def after_scale(x):
108
112
  return stage(after_scale=x)
109
113
 
110
114
 
111
- def reorder(x, y, fun=np.median, ascending=True):
112
- """
113
- Reorder categorical by sorting along another variable
114
-
115
- It is the order of the categories that changes. Values in x
116
- are grouped by categories and summarised to determine the
117
- new order.
118
-
119
- Credit: Copied from plydata
120
-
121
- Parameters
122
- ----------
123
- x : list-like
124
- Values that will make up the categorical.
125
- y : list-like
126
- Values by which `c` will be ordered.
127
- fun : callable
128
- Summarising function to `x` for each category in `c`.
129
- Default is the *median*.
130
- ascending : bool
131
- If `True`, the `c` is ordered in ascending order of `x`.
132
-
133
- Examples
134
- --------
135
- >>> c = list('abbccc')
136
- >>> x = [11, 2, 2, 3, 33, 3]
137
- >>> cat_reorder(c, x)
138
- [a, b, b, c, c, c]
139
- Categories (3, object): [b, c, a]
140
- >>> cat_reorder(c, x, fun=max)
141
- [a, b, b, c, c, c]
142
- Categories (3, object): [b, a, c]
143
- >>> cat_reorder(c, x, fun=max, ascending=False)
144
- [a, b, b, c, c, c]
145
- Categories (3, object): [c, a, b]
146
- >>> c_ordered = pd.Categorical(c, ordered=True)
147
- >>> cat_reorder(c_ordered, x)
148
- [a, b, b, c, c, c]
149
- Categories (3, object): [b < c < a]
150
- >>> cat_reorder(c + ['d'], x)
151
- Traceback (most recent call last):
152
- ...
153
- ValueError: Lengths are not equal. len(c) is 7 and len(x) is 6.
154
- """
155
- if len(x) != len(y):
156
- raise ValueError(f"Lengths are not equal. {len(x)=}, {len(x)=}")
157
- summary = (
158
- pd.Series(y)
159
- .groupby(x, observed=True)
160
- .apply(fun)
161
- .sort_values(ascending=ascending)
162
- )
163
- cats = summary.index.to_list()
164
- return pd.Categorical(x, categories=cats)
165
-
166
-
167
- # These are function that can be called by the user inside the aes()
168
- # mapping. This is meant to make the variable transformations as easy
169
- # as they are in ggplot2
170
- AES_INNER_NAMESPACE = {"factor": pd.Categorical, "reorder": reorder}
171
-
172
-
173
115
  def evaluate(
174
116
  aesthetics: aes | dict[str, Any], data: pd.DataFrame, env: Environment
175
117
  ) -> pd.DataFrame:
@@ -207,7 +149,7 @@ def evaluate(
207
149
  3 16
208
150
  4 25
209
151
  """
210
- env = env.with_outer_namespace(AES_INNER_NAMESPACE)
152
+ env = env.with_outer_namespace(EVAL_ENVIRONMENT)
211
153
 
212
154
  # Store evaluation results in a dict column in a dict
213
155
  evaled = {}
plotnine/options.py CHANGED
@@ -10,14 +10,11 @@ if TYPE_CHECKING:
10
10
  from plotnine import theme
11
11
  from plotnine.typing import FigureFormat
12
12
 
13
- close_all_figures = False
14
- """
15
- Development flag, e.g. set to `True` to prevent
16
- the queuing up of figures when errors happen.
17
- """
18
-
19
13
  current_theme: Optional[theme | Type[theme]] = None
20
- """Theme used when none is added to the ggplot object"""
14
+ """Theme used when none is added to the ggplot object
15
+
16
+ Another way to do it, to set a default theme using `theme_set()`.
17
+ """
21
18
 
22
19
  base_family: str = "sans-serif"
23
20
  """
@@ -77,6 +74,11 @@ def get_option(name: str) -> Any:
77
74
  ----------
78
75
  name :
79
76
  Name of the option
77
+
78
+ Notes
79
+ -----
80
+ See [reference](/reference/#options) for a list of all the available
81
+ options.
80
82
  """
81
83
  d = globals()
82
84
 
@@ -103,6 +105,11 @@ def set_option(name: str, value: Any) -> Any:
103
105
  -------
104
106
  :
105
107
  Old value of the option
108
+
109
+ Notes
110
+ -----
111
+ See [reference](/reference/#options) for a list of all the available
112
+ options.
106
113
  """
107
114
  d = globals()
108
115
 
@@ -0,0 +1,10 @@
1
+ from ._compose import ADD, DIV, OR, Compose
2
+ from ._spacer import spacer
3
+
4
+ __all__ = (
5
+ "Compose",
6
+ "ADD",
7
+ "DIV",
8
+ "OR",
9
+ "spacer",
10
+ )