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.
- {diff_diff-3.8.0 → diff_diff-3.9.1}/PKG-INFO +17 -13
- {diff_diff-3.8.0 → diff_diff-3.9.1}/README.md +12 -9
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/__init__.py +75 -6
- diff_diff-3.9.1/diff_diff/_base.py +169 -0
- diff_diff-3.9.1/diff_diff/_deprecation.py +164 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/_reporting_helpers.py +55 -23
- diff_diff-3.9.1/diff_diff/_staggered_triple_diff_engine.py +1704 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/agent_workflow.py +4 -3
- diff_diff-3.9.1/diff_diff/aggregation.py +709 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/bacon.py +46 -15
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/bootstrap_utils.py +2 -2
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/business_report.py +85 -34
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/chaisemartin_dhaultfoeuille.py +174 -87
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/chaisemartin_dhaultfoeuille_results.py +172 -9
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/changes_in_changes.py +119 -87
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/changes_in_changes_results.py +35 -7
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/continuous_did.py +244 -204
- diff_diff-3.9.1/diff_diff/continuous_did_aggregation.py +213 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/continuous_did_results.py +260 -3
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/datasets.py +812 -173
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/diagnostic_report.py +752 -141
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/diagnostics.py +38 -20
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/efficient_did.py +201 -506
- diff_diff-3.9.1/diff_diff/efficient_did_aggregation.py +521 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/efficient_did_results.py +315 -3
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/estimators.py +712 -385
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/guides/llms-autonomous.txt +50 -26
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/guides/llms-full.txt +566 -165
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/guides/llms-practitioner.txt +117 -57
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/guides/llms.txt +23 -13
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/had.py +320 -161
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/had_pretests.py +188 -78
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/honest_did.py +768 -64
- diff_diff-3.9.1/diff_diff/imputation.py +1493 -0
- diff_diff-3.8.0/diff_diff/imputation.py → diff_diff-3.9.1/diff_diff/imputation_aggregation.py +279 -1319
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/imputation_bootstrap.py +1 -33
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/imputation_results.py +347 -4
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/linalg.py +284 -37
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/lpdid.py +128 -38
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/lpdid_results.py +51 -3
- diff_diff-3.9.1/diff_diff/mmm.py +1600 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/power.py +127 -32
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/practitioner.py +369 -117
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/prep.py +19 -3
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/prep_dgp.py +10 -9
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/pretrends.py +633 -41
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/profile.py +4 -4
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/rdd.py +89 -55
- diff_diff-3.9.1/diff_diff/rddensity.py +1417 -0
- diff_diff-3.9.1/diff_diff/rdplot.py +1141 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/results.py +14 -3
- diff_diff-3.9.1/diff_diff/results_base.py +1363 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/spillover.py +19 -46
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/stacked_did.py +345 -138
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/stacked_did_results.py +204 -7
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/staggered.py +280 -68
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/staggered_aggregation.py +160 -27
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/staggered_bootstrap.py +23 -6
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/staggered_results.py +396 -4
- diff_diff-3.9.1/diff_diff/staggered_triple_diff.py +269 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/staggered_triple_diff_results.py +2 -1
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/sun_abraham.py +300 -55
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/survey.py +47 -22
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/synthetic_control.py +14 -39
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/synthetic_control_results.py +2 -1
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/synthetic_did.py +25 -105
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/triple_diff.py +433 -84
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/trop.py +14 -29
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/trop_results.py +2 -1
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/twfe.py +448 -24
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/two_stage.py +330 -1409
- diff_diff-3.9.1/diff_diff/two_stage_aggregation.py +1564 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/two_stage_bootstrap.py +16 -76
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/two_stage_results.py +346 -4
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/utils.py +502 -13
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/visualization/_diagnostic.py +10 -8
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/visualization/_event_study.py +410 -55
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/visualization/_power.py +5 -4
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/wooldridge.py +1280 -213
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/wooldridge_results.py +240 -44
- {diff_diff-3.8.0 → diff_diff-3.9.1}/pyproject.toml +32 -5
- {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/Cargo.lock +59 -48
- {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/Cargo.toml +1 -1
- diff_diff-3.8.0/diff_diff/staggered_triple_diff.py +0 -1680
- {diff_diff-3.8.0 → diff_diff-3.9.1}/LICENSE +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/_backend.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/_guides_api.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/_nprobust_port.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/_rdrobust_port.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/balancing.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/bootstrap_chunking.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/chaisemartin_dhaultfoeuille_bootstrap.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/conformal.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/conley.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/continuous_did_bspline.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/efficient_did_bootstrap.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/efficient_did_covariates.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/efficient_did_weights.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/guides/__init__.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/local_linear.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/trop_global.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/trop_local.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/visualization/__init__.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/visualization/_common.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/visualization/_continuous.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/visualization/_staggered.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/diff_diff/visualization/_synthetic.py +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/build.rs +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/src/alloc_profile.rs +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/src/batched_solve.rs +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/src/bootstrap.rs +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/src/demean.rs +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/src/lib.rs +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/src/linalg.rs +0 -0
- {diff_diff-3.8.0 → diff_diff-3.9.1}/rust/src/trop.rs +0 -0
- {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.
|
|
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.
|
|
24
|
-
Requires-Dist: ruff==0.
|
|
25
|
-
Requires-Dist: mypy==2.
|
|
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',
|
|
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
|
|
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 `
|
|
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
|
|
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',
|
|
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
|
|
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 `
|
|
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
|
|
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.
|
|
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
|
|
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
|