fluxplot 0.1.0__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 (65) hide show
  1. fluxplot/__init__.py +115 -0
  2. fluxplot/_fieldmap.py +97 -0
  3. fluxplot/_mesh_reduce.py +54 -0
  4. fluxplot/_scene3d_size.py +95 -0
  5. fluxplot/_viewer/THIRD-PARTY.txt +23 -0
  6. fluxplot/_viewer/flux-model3d-viewer.min.js +4221 -0
  7. fluxplot/_viewer/stamp.json +4 -0
  8. fluxplot/api.py +1196 -0
  9. fluxplot/autotag.py +164 -0
  10. fluxplot/base.mplstyle +0 -0
  11. fluxplot/brackets.py +242 -0
  12. fluxplot/canonical_json.py +23 -0
  13. fluxplot/capture.py +150 -0
  14. fluxplot/colorcheck.py +285 -0
  15. fluxplot/colors.py +727 -0
  16. fluxplot/colorscale.py +477 -0
  17. fluxplot/data.py +178 -0
  18. fluxplot/definitions/colormaps.json +1639 -0
  19. fluxplot/definitions/flexoki.tokens.json +2571 -0
  20. fluxplot/definitions/palettes.json +2547 -0
  21. fluxplot/descriptors.py +87 -0
  22. fluxplot/fields.py +611 -0
  23. fluxplot/fits.py +240 -0
  24. fluxplot/glb.py +84 -0
  25. fluxplot/ids.py +173 -0
  26. fluxplot/images.py +362 -0
  27. fluxplot/integrity.py +27 -0
  28. fluxplot/manifest.py +788 -0
  29. fluxplot/mesh3d.py +376 -0
  30. fluxplot/panels.py +284 -0
  31. fluxplot/postprocess.py +638 -0
  32. fluxplot/presets.py +66 -0
  33. fluxplot/provenance.py +177 -0
  34. fluxplot/raster.py +295 -0
  35. fluxplot/recipe.py +178 -0
  36. fluxplot/render.py +66 -0
  37. fluxplot/roles.py +147 -0
  38. fluxplot/scene3d.py +386 -0
  39. fluxplot/scene3d_manifest.py +112 -0
  40. fluxplot/scene3d_viewer.py +633 -0
  41. fluxplot/schemas/.gitkeep +0 -0
  42. fluxplot/schemas/manifest.schema.json +2479 -0
  43. fluxplot/schemas/recipe.schema.json +179 -0
  44. fluxplot/schemas/scene3d.schema.json +461 -0
  45. fluxplot/seaborn_adapters.py +323 -0
  46. fluxplot/signature_fluxplots/__init__.py +18 -0
  47. fluxplot/signature_fluxplots/_colour.py +412 -0
  48. fluxplot/signature_fluxplots/fluxbox.py +433 -0
  49. fluxplot/signature_fluxplots/glowbar.py +769 -0
  50. fluxplot/signature_fluxplots/hexmatrix.py +927 -0
  51. fluxplot/stats/__init__.py +63 -0
  52. fluxplot/stats/_common.py +196 -0
  53. fluxplot/stats/multi_group.py +443 -0
  54. fluxplot/stats/paired.py +209 -0
  55. fluxplot/stats/two_group.py +149 -0
  56. fluxplot/style.py +469 -0
  57. fluxplot/surface.py +487 -0
  58. fluxplot/surface3d.py +197 -0
  59. fluxplot/tagger.py +561 -0
  60. fluxplot/version.py +19 -0
  61. fluxplot-0.1.0.dist-info/METADATA +1199 -0
  62. fluxplot-0.1.0.dist-info/RECORD +65 -0
  63. fluxplot-0.1.0.dist-info/WHEEL +4 -0
  64. fluxplot-0.1.0.dist-info/licenses/LICENSE +21 -0
  65. fluxplot-0.1.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +472 -0
