plotnine 0.15.0.dev3__py3-none-any.whl → 0.15.2__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 (139) hide show
  1. plotnine/__init__.py +2 -0
  2. plotnine/_mpl/layout_manager/_engine.py +1 -1
  3. plotnine/_mpl/layout_manager/_layout_items.py +126 -41
  4. plotnine/_mpl/layout_manager/_layout_tree.py +712 -314
  5. plotnine/_mpl/layout_manager/_spaces.py +305 -101
  6. plotnine/_mpl/patches.py +70 -34
  7. plotnine/_mpl/text.py +144 -63
  8. plotnine/_mpl/utils.py +1 -1
  9. plotnine/_utils/__init__.py +50 -107
  10. plotnine/_utils/context.py +78 -2
  11. plotnine/_utils/ipython.py +35 -51
  12. plotnine/_utils/quarto.py +26 -0
  13. plotnine/_utils/yippie.py +115 -0
  14. plotnine/composition/__init__.py +11 -0
  15. plotnine/composition/_beside.py +55 -0
  16. plotnine/composition/_compose.py +471 -0
  17. plotnine/composition/_plot_spacer.py +60 -0
  18. plotnine/composition/_stack.py +55 -0
  19. plotnine/coords/coord.py +3 -3
  20. plotnine/data/__init__.py +31 -0
  21. plotnine/data/anscombe-quartet.csv +45 -0
  22. plotnine/doctools.py +4 -4
  23. plotnine/facets/facet.py +4 -4
  24. plotnine/facets/strips.py +17 -28
  25. plotnine/geoms/annotate.py +13 -13
  26. plotnine/geoms/annotation_logticks.py +7 -8
  27. plotnine/geoms/annotation_stripes.py +6 -6
  28. plotnine/geoms/geom.py +60 -27
  29. plotnine/geoms/geom_abline.py +3 -2
  30. plotnine/geoms/geom_area.py +2 -2
  31. plotnine/geoms/geom_bar.py +1 -0
  32. plotnine/geoms/geom_bin_2d.py +6 -2
  33. plotnine/geoms/geom_blank.py +0 -3
  34. plotnine/geoms/geom_boxplot.py +8 -4
  35. plotnine/geoms/geom_col.py +2 -2
  36. plotnine/geoms/geom_count.py +6 -2
  37. plotnine/geoms/geom_crossbar.py +3 -3
  38. plotnine/geoms/geom_density_2d.py +6 -2
  39. plotnine/geoms/geom_dotplot.py +2 -2
  40. plotnine/geoms/geom_errorbar.py +2 -2
  41. plotnine/geoms/geom_errorbarh.py +2 -2
  42. plotnine/geoms/geom_histogram.py +1 -1
  43. plotnine/geoms/geom_hline.py +3 -2
  44. plotnine/geoms/geom_linerange.py +2 -2
  45. plotnine/geoms/geom_map.py +5 -5
  46. plotnine/geoms/geom_path.py +11 -12
  47. plotnine/geoms/geom_point.py +4 -5
  48. plotnine/geoms/geom_pointdensity.py +4 -0
  49. plotnine/geoms/geom_pointrange.py +3 -5
  50. plotnine/geoms/geom_polygon.py +2 -3
  51. plotnine/geoms/geom_qq.py +4 -0
  52. plotnine/geoms/geom_qq_line.py +4 -0
  53. plotnine/geoms/geom_quantile.py +4 -0
  54. plotnine/geoms/geom_raster.py +4 -5
  55. plotnine/geoms/geom_rect.py +3 -4
  56. plotnine/geoms/geom_ribbon.py +7 -7
  57. plotnine/geoms/geom_rug.py +1 -1
  58. plotnine/geoms/geom_segment.py +2 -2
  59. plotnine/geoms/geom_sina.py +3 -3
  60. plotnine/geoms/geom_smooth.py +7 -3
  61. plotnine/geoms/geom_step.py +2 -2
  62. plotnine/geoms/geom_text.py +2 -3
  63. plotnine/geoms/geom_violin.py +8 -5
  64. plotnine/geoms/geom_vline.py +3 -2
  65. plotnine/ggplot.py +64 -85
  66. plotnine/guides/guide.py +7 -10
  67. plotnine/guides/guide_colorbar.py +3 -3
  68. plotnine/guides/guide_legend.py +3 -3
  69. plotnine/guides/guides.py +6 -6
  70. plotnine/helpers.py +49 -0
  71. plotnine/iapi.py +28 -5
  72. plotnine/labels.py +3 -3
  73. plotnine/layer.py +36 -19
  74. plotnine/mapping/_atomic.py +178 -0
  75. plotnine/mapping/_env.py +13 -2
  76. plotnine/mapping/_eval_environment.py +1 -1
  77. plotnine/mapping/aes.py +85 -49
  78. plotnine/scales/__init__.py +2 -0
  79. plotnine/scales/limits.py +7 -7
  80. plotnine/scales/scale.py +3 -3
  81. plotnine/scales/scale_color.py +82 -18
  82. plotnine/scales/scale_continuous.py +6 -4
  83. plotnine/scales/scale_datetime.py +28 -14
  84. plotnine/scales/scale_discrete.py +1 -1
  85. plotnine/scales/scale_identity.py +21 -2
  86. plotnine/scales/scale_manual.py +8 -2
  87. plotnine/scales/scale_xy.py +2 -2
  88. plotnine/stats/binning.py +4 -1
  89. plotnine/stats/smoothers.py +23 -36
  90. plotnine/stats/stat.py +20 -32
  91. plotnine/stats/stat_bin.py +6 -5
  92. plotnine/stats/stat_bin_2d.py +11 -9
  93. plotnine/stats/stat_bindot.py +13 -16
  94. plotnine/stats/stat_boxplot.py +6 -6
  95. plotnine/stats/stat_count.py +6 -9
  96. plotnine/stats/stat_density.py +7 -10
  97. plotnine/stats/stat_density_2d.py +12 -8
  98. plotnine/stats/stat_ecdf.py +7 -6
  99. plotnine/stats/stat_ellipse.py +9 -6
  100. plotnine/stats/stat_function.py +10 -8
  101. plotnine/stats/stat_hull.py +6 -3
  102. plotnine/stats/stat_identity.py +5 -2
  103. plotnine/stats/stat_pointdensity.py +5 -7
  104. plotnine/stats/stat_qq.py +46 -20
  105. plotnine/stats/stat_qq_line.py +16 -11
  106. plotnine/stats/stat_quantile.py +15 -9
  107. plotnine/stats/stat_sina.py +13 -15
  108. plotnine/stats/stat_smooth.py +8 -10
  109. plotnine/stats/stat_sum.py +5 -2
  110. plotnine/stats/stat_summary.py +7 -10
  111. plotnine/stats/stat_summary_bin.py +11 -14
  112. plotnine/stats/stat_unique.py +5 -2
  113. plotnine/stats/stat_ydensity.py +8 -11
  114. plotnine/themes/elements/__init__.py +2 -1
  115. plotnine/themes/elements/element_line.py +17 -9
  116. plotnine/themes/elements/margin.py +64 -1
  117. plotnine/themes/theme.py +9 -1
  118. plotnine/themes/theme_538.py +0 -1
  119. plotnine/themes/theme_bw.py +0 -1
  120. plotnine/themes/theme_dark.py +0 -1
  121. plotnine/themes/theme_gray.py +6 -5
  122. plotnine/themes/theme_light.py +1 -1
  123. plotnine/themes/theme_matplotlib.py +5 -5
  124. plotnine/themes/theme_seaborn.py +7 -4
  125. plotnine/themes/theme_void.py +9 -8
  126. plotnine/themes/theme_xkcd.py +0 -1
  127. plotnine/themes/themeable.py +109 -31
  128. plotnine/typing.py +17 -6
  129. plotnine/watermark.py +3 -3
  130. {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/METADATA +13 -6
  131. plotnine-0.15.2.dist-info/RECORD +221 -0
  132. {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/WHEEL +1 -1
  133. plotnine/plot_composition/__init__.py +0 -10
  134. plotnine/plot_composition/_compose.py +0 -436
  135. plotnine/plot_composition/_spacer.py +0 -32
  136. plotnine-0.15.0.dev3.dist-info/RECORD +0 -215
  137. /plotnine/{plot_composition → composition}/_plotspec.py +0 -0
  138. {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/licenses/LICENSE +0 -0
  139. {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,471 @@
1
+ from __future__ import annotations
2
+
3
+ import abc
4
+ from copy import copy, deepcopy
5
+ from dataclasses import dataclass, field
6
+ from io import BytesIO
7
+ from typing import TYPE_CHECKING, overload
8
+
9
+ from .._utils.context import plot_composition_context
10
+ from .._utils.ipython import (
11
+ get_ipython,
12
+ get_mimebundle,
13
+ is_inline_backend,
14
+ )
15
+ from .._utils.quarto import is_knitr_engine, is_quarto_environment
16
+ from ..options import get_option
17
+ from ._plotspec import plotspec
18
+
19
+ if TYPE_CHECKING:
20
+ from pathlib import Path
21
+ from typing import Generator, Iterator
22
+
23
+ from matplotlib.figure import Figure
24
+
25
+ from plotnine._mpl.gridspec import p9GridSpec
26
+ from plotnine.ggplot import PlotAddable, ggplot
27
+ from plotnine.typing import FigureFormat, MimeBundle
28
+
29
+
30
+ @dataclass
31
+ class Compose:
32
+ """
33
+ Base class for those that create plot compositions
34
+
35
+ As a user, you will never directly work with this class, except
36
+ through the operators that it makes possible.
37
+ The operators are of two kinds:
38
+
39
+ ### 1. Composing Operators
40
+
41
+ The combine plots or compositions into a single composition.
42
+ Both operands are either a plot or a composition.
43
+
44
+ `/`
45
+
46
+ : Arrange operands side by side.
47
+ Powered by the subclass [](`~plotnine.composition.Beside`).
48
+
49
+ `|`
50
+
51
+ : Arrange operands vertically.
52
+ Powered by the subclass [](`~plotnine.composition.Stack`).
53
+
54
+ `-`
55
+
56
+ : Arrange operands side by side _and_ at the same nesting level.
57
+ Also powered by the subclass [](`~plotnine.composition.Beside`).
58
+
59
+ ### 2. Plot Modifying Operators
60
+
61
+ The modify all or some of the plots in a composition.
62
+ The left operand is a composition and the right operand is a
63
+ _plotaddable_; any object that can be added to a `ggplot` object
64
+ e.g. _geoms_, _stats_, _themes_, _facets_, ... .
65
+
66
+ `&`
67
+
68
+ : Add right hand side to all plots in the composition.
69
+
70
+ `*`
71
+
72
+ : Add right hand side to all plots in the top-most nesting
73
+ level of the composition.
74
+
75
+ `+`
76
+
77
+ : Add right hand side to the last plot in the composition.
78
+
79
+ See Also
80
+ --------
81
+ plotnine.composition.Beside : To arrange plots side by side
82
+ plotnine.composition.Stack : To arrange plots vertically
83
+ plotnine.composition.plot_spacer : To add a blank space between plots
84
+ """
85
+
86
+ items: list[ggplot | Compose]
87
+ """
88
+ The objects to be arranged (composed).
89
+ """
90
+
91
+ # These are created in the _create_figure method
92
+ figure: Figure = field(init=False, repr=False)
93
+ plotspecs: list[plotspec] = field(init=False, repr=False)
94
+ gridspec: p9GridSpec = field(init=False, repr=False)
95
+
96
+ def __post_init__(self):
97
+ # The way we handle the plots has consequences that would
98
+ # prevent having a duplicate plot in the composition.
99
+ # Using copies prevents this.
100
+ self.items = [
101
+ op if isinstance(op, Compose) else deepcopy(op)
102
+ for op in self.items
103
+ ]
104
+
105
+ def __repr__(self):
106
+ """
107
+ repr
108
+
109
+ Notes
110
+ -----
111
+ Subclasses that are dataclasses should be declared with
112
+ `@dataclass(repr=False)`.
113
+ """
114
+ # knitr relies on __repr__ to automatically print the last object
115
+ # in a cell.
116
+ if is_knitr_engine():
117
+ self.show()
118
+ return ""
119
+ return super().__repr__()
120
+
121
+ @abc.abstractmethod
122
+ def __or__(self, rhs: ggplot | Compose) -> Compose:
123
+ """
124
+ Add rhs as a column
125
+ """
126
+
127
+ @abc.abstractmethod
128
+ def __truediv__(self, rhs: ggplot | Compose) -> Compose:
129
+ """
130
+ Add rhs as a row
131
+ """
132
+
133
+ def __add__(self, rhs: ggplot | Compose | PlotAddable) -> Compose:
134
+ """
135
+ Add rhs to the composition
136
+
137
+ Parameters
138
+ ----------
139
+ rhs:
140
+ What to add to the composition
141
+ """
142
+ from plotnine import ggplot
143
+
144
+ if not isinstance(rhs, (ggplot, Compose)):
145
+ cmp = deepcopy(self)
146
+ cmp.last_plot = cmp.last_plot + rhs
147
+ return cmp
148
+
149
+ t1, t2 = type(self).__name__, type(rhs).__name__
150
+ msg = f"unsupported operand type(s) for +: '{t1}' and '{t2}'"
151
+ raise TypeError(msg)
152
+
153
+ def __sub__(self, rhs: ggplot | Compose) -> Compose:
154
+ """
155
+ Add the rhs onto the composition
156
+
157
+ Parameters
158
+ ----------
159
+ rhs:
160
+ What to place besides the composition
161
+ """
162
+ from plotnine import ggplot
163
+
164
+ from . import Beside
165
+
166
+ if not isinstance(rhs, (ggplot, Compose)):
167
+ t1, t2 = type(self).__name__, type(rhs).__name__
168
+ msg = f"unsupported operand type(s) for -: '{t1}' and '{t2}'"
169
+ raise TypeError(msg)
170
+
171
+ return Beside([self, rhs])
172
+
173
+ def __and__(self, rhs: PlotAddable) -> Compose:
174
+ """
175
+ Add rhs to all plots in the composition
176
+
177
+ Parameters
178
+ ----------
179
+ rhs:
180
+ What to add.
181
+ """
182
+ self = deepcopy(self)
183
+
184
+ def add_other(cmp: Compose):
185
+ for i, item in enumerate(cmp):
186
+ if isinstance(item, Compose):
187
+ add_other(item)
188
+ else:
189
+ cmp[i] = item + copy(rhs)
190
+
191
+ add_other(self)
192
+ return self
193
+
194
+ def __mul__(self, rhs: PlotAddable) -> Compose:
195
+ """
196
+ Add rhs to the outermost nesting level of the composition
197
+
198
+ Parameters
199
+ ----------
200
+ rhs:
201
+ What to add.
202
+ """
203
+ from plotnine import ggplot
204
+
205
+ self = deepcopy(self)
206
+
207
+ for i, item in enumerate(self):
208
+ if isinstance(item, ggplot):
209
+ self[i] = item + copy(rhs)
210
+
211
+ return self
212
+
213
+ def __len__(self) -> int:
214
+ """
215
+ Number of operand
216
+ """
217
+ return len(self.items)
218
+
219
+ def __iter__(self) -> Iterator[ggplot | Compose]:
220
+ """
221
+ Return an iterable of all the items
222
+ """
223
+ return iter(self.items)
224
+
225
+ @overload
226
+ def __getitem__(self, index: int) -> ggplot | Compose: ...
227
+
228
+ @overload
229
+ def __getitem__(self, index: slice) -> list[ggplot | Compose]: ...
230
+
231
+ def __getitem__(
232
+ self,
233
+ index: int | slice,
234
+ ) -> ggplot | Compose | list[ggplot | Compose]:
235
+ return self.items[index]
236
+
237
+ def __setitem__(self, key, value):
238
+ self.items[key] = value
239
+
240
+ def _repr_mimebundle_(self, include=None, exclude=None) -> MimeBundle:
241
+ """
242
+ Return dynamic MIME bundle for composition display
243
+ """
244
+ ip = get_ipython()
245
+ format: FigureFormat = (
246
+ get_option("figure_format")
247
+ or (ip and ip.config.InlineBackend.get("figure_format"))
248
+ or "retina"
249
+ )
250
+
251
+ if format == "retina":
252
+ self = deepcopy(self)
253
+ self._to_retina()
254
+
255
+ buf = BytesIO()
256
+ self.save(buf, "png" if format == "retina" else format)
257
+ figure_size_px = self.last_plot.theme._figure_size_px
258
+ return get_mimebundle(buf.getvalue(), format, figure_size_px)
259
+
260
+ @property
261
+ def nrow(self) -> int:
262
+ """
263
+ Number of rows in the composition
264
+ """
265
+ return 0
266
+
267
+ @property
268
+ def ncol(self) -> int:
269
+ """
270
+ Number of cols in the composition
271
+ """
272
+ return 0
273
+
274
+ @property
275
+ def last_plot(self) -> ggplot:
276
+ """
277
+ Last plot added to the composition
278
+ """
279
+ from plotnine import ggplot
280
+
281
+ last_operand = self.items[-1]
282
+ if isinstance(last_operand, ggplot):
283
+ return last_operand
284
+ else:
285
+ return last_operand.last_plot
286
+
287
+ @last_plot.setter
288
+ def last_plot(self, plot: ggplot):
289
+ """
290
+ Replace the last plot in the composition
291
+ """
292
+ from plotnine import ggplot
293
+
294
+ last_operand = self.items[-1]
295
+ if isinstance(last_operand, ggplot):
296
+ self.items[-1] = plot
297
+ else:
298
+ last_operand.last_plot = plot
299
+
300
+ def __deepcopy__(self, memo):
301
+ """
302
+ Deep copy without copying the figure
303
+ """
304
+ cls = self.__class__
305
+ result = cls.__new__(cls)
306
+ memo[id(self)] = result
307
+ old = self.__dict__
308
+ new = result.__dict__
309
+
310
+ shallow = {"figure", "gridsspec", "__copy"}
311
+ for key, item in old.items():
312
+ if key in shallow:
313
+ new[key] = item
314
+ memo[id(new[key])] = new[key]
315
+ else:
316
+ new[key] = deepcopy(item, memo)
317
+
318
+ old["__copy"] = result
319
+
320
+ return result
321
+
322
+ def _to_retina(self):
323
+ from plotnine import ggplot
324
+
325
+ for item in self:
326
+ if isinstance(item, ggplot):
327
+ item.theme = item.theme.to_retina()
328
+ else:
329
+ item._to_retina()
330
+
331
+ def _create_gridspec(self, figure, nest_into):
332
+ """
333
+ Create the gridspec for this composition
334
+ """
335
+ from plotnine._mpl.gridspec import p9GridSpec
336
+
337
+ self.gridspec = p9GridSpec(
338
+ self.nrow, self.ncol, figure, nest_into=nest_into
339
+ )
340
+
341
+ def _setup(self) -> Figure:
342
+ """
343
+ Setup this instance for the building process
344
+ """
345
+ if not hasattr(self, "figure"):
346
+ self._create_figure()
347
+
348
+ return self.figure
349
+
350
+ def _create_figure(self):
351
+ import matplotlib.pyplot as plt
352
+
353
+ from plotnine import ggplot
354
+ from plotnine._mpl.gridspec import p9GridSpec
355
+
356
+ def _make_plotspecs(
357
+ cmp: Compose, parent_gridspec: p9GridSpec | None
358
+ ) -> Generator[plotspec]:
359
+ """
360
+ Return the plot specification for each subplot in the composition
361
+ """
362
+ # This gridspec contains a composition group e.g.
363
+ # (p2 | p3) of p1 | (p2 | p3)
364
+ ss_or_none = parent_gridspec[0] if parent_gridspec else None
365
+ cmp._create_gridspec(self.figure, ss_or_none)
366
+
367
+ # Each subplot in the composition will contain one of:
368
+ # 1. A plot
369
+ # 2. A plot composition
370
+ # 3. Nothing
371
+ # Iterating over the gridspec yields the SubplotSpecs for each
372
+ # "subplot" in the grid. The SubplotSpec is the handle that
373
+ # allows us to set it up for a plot or to nest another gridspec
374
+ # in it.
375
+ for item, subplot_spec in zip(cmp, cmp.gridspec): # pyright: ignore[reportArgumentType]
376
+ if isinstance(item, ggplot):
377
+ yield plotspec(
378
+ item,
379
+ self.figure,
380
+ cmp.gridspec,
381
+ subplot_spec,
382
+ p9GridSpec(1, 1, self.figure, nest_into=subplot_spec),
383
+ )
384
+ elif item:
385
+ yield from _make_plotspecs(
386
+ item,
387
+ p9GridSpec(1, 1, self.figure, nest_into=subplot_spec),
388
+ )
389
+
390
+ self.figure = plt.figure()
391
+ self.plotspecs = list(_make_plotspecs(self, None))
392
+
393
+ def _draw_plots(self):
394
+ """
395
+ Draw all plots in the composition
396
+ """
397
+ for ps in self.plotspecs:
398
+ ps.plot.draw()
399
+
400
+ def show(self):
401
+ """
402
+ Display plot in the cells output
403
+
404
+ This function is called for its side-effects.
405
+ """
406
+ # Prevent against any modifications to the users
407
+ # ggplot object. Do the copy here as we may/may not
408
+ # assign a default theme
409
+ self = deepcopy(self)
410
+
411
+ if is_inline_backend() or is_quarto_environment():
412
+ from IPython.display import display
413
+
414
+ data, metadata = self._repr_mimebundle_()
415
+ display(data, metadata=metadata, raw=True)
416
+ else:
417
+ self.draw(show=True)
418
+
419
+ def draw(self, *, show: bool = False) -> Figure:
420
+ """
421
+ Render the arranged plots
422
+
423
+ Parameters
424
+ ----------
425
+ show :
426
+ Whether to show the plot.
427
+
428
+ Returns
429
+ -------
430
+ :
431
+ Matplotlib figure
432
+ """
433
+ from .._mpl.layout_manager import PlotnineCompositionLayoutEngine
434
+
435
+ with plot_composition_context(self, show):
436
+ figure = self._setup()
437
+ self._draw_plots()
438
+ figure.set_layout_engine(PlotnineCompositionLayoutEngine(self))
439
+ return figure
440
+
441
+ def save(
442
+ self,
443
+ filename: str | Path | BytesIO,
444
+ format: str | None = None,
445
+ dpi: int | None = None,
446
+ **kwargs,
447
+ ):
448
+ """
449
+ Save a composition as an image file
450
+
451
+ Parameters
452
+ ----------
453
+ filename :
454
+ File name to write the plot to. If not specified, a name
455
+ format :
456
+ Image format to use, automatically extract from
457
+ file name extension.
458
+ dpi :
459
+ DPI to use for raster graphics. If None, defaults to using
460
+ the `dpi` of theme to the first plot.
461
+ **kwargs :
462
+ These are ignored. Here to "softly" match the API of
463
+ `ggplot.save()`.
464
+ """
465
+ from plotnine import theme
466
+
467
+ # To set the dpi, we only need to change the dpi of
468
+ # the last plot and theme gets added to the last plot
469
+ plot = (self + theme(dpi=dpi)) if dpi else self
470
+ figure = plot.draw()
471
+ figure.savefig(filename, format=format)
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ from copy import deepcopy
4
+
5
+ from plotnine import element_rect, ggplot, theme, theme_void
6
+
7
+
8
+ class plot_spacer(ggplot):
9
+ """
10
+ Blank area as wide or as tall as a plot
11
+
12
+ Parameters
13
+ ----------
14
+ fill :
15
+ Background color. The default is a transparent area, but it
16
+ can be changed through this parameter.
17
+
18
+ The color can also be modified by adding a [](`~plotnine.theme`)
19
+ and setting the [](`~plotnine.themes.themeable.plot_background`).
20
+
21
+ See Also
22
+ --------
23
+ plotnine.composition.Beside : To arrange plots side by side
24
+ plotnine.composition.Stack : To arrange plots vertically
25
+ plotnine.composition.Compose : For more on composing plots
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ fill: (
31
+ str
32
+ | tuple[float, float, float]
33
+ | tuple[float, float, float, float]
34
+ | None
35
+ ) = None,
36
+ ):
37
+ super().__init__()
38
+ self.theme = theme_void()
39
+ if fill:
40
+ self.theme += theme(plot_background=element_rect(fill=fill))
41
+
42
+ def __add__(self, rhs) -> plot_spacer:
43
+ """
44
+ Add to spacer
45
+
46
+ All added objects are no ops except the `plot_background` in
47
+ in a theme.
48
+ """
49
+ self = deepcopy(self)
50
+ if isinstance(rhs, theme):
51
+ fill = rhs.getp(("plot_background", "facecolor"))
52
+ self.theme += theme(
53
+ plot_background=element_rect(fill=fill),
54
+ # When a spacer is the "last plot" in a composition,
55
+ # it is used to determine the figure size and dpi
56
+ # and therefore those aspects should be modifiable.
57
+ figure_size=rhs.getp("figure_size"),
58
+ dpi=rhs.getp("dpi"),
59
+ )
60
+ return self
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING
5
+
6
+ from ._compose import Compose
7
+
8
+ if TYPE_CHECKING:
9
+ from plotnine.ggplot import ggplot
10
+
11
+
12
+ @dataclass(repr=False)
13
+ class Stack(Compose):
14
+ """
15
+ Place plots or compositions on top of each other
16
+
17
+ **Usage**
18
+
19
+ plot / plot
20
+ plot / composition
21
+ composition / plot
22
+ composition / composition
23
+
24
+ Typically, you will use this class through the `/` operator.
25
+
26
+ See Also
27
+ --------
28
+ plotnine.composition.Beside : To arrange plots side by side
29
+ plotnine.composition.plot_spacer : To add a blank space between plots
30
+ plotnine.composition.Compose : For more on composing plots
31
+ """
32
+
33
+ @property
34
+ def nrow(self) -> int:
35
+ return len(self)
36
+
37
+ @property
38
+ def ncol(self) -> int:
39
+ return 1
40
+
41
+ def __truediv__(self, rhs: ggplot | Compose) -> Compose:
42
+ """
43
+ Add rhs as a row
44
+ """
45
+ # This is an adjacent div i.e. (DIV | rhs) so we collapse the
46
+ # operands into a single operation
47
+ return Stack([*self, rhs])
48
+
49
+ def __or__(self, rhs: ggplot | Compose) -> Compose:
50
+ """
51
+ Add rhs as a column
52
+ """
53
+ from ._beside import Beside
54
+
55
+ return Beside([self, rhs])
plotnine/coords/coord.py CHANGED
@@ -35,12 +35,12 @@ class coord:
35
35
  # if the coordinate system needs them
36
36
  params: dict[str, Any]
37
37
 
38
- def __radd__(self, plot: ggplot) -> ggplot:
38
+ def __radd__(self, other: ggplot) -> ggplot:
39
39
  """
40
40
  Add coordinates to ggplot object
41
41
  """
42
- plot.coordinates = copy(self)
43
- return plot
42
+ other.coordinates = copy(self)
43
+ return other
44
44
 
45
45
  def setup_data(self, data: list[pd.DataFrame]) -> list[pd.DataFrame]:
46
46
  """
plotnine/data/__init__.py CHANGED
@@ -8,6 +8,7 @@ import pandas as pd
8
8
  from pandas.api.types import CategoricalDtype
9
9
 
10
10
  __all__ = (
11
+ "anscombe_quartet",
11
12
  "diamonds",
12
13
  "economics",
13
14
  "economics_long",
@@ -42,6 +43,7 @@ penguins = pd.read_csv(DATA_DIR / "penguins.csv")
42
43
  luv_colours = pd.read_csv(DATA_DIR / "luv_colours.csv")
43
44
  faithfuld = pd.read_csv(DATA_DIR / "faithfuld.csv")
44
45
  faithful = pd.read_csv(DATA_DIR / "faithful.csv")
46
+ anscombe_quartet = pd.read_csv(DATA_DIR / "anscombe-quartet.csv")
45
47
 
46
48
  # For convenience to the user, we set some columns in these
47
49
  # dataframes to categoricals.
@@ -618,3 +620,32 @@ A data frame with 83 rows and 11 variables
618
620
  Additional variables order, conservation status and
619
621
  vore were added from wikipedia.
620
622
  """
623
+
624
+ anscombe_quartet.__doc__ = """
625
+ Anscombe's Quartet
626
+
627
+ ## Description
628
+
629
+ A dataset by Statistician Francis Anscombe that challenged the commonly held
630
+ belief that "numerical calculations are exact, but graphs are rough"
631
+ (Anscombe, 1973).
632
+
633
+ It comprises of 4 (the quartet!) small sub-datasets, each with 11 points that
634
+ have different distributions but nearly identical descriptive statistics.
635
+ It is perhaps the best argument for visualising data.
636
+
637
+ ## Format
638
+
639
+ A dataframe with 44 rows and 3 variables
640
+
641
+ | Column | Description |
642
+ |--------------|---------------------------------------|
643
+ | dataset | The Dataset |
644
+ | x | x |
645
+ | y | y |
646
+
647
+ ## References
648
+
649
+ Anscombe, F. J. (1973). "Graphs in Statistical Analysis".
650
+ American Statistician. 27 (1): 17–21.
651
+ """
@@ -0,0 +1,45 @@
1
+ dataset,x,y
2
+ I,10,8.04
3
+ I,8,6.95
4
+ I,13,7.58
5
+ I,9,8.81
6
+ I,11,8.33
7
+ I,14,9.96
8
+ I,6,7.24
9
+ I,4,4.26
10
+ I,12,10.84
11
+ I,7,4.82
12
+ I,5,5.68
13
+ II,10,9.14
14
+ II,8,8.14
15
+ II,13,8.74
16
+ II,9,8.77
17
+ II,11,9.26
18
+ II,14,8.1
19
+ II,6,6.13
20
+ II,4,3.1
21
+ II,12,9.13
22
+ II,7,7.26
23
+ II,5,4.74
24
+ III,10,7.46
25
+ III,8,6.77
26
+ III,13,12.74
27
+ III,9,7.11
28
+ III,11,7.81
29
+ III,14,8.84
30
+ III,6,6.08
31
+ III,4,5.39
32
+ III,12,8.15
33
+ III,7,6.42
34
+ III,5,5.73
35
+ IV,8,6.58
36
+ IV,8,5.76
37
+ IV,8,7.71
38
+ IV,8,8.84
39
+ IV,8,8.47
40
+ IV,8,7.04
41
+ IV,8,5.25
42
+ IV,19,12.5
43
+ IV,8,5.56
44
+ IV,8,7.91
45
+ IV,8,6.89