diff-diff 3.4.1__tar.gz → 3.5.0__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.4.1 → diff_diff-3.5.0}/PKG-INFO +3 -2
- {diff_diff-3.4.1 → diff_diff-3.5.0}/README.md +2 -1
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/__init__.py +9 -1
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/_reporting_helpers.py +17 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/business_report.py +62 -14
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/chaisemartin_dhaultfoeuille.py +11 -6
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/chaisemartin_dhaultfoeuille_results.py +8 -3
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/conley.py +79 -24
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/diagnostic_report.py +225 -23
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/efficient_did.py +105 -8
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/efficient_did_bootstrap.py +92 -2
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/efficient_did_results.py +93 -7
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/estimators.py +4 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/guides/llms-full.txt +128 -7
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/guides/llms.txt +3 -2
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/honest_did.py +115 -52
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/imputation.py +127 -1
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/imputation_bootstrap.py +75 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/imputation_results.py +90 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/linalg.py +304 -59
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/power.py +133 -44
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/practitioner.py +70 -1
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/results.py +10 -1
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/spillover.py +299 -89
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/stacked_did.py +283 -22
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/stacked_did_results.py +29 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/staggered.py +486 -32
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/staggered_aggregation.py +81 -15
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/staggered_bootstrap.py +101 -2
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/staggered_results.py +49 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/staggered_triple_diff.py +125 -34
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/staggered_triple_diff_results.py +47 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/sun_abraham.py +119 -18
- diff_diff-3.5.0/diff_diff/synthetic_control.py +1312 -0
- diff_diff-3.5.0/diff_diff/synthetic_control_results.py +729 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/triple_diff.py +234 -58
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/two_stage.py +425 -88
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/two_stage_bootstrap.py +102 -6
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/two_stage_results.py +91 -0
- diff_diff-3.5.0/diff_diff/wooldridge.py +2172 -0
- diff_diff-3.5.0/diff_diff/wooldridge_results.py +871 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/pyproject.toml +1 -1
- {diff_diff-3.4.1 → diff_diff-3.5.0}/rust/Cargo.lock +21 -21
- {diff_diff-3.4.1 → diff_diff-3.5.0}/rust/Cargo.toml +1 -1
- {diff_diff-3.4.1 → diff_diff-3.5.0}/rust/src/linalg.rs +37 -5
- diff_diff-3.4.1/diff_diff/wooldridge.py +0 -1305
- diff_diff-3.4.1/diff_diff/wooldridge_results.py +0 -373
- {diff_diff-3.4.1 → diff_diff-3.5.0}/LICENSE +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/_backend.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/_guides_api.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/_nprobust_port.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/agent_workflow.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/bacon.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/bootstrap_utils.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/chaisemartin_dhaultfoeuille_bootstrap.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/continuous_did.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/continuous_did_bspline.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/continuous_did_results.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/datasets.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/diagnostics.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/efficient_did_covariates.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/efficient_did_weights.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/guides/__init__.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/guides/llms-autonomous.txt +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/guides/llms-practitioner.txt +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/had.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/had_pretests.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/local_linear.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/prep.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/prep_dgp.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/pretrends.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/profile.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/survey.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/synthetic_did.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/trop.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/trop_global.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/trop_local.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/trop_results.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/twfe.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/utils.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/visualization/__init__.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/visualization/_common.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/visualization/_continuous.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/visualization/_diagnostic.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/visualization/_event_study.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/visualization/_power.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/visualization/_staggered.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/diff_diff/visualization/_synthetic.py +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/rust/build.rs +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/rust/src/bootstrap.rs +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/rust/src/lib.rs +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/rust/src/trop.rs +0 -0
- {diff_diff-3.4.1 → diff_diff-3.5.0}/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.5.0
|
|
4
4
|
Classifier: Development Status :: 5 - Production/Stable
|
|
5
5
|
Classifier: Intended Audience :: Science/Research
|
|
6
6
|
Classifier: Operating System :: OS Independent
|
|
@@ -159,8 +159,9 @@ Full guide: `diff_diff.get_llm_guide("practitioner")`.
|
|
|
159
159
|
- [SunAbraham](https://diff-diff.readthedocs.io/en/stable/api/staggered.html) - Sun & Abraham (2021) interaction-weighted estimator for heterogeneity-robust event studies
|
|
160
160
|
- [ImputationDiD](https://diff-diff.readthedocs.io/en/stable/api/imputation.html) - Borusyak, Jaravel & Spiess (2024) imputation estimator, most efficient under homogeneous effects
|
|
161
161
|
- [TwoStageDiD](https://diff-diff.readthedocs.io/en/stable/api/two_stage.html) - Gardner (2022) two-stage estimator with GMM sandwich variance
|
|
162
|
-
- [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>`)
|
|
162
|
+
- [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)
|
|
163
163
|
- [SyntheticDiD](https://diff-diff.readthedocs.io/en/stable/api/estimators.html) - Synthetic DiD combining standard DiD and synthetic control for few treated units
|
|
164
|
+
- [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/custom V; in-space placebo permutation inference via `in_space_placebo()`)
|
|
164
165
|
- [TripleDifference](https://diff-diff.readthedocs.io/en/stable/api/triple_diff.html) - triple difference (DDD) estimator for designs requiring two criteria for treatment eligibility
|
|
165
166
|
- [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
|
|
166
167
|
- [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`.
|
|
@@ -106,8 +106,9 @@ Full guide: `diff_diff.get_llm_guide("practitioner")`.
|
|
|
106
106
|
- [SunAbraham](https://diff-diff.readthedocs.io/en/stable/api/staggered.html) - Sun & Abraham (2021) interaction-weighted estimator for heterogeneity-robust event studies
|
|
107
107
|
- [ImputationDiD](https://diff-diff.readthedocs.io/en/stable/api/imputation.html) - Borusyak, Jaravel & Spiess (2024) imputation estimator, most efficient under homogeneous effects
|
|
108
108
|
- [TwoStageDiD](https://diff-diff.readthedocs.io/en/stable/api/two_stage.html) - Gardner (2022) two-stage estimator with GMM sandwich variance
|
|
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>`)
|
|
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
|
+
- [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/custom V; in-space placebo permutation inference via `in_space_placebo()`)
|
|
111
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
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
|
|
113
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`.
|
|
@@ -222,6 +222,11 @@ from diff_diff.trop import (
|
|
|
222
222
|
TROPResults,
|
|
223
223
|
trop,
|
|
224
224
|
)
|
|
225
|
+
from diff_diff.synthetic_control import (
|
|
226
|
+
SyntheticControl,
|
|
227
|
+
synthetic_control,
|
|
228
|
+
)
|
|
229
|
+
from diff_diff.synthetic_control_results import SyntheticControlResults
|
|
225
230
|
from diff_diff.wooldridge import WooldridgeDiD
|
|
226
231
|
from diff_diff.wooldridge_results import WooldridgeDiDResults
|
|
227
232
|
from diff_diff.utils import (
|
|
@@ -293,7 +298,7 @@ ETWFE = WooldridgeDiD
|
|
|
293
298
|
DCDH = ChaisemartinDHaultfoeuille
|
|
294
299
|
HAD = HeterogeneousAdoptionDiD
|
|
295
300
|
|
|
296
|
-
__version__ = "3.
|
|
301
|
+
__version__ = "3.5.0"
|
|
297
302
|
__all__ = [
|
|
298
303
|
# Estimators
|
|
299
304
|
"DifferenceInDifferences",
|
|
@@ -309,6 +314,7 @@ __all__ = [
|
|
|
309
314
|
"SpilloverDiD",
|
|
310
315
|
"TripleDifference",
|
|
311
316
|
"TROP",
|
|
317
|
+
"SyntheticControl",
|
|
312
318
|
"StackedDiD",
|
|
313
319
|
# Estimator aliases (short names)
|
|
314
320
|
"DiD",
|
|
@@ -355,6 +361,8 @@ __all__ = [
|
|
|
355
361
|
"StaggeredTripleDiffResults",
|
|
356
362
|
"TROPResults",
|
|
357
363
|
"trop",
|
|
364
|
+
"SyntheticControlResults",
|
|
365
|
+
"synthetic_control",
|
|
358
366
|
"StackedDiDResults",
|
|
359
367
|
"stacked_did",
|
|
360
368
|
# EfficientDiD
|
|
@@ -618,6 +618,23 @@ def describe_target_parameter(results: Any) -> Dict[str, Any]:
|
|
|
618
618
|
"reference": "REGISTRY.md Sec. TROP",
|
|
619
619
|
}
|
|
620
620
|
|
|
621
|
+
if name == "SyntheticControlResults":
|
|
622
|
+
return {
|
|
623
|
+
"name": "SCM ATT (mean post-treatment gap for the single treated unit)",
|
|
624
|
+
"definition": (
|
|
625
|
+
"The average over the post-treatment periods of the gap "
|
|
626
|
+
"``alpha_hat_{1t} = Y_{1t} - sum_j w_j Y_{jt}`` between the single "
|
|
627
|
+
"treated unit and its donor-weighted synthetic control (Abadie, "
|
|
628
|
+
"Diamond & Hainmueller 2010). There is no population-averaging or "
|
|
629
|
+
"sampling estimand — it is the effect on the one treated unit; "
|
|
630
|
+
"significance is assessed by in-space placebo permutation inference "
|
|
631
|
+
"(no analytical standard error)."
|
|
632
|
+
),
|
|
633
|
+
"aggregation": "single_unit_gap",
|
|
634
|
+
"headline_attribute": "att",
|
|
635
|
+
"reference": "REGISTRY.md Sec. SyntheticControl",
|
|
636
|
+
}
|
|
637
|
+
|
|
621
638
|
# Default: unrecognized result class. Fall through with a neutral
|
|
622
639
|
# block — agents / downstream consumers can still dispatch on
|
|
623
640
|
# ``aggregation="unknown"`` and fall back to generic ATT narration.
|
|
@@ -203,6 +203,7 @@ class BusinessReport:
|
|
|
203
203
|
if honest_did_results is not None and type(results).__name__ in {
|
|
204
204
|
"SyntheticDiDResults",
|
|
205
205
|
"TROPResults",
|
|
206
|
+
"SyntheticControlResults",
|
|
206
207
|
}:
|
|
207
208
|
raise ValueError(
|
|
208
209
|
f"{type(results).__name__} routes robustness to "
|
|
@@ -213,8 +214,9 @@ class BusinessReport:
|
|
|
213
214
|
"object's native diagnostics "
|
|
214
215
|
"(SDiD: ``in_time_placebo()``, ``sensitivity_to_zeta_omega()``, "
|
|
215
216
|
"``pre_treatment_fit``; TROP: ``effective_rank``, "
|
|
216
|
-
"``loocv_score``)
|
|
217
|
-
"
|
|
217
|
+
"``loocv_score``; SyntheticControl: ``in_space_placebo()``, "
|
|
218
|
+
"``pre_rmspe``, ``get_placebo_df()``) — BusinessReport surfaces "
|
|
219
|
+
"these automatically under ``estimator_native_diagnostics``."
|
|
218
220
|
)
|
|
219
221
|
|
|
220
222
|
# Round-44 P1 CI review on PR #318: mirror the SDiD/TROP
|
|
@@ -646,10 +648,13 @@ class BusinessReport:
|
|
|
646
648
|
if att is None or not np.isfinite(att):
|
|
647
649
|
sign = "undefined"
|
|
648
650
|
ci_level = int(round((1.0 - display_alpha) * 100))
|
|
649
|
-
|
|
651
|
+
# bool(...) coerces away numpy bool_ — when ``p`` is a numpy NaN (e.g.
|
|
652
|
+
# SyntheticControl, whose analytical p_value is always NaN), ``np.isfinite``
|
|
653
|
+
# yields a numpy bool that is NOT JSON-serializable in the schema.
|
|
654
|
+
is_significant = bool(
|
|
650
655
|
p is not None and np.isfinite(p) and p < phrasing_alpha if p is not None else False
|
|
651
656
|
)
|
|
652
|
-
near_threshold = (
|
|
657
|
+
near_threshold = bool(
|
|
653
658
|
p is not None
|
|
654
659
|
and np.isfinite(p)
|
|
655
660
|
and (phrasing_alpha - 0.01) < p < (phrasing_alpha + 0.001)
|
|
@@ -1002,16 +1007,25 @@ def _lift_robustness(dr: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
|
|
1002
1007
|
return {"status": "skipped", "reason": "auto_diagnostics=False"}
|
|
1003
1008
|
bacon = dr.get("bacon") or {}
|
|
1004
1009
|
native = dr.get("estimator_native_diagnostics") or {}
|
|
1010
|
+
native_block = {
|
|
1011
|
+
"status": native.get("status"),
|
|
1012
|
+
"estimator": native.get("estimator"),
|
|
1013
|
+
"pre_treatment_fit": native.get("pre_treatment_fit"),
|
|
1014
|
+
}
|
|
1015
|
+
# Classic SCM exposes pre_rmspe + donor-weight concentration + the (opt-in)
|
|
1016
|
+
# in-space placebo rather than SDiD's pre_treatment_fit; surface those so the
|
|
1017
|
+
# top-level robustness block is not empty for SyntheticControl.
|
|
1018
|
+
if native.get("estimator") == "SyntheticControl":
|
|
1019
|
+
native_block["pre_rmspe"] = native.get("pre_rmspe")
|
|
1020
|
+
native_block["weight_concentration"] = native.get("weight_concentration")
|
|
1021
|
+
native_block["in_space_placebo"] = native.get("in_space_placebo")
|
|
1005
1022
|
return {
|
|
1006
1023
|
"bacon": {
|
|
1007
1024
|
"status": bacon.get("status"),
|
|
1008
1025
|
"forbidden_weight": bacon.get("forbidden_weight"),
|
|
1009
1026
|
"verdict": bacon.get("verdict"),
|
|
1010
1027
|
},
|
|
1011
|
-
"estimator_native":
|
|
1012
|
-
"status": native.get("status"),
|
|
1013
|
-
"pre_treatment_fit": native.get("pre_treatment_fit"),
|
|
1014
|
-
},
|
|
1028
|
+
"estimator_native": native_block,
|
|
1015
1029
|
}
|
|
1016
1030
|
|
|
1017
1031
|
|
|
@@ -1153,6 +1167,20 @@ def _describe_assumption(estimator_name: str, results: Any = None) -> Dict[str,
|
|
|
1153
1167
|
"captured through latent factor loadings."
|
|
1154
1168
|
),
|
|
1155
1169
|
}
|
|
1170
|
+
if estimator_name in {"SyntheticControlResults"}:
|
|
1171
|
+
return {
|
|
1172
|
+
# Distinct from SDiD's "synthetic_fit" weighted-PT analogue: classic
|
|
1173
|
+
# SCM is a donor-weighted level match (matches the DR "scm_fit" method).
|
|
1174
|
+
"parallel_trends_variant": "scm_fit",
|
|
1175
|
+
"no_anticipation": True,
|
|
1176
|
+
"description": (
|
|
1177
|
+
"Classic synthetic control identifies the single treated unit's "
|
|
1178
|
+
"counterfactual via a donor-weighted match to its pre-treatment "
|
|
1179
|
+
"trajectory (a design-enforced fit, not a parallel-trends test); "
|
|
1180
|
+
"significance comes from in-space placebo permutation inference "
|
|
1181
|
+
"rather than an analytical standard error."
|
|
1182
|
+
),
|
|
1183
|
+
}
|
|
1156
1184
|
if estimator_name == "ContinuousDiDResults":
|
|
1157
1185
|
# Callaway, Goodman-Bacon & Sant'Anna (2024), two-level PT:
|
|
1158
1186
|
# REGISTRY.md §ContinuousDiD > Identification.
|
|
@@ -1780,6 +1808,8 @@ def _pt_method_subject(method: Optional[str]) -> str:
|
|
|
1780
1808
|
return "Pre-treatment event-study coefficients"
|
|
1781
1809
|
if method == "synthetic_fit":
|
|
1782
1810
|
return "The synthetic-control pre-treatment fit"
|
|
1811
|
+
if method == "scm_fit":
|
|
1812
|
+
return "The synthetic-control donor-weighted pre-treatment fit"
|
|
1783
1813
|
if method == "factor":
|
|
1784
1814
|
return "The factor-model pre-treatment fit"
|
|
1785
1815
|
return "Pre-treatment data"
|
|
@@ -1806,7 +1836,9 @@ def _pt_method_stat_label(method: Optional[str]) -> Optional[str]:
|
|
|
1806
1836
|
return "joint p"
|
|
1807
1837
|
if method in {"slope_difference", "hausman"}:
|
|
1808
1838
|
return "p"
|
|
1809
|
-
if method in {"synthetic_fit", "factor"}:
|
|
1839
|
+
if method in {"synthetic_fit", "scm_fit", "factor"}:
|
|
1840
|
+
# Design-enforced fit-based paths have no p-value label (SCM's significance
|
|
1841
|
+
# is the in-space placebo, not a PT joint test).
|
|
1810
1842
|
return None
|
|
1811
1843
|
return "joint p"
|
|
1812
1844
|
|
|
@@ -1846,6 +1878,13 @@ def _references_for(estimator_name: str) -> List[Dict[str, str]]:
|
|
|
1846
1878
|
"& Wager, S. (2021). Synthetic Difference in Differences."
|
|
1847
1879
|
),
|
|
1848
1880
|
},
|
|
1881
|
+
"SyntheticControlResults": {
|
|
1882
|
+
"role": "estimator",
|
|
1883
|
+
"citation": (
|
|
1884
|
+
"Abadie, A., Diamond, A., & Hainmueller, J. (2010). Synthetic "
|
|
1885
|
+
"Control Methods for Comparative Case Studies. JASA, 105(490)."
|
|
1886
|
+
),
|
|
1887
|
+
},
|
|
1849
1888
|
"SunAbrahamResults": {
|
|
1850
1889
|
"role": "estimator",
|
|
1851
1890
|
"citation": (
|
|
@@ -2181,11 +2220,20 @@ def _render_summary(schema: Dict[str, Any]) -> str:
|
|
|
2181
2220
|
"assumption." + sens_tail_see_reliable
|
|
2182
2221
|
)
|
|
2183
2222
|
elif verdict == "design_enforced_pt":
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2223
|
+
if method == "scm_fit":
|
|
2224
|
+
sentences.append(
|
|
2225
|
+
"The synthetic control is designed to reproduce the treated "
|
|
2226
|
+
"unit's pre-period trajectory via donor weights (classic SCM's "
|
|
2227
|
+
"design-enforced analogue of parallel trends); significance "
|
|
2228
|
+
"comes from in-space placebo permutation inference, not a "
|
|
2229
|
+
"parallel-trends test."
|
|
2230
|
+
)
|
|
2231
|
+
else:
|
|
2232
|
+
sentences.append(
|
|
2233
|
+
"The synthetic control is designed to match the treated "
|
|
2234
|
+
"group's pre-period trajectory (SDiD's weighted-parallel-"
|
|
2235
|
+
"trends analogue)."
|
|
2236
|
+
)
|
|
2189
2237
|
elif verdict == "inconclusive":
|
|
2190
2238
|
# Round-35 P1 CI review on PR #318: a ``verdict=="inconclusive"``
|
|
2191
2239
|
# state means one or more pre-period coefficients had
|
|
@@ -19,7 +19,7 @@ References
|
|
|
19
19
|
- de Chaisemartin, C. & D'Haultfoeuille, X. (2020). Two-Way Fixed Effects
|
|
20
20
|
Estimators with Heterogeneous Treatment Effects. *American Economic
|
|
21
21
|
Review*, 110(9), 2964-2996.
|
|
22
|
-
- de Chaisemartin, C. & D'Haultfoeuille, X. (2022, revised 2023).
|
|
22
|
+
- de Chaisemartin, C. & D'Haultfoeuille, X. (2022, revised July 2023).
|
|
23
23
|
Difference-in-Differences Estimators of Intertemporal Treatment Effects.
|
|
24
24
|
NBER Working Paper 29873. Web Appendix Section 3.7.3 contains the
|
|
25
25
|
cohort-recentered plug-in variance formula implemented here.
|
|
@@ -4506,11 +4506,15 @@ def _compute_per_period_dids(
|
|
|
4506
4506
|
leaver_mask = (d_prev == 1) & (d_curr == 0) & present
|
|
4507
4507
|
stable1_mask = (d_prev == 1) & (d_curr == 1) & present
|
|
4508
4508
|
|
|
4509
|
-
#
|
|
4510
|
-
#
|
|
4509
|
+
# Library equal-cell weighting: N_{a,b,t} here counts (g, t)
|
|
4510
|
+
# cells in each transition state. Each cell contributes once
|
|
4511
4511
|
# regardless of how many original observations fed into the
|
|
4512
|
-
# y_gt cell mean.
|
|
4513
|
-
#
|
|
4512
|
+
# y_gt cell mean. This is a documented library deviation from
|
|
4513
|
+
# AER 2020 Equation 3 (which defines N_{d,d',t} = sum N_{g,t},
|
|
4514
|
+
# i.e., observation-sum weighting) and from R DIDmultiplegtDYN
|
|
4515
|
+
# (individual-row weighting on unbalanced inputs). See
|
|
4516
|
+
# REGISTRY.md ## ChaisemartinDHaultfoeuille L517 "Note (deviation
|
|
4517
|
+
# from R DIDmultiplegtDYN ...)" for the full text.
|
|
4514
4518
|
n_10 = int(joiner_mask.sum())
|
|
4515
4519
|
n_00 = int(stable0_mask.sum())
|
|
4516
4520
|
n_01 = int(leaver_mask.sum())
|
|
@@ -4635,7 +4639,8 @@ def _compute_placebo(
|
|
|
4635
4639
|
leaver_mask = (d_pre_prev == 1) & (d_prev == 1) & (d_curr == 0) & present
|
|
4636
4640
|
stable1_mask = (d_pre_prev == 1) & (d_prev == 1) & (d_curr == 1) & present
|
|
4637
4641
|
|
|
4638
|
-
# Placebo weights
|
|
4642
|
+
# Placebo weights: library equal-cell counts (same documented
|
|
4643
|
+
# deviation as the main DID_M path; see REGISTRY.md L517).
|
|
4639
4644
|
n_10 = int(joiner_mask.sum())
|
|
4640
4645
|
n_00 = int(stable0_mask.sum())
|
|
4641
4646
|
n_01 = int(leaver_mask.sum())
|
|
@@ -13,7 +13,7 @@ References
|
|
|
13
13
|
- de Chaisemartin, C. & D'Haultfoeuille, X. (2020). Two-Way Fixed Effects
|
|
14
14
|
Estimators with Heterogeneous Treatment Effects. *American Economic
|
|
15
15
|
Review*, 110(9), 2964-2996.
|
|
16
|
-
- de Chaisemartin, C. & D'Haultfoeuille, X. (2022, revised 2023).
|
|
16
|
+
- de Chaisemartin, C. & D'Haultfoeuille, X. (2022, revised July 2023).
|
|
17
17
|
Difference-in-Differences Estimators of Intertemporal Treatment Effects.
|
|
18
18
|
NBER Working Paper 29873.
|
|
19
19
|
"""
|
|
@@ -335,8 +335,13 @@ class ChaisemartinDHaultfoeuilleResults:
|
|
|
335
335
|
``L_max >= 1`` it was repurposed to hold the per-group count
|
|
336
336
|
that matches the ``DID_1`` estimand. Originally equals
|
|
337
337
|
once regardless of how many original observations fed into it.
|
|
338
|
-
This is the ``N_S`` denominator of ``DID_M``
|
|
339
|
-
|
|
338
|
+
This is the ``N_S`` denominator of ``DID_M`` under the library's
|
|
339
|
+
equal-cell weighting convention (cell counts, not within-cell
|
|
340
|
+
observation sums). The AER 2020 paper's Equation 3 defines
|
|
341
|
+
``N_{d,d',t} = sum_g N_{g,t}`` (observation sums); the
|
|
342
|
+
library's choice is a documented deviation - see
|
|
343
|
+
``docs/methodology/REGISTRY.md`` ``## ChaisemartinDHaultfoeuille``
|
|
344
|
+
L517 for the full Note.
|
|
340
345
|
n_cohorts : int
|
|
341
346
|
Distinct cohorts ``(D_{g,1}, F_g, S_g)`` after filtering.
|
|
342
347
|
n_groups_dropped_crossers : int
|
|
@@ -212,7 +212,7 @@ def _validate_conley_estimator_inputs(
|
|
|
212
212
|
) -> None:
|
|
213
213
|
"""Shared front-door validation for ``vcov_type='conley'`` on the
|
|
214
214
|
estimator entry points (``DifferenceInDifferences``, ``MultiPeriodDiD``,
|
|
215
|
-
``TwoWayFixedEffects``).
|
|
215
|
+
``TwoWayFixedEffects``, ``SunAbraham``, and ``WooldridgeDiD``-OLS).
|
|
216
216
|
|
|
217
217
|
Each estimator's ``fit()`` calls this BEFORE building Conley arrays or
|
|
218
218
|
threading them into the variance computation. The eight checks below
|
|
@@ -296,10 +296,14 @@ def _validate_conley_estimator_inputs(
|
|
|
296
296
|
raise ValueError(f"Cluster column '{cluster}' not found in data")
|
|
297
297
|
if survey_design is not None:
|
|
298
298
|
raise NotImplementedError(
|
|
299
|
-
f"{estimator_name}(vcov_type='conley') + survey_design is
|
|
300
|
-
"
|
|
301
|
-
"
|
|
302
|
-
"
|
|
299
|
+
f"{estimator_name}(vcov_type='conley') + survey_design is "
|
|
300
|
+
"deferred — weighted spatial-HAC under probability sampling "
|
|
301
|
+
"is an open methodological question; no canonical extension "
|
|
302
|
+
"of Conley (1999) exists for the combination. Drop "
|
|
303
|
+
"survey_design for unweighted Conley (cross-sectional or panel "
|
|
304
|
+
"block-decomposed via conley_lag_cutoff > 0), or use "
|
|
305
|
+
"vcov_type='hc1' for survey-aware cluster-robust without "
|
|
306
|
+
"spatial HAC."
|
|
303
307
|
)
|
|
304
308
|
if inference == "wild_bootstrap":
|
|
305
309
|
raise NotImplementedError(
|
|
@@ -365,6 +369,61 @@ def _uniform_kernel(u: np.ndarray) -> np.ndarray:
|
|
|
365
369
|
return (np.abs(u) <= 1.0).astype(np.float64)
|
|
366
370
|
|
|
367
371
|
|
|
372
|
+
def _serial_bartlett_kernel_matrix(t_codes: np.ndarray, L: int) -> np.ndarray:
|
|
373
|
+
"""Within-unit Newey-West (1987) Bartlett HAC kernel matrix for serial
|
|
374
|
+
correlation in panel data, indexed by panel-wide dense time codes.
|
|
375
|
+
|
|
376
|
+
Returns the K matrix with ``K[i, j] = 1 - |t_i - t_j| / (L + 1)`` for
|
|
377
|
+
``0 < |t_i - t_j| <= L``, else 0. The lag-0 diagonal is excluded so
|
|
378
|
+
callers can add this to a spatial within-period meat without
|
|
379
|
+
double-counting the diagonal.
|
|
380
|
+
|
|
381
|
+
Uses the 1-D radial pairwise form (matches conleyreg::time_dist), NOT
|
|
382
|
+
Conley 1999 Eq 3.14's 2-D separable product window — see the methodology
|
|
383
|
+
lock at :func:`_compute_conley_meat` for context.
|
|
384
|
+
"""
|
|
385
|
+
t = t_codes.astype(np.float64, copy=False)
|
|
386
|
+
lag_mat = np.abs(t[:, None] - t[None, :])
|
|
387
|
+
return ((lag_mat <= L) & (lag_mat != 0)).astype(np.float64) * (1.0 - lag_mat / (L + 1.0))
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _validate_meat_psd(
|
|
391
|
+
M: np.ndarray,
|
|
392
|
+
*,
|
|
393
|
+
error_msg: str,
|
|
394
|
+
warning_template: str,
|
|
395
|
+
stacklevel: int = 3,
|
|
396
|
+
) -> None:
|
|
397
|
+
"""Finite + PSD guard for sandwich meat matrices. Raises ``ValueError``
|
|
398
|
+
on non-finite entries; warns ``UserWarning`` when ``min(eigvalsh(M)) <
|
|
399
|
+
-1e-12``.
|
|
400
|
+
|
|
401
|
+
Parameters
|
|
402
|
+
----------
|
|
403
|
+
error_msg
|
|
404
|
+
Message passed to ``ValueError`` on non-finite entries.
|
|
405
|
+
warning_template
|
|
406
|
+
Format string for the negative-eigenvalue warning. May contain an
|
|
407
|
+
``{eigval}`` placeholder; the caller embeds ``{eigval:.2e}`` directly
|
|
408
|
+
in the template so the helper formats the minimum eigenvalue with
|
|
409
|
+
scientific notation.
|
|
410
|
+
stacklevel
|
|
411
|
+
Frame count from inside ``_validate_meat_psd``: ``stacklevel=N``
|
|
412
|
+
attributes the warning to the Nth caller above the helper itself.
|
|
413
|
+
Default 3 covers a single intermediate frame (the helper's direct
|
|
414
|
+
caller's caller); pass an explicit value matching call-site depth.
|
|
415
|
+
"""
|
|
416
|
+
if not np.all(np.isfinite(M)):
|
|
417
|
+
raise ValueError(error_msg)
|
|
418
|
+
eigvals = np.linalg.eigvalsh(M)
|
|
419
|
+
if eigvals.size and eigvals.min() < -1e-12:
|
|
420
|
+
warnings.warn(
|
|
421
|
+
warning_template.format(eigval=eigvals.min()),
|
|
422
|
+
UserWarning,
|
|
423
|
+
stacklevel=stacklevel,
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
|
|
368
427
|
def _compute_spatial_bartlett_meat_sparse(
|
|
369
428
|
S: np.ndarray,
|
|
370
429
|
coords: np.ndarray,
|
|
@@ -957,37 +1016,33 @@ def _compute_conley_meat(
|
|
|
957
1016
|
mask_u = unit_arr == u_val
|
|
958
1017
|
scores_u = scores[mask_u]
|
|
959
1018
|
# Use dense panel-period codes (NOT raw labels) for lag math.
|
|
960
|
-
|
|
961
|
-
lag_mat = np.abs(t_u[:, None] - t_u[None, :])
|
|
962
|
-
K_u = ((lag_mat <= L) & (lag_mat != 0)).astype(np.float64) * (
|
|
963
|
-
1.0 - lag_mat / (L + 1.0)
|
|
964
|
-
)
|
|
1019
|
+
K_u = _serial_bartlett_kernel_matrix(time_codes[mask_u], L)
|
|
965
1020
|
meat += scores_u.T @ K_u @ scores_u
|
|
966
|
-
if not np.all(np.isfinite(meat)):
|
|
967
|
-
raise ValueError(
|
|
968
|
-
"Conley meat contains non-finite values; check residuals and "
|
|
969
|
-
"score matrix for NaN/Inf."
|
|
970
|
-
)
|
|
971
|
-
|
|
972
1021
|
# PSD guard. Neither the uniform kernel (Conley 1999 fn 11) nor the
|
|
973
1022
|
# radial 1-D Bartlett specialization is formally PSD-guaranteed —
|
|
974
1023
|
# Conley's explicit PSD Bartlett formula (Eq 3.14) is the 2-D separable
|
|
975
1024
|
# product window, not the 1-D radial pairwise form that R `conleyreg`,
|
|
976
1025
|
# Stata `acreg`, and this implementation use. Check both kernels.
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1026
|
+
# ``{eigval:.2e}`` is a literal placeholder for ``_validate_meat_psd``;
|
|
1027
|
+
# only ``{kernel!r}`` is interpolated by the f-string here.
|
|
1028
|
+
_validate_meat_psd(
|
|
1029
|
+
meat,
|
|
1030
|
+
error_msg=(
|
|
1031
|
+
"Conley meat contains non-finite values; check residuals and "
|
|
1032
|
+
"score matrix for NaN/Inf."
|
|
1033
|
+
),
|
|
1034
|
+
warning_template=(
|
|
980
1035
|
f"Conley meat with conley_kernel={kernel!r} has a materially "
|
|
981
|
-
|
|
1036
|
+
"negative eigenvalue ({eigval:.2e}); the variance "
|
|
982
1037
|
"estimator is not guaranteed PSD on this design. Both "
|
|
983
1038
|
"supported kernels (radial bartlett and uniform) are "
|
|
984
1039
|
"practitioner specializations of Conley 1999 and are not "
|
|
985
1040
|
"formally PSD-guaranteed; consider varying conley_cutoff_km "
|
|
986
1041
|
"or reviewing the design for collinearity / degenerate "
|
|
987
|
-
"residual structure."
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1042
|
+
"residual structure."
|
|
1043
|
+
),
|
|
1044
|
+
stacklevel=4,
|
|
1045
|
+
)
|
|
991
1046
|
|
|
992
1047
|
return meat
|
|
993
1048
|
|