@@ -0,0 +1,63 @@
1
+ """Statistics behind the plots — tests that return rows ready for a plot's ``_stats`` dissection.
2
+
3
+ Accessed as ``fp.stats``. Independent (unpaired) samples:
4
+
5
+ * :func:`welch_hedges` — Welch's t-test + Hedges' g (non-pooled SD), for comparing means.
6
+ * :func:`mann_whitney_cliff` — Mann–Whitney U test + Cliff's delta, for a rank-based comparison.
7
+
8
+ Paired samples (``a[i]`` matched with ``b[i]``):
9
+
10
+ * :func:`paired_t_hedges` — paired t-test + Hedges' g_z, for the mean paired difference.
11
+ * :func:`wilcoxon_rank_biserial` — Wilcoxon signed-rank test + matched-pairs rank-biserial
12
+ correlation, for a rank-based comparison of paired differences.
13
+
14
+ Every function takes ``(a, b)``, orients signs as ``a - b``, reports a 95% CI for the effect size,
15
+ and returns one reporting row keyed by :data:`REPORT_COLUMNS` (``dof`` is ``None`` for rank tests).
16
+
17
+ Three or more groups (:mod:`fluxplot.stats.multi_group`): the omnibus tests
18
+ :func:`anova_oneway`, :func:`welch_anova`, :func:`kruskal_epsilon`, :func:`rm_anova` and
19
+ :func:`friedman_kendall` (one row each), the post-hoc tests :func:`tukey_hsd`, :func:`games_howell`
20
+ and :func:`dunn` (one row per pair), and :func:`pairwise`, which runs any two-group test over the
21
+ pairs of a ``{name: sample}`` family and corrects the p-values. Post-hoc rows drop straight into
22
+ :func:`fluxplot.brackets`.
23
+
24
+ Multiple comparisons: each row's ``p_corrected_holm`` / ``p_corrected_bh`` equals its ``p-value``
25
+ until the rows of a family are passed through :func:`holm` (family-wise error, Holm step-down) or
26
+ :func:`bh` (false discovery rate, Benjamini–Hochberg); :func:`holm_adjusted` / :func:`bh_adjusted`
27
+ do the same for a bare array of p-values.
28
+ """
29
+ from ._common import REPORT_COLUMNS, bh, bh_adjusted, holm, holm_adjusted
30
+ from .multi_group import (
31
+ anova_oneway,
32
+ dunn,
33
+ friedman_kendall,
34
+ games_howell,
35
+ kruskal_epsilon,
36
+ pairwise,
37
+ rm_anova,
38
+ tukey_hsd,
39
+ welch_anova,
40
+ )
41
+ from .paired import paired_t_hedges, wilcoxon_rank_biserial
42
+ from .two_group import mann_whitney_cliff, welch_hedges
43
+
44
+ __all__ = [
45
+ "welch_hedges",
46
+ "mann_whitney_cliff",
47
+ "paired_t_hedges",
48
+ "wilcoxon_rank_biserial",
49
+ "anova_oneway",
50
+ "welch_anova",
51
+ "kruskal_epsilon",
52
+ "rm_anova",
53
+ "friedman_kendall",
54
+ "tukey_hsd",
55
+ "games_howell",
56
+ "dunn",
57
+ "pairwise",
58
+ "holm",
59
+ "holm_adjusted",
60
+ "bh",
61
+ "bh_adjusted",
62
+ "REPORT_COLUMNS",
63
+ ]
@@ -0,0 +1,196 @@
1
+ """Shared pieces of the ``fp.stats`` tests: the reporting-row contract and input validation."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Any, Iterable, List, Optional
5
+
6
+ import numpy as np
7
+
8
+ REPORT_COLUMNS = (
9
+ "sig_test_used",
10
+ "test_statistic_value",
11
+ "p-value",
12
+ "p_corrected_holm",
13
+ "dof",
14
+ "effect_size_method",
15
+ "effect_size_value",
16
+ "effect_size_95_CI",
17
+ # appended (never reordered): the CI as numbers, the sample sizes, the groups compared, the
18
+ # alternative hypothesis, the Benjamini–Hochberg column and the error dof of F tests
19
+ "effect_size_ci_low",
20
+ "effect_size_ci_high",
21
+ "n_a",
22
+ "n_b",
23
+ "n_total",
24
+ "groups",
25
+ "alternative",
26
+ "p_corrected_bh",
27
+ "dof_error",
28
+ )
29
+
30
+ ALTERNATIVES = ("two-sided", "less", "greater")
31
+
32
+
33
+ def check_alternative(alternative: str) -> str:
34
+ if alternative not in ALTERNATIVES:
35
+ raise ValueError(f"alternative must be one of {ALTERNATIVES}, got {alternative!r}")
36
+ return alternative
37
+
38
+
39
+ def report_row(test: str, statistic: float, p: float, dof: Optional[float], method: str,
40
+ effect: float, lo: float, hi: float, *, n=None, groups=None,
41
+ alternative: str = "two-sided", dof_error: Optional[float] = None,
42
+ n_total: Optional[int] = None) -> dict:
43
+ """One reporting row keyed by :data:`REPORT_COLUMNS`; ``dof=None`` for rank tests.
44
+
45
+ ``p_corrected_holm`` and ``p_corrected_bh`` start out equal to ``p-value``: a row on its own is
46
+ a family of one, for which both corrections are the identity. Pass the rows of a family through
47
+ :func:`holm` / :func:`bh` to fill them in across the family.
48
+
49
+ ``n`` is the group sizes (``(n_a, n_b)`` for a two-group row, one size per group for an
50
+ omnibus row; ``n_total`` is their sum unless given — paired designs pass the number of
51
+ subjects). ``groups`` names the groups compared, in the order the sign convention (``a - b``)
52
+ refers to. ``dof_error`` is the denominator dof of an F test
53
+ (``dof`` is then its numerator).
54
+ """
55
+ sizes = [int(v) for v in (n if n is not None else ())]
56
+ two = len(sizes) == 2
57
+ return {
58
+ "sig_test_used": test,
59
+ "test_statistic_value": float(statistic),
60
+ "p-value": float(p),
61
+ "p_corrected_holm": float(p),
62
+ "dof": None if dof is None else float(dof),
63
+ "effect_size_method": method,
64
+ "effect_size_value": float(effect),
65
+ "effect_size_95_CI": f"[{lo:.2f}, {hi:.2f}]",
66
+ "effect_size_ci_low": float(lo),
67
+ "effect_size_ci_high": float(hi),
68
+ "n_a": sizes[0] if two else None,
69
+ "n_b": sizes[1] if two else None,
70
+ "n_total": int(n_total) if n_total is not None else (sum(sizes) if sizes else None),
71
+ "groups": None if groups is None else [str(g) for g in groups],
72
+ "alternative": check_alternative(alternative),
73
+ "p_corrected_bh": float(p),
74
+ "dof_error": None if dof_error is None else float(dof_error),
75
+ }
76
+
77
+
78
+ def as_sample(x: Any, name: str) -> np.ndarray:
79
+ """A 1-D float array of at least 2 finite values, or a ``ValueError``."""
80
+ arr = np.asarray(x, dtype=float).ravel()
81
+ if arr.size < 2:
82
+ raise ValueError(f"{name} needs at least 2 observations, got {arr.size}")
83
+ if not np.all(np.isfinite(arr)):
84
+ raise ValueError(f"{name} contains NaN or infinite values; drop them before testing")
85
+ return arr
86
+
87
+
88
+ def as_pairs(a: Any, b: Any) -> tuple:
89
+ """Two equal-length samples of matched observations (``a[i]`` pairs with ``b[i]``)."""
90
+ a, b = as_sample(a, "a"), as_sample(b, "b")
91
+ if a.size != b.size:
92
+ raise ValueError(f"paired samples must have equal length, got {a.size} and {b.size}")
93
+ return a, b
94
+
95
+
96
+ def holm_adjusted(p: Any) -> np.ndarray:
97
+ """Holm (1979) step-down adjusted p-values for a family of ``m`` raw two-sided p-values.
98
+
99
+ The smallest p-value is multiplied by ``m``, the next by ``m - 1``, and so on down to ``1``;
100
+ running maxima keep the adjusted values monotone in the raw ones and each is capped at 1.
101
+ Rejecting every adjusted value below ``alpha`` controls the family-wise error rate at ``alpha``
102
+ under any dependence between the tests, and is never less powerful than Bonferroni.
103
+ """
104
+ p = np.asarray(p, dtype=float).ravel()
105
+ keep = _checked(p)
106
+ m = int(keep.sum())
107
+ adjusted = np.full(p.size, np.nan)
108
+ if m == 0:
109
+ return adjusted
110
+ q = p[keep]
111
+ order = np.argsort(q, kind="stable")
112
+ stepped = q[order] * np.arange(m, 0, -1)
113
+ out = np.empty(m)
114
+ out[order] = np.minimum(np.maximum.accumulate(stepped), 1.0)
115
+ adjusted[keep] = out
116
+ return adjusted
117
+
118
+
119
+ def _checked(p: np.ndarray) -> np.ndarray:
120
+ """The mask of the p-values that take part in a correction: NaN passes through untouched (a
121
+ test that could not be run leaves its slot empty instead of aborting the whole family)."""
122
+ keep = ~np.isnan(p)
123
+ if not np.all((p[keep] >= 0) & (p[keep] <= 1)):
124
+ raise ValueError("p-values must lie in [0, 1]")
125
+ return keep
126
+
127
+
128
+ def bh_adjusted(p: Any) -> np.ndarray:
129
+ """Benjamini–Hochberg (1995) step-up adjusted p-values (q-values) for a family of ``m`` raw
130
+ p-values.
131
+
132
+ The largest p-value keeps its value, the next is multiplied by ``m / (m - 1)``, and so on down
133
+ to ``m / 1`` for the smallest; running minima from the top keep the adjusted values monotone
134
+ and each is capped at 1. Rejecting every adjusted value below ``alpha`` controls the false
135
+ discovery rate at ``alpha`` for independent or positively dependent tests — the right control
136
+ for a screen of many measures, where Holm's family-wise guarantee is needlessly strict. NaN
137
+ passes through.
138
+ """
139
+ p = np.asarray(p, dtype=float).ravel()
140
+ keep = _checked(p)
141
+ m = int(keep.sum())
142
+ adjusted = np.full(p.size, np.nan)
143
+ if m == 0:
144
+ return adjusted
145
+ q = p[keep]
146
+ order = np.argsort(q, kind="stable")
147
+ stepped = q[order] * (m / np.arange(1, m + 1)) # the largest p keeps its exact value
148
+ out = np.empty(m)
149
+ out[order] = np.minimum(np.minimum.accumulate(stepped[::-1])[::-1], 1.0)
150
+ adjusted[keep] = out
151
+ return adjusted
152
+
153
+
154
+ def holm(rows: Iterable[dict]) -> List[dict]:
155
+ """Fill ``p_corrected_holm`` across a family of reporting rows.
156
+
157
+ Holm's step-down correction needs every p-value in the family (each one's multiplier depends
158
+ on its rank among the others), so it cannot be applied inside a single test call. Collect the
159
+ rows of the comparisons that form one family — for instance every measure tested on the same
160
+ animals in one figure — and pass them here once:
161
+
162
+ >>> rows = [dict(measure=m, **fp.stats.welch_hedges(sd[m], sleep[m])) for m in measures]
163
+ >>> rows = fp.stats.holm(rows) # p_corrected_holm now spans the whole family
164
+ >>> pl.DataFrame(rows)
165
+
166
+ Parameters
167
+ ----------
168
+ rows
169
+ Reporting rows (dicts with a ``"p-value"`` key, as returned by any ``fp.stats`` test).
170
+ Extra keys such as a measure name are kept.
171
+
172
+ Returns
173
+ -------
174
+ list of dict
175
+ Copies of the rows in the same order, with ``p_corrected_holm`` set to the Holm-adjusted
176
+ p-value across the family. A single row comes back unchanged (Holm of one is the identity).
177
+ """
178
+ return _fill(rows, "p_corrected_holm", holm_adjusted)
179
+
180
+
181
+ def bh(rows: Iterable[dict]) -> List[dict]:
182
+ """Fill ``p_corrected_bh`` across a family of reporting rows with Benjamini–Hochberg adjusted
183
+ p-values (see :func:`bh_adjusted`); the counterpart of :func:`holm` for false-discovery-rate
184
+ control. Copies the rows, keeps their order and extra keys."""
185
+ return _fill(rows, "p_corrected_bh", bh_adjusted)
186
+
187
+
188
+ def _fill(rows: Iterable[dict], column: str, adjust) -> List[dict]:
189
+ rows = [dict(r) for r in rows]
190
+ missing = [i for i, r in enumerate(rows) if "p-value" not in r]
191
+ if missing:
192
+ raise ValueError(f"rows {missing} have no 'p-value' key; pass fp.stats reporting rows")
193
+ adjusted = adjust([r["p-value"] for r in rows])
194
+ for r, q in zip(rows, adjusted):
195
+ r[column] = float(q)
196
+ return rows