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.
- plotnine/__init__.py +2 -0
- plotnine/_mpl/layout_manager/_engine.py +1 -1
- plotnine/_mpl/layout_manager/_layout_items.py +126 -41
- plotnine/_mpl/layout_manager/_layout_tree.py +712 -314
- plotnine/_mpl/layout_manager/_spaces.py +305 -101
- plotnine/_mpl/patches.py +70 -34
- plotnine/_mpl/text.py +144 -63
- plotnine/_mpl/utils.py +1 -1
- plotnine/_utils/__init__.py +50 -107
- plotnine/_utils/context.py +78 -2
- plotnine/_utils/ipython.py +35 -51
- plotnine/_utils/quarto.py +26 -0
- plotnine/_utils/yippie.py +115 -0
- plotnine/composition/__init__.py +11 -0
- plotnine/composition/_beside.py +55 -0
- plotnine/composition/_compose.py +471 -0
- plotnine/composition/_plot_spacer.py +60 -0
- plotnine/composition/_stack.py +55 -0
- plotnine/coords/coord.py +3 -3
- plotnine/data/__init__.py +31 -0
- plotnine/data/anscombe-quartet.csv +45 -0
- plotnine/doctools.py +4 -4
- plotnine/facets/facet.py +4 -4
- plotnine/facets/strips.py +17 -28
- plotnine/geoms/annotate.py +13 -13
- plotnine/geoms/annotation_logticks.py +7 -8
- plotnine/geoms/annotation_stripes.py +6 -6
- plotnine/geoms/geom.py +60 -27
- plotnine/geoms/geom_abline.py +3 -2
- plotnine/geoms/geom_area.py +2 -2
- plotnine/geoms/geom_bar.py +1 -0
- plotnine/geoms/geom_bin_2d.py +6 -2
- plotnine/geoms/geom_blank.py +0 -3
- plotnine/geoms/geom_boxplot.py +8 -4
- plotnine/geoms/geom_col.py +2 -2
- plotnine/geoms/geom_count.py +6 -2
- plotnine/geoms/geom_crossbar.py +3 -3
- plotnine/geoms/geom_density_2d.py +6 -2
- plotnine/geoms/geom_dotplot.py +2 -2
- plotnine/geoms/geom_errorbar.py +2 -2
- plotnine/geoms/geom_errorbarh.py +2 -2
- plotnine/geoms/geom_histogram.py +1 -1
- plotnine/geoms/geom_hline.py +3 -2
- plotnine/geoms/geom_linerange.py +2 -2
- plotnine/geoms/geom_map.py +5 -5
- plotnine/geoms/geom_path.py +11 -12
- plotnine/geoms/geom_point.py +4 -5
- plotnine/geoms/geom_pointdensity.py +4 -0
- plotnine/geoms/geom_pointrange.py +3 -5
- plotnine/geoms/geom_polygon.py +2 -3
- plotnine/geoms/geom_qq.py +4 -0
- plotnine/geoms/geom_qq_line.py +4 -0
- plotnine/geoms/geom_quantile.py +4 -0
- plotnine/geoms/geom_raster.py +4 -5
- plotnine/geoms/geom_rect.py +3 -4
- plotnine/geoms/geom_ribbon.py +7 -7
- plotnine/geoms/geom_rug.py +1 -1
- plotnine/geoms/geom_segment.py +2 -2
- plotnine/geoms/geom_sina.py +3 -3
- plotnine/geoms/geom_smooth.py +7 -3
- plotnine/geoms/geom_step.py +2 -2
- plotnine/geoms/geom_text.py +2 -3
- plotnine/geoms/geom_violin.py +8 -5
- plotnine/geoms/geom_vline.py +3 -2
- plotnine/ggplot.py +64 -85
- plotnine/guides/guide.py +7 -10
- plotnine/guides/guide_colorbar.py +3 -3
- plotnine/guides/guide_legend.py +3 -3
- plotnine/guides/guides.py +6 -6
- plotnine/helpers.py +49 -0
- plotnine/iapi.py +28 -5
- plotnine/labels.py +3 -3
- plotnine/layer.py +36 -19
- plotnine/mapping/_atomic.py +178 -0
- plotnine/mapping/_env.py +13 -2
- plotnine/mapping/_eval_environment.py +1 -1
- plotnine/mapping/aes.py +85 -49
- plotnine/scales/__init__.py +2 -0
- plotnine/scales/limits.py +7 -7
- plotnine/scales/scale.py +3 -3
- plotnine/scales/scale_color.py +82 -18
- plotnine/scales/scale_continuous.py +6 -4
- plotnine/scales/scale_datetime.py +28 -14
- plotnine/scales/scale_discrete.py +1 -1
- plotnine/scales/scale_identity.py +21 -2
- plotnine/scales/scale_manual.py +8 -2
- plotnine/scales/scale_xy.py +2 -2
- plotnine/stats/binning.py +4 -1
- plotnine/stats/smoothers.py +23 -36
- plotnine/stats/stat.py +20 -32
- plotnine/stats/stat_bin.py +6 -5
- plotnine/stats/stat_bin_2d.py +11 -9
- plotnine/stats/stat_bindot.py +13 -16
- plotnine/stats/stat_boxplot.py +6 -6
- plotnine/stats/stat_count.py +6 -9
- plotnine/stats/stat_density.py +7 -10
- plotnine/stats/stat_density_2d.py +12 -8
- plotnine/stats/stat_ecdf.py +7 -6
- plotnine/stats/stat_ellipse.py +9 -6
- plotnine/stats/stat_function.py +10 -8
- plotnine/stats/stat_hull.py +6 -3
- plotnine/stats/stat_identity.py +5 -2
- plotnine/stats/stat_pointdensity.py +5 -7
- plotnine/stats/stat_qq.py +46 -20
- plotnine/stats/stat_qq_line.py +16 -11
- plotnine/stats/stat_quantile.py +15 -9
- plotnine/stats/stat_sina.py +13 -15
- plotnine/stats/stat_smooth.py +8 -10
- plotnine/stats/stat_sum.py +5 -2
- plotnine/stats/stat_summary.py +7 -10
- plotnine/stats/stat_summary_bin.py +11 -14
- plotnine/stats/stat_unique.py +5 -2
- plotnine/stats/stat_ydensity.py +8 -11
- plotnine/themes/elements/__init__.py +2 -1
- plotnine/themes/elements/element_line.py +17 -9
- plotnine/themes/elements/margin.py +64 -1
- plotnine/themes/theme.py +9 -1
- plotnine/themes/theme_538.py +0 -1
- plotnine/themes/theme_bw.py +0 -1
- plotnine/themes/theme_dark.py +0 -1
- plotnine/themes/theme_gray.py +6 -5
- plotnine/themes/theme_light.py +1 -1
- plotnine/themes/theme_matplotlib.py +5 -5
- plotnine/themes/theme_seaborn.py +7 -4
- plotnine/themes/theme_void.py +9 -8
- plotnine/themes/theme_xkcd.py +0 -1
- plotnine/themes/themeable.py +109 -31
- plotnine/typing.py +17 -6
- plotnine/watermark.py +3 -3
- {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/METADATA +13 -6
- plotnine-0.15.2.dist-info/RECORD +221 -0
- {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/WHEEL +1 -1
- plotnine/plot_composition/__init__.py +0 -10
- plotnine/plot_composition/_compose.py +0 -436
- plotnine/plot_composition/_spacer.py +0 -32
- plotnine-0.15.0.dev3.dist-info/RECORD +0 -215
- /plotnine/{plot_composition → composition}/_plotspec.py +0 -0
- {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/licenses/LICENSE +0 -0
- {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,
|
|
38
|
+
def __radd__(self, other: ggplot) -> ggplot:
|
|
39
39
|
"""
|
|
40
40
|
Add coordinates to ggplot object
|
|
41
41
|
"""
|
|
42
|
-
|
|
43
|
-
return
|
|
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
|