Skip to content

Commit d1719bb

Browse files
release: v1.5.0 — MR/conformal/interference family consolidation
- Unified family dispatchers: sp.mr(method=...), sp.conformal(kind=...), sp.interference(design=...) mirroring sp.synth / sp.decompose / sp.dml - ⚠️ Breaking: sp.mr is now a dispatcher function, not a module alias (use sp.mendelian for module-level access; sp.mr_* top-level helpers unchanged) - ⚠️ Correctness: mr_egger slope inference now uses t(n−2) instead of Normal (anti-conservative at small n_snps); mr_presso MC p-values now use (k+1)/(B+1) convention (no more exact-zero p-values) - Fixed: dead var= line in network_exposure._ht_estimate (SE unchanged) - Registry coverage: network_exposure, peer_effects, weighted_conformal_prediction, conformal_counterfactual, conformal_ite_interval now surface in sp.list_functions() - Docs: three new family guides (interference / conformal / MR) - Tests: +44 tests (30 dispatcher parity + 14 correctness guards)
1 parent 60815e3 commit d1719bb

22 files changed

Lines changed: 2691 additions & 34 deletions

CHANGELOG.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,146 @@
22

33
All notable changes to StatsPAI will be documented in this file.
44

5+
## [1.5.0] — 2026-04-21 — Interference / Conformal / Mendelian family consolidation
6+
7+
Minor release. Three concurrent improvements to the interference,
8+
conformal causal inference, and Mendelian Randomization families:
9+
full-family documentation guides, unified dispatchers matching the
10+
`sp.synth` / `sp.decompose` / `sp.dml` pattern, and a targeted
11+
correctness audit that surfaced and fixed two silent-wrong-numbers
12+
issues.
13+
14+
### Added — three new family guides (interference / conformal / MR)
15+
16+
- `docs/guides/interference_family.md` — complete walkthrough of
17+
`sp.spillover`, `sp.network_exposure`, `sp.peer_effects`,
18+
`sp.network_hte`, `sp.inward_outward_spillover`,
19+
`sp.cluster_matched_pair`, `sp.cluster_cross_interference`,
20+
`sp.cluster_staggered_rollout`, `sp.dnc_gnn_did`. Decision tree
21+
covering partial / network / cluster-RCT designs with the 5
22+
diagnostics every interference analysis should report (exposure
23+
balance, identification check for peer_effects, overlap for
24+
network_hte, parallel trends for staggered-cluster, sensitivity to
25+
exposure function).
26+
- `docs/guides/conformal_family.md` — complete walkthrough of
27+
`sp.conformal_cate`, `sp.weighted_conformal_prediction`,
28+
`sp.conformal_counterfactual`, `sp.conformal_ite_interval`,
29+
`sp.conformal_density_ite`, `sp.conformal_ite_multidp`,
30+
`sp.conformal_debiased_ml`, `sp.conformal_fair_ite`,
31+
`sp.conformal_continuous`, `sp.conformal_interference`. Clarifies
32+
the distinction between marginal and conditional coverage, with
33+
per-tool "when to use it" + how-to-read-disagreement guidance.
34+
- `docs/guides/mendelian_family.md` — complete walkthrough of all 17
35+
MR functions (4 point estimators + 6 diagnostics + 3 multi-exposure
36+
extensions + instrument-strength F + 2 plots), organised around the
37+
IV1 / IV2 / IV3 assumption hierarchy. Ships the 4 sanity checks every
38+
MR analysis should report and a worked BMI → T2D example.
39+
40+
Each guide is linked from `mkdocs.yml` under Guides and surfaces via
41+
`sp.search_functions()`.
42+
43+
### Added — unified family dispatchers
44+
45+
Three new top-level dispatchers mirroring the style of `sp.synth` /
46+
`sp.decompose` / `sp.dml`:
47+
48+
- **`sp.mr(method=..., ...)`** — single entry point for the 17-function
49+
Mendelian Randomization family. Supports
50+
`method ∈ {"ivw", "egger", "median", "penalized_median", "mode",
51+
"simple_mode", "all", "mvmr", "mediation", "bma", "presso", "radial",
52+
"leave_one_out", "steiger", "heterogeneity", "pleiotropy_egger",
53+
"f_statistic", ...}` with aliases. kwargs pass through to the target
54+
function. `sp.mr_available_methods()` lists all aliases.
55+
56+
- **`sp.conformal(kind=..., ...)`** — single entry point for the
57+
10-function conformal causal inference family. Supports
58+
`kind ∈ {"cate", "counterfactual", "ite", "weighted", "density",
59+
"multidp", "debiased", "fair", "continuous", "interference", ...}`.
60+
`sp.conformal_available_kinds()` lists all aliases.
61+
62+
- **`sp.interference(design=..., ...)`** — single entry point for the
63+
9-function interference / spillover family. Supports
64+
`design ∈ {"partial", "network_exposure", "peer_effects",
65+
"network_hte", "inward_outward", "cluster_matched_pair",
66+
"cluster_cross", "cluster_staggered", "dnc_gnn", ...}`.
67+
`sp.interference_available_designs()` lists all aliases.
68+
69+
All three dispatchers are registered with hand-written schemas so
70+
`sp.describe_function("mr")` / `"conformal"` / `"interference"` return
71+
agent-readable descriptions. 30 new tests in
72+
`tests/test_dispatchers_v150.py` guarantee the dispatcher path and the
73+
direct-call path produce byte-for-byte identical results.
74+
75+
### ⚠️ Breaking — `sp.mr` is now a function, not a module alias
76+
77+
Prior to v1.5.0 `sp.mr` was a reference to the `statspai.mendelian`
78+
submodule (`from . import mendelian as mr`), so `sp.mr.mr_ivw(...)`
79+
worked. v1.5.0 replaces this with the new **dispatcher function**
80+
`sp.mr(method=..., ...)`.
81+
82+
**Migration**: code that previously wrote `sp.mr.mr_ivw(bx, by, sx, sy)`
83+
should use the top-level `sp.mr_ivw(bx, by, sx, sy)` (already exported
84+
in every prior version) or the new `sp.mr("ivw", beta_exposure=bx, ...)`
85+
dispatcher. The module is still accessible as `sp.mendelian` for users
86+
who were doing submodule-level introspection.
87+
88+
Updated references: the only in-repo consumer of the old
89+
`sp.mr.mr_ivw` form was `tests/reference_parity/test_mr_parity.py`,
90+
which has been migrated to top-level calls. All external user code
91+
that already uses `sp.mr_ivw` / `sp.mendelian_randomization` / etc
92+
continues to work unchanged.
93+
94+
### Fixed — silent wrong numbers (correctness audit)
95+
96+
- **`sp.mr_egger` — slope inference used Normal, not t(n−2).** The
97+
companion `sp.mr_pleiotropy_egger` correctly used `t(n−2)` for the
98+
Egger intercept p-value, but `mr_egger` itself used `stats.norm.cdf`
99+
for both the slope p-value and the slope CI's critical value. This
100+
was anti-conservative at small `n_snps`: e.g. for `n_snps = 5` and a
101+
t-stat of 1.5, the Normal-based two-sided p is 0.134 whereas the
102+
correct t(3)-based p is 0.231. `mendelian_randomization(..., methods=["egger"])`
103+
inherited the bug through its internal call. The fix switches both the
104+
p-value and the CI critical value to `t(n−2)`. Regression guard in
105+
`tests/test_correctness_v150.py::TestMREggerUsesTDistribution`.
106+
For `n_snps ≥ 100` the change is numerically invisible (< 1e-3 in p).
107+
108+
- **`sp.mr_presso` — MC p-value could equal exactly 0.** Both the
109+
global test p-value and the per-SNP outlier p-values used the raw
110+
`mean(null >= obs)` form, which collapses to `0.0` when the observed
111+
statistic exceeds every simulated null. An MC-estimated p-value
112+
cannot be zero — its true lower bound is `1 / (B + 1)`. The fix
113+
switches to the standard `(k + 1) / (B + 1)` convention (matching
114+
R's `MR-PRESSO` package). Downstream effect: reported p-values are
115+
now always strictly positive and in `[1/(B+1), 1]`, which prevents
116+
log-transforms and sensitivity analyses from silently producing
117+
`-inf`. Regression guard in
118+
`tests/test_correctness_v150.py::TestMRPressoMCPvalueConvention`.
119+
120+
### Fixed — dead code
121+
122+
- **`sp.network_exposure._ht_estimate`** contained a dimensionally
123+
inconsistent `var = ...` expression that was immediately overwritten
124+
by the conservative Aronow-Samii Theorem 1 bound `var_as = ...`. The
125+
dead line is removed; the reported SE is unchanged.
126+
127+
### Fixed — registry coverage
128+
129+
Five previously-exposed-but-unregistered family functions now surface
130+
in `sp.list_functions()` and have agent-readable schemas via
131+
`sp.describe_function()`:
132+
133+
- `sp.network_exposure` (Aronow-Samii HT)
134+
- `sp.peer_effects` (Bramoullé-Djebbari-Fortin 2SLS)
135+
- `sp.weighted_conformal_prediction` (TBCR 2019 primitive)
136+
- `sp.conformal_counterfactual` (Lei-Candès Theorem 1)
137+
- `sp.conformal_ite_interval` (Lei-Candès Eq. 3.4 nested bound)
138+
139+
### No other API changes
140+
141+
Every other public signature is byte-for-byte identical to v1.4.2.
142+
Existing user code keeps working; upgrades reveal slightly wider Egger
143+
CIs at small `n_snps` and strictly positive `mr_presso` p-values.
144+
5145
## [1.4.2] — 2026-04-21 — correctness patches + family guides
6146

7147
Patch release. No breaking changes; two silent-wrong-numbers bug

MIGRATION.md

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,58 @@
1-
# Migrating from PyStataR to StatsPAI
1+
# Migrating between StatsPAI versions + from PyStataR
2+
3+
Internal version-to-version migrations are at the top; the long-form
4+
`PyStataR → StatsPAI` migration follows below.
5+
6+
---
7+
8+
## v1.4.x → v1.5.0
9+
10+
Minor release. Only one change requires any migration:
11+
12+
### `sp.mr` is now a dispatcher function, not a module alias
13+
14+
Before v1.5.0, `sp.mr` was a reference to the `statspai.mendelian`
15+
submodule, and `sp.mr.mr_ivw(...)` worked as attribute access on the
16+
module.
17+
18+
In v1.5.0, `sp.mr` is the new **unified dispatcher** for the MR family,
19+
matching the pattern of `sp.synth` / `sp.decompose` / `sp.dml`:
20+
21+
```python
22+
sp.mr("ivw", beta_exposure=bx, beta_outcome=by,
23+
se_exposure=sx, se_outcome=sy)
24+
sp.mr("egger", beta_exposure=bx, beta_outcome=by,
25+
se_exposure=sx, se_outcome=sy)
26+
sp.mr("mvmr", snp_associations=snp_df,
27+
outcome="beta_y", outcome_se="se_y",
28+
exposures=["beta_bmi", "beta_ldl"])
29+
```
30+
31+
| Old (<= v1.4.2) | New (>= v1.5.0) |
32+
| --- | --- |
33+
| `sp.mr.mr_ivw(...)` | `sp.mr_ivw(...)` (already available since v0.9) or `sp.mr("ivw", ...)` |
34+
| `sp.mr.mr_egger(...)` | `sp.mr_egger(...)` or `sp.mr("egger", ...)` |
35+
| `sp.mr.mr_presso(...)` | `sp.mr_presso(...)` or `sp.mr("presso", ...)` |
36+
| `sp.mr` (as module alias) | `sp.mendelian` (module access preserved under this name) |
37+
38+
**Rule of thumb:** if your code uses `sp.mr_*` (underscore form) it
39+
already works unchanged in v1.5.0. Only the uncommon
40+
`sp.mr.<attribute>` pattern needs rewriting.
41+
42+
### Output numerical differences you may notice after upgrading
43+
44+
- `sp.mr_egger` / `sp.mendelian_randomization(..., methods=["egger"])`
45+
slope p-values and CIs now use `t(n − 2)` rather than `Normal`, matching
46+
`sp.mr_pleiotropy_egger` and R's `MendelianRandomization` package.
47+
Effect is invisible for `n_snps ≥ ~100`. For very small `n_snps` (say
48+
5 or 6) CIs widen by ~1.6×.
49+
- `sp.mr_presso` p-values now use the `(k + 1) / (B + 1)` MC convention,
50+
so they are strictly positive (floor `1 / (B + 1)`). No change for
51+
non-extreme cases; fixes `-inf` propagation through `log(p)` downstream.
52+
53+
---
54+
55+
## From PyStataR to StatsPAI
256

357
`PyStataR` is deprecated. All of its functionality is now available in
458
[StatsPAI](https://github.com/brycewang-stanford/StatsPAI), under a

README.md

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,28 @@ StatsPAI is the **agent-native** Python package for causal inference and applied
1515

1616
It brings R's [Causal Inference Task View](https://cran.r-project.org/web/views/CausalInference.html) (fixest, did, rdrobust, gsynth, DoubleML, MatchIt, CausalImpact, ...) and Stata's core econometrics commands into a single, consistent Python API.
1717

18-
**🎉 NEW in v1.4.2correctness patches + Proximal / QTE / Causal-RL family guides**
18+
**🎉 NEW in v1.5.0Interference / Conformal / Mendelian family consolidation**
1919

20-
StatsPAI 1.4.2 is a patch release. No breaking changes, no new public signatures — just two silent-wrong-numbers fixes and three family guides closing the last gaps between the v3 reference and the published documentation.
20+
StatsPAI 1.5.0 is a minor release bundling three concurrent improvements to the interference, conformal causal inference, and Mendelian Randomization families: full-family documentation guides, unified dispatchers matching the `sp.synth` / `sp.decompose` / `sp.dml` pattern, and a targeted correctness audit that fixed two silent-wrong-numbers issues.
21+
22+
- **Three new family guides**`docs/guides/interference_family.md` (complete walkthrough of `sp.spillover`, `sp.network_exposure`, `sp.peer_effects`, `sp.network_hte`, `sp.inward_outward_spillover`, `sp.cluster_matched_pair`, `sp.cluster_cross_interference`, `sp.cluster_staggered_rollout`, `sp.dnc_gnn_did` with decision tree + 5 diagnostics every interference analysis should report), `docs/guides/conformal_family.md` (all 10 conformal estimators organised around what the marginal-coverage guarantee actually buys you), `docs/guides/mendelian_family.md` (all 17 MR functions structured around the IV1 / IV2 / IV3 assumption hierarchy + 4 sanity checks every MR analysis should report + a worked BMI → T2D example).
23+
- **Three new unified dispatchers**`sp.mr(method=...)` (33 aliases covering IVW / Egger / median / mode / MVMR / mediation / BMA / PRESSO / radial / Steiger / F-stat / LOO / pleiotropy / heterogeneity), `sp.conformal(kind=...)` (29 aliases across CATE / counterfactual / ITE / density / multi-DP / debiased-ML / fair / continuous / interference), `sp.interference(design=...)` (29 aliases across partial / network-exposure / peer-effects / network-HTE / inward-outward / 4× cluster-RCT variants). Kwargs pass through unchanged; each dispatcher's output is byte-for-byte identical to the direct call (30 new tests guarantee this).
24+
- **⚠️ correctness fix — `sp.mr_egger` slope inference used Normal, not t(n−2).** The companion `sp.mr_pleiotropy_egger` correctly used `t(n−2)` for the Egger intercept p-value, but `mr_egger` itself used `stats.norm` for both the slope p-value and CI critical value. Anti-conservative at small `n_snps` — e.g. `n_snps = 5` and `t ≈ 1.5` gave `p = 0.134` instead of the correct `p = 0.231`. The fix switches both to `t(n − 2)`. For `n_snps ≥ ~100` the change is numerically invisible. Regression guard: `tests/test_correctness_v150.py::TestMREggerUsesTDistribution`.
25+
- **⚠️ correctness fix — `sp.mr_presso` MC p-value could equal 0.** Both the global test and the per-SNP outlier p-values used the raw `mean(null ≥ obs)` form, which collapses to `0.0` when the observed statistic exceeds every simulated null. An MC-estimated p-value cannot be zero — its true lower bound is `1 / (B + 1)`. Fix switches to the standard `(k + 1) / (B + 1)` convention (matching R's MR-PRESSO). Downstream effect: `log(p)` and sensitivity transforms no longer silently produce `-inf`. Regression guard: `tests/test_correctness_v150.py::TestMRPressoMCPvalueConvention`.
26+
- **⚠️ breaking — `sp.mr` is now a function, not a module alias.** Prior to v1.5.0 `sp.mr` was a module alias so `sp.mr.mr_ivw(...)` worked. v1.5.0 repurposes `sp.mr` as the new dispatcher. Migration: use the top-level `sp.mr_ivw(...)` (already exported in every prior version) or the new `sp.mr("ivw", ...)` dispatcher. Module access preserved under `sp.mendelian`. See [MIGRATION.md](MIGRATION.md#v14x--v150).
27+
- **Registry coverage fixes** — 5 previously-exposed-but-unregistered family functions now surface in `sp.list_functions()` and have agent-readable schemas via `sp.describe_function()`: `network_exposure`, `peer_effects`, `weighted_conformal_prediction`, `conformal_counterfactual`, `conformal_ite_interval`.
28+
29+
Every public signature other than the `sp.mr` module-to-function change is byte-for-byte identical to v1.4.2.
30+
31+
**Previously in v1.4.2 — correctness patches + Proximal / QTE / Causal-RL family guides**
32+
33+
StatsPAI 1.4.2 was a patch release with two silent-wrong-numbers fixes and three family guides:
2134

2235
- **⚠️ correctness fix — `sp.dml_model_averaging` √n SE scaling bug.** The cross-candidate variance aggregator treated the sample-mean influence-function outer product as `Var(θ̂_avg)` directly, missing a final `/ n`. Reported SEs were `√n` times too large; on the canonical n=400 DGP the 95% CI width was 4.20 (nominal ≈ 0.21) and empirical coverage was 100%. After the fix, CI width is 0.21 and coverage is ≈ nominal. Regression guard: `tests/test_dml_model_averaging.py::test_se_on_correct_scale`.
2336
- **⚠️ correctness fix — `sp.gardner_did` event-study reference-category contamination.** Stage-2 dummy regression pooled never-treated units *and* treated units outside the event-study horizon into a single baseline, dragging every event-time coefficient toward the mean of that pool. On a synthetic panel with true τ=2 and strict parallel trends, pre-trends came out ≈ -0.30 (should be 0) and post ≈ +1.72 (should be 2.0). Replaced the Stage-2 regression in event-study mode with direct Borusyak-Jaravel-Spiess-style within-(cohort × relative-time) averaging of the imputed gap. After the fix: pre-trends ≈ +0.01, post ≈ +2.02. Non-event-study single-ATT path was already correct and is unchanged.
24-
- **New family guides**`docs/guides/proximal_family.md` (full Proximal Causal Inference walkthrough covering `sp.proximal`, `sp.fortified_pci`, `sp.bidirectional_pci`, `sp.pci_mtp`, `sp.double_negative_control`, `sp.proximal_surrogate_index`, `sp.select_pci_proxies` with a decision tree + 4 diagnostics every PCI analysis should report), `docs/guides/qte_family.md` (mean → quantile → distribution with cross-section / DiD / IV / panel decision paths across `sp.qte`, `sp.qdid`, `sp.cic`, `sp.distributional_te`, `sp.dist_iv`, `sp.kan_dlate`, `sp.beyond_average_late`, `sp.qte_hd_panel`), `docs/guides/causal_rl_family.md` (when to use causal RL vs classical CI, covering `sp.causal_bandit`, `sp.causal_dqn`, `sp.offline_safe_policy`, `sp.counterfactual_policy_optimization`, `sp.structural_mdp`, `sp.causal_rl_benchmark` + 4 causal-RL-specific sanity checks).
37+
- **Family guides**`docs/guides/proximal_family.md` (full Proximal Causal Inference walkthrough), `docs/guides/qte_family.md` (mean → quantile → distribution), `docs/guides/causal_rl_family.md` (causal RL vs classical CI).
2538
- **Formally shipped from v1.4.1 cherry-picks**`tests/test_bridge_full.py` (10 end-to-end tests for `sp.bridge(kind=...)` bridging theorems) and `docs/guides/bridging_theorems.md`.
2639

27-
Every public signature is byte-for-byte identical to v1.4.1. Upgrading reveals narrower CIs for `dml_model_averaging` and cleaner event-study coefficients for `gardner_did`.
28-
2940
**Previously in v1.4.1 — v3-frontier Sprint 3: AKM shock-clustered SE, Claude extended thinking, parity + integration suites, 2 new guides**
3041

3142
StatsPAI 1.4.1 is an additive follow-up to 1.4.0 that closes the Sprint 3 items:
@@ -992,7 +1003,7 @@ pytest
9921003
author={Wang, Biaoyue},
9931004
year={2026},
9941005
url={https://github.com/brycewang-stanford/statspai},
995-
version={1.4.2}
1006+
version={1.5.0}
9961007
}
9971008
```
9981009

0 commit comments

Comments
 (0)