Skip to content

Commit 012e4de

Browse files
feat: v0.9.16 — breadth expansion (new-methods pass across 15 sub-packages)
Ships 30+ new modules across 15 sub-packages plus 7 dedicated test files, bumping StatsPAI's coverage of the applied-causal-inference landscape closer to v1.0. This is a breadth release in the spirit of the earlier v1.0 upgrade pass (see memory project_v1_upgrade), not an architectural change. Spec: docs/superpowers/specs/2026-04-20-v0916-breadth-expansion.md New estimators and analyses by area Causal forests: multi_arm_forest, iv_forest, survival/causal_forest (Cui-Kosorok 2023) Causal discovery: FCI with latent confounders (Zhang 2008), ICP / nonlinear_icp (Peters-Bühlmann-Meinshausen) DAG tooling: do_calculus, identification, counterfactual, swig, llm_dag DTR: q_learning, a_learning, snmm (Robins 2004) TMLE: ltmle (van der Laan-Gruber 2012) Proximal: negative_controls (Lipsitch / Miao-Shi-Tchetgen), pci_regression (Cui-Tchetgen 2020) Interference: network_exposure (Aronow-Samii 2017), peer_effects Dose-response: vcnet + scigan (Nie-Brunskill-Wager 2021) Matching: genmatch (Diamond-Sekhon 2013) Sensitivity: rosenbaum_bounds (Rosenbaum 2002) Spatial: spatial_did, spatial_iv (Kelejian-Prucha 1998) Policy learning: ope direct / IPS / SNIPS / doubly-robust Time series: its (interrupted time series) Censoring / survival IPCW: ipcw (Robins-Finkelstein 2000) Target-trial emulation: protocol, emulate, ccw, diagnostics g-formula: ice (iterated conditional expectation) Transport: identify, generalize, weighting Bounds: balke_pearl (Balke-Pearl 1997) Mediation: four_way_decomposition (VanderWeele 2014) Neural causal: cevae (Louizos et al. 2017), gnn_causal Many-weak-IV: iv/many_weak OPE package: new top-level ope/ Bayes DID: bayes_did gains cohort= parameter and dedicated BayesianDIDResult type New tests (7 files) test_target_trial.py, test_dag_scm.py, test_icp.py, test_ope_cevae.py, test_transport.py, test_gformula_ice.py, test_bayes_mte_bivariate_normal.py Test hardening gap (declared up front): 20+ of the 30+ modules ship without a dedicated test file. The import chain is healthy (collection: 2373 / 2381 pass; 559 passed in the focused regression over affected sub-packages), but per-module correctness tests are deferred to follow-up commits per spec §4.4 (P0: ltmle, fci; P1: dtr trio, negative_controls, network_exposure). Version 0.9.15 → 0.9.16 in pyproject.toml and src/statspai/__init__.py. Regression evidence pytest --collect-only -q: 2373 collected, 8 deselected in 11s pytest on affected packages: 559 passed, 3 skipped, 0 failed in 339s Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6d59fe8 commit 012e4de

77 files changed

