ggplot2-python 4.0.2.9000__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.
- ggplot2_py/__init__.py +852 -0
- ggplot2_py/_compat.py +475 -0
- ggplot2_py/_plugins.py +129 -0
- ggplot2_py/_utils.py +544 -0
- ggplot2_py/aes.py +586 -0
- ggplot2_py/annotation.py +540 -0
- ggplot2_py/coord.py +2108 -0
- ggplot2_py/coords/__init__.py +49 -0
- ggplot2_py/datasets.py +265 -0
- ggplot2_py/draw_key.py +454 -0
- ggplot2_py/facet.py +1456 -0
- ggplot2_py/fortify.py +95 -0
- ggplot2_py/geom.py +4516 -0
- ggplot2_py/geoms/__init__.py +12 -0
- ggplot2_py/ggproto.py +279 -0
- ggplot2_py/guide.py +2925 -0
- ggplot2_py/guide_axis.py +615 -0
- ggplot2_py/guide_colourbar.py +657 -0
- ggplot2_py/guide_legend.py +1061 -0
- ggplot2_py/guides/__init__.py +8 -0
- ggplot2_py/labeller.py +296 -0
- ggplot2_py/labels.py +309 -0
- ggplot2_py/layer.py +954 -0
- ggplot2_py/layout.py +754 -0
- ggplot2_py/limits.py +314 -0
- ggplot2_py/plot.py +1401 -0
- ggplot2_py/plot_render.py +866 -0
- ggplot2_py/position.py +1269 -0
- ggplot2_py/protocols.py +171 -0
- ggplot2_py/py.typed +0 -0
- ggplot2_py/qplot.py +233 -0
- ggplot2_py/resources/diamonds.csv +53941 -0
- ggplot2_py/resources/economics.csv +575 -0
- ggplot2_py/resources/economics_long.csv +2871 -0
- ggplot2_py/resources/faithfuld.csv +5626 -0
- ggplot2_py/resources/luv_colours.csv +658 -0
- ggplot2_py/resources/midwest.csv +438 -0
- ggplot2_py/resources/mpg.csv +235 -0
- ggplot2_py/resources/msleep.csv +84 -0
- ggplot2_py/resources/presidential.csv +13 -0
- ggplot2_py/resources/seals.csv +1156 -0
- ggplot2_py/resources/txhousing.csv +8603 -0
- ggplot2_py/save.py +316 -0
- ggplot2_py/scale.py +2727 -0
- ggplot2_py/scales/__init__.py +4252 -0
- ggplot2_py/stat.py +6071 -0
- ggplot2_py/stats/__init__.py +9 -0
- ggplot2_py/theme.py +490 -0
- ggplot2_py/theme_defaults.py +1350 -0
- ggplot2_py/theme_elements.py +2052 -0
- ggplot2_python-4.0.2.9000.dist-info/METADATA +179 -0
- ggplot2_python-4.0.2.9000.dist-info/RECORD +54 -0
- ggplot2_python-4.0.2.9000.dist-info/WHEEL +4 -0
- ggplot2_python-4.0.2.9000.dist-info/licenses/LICENSE +3 -0
ggplot2_py/aes.py
ADDED
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Aesthetic mapping system for ggplot2_py.
|
|
3
|
+
|
|
4
|
+
This module implements the core aesthetic-mapping machinery that translates
|
|
5
|
+
user-specified column references (and computed-variable references) into a
|
|
6
|
+
structured :class:`Mapping` object consumed by layers, scales, and stats.
|
|
7
|
+
|
|
8
|
+
In R's ggplot2, ``aes()`` uses non-standard evaluation (quosures). In this
|
|
9
|
+
Python port we use plain strings for column names and the helper classes
|
|
10
|
+
:class:`AfterStat`, :class:`AfterScale`, and :class:`Stage` for deferred
|
|
11
|
+
references.
|
|
12
|
+
|
|
13
|
+
Examples
|
|
14
|
+
--------
|
|
15
|
+
>>> from ggplot2_py.aes import aes, after_stat, after_scale, stage
|
|
16
|
+
>>> aes(x="displ", y="hwy", colour="class")
|
|
17
|
+
>>> aes(x="displ", y=after_stat("count"))
|
|
18
|
+
>>> aes(colour=stage(start="class", after_scale="fill"))
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from typing import (
|
|
24
|
+
Any,
|
|
25
|
+
Callable,
|
|
26
|
+
Dict,
|
|
27
|
+
Iterable,
|
|
28
|
+
List,
|
|
29
|
+
Optional,
|
|
30
|
+
Sequence,
|
|
31
|
+
Union,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"aes",
|
|
36
|
+
"after_stat",
|
|
37
|
+
"after_scale",
|
|
38
|
+
"stage",
|
|
39
|
+
"vars",
|
|
40
|
+
"is_mapping",
|
|
41
|
+
"standardise_aes_names",
|
|
42
|
+
"rename_aes",
|
|
43
|
+
"Mapping",
|
|
44
|
+
"AfterStat",
|
|
45
|
+
"AfterScale",
|
|
46
|
+
"Stage",
|
|
47
|
+
"AESTHETIC_ALIASES",
|
|
48
|
+
"eval_aes_value",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
# Aesthetic-alias lookup (R name -> canonical ggplot2 name)
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
AESTHETIC_ALIASES: Dict[str, str] = {
|
|
56
|
+
"color": "colour",
|
|
57
|
+
"pch": "shape",
|
|
58
|
+
"cex": "size",
|
|
59
|
+
"lwd": "linewidth",
|
|
60
|
+
"lty": "linetype",
|
|
61
|
+
"bg": "fill",
|
|
62
|
+
}
|
|
63
|
+
"""Mapping of common R aesthetic aliases to their canonical ggplot2 names."""
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
# Helper classes for deferred / staged aesthetics
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class AfterStat:
|
|
71
|
+
"""Reference to a variable computed by the stat layer.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
x : str or callable
|
|
76
|
+
Name of the computed-stat variable (e.g. ``"count"``, ``"density"``),
|
|
77
|
+
or a callable ``f(data: DataFrame) -> array`` evaluated after stat
|
|
78
|
+
computation.
|
|
79
|
+
|
|
80
|
+
Examples
|
|
81
|
+
--------
|
|
82
|
+
>>> AfterStat("count")
|
|
83
|
+
AfterStat('count')
|
|
84
|
+
>>> AfterStat(lambda d: d["count"] / d["count"].max())
|
|
85
|
+
AfterStat(<lambda>)
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
__slots__ = ("x",)
|
|
89
|
+
|
|
90
|
+
def __init__(self, x: Union[str, Callable[..., Any]]) -> None:
|
|
91
|
+
if not isinstance(x, str) and not callable(x):
|
|
92
|
+
raise TypeError(
|
|
93
|
+
f"AfterStat expects a str or callable, got {type(x).__name__}"
|
|
94
|
+
)
|
|
95
|
+
self.x = x
|
|
96
|
+
|
|
97
|
+
# -- repr / eq / hash ---------------------------------------------------
|
|
98
|
+
|
|
99
|
+
def __repr__(self) -> str:
|
|
100
|
+
if callable(self.x) and not isinstance(self.x, str):
|
|
101
|
+
name = getattr(self.x, "__name__", "<callable>")
|
|
102
|
+
return f"AfterStat({name})"
|
|
103
|
+
return f"AfterStat({self.x!r})"
|
|
104
|
+
|
|
105
|
+
def __eq__(self, other: object) -> bool:
|
|
106
|
+
if isinstance(other, AfterStat):
|
|
107
|
+
return self.x == other.x
|
|
108
|
+
return NotImplemented
|
|
109
|
+
|
|
110
|
+
def __hash__(self) -> int:
|
|
111
|
+
if callable(self.x) and not isinstance(self.x, str):
|
|
112
|
+
return hash(("AfterStat", id(self.x)))
|
|
113
|
+
return hash(("AfterStat", self.x))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class AfterScale:
|
|
117
|
+
"""Reference to a variable available after scale transformation.
|
|
118
|
+
|
|
119
|
+
Parameters
|
|
120
|
+
----------
|
|
121
|
+
x : str or callable
|
|
122
|
+
Name of the post-scale variable (e.g. ``"fill"``), or a callable
|
|
123
|
+
``f(data: DataFrame) -> array`` evaluated after scale mapping.
|
|
124
|
+
|
|
125
|
+
Examples
|
|
126
|
+
--------
|
|
127
|
+
>>> AfterScale("fill")
|
|
128
|
+
AfterScale('fill')
|
|
129
|
+
>>> AfterScale(lambda d: alpha(d["colour"], 0.3))
|
|
130
|
+
AfterScale(<lambda>)
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
__slots__ = ("x",)
|
|
134
|
+
|
|
135
|
+
def __init__(self, x: Union[str, Callable[..., Any]]) -> None:
|
|
136
|
+
if not isinstance(x, str) and not callable(x):
|
|
137
|
+
raise TypeError(
|
|
138
|
+
f"AfterScale expects a str or callable, got {type(x).__name__}"
|
|
139
|
+
)
|
|
140
|
+
self.x = x
|
|
141
|
+
|
|
142
|
+
def __repr__(self) -> str:
|
|
143
|
+
if callable(self.x) and not isinstance(self.x, str):
|
|
144
|
+
name = getattr(self.x, "__name__", "<callable>")
|
|
145
|
+
return f"AfterScale({name})"
|
|
146
|
+
return f"AfterScale({self.x!r})"
|
|
147
|
+
|
|
148
|
+
def __eq__(self, other: object) -> bool:
|
|
149
|
+
if isinstance(other, AfterScale):
|
|
150
|
+
return self.x == other.x
|
|
151
|
+
return NotImplemented
|
|
152
|
+
|
|
153
|
+
def __hash__(self) -> int:
|
|
154
|
+
if callable(self.x) and not isinstance(self.x, str):
|
|
155
|
+
return hash(("AfterScale", id(self.x)))
|
|
156
|
+
return hash(("AfterScale", self.x))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class Stage:
|
|
160
|
+
"""Staged aesthetic with potentially different values at each pipeline stage.
|
|
161
|
+
|
|
162
|
+
At most one of *start*, *after_stat*, or *after_scale* should be the
|
|
163
|
+
"primary" mapping; the others provide overrides for later stages.
|
|
164
|
+
|
|
165
|
+
Each slot accepts a column-name string, a callable
|
|
166
|
+
``f(data: DataFrame) -> array``, or a wrapper object.
|
|
167
|
+
|
|
168
|
+
Parameters
|
|
169
|
+
----------
|
|
170
|
+
start : str, callable, or None, optional
|
|
171
|
+
Column name or callable used at the initial data stage.
|
|
172
|
+
after_stat : str, callable, AfterStat, or None, optional
|
|
173
|
+
Reference used after the stat computation.
|
|
174
|
+
after_scale : str, callable, AfterScale, or None, optional
|
|
175
|
+
Reference used after scale transformation.
|
|
176
|
+
|
|
177
|
+
Examples
|
|
178
|
+
--------
|
|
179
|
+
>>> Stage(start="class", after_scale="fill")
|
|
180
|
+
Stage(start='class', after_stat=None, after_scale='fill')
|
|
181
|
+
>>> Stage(after_stat=lambda d: d["count"] / d["count"].max())
|
|
182
|
+
Stage(start=None, after_stat=AfterStat(<lambda>), after_scale=None)
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
__slots__ = ("start", "after_stat", "after_scale")
|
|
186
|
+
|
|
187
|
+
def __init__(
|
|
188
|
+
self,
|
|
189
|
+
start: Optional[Union[str, Callable[..., Any]]] = None,
|
|
190
|
+
after_stat: Optional[Union[str, Callable[..., Any], AfterStat]] = None,
|
|
191
|
+
after_scale: Optional[Union[str, Callable[..., Any], AfterScale]] = None,
|
|
192
|
+
) -> None:
|
|
193
|
+
self.start = start
|
|
194
|
+
|
|
195
|
+
# Normalise bare strings and callables to wrapper objects.
|
|
196
|
+
if isinstance(after_stat, str) or (
|
|
197
|
+
callable(after_stat) and not isinstance(after_stat, AfterStat)
|
|
198
|
+
):
|
|
199
|
+
after_stat = AfterStat(after_stat)
|
|
200
|
+
self.after_stat: Optional[AfterStat] = after_stat # type: ignore[assignment]
|
|
201
|
+
|
|
202
|
+
if isinstance(after_scale, str) or (
|
|
203
|
+
callable(after_scale) and not isinstance(after_scale, AfterScale)
|
|
204
|
+
):
|
|
205
|
+
after_scale = AfterScale(after_scale)
|
|
206
|
+
self.after_scale: Optional[AfterScale] = after_scale # type: ignore[assignment]
|
|
207
|
+
|
|
208
|
+
def __repr__(self) -> str:
|
|
209
|
+
return (
|
|
210
|
+
f"Stage(start={self.start!r}, after_stat={self.after_stat!r}, "
|
|
211
|
+
f"after_scale={self.after_scale!r})"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def __eq__(self, other: object) -> bool:
|
|
215
|
+
if isinstance(other, Stage):
|
|
216
|
+
return (
|
|
217
|
+
self.start == other.start
|
|
218
|
+
and self.after_stat == other.after_stat
|
|
219
|
+
and self.after_scale == other.after_scale
|
|
220
|
+
)
|
|
221
|
+
return NotImplemented
|
|
222
|
+
|
|
223
|
+
def __hash__(self) -> int:
|
|
224
|
+
return hash(("Stage", self.start, self.after_stat, self.after_scale))
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
# ---------------------------------------------------------------------------
|
|
228
|
+
# Mapping class (dict subclass)
|
|
229
|
+
# ---------------------------------------------------------------------------
|
|
230
|
+
|
|
231
|
+
#: Type alias for a single aesthetic value.
|
|
232
|
+
AesValue = Union[str, AfterStat, AfterScale, Stage, Callable[..., Any], int, float]
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class Mapping(dict):
|
|
236
|
+
"""Aesthetic mapping object, analogous to the output of R's ``aes()``.
|
|
237
|
+
|
|
238
|
+
A thin :class:`dict` subclass whose keys are canonical aesthetic names
|
|
239
|
+
(e.g. ``"x"``, ``"colour"``) and whose values describe how to map data
|
|
240
|
+
to that aesthetic.
|
|
241
|
+
|
|
242
|
+
Values may be:
|
|
243
|
+
|
|
244
|
+
* **str** — column-name reference (most common).
|
|
245
|
+
* **AfterStat** — computed variable from a stat layer.
|
|
246
|
+
* **AfterScale** — variable available after scale transformation.
|
|
247
|
+
* **Stage** — staged aesthetic with per-stage overrides.
|
|
248
|
+
* **callable** — a lambda or function applied to the data.
|
|
249
|
+
* **scalar** (int, float, str) — constant aesthetic value.
|
|
250
|
+
|
|
251
|
+
Examples
|
|
252
|
+
--------
|
|
253
|
+
>>> m = Mapping(x="displ", y="hwy", colour="class")
|
|
254
|
+
>>> m["x"]
|
|
255
|
+
'displ'
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
def __repr__(self) -> str:
|
|
259
|
+
inner = ", ".join(f"{k}={v!r}" for k, v in self.items())
|
|
260
|
+
return f"aes({inner})"
|
|
261
|
+
|
|
262
|
+
# Convenience: attribute access mirrors key access (read-only).
|
|
263
|
+
def __getattr__(self, name: str) -> Any:
|
|
264
|
+
try:
|
|
265
|
+
return self[name]
|
|
266
|
+
except KeyError:
|
|
267
|
+
raise AttributeError(
|
|
268
|
+
f"{type(self).__name__!r} object has no attribute {name!r}"
|
|
269
|
+
) from None
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# ---------------------------------------------------------------------------
|
|
273
|
+
# Centralised aesthetic-value evaluator
|
|
274
|
+
# ---------------------------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
import numpy as np
|
|
277
|
+
import pandas as pd
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def eval_aes_value(
|
|
281
|
+
val: Any,
|
|
282
|
+
data: "pd.DataFrame",
|
|
283
|
+
) -> Any:
|
|
284
|
+
"""Evaluate a single aesthetic value against a DataFrame.
|
|
285
|
+
|
|
286
|
+
This is the Python equivalent of R's ``eval_tidy(quo, data)`` pattern.
|
|
287
|
+
It handles the three kinds of aesthetic values that appear in
|
|
288
|
+
:class:`Mapping` objects:
|
|
289
|
+
|
|
290
|
+
* **str** — looked up as a column name in *data*.
|
|
291
|
+
* **callable** — called with *data* as the single argument.
|
|
292
|
+
* **scalar / other** — returned unchanged (will be broadcast by the
|
|
293
|
+
caller).
|
|
294
|
+
|
|
295
|
+
Parameters
|
|
296
|
+
----------
|
|
297
|
+
val : str, callable, or scalar
|
|
298
|
+
The aesthetic value to evaluate.
|
|
299
|
+
data : DataFrame
|
|
300
|
+
The layer data to evaluate against.
|
|
301
|
+
|
|
302
|
+
Returns
|
|
303
|
+
-------
|
|
304
|
+
numpy array, scalar, or ``None``
|
|
305
|
+
``None`` is returned when *val* is a string that does not match any
|
|
306
|
+
column in *data* (the column may appear in a later pipeline stage).
|
|
307
|
+
"""
|
|
308
|
+
if callable(val) and not isinstance(val, (str, type)):
|
|
309
|
+
result = val(data)
|
|
310
|
+
if isinstance(result, pd.Series):
|
|
311
|
+
return result.values
|
|
312
|
+
return result
|
|
313
|
+
elif isinstance(val, str):
|
|
314
|
+
if val in data.columns:
|
|
315
|
+
return data[val].values
|
|
316
|
+
return None # column not yet available
|
|
317
|
+
else:
|
|
318
|
+
return val # scalar — broadcast by caller
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
# ---------------------------------------------------------------------------
|
|
322
|
+
# Public convenience constructors
|
|
323
|
+
# ---------------------------------------------------------------------------
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def after_stat(x: Union[str, Callable[..., Any]]) -> AfterStat:
|
|
327
|
+
"""Create an :class:`AfterStat` reference.
|
|
328
|
+
|
|
329
|
+
Parameters
|
|
330
|
+
----------
|
|
331
|
+
x : str or callable
|
|
332
|
+
Name of a stat-computed variable (e.g. ``"count"``), or a callable
|
|
333
|
+
``f(data) -> array`` to be evaluated after stat computation.
|
|
334
|
+
|
|
335
|
+
Returns
|
|
336
|
+
-------
|
|
337
|
+
AfterStat
|
|
338
|
+
|
|
339
|
+
Examples
|
|
340
|
+
--------
|
|
341
|
+
>>> after_stat("density")
|
|
342
|
+
AfterStat('density')
|
|
343
|
+
>>> after_stat(lambda d: d["count"] / d["count"].max())
|
|
344
|
+
AfterStat(<lambda>)
|
|
345
|
+
"""
|
|
346
|
+
return AfterStat(x)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def after_scale(x: Union[str, Callable[..., Any]]) -> AfterScale:
|
|
350
|
+
"""Create an :class:`AfterScale` reference.
|
|
351
|
+
|
|
352
|
+
Parameters
|
|
353
|
+
----------
|
|
354
|
+
x : str or callable
|
|
355
|
+
Name of a post-scale variable (e.g. ``"fill"``), or a callable
|
|
356
|
+
``f(data) -> array`` to be evaluated after scale mapping.
|
|
357
|
+
|
|
358
|
+
Returns
|
|
359
|
+
-------
|
|
360
|
+
AfterScale
|
|
361
|
+
|
|
362
|
+
Examples
|
|
363
|
+
--------
|
|
364
|
+
>>> after_scale("fill")
|
|
365
|
+
AfterScale('fill')
|
|
366
|
+
>>> after_scale(lambda d: d["colour"].str.replace("FF", "80"))
|
|
367
|
+
AfterScale(<lambda>)
|
|
368
|
+
"""
|
|
369
|
+
return AfterScale(x)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def stage(
|
|
373
|
+
start: Optional[Union[str, Callable[..., Any]]] = None,
|
|
374
|
+
after_stat: Optional[Union[str, Callable[..., Any], AfterStat]] = None,
|
|
375
|
+
after_scale: Optional[Union[str, Callable[..., Any], AfterScale]] = None,
|
|
376
|
+
) -> Stage:
|
|
377
|
+
"""Create a :class:`Stage` aesthetic with per-stage overrides.
|
|
378
|
+
|
|
379
|
+
Parameters
|
|
380
|
+
----------
|
|
381
|
+
start : str, callable, or None, optional
|
|
382
|
+
Column name or callable used at the initial data stage.
|
|
383
|
+
after_stat : str, callable, AfterStat, or None, optional
|
|
384
|
+
Reference used after the stat computation.
|
|
385
|
+
after_scale : str, callable, AfterScale, or None, optional
|
|
386
|
+
Reference used after scale transformation.
|
|
387
|
+
|
|
388
|
+
Returns
|
|
389
|
+
-------
|
|
390
|
+
Stage
|
|
391
|
+
|
|
392
|
+
Examples
|
|
393
|
+
--------
|
|
394
|
+
>>> stage(start="class", after_scale="fill")
|
|
395
|
+
Stage(start='class', after_stat=None, after_scale='fill')
|
|
396
|
+
"""
|
|
397
|
+
return Stage(start=start, after_stat=after_stat, after_scale=after_scale)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
# ---------------------------------------------------------------------------
|
|
401
|
+
# aes() — main entry point
|
|
402
|
+
# ---------------------------------------------------------------------------
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def aes(
|
|
406
|
+
x: Optional[AesValue] = None,
|
|
407
|
+
y: Optional[AesValue] = None,
|
|
408
|
+
**kwargs: AesValue,
|
|
409
|
+
) -> Mapping:
|
|
410
|
+
"""Create an aesthetic mapping.
|
|
411
|
+
|
|
412
|
+
This is the Python equivalent of R's ``aes()`` function. Column
|
|
413
|
+
references are passed as plain strings; deferred references use
|
|
414
|
+
:func:`after_stat`, :func:`after_scale`, or :func:`stage`.
|
|
415
|
+
|
|
416
|
+
Parameters
|
|
417
|
+
----------
|
|
418
|
+
x : str, callable, or scalar, optional
|
|
419
|
+
Mapping for the *x* aesthetic.
|
|
420
|
+
y : str, callable, or scalar, optional
|
|
421
|
+
Mapping for the *y* aesthetic.
|
|
422
|
+
**kwargs
|
|
423
|
+
Additional aesthetic mappings. Names are automatically
|
|
424
|
+
standardised (e.g. ``color`` becomes ``colour``).
|
|
425
|
+
|
|
426
|
+
Returns
|
|
427
|
+
-------
|
|
428
|
+
Mapping
|
|
429
|
+
An aesthetic-mapping dictionary.
|
|
430
|
+
|
|
431
|
+
Examples
|
|
432
|
+
--------
|
|
433
|
+
>>> aes(x="displ", y="hwy")
|
|
434
|
+
aes(x='displ', y='hwy')
|
|
435
|
+
|
|
436
|
+
>>> aes(x="displ", y="hwy", color="class")
|
|
437
|
+
aes(x='displ', y='hwy', colour='class')
|
|
438
|
+
|
|
439
|
+
>>> aes(x="displ", y=after_stat("count"))
|
|
440
|
+
aes(x='displ', y=AfterStat('count'))
|
|
441
|
+
"""
|
|
442
|
+
raw: Dict[str, AesValue] = {}
|
|
443
|
+
if x is not None:
|
|
444
|
+
raw["x"] = x
|
|
445
|
+
if y is not None:
|
|
446
|
+
raw["y"] = y
|
|
447
|
+
raw.update(kwargs)
|
|
448
|
+
|
|
449
|
+
# Standardise names (e.g. "color" -> "colour").
|
|
450
|
+
result = Mapping()
|
|
451
|
+
for key, value in raw.items():
|
|
452
|
+
canonical = _standardise_single(key)
|
|
453
|
+
result[canonical] = value
|
|
454
|
+
|
|
455
|
+
return result
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
# ---------------------------------------------------------------------------
|
|
459
|
+
# Name standardisation
|
|
460
|
+
# ---------------------------------------------------------------------------
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def _standardise_single(name: str) -> str:
|
|
464
|
+
"""Canonicalise a single aesthetic name.
|
|
465
|
+
|
|
466
|
+
Parameters
|
|
467
|
+
----------
|
|
468
|
+
name : str
|
|
469
|
+
Raw aesthetic name.
|
|
470
|
+
|
|
471
|
+
Returns
|
|
472
|
+
-------
|
|
473
|
+
str
|
|
474
|
+
Canonical aesthetic name.
|
|
475
|
+
"""
|
|
476
|
+
return AESTHETIC_ALIASES.get(name, name)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def standardise_aes_names(aes_names: Iterable[str]) -> List[str]:
|
|
480
|
+
"""Standardise a sequence of aesthetic names to their canonical forms.
|
|
481
|
+
|
|
482
|
+
Parameters
|
|
483
|
+
----------
|
|
484
|
+
aes_names : Iterable[str]
|
|
485
|
+
Aesthetic names, possibly containing aliases.
|
|
486
|
+
|
|
487
|
+
Returns
|
|
488
|
+
-------
|
|
489
|
+
list of str
|
|
490
|
+
Canonical aesthetic names in the same order.
|
|
491
|
+
|
|
492
|
+
Examples
|
|
493
|
+
--------
|
|
494
|
+
>>> standardise_aes_names(["color", "lwd", "x"])
|
|
495
|
+
['colour', 'linewidth', 'x']
|
|
496
|
+
"""
|
|
497
|
+
return [_standardise_single(n) for n in aes_names]
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def rename_aes(mapping: Mapping) -> Mapping:
|
|
501
|
+
"""Return a copy of *mapping* with all keys standardised.
|
|
502
|
+
|
|
503
|
+
Parameters
|
|
504
|
+
----------
|
|
505
|
+
mapping : Mapping
|
|
506
|
+
An aesthetic mapping (or plain dict).
|
|
507
|
+
|
|
508
|
+
Returns
|
|
509
|
+
-------
|
|
510
|
+
Mapping
|
|
511
|
+
New mapping with canonical aesthetic names as keys.
|
|
512
|
+
|
|
513
|
+
Examples
|
|
514
|
+
--------
|
|
515
|
+
>>> rename_aes(Mapping(color="class"))
|
|
516
|
+
aes(colour='class')
|
|
517
|
+
"""
|
|
518
|
+
return Mapping(
|
|
519
|
+
{_standardise_single(k): v for k, v in mapping.items()}
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
# ---------------------------------------------------------------------------
|
|
524
|
+
# vars() helper (for facets)
|
|
525
|
+
# ---------------------------------------------------------------------------
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def vars(*args: str, **kwargs: str) -> List[str]:
|
|
529
|
+
"""Specify variables for faceting.
|
|
530
|
+
|
|
531
|
+
A thin helper that collects positional and keyword variable names into a
|
|
532
|
+
flat list of strings, suitable for passing to ``facet_wrap`` or
|
|
533
|
+
``facet_grid``.
|
|
534
|
+
|
|
535
|
+
Parameters
|
|
536
|
+
----------
|
|
537
|
+
*args : str
|
|
538
|
+
Variable names given positionally.
|
|
539
|
+
**kwargs : str
|
|
540
|
+
Variable names given as keyword arguments (values are used, keys
|
|
541
|
+
are ignored).
|
|
542
|
+
|
|
543
|
+
Returns
|
|
544
|
+
-------
|
|
545
|
+
list of str
|
|
546
|
+
Flat list of variable-name strings.
|
|
547
|
+
|
|
548
|
+
Examples
|
|
549
|
+
--------
|
|
550
|
+
>>> vars("cyl", "drv")
|
|
551
|
+
['cyl', 'drv']
|
|
552
|
+
|
|
553
|
+
>>> vars(rows="cyl")
|
|
554
|
+
['cyl']
|
|
555
|
+
"""
|
|
556
|
+
result: List[str] = list(args)
|
|
557
|
+
result.extend(kwargs.values())
|
|
558
|
+
return result
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
# ---------------------------------------------------------------------------
|
|
562
|
+
# Type guard
|
|
563
|
+
# ---------------------------------------------------------------------------
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def is_mapping(x: Any) -> bool:
|
|
567
|
+
"""Test whether *x* is an aesthetic :class:`Mapping`.
|
|
568
|
+
|
|
569
|
+
Parameters
|
|
570
|
+
----------
|
|
571
|
+
x : object
|
|
572
|
+
Object to test.
|
|
573
|
+
|
|
574
|
+
Returns
|
|
575
|
+
-------
|
|
576
|
+
bool
|
|
577
|
+
``True`` if *x* is a :class:`Mapping` instance.
|
|
578
|
+
|
|
579
|
+
Examples
|
|
580
|
+
--------
|
|
581
|
+
>>> is_mapping(aes(x="a"))
|
|
582
|
+
True
|
|
583
|
+
>>> is_mapping({"x": "a"})
|
|
584
|
+
False
|
|
585
|
+
"""
|
|
586
|
+
return isinstance(x, Mapping)
|