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.
- fluxplot/__init__.py +115 -0
- fluxplot/_fieldmap.py +97 -0
- fluxplot/_mesh_reduce.py +54 -0
- fluxplot/_scene3d_size.py +95 -0
- fluxplot/_viewer/THIRD-PARTY.txt +23 -0
- fluxplot/_viewer/flux-model3d-viewer.min.js +4221 -0
- fluxplot/_viewer/stamp.json +4 -0
- fluxplot/api.py +1196 -0
- fluxplot/autotag.py +164 -0
- fluxplot/base.mplstyle +0 -0
- fluxplot/brackets.py +242 -0
- fluxplot/canonical_json.py +23 -0
- fluxplot/capture.py +150 -0
- fluxplot/colorcheck.py +285 -0
- fluxplot/colors.py +727 -0
- fluxplot/colorscale.py +477 -0
- fluxplot/data.py +178 -0
- fluxplot/definitions/colormaps.json +1639 -0
- fluxplot/definitions/flexoki.tokens.json +2571 -0
- fluxplot/definitions/palettes.json +2547 -0
- fluxplot/descriptors.py +87 -0
- fluxplot/fields.py +611 -0
- fluxplot/fits.py +240 -0
- fluxplot/glb.py +84 -0
- fluxplot/ids.py +173 -0
- fluxplot/images.py +362 -0
- fluxplot/integrity.py +27 -0
- fluxplot/manifest.py +788 -0
- fluxplot/mesh3d.py +376 -0
- fluxplot/panels.py +284 -0
- fluxplot/postprocess.py +638 -0
- fluxplot/presets.py +66 -0
- fluxplot/provenance.py +177 -0
- fluxplot/raster.py +295 -0
- fluxplot/recipe.py +178 -0
- fluxplot/render.py +66 -0
- fluxplot/roles.py +147 -0
- fluxplot/scene3d.py +386 -0
- fluxplot/scene3d_manifest.py +112 -0
- fluxplot/scene3d_viewer.py +633 -0
- fluxplot/schemas/.gitkeep +0 -0
- fluxplot/schemas/manifest.schema.json +2479 -0
- fluxplot/schemas/recipe.schema.json +179 -0
- fluxplot/schemas/scene3d.schema.json +461 -0
- fluxplot/seaborn_adapters.py +323 -0
- fluxplot/signature_fluxplots/__init__.py +18 -0
- fluxplot/signature_fluxplots/_colour.py +412 -0
- fluxplot/signature_fluxplots/fluxbox.py +433 -0
- fluxplot/signature_fluxplots/glowbar.py +769 -0
- fluxplot/signature_fluxplots/hexmatrix.py +927 -0
- fluxplot/stats/__init__.py +63 -0
- fluxplot/stats/_common.py +196 -0
- fluxplot/stats/multi_group.py +443 -0
- fluxplot/stats/paired.py +209 -0
- fluxplot/stats/two_group.py +149 -0
- fluxplot/style.py +469 -0
- fluxplot/surface.py +487 -0
- fluxplot/surface3d.py +197 -0
- fluxplot/tagger.py +561 -0
- fluxplot/version.py +19 -0
- fluxplot-0.1.0.dist-info/METADATA +1199 -0
- fluxplot-0.1.0.dist-info/RECORD +65 -0
- fluxplot-0.1.0.dist-info/WHEEL +4 -0
- fluxplot-0.1.0.dist-info/licenses/LICENSE +21 -0
- 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
|