Skip to content

Commit 7888c24

Browse files
v0.9.17: three-school completion + clinical/MR depth + preregistration + review-fix pass
Three-school completion driven by the "Causal Inference Knowledge Map" article (Econometrics / Epidemiology / ML), adds the entry-level primitives, DSL, and QA layer that closed the last gaps vs. Stata / R. New subpackages - sp.epi — OR / RR / RD / AF / IRR / NNT / PR, Mantel-Haenszel (+ Tarone-corrected Breslow-Day), direct + indirect standardization, Bradford-Hill rubric, and the clinical-diagnostics toolkit (sensitivity/ specificity, ROC + Hanley-McNeil SE, Cohen's kappa). - sp.longitudinal — unified What-If Layer-4 dispatcher (IPW / MSM / parametric g-formula ICE) plus a safe AST-walked dynamic-treatment-regime DSL (no eval). contrast() gives delta-method SE for two-regime comparisons. - sp.question — estimand-first causal_question() DSL with identify() -> estimate() -> report() flow and an auto-design picker (IV / RD / DiD / longitudinal / selection-on-observables). MR full suite (sp.mr / sp.mendelian extended) - Cochran Q / Ruecker Q', MR-Egger intercept test (t(n-2)), leave-one-out, Steiger directionality, MR-PRESSO with per-SNP outlier + distortion tests, Radial MR, weighted/simple mode estimator (Hartwig ZEMPA), F-statistic instrument-strength panel, funnel + scatter plot helpers. Target Trial Emulation flagship - TargetTrialResult.to_paper(fmt=markdown/latex/text) renders STROBE-compatible Methods + Results blocks tracking the JAMA 2022 7-component TTE spec. - fmt='target' adds the JAMA/BMJ Sep-2025 TARGET Statement 21-item checklist with AUTO-filled fields from the protocol + result. DAG UX - DAG.recommend_estimator() with priority backdoor -> IV -> frontdoor -> not-identifiable, plain-English identification story, and a mediator detector. Unified sensitivity dashboard - result.sensitivity() method on CausalResult + EconometricResults; single call runs E-value + Oster delta + Rosenbaum Gamma + Sensemakr + a CI-based breakdown-frontier estimate. Pre-registration - sp.preregister() / sp.load_preregister() + CausalQuestion.save() / .load() / .to_yaml() produce OSF/AEA-style PAP files with a timestamp + statspai-version metadata block, zero-dep YAML fallback. Registry - 20+ hand-written rich FunctionSpec entries for the new APIs; total public callables registered now 729+, including first-class entries for every new sp.epi / sp.mr / sp.longitudinal / sp.question name. Independent review fixes (2 Critical + 5 High + 4 Medium) - Critical: Katz RR SE formula corrected for Haldane-corrected cells. - Critical: Mantel-Haenszel homogeneity Q replaced with the correct inverse-variance weighted chi^2(K-1) form (previously fabricated). - Regime AST walker now rejects non-numeric values from history (blocks callable/string leakage via user-controlled dicts). - NNT CI ordering fixed when RD CI is entirely negative. - unified_sensitivity no longer fabricates beta_uncontrolled for Oster delta; requires explicit input or skips with a warning. - unified_sensitivity breakdown bias_to_flip now derives from the CI bound closer to the null, not abs(estimate). - unified_sensitivity exceptions now emit warnings.warn instead of silently returning NaN. - MR-Egger intercept p-value uses t(n-2) (matches R packages). - mr_radial requires >= 2 SNPs; mr_f_statistic now reports per-SNP F. - Longitudinal g-formula warns when pivot fills missing panel cells. - YAML serializer partitions on ": " so colons in values round-trip. Tests - 129 new tests across 11 new test files; 0 regressions across the 2158-test existing suite. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2a5f368 commit 7888c24

99 files changed

Lines changed: 18448 additions & 41 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,4 @@ test-notebooks/demo_causal_inference.ipynb
178178

179179
# Claude Code session state (local only)
180180
.claude/
181-
181+
CLAUDE.md

CHANGELOG.md

Lines changed: 112 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@
22

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

5-
## [0.9.17] - 2026-04-21 — Modern-weighting + MC g-formula + weakrobust panel + end-to-end workflow
6-
7-
Surgical release targeting four of the most-requested gaps identified
8-
in the v1.0 gap-analysis pass: a Stata-style unified weak-IV-robust
9-
diagnostic panel, the Zubizarreta (2015) stable-balancing-weights
10-
estimator, the Robins (1986) Monte-Carlo g-formula (complementing the
11-
existing Bang-Robins ICE), and a truly end-to-end `sp.causal()`
12-
orchestrator that auto-runs multi-estimator comparison + sensitivity
13-
triad + CATE heterogeneity on top of the existing diagnose/recommend/
14-
estimate/robustness stages.
5+
## [0.9.17] - 2026-04-21 — Modern-weighting + MC g-formula + weakrobust panel + three-school completion
6+
7+
Two-pronged release. First, a surgical pass targeting four of the most-
8+
requested gaps from the v1.0 gap-analysis: a Stata-style unified
9+
weak-IV-robust diagnostic panel, the Zubizarreta (2015) stable-balancing-
10+
weights estimator, the Robins (1986) Monte-Carlo g-formula (complementing
11+
the existing Bang-Robins ICE), and a truly end-to-end `sp.causal()`
12+
orchestrator. Second, a three-school completion pass mapping the
13+
*Econometrics ↔ Epidemiology ↔ ML* knowledge-map article onto StatsPAI:
14+
epidemiology primitives, MR full suite, longitudinal dispatcher, DAG-to-
15+
estimator recommender, estimand-first DSL, and a unified sensitivity
16+
dashboard attached to every `Result` object.
1517

1618
### Added
1719

@@ -75,6 +77,106 @@ time-series causal discovery, Partial-ID + ML bounds, and the
7577
Agent-MCP server integration. Each is substantial enough to warrant
7678
its own focused sprint rather than being shipped half-finished here.
7779

80+
### Added — three-school completion (2026-04-21 sub-release)
81+
82+
Driven by a cross-reference audit against the article
83+
"Causal Inference Knowledge Map — Econometrics, Epidemiology, ML",
84+
which pinpointed Layer-4 (*What If* longitudinal), epidemiology
85+
entry-level primitives, Mendelian randomization diagnostic depth,
86+
DAG-to-estimator UX, and estimand-first workflow as the remaining
87+
gaps vs. Stata / R dominance.
88+
89+
**Epidemiology primitives (`sp.epi`) — NEW subpackage**
90+
91+
- `odds_ratio`, `relative_risk`, `risk_difference`,
92+
`attributable_risk` (Levin PAF), `incidence_rate_ratio` (exact
93+
Poisson CI via Clopper-Pearson), `number_needed_to_treat`,
94+
`prevalence_ratio` — Woolf / Fisher-exact / Katz / Wald / Newcombe
95+
intervals; Haldane-Anscombe correction for zero cells.
96+
- `mantel_haenszel` (OR / RR with Robins-Breslow-Greenland variance),
97+
`breslow_day_test` (homogeneity of OR with Tarone correction).
98+
- `direct_standardize`, `indirect_standardize` — direct-standardized
99+
rates + SMR with Garwood exact Poisson CI.
100+
- `bradford_hill` — structured 9-viewpoint causal-assessment rubric
101+
with prerequisite check (no causality claim without temporality).
102+
103+
**Mendelian randomization full suite (`sp.mr` / `sp.mendelian`)**
104+
105+
- `mr_heterogeneity` — Cochran's Q (IVW) or Rücker's Q' (Egger) + I².
106+
- `mr_pleiotropy_egger` — formal MR-Egger intercept test for
107+
directional horizontal pleiotropy (Bowden 2015).
108+
- `mr_leave_one_out` — per-SNP drop-one IVW sensitivity.
109+
- `mr_steiger` — Hemani (2017) directionality test using Fisher-z of
110+
per-trait R² contributions.
111+
- `mr_presso` — Verbanck (2018) global outlier test + per-SNP outlier
112+
detection + distortion test for raw-vs-corrected comparison.
113+
- `mr_radial` — Bowden (2018) radial reparameterization + Bonferroni-
114+
thresholded outlier flagging.
115+
116+
**Target trial emulation — publication-ready report**
117+
118+
- `TargetTrialResult.to_paper(fmt=...)` / `sp.target_trial.to_paper`
119+
render STROBE-compatible Methods + Results block in Markdown /
120+
LaTeX / plain-text for direct inclusion in manuscripts. Table
121+
structure tracks the JAMA 2022 7-component TTE spec exactly.
122+
123+
**Longitudinal causal dispatcher (`sp.longitudinal`) — NEW subpackage**
124+
125+
- `sp.longitudinal.analyze` — unified entry point that auto-routes
126+
to IPW (no time-varying confounders) / MSM (dynamic regime with
127+
time-varying confounders) / parametric g-formula ICE (static
128+
regime) based on data shape and the supplied regime object.
129+
- `sp.longitudinal.contrast` — plug-in estimator of
130+
`E[Y(regime_a)] - E[Y(regime_b)]` with delta-method SE.
131+
- `sp.regime`, `sp.always_treat`, `sp.never_treat` — dynamic-treatment-
132+
regime DSL supporting static sequences, callables, and a safe
133+
`"if cd4 < 200 then 1 else 0"` string DSL. The string DSL is parsed
134+
into a whitelisted AST and interpreted by a tiny tree-walker — no
135+
dynamic code execution is ever invoked, and disallowed constructs
136+
are rejected at regime-construction time.
137+
138+
**Estimand-first causal-question DSL (`sp.causal_question`) — NEW subpackage**
139+
140+
- `sp.causal_question(treatment=, outcome=, estimand=, design=, ...)`
141+
declares a causal question up front. `.identify()` picks an
142+
estimator + lists the identifying assumptions the user must defend;
143+
`.estimate()` runs the analysis; `.report()` produces a Markdown
144+
Methods + Results paragraph.
145+
- Auto-design selects IV when instruments are present, RD when running
146+
variable + cutoff given, DiD when panel + time, longitudinal when
147+
repeated measures, else selection-on-observables.
148+
- Dispatches internally to `sp.regress` / `sp.aipw` / `sp.iv` /
149+
`sp.did` / `sp.rdrobust` / `sp.synth` / `sp.longitudinal.analyze` /
150+
`sp.event_study`.
151+
152+
**DAG → estimator recommender (`sp.dag.recommend_estimator`)**
153+
154+
- `DAG.recommend_estimator(exposure, outcome)` — inspects the declared
155+
graph and suggests a StatsPAI estimator with a plain-English
156+
identification story. Priority order: backdoor adjustment (OLS /
157+
IPW / matching) → IV (heuristic relevance + exclusion check) →
158+
frontdoor → not-identifiable (with sensitivity-analysis fallbacks).
159+
- Detects mediators on the causal path automatically.
160+
161+
**Unified sensitivity dashboard (`sp.unified_sensitivity`)**
162+
163+
- `result.sensitivity()` — method added to both `CausalResult` and
164+
`EconometricResults`. Single call runs E-value (always), Oster δ
165+
(when R² inputs supplied), Rosenbaum Γ (when a matched structure is
166+
exposed), Sensemakr (regression models), and a breakdown-frontier
167+
bias estimate.
168+
169+
### Changed (three-school completion)
170+
171+
- `__init__.py`: 40+ new names exposed at top level including `sp.epi`,
172+
`sp.longitudinal`, `sp.question`, `sp.tte` / `sp.mr` short aliases.
173+
174+
### Fixed (three-school completion)
175+
176+
- Regime DSL: AST validation moved from evaluate-time to compile-time
177+
so unsafe expressions are rejected immediately at `sp.regime(...)`
178+
construction, before any history is supplied.
179+
78180
## [0.9.16] - 2026-04-20 — v1.0 breadth expansion + Bayesian family polish + Rust Phase-2 CI
79181

80182
The largest release since the v1.0 breadth pass. Maps StatsPAI onto

src/statspai/__init__.py

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
bacon_decomposition, honest_did, breakdown_m, event_study,
4141
did_analysis, DIDAnalysis, did_multiplegt, did_imputation, stacked_did, cic,
4242
wooldridge_did, etwfe, etwfe_emfx, drdid, twfe_decomposition,
43+
did_bcf, cohort_anchored_event_study, design_robust_event_study,
44+
did_misclassified,
4345
did_summary, did_summary_to_markdown, did_summary_to_latex, did_report,
4446
pretrends_test, pretrends_power, sensitivity_rr, SensitivityResult, pretrends_summary,
4547
parallel_trends_plot, bacon_plot, group_time_plot, did_plot,
@@ -73,6 +75,7 @@
7375
synthdid_placebo, synthdid_plot, synthdid_units_plot, synthdid_rmse_plot,
7476
california_prop99,
7577
)
78+
from .synth.sequential_sdid import sequential_sdid, SequentialSDIDResult
7679
from .matching import (
7780
match, MatchEstimator, ebalance, balanceplot, psplot,
7881
propensity_score, overlap_plot, trimming, love_plot,
@@ -115,6 +118,8 @@
115118
negative_control_outcome, negative_control_exposure,
116119
double_negative_control, NegativeControlResult,
117120
proximal_regression, ProximalRegResult,
121+
fortified_pci, bidirectional_pci, pci_mtp,
122+
select_pci_proxies, ProxyScoreResult,
118123
)
119124
from .principal_strat import (
120125
principal_strat, PrincipalStratResult, survivor_average_causal_effect,
@@ -168,15 +173,24 @@
168173
from .regression.count import poisson, nbreg, ppmlhdfe
169174
from .neural_causal import tarnet, cfrnet, dragonnet, TARNet, CFRNet, DragonNet, gnn_causal, GNNCausalResult
170175
from .neural_causal.cevae import cevae, CEVAE, CEVAEResult
171-
from .causal_discovery import notears, NOTEARS, pc_algorithm, PCAlgorithm, lingam, LiNGAMResult, ges, GESResult, fci, FCIResult, icp, nonlinear_icp, ICPResult
172-
from .tmle import tmle, TMLE, super_learner, SuperLearner, ltmle, LTMLEResult
176+
from .causal_discovery import notears, NOTEARS, pc_algorithm, PCAlgorithm, lingam, LiNGAMResult, ges, GESResult, fci, FCIResult, icp, nonlinear_icp, ICPResult, pcmci, PCMCIResult, partial_corr_pvalue, lpcmci, LPCMCIResult, dynotears, DYNOTEARSResult
177+
from .tmle import tmle, TMLE, super_learner, SuperLearner, ltmle, LTMLEResult, ltmle_survival, LTMLESurvivalResult
173178
from .policy_learning import policy_tree, PolicyTree, policy_value, direct_method, ips, snips, doubly_robust, OPEResult
174-
from .conformal_causal import conformal_cate, ConformalCATE
175-
from .bcf import bcf, BayesianCausalForest
179+
from .conformal_causal import (
180+
conformal_cate, ConformalCATE,
181+
weighted_conformal_prediction,
182+
conformal_counterfactual, ConformalCounterfactualResult,
183+
conformal_ite_interval, ConformalITEResult,
184+
conformal_density_ite, ConformalDensityResult,
185+
conformal_ite_multidp, MultiDPConformalResult,
186+
conformal_debiased_ml, DebiasedConformalResult,
187+
conformal_fair_ite, FairConformalResult,
188+
)
189+
from .bcf import bcf, BayesianCausalForest, bcf_longitudinal, BCFLongResult
176190
from .bunching import bunching, BunchingEstimator, notch, NotchResult
177191
from .matrix_completion import mc_panel, MCPanel
178192
from .dose_response import dose_response, DoseResponse, vcnet, scigan, VCNetResult
179-
from .bounds import lee_bounds, manski_bounds, BoundsResult, horowitz_manski, iv_bounds, oster_delta, selection_bounds, breakdown_frontier, balke_pearl, BalkePearlResult
193+
from .bounds import lee_bounds, manski_bounds, BoundsResult, horowitz_manski, iv_bounds, oster_delta, selection_bounds, breakdown_frontier, balke_pearl, BalkePearlResult, ml_bounds, MLBoundsResult
180194
from .interference import spillover, SpilloverEstimator, network_exposure, NetworkExposureResult, peer_effects, PeerEffectsResult
181195
from .dtr import g_estimation, GEstimation, q_learning, QLearningResult, a_learning, ALearningResult, snmm, SNMMResult
182196
from .multi_treatment import multi_treatment, MultiTreatment
@@ -191,6 +205,25 @@
191205
llm_dag, LLMDAGResult,
192206
)
193207

