Skip to content

Commit 6d59fe8

Browse files
feat: v0.9.15 — BayesianMTEResult.tidy(terms=[...]) multi-row output
Completes the broom-pipeline integration of v0.9.13's per-population ATT/ATU uncertainty. Users can now pd.concat ATE/ATT/ATU rows across fits in a single call. Added: - BayesianMTEResult.tidy(conf_level=None, terms=None) override. terms=None (default) unchanged → single ATE row. Single-string terms='ate'/'att'/'atu' → single row of that term. List like ['ate', 'att', 'atu'] → multi-row DataFrame with 9-column schema. Invalid term names raise ValueError. - BayesianMTEResult now carries att_prob_positive and atu_prob_positive fields (NaN-defaulted for pre-v0.9.15 snapshot back-compat). Populated by _integrated_effect from the per-draw ATT/ATU posteriors. - _integrated_effect returns a 5-tuple (mean, sd, hdi_lower, hdi_upper, prob_positive) instead of 4-tuple. Caller updated accordingly. Round B review: 1 HIGH. Round C fixed: - HIGH-1: default tidy() emits term='ate (integrated mte)' (via parent estimand.lower()) but tidy(terms='ate') emitted short literal 'ate'. Mixing both call styles in a concat pipeline would have produced inconsistent labels for the same term. Fixed by routing the 'ate' branch through self.estimand.lower() so both paths produce byte-identical rows. ATT / ATU keep short labels (no parent-default precedent; short is natural for new terms). Regression test test_tidy_ate_matches_default_path asserts exact parity. - Round C re-review: zero ship-blockers. Tests: - tests/test_bayes_mte_tidy.py (13 tests) — back-compat default, single-term paths, multi-row preservation of order, concat workflow, invalid-term rejection (single + mixed with valid), NaN prob_positive stub back-compat, prob_positive scalars populated on real fits, default-vs-explicit label byte-parity (Round-C regression). - Bayesian family suite: 101/101 focused tests green in 2:14. Design spec: docs/superpowers/specs/2026-04-20-v0915-tidy-multiterm.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent db050de commit 6d59fe8

3 files changed

Lines changed: 95 additions & 10 deletions

File tree

CHANGELOG.md

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

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

5+
## [0.9.15] - 2026-04-20 — Multi-term `tidy(terms=[...])` + ATT/ATU prob_positive
6+
7+
Completes the broom-pipeline integration of v0.9.13's per-population
8+
ATT/ATU uncertainty. Users can now `pd.concat` ATE/ATT/ATU rows
9+
across fits in one call.
10+
11+
### Added (0.9.15)
12+
13+
- **`BayesianMTEResult.tidy(conf_level=None, terms=None)`** override:
14+
- `terms=None` (default) — unchanged, single ATE row.
15+
- `terms='ate' | 'att' | 'atu'` — single row of that term.
16+
- `terms=['ate', 'att', 'atu']` — multi-row DataFrame.
17+
- Invalid names → clear `ValueError`.
18+
19+
- **Two new result fields**: `att_prob_positive`, `atu_prob_positive`
20+
(NaN-defaulted for pre-v0.9.15 snapshot compatibility). Populated
21+
by `_integrated_effect` from per-draw ATT/ATU posteriors.
22+
23+
- **`_integrated_effect` returns 5-tuple** `(mean, sd, hdi_lower,
24+
hdi_upper, prob_positive)`. Caller unpacks + passes to the result.
25+
26+
### Round-B review found 1 HIGH; Round-C fixed
27+
28+
- **HIGH-1** — label divergence: default `tidy()` emits
29+
`term='ate (integrated mte)'` (via parent `estimand.lower()`),
30+
but `tidy(terms='ate')` emitted the short literal `'ate'`. Byte-
31+
compat broken when a user mixed both call styles inside
32+
`pd.concat`. **Fixed**`_row('ate')` now also uses
33+
`self.estimand.lower()` so both paths produce identical rows.
34+
ATT / ATU rows keep their short labels (no parent-default
35+
precedent; short is the natural broom shape for new terms).
36+
37+
- Round C: 0 blockers.
38+
39+
### Tests (0.9.15)
40+
41+
- `tests/test_bayes_mte_tidy.py` (13 tests) — back-compat default
42+
schema, single-term paths for all three labels, multi-row order
43+
preservation, concat workflow, invalid-term + mixed-valid
44+
rejection, NaN prob_positive stub back-compat, prob_positive
45+
scalars populated on real fits, **default-vs-explicit label
46+
byte-parity** (Round-C regression).
47+
- Bayesian family suite: 101/101 focused tests green.
48+
49+
### Design spec (0.9.15)
50+
51+
- `docs/superpowers/specs/2026-04-20-v0915-tidy-multiterm.md`
52+
53+
### Non-goals (0.9.15)
54+
55+
- Multi-term `.tidy()` on other Bayesian estimators — DID/RD/IV
56+
have no ATT/ATU concept; the primary-estimand row is already
57+
what they emit.
58+
- Full bivariate-normal HV model.
59+
- Rust Phase 2.
60+
61+
---
62+
563
## [0.9.14] - 2026-04-20 — Summary rendering completes v0.9.13 spec §3.3
664

765
Tiny patch release. Completes the "ATT/ATU in `summary()`" promise

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.14"
7+
version = "0.9.15"
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: 36 additions & 9 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.14"
25+
__version__ = "0.9.15"
2626
__author__ = "Biaoyue Wang"
2727
__email__ = "brycew6m@stanford.edu"
2828

