diff-diff 3.8.0__tar.gz → 3.9.1__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.
Files changed (116) hide show
  1. {diff_diff-3.8.0 → diff_diff-3.9.1}/PKG-INFO +17 -13
  2. {diff_diff-3.8.0 → diff_diff-3.9.1}/README.md +12 -9
  3. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/__init__.py +75 -6
  4. diff_diff-3.9.1/diff_diff/_base.py +169 -0
  5. diff_diff-3.9.1/diff_diff/_deprecation.py +164 -0
  6. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/_reporting_helpers.py +55 -23
  7. diff_diff-3.9.1/diff_diff/_staggered_triple_diff_engine.py +1704 -0
  8. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/agent_workflow.py +4 -3
  9. diff_diff-3.9.1/diff_diff/aggregation.py +709 -0
  10. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/bacon.py +46 -15
  11. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/bootstrap_utils.py +2 -2
  12. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/business_report.py +85 -34
  13. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/chaisemartin_dhaultfoeuille.py +174 -87
  14. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/chaisemartin_dhaultfoeuille_results.py +172 -9
  15. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/changes_in_changes.py +119 -87
  16. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/changes_in_changes_results.py +35 -7
  17. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/continuous_did.py +244 -204
  18. diff_diff-3.9.1/diff_diff/continuous_did_aggregation.py +213 -0
  19. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/continuous_did_results.py +260 -3
  20. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/datasets.py +812 -173
  21. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/diagnostic_report.py +752 -141
  22. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/diagnostics.py +38 -20
  23. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/efficient_did.py +201 -506
  24. diff_diff-3.9.1/diff_diff/efficient_did_aggregation.py +521 -0
  25. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/efficient_did_results.py +315 -3
  26. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/estimators.py +712 -385
  27. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/guides/llms-autonomous.txt +50 -26
  28. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/guides/llms-full.txt +566 -165
  29. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/guides/llms-practitioner.txt +117 -57
  30. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/guides/llms.txt +23 -13
  31. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/had.py +320 -161
  32. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/had_pretests.py +188 -78
  33. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/honest_did.py +768 -64
  34. diff_diff-3.9.1/diff_diff/imputation.py +1493 -0
  35. diff_diff-3.8.0/diff_diff/imputation.py → diff_diff-3.9.1/diff_diff/imputation_aggregation.py +279 -1319
  36. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/imputation_bootstrap.py +1 -33
  37. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/imputation_results.py +347 -4
  38. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/linalg.py +284 -37
  39. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/lpdid.py +128 -38
  40. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/lpdid_results.py +51 -3
  41. diff_diff-3.9.1/diff_diff/mmm.py +1600 -0
  42. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/power.py +127 -32
  43. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/practitioner.py +369 -117
  44. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/prep.py +19 -3
  45. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/prep_dgp.py +10 -9
  46. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/pretrends.py +633 -41
  47. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/profile.py +4 -4
  48. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/rdd.py +89 -55
  49. diff_diff-3.9.1/diff_diff/rddensity.py +1417 -0
  50. diff_diff-3.9.1/diff_diff/rdplot.py +1141 -0
  51. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/results.py +14 -3
  52. diff_diff-3.9.1/diff_diff/results_base.py +1363 -0
  53. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/spillover.py +19 -46
  54. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/stacked_did.py +345 -138
  55. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/stacked_did_results.py +204 -7
  56. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/staggered.py +280 -68
  57. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/staggered_aggregation.py +160 -27
  58. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/staggered_bootstrap.py +23 -6
  59. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/staggered_results.py +396 -4
  60. diff_diff-3.9.1/diff_diff/staggered_triple_diff.py +269 -0
  61. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/staggered_triple_diff_results.py +2 -1
  62. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/sun_abraham.py +300 -55
  63. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/survey.py +47 -22
  64. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/synthetic_control.py +14 -39
  65. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/synthetic_control_results.py +2 -1
  66. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/synthetic_did.py +25 -105
  67. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/triple_diff.py +433 -84
  68. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/trop.py +14 -29
  69. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/trop_results.py +2 -1
  70. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/twfe.py +448 -24
  71. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/two_stage.py +330 -1409
  72. diff_diff-3.9.1/diff_diff/two_stage_aggregation.py +1564 -0
  73. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/two_stage_bootstrap.py +16 -76
  74. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/two_stage_results.py +346 -4
  75. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/utils.py +502 -13
  76. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/visualization/_diagnostic.py +10 -8
  77. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/visualization/_event_study.py +410 -55
  78. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/visualization/_power.py +5 -4
  79. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/wooldridge.py +1280 -213
  80. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/wooldridge_results.py +240 -44
  81. {diff_diff-3.8.0 → diff_diff-3.9.1}/pyproject.toml +32 -5
  82. {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/Cargo.lock +59 -48
  83. {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/Cargo.toml +1 -1
  84. diff_diff-3.8.0/diff_diff/staggered_triple_diff.py +0 -1680
  85. {diff_diff-3.8.0 → diff_diff-3.9.1}/LICENSE +0 -0
  86. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/_backend.py +0 -0
  87. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/_guides_api.py +0 -0
  88. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/_nprobust_port.py +0 -0
  89. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/_rdrobust_port.py +0 -0
  90. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/balancing.py +0 -0
  91. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/bootstrap_chunking.py +0 -0
  92. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/chaisemartin_dhaultfoeuille_bootstrap.py +0 -0
  93. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/conformal.py +0 -0
  94. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/conley.py +0 -0
  95. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/continuous_did_bspline.py +0 -0
  96. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/efficient_did_bootstrap.py +0 -0
  97. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/efficient_did_covariates.py +0 -0
  98. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/efficient_did_weights.py +0 -0
  99. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/guides/__init__.py +0 -0
  100. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/local_linear.py +0 -0
  101. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/trop_global.py +0 -0
  102. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/trop_local.py +0 -0
  103. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/visualization/__init__.py +0 -0
  104. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/visualization/_common.py +0 -0
  105. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/visualization/_continuous.py +0 -0
  106. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/visualization/_staggered.py +0 -0
  107. {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/visualization/_synthetic.py +0 -0
  108. {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/build.rs +0 -0
  109. {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/src/alloc_profile.rs +0 -0
  110. {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/src/batched_solve.rs +0 -0
  111. {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/src/bootstrap.rs +0 -0
  112. {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/src/demean.rs +0 -0
  113. {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/src/lib.rs +0 -0
  114. {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/src/linalg.rs +0 -0
  115. {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/src/trop.rs +0 -0
  116. {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/src/weights.rs +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diff-diff
3
- Version: 3.8.0
3
+ Version: 3.9.1
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Intended Audience :: Science/Research
6
6
  Classifier: Operating System :: OS Independent
@@ -20,9 +20,9 @@ Requires-Dist: scipy>=1.10
20
20
  Requires-Dist: pytest>=7.0 ; extra == 'dev'
21
21
  Requires-Dist: pytest-xdist>=3.0 ; extra == 'dev'
22
22
  Requires-Dist: pytest-cov>=4.0 ; extra == 'dev'
23
- Requires-Dist: black==26.3.1 ; extra == 'dev'
24
- Requires-Dist: ruff==0.15.13 ; extra == 'dev'
25
- Requires-Dist: mypy==2.1.0 ; extra == 'dev'
23
+ Requires-Dist: black==26.5.1 ; extra == 'dev'
24
+ Requires-Dist: ruff==0.16.2 ; extra == 'dev'
25
+ Requires-Dist: mypy==2.3.0 ; extra == 'dev'
26
26
  Requires-Dist: maturin>=1.4,<2.0 ; extra == 'dev'
27
27
  Requires-Dist: matplotlib>=3.5 ; extra == 'dev'
28
28
  Requires-Dist: nbmake>=1.5 ; extra == 'dev'
@@ -33,6 +33,7 @@ Requires-Dist: sphinxext-opengraph>=0.9 ; extra == 'docs'
33
33
  Requires-Dist: sphinx-sitemap>=2.5 ; extra == 'docs'
34
34
  Requires-Dist: nbsphinx>=0.9 ; extra == 'docs'
35
35
  Requires-Dist: myst-parser>=2.0 ; extra == 'docs'
36
+ Requires-Dist: sphinx-design>=0.6.1 ; extra == 'docs'
36
37
  Requires-Dist: matplotlib>=3.5 ; extra == 'docs'
37
38
  Requires-Dist: ipython>=8.0 ; extra == 'docs'
38
39
  Requires-Dist: plotly>=5.0 ; extra == 'plotly'
@@ -95,7 +96,7 @@ data = pd.DataFrame({
95
96
  })
96
97
 
97
98
  did = DifferenceInDifferences()
98
- results = did.fit(data, outcome='outcome', treatment='treated', time='post')
99
+ results = did.fit(data, outcome='outcome', treatment='treated', post='post')
99
100
  print(results) # DiDResults(ATT=3.0000, SE=1.7321, p=0.1583)
100
101
  results.print_summary() # full statsmodels-style table
101
102
  ```
@@ -143,9 +144,9 @@ For rigorous DiD analysis, follow these 8 steps. Skipping diagnostic steps produ
143
144
  2. **State identification assumptions** - which parallel trends variant (unconditional, conditional, PT-GT-Nev, PT-GT-NYT), no-anticipation, overlap.
144
145
  3. **Test parallel trends** - simple 2x2: `check_parallel_trends()`, `equivalence_test_trends()`; staggered: inspect CS event-study pre-period coefficients (generic PT tests are invalid for staggered designs). Insignificant pre-trends do NOT prove PT holds.
145
146
  4. **Choose estimator** - staggered adoption -> CS/SA/BJS (NOT plain TWFE); few treated units -> SDiD; factor confounding -> TROP; simple 2x2 -> DiD. Run `BaconDecomposition` to diagnose TWFE bias.
146
- 5. **Estimate** - `estimator.fit(data, ...)`. Always print the cluster count first and choose inference method based on the result (cluster-robust if >= 50 clusters, wild bootstrap if fewer).
147
- 6. **Sensitivity analysis** - `compute_honest_did(results)` for bounds under PT violations (MultiPeriodDiD, CS, or dCDH), `run_all_placebo_tests()` for 2x2 falsification, specification comparisons for staggered designs.
148
- 7. **Heterogeneity** - CS: `aggregate='group'`/`'event_study'`; SA: `results.event_study_effects` / `to_dataframe(level='cohort')`; subgroup re-estimation.
147
+ 5. **Estimate** - `estimator.fit(data, ...)`. Always print the cluster count first and choose inference method based on the result (cluster-robust if >= 50 clusters, wild bootstrap if fewer - for DifferenceInDifferences pass `cluster=`; TwoWayFixedEffects auto-clusters at unit level).
148
+ 6. **Sensitivity analysis** - `compute_honest_did(results)` for bounds under PT violations (MultiPeriodDiD, CS, or dCDH natively; the TwoWayFixedEffects `event_study=True` surface and a StackedDiD `results.aggregate('event_study')` container also admit - Stacked needs `kappa_pre >= 2`), `run_all_placebo_tests()` for 2x2 falsification, specification comparisons for staggered designs.
149
+ 7. **Heterogeneity** - CS: `results.aggregate('group')`/`'event_study'` (post-fit, no refit); SA: `results.event_study_effects` / `to_dataframe(level='cohort')`; Stacked: `results.aggregate('event_study')`/`'simple'` post-fit views (surface always computed since 3.9); EDiD: `results.aggregate(...)` post-fit from retained EIFs (3.9); ImputationDiD/TwoStageDiD: `results.aggregate(...)` post-fit from panel-backed kits (3.9); ContinuousDiD: `results.aggregate('dose'/'simple'/'event_study')` post-fit (3.9; dose/simple are views, event_study recomputes); subgroup re-estimation.
149
150
  8. **Robustness** - compare 2-3 estimators (CS vs SA vs BJS), report with and without covariates (shows whether conditioning drives identification), present pre-trends and sensitivity bounds.
150
151
 
151
152
  Full guide: `diff_diff.get_llm_guide("practitioner")`.
@@ -154,7 +155,7 @@ Full guide: `diff_diff.get_llm_guide("practitioner")`.
154
155
 
155
156
  - [DifferenceInDifferences](https://diff-diff.readthedocs.io/en/stable/api/estimators.html) - basic 2x2 DiD with robust/cluster-robust SEs, wild bootstrap, formula interface, and fixed effects
156
157
  - [TwoWayFixedEffects](https://diff-diff.readthedocs.io/en/stable/api/estimators.html) - panel data DiD with unit and time fixed effects via within-transformation or dummies
157
- - [MultiPeriodDiD](https://diff-diff.readthedocs.io/en/stable/api/estimators.html) - event study design with period-specific treatment effects for dynamic analysis
158
+ - [MultiPeriodDiD](https://diff-diff.readthedocs.io/en/stable/api/estimators.html) - event study design with period-specific treatment effects for dynamic analysis (deprecated 3.9 - use TwoWayFixedEffects `event_study=True`)
158
159
  - [CallawaySantAnna](https://diff-diff.readthedocs.io/en/stable/api/staggered.html) - Callaway & Sant'Anna (2021) group-time ATT estimator for staggered adoption
159
160
  - [ChaisemartinDHaultfoeuille](https://diff-diff.readthedocs.io/en/stable/api/chaisemartin_dhaultfoeuille.html) - de Chaisemartin & D'Haultfœuille (2020/2022) for **reversible (non-absorbing) treatments** with multi-horizon event study, normalized effects, cost-benefit delta, sup-t bands, and dynamic placebos. The most general option for treatments that switch on AND off (see also `LPDiD`/`TROP` `non_absorbing`). Alias `DCDH`.
160
161
  - [SunAbraham](https://diff-diff.readthedocs.io/en/stable/api/staggered.html) - Sun & Abraham (2021) interaction-weighted estimator for heterogeneity-robust event studies
@@ -163,26 +164,29 @@ Full guide: `diff_diff.get_llm_guide("practitioner")`.
163
164
  - [SpilloverDiD](https://diff-diff.readthedocs.io/en/stable/api/spillover.html) - Butts (2021) ring-indicator spillover-aware DiD identifying direct effect on treated + per-ring spillover on near-control units; handles non-staggered and staggered timing; supports survey-design variance under `survey_design=` for HC1 / CR1 (Wave E.1 Binder TSL) and Conley (Wave E.2 panel-aware stratified-Conley sandwich on per-period PSU totals; extended in Wave E.2 follow-up to `conley_lag_cutoff > 0` via panel-block composition with within-PSU serial Bartlett HAC — `lag>0` requires an effective PSU via explicit `survey_design.psu` or injected `cluster=<col>`); `SurveyDesign.subpopulation()` preserves full-design `n_psu` / `df_survey` via zero-padded scores (Wave E.3, R `svyrecvar(subset())` form)
164
165
  - [SyntheticDiD](https://diff-diff.readthedocs.io/en/stable/api/estimators.html) - Synthetic DiD combining standard DiD and synthetic control for few treated units
165
166
  - [SyntheticControl](https://diff-diff.readthedocs.io/en/stable/api/synthetic_control.html) - Abadie, Diamond & Hainmueller (2010) classic synthetic control for a single treated unit (donor-weight counterfactual, nested/cv/inverse-variance/custom V; in-space placebo permutation inference via `in_space_placebo()`, plus ADH-2015 `leave_one_out()` + `in_time_placebo()` robustness, Firpo-Possebom (2018) test-inversion confidence sets, and Chernozhukov-Wüthrich-Zhu (2021) conformal inference)
166
- - [TripleDifference](https://diff-diff.readthedocs.io/en/stable/api/triple_diff.html) - triple difference (DDD) estimator for designs requiring two criteria for treatment eligibility
167
+ - [TripleDifference](https://diff-diff.readthedocs.io/en/stable/api/triple_diff.html) - triple difference (DDD) estimator for designs requiring two criteria for treatment eligibility; serves both the 2x2x2 and the staggered-adoption design from one signature (`fit(..., first_treat=)` selects the staggered engine)
167
168
  - [ContinuousDiD](https://diff-diff.readthedocs.io/en/stable/api/continuous_did.html) - Callaway, Goodman-Bacon & Sant'Anna (2024) continuous treatment DiD with dose-response curves
168
169
  - [HeterogeneousAdoptionDiD](https://diff-diff.readthedocs.io/en/stable/api/had.html) - de Chaisemartin, Ciccia, D'Haultfœuille & Knau (2026) for designs where **no unit remains untreated**; local-linear estimator at the dose support boundary returning Weighted Average Slope (WAS) on Design 1' (`d̲ = 0` / QUG) or `WAS_{d̲}` on Design 1 (`d̲ > 0`, continuous-near-d̲ or mass-point), with a multi-period event-study extension (last-treatment cohort, pointwise CIs). **Panel-only** in this release - repeated cross-sections rejected by the validator. Alias `HAD`.
169
- - [RegressionDiscontinuity](https://diff-diff.readthedocs.io/en/stable/api/regression_discontinuity.html) - Calonico, Cattaneo & Titiunik (2014) sharp, fuzzy, AND covariate-adjusted regression discontinuity with robust bias-corrected inference and rdrobust-parity bandwidth selection (all 10 selectors, mass-point handling; fuzzy via `treatment_col=` with a first-stage block and weak-identification warning; covariates via `covariates=` - CCFT 2019, same estimand, covariate-aware bandwidths). Canonical `att` is the bias-corrected estimate with a coherent robust CI (rdrobust's printed headline is `att_conventional`). Alias `RDD`.
170
+ - [RegressionDiscontinuity](https://diff-diff.readthedocs.io/en/stable/api/regression_discontinuity.html) - Calonico, Cattaneo & Titiunik (2014) sharp, fuzzy, AND covariate-adjusted regression discontinuity with robust bias-corrected inference and rdrobust-parity bandwidth selection (all 10 selectors, mass-point handling; fuzzy via `takeup=` with a first-stage block and weak-identification warning; covariates via `covariates=` - CCFT 2019, same estimand, covariate-aware bandwidths). Canonical `att` is the bias-corrected estimate with a coherent robust CI (rdrobust's printed headline is `att_conventional`). Alias `RDD`.
170
171
  - [StackedDiD](https://diff-diff.readthedocs.io/en/stable/api/stacked_did.html) - Wing, Freedman & Hollingsworth (2024) stacked DiD with Q-weights and sub-experiments; optional covariate balancing (Ustyuzhanin 2026)
171
172
  - [EfficientDiD](https://diff-diff.readthedocs.io/en/stable/api/efficient_did.html) - Chen, Sant'Anna & Xie (2025) efficient DiD with optimal weighting for tighter SEs
172
173
  - [TROP](https://diff-diff.readthedocs.io/en/stable/api/trop.html) - Triply Robust Panel estimator (Athey et al. 2025) with nuclear norm factor adjustment
173
- - [StaggeredTripleDifference](https://diff-diff.readthedocs.io/en/stable/api/staggered.html#staggeredtripledifference) - Ortiz-Villavicencio & Sant'Anna (2025) staggered DDD with group-time ATT
174
+ - [StaggeredTripleDifference](https://diff-diff.readthedocs.io/en/stable/api/staggered.html#staggeredtripledifference) - Ortiz-Villavicencio & Sant'Anna (2025) staggered DDD with group-time ATT (deprecated 3.9 - use `TripleDifference` with `first_treat=`)
174
175
  - [WooldridgeDiD](https://diff-diff.readthedocs.io/en/stable/api/wooldridge_etwfe.html) - Wooldridge (2023, 2025) ETWFE: saturated OLS, logit/Poisson QMLE (ASF-based ATT). Alias `ETWFE`.
175
176
  - [LPDiD](https://diff-diff.readthedocs.io/en/stable/api/lpdid.html) - Dube, Girardi, Jorda & Taylor (2025) Local Projections DiD: per-horizon long-difference event study on clean controls (no negative weighting), variance- or equally-weighted ATT, for absorbing or non-absorbing (reversible) treatment
176
- - [ChangesInChanges](https://diff-diff.readthedocs.io/en/stable/api/changes_in_changes.html) - Athey & Imbens (2006) nonlinear/distributional DiD for the 2x2 design: full counterfactual distribution and quantile treatment effects via CDF transformation, plus the QDiD comparison estimator; bootstrap inference; R qte parity. Alias `CiC`
177
+ - [ChangesInChanges](https://diff-diff.readthedocs.io/en/stable/api/changes_in_changes.html) - Athey & Imbens (2006) nonlinear/distributional DiD for the 2x2 design: full counterfactual distribution and quantile treatment effects via CDF transformation, plus the QDiD comparison estimator via `method="qdid"`; bootstrap inference; R qte parity. Alias `CiC`
177
178
  - [BaconDecomposition](https://diff-diff.readthedocs.io/en/stable/api/bacon.html) - Goodman-Bacon (2021) decomposition for diagnosing TWFE bias in staggered settings
178
179
 
179
180
  ## Diagnostics & Sensitivity
180
181
 
182
+ - [RD Plots](https://diff-diff.readthedocs.io/en/stable/api/regression_discontinuity.html) - Calonico, Cattaneo & Titiunik (2015) optimal data-driven RD plots (`RDPlot`): all 8 rdrobust `binselect` bin selectors, implied-scale/WIMSE-weight reporting, optional matplotlib rendering
183
+ - [Manipulation Testing](https://diff-diff.readthedocs.io/en/stable/api/regression_discontinuity.html) - Cattaneo, Jansson & Ma (2020) density-discontinuity test (`RDDensityTest`): rddensity 3.0 parity, robust bias-corrected inference, unrestricted/restricted models, mass-point adjustment
181
184
  - [Parallel Trends Testing](https://diff-diff.readthedocs.io/en/stable/api/diagnostics.html) - simple and Wasserstein-robust parallel trends tests, equivalence testing (TOST)
182
185
  - [Placebo Tests](https://diff-diff.readthedocs.io/en/stable/api/diagnostics.html) - placebo timing, group, permutation, leave-one-out
183
186
  - [Honest DiD](https://diff-diff.readthedocs.io/en/stable/api/honest_did.html) - Rambachan & Roth (2023) sensitivity analysis: robust CI under PT violations, breakdown values
184
187
  - [Pre-Trends Power Analysis](https://diff-diff.readthedocs.io/en/stable/api/pretrends.html) - Roth (2022) minimum detectable violation and power curves
185
188
  - [Power Analysis](https://diff-diff.readthedocs.io/en/stable/api/power.html) - analytical and simulation-based MDE, sample size, power curves for study design
189
+ - [MMM Calibration Export](https://diff-diff.readthedocs.io/en/stable/api/mmm.html) - convert experiment results into MMM calibration inputs: PyMC-Marketing lift-test frames and Google Meridian lognormal ROI priors
186
190
  - Conley spatial HAC SE (`vcov_type="conley"`) on cross-sectional `LinearRegression` / `compute_robust_vcov` plus panel `DifferenceInDifferences` / `MultiPeriodDiD` / `TwoWayFixedEffects` (with `conley_lag_cutoff` for within-unit Bartlett temporal HAC) - Conley (1999) spatial-correlation-aware SEs with parity vs R `conleyreg` on cross-sectional + panel fixtures, optional combined spatial + cluster product kernel via explicit `cluster=`, auto-activating sparse k-d-tree fast path for `n > 5_000`
187
191
 
188
192
  ## Survey Support
@@ -41,7 +41,7 @@ data = pd.DataFrame({
41
41
  })
42
42
 
43
43
  did = DifferenceInDifferences()
44
- results = did.fit(data, outcome='outcome', treatment='treated', time='post')
44
+ results = did.fit(data, outcome='outcome', treatment='treated', post='post')
45
45
  print(results) # DiDResults(ATT=3.0000, SE=1.7321, p=0.1583)
46
46
  results.print_summary() # full statsmodels-style table
47
47
  ```
@@ -89,9 +89,9 @@ For rigorous DiD analysis, follow these 8 steps. Skipping diagnostic steps produ
89
89
  2. **State identification assumptions** - which parallel trends variant (unconditional, conditional, PT-GT-Nev, PT-GT-NYT), no-anticipation, overlap.
90
90
  3. **Test parallel trends** - simple 2x2: `check_parallel_trends()`, `equivalence_test_trends()`; staggered: inspect CS event-study pre-period coefficients (generic PT tests are invalid for staggered designs). Insignificant pre-trends do NOT prove PT holds.
91
91
  4. **Choose estimator** - staggered adoption -> CS/SA/BJS (NOT plain TWFE); few treated units -> SDiD; factor confounding -> TROP; simple 2x2 -> DiD. Run `BaconDecomposition` to diagnose TWFE bias.
92
- 5. **Estimate** - `estimator.fit(data, ...)`. Always print the cluster count first and choose inference method based on the result (cluster-robust if >= 50 clusters, wild bootstrap if fewer).
93
- 6. **Sensitivity analysis** - `compute_honest_did(results)` for bounds under PT violations (MultiPeriodDiD, CS, or dCDH), `run_all_placebo_tests()` for 2x2 falsification, specification comparisons for staggered designs.
94
- 7. **Heterogeneity** - CS: `aggregate='group'`/`'event_study'`; SA: `results.event_study_effects` / `to_dataframe(level='cohort')`; subgroup re-estimation.
92
+ 5. **Estimate** - `estimator.fit(data, ...)`. Always print the cluster count first and choose inference method based on the result (cluster-robust if >= 50 clusters, wild bootstrap if fewer - for DifferenceInDifferences pass `cluster=`; TwoWayFixedEffects auto-clusters at unit level).
93
+ 6. **Sensitivity analysis** - `compute_honest_did(results)` for bounds under PT violations (MultiPeriodDiD, CS, or dCDH natively; the TwoWayFixedEffects `event_study=True` surface and a StackedDiD `results.aggregate('event_study')` container also admit - Stacked needs `kappa_pre >= 2`), `run_all_placebo_tests()` for 2x2 falsification, specification comparisons for staggered designs.
94
+ 7. **Heterogeneity** - CS: `results.aggregate('group')`/`'event_study'` (post-fit, no refit); SA: `results.event_study_effects` / `to_dataframe(level='cohort')`; Stacked: `results.aggregate('event_study')`/`'simple'` post-fit views (surface always computed since 3.9); EDiD: `results.aggregate(...)` post-fit from retained EIFs (3.9); ImputationDiD/TwoStageDiD: `results.aggregate(...)` post-fit from panel-backed kits (3.9); ContinuousDiD: `results.aggregate('dose'/'simple'/'event_study')` post-fit (3.9; dose/simple are views, event_study recomputes); subgroup re-estimation.
95
95
  8. **Robustness** - compare 2-3 estimators (CS vs SA vs BJS), report with and without covariates (shows whether conditioning drives identification), present pre-trends and sensitivity bounds.
96
96
 
97
97
  Full guide: `diff_diff.get_llm_guide("practitioner")`.
@@ -100,7 +100,7 @@ Full guide: `diff_diff.get_llm_guide("practitioner")`.
100
100
 
101
101
  - [DifferenceInDifferences](https://diff-diff.readthedocs.io/en/stable/api/estimators.html) - basic 2x2 DiD with robust/cluster-robust SEs, wild bootstrap, formula interface, and fixed effects
102
102
  - [TwoWayFixedEffects](https://diff-diff.readthedocs.io/en/stable/api/estimators.html) - panel data DiD with unit and time fixed effects via within-transformation or dummies
103
- - [MultiPeriodDiD](https://diff-diff.readthedocs.io/en/stable/api/estimators.html) - event study design with period-specific treatment effects for dynamic analysis
103
+ - [MultiPeriodDiD](https://diff-diff.readthedocs.io/en/stable/api/estimators.html) - event study design with period-specific treatment effects for dynamic analysis (deprecated 3.9 - use TwoWayFixedEffects `event_study=True`)
104
104
  - [CallawaySantAnna](https://diff-diff.readthedocs.io/en/stable/api/staggered.html) - Callaway & Sant'Anna (2021) group-time ATT estimator for staggered adoption
105
105
  - [ChaisemartinDHaultfoeuille](https://diff-diff.readthedocs.io/en/stable/api/chaisemartin_dhaultfoeuille.html) - de Chaisemartin & D'Haultfœuille (2020/2022) for **reversible (non-absorbing) treatments** with multi-horizon event study, normalized effects, cost-benefit delta, sup-t bands, and dynamic placebos. The most general option for treatments that switch on AND off (see also `LPDiD`/`TROP` `non_absorbing`). Alias `DCDH`.
106
106
  - [SunAbraham](https://diff-diff.readthedocs.io/en/stable/api/staggered.html) - Sun & Abraham (2021) interaction-weighted estimator for heterogeneity-robust event studies
@@ -109,26 +109,29 @@ Full guide: `diff_diff.get_llm_guide("practitioner")`.
109
109
  - [SpilloverDiD](https://diff-diff.readthedocs.io/en/stable/api/spillover.html) - Butts (2021) ring-indicator spillover-aware DiD identifying direct effect on treated + per-ring spillover on near-control units; handles non-staggered and staggered timing; supports survey-design variance under `survey_design=` for HC1 / CR1 (Wave E.1 Binder TSL) and Conley (Wave E.2 panel-aware stratified-Conley sandwich on per-period PSU totals; extended in Wave E.2 follow-up to `conley_lag_cutoff > 0` via panel-block composition with within-PSU serial Bartlett HAC — `lag>0` requires an effective PSU via explicit `survey_design.psu` or injected `cluster=<col>`); `SurveyDesign.subpopulation()` preserves full-design `n_psu` / `df_survey` via zero-padded scores (Wave E.3, R `svyrecvar(subset())` form)
110
110
  - [SyntheticDiD](https://diff-diff.readthedocs.io/en/stable/api/estimators.html) - Synthetic DiD combining standard DiD and synthetic control for few treated units
111
111
  - [SyntheticControl](https://diff-diff.readthedocs.io/en/stable/api/synthetic_control.html) - Abadie, Diamond & Hainmueller (2010) classic synthetic control for a single treated unit (donor-weight counterfactual, nested/cv/inverse-variance/custom V; in-space placebo permutation inference via `in_space_placebo()`, plus ADH-2015 `leave_one_out()` + `in_time_placebo()` robustness, Firpo-Possebom (2018) test-inversion confidence sets, and Chernozhukov-Wüthrich-Zhu (2021) conformal inference)
112
- - [TripleDifference](https://diff-diff.readthedocs.io/en/stable/api/triple_diff.html) - triple difference (DDD) estimator for designs requiring two criteria for treatment eligibility
112
+ - [TripleDifference](https://diff-diff.readthedocs.io/en/stable/api/triple_diff.html) - triple difference (DDD) estimator for designs requiring two criteria for treatment eligibility; serves both the 2x2x2 and the staggered-adoption design from one signature (`fit(..., first_treat=)` selects the staggered engine)
113
113
  - [ContinuousDiD](https://diff-diff.readthedocs.io/en/stable/api/continuous_did.html) - Callaway, Goodman-Bacon & Sant'Anna (2024) continuous treatment DiD with dose-response curves
114
114
  - [HeterogeneousAdoptionDiD](https://diff-diff.readthedocs.io/en/stable/api/had.html) - de Chaisemartin, Ciccia, D'Haultfœuille & Knau (2026) for designs where **no unit remains untreated**; local-linear estimator at the dose support boundary returning Weighted Average Slope (WAS) on Design 1' (`d̲ = 0` / QUG) or `WAS_{d̲}` on Design 1 (`d̲ > 0`, continuous-near-d̲ or mass-point), with a multi-period event-study extension (last-treatment cohort, pointwise CIs). **Panel-only** in this release - repeated cross-sections rejected by the validator. Alias `HAD`.
115
- - [RegressionDiscontinuity](https://diff-diff.readthedocs.io/en/stable/api/regression_discontinuity.html) - Calonico, Cattaneo & Titiunik (2014) sharp, fuzzy, AND covariate-adjusted regression discontinuity with robust bias-corrected inference and rdrobust-parity bandwidth selection (all 10 selectors, mass-point handling; fuzzy via `treatment_col=` with a first-stage block and weak-identification warning; covariates via `covariates=` - CCFT 2019, same estimand, covariate-aware bandwidths). Canonical `att` is the bias-corrected estimate with a coherent robust CI (rdrobust's printed headline is `att_conventional`). Alias `RDD`.
115
+ - [RegressionDiscontinuity](https://diff-diff.readthedocs.io/en/stable/api/regression_discontinuity.html) - Calonico, Cattaneo & Titiunik (2014) sharp, fuzzy, AND covariate-adjusted regression discontinuity with robust bias-corrected inference and rdrobust-parity bandwidth selection (all 10 selectors, mass-point handling; fuzzy via `takeup=` with a first-stage block and weak-identification warning; covariates via `covariates=` - CCFT 2019, same estimand, covariate-aware bandwidths). Canonical `att` is the bias-corrected estimate with a coherent robust CI (rdrobust's printed headline is `att_conventional`). Alias `RDD`.
116
116
  - [StackedDiD](https://diff-diff.readthedocs.io/en/stable/api/stacked_did.html) - Wing, Freedman & Hollingsworth (2024) stacked DiD with Q-weights and sub-experiments; optional covariate balancing (Ustyuzhanin 2026)
117
117
  - [EfficientDiD](https://diff-diff.readthedocs.io/en/stable/api/efficient_did.html) - Chen, Sant'Anna & Xie (2025) efficient DiD with optimal weighting for tighter SEs
118
118
  - [TROP](https://diff-diff.readthedocs.io/en/stable/api/trop.html) - Triply Robust Panel estimator (Athey et al. 2025) with nuclear norm factor adjustment
119
- - [StaggeredTripleDifference](https://diff-diff.readthedocs.io/en/stable/api/staggered.html#staggeredtripledifference) - Ortiz-Villavicencio & Sant'Anna (2025) staggered DDD with group-time ATT
119
+ - [StaggeredTripleDifference](https://diff-diff.readthedocs.io/en/stable/api/staggered.html#staggeredtripledifference) - Ortiz-Villavicencio & Sant'Anna (2025) staggered DDD with group-time ATT (deprecated 3.9 - use `TripleDifference` with `first_treat=`)
120
120
  - [WooldridgeDiD](https://diff-diff.readthedocs.io/en/stable/api/wooldridge_etwfe.html) - Wooldridge (2023, 2025) ETWFE: saturated OLS, logit/Poisson QMLE (ASF-based ATT). Alias `ETWFE`.
121
121
  - [LPDiD](https://diff-diff.readthedocs.io/en/stable/api/lpdid.html) - Dube, Girardi, Jorda & Taylor (2025) Local Projections DiD: per-horizon long-difference event study on clean controls (no negative weighting), variance- or equally-weighted ATT, for absorbing or non-absorbing (reversible) treatment
122
- - [ChangesInChanges](https://diff-diff.readthedocs.io/en/stable/api/changes_in_changes.html) - Athey & Imbens (2006) nonlinear/distributional DiD for the 2x2 design: full counterfactual distribution and quantile treatment effects via CDF transformation, plus the QDiD comparison estimator; bootstrap inference; R qte parity. Alias `CiC`
122
+ - [ChangesInChanges](https://diff-diff.readthedocs.io/en/stable/api/changes_in_changes.html) - Athey & Imbens (2006) nonlinear/distributional DiD for the 2x2 design: full counterfactual distribution and quantile treatment effects via CDF transformation, plus the QDiD comparison estimator via `method="qdid"`; bootstrap inference; R qte parity. Alias `CiC`
123
123
  - [BaconDecomposition](https://diff-diff.readthedocs.io/en/stable/api/bacon.html) - Goodman-Bacon (2021) decomposition for diagnosing TWFE bias in staggered settings
124
124
 
125
125
  ## Diagnostics & Sensitivity
126
126
 
127
+ - [RD Plots](https://diff-diff.readthedocs.io/en/stable/api/regression_discontinuity.html) - Calonico, Cattaneo & Titiunik (2015) optimal data-driven RD plots (`RDPlot`): all 8 rdrobust `binselect` bin selectors, implied-scale/WIMSE-weight reporting, optional matplotlib rendering
128
+ - [Manipulation Testing](https://diff-diff.readthedocs.io/en/stable/api/regression_discontinuity.html) - Cattaneo, Jansson & Ma (2020) density-discontinuity test (`RDDensityTest`): rddensity 3.0 parity, robust bias-corrected inference, unrestricted/restricted models, mass-point adjustment
127
129
  - [Parallel Trends Testing](https://diff-diff.readthedocs.io/en/stable/api/diagnostics.html) - simple and Wasserstein-robust parallel trends tests, equivalence testing (TOST)
128
130
  - [Placebo Tests](https://diff-diff.readthedocs.io/en/stable/api/diagnostics.html) - placebo timing, group, permutation, leave-one-out
129
131
  - [Honest DiD](https://diff-diff.readthedocs.io/en/stable/api/honest_did.html) - Rambachan & Roth (2023) sensitivity analysis: robust CI under PT violations, breakdown values
130
132
  - [Pre-Trends Power Analysis](https://diff-diff.readthedocs.io/en/stable/api/pretrends.html) - Roth (2022) minimum detectable violation and power curves
131
133
  - [Power Analysis](https://diff-diff.readthedocs.io/en/stable/api/power.html) - analytical and simulation-based MDE, sample size, power curves for study design
134
+ - [MMM Calibration Export](https://diff-diff.readthedocs.io/en/stable/api/mmm.html) - convert experiment results into MMM calibration inputs: PyMC-Marketing lift-test frames and Google Meridian lognormal ROI priors
132
135
  - Conley spatial HAC SE (`vcov_type="conley"`) on cross-sectional `LinearRegression` / `compute_robust_vcov` plus panel `DifferenceInDifferences` / `MultiPeriodDiD` / `TwoWayFixedEffects` (with `conley_lag_cutoff` for within-unit Bartlett temporal HAC) - Conley (1999) spatial-correlation-aware SEs with parity vs R `conleyreg` on cross-sectional + panel fixtures, optional combined spatial + cluster product kernel via explicit `cluster=`, auto-activating sparse k-d-tree fast path for `n > 5_000`
133
136
 
134
137
  ## Survey Support
@@ -20,6 +20,9 @@ This library provides sklearn-like estimators for causal inference using
20
20
  the difference-in-differences methodology.
21
21
  """
22
22
 
23
+ import warnings as _warnings
24
+ from typing import Any as _Any
25
+
23
26
  # Import backend detection from dedicated module (avoids circular imports)
24
27
  from diff_diff._backend import (
25
28
  HAS_RUST_BACKEND,
@@ -30,6 +33,9 @@ from diff_diff._backend import (
30
33
  )
31
34
  from diff_diff._guides_api import get_llm_guide
32
35
  from diff_diff.agent_workflow import agent_workflow
36
+ from diff_diff.aggregation import (
37
+ AggregationResult,
38
+ )
33
39
  from diff_diff.bacon import (
34
40
  BaconDecomposition,
35
41
  BaconDecompositionResults,
@@ -154,6 +160,12 @@ from diff_diff.local_linear import (
154
160
  )
155
161
  from diff_diff.lpdid import LPDiD
156
162
  from diff_diff.lpdid_results import LPDiDResults
163
+ from diff_diff.mmm import (
164
+ MeridianROIPrior,
165
+ meridian_calibration_mask,
166
+ to_meridian_roi_prior,
167
+ to_pymc_marketing_lift_test,
168
+ )
157
169
  from diff_diff.power import (
158
170
  PowerAnalysis,
159
171
  PowerResults,
@@ -212,6 +224,14 @@ from diff_diff.rdd import (
212
224
  RegressionDiscontinuity,
213
225
  RegressionDiscontinuityResults,
214
226
  )
227
+ from diff_diff.rddensity import (
228
+ RDDensityTest,
229
+ RDDensityTestResult,
230
+ )
231
+ from diff_diff.rdplot import (
232
+ RDPlot,
233
+ RDPlotResult,
234
+ )
215
235
  from diff_diff.results import (
216
236
  DiDResults,
217
237
  MultiPeriodDiDResults,
@@ -219,6 +239,11 @@ from diff_diff.results import (
219
239
  SpilloverDiDResults, # re-export
220
240
  SyntheticDiDResults,
221
241
  )
242
+ from diff_diff.results_base import (
243
+ BaseResults,
244
+ Diagnostic,
245
+ EventStudyResults,
246
+ )
222
247
  from diff_diff.spillover import (
223
248
  SpilloverDiD,
224
249
  )
@@ -301,13 +326,10 @@ TWFE = TwoWayFixedEffects
301
326
  EventStudy = MultiPeriodDiD
302
327
  SDiD = SyntheticDiD
303
328
  CS = CallawaySantAnna
304
- CDiD = ContinuousDiD
305
329
  SA = SunAbraham
306
330
  BJS = ImputationDiD
307
- Gardner = TwoStageDiD
308
331
  DDD = TripleDifference
309
332
  SDDD = StaggeredTripleDifference
310
- Stacked = StackedDiD
311
333
  Bacon = BaconDecomposition
312
334
  EDiD = EfficientDiD
313
335
  ETWFE = WooldridgeDiD
@@ -315,8 +337,36 @@ DCDH = ChaisemartinDHaultfoeuille
315
337
  HAD = HeterogeneousAdoptionDiD
316
338
  CiC = ChangesInChanges
317
339
  RDD = RegressionDiscontinuity
340
+ SCM = SyntheticControl
341
+
342
+ # Alias diet (rows M-132..M-134, mechanism M-135): CDiD / Gardner /
343
+ # Stacked are deprecated in 3.9 and removed in 4.0. They deliberately
344
+ # do NOT live in module globals — dir()/vars() no longer list them —
345
+ # but stay importable (and in __all__) through 3.9, served by the PEP
346
+ # 562 module __getattr__ below, which emits the FutureWarning naming
347
+ # the surviving class.
348
+ _DEPRECATED_ALIASES = {
349
+ "CDiD": "ContinuousDiD",
350
+ "Gardner": "TwoStageDiD",
351
+ "Stacked": "StackedDiD",
352
+ }
353
+
354
+
355
+ def __getattr__(name: str) -> _Any:
356
+ """PEP 562 warning shim for the dieted aliases (row M-135)."""
357
+ target = _DEPRECATED_ALIASES.get(name)
358
+ if target is not None:
359
+ _warnings.warn(
360
+ f"diff_diff.{name} is deprecated and will be removed in 4.0; "
361
+ f"use diff_diff.{target}.",
362
+ FutureWarning,
363
+ stacklevel=2,
364
+ )
365
+ return globals()[target]
366
+ raise AttributeError(f"module 'diff_diff' has no attribute {name!r}")
367
+
318
368
 
319
- __version__ = "3.8.0"
369
+ __version__ = "3.9.1"
320
370
  __all__ = [
321
371
  # Estimators
322
372
  "DifferenceInDifferences",
@@ -336,7 +386,9 @@ __all__ = [
336
386
  "TROP",
337
387
  "SyntheticControl",
338
388
  "StackedDiD",
339
- # Estimator aliases (short names)
389
+ # Estimator aliases (short names). CDiD / Gardner / Stacked are
390
+ # deprecated (M-132..M-134): still importable through 3.9 via the
391
+ # module __getattr__ (M-135), gone from module globals/dir().
340
392
  "DiD",
341
393
  "TWFE",
342
394
  "EventStudy",
@@ -350,6 +402,7 @@ __all__ = [
350
402
  "Gardner",
351
403
  "DDD",
352
404
  "SDDD",
405
+ "SCM",
353
406
  "Stacked",
354
407
  "Bacon",
355
408
  # Bacon Decomposition
@@ -519,6 +572,12 @@ __all__ = [
519
572
  "RegressionDiscontinuity",
520
573
  "RegressionDiscontinuityResults",
521
574
  "RDD",
575
+ # RDPlot (data-driven RD plots, rdplot parity)
576
+ "RDPlot",
577
+ "RDPlotResult",
578
+ # RDDensityTest (manipulation testing, rddensity parity)
579
+ "RDDensityTest",
580
+ "RDDensityTestResult",
522
581
  # HeterogeneousAdoptionDiD pre-test diagnostics (Phase 3)
523
582
  "qug_test",
524
583
  "stute_test",
@@ -529,7 +588,7 @@ __all__ = [
529
588
  "YatchewTestResults",
530
589
  "HADPretestReport",
531
590
  # HAD joint pre-tests (Phase 3 follow-up) — multi-period event-study
532
- # workflow dispatch via did_had_pretest_workflow(aggregate="event_study")
591
+ # workflow dispatch selected from the panel shape (M-139)
533
592
  "stute_joint_pretest",
534
593
  "joint_pretrends_test",
535
594
  "joint_homogeneity_test",
@@ -559,8 +618,18 @@ __all__ = [
559
618
  "Alert",
560
619
  "OutcomeShape",
561
620
  "TreatmentDoseShape",
621
+ # MMM calibration export (interop)
622
+ "to_pymc_marketing_lift_test",
623
+ "to_meridian_roi_prior",
624
+ "meridian_calibration_mask",
625
+ "MeridianROIPrior",
562
626
  # LLM guide accessor
563
627
  "get_llm_guide",
628
+ # Results-contract foundations (4.0 program Phase 2, spec sections 3.5/5)
629
+ "BaseResults",
630
+ "Diagnostic",
631
+ "EventStudyResults",
632
+ "AggregationResult",
564
633
  ]
565
634
 
566
635
  # Agent-facing entrypoints surface first in dir(diff_diff). LLM agents
@@ -0,0 +1,169 @@
1
+ """Shared estimator parameter surface (4.0 program, Phase 2 PR 2(c)-i).
2
+
3
+ This module is a deliberate LEAF: it imports only the standard library, never
4
+ other ``diff_diff`` modules, so every estimator module can import it without
5
+ cycles (the ``results_base.py`` precedent).
6
+
7
+ One public symbol:
8
+
9
+ ``BaseEstimator``
10
+ Mixin providing the sklearn-compatible ``get_params`` / ``set_params``
11
+ pair for every estimator class, replacing the 25 hand-rolled copies
12
+ (see ``docs/v4-design.md`` section 7, "Constructor hygiene").
13
+
14
+ Design contract (the locked transactional rule):
15
+
16
+ - Parameter names are introspected from the concrete class's ``__init__``
17
+ signature, so the parameter surface can never drift from the constructor.
18
+ - ``get_params`` returns ``{name: getattr(self, alias_or_name)}`` for every
19
+ constructor parameter. ``deep`` is accepted for sklearn compatibility
20
+ (``sklearn.base.clone`` calls ``get_params(deep=False)``) and ignored:
21
+ no diff-diff estimator nests another estimator.
22
+ - ``set_params`` is TRANSACTIONAL via probe re-init: it validates the merged
23
+ configuration by constructing a throwaway ``type(self)(**merged)`` - which
24
+ raises before ``self`` is touched - then adopts the probe's parameter
25
+ attributes plus each class's declared derived-config attributes. Fitted
26
+ state (attributes ``fit()`` sets) is never touched, because only declared
27
+ attributes are copied. The behavioral contract, stated post-normalization:
28
+
29
+ est.set_params(**p) == config state of
30
+ type(est)(**{**est.get_params(), **cls._normalize_set_params(p)})
31
+
32
+ Validation is therefore exactly ``__init__``'s validation, eagerly, and
33
+ can never drift from it.
34
+
35
+ Per-class accommodation hooks (declarative; default no-op):
36
+
37
+ ``_PARAM_ATTR_ALIASES``
38
+ Maps a constructor parameter name to the attribute that stores its RAW
39
+ value when the two differ (e.g. ``DifferenceInDifferences`` stores the
40
+ raw ``vcov_type`` argument under ``_vcov_type_arg`` and the resolved
41
+ value under ``vcov_type``).
42
+ ``_DERIVED_CONFIG_ATTRS``
43
+ Extra attributes ``__init__`` computes FROM the parameters that must be
44
+ re-adopted from the probe after a successful ``set_params`` (e.g.
45
+ ``_vcov_type_explicit``).
46
+ ``_normalize_set_params``
47
+ Classmethod rewriting the user-supplied ``params`` dict before the
48
+ merge (e.g. the ``robust``-alone alias re-derivation on
49
+ ``DifferenceInDifferences``).
50
+ """
51
+
52
+ import inspect
53
+ from typing import Any, ClassVar, Dict, Mapping, Tuple, TypeVar
54
+
55
+ __all__ = ["BaseEstimator"]
56
+
57
+ TSelf = TypeVar("TSelf", bound="BaseEstimator")
58
+
59
+ # Signature kinds accepted as constructor parameters. VAR_POSITIONAL /
60
+ # VAR_KEYWORD are rejected loudly: an estimator constructor forwarding
61
+ # through *args/**kwargs has no introspectable parameter surface.
62
+ _ACCEPTED_KINDS = (
63
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
64
+ inspect.Parameter.KEYWORD_ONLY,
65
+ )
66
+
67
+
68
+ class BaseEstimator:
69
+ """Mixin providing sklearn-compatible ``get_params`` / ``set_params``.
70
+
71
+ See the module docstring for the full contract. Subclasses only declare
72
+ the accommodation hooks when their constructor stores state under
73
+ different names than its parameters; most estimators are plain drop-ins.
74
+ """
75
+
76
+ _PARAM_ATTR_ALIASES: ClassVar[Mapping[str, str]] = {}
77
+ _DERIVED_CONFIG_ATTRS: ClassVar[Tuple[str, ...]] = ()
78
+
79
+ @classmethod
80
+ def _param_names(cls) -> Tuple[str, ...]:
81
+ """Constructor parameter names, introspected once per class.
82
+
83
+ Cached via ``cls.__dict__`` (never a plain class attribute, which a
84
+ subclass with its own ``__init__`` would inherit; never an instance
85
+ attribute, which would break ``vars(est)`` stability across fit).
86
+ """
87
+ cached = cls.__dict__.get("_param_names_cache")
88
+ if cached is not None:
89
+ return cached
90
+ names = []
91
+ for name, param in inspect.signature(cls.__init__).parameters.items():
92
+ if name == "self":
93
+ continue
94
+ if param.kind not in _ACCEPTED_KINDS:
95
+ raise TypeError(
96
+ f"{cls.__name__}.__init__ uses *args/**kwargs "
97
+ f"(parameter {name!r}); its parameter surface cannot be "
98
+ "introspected for get_params/set_params."
99
+ )
100
+ names.append(name)
101
+ result: Tuple[str, ...] = tuple(names)
102
+ setattr(cls, "_param_names_cache", result)
103
+ return result
104
+
105
+ @classmethod
106
+ def _normalize_set_params(cls, params: Dict[str, Any]) -> Dict[str, Any]:
107
+ """Rewrite the user-supplied params before the merge (default no-op)."""
108
+ return params
109
+
110
+ def get_params(self, deep: bool = True) -> Dict[str, Any]:
111
+ """
112
+ Get estimator parameters (sklearn-compatible).
113
+
114
+ Parameters
115
+ ----------
116
+ deep : bool, default True
117
+ Accepted for sklearn compatibility (``sklearn.base.clone``
118
+ calls ``get_params(deep=False)``) and ignored: no diff-diff
119
+ estimator nests another estimator.
120
+
121
+ Returns
122
+ -------
123
+ Dict[str, Any]
124
+ Estimator parameters suitable for passing to ``__init__``.
125
+ Keys follow the ``__init__`` signature order. Where a class
126
+ stores a parameter's raw value under a different attribute
127
+ (``_PARAM_ATTR_ALIASES``), the raw value is returned so that
128
+ ``type(est)(**est.get_params())`` reconstructs the same
129
+ configuration.
130
+ """
131
+ del deep
132
+ aliases = self._PARAM_ATTR_ALIASES
133
+ return {name: getattr(self, aliases.get(name, name)) for name in self._param_names()}
134
+
135
+ def set_params(self: TSelf, **params: Any) -> TSelf:
136
+ """
137
+ Set estimator parameters (sklearn-compatible, transactional).
138
+
139
+ Validates the merged configuration by constructing a throwaway
140
+ instance of ``type(self)`` - so a rejected call raises the same
141
+ error ``__init__`` would and leaves this estimator unchanged - then
142
+ adopts the probe's parameter attributes and the class's declared
143
+ derived-config attributes. Fitted state is never touched.
144
+
145
+ Parameters
146
+ ----------
147
+ **params
148
+ Estimator parameters. Unknown names raise ``ValueError``
149
+ before any validation or mutation.
150
+
151
+ Returns
152
+ -------
153
+ self
154
+ """
155
+ names = self._param_names()
156
+ for key in params:
157
+ if key not in names:
158
+ raise ValueError(f"Unknown parameter: {key}")
159
+ cls = type(self)
160
+ normalized = cls._normalize_set_params(dict(params))
161
+ merged = {**self.get_params(), **normalized}
162
+ probe = cls(**merged)
163
+ aliases = self._PARAM_ATTR_ALIASES
164
+ for name in names:
165
+ attr = aliases.get(name, name)
166
+ setattr(self, attr, getattr(probe, attr))
167
+ for attr in self._DERIVED_CONFIG_ATTRS:
168
+ setattr(self, attr, getattr(probe, attr))
169
+ return self