zidstats 0.1.0__tar.gz
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.
- zidstats-0.1.0/LICENSE +21 -0
- zidstats-0.1.0/PKG-INFO +146 -0
- zidstats-0.1.0/README.md +132 -0
- zidstats-0.1.0/pyproject.toml +26 -0
- zidstats-0.1.0/setup.cfg +4 -0
- zidstats-0.1.0/src/zidstats/__init__.py +36 -0
- zidstats-0.1.0/src/zidstats/api.py +490 -0
- zidstats-0.1.0/src/zidstats/distance.py +52 -0
- zidstats-0.1.0/src/zidstats/get_test.py +189 -0
- zidstats-0.1.0/src/zidstats/gpk.py +203 -0
- zidstats-0.1.0/src/zidstats/rise.py +111 -0
- zidstats-0.1.0/src/zidstats.egg-info/PKG-INFO +146 -0
- zidstats-0.1.0/src/zidstats.egg-info/SOURCES.txt +16 -0
- zidstats-0.1.0/src/zidstats.egg-info/dependency_links.txt +1 -0
- zidstats-0.1.0/src/zidstats.egg-info/requires.txt +2 -0
- zidstats-0.1.0/src/zidstats.egg-info/top_level.txt +1 -0
- zidstats-0.1.0/tests/test_core_extras.py +125 -0
- zidstats-0.1.0/tests/test_zidstats.py +152 -0
zidstats-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hao Chen and contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
zidstats-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: zidstats
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Calibrated, distribution-free two-sample tests with a p-value and signed location/dispersion attribution (ZID / GPK / RISE / GET).
|
|
5
|
+
Author-email: Hao Chen <hxchen@ucdavis.edu>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/haochen6/zidstats
|
|
8
|
+
Keywords: two-sample-test,distribution-free,calibrated,p-value,kernel-test,graph-based-test,dispersion,attribution,nonparametric,high-dimensional
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: numpy>=1.20
|
|
13
|
+
Requires-Dist: scipy>=1.6
|
|
14
|
+
|
|
15
|
+
# zidstats — calibrated, distribution-free two-sample tests (with a p-value)
|
|
16
|
+
|
|
17
|
+
A family of **two-sample tests** (ZID, GPK, RISE, GET) that, unlike a bare
|
|
18
|
+
distance/scalar, return a **calibrated p-value** and, by using *within-group*
|
|
19
|
+
similarity rather than only the cross term, stay sensitive to **dispersion/scale and
|
|
20
|
+
shape** differences — not just location — in high dimension. Each also exposes signed
|
|
21
|
+
`Z_W`/`Z_D` arms — one location-sensitive, one spread/shape-sensitive — for reading the
|
|
22
|
+
*direction* of a difference, not just its size (see Attribution below for details).
|
|
23
|
+
|
|
24
|
+
General-purpose: anywhere you compare two samples `X` and `Y` and want a *significance
|
|
25
|
+
statement* plus *how* they differ, not just a number.
|
|
26
|
+
|
|
27
|
+
**Dissimilarity-native:** the tests run on any pairwise dissimilarity — no coordinates, no
|
|
28
|
+
metric, no triangle inequality — so any object type works (sequences, graphs, distributions,
|
|
29
|
+
…). Pass point matrices with a `metric`, or a precomputed dissimilarity via
|
|
30
|
+
`two_sample_from_dissimilarity`; point inputs are just the convenience case.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
```bash
|
|
34
|
+
pip install zidstats # numpy, scipy only
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick start — one call, zero choices
|
|
38
|
+
```python
|
|
39
|
+
import numpy as np
|
|
40
|
+
from zidstats import two_sample_test
|
|
41
|
+
|
|
42
|
+
rng = np.random.default_rng(0)
|
|
43
|
+
X = rng.standard_normal((300, 64))
|
|
44
|
+
Y = rng.standard_normal((300, 64)) * 0.85 # same mean, 15% less spread
|
|
45
|
+
res = two_sample_test(X, Y) # default = ZID omnibus; nothing else to pick
|
|
46
|
+
print(res)
|
|
47
|
+
# ZID: p=0.00498 (stat=181.4, alpha=0.05) -> reject H0.
|
|
48
|
+
# Significant difference driven by SMALLER dispersion (Z_D=-12.4).
|
|
49
|
+
res.pvalue, res.statistic, res.reject, res.verdict
|
|
50
|
+
res.extra["members"] # member p-values: RISE, GPK@median, GPK@0.3med
|
|
51
|
+
res.extra["Z_D"], res.extra["Z_D_pval"] # spread/shape-sensitive arm (from the RISE member)
|
|
52
|
+
res.extra["Z_W"], res.extra["Z_W_pval"] # location-sensitive arm — clean two-axis (from RISE)
|
|
53
|
+
```
|
|
54
|
+
**Run this first.** The default is **ZID**, a broad-coverage omnibus over three members —
|
|
55
|
+
**RISE**, **GPK at the median bandwidth**, and **GPK at a small (0.3×median) bandwidth** (`GPK@0.3med` in `extra["members"]`). Its p-value comes from
|
|
56
|
+
a **permutation**: the members are permuted and flat-Simes-combined, and *that combined value is
|
|
57
|
+
then permuted too*, so the final p-value is the permutation p of the combination, not an analytic
|
|
58
|
+
Simes threshold (exact under exchangeability; `omnibus_null="simes"` uses the analytic threshold instead). It stays sensitive across location, dispersion, shape, and
|
|
59
|
+
matched-moment multimodality (the small-bandwidth member catches equal-mean/equal-variance
|
|
60
|
+
multimodal differences the others miss). It returns one calibrated p-value plus its RISE
|
|
61
|
+
member's signed **Z_W**/**Z_D** arms — location- and spread/shape-sensitive (see Attribution) —
|
|
62
|
+
and `extra["members"]` holds the three member p-values.
|
|
63
|
+
|
|
64
|
+
**Attribution (`Z_W`, `Z_D`).** `X` and `Y` are interchangeable — the p-value is the same
|
|
65
|
+
either way; only the *sign* of `Z_D` depends on order (swapping flips it), so put the reference
|
|
66
|
+
first for a directional read. `Z_D` is the spread/shape arm, not a variance test: for a pure
|
|
67
|
+
scale change `Z_D<0` reads as `Y` less dispersed than `X`, but `Z_D` also fires on shape, tail,
|
|
68
|
+
and matched-moment / multimodal differences — it can be significant, in either sign, even when
|
|
69
|
+
the first two moments of `X` and `Y` already match. `Z_W` is the location arm, but that reading
|
|
70
|
+
is clean only in moderate-to-high dimension: at low `d` a spread/shape difference also inflates
|
|
71
|
+
`Z_W`, so a large `Z_W` there does not by itself isolate a mean shift. Both are attribution
|
|
72
|
+
only; the omnibus p-value does not depend on the split.
|
|
73
|
+
|
|
74
|
+
**How the ZID p-value is built** (`omnibus_null="perm"`, the default): under one shared set of
|
|
75
|
+
`n_perm` label permutations, (1) each member statistic gets a permutation-rank p, (2) the three
|
|
76
|
+
are flat-Simes-combined into one value, and (3) *that combined value is re-ranked against the same
|
|
77
|
+
permutations* — step (3) is the second permutation, and it's what makes the whole thing exact
|
|
78
|
+
under exchangeability (no Simes/PRDS assumption). `omnibus_null="simes"` stops after an analytic
|
|
79
|
+
Simes threshold on the member p-values (faster; Type-I holds empirically). For a fully analytic
|
|
80
|
+
GPK, use `method="gpk", gpk_null="analytic"` (the fast fGPK approximation).
|
|
81
|
+
|
|
82
|
+
For a quick, fully analytic single-member check — or on very large `n` — use
|
|
83
|
+
**`method="rise"`**: RISE alone is itself a χ²₂ = `Z_W²+Z_D²` test, robust across geometries,
|
|
84
|
+
deterministic, no permutation.
|
|
85
|
+
|
|
86
|
+
## Routing — which member when
|
|
87
|
+
You almost never need to choose by hand: the default ZID omnibus covers everything. When you do
|
|
88
|
+
want a specialist (known geometry, max power, or discrete data), route on geometry rather than on
|
|
89
|
+
the smallest p-value:
|
|
90
|
+
|
|
91
|
+
| your situation | route → | why |
|
|
92
|
+
|----------------|---------|-----|
|
|
93
|
+
| **anything / unsure** (default) | **ZID** (omnibus) | broad coverage: joint permutation over RISE + GPK@median + GPK@0.3med; location / dispersion / shape / multimodality |
|
|
94
|
+
| **quick check / very large n** | **RISE** (`method="rise"`) | fast analytic single-member test (χ²₂), deterministic, no permutation |
|
|
95
|
+
| non-Euclidean / **manifold** / graph features | **RISE** | local rank-graph; a global kernel bandwidth smooths manifold density and goes blind to dispersion |
|
|
96
|
+
| plain **Euclidean / Gaussian**, want max power | **GPK** | kernel power specialist (`Z_W²+Z_D²`), strong on location *and* dispersion in high-d |
|
|
97
|
+
| need **attribution** (which arm?) | **RISE** (read `Z_W` & `Z_D`) | RISE/GET/GPK give a clean two-axis read |
|
|
98
|
+
| **discrete / heavy ties** (genotypes, counts, rounded) | **GET-discrete** (`method="get", discrete=True`) | validated tied-distance null; the graph/kernel nulls miscalibrate on ties |
|
|
99
|
+
| location said to dominate (`Z_D≈0`) | a **mean test** (t / Hotelling) | the verdict flags this; use zidstats when scale/shape may *also* differ |
|
|
100
|
+
|
|
101
|
+
## Non-Euclidean / object data
|
|
102
|
+
Euclidean is the default. To use another distance on point inputs, pass `metric=` — any scipy
|
|
103
|
+
`cdist` metric name or a callable `f(u, v) -> float`; the test then runs DIRECTLY on that
|
|
104
|
+
dissimilarity (no embedding, no metric assumption):
|
|
105
|
+
```python
|
|
106
|
+
res = two_sample_test(X, Y, metric="cosine") # or "cityblock", "braycurtis", ...
|
|
107
|
+
```
|
|
108
|
+
If your data is only a **precomputed pairwise dissimilarity matrix** (graphs, trees, sequences,
|
|
109
|
+
any custom measure), use `two_sample_from_dissimilarity(D, n_x)` — it runs the family directly on
|
|
110
|
+
`D` (first `n_x` rows = group 1). The dissimilarity need not be a metric (no triangle inequality).
|
|
111
|
+
|
|
112
|
+
## Member roles
|
|
113
|
+
- **ZID — the default (omnibus).** Run it first: a joint permutation over RISE + GPK@median + GPK@0.3med → one calibrated p-value with the signed `Z_W`/`Z_D`. Broad coverage — location, dispersion, shape, matched-moment multimodality. The one call most users ever make.
|
|
114
|
+
- **RISE — the fast single-member path.** `method="rise"`: analytic χ²₂, deterministic, no permutation, manifold-aware, best-calibrated single test. Use for speed or very large `n`.
|
|
115
|
+
- **GPK — the Euclidean power specialist.** Use when the geometry is plainly Euclidean/Gaussian and you want maximum power on a location *or* continuous-dispersion difference. Caveat: a single global bandwidth can go blind on manifolds.
|
|
116
|
+
- **GET — the reference member.** The earliest of the family (edge-count); GPK and RISE descend from its within-group idea. A balanced graph alternative.
|
|
117
|
+
- **GET-discrete — the heavy-ties handler.** The validated path for discrete/tied data (genotypes 0/1/2, low counts, rounded values) where continuous nulls break.
|
|
118
|
+
|
|
119
|
+
## Methods
|
|
120
|
+
| method | type | p-value | sensitive to | tuning (default) |
|
|
121
|
+
|--------|------|---------|--------------|------------------|
|
|
122
|
+
| `zid` | omnibus (default) | **two-pass permutation (members permuted → flat-Simes → the combination permuted too)** (`omnibus_null="perm"`; `"simes"` = analytic Simes over members (RISE analytic χ²₂; GPK members per `gpk_null`)) | location + dispersion + shape + multimodality | `sigma`=median, small=0.3×; `n_perm`=200 |
|
|
123
|
+
| `gpk` | kernel | **permutation** (exact) | location + dispersion (Euclidean) | bandwidth = median |
|
|
124
|
+
| `gpk` + `gpk_null="analytic"` | kernel | analytic fGPK (fast approx of `gpk`; anti-conservative small-n) | location + dispersion | bandwidth = median |
|
|
125
|
+
| `rise` | graph-induced rank | **analytic χ²₂** | balance + mode-drop + dependence | k = ⌊N^0.65⌋ |
|
|
126
|
+
| `get` | MST edge-count | **analytic χ²₂** | scale + dependence | k = ⌊N^0.5⌋ |
|
|
127
|
+
|
|
128
|
+
The family tests are **distribution-free** — validated Python ports of the CRAN packages
|
|
129
|
+
`kerTests` / `GraphRankTest` / `gTests`.
|
|
130
|
+
|
|
131
|
+
**Attribution arms.** RISE and GET are χ²₂ = `Z_W²+Z_D²` with both arms `N(0,1)`, so they give
|
|
132
|
+
a **clean two-axis read**: two orthogonal arms, `Z_W` (location-sensitive) and `Z_D`
|
|
133
|
+
(spread/shape-sensitive), each with an analytic p-value (`2·Φ(−|·|)`). For the kernel member,
|
|
134
|
+
`GPK = Z_W²+Z_D²` too (Song & Chen 2024): its `Z_D` arm is `N(0,1)` with an analytic p, while
|
|
135
|
+
the non-normal `Z_W` arm (unweighted `Z_{W,1.0}` ≈ standardized MMD) gets an exact
|
|
136
|
+
**permutation** p (`extra["Z_W_pval_perm"]`). fGPK also exposes the weighted `N(0,1)` arms
|
|
137
|
+
`ZW1`/`ZW2`.
|
|
138
|
+
|
|
139
|
+
### Note on "kernel" (≠ MMD)
|
|
140
|
+
GPK is a kernel test but **not** MMD: MMD compares the two samples through the **cross** term
|
|
141
|
+
and is location-dominated / scale-blind in high dimension; GPK combines the **within-group**
|
|
142
|
+
similarities and so detects dispersion collapse (`MMD` is the `Z_W` arm alone; GPK adds `Z_D`).
|
|
143
|
+
|
|
144
|
+
## Cite
|
|
145
|
+
Methods: GPK (Song & Chen 2024, *Biometrika* 111(3):755–770), RISE (Zhou & Chen 2023,
|
|
146
|
+
COLT/PMLR 195), GET (Chen & Friedman 2017, *JASA* 112(517):397–409).
|
zidstats-0.1.0/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# zidstats — calibrated, distribution-free two-sample tests (with a p-value)
|
|
2
|
+
|
|
3
|
+
A family of **two-sample tests** (ZID, GPK, RISE, GET) that, unlike a bare
|
|
4
|
+
distance/scalar, return a **calibrated p-value** and, by using *within-group*
|
|
5
|
+
similarity rather than only the cross term, stay sensitive to **dispersion/scale and
|
|
6
|
+
shape** differences — not just location — in high dimension. Each also exposes signed
|
|
7
|
+
`Z_W`/`Z_D` arms — one location-sensitive, one spread/shape-sensitive — for reading the
|
|
8
|
+
*direction* of a difference, not just its size (see Attribution below for details).
|
|
9
|
+
|
|
10
|
+
General-purpose: anywhere you compare two samples `X` and `Y` and want a *significance
|
|
11
|
+
statement* plus *how* they differ, not just a number.
|
|
12
|
+
|
|
13
|
+
**Dissimilarity-native:** the tests run on any pairwise dissimilarity — no coordinates, no
|
|
14
|
+
metric, no triangle inequality — so any object type works (sequences, graphs, distributions,
|
|
15
|
+
…). Pass point matrices with a `metric`, or a precomputed dissimilarity via
|
|
16
|
+
`two_sample_from_dissimilarity`; point inputs are just the convenience case.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
```bash
|
|
20
|
+
pip install zidstats # numpy, scipy only
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick start — one call, zero choices
|
|
24
|
+
```python
|
|
25
|
+
import numpy as np
|
|
26
|
+
from zidstats import two_sample_test
|
|
27
|
+
|
|
28
|
+
rng = np.random.default_rng(0)
|
|
29
|
+
X = rng.standard_normal((300, 64))
|
|
30
|
+
Y = rng.standard_normal((300, 64)) * 0.85 # same mean, 15% less spread
|
|
31
|
+
res = two_sample_test(X, Y) # default = ZID omnibus; nothing else to pick
|
|
32
|
+
print(res)
|
|
33
|
+
# ZID: p=0.00498 (stat=181.4, alpha=0.05) -> reject H0.
|
|
34
|
+
# Significant difference driven by SMALLER dispersion (Z_D=-12.4).
|
|
35
|
+
res.pvalue, res.statistic, res.reject, res.verdict
|
|
36
|
+
res.extra["members"] # member p-values: RISE, GPK@median, GPK@0.3med
|
|
37
|
+
res.extra["Z_D"], res.extra["Z_D_pval"] # spread/shape-sensitive arm (from the RISE member)
|
|
38
|
+
res.extra["Z_W"], res.extra["Z_W_pval"] # location-sensitive arm — clean two-axis (from RISE)
|
|
39
|
+
```
|
|
40
|
+
**Run this first.** The default is **ZID**, a broad-coverage omnibus over three members —
|
|
41
|
+
**RISE**, **GPK at the median bandwidth**, and **GPK at a small (0.3×median) bandwidth** (`GPK@0.3med` in `extra["members"]`). Its p-value comes from
|
|
42
|
+
a **permutation**: the members are permuted and flat-Simes-combined, and *that combined value is
|
|
43
|
+
then permuted too*, so the final p-value is the permutation p of the combination, not an analytic
|
|
44
|
+
Simes threshold (exact under exchangeability; `omnibus_null="simes"` uses the analytic threshold instead). It stays sensitive across location, dispersion, shape, and
|
|
45
|
+
matched-moment multimodality (the small-bandwidth member catches equal-mean/equal-variance
|
|
46
|
+
multimodal differences the others miss). It returns one calibrated p-value plus its RISE
|
|
47
|
+
member's signed **Z_W**/**Z_D** arms — location- and spread/shape-sensitive (see Attribution) —
|
|
48
|
+
and `extra["members"]` holds the three member p-values.
|
|
49
|
+
|
|
50
|
+
**Attribution (`Z_W`, `Z_D`).** `X` and `Y` are interchangeable — the p-value is the same
|
|
51
|
+
either way; only the *sign* of `Z_D` depends on order (swapping flips it), so put the reference
|
|
52
|
+
first for a directional read. `Z_D` is the spread/shape arm, not a variance test: for a pure
|
|
53
|
+
scale change `Z_D<0` reads as `Y` less dispersed than `X`, but `Z_D` also fires on shape, tail,
|
|
54
|
+
and matched-moment / multimodal differences — it can be significant, in either sign, even when
|
|
55
|
+
the first two moments of `X` and `Y` already match. `Z_W` is the location arm, but that reading
|
|
56
|
+
is clean only in moderate-to-high dimension: at low `d` a spread/shape difference also inflates
|
|
57
|
+
`Z_W`, so a large `Z_W` there does not by itself isolate a mean shift. Both are attribution
|
|
58
|
+
only; the omnibus p-value does not depend on the split.
|
|
59
|
+
|
|
60
|
+
**How the ZID p-value is built** (`omnibus_null="perm"`, the default): under one shared set of
|
|
61
|
+
`n_perm` label permutations, (1) each member statistic gets a permutation-rank p, (2) the three
|
|
62
|
+
are flat-Simes-combined into one value, and (3) *that combined value is re-ranked against the same
|
|
63
|
+
permutations* — step (3) is the second permutation, and it's what makes the whole thing exact
|
|
64
|
+
under exchangeability (no Simes/PRDS assumption). `omnibus_null="simes"` stops after an analytic
|
|
65
|
+
Simes threshold on the member p-values (faster; Type-I holds empirically). For a fully analytic
|
|
66
|
+
GPK, use `method="gpk", gpk_null="analytic"` (the fast fGPK approximation).
|
|
67
|
+
|
|
68
|
+
For a quick, fully analytic single-member check — or on very large `n` — use
|
|
69
|
+
**`method="rise"`**: RISE alone is itself a χ²₂ = `Z_W²+Z_D²` test, robust across geometries,
|
|
70
|
+
deterministic, no permutation.
|
|
71
|
+
|
|
72
|
+
## Routing — which member when
|
|
73
|
+
You almost never need to choose by hand: the default ZID omnibus covers everything. When you do
|
|
74
|
+
want a specialist (known geometry, max power, or discrete data), route on geometry rather than on
|
|
75
|
+
the smallest p-value:
|
|
76
|
+
|
|
77
|
+
| your situation | route → | why |
|
|
78
|
+
|----------------|---------|-----|
|
|
79
|
+
| **anything / unsure** (default) | **ZID** (omnibus) | broad coverage: joint permutation over RISE + GPK@median + GPK@0.3med; location / dispersion / shape / multimodality |
|
|
80
|
+
| **quick check / very large n** | **RISE** (`method="rise"`) | fast analytic single-member test (χ²₂), deterministic, no permutation |
|
|
81
|
+
| non-Euclidean / **manifold** / graph features | **RISE** | local rank-graph; a global kernel bandwidth smooths manifold density and goes blind to dispersion |
|
|
82
|
+
| plain **Euclidean / Gaussian**, want max power | **GPK** | kernel power specialist (`Z_W²+Z_D²`), strong on location *and* dispersion in high-d |
|
|
83
|
+
| need **attribution** (which arm?) | **RISE** (read `Z_W` & `Z_D`) | RISE/GET/GPK give a clean two-axis read |
|
|
84
|
+
| **discrete / heavy ties** (genotypes, counts, rounded) | **GET-discrete** (`method="get", discrete=True`) | validated tied-distance null; the graph/kernel nulls miscalibrate on ties |
|
|
85
|
+
| location said to dominate (`Z_D≈0`) | a **mean test** (t / Hotelling) | the verdict flags this; use zidstats when scale/shape may *also* differ |
|
|
86
|
+
|
|
87
|
+
## Non-Euclidean / object data
|
|
88
|
+
Euclidean is the default. To use another distance on point inputs, pass `metric=` — any scipy
|
|
89
|
+
`cdist` metric name or a callable `f(u, v) -> float`; the test then runs DIRECTLY on that
|
|
90
|
+
dissimilarity (no embedding, no metric assumption):
|
|
91
|
+
```python
|
|
92
|
+
res = two_sample_test(X, Y, metric="cosine") # or "cityblock", "braycurtis", ...
|
|
93
|
+
```
|
|
94
|
+
If your data is only a **precomputed pairwise dissimilarity matrix** (graphs, trees, sequences,
|
|
95
|
+
any custom measure), use `two_sample_from_dissimilarity(D, n_x)` — it runs the family directly on
|
|
96
|
+
`D` (first `n_x` rows = group 1). The dissimilarity need not be a metric (no triangle inequality).
|
|
97
|
+
|
|
98
|
+
## Member roles
|
|
99
|
+
- **ZID — the default (omnibus).** Run it first: a joint permutation over RISE + GPK@median + GPK@0.3med → one calibrated p-value with the signed `Z_W`/`Z_D`. Broad coverage — location, dispersion, shape, matched-moment multimodality. The one call most users ever make.
|
|
100
|
+
- **RISE — the fast single-member path.** `method="rise"`: analytic χ²₂, deterministic, no permutation, manifold-aware, best-calibrated single test. Use for speed or very large `n`.
|
|
101
|
+
- **GPK — the Euclidean power specialist.** Use when the geometry is plainly Euclidean/Gaussian and you want maximum power on a location *or* continuous-dispersion difference. Caveat: a single global bandwidth can go blind on manifolds.
|
|
102
|
+
- **GET — the reference member.** The earliest of the family (edge-count); GPK and RISE descend from its within-group idea. A balanced graph alternative.
|
|
103
|
+
- **GET-discrete — the heavy-ties handler.** The validated path for discrete/tied data (genotypes 0/1/2, low counts, rounded values) where continuous nulls break.
|
|
104
|
+
|
|
105
|
+
## Methods
|
|
106
|
+
| method | type | p-value | sensitive to | tuning (default) |
|
|
107
|
+
|--------|------|---------|--------------|------------------|
|
|
108
|
+
| `zid` | omnibus (default) | **two-pass permutation (members permuted → flat-Simes → the combination permuted too)** (`omnibus_null="perm"`; `"simes"` = analytic Simes over members (RISE analytic χ²₂; GPK members per `gpk_null`)) | location + dispersion + shape + multimodality | `sigma`=median, small=0.3×; `n_perm`=200 |
|
|
109
|
+
| `gpk` | kernel | **permutation** (exact) | location + dispersion (Euclidean) | bandwidth = median |
|
|
110
|
+
| `gpk` + `gpk_null="analytic"` | kernel | analytic fGPK (fast approx of `gpk`; anti-conservative small-n) | location + dispersion | bandwidth = median |
|
|
111
|
+
| `rise` | graph-induced rank | **analytic χ²₂** | balance + mode-drop + dependence | k = ⌊N^0.65⌋ |
|
|
112
|
+
| `get` | MST edge-count | **analytic χ²₂** | scale + dependence | k = ⌊N^0.5⌋ |
|
|
113
|
+
|
|
114
|
+
The family tests are **distribution-free** — validated Python ports of the CRAN packages
|
|
115
|
+
`kerTests` / `GraphRankTest` / `gTests`.
|
|
116
|
+
|
|
117
|
+
**Attribution arms.** RISE and GET are χ²₂ = `Z_W²+Z_D²` with both arms `N(0,1)`, so they give
|
|
118
|
+
a **clean two-axis read**: two orthogonal arms, `Z_W` (location-sensitive) and `Z_D`
|
|
119
|
+
(spread/shape-sensitive), each with an analytic p-value (`2·Φ(−|·|)`). For the kernel member,
|
|
120
|
+
`GPK = Z_W²+Z_D²` too (Song & Chen 2024): its `Z_D` arm is `N(0,1)` with an analytic p, while
|
|
121
|
+
the non-normal `Z_W` arm (unweighted `Z_{W,1.0}` ≈ standardized MMD) gets an exact
|
|
122
|
+
**permutation** p (`extra["Z_W_pval_perm"]`). fGPK also exposes the weighted `N(0,1)` arms
|
|
123
|
+
`ZW1`/`ZW2`.
|
|
124
|
+
|
|
125
|
+
### Note on "kernel" (≠ MMD)
|
|
126
|
+
GPK is a kernel test but **not** MMD: MMD compares the two samples through the **cross** term
|
|
127
|
+
and is location-dominated / scale-blind in high dimension; GPK combines the **within-group**
|
|
128
|
+
similarities and so detects dispersion collapse (`MMD` is the `Z_W` arm alone; GPK adds `Z_D`).
|
|
129
|
+
|
|
130
|
+
## Cite
|
|
131
|
+
Methods: GPK (Song & Chen 2024, *Biometrika* 111(3):755–770), RISE (Zhou & Chen 2023,
|
|
132
|
+
COLT/PMLR 195), GET (Chen & Friedman 2017, *JASA* 112(517):397–409).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "zidstats"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Calibrated, distribution-free two-sample tests with a p-value and signed location/dispersion attribution (ZID / GPK / RISE / GET)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Hao Chen", email = "hxchen@ucdavis.edu" }]
|
|
13
|
+
keywords = ["two-sample-test", "distribution-free", "calibrated", "p-value",
|
|
14
|
+
"kernel-test", "graph-based-test", "dispersion", "attribution",
|
|
15
|
+
"nonparametric", "high-dimensional"]
|
|
16
|
+
dependencies = ["numpy>=1.20", "scipy>=1.6"]
|
|
17
|
+
|
|
18
|
+
[project.urls]
|
|
19
|
+
Homepage = "https://github.com/haochen6/zidstats"
|
|
20
|
+
|
|
21
|
+
[tool.setuptools.packages.find]
|
|
22
|
+
where = ["src"]
|
|
23
|
+
include = ["zidstats*"]
|
|
24
|
+
|
|
25
|
+
[tool.pytest.ini_options]
|
|
26
|
+
testpaths = ["tests"]
|
zidstats-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""zidstats — calibrated, distribution-free two-sample tests (with a p-value).
|
|
2
|
+
|
|
3
|
+
A general-purpose family of two-sample tests (ZID, GPK, RISE, GET): unlike a bare
|
|
4
|
+
distance/scalar, each returns a calibrated p-value and, by using *within-group*
|
|
5
|
+
similarity rather than only the cross term, stays sensitive to dispersion/scale and
|
|
6
|
+
shape differences (not just location) in high dimension, with a signed location/
|
|
7
|
+
dispersion attribution (Z_W, Z_D). The tests are dissimilarity-native: they run on any
|
|
8
|
+
pairwise dissimilarity (no metric, no triangle inequality), so any object type works via
|
|
9
|
+
two_sample_from_dissimilarity; point inputs are the convenience case.
|
|
10
|
+
|
|
11
|
+
from zidstats import two_sample_test
|
|
12
|
+
res = two_sample_test(X, Y) # X, Y: (n, d) sample matrices -> ZID omnibus (default)
|
|
13
|
+
print(res) # p-value + plain-language verdict
|
|
14
|
+
res.pvalue, res.reject, res.statistic # res.extra['Z_D'] = dispersion arm
|
|
15
|
+
|
|
16
|
+
One call, zero choices: the default is **ZID**, the broad-coverage omnibus (a joint
|
|
17
|
+
permutation over RISE + GPK@median + GPK@0.3med; catches location, dispersion, shape, and matched-
|
|
18
|
+
moment multimodality), returning one calibrated p-value plus the signed Z_W/Z_D arms
|
|
19
|
+
from its RISE member. Use ``method="rise"`` for a fast analytic single-member check.
|
|
20
|
+
For object data given as a precomputed dissimilarity matrix, use ``two_sample_from_dissimilarity``.
|
|
21
|
+
Members: ZID (default) / RISE (fast single-member) / GPK (Euclidean power specialist;
|
|
22
|
+
gpk_null="analytic" for its fast fGPK approximation) / GET (reference) / GET-discrete (heavy ties).
|
|
23
|
+
"""
|
|
24
|
+
from .api import two_sample_test, TestResult, FAMILY, BASELINES, DuplicateSampleWarning
|
|
25
|
+
from .gpk import kertests, median_heuristic
|
|
26
|
+
from .rise import rise_test
|
|
27
|
+
from .get_test import get_test, get_test_discrete
|
|
28
|
+
from .distance import two_sample_from_dissimilarity, two_sample_from_distance, pcoa_coords
|
|
29
|
+
|
|
30
|
+
__version__ = "0.1.0"
|
|
31
|
+
__all__ = [
|
|
32
|
+
"two_sample_test", "TestResult", "FAMILY", "BASELINES", "DuplicateSampleWarning",
|
|
33
|
+
"kertests", "median_heuristic", "rise_test", "get_test", "get_test_discrete",
|
|
34
|
+
"two_sample_from_dissimilarity", "two_sample_from_distance", "pcoa_coords",
|
|
35
|
+
"__version__",
|
|
36
|
+
]
|