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/_utils.py
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Internal utilities for ggplot2.
|
|
3
|
+
|
|
4
|
+
Replaces functionality from ``utilities.R``, ``compat-plyr.R``, ``bin.R``,
|
|
5
|
+
and ``grouping.R`` in the R package.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
import warnings
|
|
12
|
+
from typing import Any, Dict, List, Optional, Sequence, TypeVar, Union
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
import pandas as pd
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"remove_missing",
|
|
19
|
+
"resolution",
|
|
20
|
+
"snake_class",
|
|
21
|
+
"has_groups",
|
|
22
|
+
"empty",
|
|
23
|
+
"is_empty",
|
|
24
|
+
"try_fetch",
|
|
25
|
+
"compact",
|
|
26
|
+
"modify_list",
|
|
27
|
+
"data_frame",
|
|
28
|
+
"unique_default",
|
|
29
|
+
"rename",
|
|
30
|
+
"id_var",
|
|
31
|
+
"plyr_id",
|
|
32
|
+
"interleave",
|
|
33
|
+
"width_cm",
|
|
34
|
+
"height_cm",
|
|
35
|
+
"stapled_to_list",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
T = TypeVar("T")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# Missing-data helpers
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
def remove_missing(
|
|
46
|
+
df: pd.DataFrame,
|
|
47
|
+
vars: Optional[List[str]] = None,
|
|
48
|
+
na_rm: bool = False,
|
|
49
|
+
name: str = "",
|
|
50
|
+
finite: bool = False,
|
|
51
|
+
) -> pd.DataFrame:
|
|
52
|
+
"""Remove rows with missing (or non-finite) values from a DataFrame.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
df : pd.DataFrame
|
|
57
|
+
Input data.
|
|
58
|
+
vars : list of str, optional
|
|
59
|
+
Column names to check. If ``None``, all columns are checked.
|
|
60
|
+
na_rm : bool, optional
|
|
61
|
+
If ``False``, emit a warning when rows are removed but still
|
|
62
|
+
remove them. If ``True``, remove silently. Default ``False``.
|
|
63
|
+
name : str, optional
|
|
64
|
+
Name of the calling layer (used in warning messages).
|
|
65
|
+
finite : bool, optional
|
|
66
|
+
If ``True``, also remove rows where checked columns contain
|
|
67
|
+
``inf`` / ``-inf``. Default ``False``.
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
pd.DataFrame
|
|
72
|
+
A copy of *df* with offending rows removed.
|
|
73
|
+
"""
|
|
74
|
+
if df.empty:
|
|
75
|
+
return df
|
|
76
|
+
|
|
77
|
+
if vars is None:
|
|
78
|
+
check_cols = list(df.columns)
|
|
79
|
+
else:
|
|
80
|
+
check_cols = [c for c in vars if c in df.columns]
|
|
81
|
+
|
|
82
|
+
if not check_cols:
|
|
83
|
+
return df
|
|
84
|
+
|
|
85
|
+
if finite:
|
|
86
|
+
# Check for NA *and* non-finite in numeric columns.
|
|
87
|
+
mask = pd.Series(True, index=df.index)
|
|
88
|
+
for col in check_cols:
|
|
89
|
+
s = df[col]
|
|
90
|
+
if pd.api.types.is_numeric_dtype(s):
|
|
91
|
+
mask = mask & np.isfinite(s.to_numpy(dtype=float, na_value=np.nan))
|
|
92
|
+
else:
|
|
93
|
+
mask = mask & s.notna()
|
|
94
|
+
else:
|
|
95
|
+
mask = df[check_cols].notna().all(axis=1)
|
|
96
|
+
|
|
97
|
+
n_removed = int((~mask).sum())
|
|
98
|
+
if n_removed > 0 and not na_rm:
|
|
99
|
+
qual = "non-finite" if finite else "missing"
|
|
100
|
+
where = f" ({name})" if name else ""
|
|
101
|
+
warnings.warn(
|
|
102
|
+
f"Removed {n_removed} rows containing {qual} values{where}.",
|
|
103
|
+
UserWarning,
|
|
104
|
+
stacklevel=2,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return df.loc[mask].reset_index(drop=True)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
# Numeric helpers
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
def resolution(x: Any, zero: bool = True, discrete: bool = False) -> float:
|
|
115
|
+
"""Compute the resolution of a numeric vector.
|
|
116
|
+
|
|
117
|
+
The resolution is the smallest non-zero difference between adjacent
|
|
118
|
+
unique sorted values. This is useful for choosing default bin widths.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
x : array-like
|
|
123
|
+
Numeric values.
|
|
124
|
+
zero : bool, optional
|
|
125
|
+
If ``True`` (default), include zero in the set of differences so
|
|
126
|
+
that the result is at most 1 when there is only one unique value.
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
float
|
|
131
|
+
The resolution.
|
|
132
|
+
"""
|
|
133
|
+
if discrete:
|
|
134
|
+
return 1.0
|
|
135
|
+
x = np.asarray(x, dtype=float)
|
|
136
|
+
x = x[np.isfinite(x)]
|
|
137
|
+
if len(x) == 0:
|
|
138
|
+
return 1.0
|
|
139
|
+
|
|
140
|
+
x = np.sort(np.unique(x))
|
|
141
|
+
if len(x) == 1:
|
|
142
|
+
return 1.0 if zero else 0.0
|
|
143
|
+
|
|
144
|
+
diffs = np.diff(x)
|
|
145
|
+
if zero:
|
|
146
|
+
diffs = np.concatenate([[0.0], diffs])
|
|
147
|
+
diffs = diffs[diffs > 0]
|
|
148
|
+
if len(diffs) == 0:
|
|
149
|
+
return 1.0
|
|
150
|
+
return float(np.min(diffs))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
# String / naming helpers
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
def snake_class(x: Any) -> str:
|
|
158
|
+
"""Convert an object's class name to snake_case.
|
|
159
|
+
|
|
160
|
+
Parameters
|
|
161
|
+
----------
|
|
162
|
+
x : Any
|
|
163
|
+
An object (or class).
|
|
164
|
+
|
|
165
|
+
Returns
|
|
166
|
+
-------
|
|
167
|
+
str
|
|
168
|
+
The class name in ``snake_case``.
|
|
169
|
+
|
|
170
|
+
Examples
|
|
171
|
+
--------
|
|
172
|
+
>>> snake_class(pd.DataFrame())
|
|
173
|
+
'data_frame'
|
|
174
|
+
"""
|
|
175
|
+
name = type(x).__name__ if not isinstance(x, type) else x.__name__
|
|
176
|
+
# Insert underscore before uppercase letters that follow a lowercase
|
|
177
|
+
# letter or another uppercase followed by a lowercase.
|
|
178
|
+
s1 = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", name)
|
|
179
|
+
s2 = re.sub(r"([a-z\d])([A-Z])", r"\1_\2", s1)
|
|
180
|
+
return s2.lower()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def rename(
|
|
184
|
+
x: Dict[str, Any],
|
|
185
|
+
mapping: Optional[Dict[str, str]] = None,
|
|
186
|
+
**kwargs: str,
|
|
187
|
+
) -> Dict[str, Any]:
|
|
188
|
+
"""Rename keys in a dictionary.
|
|
189
|
+
|
|
190
|
+
Parameters
|
|
191
|
+
----------
|
|
192
|
+
x : dict
|
|
193
|
+
Original dictionary.
|
|
194
|
+
mapping : dict, optional
|
|
195
|
+
``{old_name: new_name}`` pairs.
|
|
196
|
+
**kwargs : str
|
|
197
|
+
Additional ``old_name=new_name`` pairs (convenience).
|
|
198
|
+
|
|
199
|
+
Returns
|
|
200
|
+
-------
|
|
201
|
+
dict
|
|
202
|
+
A new dictionary with renamed keys.
|
|
203
|
+
"""
|
|
204
|
+
if mapping is None:
|
|
205
|
+
mapping = {}
|
|
206
|
+
mapping.update(kwargs)
|
|
207
|
+
return {mapping.get(k, k): v for k, v in x.items()}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# ---------------------------------------------------------------------------
|
|
211
|
+
# DataFrame / grouping utilities
|
|
212
|
+
# ---------------------------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
def has_groups(df: pd.DataFrame) -> bool:
|
|
215
|
+
"""Check whether a DataFrame has a ``"group"`` column with >1 group.
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
df : pd.DataFrame
|
|
220
|
+
Input data.
|
|
221
|
+
|
|
222
|
+
Returns
|
|
223
|
+
-------
|
|
224
|
+
bool
|
|
225
|
+
"""
|
|
226
|
+
if "group" not in df.columns:
|
|
227
|
+
return False
|
|
228
|
+
return int(df["group"].nunique()) > 1
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def empty(df: pd.DataFrame) -> bool:
|
|
232
|
+
"""Check whether a DataFrame is conceptually empty.
|
|
233
|
+
|
|
234
|
+
A DataFrame is considered empty if it has zero rows **or** zero
|
|
235
|
+
columns.
|
|
236
|
+
|
|
237
|
+
Parameters
|
|
238
|
+
----------
|
|
239
|
+
df : pd.DataFrame
|
|
240
|
+
Input data.
|
|
241
|
+
|
|
242
|
+
Returns
|
|
243
|
+
-------
|
|
244
|
+
bool
|
|
245
|
+
"""
|
|
246
|
+
return df.shape[0] == 0 or df.shape[1] == 0
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def is_empty(x: Any) -> bool:
|
|
250
|
+
"""Check whether an object is empty.
|
|
251
|
+
|
|
252
|
+
Parameters
|
|
253
|
+
----------
|
|
254
|
+
x : Any
|
|
255
|
+
Object to test. Works with DataFrames, dicts, lists, and other
|
|
256
|
+
sized objects.
|
|
257
|
+
|
|
258
|
+
Returns
|
|
259
|
+
-------
|
|
260
|
+
bool
|
|
261
|
+
"""
|
|
262
|
+
if isinstance(x, pd.DataFrame):
|
|
263
|
+
return empty(x)
|
|
264
|
+
if x is None:
|
|
265
|
+
return True
|
|
266
|
+
try:
|
|
267
|
+
return len(x) == 0 # type: ignore[arg-type]
|
|
268
|
+
except TypeError:
|
|
269
|
+
return False
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def data_frame(**kwargs: Any) -> pd.DataFrame:
|
|
273
|
+
"""Create a ``pd.DataFrame`` from keyword arguments.
|
|
274
|
+
|
|
275
|
+
Each keyword becomes a column. Scalar values are broadcast to match
|
|
276
|
+
the length of the longest array-like argument.
|
|
277
|
+
|
|
278
|
+
Parameters
|
|
279
|
+
----------
|
|
280
|
+
**kwargs : Any
|
|
281
|
+
Column name/value pairs.
|
|
282
|
+
|
|
283
|
+
Returns
|
|
284
|
+
-------
|
|
285
|
+
pd.DataFrame
|
|
286
|
+
"""
|
|
287
|
+
return pd.DataFrame(kwargs)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def unique_default(x: Any) -> np.ndarray:
|
|
291
|
+
"""Return unique values preserving the order of first occurrence.
|
|
292
|
+
|
|
293
|
+
Parameters
|
|
294
|
+
----------
|
|
295
|
+
x : array-like
|
|
296
|
+
Input values.
|
|
297
|
+
|
|
298
|
+
Returns
|
|
299
|
+
-------
|
|
300
|
+
np.ndarray
|
|
301
|
+
"""
|
|
302
|
+
x = np.asarray(x)
|
|
303
|
+
_, idx = np.unique(x, return_index=True)
|
|
304
|
+
return x[np.sort(idx)]
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def id_var(x: Any) -> np.ndarray:
|
|
308
|
+
"""Compute integer group IDs for a single variable.
|
|
309
|
+
|
|
310
|
+
Parameters
|
|
311
|
+
----------
|
|
312
|
+
x : array-like
|
|
313
|
+
Values (may be numeric, string, or categorical).
|
|
314
|
+
|
|
315
|
+
Returns
|
|
316
|
+
-------
|
|
317
|
+
np.ndarray
|
|
318
|
+
Integer array of 1-based group IDs, one per element.
|
|
319
|
+
"""
|
|
320
|
+
x = np.asarray(x)
|
|
321
|
+
uniques, inverse = np.unique(x, return_inverse=True)
|
|
322
|
+
return inverse + 1 # 1-based to match R convention
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def plyr_id(
|
|
326
|
+
df: pd.DataFrame,
|
|
327
|
+
drop: bool = False,
|
|
328
|
+
) -> np.ndarray:
|
|
329
|
+
"""Compute interaction-style group IDs across multiple columns.
|
|
330
|
+
|
|
331
|
+
Mimics ``plyr::id()`` — each unique combination of values across all
|
|
332
|
+
columns of *df* receives a unique integer ID (1-based).
|
|
333
|
+
|
|
334
|
+
Parameters
|
|
335
|
+
----------
|
|
336
|
+
df : pd.DataFrame
|
|
337
|
+
Data with columns to interact.
|
|
338
|
+
drop : bool, optional
|
|
339
|
+
If ``True``, re-number IDs so that unused combinations are
|
|
340
|
+
removed. Default ``False``.
|
|
341
|
+
|
|
342
|
+
Returns
|
|
343
|
+
-------
|
|
344
|
+
np.ndarray
|
|
345
|
+
1-based integer group IDs with length ``len(df)``.
|
|
346
|
+
"""
|
|
347
|
+
if df.shape[1] == 0 or df.shape[0] == 0:
|
|
348
|
+
return np.ones(len(df), dtype=int)
|
|
349
|
+
|
|
350
|
+
# Build a single interaction key using tuples.
|
|
351
|
+
cols = [df.iloc[:, i].to_numpy() for i in range(df.shape[1])]
|
|
352
|
+
keys = list(zip(*cols))
|
|
353
|
+
|
|
354
|
+
# Map each unique key to an integer.
|
|
355
|
+
seen: Dict[Any, int] = {}
|
|
356
|
+
ids = np.empty(len(keys), dtype=int)
|
|
357
|
+
counter = 0
|
|
358
|
+
for i, k in enumerate(keys):
|
|
359
|
+
if k not in seen:
|
|
360
|
+
counter += 1
|
|
361
|
+
seen[k] = counter
|
|
362
|
+
ids[i] = seen[k]
|
|
363
|
+
return ids
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
# ---------------------------------------------------------------------------
|
|
367
|
+
# Dict / list helpers
|
|
368
|
+
# ---------------------------------------------------------------------------
|
|
369
|
+
|
|
370
|
+
def try_fetch(expr: Any, default: Any = None) -> Any:
|
|
371
|
+
"""Execute a callable and return *default* on any exception.
|
|
372
|
+
|
|
373
|
+
If *expr* is not callable it is returned as-is.
|
|
374
|
+
|
|
375
|
+
Parameters
|
|
376
|
+
----------
|
|
377
|
+
expr : callable or Any
|
|
378
|
+
A zero-argument callable, or a plain value.
|
|
379
|
+
default : Any, optional
|
|
380
|
+
Value to return if *expr* raises.
|
|
381
|
+
|
|
382
|
+
Returns
|
|
383
|
+
-------
|
|
384
|
+
Any
|
|
385
|
+
"""
|
|
386
|
+
if callable(expr):
|
|
387
|
+
try:
|
|
388
|
+
return expr()
|
|
389
|
+
except Exception:
|
|
390
|
+
return default
|
|
391
|
+
return expr
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def compact(x: Dict[str, Any]) -> Dict[str, Any]:
|
|
395
|
+
"""Remove ``None`` values from a dictionary.
|
|
396
|
+
|
|
397
|
+
Parameters
|
|
398
|
+
----------
|
|
399
|
+
x : dict
|
|
400
|
+
Input dictionary.
|
|
401
|
+
|
|
402
|
+
Returns
|
|
403
|
+
-------
|
|
404
|
+
dict
|
|
405
|
+
A new dictionary with all ``None``-valued entries removed.
|
|
406
|
+
"""
|
|
407
|
+
return {k: v for k, v in x.items() if v is not None}
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def modify_list(
|
|
411
|
+
old: Dict[str, Any],
|
|
412
|
+
new: Dict[str, Any],
|
|
413
|
+
) -> Dict[str, Any]:
|
|
414
|
+
"""Merge two dictionaries, with *new* overriding *old*.
|
|
415
|
+
|
|
416
|
+
Parameters
|
|
417
|
+
----------
|
|
418
|
+
old : dict
|
|
419
|
+
Base dictionary.
|
|
420
|
+
new : dict
|
|
421
|
+
Override dictionary. Keys whose values are ``None`` will
|
|
422
|
+
**remove** the corresponding key from the result (mirroring
|
|
423
|
+
``utils::modifyList`` in R).
|
|
424
|
+
|
|
425
|
+
Returns
|
|
426
|
+
-------
|
|
427
|
+
dict
|
|
428
|
+
Merged dictionary (a fresh copy).
|
|
429
|
+
"""
|
|
430
|
+
result = dict(old)
|
|
431
|
+
for k, v in new.items():
|
|
432
|
+
if v is None:
|
|
433
|
+
result.pop(k, None)
|
|
434
|
+
else:
|
|
435
|
+
result[k] = v
|
|
436
|
+
return result
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
# ---------------------------------------------------------------------------
|
|
440
|
+
# Sequence helpers
|
|
441
|
+
# ---------------------------------------------------------------------------
|
|
442
|
+
|
|
443
|
+
def interleave(*args: Sequence[Any]) -> list:
|
|
444
|
+
"""Interleave elements from multiple sequences.
|
|
445
|
+
|
|
446
|
+
Parameters
|
|
447
|
+
----------
|
|
448
|
+
*args : Sequence
|
|
449
|
+
Input sequences. They should all have the same length.
|
|
450
|
+
|
|
451
|
+
Returns
|
|
452
|
+
-------
|
|
453
|
+
list
|
|
454
|
+
A flat list with elements taken round-robin from each input.
|
|
455
|
+
|
|
456
|
+
Examples
|
|
457
|
+
--------
|
|
458
|
+
>>> interleave([1, 2, 3], [10, 20, 30])
|
|
459
|
+
[1, 10, 2, 20, 3, 30]
|
|
460
|
+
"""
|
|
461
|
+
if not args:
|
|
462
|
+
return []
|
|
463
|
+
result: list = []
|
|
464
|
+
max_len = max(len(a) for a in args)
|
|
465
|
+
for i in range(max_len):
|
|
466
|
+
for a in args:
|
|
467
|
+
if i < len(a):
|
|
468
|
+
result.append(a[i])
|
|
469
|
+
return result
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
# ---------------------------------------------------------------------------
|
|
473
|
+
# Unit conversion helpers
|
|
474
|
+
# ---------------------------------------------------------------------------
|
|
475
|
+
|
|
476
|
+
def width_cm(x: Any) -> Union[float, np.ndarray]:
|
|
477
|
+
"""Convert a grid unit to centimetres (width / x-axis).
|
|
478
|
+
|
|
479
|
+
Parameters
|
|
480
|
+
----------
|
|
481
|
+
x : Unit or numeric
|
|
482
|
+
A ``grid_py.Unit`` object or a plain number (assumed to be in
|
|
483
|
+
centimetres already).
|
|
484
|
+
|
|
485
|
+
Returns
|
|
486
|
+
-------
|
|
487
|
+
float or np.ndarray
|
|
488
|
+
The width in centimetres.
|
|
489
|
+
"""
|
|
490
|
+
from grid_py import Unit, convert_unit
|
|
491
|
+
|
|
492
|
+
if isinstance(x, Unit):
|
|
493
|
+
result = convert_unit(x, "cm", axisFrom="x", typeFrom="dimension", valueOnly=True)
|
|
494
|
+
return result
|
|
495
|
+
return float(x) if np.isscalar(x) else np.asarray(x, dtype=float)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def height_cm(x: Any) -> Union[float, np.ndarray]:
|
|
499
|
+
"""Convert a grid unit to centimetres (height / y-axis).
|
|
500
|
+
|
|
501
|
+
Parameters
|
|
502
|
+
----------
|
|
503
|
+
x : Unit or numeric
|
|
504
|
+
A ``grid_py.Unit`` object or a plain number (assumed to be in
|
|
505
|
+
centimetres already).
|
|
506
|
+
|
|
507
|
+
Returns
|
|
508
|
+
-------
|
|
509
|
+
float or np.ndarray
|
|
510
|
+
The height in centimetres.
|
|
511
|
+
"""
|
|
512
|
+
from grid_py import Unit, convert_unit
|
|
513
|
+
|
|
514
|
+
if isinstance(x, Unit):
|
|
515
|
+
result = convert_unit(x, "cm", axisFrom="y", typeFrom="dimension", valueOnly=True)
|
|
516
|
+
return result
|
|
517
|
+
return float(x) if np.isscalar(x) else np.asarray(x, dtype=float)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
# ---------------------------------------------------------------------------
|
|
521
|
+
# Miscellaneous
|
|
522
|
+
# ---------------------------------------------------------------------------
|
|
523
|
+
|
|
524
|
+
def stapled_to_list(x: Any) -> list:
|
|
525
|
+
"""Convert a "stapled" object to a plain list.
|
|
526
|
+
|
|
527
|
+
In R, ``vctrs::vec_proxy()`` sometimes returns stapled vectors. In
|
|
528
|
+
Python this is a no-op — if *x* is already a list it is returned
|
|
529
|
+
unchanged; otherwise it is wrapped in a list.
|
|
530
|
+
|
|
531
|
+
Parameters
|
|
532
|
+
----------
|
|
533
|
+
x : Any
|
|
534
|
+
Object to convert.
|
|
535
|
+
|
|
536
|
+
Returns
|
|
537
|
+
-------
|
|
538
|
+
list
|
|
539
|
+
"""
|
|
540
|
+
if isinstance(x, list):
|
|
541
|
+
return x
|
|
542
|
+
if x is None:
|
|
543
|
+
return []
|
|
544
|
+
return [x]
|