ggh4x-python 0.3.1.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.
- ggh4x/__init__.py +140 -0
- ggh4x/_aimed_text_grob.py +432 -0
- ggh4x/_borrowed_ggplot2.py +273 -0
- ggh4x/_cli.py +84 -0
- ggh4x/_datasets.py +106 -0
- ggh4x/_download.py +111 -0
- ggh4x/_facet_helpers.py +313 -0
- ggh4x/_facet_utils.py +649 -0
- ggh4x/_gap_grobs.py +606 -0
- ggh4x/_registry.py +10 -0
- ggh4x/_rlang.py +93 -0
- ggh4x/_utils.py +150 -0
- ggh4x/_vctrs.py +233 -0
- ggh4x/conveniences.py +601 -0
- ggh4x/coord_axes_inside.py +380 -0
- ggh4x/element_part_rect.py +545 -0
- ggh4x/facet_grid2.py +1018 -0
- ggh4x/facet_manual.py +901 -0
- ggh4x/facet_nested.py +776 -0
- ggh4x/facet_nested_wrap.py +193 -0
- ggh4x/facet_wrap2.py +896 -0
- ggh4x/geom_box.py +536 -0
- ggh4x/geom_outline_point.py +444 -0
- ggh4x/geom_pointpath.py +259 -0
- ggh4x/geom_polygonraster.py +252 -0
- ggh4x/geom_rectrug.py +489 -0
- ggh4x/geom_text_aimed.py +279 -0
- ggh4x/guide_stringlegend.py +354 -0
- ggh4x/help_secondary.py +549 -0
- ggh4x/multiscale/__init__.py +51 -0
- ggh4x/multiscale/_multiscale_add.py +207 -0
- ggh4x/multiscale/scale_listed.py +167 -0
- ggh4x/multiscale/scale_manual.py +478 -0
- ggh4x/multiscale/scale_multi.py +393 -0
- ggh4x/panel_scales/__init__.py +58 -0
- ggh4x/panel_scales/at_panel.py +115 -0
- ggh4x/panel_scales/facetted_pos_scales.py +647 -0
- ggh4x/panel_scales/force_panelsize.py +411 -0
- ggh4x/panel_scales/scale_facet.py +222 -0
- ggh4x/position_disjoint_ranges.py +229 -0
- ggh4x/position_lineartrans.py +242 -0
- ggh4x/py.typed +0 -0
- ggh4x/resources/faithful.csv +273 -0
- ggh4x/resources/iris.csv +151 -0
- ggh4x/resources/mtcars.csv +33 -0
- ggh4x/resources/pressure.csv +20 -0
- ggh4x/resources/volcano.csv +87 -0
- ggh4x/save.py +255 -0
- ggh4x/stat_difference.py +388 -0
- ggh4x/stat_funxy.py +436 -0
- ggh4x/stat_rle.py +290 -0
- ggh4x/stat_rollingkernel.py +369 -0
- ggh4x/stat_theodensity.py +681 -0
- ggh4x/strip_nested.py +448 -0
- ggh4x/strip_split.py +687 -0
- ggh4x/strip_tag.py +636 -0
- ggh4x/strip_themed.py +232 -0
- ggh4x/strip_vanilla.py +1464 -0
- ggh4x/themes.py +31 -0
- ggh4x/themes_ggh4x.py +67 -0
- ggh4x_python-0.3.1.9000.dist-info/METADATA +40 -0
- ggh4x_python-0.3.1.9000.dist-info/RECORD +64 -0
- ggh4x_python-0.3.1.9000.dist-info/WHEEL +4 -0
- ggh4x_python-0.3.1.9000.dist-info/licenses/LICENSE +3 -0
ggh4x/_utils.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Small internal utilities (R source: utils.R, utils_grid.R).
|
|
2
|
+
|
|
3
|
+
Grob/unit measurement helpers used by strip and facet assembly. These delegate to grid_py's
|
|
4
|
+
snake_case conversion API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, List
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
from ._cli import cli_abort
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"width_cm",
|
|
17
|
+
"height_cm",
|
|
18
|
+
"seq_range",
|
|
19
|
+
"has_null_unit",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _is_grob(x: Any) -> bool:
|
|
24
|
+
from grid_py import is_grob
|
|
25
|
+
|
|
26
|
+
return bool(is_grob(x))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _is_unit(x: Any) -> bool:
|
|
30
|
+
from grid_py import is_unit
|
|
31
|
+
|
|
32
|
+
return bool(is_unit(x))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def width_cm(x: Any) -> float | np.ndarray | List[Any]:
|
|
36
|
+
"""Width in cm of a grob, unit, or list thereof, mirroring ``utils.R::width_cm``.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
x : grob | unit | list
|
|
41
|
+
A grid grob, a unit object, or a list of either.
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
float or numpy.ndarray or list
|
|
46
|
+
Width(s) in centimetres. A length-1 unit/grob collapses to a Python
|
|
47
|
+
``float``; a multi-element unit returns a ``numpy.ndarray`` (one value
|
|
48
|
+
per element), matching R's vectorised ``convertWidth(..., valueOnly=TRUE)``.
|
|
49
|
+
|
|
50
|
+
Raises
|
|
51
|
+
------
|
|
52
|
+
ValueError
|
|
53
|
+
If *x* is none of grob/unit/list.
|
|
54
|
+
"""
|
|
55
|
+
from grid_py import convert_width, grob_width
|
|
56
|
+
|
|
57
|
+
if _is_grob(x):
|
|
58
|
+
return float(convert_width(grob_width(x), "cm", valueOnly=True))
|
|
59
|
+
if _is_unit(x):
|
|
60
|
+
vals = np.asarray(convert_width(x, "cm", valueOnly=True), dtype=float)
|
|
61
|
+
if vals.size == 1:
|
|
62
|
+
return float(vals.reshape(-1)[0])
|
|
63
|
+
return vals
|
|
64
|
+
if isinstance(x, (list, tuple)):
|
|
65
|
+
return [width_cm(e) for e in x]
|
|
66
|
+
cli_abort(f"Unknown input: {type(x).__name__}.")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def height_cm(x: Any) -> float | np.ndarray | List[Any]:
|
|
70
|
+
"""Height in cm of a grob, unit, or list thereof, mirroring ``utils.R::height_cm``.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
x : grob | unit | list
|
|
75
|
+
A grid grob, a unit object, or a list of either.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
float or numpy.ndarray or list
|
|
80
|
+
Height(s) in centimetres. A length-1 unit/grob collapses to a Python
|
|
81
|
+
``float``; a multi-element unit returns a ``numpy.ndarray`` (one value
|
|
82
|
+
per element), matching R's vectorised ``convertHeight(..., valueOnly=TRUE)``.
|
|
83
|
+
|
|
84
|
+
Raises
|
|
85
|
+
------
|
|
86
|
+
ValueError
|
|
87
|
+
If *x* is none of grob/unit/list.
|
|
88
|
+
"""
|
|
89
|
+
from grid_py import convert_height, grob_height
|
|
90
|
+
|
|
91
|
+
if _is_grob(x):
|
|
92
|
+
return float(convert_height(grob_height(x), "cm", valueOnly=True))
|
|
93
|
+
if _is_unit(x):
|
|
94
|
+
vals = np.asarray(convert_height(x, "cm", valueOnly=True), dtype=float)
|
|
95
|
+
if vals.size == 1:
|
|
96
|
+
return float(vals.reshape(-1)[0])
|
|
97
|
+
return vals
|
|
98
|
+
if isinstance(x, (list, tuple)):
|
|
99
|
+
return [height_cm(e) for e in x]
|
|
100
|
+
cli_abort(f"Unknown input: {type(x).__name__}.")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def seq_range(dat: Any, step: float | None = None, length_out: int | None = None) -> np.ndarray:
|
|
104
|
+
"""Sequence over the data range, mirroring ``utils.R::seq_range`` (``seq.int(min, max, ...)``).
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
dat : array-like
|
|
109
|
+
Values whose min/max bound the sequence (NA ignored).
|
|
110
|
+
step : float, optional
|
|
111
|
+
Step size (``by`` in R).
|
|
112
|
+
length_out : int, optional
|
|
113
|
+
Number of points (``length.out`` in R).
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
numpy.ndarray
|
|
118
|
+
"""
|
|
119
|
+
arr = np.asarray(dat, dtype=float)
|
|
120
|
+
lo = np.nanmin(arr)
|
|
121
|
+
hi = np.nanmax(arr)
|
|
122
|
+
if length_out is not None:
|
|
123
|
+
return np.linspace(lo, hi, length_out)
|
|
124
|
+
if step is not None:
|
|
125
|
+
return np.arange(lo, hi + step / 2.0, step)
|
|
126
|
+
# R seq_range = seq.int(min, max, ...); with no step/length it is the unit
|
|
127
|
+
# step sequence min, min+1, ..., <= max (NOT just the two endpoints).
|
|
128
|
+
return np.arange(lo, hi + 0.5, 1.0)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def has_null_unit(x: Any) -> bool:
|
|
132
|
+
"""Test whether a unit object contains any ``"null"`` units, mirroring ``has_null_unit``.
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
x : unit
|
|
137
|
+
A grid unit (possibly compound/vector).
|
|
138
|
+
|
|
139
|
+
Returns
|
|
140
|
+
-------
|
|
141
|
+
bool
|
|
142
|
+
"""
|
|
143
|
+
from grid_py import unit_type
|
|
144
|
+
|
|
145
|
+
if x is None:
|
|
146
|
+
return False
|
|
147
|
+
types = unit_type(x)
|
|
148
|
+
if isinstance(types, str):
|
|
149
|
+
return types == "null"
|
|
150
|
+
return "null" in list(types)
|
ggh4x/_vctrs.py
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""vctrs shims (R source: vctrs usage in ggh4x).
|
|
2
|
+
|
|
3
|
+
Faithful Python reimplementations of the small set of ``vec_*`` helpers ggh4x uses for
|
|
4
|
+
data-frame and vector manipulation. Each mirrors the exact R semantics (ordering,
|
|
5
|
+
run-length, recycling) verified against a live ``vctrs`` session.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, List, Sequence
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
import pandas as pd
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"vec_interleave",
|
|
17
|
+
"vec_unrep",
|
|
18
|
+
"vec_rep_each",
|
|
19
|
+
"vec_match",
|
|
20
|
+
"vec_unique",
|
|
21
|
+
"vec_unique_count",
|
|
22
|
+
"vec_group_loc",
|
|
23
|
+
"vec_rbind",
|
|
24
|
+
"vec_recycle_common",
|
|
25
|
+
"data_frame0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def vec_interleave(*args: Sequence[Any]) -> np.ndarray:
|
|
30
|
+
"""Interleave vectors element-by-element, mirroring ``vctrs::vec_interleave``.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
*args : sequence
|
|
35
|
+
Vectors of length 1 or *n* (length-1 recycles; incompatible lengths
|
|
36
|
+
raise, matching R).
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
np.ndarray
|
|
41
|
+
``[a0, b0, ..., a1, b1, ...]`` order.
|
|
42
|
+
"""
|
|
43
|
+
if not args:
|
|
44
|
+
return np.array([])
|
|
45
|
+
# R: vec_interleave recycles via vec_recycle_common — only length-1 vectors
|
|
46
|
+
# recycle; incompatible lengths ERROR (rather than silently cycling, which
|
|
47
|
+
# np.resize would do).
|
|
48
|
+
arrays = vec_recycle_common(*args)
|
|
49
|
+
if not arrays or len(arrays[0]) == 0:
|
|
50
|
+
return np.array([])
|
|
51
|
+
return np.stack(arrays, axis=1).reshape(-1)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def vec_unrep(x: Sequence[Any]) -> pd.DataFrame:
|
|
55
|
+
"""Run-length encode, mirroring ``vctrs::vec_unrep``.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
x : sequence
|
|
60
|
+
Input vector.
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
pandas.DataFrame
|
|
65
|
+
Columns ``key`` (the run values, in order) and ``times`` (run lengths).
|
|
66
|
+
"""
|
|
67
|
+
arr = np.asarray(x, dtype=object)
|
|
68
|
+
n = len(arr)
|
|
69
|
+
if n == 0:
|
|
70
|
+
return pd.DataFrame({"key": pd.Series([], dtype=object), "times": pd.Series([], dtype=int)})
|
|
71
|
+
# boundaries where the value changes
|
|
72
|
+
change = np.empty(n, dtype=bool)
|
|
73
|
+
change[0] = True
|
|
74
|
+
change[1:] = arr[1:] != arr[:-1]
|
|
75
|
+
idx = np.flatnonzero(change)
|
|
76
|
+
keys = arr[idx]
|
|
77
|
+
times = np.diff(np.append(idx, n))
|
|
78
|
+
return pd.DataFrame({"key": keys, "times": times.astype(int)})
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def vec_rep_each(x: Sequence[Any], times: Sequence[int] | int) -> np.ndarray:
|
|
82
|
+
"""Repeat each element *times[i]* times, mirroring ``vctrs::vec_rep_each``.
|
|
83
|
+
|
|
84
|
+
Parameters
|
|
85
|
+
----------
|
|
86
|
+
x : sequence
|
|
87
|
+
Values to repeat.
|
|
88
|
+
times : sequence of int or int
|
|
89
|
+
Per-element repeat counts (or a scalar applied to all).
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
np.ndarray
|
|
94
|
+
Expanded vector.
|
|
95
|
+
"""
|
|
96
|
+
arr = np.asarray(x)
|
|
97
|
+
return np.repeat(arr, times)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def vec_match(needles: Sequence[Any], haystack: Sequence[Any]) -> np.ndarray:
|
|
101
|
+
"""First-match index of *needles* in *haystack*, mirroring ``vctrs::vec_match``.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
needles : sequence
|
|
106
|
+
Values to look up.
|
|
107
|
+
haystack : sequence
|
|
108
|
+
Table of unique-or-not reference values.
|
|
109
|
+
|
|
110
|
+
Returns
|
|
111
|
+
-------
|
|
112
|
+
np.ndarray
|
|
113
|
+
0-based indices of the first match (or -1 when absent). NB: R returns 1-based or
|
|
114
|
+
``NA``; callers that need R indices add 1.
|
|
115
|
+
"""
|
|
116
|
+
# R vec_match: first match, and it tolerates a DUPLICATED haystack
|
|
117
|
+
# (pd.Index.get_indexer raises on a non-unique index). NA matches NA;
|
|
118
|
+
# absent -> -1 (R returns NA; -1 is this port's documented sentinel).
|
|
119
|
+
first_pos: dict = {}
|
|
120
|
+
na_pos = -1
|
|
121
|
+
for i, v in enumerate(haystack):
|
|
122
|
+
if pd.isna(v):
|
|
123
|
+
if na_pos == -1:
|
|
124
|
+
na_pos = i
|
|
125
|
+
elif v not in first_pos:
|
|
126
|
+
first_pos[v] = i
|
|
127
|
+
out = [(na_pos if pd.isna(nd) else first_pos.get(nd, -1)) for nd in needles]
|
|
128
|
+
return np.array(out, dtype=int)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def vec_unique(x: Sequence[Any]) -> np.ndarray:
|
|
132
|
+
"""Unique values preserving first-appearance order, mirroring ``vctrs::vec_unique``.
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
x : sequence
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
np.ndarray
|
|
141
|
+
"""
|
|
142
|
+
return pd.unique(pd.Series(list(x)))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def vec_unique_count(x: Sequence[Any]) -> int:
|
|
146
|
+
"""Number of unique values, mirroring ``vctrs::vec_unique_count``.
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
x : sequence
|
|
151
|
+
|
|
152
|
+
Returns
|
|
153
|
+
-------
|
|
154
|
+
int
|
|
155
|
+
"""
|
|
156
|
+
return int(len(vec_unique(x)))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def vec_group_loc(x: Sequence[Any]) -> pd.DataFrame:
|
|
160
|
+
"""Group rows by value, mirroring ``vctrs::vec_group_loc``.
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
x : sequence
|
|
165
|
+
|
|
166
|
+
Returns
|
|
167
|
+
-------
|
|
168
|
+
pandas.DataFrame
|
|
169
|
+
Columns ``key`` (first-appearance order) and ``loc`` (0-based row indices per group).
|
|
170
|
+
"""
|
|
171
|
+
s = pd.Series(list(x))
|
|
172
|
+
keys = pd.unique(s)
|
|
173
|
+
loc = [np.flatnonzero((s == k).to_numpy()) for k in keys]
|
|
174
|
+
return pd.DataFrame({"key": list(keys), "loc": loc})
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def vec_rbind(*frames: pd.DataFrame) -> pd.DataFrame:
|
|
178
|
+
"""Row-bind data frames filling missing columns, mirroring ``vctrs::vec_rbind``.
|
|
179
|
+
|
|
180
|
+
Parameters
|
|
181
|
+
----------
|
|
182
|
+
*frames : pandas.DataFrame
|
|
183
|
+
|
|
184
|
+
Returns
|
|
185
|
+
-------
|
|
186
|
+
pandas.DataFrame
|
|
187
|
+
Column union; absent columns filled with ``NA``; row index reset.
|
|
188
|
+
"""
|
|
189
|
+
frames = [f for f in frames if f is not None and len(f.columns) > 0]
|
|
190
|
+
if not frames:
|
|
191
|
+
return pd.DataFrame()
|
|
192
|
+
return pd.concat(frames, axis=0, ignore_index=True, sort=False)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def vec_recycle_common(*args: Sequence[Any]) -> List[np.ndarray]:
|
|
196
|
+
"""Recycle vectors to a common length, mirroring ``vctrs::vec_recycle_common``.
|
|
197
|
+
|
|
198
|
+
Parameters
|
|
199
|
+
----------
|
|
200
|
+
*args : sequence
|
|
201
|
+
Vectors of length 1 or *n*.
|
|
202
|
+
|
|
203
|
+
Returns
|
|
204
|
+
-------
|
|
205
|
+
list of np.ndarray
|
|
206
|
+
All recycled to the common length *n*.
|
|
207
|
+
|
|
208
|
+
Raises
|
|
209
|
+
------
|
|
210
|
+
ValueError
|
|
211
|
+
If lengths are incompatible (not 1 and not *n*).
|
|
212
|
+
"""
|
|
213
|
+
arrays = [np.asarray(a) for a in args]
|
|
214
|
+
lengths = {len(a) for a in arrays if len(a) != 1}
|
|
215
|
+
if len(lengths) > 1:
|
|
216
|
+
raise ValueError(f"Incompatible lengths for recycling: {sorted(lengths)}")
|
|
217
|
+
n = lengths.pop() if lengths else (len(arrays[0]) if arrays else 0)
|
|
218
|
+
return [np.resize(a, n) if len(a) == 1 else a for a in arrays]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def data_frame0(**columns: Any) -> pd.DataFrame:
|
|
222
|
+
"""Construct a DataFrame, mirroring ggh4x's ``data_frame0`` (minimal name repair).
|
|
223
|
+
|
|
224
|
+
Parameters
|
|
225
|
+
----------
|
|
226
|
+
**columns : Any
|
|
227
|
+
Column name -> values.
|
|
228
|
+
|
|
229
|
+
Returns
|
|
230
|
+
-------
|
|
231
|
+
pandas.DataFrame
|
|
232
|
+
"""
|
|
233
|
+
return pd.DataFrame(columns)
|