gating-metrics 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.
- gating_metrics-0.1.0/LICENSE +21 -0
- gating_metrics-0.1.0/PKG-INFO +105 -0
- gating_metrics-0.1.0/README.md +83 -0
- gating_metrics-0.1.0/gating_metrics/__init__.py +1 -0
- gating_metrics-0.1.0/gating_metrics/coupling.py +84 -0
- gating_metrics-0.1.0/gating_metrics/partition.py +85 -0
- gating_metrics-0.1.0/gating_metrics/platform_contrast.py +52 -0
- gating_metrics-0.1.0/gating_metrics/power.py +29 -0
- gating_metrics-0.1.0/gating_metrics/test_gating_metrics.py +77 -0
- gating_metrics-0.1.0/gating_metrics.egg-info/PKG-INFO +105 -0
- gating_metrics-0.1.0/gating_metrics.egg-info/SOURCES.txt +14 -0
- gating_metrics-0.1.0/gating_metrics.egg-info/dependency_links.txt +1 -0
- gating_metrics-0.1.0/gating_metrics.egg-info/requires.txt +3 -0
- gating_metrics-0.1.0/gating_metrics.egg-info/top_level.txt +1 -0
- gating_metrics-0.1.0/pyproject.toml +28 -0
- gating_metrics-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Liu Yuanshui and the ARDS gating-circuit study authors
|
|
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.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gating-metrics
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Measurement toolkit for state-gated co-expression coupling: layer couplings, exact covariance partition, platform contrast, closed-form power planning
|
|
5
|
+
Author: Liu Yuanshui
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: OSF registration, https://doi.org/10.17605/OSF.IO/4TZH9
|
|
8
|
+
Project-URL: OSF project, https://osf.io/v8w5a
|
|
9
|
+
Project-URL: Homepage, https://github.com/Shuiruolys-Liu/gating-metrics
|
|
10
|
+
Keywords: co-expression,single-cell,gating,ARDS,inflammasome,measurement
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: numpy>=1.24
|
|
19
|
+
Requires-Dist: pandas>=2.0
|
|
20
|
+
Requires-Dist: scipy>=1.10
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# gating_metrics — measurement toolkit for state-gated co-expression coupling
|
|
24
|
+
|
|
25
|
+
Open-source Python toolkit accompanying the ARDS gating-circuit study
|
|
26
|
+
(manifest modules F1/F2/F3, registered 2026-08-18 before execution). It
|
|
27
|
+
formalises the study's core measurement claim — that a state-gated
|
|
28
|
+
hub–module coupling (e.g. KAT2A vs the inflammasome module) is an
|
|
29
|
+
**individual-level, platform-dependent emergent measurement** — into
|
|
30
|
+
reusable, tested routines.
|
|
31
|
+
|
|
32
|
+
## What it provides
|
|
33
|
+
|
|
34
|
+
| Module | Contents |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `coupling.py` | Layer-wise Spearman coupling: `L_cell` (within-sample per-cell, with detected-gene-count partial sensitivity), `L_indiv` (between-individual pseudobulk), `L_bulk` (bulk-assay). |
|
|
37
|
+
| `partition.py` | Exact method-of-moments (nested ANOVA cross-product) covariance partition: between-individual / between-compartment-within-individual / within components, with sample-level bootstrap CIs. |
|
|
38
|
+
| `platform_contrast.py` (renamed from `platform.py` to avoid the stdlib collision) | Fisher-Z same-compartment cross-platform contrast (the "is it a platform artefact?" test) and the measurement-conditions compatibility report / atlas verdict (frozen classification thresholds: DOMINANT-NEGATIVE r≤−0.2 & p<0.05; DOMINANT-POSITIVE r≥+0.2 & p<0.05; else INDETERMINATE; atlas support = ≥80% DOMINANT-NEGATIVE within a condition class). |
|
|
39
|
+
| `power.py` | Closed-form Fisher-Z planning tools: n for a target coupling, power at n, minimum detectable |r|. |
|
|
40
|
+
|
|
41
|
+
## Quick start
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import pandas as pd
|
|
45
|
+
from coupling import layer_cell, layer_indiv
|
|
46
|
+
from partition import partition, bootstrap_share_between
|
|
47
|
+
|
|
48
|
+
# cells: one row per cell with columns sample, compartment, x (hub gene),
|
|
49
|
+
# y (module score), ng (detected-gene count)
|
|
50
|
+
per_group, summary = layer_cell(cells, ["sample", "compartment"])
|
|
51
|
+
r_indiv, p_indiv = layer_indiv(pseudobulk) # one row per sample
|
|
52
|
+
part = partition(cells, by=("sample", "compartment"))
|
|
53
|
+
lo, hi = bootstrap_share_between(cells, by=("sample", "compartment"))
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Self-tests (synthetic two-level data, seed 42; partition identity, power
|
|
57
|
+
closed form, Fisher-Z sign reversal, classification thresholds):
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
python test_gating_metrics.py
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Registered falsifiable prediction (public test for the field)
|
|
64
|
+
|
|
65
|
+
Any new **acute-inflammation whole-blood cohort assayed in bulk** (array or
|
|
66
|
+
bulk RNA-seq) should reproduce a **negative reference-stratum hub–module
|
|
67
|
+
coupling**; **scRNA pseudobulk and protein layers are expected non-negative**
|
|
68
|
+
(same compartment). The F1/F2 tables in the companion manuscript are the
|
|
69
|
+
current evidence base; the prediction is falsifiable by any cohort that
|
|
70
|
+
violates the sign-by-condition pattern.
|
|
71
|
+
|
|
72
|
+
**Public registration**: OSF Preregistration, 2026-08-18 —
|
|
73
|
+
DOI [10.17605/OSF.IO/4TZH9](https://doi.org/10.17605/OSF.IO/4TZH9)
|
|
74
|
+
(associated project https://osf.io/v8w5a).
|
|
75
|
+
|
|
76
|
+
**External validation status (recorded 2026-08-19, honest)**: the authors'
|
|
77
|
+
own value-blind external hold-out (module G1; four never-used
|
|
78
|
+
acute-inflammation blood cohorts GSE66099/GSE40012/GSE57065/GSE37069)
|
|
79
|
+
did **NOT** confirm the reference-stratum prediction (2/4 direction-negative;
|
|
80
|
+
pooled fixed-effect r=+0.004). The registered text above is kept unchanged as
|
|
81
|
+
the auditable record; by the external data, the reference-stratum claim is
|
|
82
|
+
refuted and the inflamed-stratum negative coupling is supported (manuscript
|
|
83
|
+
TableS36). Independent replication by other groups remains the intended
|
|
84
|
+
falsification channel.
|
|
85
|
+
|
|
86
|
+
## Installation
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pip install gating-metrics # once published on PyPI (pending author action)
|
|
90
|
+
# or from source:
|
|
91
|
+
git clone https://github.com/Shuiruolys-Liu/gating-metrics
|
|
92
|
+
cd gating-metrics && pip install .
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Provenance
|
|
96
|
+
|
|
97
|
+
Metric definitions are copied verbatim from the frozen F1 decomposition
|
|
98
|
+
dictionary (`manifest.json -> preregistrations.modules.
|
|
99
|
+
F1_variance_decomposition_gating`, registered 2026-08-18). No metric in this
|
|
100
|
+
package was tuned after inspecting the outcome data.
|
|
101
|
+
|
|
102
|
+
## Citing
|
|
103
|
+
|
|
104
|
+
If you use this toolkit, please cite the companion manuscript (under
|
|
105
|
+
preparation) and the OSF registration (DOI 10.17605/OSF.IO/4TZH9).
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# gating_metrics — measurement toolkit for state-gated co-expression coupling
|
|
2
|
+
|
|
3
|
+
Open-source Python toolkit accompanying the ARDS gating-circuit study
|
|
4
|
+
(manifest modules F1/F2/F3, registered 2026-08-18 before execution). It
|
|
5
|
+
formalises the study's core measurement claim — that a state-gated
|
|
6
|
+
hub–module coupling (e.g. KAT2A vs the inflammasome module) is an
|
|
7
|
+
**individual-level, platform-dependent emergent measurement** — into
|
|
8
|
+
reusable, tested routines.
|
|
9
|
+
|
|
10
|
+
## What it provides
|
|
11
|
+
|
|
12
|
+
| Module | Contents |
|
|
13
|
+
|---|---|
|
|
14
|
+
| `coupling.py` | Layer-wise Spearman coupling: `L_cell` (within-sample per-cell, with detected-gene-count partial sensitivity), `L_indiv` (between-individual pseudobulk), `L_bulk` (bulk-assay). |
|
|
15
|
+
| `partition.py` | Exact method-of-moments (nested ANOVA cross-product) covariance partition: between-individual / between-compartment-within-individual / within components, with sample-level bootstrap CIs. |
|
|
16
|
+
| `platform_contrast.py` (renamed from `platform.py` to avoid the stdlib collision) | Fisher-Z same-compartment cross-platform contrast (the "is it a platform artefact?" test) and the measurement-conditions compatibility report / atlas verdict (frozen classification thresholds: DOMINANT-NEGATIVE r≤−0.2 & p<0.05; DOMINANT-POSITIVE r≥+0.2 & p<0.05; else INDETERMINATE; atlas support = ≥80% DOMINANT-NEGATIVE within a condition class). |
|
|
17
|
+
| `power.py` | Closed-form Fisher-Z planning tools: n for a target coupling, power at n, minimum detectable |r|. |
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
import pandas as pd
|
|
23
|
+
from coupling import layer_cell, layer_indiv
|
|
24
|
+
from partition import partition, bootstrap_share_between
|
|
25
|
+
|
|
26
|
+
# cells: one row per cell with columns sample, compartment, x (hub gene),
|
|
27
|
+
# y (module score), ng (detected-gene count)
|
|
28
|
+
per_group, summary = layer_cell(cells, ["sample", "compartment"])
|
|
29
|
+
r_indiv, p_indiv = layer_indiv(pseudobulk) # one row per sample
|
|
30
|
+
part = partition(cells, by=("sample", "compartment"))
|
|
31
|
+
lo, hi = bootstrap_share_between(cells, by=("sample", "compartment"))
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Self-tests (synthetic two-level data, seed 42; partition identity, power
|
|
35
|
+
closed form, Fisher-Z sign reversal, classification thresholds):
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
python test_gating_metrics.py
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Registered falsifiable prediction (public test for the field)
|
|
42
|
+
|
|
43
|
+
Any new **acute-inflammation whole-blood cohort assayed in bulk** (array or
|
|
44
|
+
bulk RNA-seq) should reproduce a **negative reference-stratum hub–module
|
|
45
|
+
coupling**; **scRNA pseudobulk and protein layers are expected non-negative**
|
|
46
|
+
(same compartment). The F1/F2 tables in the companion manuscript are the
|
|
47
|
+
current evidence base; the prediction is falsifiable by any cohort that
|
|
48
|
+
violates the sign-by-condition pattern.
|
|
49
|
+
|
|
50
|
+
**Public registration**: OSF Preregistration, 2026-08-18 —
|
|
51
|
+
DOI [10.17605/OSF.IO/4TZH9](https://doi.org/10.17605/OSF.IO/4TZH9)
|
|
52
|
+
(associated project https://osf.io/v8w5a).
|
|
53
|
+
|
|
54
|
+
**External validation status (recorded 2026-08-19, honest)**: the authors'
|
|
55
|
+
own value-blind external hold-out (module G1; four never-used
|
|
56
|
+
acute-inflammation blood cohorts GSE66099/GSE40012/GSE57065/GSE37069)
|
|
57
|
+
did **NOT** confirm the reference-stratum prediction (2/4 direction-negative;
|
|
58
|
+
pooled fixed-effect r=+0.004). The registered text above is kept unchanged as
|
|
59
|
+
the auditable record; by the external data, the reference-stratum claim is
|
|
60
|
+
refuted and the inflamed-stratum negative coupling is supported (manuscript
|
|
61
|
+
TableS36). Independent replication by other groups remains the intended
|
|
62
|
+
falsification channel.
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install gating-metrics # once published on PyPI (pending author action)
|
|
68
|
+
# or from source:
|
|
69
|
+
git clone https://github.com/Shuiruolys-Liu/gating-metrics
|
|
70
|
+
cd gating-metrics && pip install .
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Provenance
|
|
74
|
+
|
|
75
|
+
Metric definitions are copied verbatim from the frozen F1 decomposition
|
|
76
|
+
dictionary (`manifest.json -> preregistrations.modules.
|
|
77
|
+
F1_variance_decomposition_gating`, registered 2026-08-18). No metric in this
|
|
78
|
+
package was tuned after inspecting the outcome data.
|
|
79
|
+
|
|
80
|
+
## Citing
|
|
81
|
+
|
|
82
|
+
If you use this toolkit, please cite the companion manuscript (under
|
|
83
|
+
preparation) and the OSF registration (DOI 10.17605/OSF.IO/4TZH9).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""gating_metrics — see README.md; modules: coupling, partition, platform_contrast, power."""
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""gating_metrics.coupling — layer-wise coupling metrics for state-gated
|
|
2
|
+
co-expression structures (channel-B methodology of the ARDS gating study).
|
|
3
|
+
|
|
4
|
+
Definitions are copied verbatim from the frozen F1 decomposition dictionary
|
|
5
|
+
(manifest F1_variance_decomposition_gating, registered 2026-08-18):
|
|
6
|
+
coupling metric = Spearman correlation between the hub gene (e.g. KAT2A)
|
|
7
|
+
and the module (e.g. mean of the inflammasome genes), evaluated at three
|
|
8
|
+
layers:
|
|
9
|
+
L_cell — within-sample, within-compartment, per-cell correlation, plus a
|
|
10
|
+
detected-gene-count partial sensitivity (library-complexity
|
|
11
|
+
background);
|
|
12
|
+
L_indiv — between-individual pseudobulk correlation across samples;
|
|
13
|
+
L_bulk — bulk-assay correlation across samples.
|
|
14
|
+
"""
|
|
15
|
+
import numpy as np
|
|
16
|
+
from scipy import stats
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def spearman_np(a, b, min_n=4):
|
|
20
|
+
"""Spearman r and p over the jointly-finite values of a and b."""
|
|
21
|
+
a, b = np.asarray(a, float), np.asarray(b, float)
|
|
22
|
+
m = np.isfinite(a) & np.isfinite(b)
|
|
23
|
+
if m.sum() < min_n or np.std(a[m]) == 0 or np.std(b[m]) == 0:
|
|
24
|
+
return np.nan, np.nan
|
|
25
|
+
return stats.spearmanr(a[m], b[m])
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def partial_spearman(a, b, c, min_n=8):
|
|
29
|
+
"""Rank-partial Spearman of a vs b controlling c (residualised ranks)."""
|
|
30
|
+
a, b, c = np.asarray(a, float), np.asarray(b, float), np.asarray(c, float)
|
|
31
|
+
m = np.isfinite(a) & np.isfinite(b) & np.isfinite(c)
|
|
32
|
+
if m.sum() < min_n:
|
|
33
|
+
return np.nan, np.nan
|
|
34
|
+
ra, rb, rc = (stats.rankdata(v[m]) for v in (a, b, c))
|
|
35
|
+
|
|
36
|
+
def resid(y):
|
|
37
|
+
X = np.column_stack([np.ones_like(rc), rc])
|
|
38
|
+
beta, *_ = np.linalg.lstsq(X, y, rcond=None)
|
|
39
|
+
return y - X @ beta
|
|
40
|
+
|
|
41
|
+
ea, eb = resid(ra), resid(rb)
|
|
42
|
+
if np.std(ea) == 0 or np.std(eb) == 0:
|
|
43
|
+
return np.nan, np.nan
|
|
44
|
+
return stats.pearsonr(ea, eb)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def layer_cell(df, group_cols, x="x", y="y", background="ng", min_cells=30):
|
|
48
|
+
"""L_cell: per-group within-sample Spearman (x vs y) and its partial
|
|
49
|
+
(controlling `background`, e.g. per-cell detected-gene count).
|
|
50
|
+
|
|
51
|
+
df: long dataframe with one row per CELL plus the group columns
|
|
52
|
+
(e.g. ['sample','compartment']); returns per-group r/p plus the
|
|
53
|
+
median-across-groups summary.
|
|
54
|
+
"""
|
|
55
|
+
rows = []
|
|
56
|
+
for key, g in df.groupby(group_cols):
|
|
57
|
+
if len(g) < min_cells:
|
|
58
|
+
continue
|
|
59
|
+
r, p = spearman_np(g[x].values, g[y].values)
|
|
60
|
+
rp, pp = partial_spearman(g[x].values, g[y].values, g[background].values) \
|
|
61
|
+
if background in g else (np.nan, np.nan)
|
|
62
|
+
rows.append(dict(zip(group_cols, key if isinstance(key, tuple) else (key,)),
|
|
63
|
+
n_cells=len(g), r=r, p=p, r_partial=r, p_partial=pp))
|
|
64
|
+
per = pd.DataFrame(rows) if rows else None
|
|
65
|
+
import pandas as pd
|
|
66
|
+
per = pd.DataFrame(rows)
|
|
67
|
+
if len(per):
|
|
68
|
+
summary = dict(median_r=float(np.nanmedian(per.r)),
|
|
69
|
+
median_r_partial=float(np.nanmedian(per.r_partial)),
|
|
70
|
+
n_groups=int(len(per)))
|
|
71
|
+
else:
|
|
72
|
+
summary = dict(median_r=np.nan, median_r_partial=np.nan, n_groups=0)
|
|
73
|
+
return per, summary
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def layer_indiv(pb, x="x", y="y", stratum=None, min_n=8):
|
|
77
|
+
"""L_indiv: between-individual Spearman across sample-level pseudobulk
|
|
78
|
+
values (pb: one row per sample[ x stratum])."""
|
|
79
|
+
if stratum:
|
|
80
|
+
out = {}
|
|
81
|
+
for s, g in pb.dropna(subset=[x, y]).groupby(stratum):
|
|
82
|
+
out[s] = spearman_np(g[x].values, g[y].values) if len(g) >= min_n else (np.nan, np.nan)
|
|
83
|
+
return out
|
|
84
|
+
return spearman_np(pb[x].values, pb[y].values)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""gating_metrics.partition — exact method-of-moments (nested ANOVA
|
|
2
|
+
cross-product) decomposition of a two-variable covariance across hierarchy
|
|
3
|
+
levels (frozen F1 dictionary): between-individual (between-sample),
|
|
4
|
+
between-compartment-within-sample, and within-sample residual components.
|
|
5
|
+
|
|
6
|
+
The partition is exact algebra:
|
|
7
|
+
SSP_tot = sum_ij (x_ij - xbar)(y_ij - ybar)
|
|
8
|
+
SSP_between = sum_s n_s (xbar_s - xbar)(ybar_s - ybar)
|
|
9
|
+
SSP_comp = sum_{s,c} n_sc (xbar_sc - xbar_s)(ybar_sc - ybar_s)
|
|
10
|
+
SSP_within = SSP_tot - SSP_between - SSP_comp
|
|
11
|
+
with bootstrap CIs obtained by resampling INDIVIDUALS (samples), the
|
|
12
|
+
independent units of the hierarchy.
|
|
13
|
+
"""
|
|
14
|
+
import numpy as np
|
|
15
|
+
import pandas as pd
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def group_stats(df, x="x", y="y", by=("sample", "comp")):
|
|
19
|
+
d = df.copy()
|
|
20
|
+
d["xy"] = d[x] * d[y]
|
|
21
|
+
return d.groupby(list(by)).agg(n=(x, "size"), sx=(x, "sum"), sy=(y, "sum"),
|
|
22
|
+
sxy=("xy", "sum")).reset_index()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def partition_from_gs(gs):
|
|
26
|
+
"""Partition from per-group sufficient statistics (n, sx, sy, sxy)."""
|
|
27
|
+
N = gs.n.sum()
|
|
28
|
+
xbar, ybar = gs.sx.sum() / N, gs.sy.sum() / N
|
|
29
|
+
SSP_tot = gs.sxy.sum() - N * xbar * ybar
|
|
30
|
+
gs = gs.copy()
|
|
31
|
+
gs["mx"], gs["my"] = gs.sx / gs.n, gs.sy / gs.n
|
|
32
|
+
ss_rows = []
|
|
33
|
+
for s, g in gs.groupby("sample" if "sample" in gs.columns else gs.columns[0]):
|
|
34
|
+
ss_rows.append({"sample": s, "n": g.n.sum(),
|
|
35
|
+
"mxs": np.average(g.mx, weights=g.n),
|
|
36
|
+
"mys": np.average(g.my, weights=g.n)})
|
|
37
|
+
ss = pd.DataFrame(ss_rows)
|
|
38
|
+
gcol = "sample" if "sample" in gs.columns else gs.columns[0]
|
|
39
|
+
SSP_B = float((ss.n * (ss.mxs - xbar) * (ss.mys - ybar)).sum())
|
|
40
|
+
merged = gs.merge(ss[[gcol, "mxs", "mys"]], on=gcol)
|
|
41
|
+
if len(gs.columns) > 4: # compartment level present
|
|
42
|
+
SSP_C = float((merged.n * (merged.mx - merged.mxs) *
|
|
43
|
+
(merged.my - merged.mys)).sum())
|
|
44
|
+
else:
|
|
45
|
+
SSP_C = 0.0
|
|
46
|
+
SSP_W = SSP_tot - SSP_B - SSP_C
|
|
47
|
+
tot = SSP_tot if SSP_tot != 0 else np.nan
|
|
48
|
+
return dict(n_cells=int(N), n_groups=len(gs), SSP_tot=SSP_tot,
|
|
49
|
+
SSP_between_individual=SSP_B,
|
|
50
|
+
SSP_between_compartment_within=SSP_C, SSP_within=SSP_W,
|
|
51
|
+
share_between_individual=SSP_B / tot if tot == tot else np.nan,
|
|
52
|
+
share_between_compartment=SSP_C / tot if tot == tot else np.nan,
|
|
53
|
+
share_within=SSP_W / tot if tot == tot else np.nan)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def partition(df, x="x", y="y", by=("sample", "comp")):
|
|
57
|
+
"""Convenience wrapper: partition + algebraic identity assert."""
|
|
58
|
+
gs = group_stats(df, x, y, by)
|
|
59
|
+
out = partition_from_gs(gs)
|
|
60
|
+
if by == ("sample",):
|
|
61
|
+
assert abs(out["SSP_tot"] - out["SSP_between_individual"]
|
|
62
|
+
- out["SSP_within"]) < 1e-6 * max(1, abs(out["SSP_tot"]))
|
|
63
|
+
else:
|
|
64
|
+
assert abs(out["SSP_tot"] - out["SSP_between_individual"]
|
|
65
|
+
- out["SSP_between_compartment_within"]
|
|
66
|
+
- out["SSP_within"]) < 1e-6 * max(1, abs(out["SSP_tot"]))
|
|
67
|
+
return out
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def bootstrap_share_between(df, x="x", y="y", by=("sample", "comp"),
|
|
71
|
+
n_boot=1000, seed=42):
|
|
72
|
+
"""Bootstrap CI of share_between_individual by resampling samples."""
|
|
73
|
+
rng = np.random.default_rng(seed)
|
|
74
|
+
gs = group_stats(df, x, y, by)
|
|
75
|
+
samples = gs["sample"].unique()
|
|
76
|
+
shares = []
|
|
77
|
+
for _ in range(n_boot):
|
|
78
|
+
draw = rng.choice(samples, len(samples), replace=True)
|
|
79
|
+
rep = pd.DataFrame({"sample": draw}).groupby("sample").size().rename("mult").reset_index()
|
|
80
|
+
g2 = gs.merge(rep, on="sample")
|
|
81
|
+
for c in ["n", "sx", "sy", "sxy"]:
|
|
82
|
+
g2[c] = g2[c] * g2.mult
|
|
83
|
+
shares.append(partition_from_gs(g2)["share_between_individual"])
|
|
84
|
+
lo, hi = np.nanpercentile(shares, [2.5, 97.5])
|
|
85
|
+
return float(lo), float(hi)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""gating_metrics.platform_contrast — same-compartment cross-platform coupling
|
|
2
|
+
contrast (the H3 natural-experiment test) and a measurement-condition
|
|
3
|
+
compatibility report builder (frozen F1/F2 dictionary).
|
|
4
|
+
"""
|
|
5
|
+
import numpy as np
|
|
6
|
+
from scipy import stats
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def fisher_z_contrast(r1, n1, r2, n2):
|
|
11
|
+
"""Fisher-Z test of two independent correlations (e.g. bulk-array vs
|
|
12
|
+
scRNA-pseudobulk coupling measured in the same biological compartment)."""
|
|
13
|
+
z = (np.arctanh(r1) - np.arctanh(r2)) / np.sqrt(1 / (n1 - 3) + 1 / (n2 - 3))
|
|
14
|
+
p = 2 * (1 - stats.norm.cdf(abs(z)))
|
|
15
|
+
return dict(fisher_z=float(z), p=float(p), sign_reversal=bool(r1 * r2 < 0))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def compatibility_report(rows):
|
|
19
|
+
"""Build the measurement-conditions range table (F2).
|
|
20
|
+
|
|
21
|
+
rows: list of dicts with keys dataset, platform, compartment, state_class,
|
|
22
|
+
stratum, r, p, n. Classification rule (frozen): DOMINANT-NEGATIVE
|
|
23
|
+
r<=-0.2 & p<0.05; DOMINANT-POSITIVE r>=+0.2 & p<0.05; else
|
|
24
|
+
INDETERMINATE.
|
|
25
|
+
"""
|
|
26
|
+
df = pd.DataFrame(rows)
|
|
27
|
+
if len(df) == 0:
|
|
28
|
+
return df
|
|
29
|
+
|
|
30
|
+
def classify(r, p):
|
|
31
|
+
if not np.isfinite(r) or not np.isfinite(p):
|
|
32
|
+
return "NON-EVALUABLE"
|
|
33
|
+
if r <= -0.2 and p < 0.05:
|
|
34
|
+
return "DOMINANT-NEGATIVE"
|
|
35
|
+
if r >= 0.2 and p < 0.05:
|
|
36
|
+
return "DOMINANT-POSITIVE"
|
|
37
|
+
return "INDETERMINATE"
|
|
38
|
+
df["classification"] = [classify(r, p) for r, p in zip(df.r, df.p)]
|
|
39
|
+
return df
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def atlas_verdict(report, condition_filter):
|
|
43
|
+
"""Frozen F2 atlas-level verdict: the individual-level axis principle is
|
|
44
|
+
supported at atlas level iff DOMINANT-NEGATIVE holds in >=80% of evaluable
|
|
45
|
+
cohorts of the given condition class (e.g. acute-inflammation blood)."""
|
|
46
|
+
sub = report[report.state_class.isin(condition_filter)]
|
|
47
|
+
sub = sub[sub.classification != "NON-EVALUABLE"]
|
|
48
|
+
if len(sub) == 0:
|
|
49
|
+
return dict(k=0, n=0, fraction=np.nan, supported=False)
|
|
50
|
+
k = int((sub.classification == "DOMINANT-NEGATIVE").sum())
|
|
51
|
+
frac = k / len(sub)
|
|
52
|
+
return dict(k=k, n=int(len(sub)), fraction=frac, supported=bool(frac >= 0.8))
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""gating_metrics.power — closed-form power/sample-size for detecting a
|
|
2
|
+
reference-stratum Spearman coupling (the gating-metric planning tool).
|
|
3
|
+
"""
|
|
4
|
+
import numpy as np
|
|
5
|
+
from scipy import stats
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def n_for_coupling(r_target, alpha=0.05, power=0.8, two_sided=True):
|
|
9
|
+
"""Minimum n to detect a true Spearman r at Fisher-Z (normal approx)."""
|
|
10
|
+
z_a = stats.norm.ppf(1 - alpha / 2 if two_sided else 1 - alpha)
|
|
11
|
+
z_b = stats.norm.ppf(power)
|
|
12
|
+
zr = np.arctanh(r_target)
|
|
13
|
+
return int(np.ceil((z_a + z_b) ** 2 / zr ** 2 + 3))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def power_at_n(r_true, n, alpha=0.05, two_sided=True):
|
|
17
|
+
"""Power of the Fisher-Z test for a true correlation r at sample size n."""
|
|
18
|
+
z_a = stats.norm.ppf(1 - alpha / 2 if two_sided else 1 - alpha)
|
|
19
|
+
se = 1 / np.sqrt(n - 3)
|
|
20
|
+
zr = np.arctanh(r_true)
|
|
21
|
+
return float(stats.norm.cdf(abs(zr) / se - z_a))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def mde_at_n(n, alpha=0.05, power=0.8, two_sided=True):
|
|
25
|
+
"""Minimum detectable |r| at given n."""
|
|
26
|
+
z_a = stats.norm.ppf(1 - alpha / 2 if two_sided else 1 - alpha)
|
|
27
|
+
z_b = stats.norm.ppf(power)
|
|
28
|
+
r = np.tanh((z_a + z_b) / np.sqrt(n - 3))
|
|
29
|
+
return float(r)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""gating_metrics self-tests (frozen F3 deliverable): run with
|
|
3
|
+
python test_gating_metrics.py
|
|
4
|
+
Asserts (i) the method-of-moments partition equals the direct algebraic
|
|
5
|
+
identity on a synthetic two-level dataset (seed 42), (ii) the closed-form
|
|
6
|
+
power calculator reproduces the standard Fisher-Z n formula, (iii) the
|
|
7
|
+
platform Fisher-Z contrast recovers a known sign reversal, and (iv) the
|
|
8
|
+
compatibility-report classifier matches the frozen thresholds.
|
|
9
|
+
"""
|
|
10
|
+
import os, sys
|
|
11
|
+
import numpy as np
|
|
12
|
+
import pandas as pd
|
|
13
|
+
|
|
14
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
15
|
+
from partition import partition, bootstrap_share_between
|
|
16
|
+
from power import n_for_coupling, power_at_n, mde_at_n
|
|
17
|
+
from platform_contrast import fisher_z_contrast, compatibility_report, atlas_verdict
|
|
18
|
+
from coupling import spearman_np, partial_spearman
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def make_two_level(seed=42, n_samples=60, cells_per=40, r_between=-0.6,
|
|
22
|
+
r_within=0.0):
|
|
23
|
+
rng = np.random.default_rng(seed)
|
|
24
|
+
mus = rng.multivariate_normal([0, 0], [[1, r_between], [r_between, 1]],
|
|
25
|
+
size=n_samples)
|
|
26
|
+
rows = []
|
|
27
|
+
for s, (mx, my) in enumerate(mus):
|
|
28
|
+
xy = rng.multivariate_normal([mx, my], [[1, r_within], [r_within, 1]],
|
|
29
|
+
size=cells_per)
|
|
30
|
+
for x, y in xy:
|
|
31
|
+
rows.append({"sample": f"S{s}", "x": x, "y": y})
|
|
32
|
+
return pd.DataFrame(rows)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main():
|
|
36
|
+
# (i) partition identity + between-individual share recovery
|
|
37
|
+
df = make_two_level()
|
|
38
|
+
p = partition(df[["sample", "x", "y"]], by=("sample",))
|
|
39
|
+
assert abs(p["SSP_tot"] - p["SSP_between_individual"] - p["SSP_within"]) \
|
|
40
|
+
< 1e-6 * max(1.0, abs(p["SSP_tot"]))
|
|
41
|
+
lo, hi = bootstrap_share_between(df[["sample", "x", "y"]], by=("sample",),
|
|
42
|
+
n_boot=200)
|
|
43
|
+
assert lo < p["share_between_individual"] < hi, (lo, p, hi)
|
|
44
|
+
print(f"partition OK: share_between={p['share_between_individual']:.3f} "
|
|
45
|
+
f"bootstrap95=[{lo:.3f},{hi:.3f}]")
|
|
46
|
+
|
|
47
|
+
# (ii) power calculators
|
|
48
|
+
n_req = n_for_coupling(0.3)
|
|
49
|
+
assert power_at_n(0.3, n_req) >= 0.8 - 1e-6
|
|
50
|
+
assert abs(mde_at_n(n_req) - 0.3) < 0.02
|
|
51
|
+
print(f"power OK: n(r=0.3, 80%)={n_req}, MDE at that n={mde_at_n(n_req):.3f}")
|
|
52
|
+
|
|
53
|
+
# (iii) Fisher-Z platform contrast with known sign reversal
|
|
54
|
+
c = fisher_z_contrast(-0.5, 40, +0.4, 200)
|
|
55
|
+
assert c["sign_reversal"] and c["p"] < 0.05
|
|
56
|
+
print(f"fisher-z OK: p={c['p']:.2e}, sign_reversal={c['sign_reversal']}")
|
|
57
|
+
|
|
58
|
+
# (iv) classification thresholds + atlas verdict
|
|
59
|
+
rows = [
|
|
60
|
+
dict(dataset="A", platform="array", compartment="whole_blood",
|
|
61
|
+
state_class="acute_blood", stratum="ref", r=-0.4, p=0.01, n=50),
|
|
62
|
+
dict(dataset="B", platform="array", compartment="whole_blood",
|
|
63
|
+
state_class="acute_blood", stratum="ref", r=-0.3, p=0.02, n=80),
|
|
64
|
+
dict(dataset="C", platform="scRNA", compartment="PBMC",
|
|
65
|
+
state_class="acute_blood", stratum="ref", r=+0.3, p=0.03, n=100),
|
|
66
|
+
dict(dataset="D", platform="array", compartment="whole_blood",
|
|
67
|
+
state_class="acute_blood", stratum="ref", r=-0.1, p=0.4, n=30),
|
|
68
|
+
]
|
|
69
|
+
rep = compatibility_report(rows)
|
|
70
|
+
v = atlas_verdict(rep, ["acute_blood"])
|
|
71
|
+
assert v["k"] == 2 and v["n"] == 4 and not v["supported"]
|
|
72
|
+
print("classification OK:", dict(zip(rep.dataset, rep.classification)))
|
|
73
|
+
print("ALL gating_metrics SELF-TESTS PASSED")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
main()
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gating-metrics
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Measurement toolkit for state-gated co-expression coupling: layer couplings, exact covariance partition, platform contrast, closed-form power planning
|
|
5
|
+
Author: Liu Yuanshui
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: OSF registration, https://doi.org/10.17605/OSF.IO/4TZH9
|
|
8
|
+
Project-URL: OSF project, https://osf.io/v8w5a
|
|
9
|
+
Project-URL: Homepage, https://github.com/Shuiruolys-Liu/gating-metrics
|
|
10
|
+
Keywords: co-expression,single-cell,gating,ARDS,inflammasome,measurement
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: numpy>=1.24
|
|
19
|
+
Requires-Dist: pandas>=2.0
|
|
20
|
+
Requires-Dist: scipy>=1.10
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# gating_metrics — measurement toolkit for state-gated co-expression coupling
|
|
24
|
+
|
|
25
|
+
Open-source Python toolkit accompanying the ARDS gating-circuit study
|
|
26
|
+
(manifest modules F1/F2/F3, registered 2026-08-18 before execution). It
|
|
27
|
+
formalises the study's core measurement claim — that a state-gated
|
|
28
|
+
hub–module coupling (e.g. KAT2A vs the inflammasome module) is an
|
|
29
|
+
**individual-level, platform-dependent emergent measurement** — into
|
|
30
|
+
reusable, tested routines.
|
|
31
|
+
|
|
32
|
+
## What it provides
|
|
33
|
+
|
|
34
|
+
| Module | Contents |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `coupling.py` | Layer-wise Spearman coupling: `L_cell` (within-sample per-cell, with detected-gene-count partial sensitivity), `L_indiv` (between-individual pseudobulk), `L_bulk` (bulk-assay). |
|
|
37
|
+
| `partition.py` | Exact method-of-moments (nested ANOVA cross-product) covariance partition: between-individual / between-compartment-within-individual / within components, with sample-level bootstrap CIs. |
|
|
38
|
+
| `platform_contrast.py` (renamed from `platform.py` to avoid the stdlib collision) | Fisher-Z same-compartment cross-platform contrast (the "is it a platform artefact?" test) and the measurement-conditions compatibility report / atlas verdict (frozen classification thresholds: DOMINANT-NEGATIVE r≤−0.2 & p<0.05; DOMINANT-POSITIVE r≥+0.2 & p<0.05; else INDETERMINATE; atlas support = ≥80% DOMINANT-NEGATIVE within a condition class). |
|
|
39
|
+
| `power.py` | Closed-form Fisher-Z planning tools: n for a target coupling, power at n, minimum detectable |r|. |
|
|
40
|
+
|
|
41
|
+
## Quick start
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import pandas as pd
|
|
45
|
+
from coupling import layer_cell, layer_indiv
|
|
46
|
+
from partition import partition, bootstrap_share_between
|
|
47
|
+
|
|
48
|
+
# cells: one row per cell with columns sample, compartment, x (hub gene),
|
|
49
|
+
# y (module score), ng (detected-gene count)
|
|
50
|
+
per_group, summary = layer_cell(cells, ["sample", "compartment"])
|
|
51
|
+
r_indiv, p_indiv = layer_indiv(pseudobulk) # one row per sample
|
|
52
|
+
part = partition(cells, by=("sample", "compartment"))
|
|
53
|
+
lo, hi = bootstrap_share_between(cells, by=("sample", "compartment"))
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Self-tests (synthetic two-level data, seed 42; partition identity, power
|
|
57
|
+
closed form, Fisher-Z sign reversal, classification thresholds):
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
python test_gating_metrics.py
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Registered falsifiable prediction (public test for the field)
|
|
64
|
+
|
|
65
|
+
Any new **acute-inflammation whole-blood cohort assayed in bulk** (array or
|
|
66
|
+
bulk RNA-seq) should reproduce a **negative reference-stratum hub–module
|
|
67
|
+
coupling**; **scRNA pseudobulk and protein layers are expected non-negative**
|
|
68
|
+
(same compartment). The F1/F2 tables in the companion manuscript are the
|
|
69
|
+
current evidence base; the prediction is falsifiable by any cohort that
|
|
70
|
+
violates the sign-by-condition pattern.
|
|
71
|
+
|
|
72
|
+
**Public registration**: OSF Preregistration, 2026-08-18 —
|
|
73
|
+
DOI [10.17605/OSF.IO/4TZH9](https://doi.org/10.17605/OSF.IO/4TZH9)
|
|
74
|
+
(associated project https://osf.io/v8w5a).
|
|
75
|
+
|
|
76
|
+
**External validation status (recorded 2026-08-19, honest)**: the authors'
|
|
77
|
+
own value-blind external hold-out (module G1; four never-used
|
|
78
|
+
acute-inflammation blood cohorts GSE66099/GSE40012/GSE57065/GSE37069)
|
|
79
|
+
did **NOT** confirm the reference-stratum prediction (2/4 direction-negative;
|
|
80
|
+
pooled fixed-effect r=+0.004). The registered text above is kept unchanged as
|
|
81
|
+
the auditable record; by the external data, the reference-stratum claim is
|
|
82
|
+
refuted and the inflamed-stratum negative coupling is supported (manuscript
|
|
83
|
+
TableS36). Independent replication by other groups remains the intended
|
|
84
|
+
falsification channel.
|
|
85
|
+
|
|
86
|
+
## Installation
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pip install gating-metrics # once published on PyPI (pending author action)
|
|
90
|
+
# or from source:
|
|
91
|
+
git clone https://github.com/Shuiruolys-Liu/gating-metrics
|
|
92
|
+
cd gating-metrics && pip install .
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Provenance
|
|
96
|
+
|
|
97
|
+
Metric definitions are copied verbatim from the frozen F1 decomposition
|
|
98
|
+
dictionary (`manifest.json -> preregistrations.modules.
|
|
99
|
+
F1_variance_decomposition_gating`, registered 2026-08-18). No metric in this
|
|
100
|
+
package was tuned after inspecting the outcome data.
|
|
101
|
+
|
|
102
|
+
## Citing
|
|
103
|
+
|
|
104
|
+
If you use this toolkit, please cite the companion manuscript (under
|
|
105
|
+
preparation) and the OSF registration (DOI 10.17605/OSF.IO/4TZH9).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
gating_metrics/__init__.py
|
|
5
|
+
gating_metrics/coupling.py
|
|
6
|
+
gating_metrics/partition.py
|
|
7
|
+
gating_metrics/platform_contrast.py
|
|
8
|
+
gating_metrics/power.py
|
|
9
|
+
gating_metrics/test_gating_metrics.py
|
|
10
|
+
gating_metrics.egg-info/PKG-INFO
|
|
11
|
+
gating_metrics.egg-info/SOURCES.txt
|
|
12
|
+
gating_metrics.egg-info/dependency_links.txt
|
|
13
|
+
gating_metrics.egg-info/requires.txt
|
|
14
|
+
gating_metrics.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gating_metrics
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gating-metrics"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Measurement toolkit for state-gated co-expression coupling: layer couplings, exact covariance partition, platform contrast, closed-form power planning"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Liu Yuanshui" }]
|
|
13
|
+
keywords = ["co-expression", "single-cell", "gating", "ARDS", "inflammasome", "measurement"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Intended Audience :: Science/Research",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Topic :: Scientific/Engineering :: Bio-Informatics",
|
|
19
|
+
]
|
|
20
|
+
dependencies = ["numpy>=1.24", "pandas>=2.0", "scipy>=1.10"]
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
"OSF registration" = "https://doi.org/10.17605/OSF.IO/4TZH9"
|
|
24
|
+
"OSF project" = "https://osf.io/v8w5a"
|
|
25
|
+
Homepage = "https://github.com/Shuiruolys-Liu/gating-metrics"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools]
|
|
28
|
+
packages = ["gating_metrics"]
|