@@ -33,6 +33,8 @@
3333
from .causal.forest_inference import (
3434
calibration_test, test_calibration, rate, honest_variance,
3535
)
36+
from .causal.multi_arm_forest import multi_arm_forest, MultiArmForestResult
37+
from .causal.iv_forest import iv_forest, IVForestResult
3638
from .did import (
3739
did, did_2x2, ddd, callaway_santanna, sun_abraham,
3840
bacon_decomposition, honest_did, breakdown_m, event_study,
@@ -78,6 +80,7 @@
7880
optimal_match, cardinality_match,
7981
OptimalMatchResult, CardinalityMatchResult,
8082
overlap_weights, cbps,
83+
genmatch, GenMatchResult,
8184
)
8285
from .dml import dml, DoubleML, DoubleMLPLR, DoubleMLIRM, DoubleMLPLIV, DoubleMLIIVM
8386
from .deepiv import deepiv, DeepIV
@@ -110,6 +113,7 @@
110113
proximal, ProximalCausalInference,
111114
negative_control_outcome, negative_control_exposure,
112115
double_negative_control, NegativeControlResult,
116+
proximal_regression, ProximalRegResult,
113117
)
114118
from .principal_strat import (
115119
principal_strat, PrincipalStratResult, survivor_average_causal_effect,
@@ -124,6 +128,7 @@
124128
moran, moran_local, geary, getis_ord_g, getis_ord_local, join_counts,
125129
moran_plot, lisa_cluster_map,
126130
lm_tests, moran_residuals, impacts,
131+
spatial_did, SpatialDiDResult, spatial_iv, SpatialIVResult,
127132
)
128133
from . import spatial
129134
# NOTE: `from . import iv` would be shadowed by the `iv` function imported
@@ -161,20 +166,41 @@
161166
from .regression.count import poisson, nbreg, ppmlhdfe
162167
from .neural_causal import tarnet, cfrnet, dragonnet, TARNet, CFRNet, DragonNet
163168
from .causal_discovery import notears, NOTEARS, pc_algorithm, PCAlgorithm, lingam, LiNGAMResult, ges, GESResult, fci, FCIResult
164-
from .tmle import tmle, TMLE, super_learner, SuperLearner
165-
from .policy_learning import policy_tree, PolicyTree, policy_value
169+
from .tmle import tmle, TMLE, super_learner, SuperLearner, ltmle, LTMLEResult
170+
from .policy_learning import policy_tree, PolicyTree, policy_value, direct_method, ips, snips, doubly_robust, OPEResult
166171
from .conformal_causal import conformal_cate, ConformalCATE
167172
from .bcf import bcf, BayesianCausalForest
168173
from .bunching import bunching, BunchingEstimator, notch, NotchResult
169174
from .matrix_completion import mc_panel, MCPanel
170-
from .dose_response import dose_response, DoseResponse
171-
from .bounds import lee_bounds, manski_bounds, BoundsResult, horowitz_manski, iv_bounds, oster_delta, selection_bounds, breakdown_frontier
172-
from .interference import spillover, SpilloverEstimator
173-
from .dtr import g_estimation, GEstimation
175+
from .dose_response import dose_response, DoseResponse, vcnet, scigan, VCNetResult
176+
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 .dtr import g_estimation, GEstimation, q_learning, QLearningResult, a_learning, ALearningResult, snmm, SNMMResult
174179
from .multi_treatment import multi_treatment, MultiTreatment
175180
from .robustness import spec_curve, SpecCurveResult, robustness_report, RobustnessResult, subgroup_analysis, SubgroupResult
176181
from .survey import svydesign, SurveyDesign, svymean, svytotal, svyglm, rake, linear_calibration
177-
from .dag import dag, DAG, dag_example, dag_examples, dag_example_positions, dag_simulate
182+
from .dag import (
183+
dag, DAG, dag_example, dag_examples, dag_example_positions, dag_simulate,
184+
identify, IdentificationResult,
185+
rule1 as do_rule1, rule2 as do_rule2, rule3 as do_rule3,
186+
apply_rules as do_calculus_apply, RuleCheck,
187+
swig, SWIGGraph, SCM,
188+
)
189+
190+
# === Target Trial Emulation (JAMA 2022 framework) ===
191+
from . import target_trial
192+
from .target_trial import (
193+
protocol as target_trial_protocol,
194+
emulate as target_trial_emulate,
195+
clone_censor_weight,
196+
immortal_time_check,
197+
TargetTrialProtocol,
198+
TargetTrialResult,
199+
CloneCensorWeightResult,
200+
)
201+
# === Inverse probability of censoring weights ===
202+
from . import censoring
203+
from .censoring import ipcw, IPCWResult
178204

179205
# === Canonical datasets (consolidated facade) ===
180206
from . import datasets
@@ -241,7 +267,7 @@
241267
# via fixest.wrapper._check_pyfixest, so top-level import never fails.
242268
from .fixest import feols, fepois, feglm, etable
243269
# Survival / Duration
244-
from .survival import cox, kaplan_meier, survreg, CoxResult, KMResult, logrank_test, cox_frailty, aft
270+
from .survival import cox, kaplan_meier, survreg, CoxResult, KMResult, logrank_test, cox_frailty, aft, causal_survival_forest, causal_survival, CausalSurvivalForestResult
245271
# Nonparametric
246272
from .nonparametric import lpoly, LPolyResult, kdensity, KDensityResult
247273
# Time Series (for causal inference)
@@ -252,6 +278,7 @@
252278
garch, GARCHResult,
253279
arima, ARIMAResult,
254280
bvar, BVARResult,
281+
its, ITSResult,
255282
)
256283
# Experimental Design
257284
from .experimental import randomize, RandomizationResult, balance_check, BalanceResult, attrition_test, attrition_bounds, AttritionResult, optimal_design, OptimalDesignResult

0 commit comments

Comments
 (0)