Lines changed: 9835 additions & 22 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# StatsPAI v0.9.16 — v1.0 breadth expansion (20 new modules)
2+
3+
**Author:** Bryce Wang · **Date:** 2026-04-20 · **Status:** design → implementation
4+
5+
## 1. Motivation
6+
7+
Between the v0.9.15 tidy-multiterm spec (landed as db050de) and this
8+
release, 20 new modules were authored to cover previously-unsupported
9+
methods — most of them requested by users working on applied causal
10+
projects where StatsPAI was already close but missing a specific
11+
estimator. This spec catalogs the batch, flags gaps, and defines the
12+
ship gate.
13+
14+
This is a **breadth pass**, analogous to the v1.0 upgrade recorded in
15+
memory `project_v1_upgrade.md`, but scoped tighter: each module is a
16+
standalone addition with no cross-cutting architectural change.
17+
18+
## 2. Inventory
19+
20+
20 new modules, ~4,800 LOC, organised by sub-package.
21+
22+
> **Note:** this inventory was captured at the start of review. The
23+
> actual release grew to **27+ modules** and **3 dedicated test files**
24+
> as the author added more coverage during the sign-off window (DAG
25+
> do-calculus / SWIG / counterfactual, Balke-Pearl bounds, ICP causal
26+
> discovery, four-way decomposition, peer effects, transport package,
27+
> plus `tests/test_dag_scm.py` and `tests/test_icp.py`). The authoritative
28+
> file list is the release commit diff.
29+
30+
### 2.1 Causal forests family (648 LOC)
31+
| Module | LOC | Estimand | Reference |
32+
|---|---|---|---|
33+
| `causal/iv_forest.py` | 200 | Forest-based LATE | Athey-Tibshirani-Wager 2019 |
34+
| `causal/multi_arm_forest.py` | 176 | Multi-arm CATE | Tibshirani-Wager 2020 |
35+
| `survival/causal_forest.py` | 272 | Survival CATE | Cui-Kosorok-Athey-Wager 2023 |
36+
37+
### 2.2 Causal discovery (726 LOC)
38+
| Module | LOC | Output | Reference |
39+
|---|---|---|---|
40+
| `causal_discovery/fci.py` | 430 | PAG with latent-confounder edges | Spirtes-Meek-Richardson 1995, Zhang 2008 |
41+
| `dag/identification.py` | 296 | Identifiability certificate on a user DAG | Pearl back-door/front-door + ID algorithm |
42+
43+
### 2.3 Dynamic treatment regimes (522 LOC)
44+
| Module | LOC | Estimand | Reference |
45+
|---|---|---|---|
46+
| `dtr/q_learning.py` | 185 | Q-function backward induction | Murphy 2003 |
47+
| `dtr/a_learning.py` | 173 | Advantage / blip functions | Robins 2004, Moodie et al. 2007 |
48+
| `dtr/snmm.py` | 164 | Structural nested mean model | Robins 1994 |
49+
50+
### 2.4 TMLE extensions (381 LOC)
51+
| Module | LOC | Estimand | Reference |
52+
|---|---|---|---|
53+
| `tmle/ltmle.py` | 381 | Longitudinal ATE under static regime | van der Laan-Gruber 2012 |
54+
55+
### 2.5 Proximal causal inference (505 LOC)
56+
| Module | LOC | Estimand | Reference |
57+
|---|---|---|---|
58+
| `proximal/negative_controls.py` | 312 | NCO/NCE calibration + double-NC ATE | Lipsitch 2010, Miao-Shi-Tchetgen 2018/2020 |
59+
| `proximal/pci_regression.py` | 193 | Proximal 2-stage regression | Cui-Tchetgen 2020 |
60+
61+
### 2.6 Interference / spillover (323 LOC)
62+
| Module | LOC | Estimand | Reference |
63+
|---|---|---|---|
64+
| `interference/network_exposure.py` | 323 | HT estimator for exposure mappings | Aronow-Samii 2017 |
65+
66+
### 2.7 Dose-response / continuous treatment (253 LOC)
67+
| Module | LOC | Estimand | Reference |
68+
|---|---|---|---|
69+
| `dose_response/vcnet.py` | 253 | Varying-coefficient dose-response (+ SCIGAN) | Nie-Brunskill-Wager 2021 |
70+
71+
### 2.8 Matching (231 LOC)
72+
| Module | LOC | Estimand | Reference |
73+
|---|---|---|---|
74+
| `matching/genmatch.py` | 231 | Genetic-algorithm matching | Diamond-Sekhon 2013 |
75+
76+
### 2.9 Diagnostics / sensitivity (295 LOC)
77+
| Module | LOC | Estimand | Reference |
78+
|---|---|---|---|
79+
| `diagnostics/rosenbaum.py` | 295 | Rosenbaum Γ sensitivity bounds | Rosenbaum 2002 |
80+
81+
### 2.10 Spatial econometrics (376 LOC)
82+
| Module | LOC | Estimand | Reference |
83+
|---|---|---|---|
84+
| `spatial/did.py` | 208 | DID with spatial spillovers | Delgado-Florax 2015, Butts 2021 |
85+
| `spatial/iv.py` | 168 | Spatial IV | Kelejian-Prucha 1998 |
86+
87+
### 2.11 Policy learning (222 LOC)
88+
| Module | LOC | Estimand | Reference |
89+
|---|---|---|---|
90+
| `policy_learning/ope.py` | 222 | Off-policy evaluation (IPS, DR, switch) | Dudík-Erhan-Langford-Li 2014 |
91+
92+
### 2.12 Time series causal (203 LOC)
93+
| Module | LOC | Estimand | Reference |
94+
|---|---|---|---|
95+
| `timeseries/its.py` | 203 | Interrupted time series | Wagner et al. 2002 |
96+
97+
### 2.13 Censoring / survival weights (266 LOC)
98+
| Module | LOC | Estimand | Reference |
99+
|---|---|---|---|
100+
| `censoring/ipcw.py` | 260 | Inverse-probability-of-censoring weights | Robins-Finkelstein 2000 |
101+
102+
### 2.14 Target trial emulation (503 LOC, **only module WITH tests**)
103+
| Module | LOC | Purpose |
104+
|---|---|---|
105+
| `target_trial/protocol.py` | 151 | Protocol specification primitives |
106+
| `target_trial/emulate.py` | 125 | Emulation pipeline |
107+
| `target_trial/ccw.py` + `ccw_internal.py` | 188 | Clone-censor-weight backbone |
108+
| `target_trial/diagnostics.py` | 61 | Balance + positivity diagnostics |
109+
| **`tests/test_target_trial.py`** | **188 LOC** | **sole dedicated test** |
110+
111+
## 3. Re-export surface
112+
113+
Main `src/statspai/__init__.py`: +35 / −8 lines, registers 13 new
114+
top-level symbols:
115+
`multi_arm_forest`, `iv_forest`, `genmatch`, `proximal_regression`,
116+
`ltmle`, `vcnet`, `scigan`, `fci`, `network_exposure`, `q_learning`,
117+
`a_learning`, `snmm`, `causal_survival_forest`.
118+
119+
Sub-package `__init__.py` diffs: 12 files, 1–4 lines each, all pure
120+
re-exports.
121+
122+
`sp.*` smoke check: all 13 new symbols resolve on import.
123+
124+
## 4. Ship gate
125+
126+
### 4.1 Blockers identified
127+
128+
| # | Blocker | Severity |
129+
|---|---|---|
130+
| B1 | 22 of 23 new modules have **no dedicated tests** — only `target_trial` does | HIGH |
131+
| B2 | Version string stays at `0.9.14` — not bumped to `0.9.16` | MED |
132+
| B3 | `CHANGELOG.md` not updated | LOW |
133+
134+
### 4.2 Mitigation strategy — **WIP release**
135+
136+
This release ships as **`v0.9.16.dev` / "breadth expansion WIP"** with
137+
a clear documentation disclaimer. Reasoning:
138+
139+
- Writing production tests for 22 modules upfront would gate the
140+
release by 2–4 weeks and leave finished code rotting in working
141+
tree.
142+
- The import chain is healthy (`sp.*` all resolve), so users who
143+
discover a module and read its docstring + paper refs get a
144+
working-in-principle implementation.
145+
- The known-gap signalling pattern matches `memory/project_v1_upgrade`
146+
and `project_synth_v090_review` precedent: ship, then harden with
147+
follow-up test commits per module.
148+
149+
### 4.3 Required pre-commit actions
150+
151+
Only what is strictly needed to avoid shipping a broken release:
152+
153+
1. Bump version `0.9.14 → 0.9.16` in `pyproject.toml` + `src/statspai/__init__.py`.
154+
2. Run full test suite (no new tests added, but must confirm no
155+
regressions against the 2,319 existing tests).
156+
3. Commit as **one release commit**: `feat: v0.9.16 — breadth expansion (20 new modules, test hardening follow-up)`.
157+
158+
### 4.4 Deferred (follow-up commits)
159+
160+
Per-module test files, one PR/commit per module, roughly prioritised by user-visibility:
161+
162+
| Priority | Module | Reason |
163+
|---|---|---|
164+
| P0 | `tmle/ltmle.py` | Most complex algorithm (recursive backward induction); highest failure surface |
165+
| P0 | `causal_discovery/fci.py` | PAG output format is sticky to API changes |
166+
| P1 | `dtr/{q,a,snmm}_learning.py` | 3-way API consistency |
167+
| P1 | `proximal/negative_controls.py` | Paper-sensitive math |
168+
| P1 | `interference/network_exposure.py` | MC exposure probs are numerically delicate |
169+
| P2 | All others | Smaller and / or simpler |
170+
171+
## 5. Commit strategy
172+
173+
**Single commit**, following the project's historical release pattern
174+
(`a546ead feat: v0.9.12 —`, `2fd88de feat: v0.9.13 —`, etc.):
175+
176+
```
177+
feat: v0.9.16 — breadth expansion (20 new modules, test hardening follow-up)
178+
```
179+
180+
The commit body explicitly declares the WIP test status so future
181+
`git blame` readers see the gap.
182+
183+
## 6. Success criteria
184+
185+
1. `git push origin main` succeeds.
186+
2. `pip install .` produces a 0.9.16 wheel.
187+
3. `import statspai as sp` + 13 new symbol lookups still resolve after
188+
checkout.
189+
4. Full existing test suite stays green (no regressions).
190+
5. At least one follow-up commit per P0 module within 2 weeks.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "StatsPAI"
7-
version = "0.9.15"
7+
version = "0.9.16"
88
description = "The Agent-Native Causal Inference & Econometrics Toolkit for Python"
99
readme = "README.md"
1010
license = {text = "MIT"}