208+
# === Bridging theorems (DiD≡SC, EWM≡CATE, CB≡IPW, Kink≡RDD,
209+
# DR-Calib, Surrogate≡PCI) ===
210+
from .bridge import bridge, BridgeResult
211+
212+
# === Long-term effects via surrogate indices ===
213+
from . import surrogate
214+
from .surrogate import (
215+
surrogate_index, long_term_from_short, proximal_surrogate_index,
216+
SurrogateResult,
217+
)
218+
219+
# === Counterfactual fairness / algorithmic-bias diagnostics ===
220+
from . import fairness
221+
from .fairness import (
222+
counterfactual_fairness, orthogonal_to_bias,
223+
demographic_parity, equalized_odds, fairness_audit,
224+
FairnessResult, FairnessAudit,
225+
)
226+
194227
# === Transportability (Pearl-Bareinboim + Dahabreh-Stuart) ===
195228
from . import transport
196229
from .transport import (
@@ -216,6 +249,8 @@
216249
protocol as target_trial_protocol,
217250
emulate as target_trial_emulate,
218251
to_paper as target_trial_report,
252+
target_checklist as target_trial_checklist,
253+
TARGET_ITEMS,
219254
clone_censor_weight,
220255
immortal_time_check,
221256
TargetTrialProtocol,
@@ -234,6 +269,9 @@
234269
mantel_haenszel, breslow_day_test,
235270
direct_standardize, indirect_standardize,
236271
bradford_hill,
272+
diagnostic_test, sensitivity_specificity,
273+
roc_curve, auc, cohen_kappa,
274+
DiagnosticTestResult, ROCResult, KappaResult,
237275
)
238276

