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/limits.py
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scale-limit shortcuts for ggplot2.
|
|
3
|
+
|
|
4
|
+
Provides ``xlim()``, ``ylim()``, ``lims()``, and ``expand_limits()`` for
|
|
5
|
+
quickly setting axis and aesthetic limits.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, List, Optional, Sequence, Union
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
import pandas as pd
|
|
14
|
+
|
|
15
|
+
from ggplot2_py._compat import cli_abort
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"lims",
|
|
19
|
+
"xlim",
|
|
20
|
+
"ylim",
|
|
21
|
+
"expand_limits",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Internal helpers
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
def _make_scale(
|
|
30
|
+
scale_type: str,
|
|
31
|
+
var: str,
|
|
32
|
+
*,
|
|
33
|
+
limits: Any = None,
|
|
34
|
+
transform: str = "identity",
|
|
35
|
+
) -> Any:
|
|
36
|
+
"""Construct a scale via name look-up.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
scale_type : str
|
|
41
|
+
One of ``"continuous"``, ``"discrete"``, ``"date"``, ``"datetime"``.
|
|
42
|
+
var : str
|
|
43
|
+
Aesthetic variable name (e.g. ``"x"``, ``"y"``, ``"colour"``).
|
|
44
|
+
limits : object
|
|
45
|
+
The limits to pass to the scale constructor.
|
|
46
|
+
transform : str
|
|
47
|
+
Transform name (only meaningful for continuous scales).
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
Scale
|
|
52
|
+
A configured Scale instance.
|
|
53
|
+
"""
|
|
54
|
+
# Lazy imports to avoid circular dependency
|
|
55
|
+
import ggplot2_py.scales as _scales_mod
|
|
56
|
+
|
|
57
|
+
name = f"scale_{var}_{scale_type}"
|
|
58
|
+
scale_fn = getattr(_scales_mod, name, None)
|
|
59
|
+
if scale_fn is None:
|
|
60
|
+
cli_abort(f"Unknown scale function: {name}")
|
|
61
|
+
|
|
62
|
+
kwargs: dict = {"limits": limits}
|
|
63
|
+
if scale_type == "continuous":
|
|
64
|
+
kwargs["transform"] = transform
|
|
65
|
+
|
|
66
|
+
return scale_fn(**kwargs)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _limits_numeric(lims: Sequence, var: str) -> Any:
|
|
70
|
+
"""Create a continuous scale with numeric limits.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
lims : sequence of float
|
|
75
|
+
Two-element sequence ``[lower, upper]``. ``None``/``np.nan``
|
|
76
|
+
elements are treated as auto-computed.
|
|
77
|
+
var : str
|
|
78
|
+
Aesthetic name.
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
Scale
|
|
83
|
+
"""
|
|
84
|
+
if len(lims) != 2:
|
|
85
|
+
cli_abort(
|
|
86
|
+
f"`{var}` must be a two-element numeric sequence, "
|
|
87
|
+
f"got length {len(lims)}."
|
|
88
|
+
)
|
|
89
|
+
low, high = lims
|
|
90
|
+
# Determine transform
|
|
91
|
+
has_low = low is not None and not (isinstance(low, float) and np.isnan(low))
|
|
92
|
+
has_high = high is not None and not (isinstance(high, float) and np.isnan(high))
|
|
93
|
+
if has_low and has_high and low > high:
|
|
94
|
+
transform = "reverse"
|
|
95
|
+
else:
|
|
96
|
+
transform = "identity"
|
|
97
|
+
return _make_scale("continuous", var, limits=list(lims), transform=transform)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _limits_character(lims: Sequence[str], var: str) -> Any:
|
|
101
|
+
"""Create a discrete scale with character limits.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
lims : sequence of str
|
|
106
|
+
Factor / category levels.
|
|
107
|
+
var : str
|
|
108
|
+
Aesthetic name.
|
|
109
|
+
|
|
110
|
+
Returns
|
|
111
|
+
-------
|
|
112
|
+
Scale
|
|
113
|
+
"""
|
|
114
|
+
return _make_scale("discrete", var, limits=list(lims))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _limits_date(lims: Sequence, var: str) -> Any:
|
|
118
|
+
"""Create a date scale with date limits.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
lims : sequence
|
|
123
|
+
Two-element date sequence.
|
|
124
|
+
var : str
|
|
125
|
+
Aesthetic name.
|
|
126
|
+
|
|
127
|
+
Returns
|
|
128
|
+
-------
|
|
129
|
+
Scale
|
|
130
|
+
"""
|
|
131
|
+
if len(lims) != 2:
|
|
132
|
+
cli_abort(
|
|
133
|
+
f"`{var}` must be a two-element date sequence, "
|
|
134
|
+
f"got length {len(lims)}."
|
|
135
|
+
)
|
|
136
|
+
return _make_scale("date", var, limits=list(lims))
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _limits_dispatch(lims: Any, var: str) -> Any:
|
|
140
|
+
"""Dispatch to the correct limit constructor based on *lims* type.
|
|
141
|
+
|
|
142
|
+
Parameters
|
|
143
|
+
----------
|
|
144
|
+
lims : object
|
|
145
|
+
Limits specification.
|
|
146
|
+
var : str
|
|
147
|
+
Aesthetic name.
|
|
148
|
+
|
|
149
|
+
Returns
|
|
150
|
+
-------
|
|
151
|
+
Scale
|
|
152
|
+
"""
|
|
153
|
+
if isinstance(lims, (list, tuple)):
|
|
154
|
+
if len(lims) == 0:
|
|
155
|
+
cli_abort(f"`{var}` limits must be non-empty.")
|
|
156
|
+
first = next((v for v in lims if v is not None), None)
|
|
157
|
+
if first is None or isinstance(first, (int, float, np.integer, np.floating)):
|
|
158
|
+
return _limits_numeric(lims, var)
|
|
159
|
+
elif isinstance(first, str):
|
|
160
|
+
return _limits_character(lims, var)
|
|
161
|
+
elif isinstance(first, (pd.Timestamp, np.datetime64)):
|
|
162
|
+
return _limits_date(lims, var)
|
|
163
|
+
else:
|
|
164
|
+
# Try numeric
|
|
165
|
+
return _limits_numeric(lims, var)
|
|
166
|
+
elif isinstance(lims, np.ndarray):
|
|
167
|
+
return _limits_numeric(list(lims), var)
|
|
168
|
+
else:
|
|
169
|
+
cli_abort(
|
|
170
|
+
f"Cannot create limits for `{var}` from type {type(lims).__name__}."
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ---------------------------------------------------------------------------
|
|
175
|
+
# Public API
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
def xlim(*args: Any) -> Any:
|
|
179
|
+
"""Set x-axis limits.
|
|
180
|
+
|
|
181
|
+
Parameters
|
|
182
|
+
----------
|
|
183
|
+
*args
|
|
184
|
+
Either a single two-element sequence or two scalar values
|
|
185
|
+
specifying the lower and upper bounds. ``None`` leaves the
|
|
186
|
+
corresponding limit to be computed from the data.
|
|
187
|
+
|
|
188
|
+
Returns
|
|
189
|
+
-------
|
|
190
|
+
Scale
|
|
191
|
+
A position scale with the specified limits.
|
|
192
|
+
|
|
193
|
+
Examples
|
|
194
|
+
--------
|
|
195
|
+
>>> xlim(0, 10)
|
|
196
|
+
>>> xlim(10, 0) # reversed
|
|
197
|
+
>>> xlim(None, 20) # auto lower bound
|
|
198
|
+
"""
|
|
199
|
+
if len(args) == 1 and isinstance(args[0], (list, tuple, np.ndarray)):
|
|
200
|
+
values = list(args[0])
|
|
201
|
+
else:
|
|
202
|
+
values = list(args)
|
|
203
|
+
return _limits_dispatch(values, "x")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def ylim(*args: Any) -> Any:
|
|
207
|
+
"""Set y-axis limits.
|
|
208
|
+
|
|
209
|
+
Parameters
|
|
210
|
+
----------
|
|
211
|
+
*args
|
|
212
|
+
Either a single two-element sequence or two scalar values
|
|
213
|
+
specifying the lower and upper bounds.
|
|
214
|
+
|
|
215
|
+
Returns
|
|
216
|
+
-------
|
|
217
|
+
Scale
|
|
218
|
+
A position scale with the specified limits.
|
|
219
|
+
|
|
220
|
+
Examples
|
|
221
|
+
--------
|
|
222
|
+
>>> ylim(0, 50)
|
|
223
|
+
"""
|
|
224
|
+
if len(args) == 1 and isinstance(args[0], (list, tuple, np.ndarray)):
|
|
225
|
+
values = list(args[0])
|
|
226
|
+
else:
|
|
227
|
+
values = list(args)
|
|
228
|
+
return _limits_dispatch(values, "y")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def lims(**kwargs: Any) -> List[Any]:
|
|
232
|
+
"""Set limits for one or more aesthetics.
|
|
233
|
+
|
|
234
|
+
Parameters
|
|
235
|
+
----------
|
|
236
|
+
**kwargs
|
|
237
|
+
Keyword arguments mapping aesthetic names to limit specifications.
|
|
238
|
+
Numeric limits create continuous scales; string limits create
|
|
239
|
+
discrete scales.
|
|
240
|
+
|
|
241
|
+
Returns
|
|
242
|
+
-------
|
|
243
|
+
list of Scale
|
|
244
|
+
A list of scale objects suitable for adding to a ggplot via ``+``.
|
|
245
|
+
|
|
246
|
+
Examples
|
|
247
|
+
--------
|
|
248
|
+
>>> lims(x=(0, 10), y=(0, 50), colour=["red", "blue"])
|
|
249
|
+
"""
|
|
250
|
+
result: List[Any] = []
|
|
251
|
+
for var, lim in kwargs.items():
|
|
252
|
+
if isinstance(lim, (list, tuple)):
|
|
253
|
+
result.append(_limits_dispatch(lim, var))
|
|
254
|
+
else:
|
|
255
|
+
cli_abort(
|
|
256
|
+
f"Limits for `{var}` must be a list or tuple, "
|
|
257
|
+
f"got {type(lim).__name__}."
|
|
258
|
+
)
|
|
259
|
+
return result
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def expand_limits(**kwargs: Any) -> Any:
|
|
263
|
+
"""Expand plot limits to include specified values.
|
|
264
|
+
|
|
265
|
+
Creates a ``geom_blank`` layer with data containing the specified
|
|
266
|
+
values, which forces the scales to expand.
|
|
267
|
+
|
|
268
|
+
Parameters
|
|
269
|
+
----------
|
|
270
|
+
**kwargs
|
|
271
|
+
Aesthetic-name = value pairs. Each value (or list of values)
|
|
272
|
+
will be included in the scale training for the corresponding
|
|
273
|
+
aesthetic.
|
|
274
|
+
|
|
275
|
+
Returns
|
|
276
|
+
-------
|
|
277
|
+
Layer
|
|
278
|
+
A ``geom_blank`` layer with the specified data.
|
|
279
|
+
|
|
280
|
+
Examples
|
|
281
|
+
--------
|
|
282
|
+
>>> expand_limits(x=0, y=[0, 100])
|
|
283
|
+
"""
|
|
284
|
+
# Build a DataFrame from the kwargs, repeating shorter vectors
|
|
285
|
+
data: dict = {}
|
|
286
|
+
for k, v in kwargs.items():
|
|
287
|
+
if isinstance(v, (list, tuple, np.ndarray)):
|
|
288
|
+
data[k] = list(v)
|
|
289
|
+
elif isinstance(v, pd.Series):
|
|
290
|
+
data[k] = list(v)
|
|
291
|
+
else:
|
|
292
|
+
data[k] = [v]
|
|
293
|
+
|
|
294
|
+
# Repeat vectors up to the max length
|
|
295
|
+
if data:
|
|
296
|
+
max_len = max(len(v) for v in data.values())
|
|
297
|
+
for k, v in data.items():
|
|
298
|
+
if len(v) < max_len:
|
|
299
|
+
# Repeat cyclically
|
|
300
|
+
reps = (max_len // len(v)) + 1
|
|
301
|
+
data[k] = (v * reps)[:max_len]
|
|
302
|
+
|
|
303
|
+
df = pd.DataFrame(data)
|
|
304
|
+
|
|
305
|
+
# Lazy imports
|
|
306
|
+
from ggplot2_py.aes import aes, Mapping
|
|
307
|
+
|
|
308
|
+
# Build a mapping from the column names
|
|
309
|
+
mapping = aes(**{k: k for k in df.columns})
|
|
310
|
+
|
|
311
|
+
# Use geom_blank
|
|
312
|
+
from ggplot2_py.geom import geom_blank
|
|
313
|
+
|
|
314
|
+
return geom_blank(mapping=mapping, data=df, inherit_aes=False)
|