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/stat_funxy.py ADDED
@@ -0,0 +1,436 @@
1
+ """Apply a function to position coordinates per group.
2
+
3
+ Python port of ``stat_funxy.R`` from the R package **ggh4x**.
4
+
5
+ The "function xy" stat applies a user-supplied function to the ``x`` and
6
+ ``y`` aesthetics of a layer's positions, computed separately per group.
7
+ :func:`stat_centroid` and :func:`stat_midpoint` are convenience wrappers
8
+ that compute group centroids (means) and midpoints (``(min + max) / 2``)
9
+ respectively. :func:`stat_funxy` leaves the data unchanged by default but
10
+ can be supplied arbitrary functions and arguments.
11
+
12
+ R source
13
+ --------
14
+ ``ggh4x/R/stat_funxy.R`` -- :class:`StatFunxy`, :func:`stat_funxy`,
15
+ :func:`stat_centroid`, :func:`stat_midpoint`.
16
+
17
+ Notes
18
+ -----
19
+ This statistic makes only a minimal attempt at ensuring that the results
20
+ of calling both functions are of equal length. Results of length one are
21
+ recycled to match the longest result (mirroring
22
+ ``vctrs::vec_recycle_common``).
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ from typing import Any, Callable, Dict, Mapping, Optional, Sequence
28
+
29
+ import numpy as np
30
+ import pandas as pd
31
+
32
+ from ggplot2_py import Stat
33
+
34
+ from ._cli import cli_abort
35
+ from ._vctrs import data_frame0, vec_recycle_common
36
+
37
+ __all__ = [
38
+ "StatFunxy",
39
+ "stat_funxy",
40
+ "stat_centroid",
41
+ "stat_midpoint",
42
+ ]
43
+
44
+
45
+ # ---------------------------------------------------------------------------
46
+ # Lazy ``layer`` import (mirrors ggplot2_py.stat._layer; avoids importing the
47
+ # heavy layer machinery -- and any circular dependency -- at module import).
48
+ # ---------------------------------------------------------------------------
49
+ def _layer(**kwargs: Any) -> Any:
50
+ """Construct a :class:`ggplot2_py.Layer`, importing ``layer`` lazily.
51
+
52
+ Parameters
53
+ ----------
54
+ **kwargs : Any
55
+ Forwarded verbatim to :func:`ggplot2_py.layer.layer`.
56
+
57
+ Returns
58
+ -------
59
+ ggplot2_py.Layer
60
+ """
61
+ from ggplot2_py.layer import layer
62
+
63
+ return layer(**kwargs)
64
+
65
+
66
+ # ---------------------------------------------------------------------------
67
+ # Helper reductions used by the wrapper constructors.
68
+ #
69
+ # R forwards ``na.rm = TRUE`` to ``mean``/``range``. NumPy's ``mean``/``min``/
70
+ # ``max`` have no ``na_rm`` keyword, so the wrappers translate to the
71
+ # NaN-aware reductions and accept (and honour) an ``na_rm`` keyword so the
72
+ # translated ``argx``/``argy`` dicts forward cleanly.
73
+ # ---------------------------------------------------------------------------
74
+ def _mean(x: Sequence[float], na_rm: bool = False) -> np.floating:
75
+ """Arithmetic mean with optional NA removal (port of R ``mean``).
76
+
77
+ Parameters
78
+ ----------
79
+ x : sequence of float
80
+ Values to average.
81
+ na_rm : bool, default False
82
+ Whether to ignore ``NaN`` values (R's ``na.rm``).
83
+
84
+ Returns
85
+ -------
86
+ numpy.floating
87
+ The (NaN-aware) mean of *x*.
88
+ """
89
+ arr = np.asarray(x, dtype=float)
90
+ return np.nanmean(arr) if na_rm else np.mean(arr)
91
+
92
+
93
+ def _midpoint(x: Sequence[float], na_rm: bool = True) -> np.floating:
94
+ """Midpoint of the data range: ``(min + max) / 2``.
95
+
96
+ Port of the closure defined inside R ``stat_midpoint`` --
97
+ ``sum(range(x, na.rm = na.rm), na.rm = na.rm) / 2``.
98
+
99
+ Parameters
100
+ ----------
101
+ x : sequence of float
102
+ Values to summarise.
103
+ na_rm : bool, default True
104
+ Whether to ignore ``NaN`` values when computing the range.
105
+
106
+ Returns
107
+ -------
108
+ numpy.floating
109
+ Half the sum of the minimum and maximum of *x*.
110
+ """
111
+ arr = np.asarray(x, dtype=float)
112
+ if na_rm:
113
+ lo, hi = np.nanmin(arr), np.nanmax(arr)
114
+ else:
115
+ lo, hi = np.min(arr), np.max(arr)
116
+ return (lo + hi) / 2.0
117
+
118
+
119
+ def _identity(x: Any) -> Any:
120
+ """Return *x* unchanged (port of R ``force`` used as the default fun).
121
+
122
+ Parameters
123
+ ----------
124
+ x : Any
125
+
126
+ Returns
127
+ -------
128
+ Any
129
+ The input, unchanged.
130
+ """
131
+ return x
132
+
133
+
134
+ # ---------------------------------------------------------------------------
135
+ # StatFunxy ggproto
136
+ # ---------------------------------------------------------------------------
137
+ class StatFunxy(Stat):
138
+ """Apply ``funx``/``funy`` to the ``x``/``y`` positions per group.
139
+
140
+ Mirrors the R ``StatFunxy`` ggproto object. ``compute_group`` calls
141
+ ``funx`` on the group's ``x`` values and ``funy`` on its ``y`` values,
142
+ then reconciles the lengths of the remaining columns (cropping or
143
+ recycling) before recombining.
144
+
145
+ Attributes
146
+ ----------
147
+ required_aes : list of str
148
+ ``['x', 'y']``.
149
+ """
150
+
151
+ required_aes = ["x", "y"]
152
+
153
+ def compute_group(
154
+ self,
155
+ data: pd.DataFrame,
156
+ scales: Any,
157
+ funx: Callable[..., Any] = _identity,
158
+ funy: Callable[..., Any] = _identity,
159
+ argx: Optional[Dict[str, Any]] = None,
160
+ argy: Optional[Dict[str, Any]] = None,
161
+ crop_other: bool = True,
162
+ ) -> pd.DataFrame:
163
+ """Apply ``funx``/``funy`` to ``x``/``y`` and reconcile lengths.
164
+
165
+ Parameters
166
+ ----------
167
+ data : pandas.DataFrame
168
+ One group's data; must contain ``x`` and ``y`` columns.
169
+ scales : Any
170
+ Panel scales (unused; present for signature parity).
171
+ funx, funy : callable
172
+ Functions applied to the ``x`` and ``y`` positions
173
+ respectively. Each is called as ``fun(values, **arg)``.
174
+ argx, argy : dict, optional
175
+ Named keyword arguments forwarded to ``funx`` / ``funy``.
176
+ crop_other : bool, default True
177
+ Whether the remaining (non ``x``/``y``) columns should be
178
+ cropped to the length of the longest of ``funx``/``funy``'s
179
+ results. Set ``False`` when the functions return length-one
180
+ summaries that should be recycled against the full group.
181
+
182
+ Returns
183
+ -------
184
+ pandas.DataFrame
185
+ Columns: the original non ``x``/``y`` columns (in their
186
+ original order) followed by ``x`` and ``y``, each recycled to
187
+ a common length.
188
+ """
189
+ if argx is None:
190
+ argx = {}
191
+ if argy is None:
192
+ argy = {}
193
+
194
+ # Apply functions. R: do.call(funx, c(unname(data["x"]), argx)),
195
+ # i.e. the column is the first positional argument with its name
196
+ # stripped, followed by the named ``argx`` arguments.
197
+ x = funx(np.asarray(data["x"].to_numpy(), dtype=float), **argx)
198
+ y = funy(np.asarray(data["y"].to_numpy(), dtype=float), **argy)
199
+ x = np.atleast_1d(np.asarray(x))
200
+ y = np.atleast_1d(np.asarray(y))
201
+
202
+ # Ensure the rest of the data is of the correct length.
203
+ other_names = [c for c in data.columns if c not in ("x", "y")]
204
+ size = int(max(len(x), len(y)))
205
+ if crop_other:
206
+ # R: lapply(data[other], `[`, i = size) where size = seq_len(...).
207
+ # Positional crop to the first ``size`` elements (1-based 1:size
208
+ # -> 0-based slice [0:size]).
209
+ other = {c: data[c].to_numpy()[:size] for c in other_names}
210
+ else:
211
+ other = {c: data[c].to_numpy() for c in other_names}
212
+
213
+ # Combine: other columns first, then x, then y (R column order).
214
+ combined: Dict[str, Any] = dict(other)
215
+ combined["x"] = x
216
+ combined["y"] = y
217
+
218
+ # Recycle every column to a common length (length-1 -> longest).
219
+ recycled = vec_recycle_common(*combined.values())
220
+ out = {name: arr for name, arr in zip(combined.keys(), recycled)}
221
+ return data_frame0(**out)
222
+
223
+
224
+ # ---------------------------------------------------------------------------
225
+ # Constructors
226
+ # ---------------------------------------------------------------------------
227
+ def stat_funxy(
228
+ mapping: Optional[Mapping] = None,
229
+ data: Any = None,
230
+ geom: str = "point",
231
+ position: str = "identity",
232
+ *,
233
+ funx: Callable[..., Any] = _identity,
234
+ funy: Callable[..., Any] = _identity,
235
+ argx: Optional[Dict[str, Any]] = None,
236
+ argy: Optional[Dict[str, Any]] = None,
237
+ crop_other: bool = True,
238
+ show_legend: Optional[bool] = None,
239
+ inherit_aes: bool = True,
240
+ **kwargs: Any,
241
+ ) -> Any:
242
+ """Apply a function to a layer's ``x`` and ``y`` position coordinates.
243
+
244
+ Port of R ``stat_funxy``.
245
+
246
+ Parameters
247
+ ----------
248
+ mapping : Mapping, optional
249
+ Aesthetic mapping (see :func:`ggplot2_py.aes`).
250
+ data : DataFrame or callable, optional
251
+ Layer data.
252
+ geom : str, default ``"point"``
253
+ Geometry used to render the computed positions.
254
+ position : str, default ``"identity"``
255
+ Position adjustment.
256
+ funx, funy : callable, default identity
257
+ Functions called on the layer's ``x`` and ``y`` positions
258
+ respectively. Default ``force`` (identity) leaves data as-is.
259
+ argx, argy : dict, optional
260
+ Named arguments forwarded to ``funx`` / ``funy``. Default empty.
261
+ crop_other : bool, default True
262
+ Whether the other data should be fitted to the length of ``x`` and
263
+ ``y``. Set ``False`` when ``funx``/``funy`` compute length-one
264
+ summaries that need to be recycled.
265
+ show_legend : bool, optional
266
+ Whether to include this layer in the legend.
267
+ inherit_aes : bool, default True
268
+ Whether to inherit the plot-level mapping.
269
+ **kwargs
270
+ Additional parameters forwarded to the layer.
271
+
272
+ Returns
273
+ -------
274
+ ggplot2_py.Layer
275
+ A layer backed by :class:`StatFunxy`.
276
+
277
+ Raises
278
+ ------
279
+ ValueError
280
+ If ``funx``/``funy`` are not callable, if ``argx``/``argy`` are not
281
+ dicts, or if ``argx``/``argy`` contain unnamed (empty-string-key)
282
+ elements.
283
+ """
284
+ if argx is None:
285
+ argx = {}
286
+ if argy is None:
287
+ argy = {}
288
+
289
+ # stopifnot(...) validations from R, translated to cli_abort.
290
+ if not callable(funx):
291
+ cli_abort("The `funx` argument must be a function.")
292
+ if not callable(funy):
293
+ cli_abort("The `funy` argument must be a function.")
294
+ if not isinstance(argx, dict) or not isinstance(argy, dict):
295
+ cli_abort("The `argx` and `argy` arguments must be lists.")
296
+ # R: length(argx) == sum(nzchar(names(argx))) -- every element named.
297
+ if any((not isinstance(k, str)) or k == "" for k in argx.keys()):
298
+ cli_abort("The `argx` list must have named elements")
299
+ if any((not isinstance(k, str)) or k == "" for k in argy.keys()):
300
+ cli_abort("The `argy` list must have named elements")
301
+
302
+ return _layer(
303
+ data=data,
304
+ mapping=mapping,
305
+ stat=StatFunxy,
306
+ geom=geom,
307
+ position=position,
308
+ show_legend=show_legend,
309
+ inherit_aes=inherit_aes,
310
+ params={
311
+ "funx": funx,
312
+ "funy": funy,
313
+ "argx": argx,
314
+ "argy": argy,
315
+ "crop_other": crop_other,
316
+ **kwargs,
317
+ },
318
+ )
319
+
320
+
321
+ def stat_centroid(
322
+ mapping: Optional[Mapping] = None,
323
+ data: Any = None,
324
+ geom: str = "point",
325
+ position: str = "identity",
326
+ *,
327
+ funx: Callable[..., Any] = _mean,
328
+ funy: Callable[..., Any] = _mean,
329
+ argx: Optional[Dict[str, Any]] = None,
330
+ argy: Optional[Dict[str, Any]] = None,
331
+ crop_other: bool = True,
332
+ show_legend: Optional[bool] = None,
333
+ inherit_aes: bool = True,
334
+ **kwargs: Any,
335
+ ) -> Any:
336
+ """Compute group centroids (means of ``x`` and ``y``).
337
+
338
+ Convenience wrapper around :func:`stat_funxy` with ``funx = funy =
339
+ mean`` and ``argx = argy = {'na_rm': True}`` (port of R
340
+ ``stat_centroid``).
341
+
342
+ Parameters
343
+ ----------
344
+ mapping : Mapping, optional
345
+ data : DataFrame or callable, optional
346
+ geom : str, default ``"point"``
347
+ position : str, default ``"identity"``
348
+ funx, funy : callable, default NaN-aware mean
349
+ Functions applied to ``x`` / ``y``.
350
+ argx, argy : dict, optional
351
+ Named arguments forwarded to ``funx`` / ``funy``. Default
352
+ ``{'na_rm': True}``.
353
+ crop_other : bool, default True
354
+ show_legend : bool, optional
355
+ inherit_aes : bool, default True
356
+ **kwargs
357
+
358
+ Returns
359
+ -------
360
+ ggplot2_py.Layer
361
+ """
362
+ if argx is None:
363
+ argx = {"na_rm": True}
364
+ if argy is None:
365
+ argy = {"na_rm": True}
366
+ return stat_funxy(
367
+ mapping=mapping,
368
+ data=data,
369
+ geom=geom,
370
+ position=position,
371
+ funx=funx,
372
+ funy=funy,
373
+ argx=argx,
374
+ argy=argy,
375
+ crop_other=crop_other,
376
+ show_legend=show_legend,
377
+ inherit_aes=inherit_aes,
378
+ **kwargs,
379
+ )
380
+
381
+
382
+ def stat_midpoint(
383
+ mapping: Optional[Mapping] = None,
384
+ data: Any = None,
385
+ geom: str = "point",
386
+ position: str = "identity",
387
+ *,
388
+ argx: Optional[Dict[str, Any]] = None,
389
+ argy: Optional[Dict[str, Any]] = None,
390
+ crop_other: bool = True,
391
+ show_legend: Optional[bool] = None,
392
+ inherit_aes: bool = True,
393
+ **kwargs: Any,
394
+ ) -> Any:
395
+ """Compute group midpoints (``(min + max) / 2`` of ``x`` and ``y``).
396
+
397
+ Convenience wrapper around :func:`stat_funxy` whose ``funx``/``funy``
398
+ is the closure ``sum(range(x, na.rm)) / 2`` (port of R
399
+ ``stat_midpoint``).
400
+
401
+ Parameters
402
+ ----------
403
+ mapping : Mapping, optional
404
+ data : DataFrame or callable, optional
405
+ geom : str, default ``"point"``
406
+ position : str, default ``"identity"``
407
+ argx, argy : dict, optional
408
+ Named arguments forwarded to the midpoint function. Default
409
+ ``{'na_rm': True}``.
410
+ crop_other : bool, default True
411
+ show_legend : bool, optional
412
+ inherit_aes : bool, default True
413
+ **kwargs
414
+
415
+ Returns
416
+ -------
417
+ ggplot2_py.Layer
418
+ """
419
+ if argx is None:
420
+ argx = {"na_rm": True}
421
+ if argy is None:
422
+ argy = {"na_rm": True}
423
+ return stat_funxy(
424
+ mapping=mapping,
425
+ data=data,
426
+ geom=geom,
427
+ position=position,
428
+ funx=_midpoint,
429
+ funy=_midpoint,
430
+ argx=argx,
431
+ argy=argy,
432
+ crop_other=crop_other,
433
+ show_legend=show_legend,
434
+ inherit_aes=inherit_aes,
435
+ **kwargs,
436
+ )