239277
# === Longitudinal causal inference (What If Layer 4) ===
@@ -250,6 +288,7 @@
250288
from .question import (
251289
causal_question, CausalQuestion,
252290
IdentificationPlan, EstimationResult,
291+
preregister, load_preregister,
253292
)
254293

255294
# === Unified sensitivity dashboard ===
@@ -349,6 +388,8 @@
349388
mr_steiger, mr_presso, mr_radial,
350389
HeterogeneityResult, PleiotropyResult, LeaveOneOutResult,
351390
SteigerResult, MRPressoResult, RadialResult,
391+
mr_mode, mr_f_statistic, mr_funnel_plot, mr_scatter_plot,
392+
ModeBasedResult, FStatisticResult,
352393
)
353394
# Expose recommend_estimator at top level too
354395
from .dag import recommend_estimator as dag_recommend_estimator
@@ -372,6 +413,11 @@
372413
from .regression.selection import biprobit, etregress
373414
# Distributional Treatment Effects
374415
from .qte import distributional_te, DTEResult
416+
from .qte import (
417+
dist_iv, kan_dlate, DistIVResult,
418+
qte_hd_panel, HDPanelQTEResult,
419+
beyond_average_late, BeyondAverageResult,
420+
)
375421
# Structural Estimation (BLP)
376422
from .structural import blp, BLPResult
377423

