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.
Files changed (64) hide show
  1. ggh4x/__init__.py +140 -0
  2. ggh4x/_aimed_text_grob.py +432 -0
  3. ggh4x/_borrowed_ggplot2.py +273 -0
  4. ggh4x/_cli.py +84 -0
  5. ggh4x/_datasets.py +106 -0
  6. ggh4x/_download.py +111 -0
  7. ggh4x/_facet_helpers.py +313 -0
  8. ggh4x/_facet_utils.py +649 -0
  9. ggh4x/_gap_grobs.py +606 -0
  10. ggh4x/_registry.py +10 -0
  11. ggh4x/_rlang.py +93 -0
  12. ggh4x/_utils.py +150 -0
  13. ggh4x/_vctrs.py +233 -0
  14. ggh4x/conveniences.py +601 -0
  15. ggh4x/coord_axes_inside.py +380 -0
  16. ggh4x/element_part_rect.py +545 -0
  17. ggh4x/facet_grid2.py +1018 -0
  18. ggh4x/facet_manual.py +901 -0
  19. ggh4x/facet_nested.py +776 -0
  20. ggh4x/facet_nested_wrap.py +193 -0
  21. ggh4x/facet_wrap2.py +896 -0
  22. ggh4x/geom_box.py +536 -0
  23. ggh4x/geom_outline_point.py +444 -0
  24. ggh4x/geom_pointpath.py +259 -0
  25. ggh4x/geom_polygonraster.py +252 -0
  26. ggh4x/geom_rectrug.py +489 -0
  27. ggh4x/geom_text_aimed.py +279 -0
  28. ggh4x/guide_stringlegend.py +354 -0
  29. ggh4x/help_secondary.py +549 -0
  30. ggh4x/multiscale/__init__.py +51 -0
  31. ggh4x/multiscale/_multiscale_add.py +207 -0
  32. ggh4x/multiscale/scale_listed.py +167 -0
  33. ggh4x/multiscale/scale_manual.py +478 -0
  34. ggh4x/multiscale/scale_multi.py +393 -0
  35. ggh4x/panel_scales/__init__.py +58 -0
  36. ggh4x/panel_scales/at_panel.py +115 -0
  37. ggh4x/panel_scales/facetted_pos_scales.py +647 -0
  38. ggh4x/panel_scales/force_panelsize.py +411 -0
  39. ggh4x/panel_scales/scale_facet.py +222 -0
  40. ggh4x/position_disjoint_ranges.py +229 -0
  41. ggh4x/position_lineartrans.py +242 -0
  42. ggh4x/py.typed +0 -0
  43. ggh4x/resources/faithful.csv +273 -0
  44. ggh4x/resources/iris.csv +151 -0
  45. ggh4x/resources/mtcars.csv +33 -0
  46. ggh4x/resources/pressure.csv +20 -0
  47. ggh4x/resources/volcano.csv +87 -0
  48. ggh4x/save.py +255 -0
  49. ggh4x/stat_difference.py +388 -0
  50. ggh4x/stat_funxy.py +436 -0
  51. ggh4x/stat_rle.py +290 -0
  52. ggh4x/stat_rollingkernel.py +369 -0
  53. ggh4x/stat_theodensity.py +681 -0
  54. ggh4x/strip_nested.py +448 -0
  55. ggh4x/strip_split.py +687 -0
  56. ggh4x/strip_tag.py +636 -0
  57. ggh4x/strip_themed.py +232 -0
  58. ggh4x/strip_vanilla.py +1464 -0
  59. ggh4x/themes.py +31 -0
  60. ggh4x/themes_ggh4x.py +67 -0
  61. ggh4x_python-0.3.1.9000.dist-info/METADATA +40 -0
  62. ggh4x_python-0.3.1.9000.dist-info/RECORD +64 -0
  63. ggh4x_python-0.3.1.9000.dist-info/WHEEL +4 -0
  64. 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)