src/statspai/__init__.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
>>> sp.outreg2(result, filename="results.xlsx")
2323
"""
2424

25-
__version__ = "0.9.15"
25+
__version__ = "0.9.16"
2626
__author__ = "Biaoyue Wang"
2727
__email__ = "brycew6m@stanford.edu"
2828

@@ -89,7 +89,7 @@
8989
Absorber, demean, absorb_ols, hdfe_ols, FEOLSResult,
9090
)
9191
from .causal_impact import causal_impact, CausalImpactEstimator, impactplot
92-
from .mediation import mediate, MediationAnalysis, mediate_sensitivity, mediate_interventional
92+
from .mediation import mediate, MediationAnalysis, mediate_sensitivity, mediate_interventional, four_way_decomposition, FourWayResult
9393
from .bartik import bartik, BartikIV, ssaggregate, shift_share_se
9494
from .output.outreg2 import OutReg2, outreg2
9595
from .output.modelsummary import modelsummary, coefplot
@@ -164,8 +164,9 @@
164164
from .regression.logit_probit import logit, probit, cloglog
165165
from .regression.glm import glm, GLMRegression, GLMEstimator
166166
from .regression.count import poisson, nbreg, ppmlhdfe
167-
from .neural_causal import tarnet, cfrnet, dragonnet, TARNet, CFRNet, DragonNet
168-
from .causal_discovery import notears, NOTEARS, pc_algorithm, PCAlgorithm, lingam, LiNGAMResult, ges, GESResult, fci, FCIResult
167+
from .neural_causal import tarnet, cfrnet, dragonnet, TARNet, CFRNet, DragonNet, gnn_causal, GNNCausalResult
168+
from .neural_causal.cevae import cevae, CEVAE, CEVAEResult
169+
from .causal_discovery import notears, NOTEARS, pc_algorithm, PCAlgorithm, lingam, LiNGAMResult, ges, GESResult, fci, FCIResult, icp, nonlinear_icp, ICPResult
169170
from .tmle import tmle, TMLE, super_learner, SuperLearner, ltmle, LTMLEResult
170171
from .policy_learning import policy_tree, PolicyTree, policy_value, direct_method, ips, snips, doubly_robust, OPEResult
171172
from .conformal_causal import conformal_cate, ConformalCATE
@@ -174,7 +175,7 @@
174175
from .matrix_completion import mc_panel, MCPanel
175176
from .dose_response import dose_response, DoseResponse, vcnet, scigan, VCNetResult
176177
from .bounds import lee_bounds, manski_bounds, BoundsResult, horowitz_manski, iv_bounds, oster_delta, selection_bounds, breakdown_frontier, balke_pearl, BalkePearlResult
177-
from .interference import spillover, SpilloverEstimator, network_exposure, NetworkExposureResult
178+
from .interference import spillover, SpilloverEstimator, network_exposure, NetworkExposureResult, peer_effects, PeerEffectsResult
178179
from .dtr import g_estimation, GEstimation, q_learning, QLearningResult, a_learning, ALearningResult, snmm, SNMMResult
179180
from .multi_treatment import multi_treatment, MultiTreatment
180181
from .robustness import spec_curve, SpecCurveResult, robustness_report, RobustnessResult, subgroup_analysis, SubgroupResult
@@ -185,8 +186,24 @@
185186
rule1 as do_rule1, rule2 as do_rule2, rule3 as do_rule3,
186187
apply_rules as do_calculus_apply, RuleCheck,
187188
swig, SWIGGraph, SCM,
189+
llm_dag, LLMDAGResult,
188190
)
189191

192+
# === Transportability (Pearl-Bareinboim + Dahabreh-Stuart) ===
193+
from . import transport
194+
from .transport import (
195+
TransportWeightResult,
196+
identify_transport, TransportIdentificationResult,
197+
)
198+
199+
# === Off-Policy Evaluation (contextual bandits) ===
200+
from . import ope
201+
from .ope import OPEResult
202+
203+
# === Parametric g-formula (iterative conditional expectation) ===
204+
from . import gformula
205+
from .gformula import ice as gformula_ice_fn, ICEResult
206+
190207
# === Target Trial Emulation (JAMA 2022 framework) ===
191208
from . import target_trial
192209
from .target_trial import (

0 commit comments

Comments
 (0)