@@ -1039,19 +1085,27 @@
10391085
"number_needed_to_treat", "prevalence_ratio",
10401086
"mantel_haenszel", "breslow_day_test",
10411087
"direct_standardize", "indirect_standardize", "bradford_hill",
1088+
# v0.9.17 additions (epi clinical diagnostics)
1089+
"diagnostic_test", "sensitivity_specificity",
1090+
"roc_curve", "auc", "cohen_kappa",
1091+
"DiagnosticTestResult", "ROCResult", "KappaResult",
10421092
# v0.9.17 additions (MR full suite)
10431093
"mr", "mendelian",
10441094
"mr_heterogeneity", "mr_pleiotropy_egger", "mr_leave_one_out",
10451095
"mr_steiger", "mr_presso", "mr_radial",
10461096
"HeterogeneityResult", "PleiotropyResult", "LeaveOneOutResult",
10471097
"SteigerResult", "MRPressoResult", "RadialResult",
1098+
# v0.9.17 additions (MR deepening)
1099+
"mr_mode", "mr_f_statistic", "mr_funnel_plot", "mr_scatter_plot",
1100+
"ModeBasedResult", "FStatisticResult",
10481101
# v0.9.17 additions (longitudinal unified)
10491102
"longitudinal", "longitudinal_analyze", "longitudinal_contrast",
10501103
"regime", "always_treat", "never_treat",
10511104
"LongitudinalResult", "Regime",
1052-
# v0.9.17 additions (causal-question DSL)
1105+
# v0.9.17 additions (causal-question DSL + pre-registration)
10531106
"question", "causal_question", "CausalQuestion",
10541107
"IdentificationPlan", "EstimationResult",
1108+
"preregister", "load_preregister",
10551109
# v0.9.17 additions (unified sensitivity)
10561110
"unified_sensitivity", "SensitivityDashboard",
10571111
# v0.9.17 additions (DAG UX)

src/statspai/agent/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,23 @@
4747
"""
4848
from .tools import tool_manifest, execute_tool, TOOL_REGISTRY
4949
from .remediation import remediate, REMEDIATIONS
50+
from .mcp_server import (
51+
serve_stdio as mcp_serve_stdio,
52+
handle_request as mcp_handle_request,
53+
SERVER_NAME as MCP_SERVER_NAME,
54+
SERVER_VERSION as MCP_SERVER_VERSION,
55+
MCP_PROTOCOL_VERSION,
56+
)
5057

5158
__all__ = [
5259
'tool_manifest',
5360
'execute_tool',
5461
'TOOL_REGISTRY',
5562
'remediate',
5663
'REMEDIATIONS',
64+
'mcp_serve_stdio',
65+
'mcp_handle_request',
66+
'MCP_SERVER_NAME',
67+
'MCP_SERVER_VERSION',
68+
'MCP_PROTOCOL_VERSION',
5769
]

0 commit comments

Comments
 (0)