diff-diff 3.4.2__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.2 → diff_diff-3.5.0}/PKG-INFO +2 -1
- {diff_diff-3.4.2 → diff_diff-3.5.0}/README.md +1 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/__init__.py +9 -1
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/_reporting_helpers.py +17 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/business_report.py +62 -14
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/conley.py +9 -5
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/diagnostic_report.py +225 -23
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/efficient_did.py +105 -8
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/efficient_did_bootstrap.py +92 -2
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/efficient_did_results.py +93 -7
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/estimators.py +4 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/guides/llms-full.txt +111 -11
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/guides/llms.txt +2 -1
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/linalg.py +304 -59
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/power.py +133 -44
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/practitioner.py +70 -1
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/stacked_did.py +16 -9
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/staggered.py +62 -25
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/staggered_aggregation.py +81 -15
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/staggered_bootstrap.py +57 -5
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/staggered_triple_diff.py +125 -34
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/staggered_triple_diff_results.py +47 -0
- {diff_diff-3.4.2 → 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.2 → diff_diff-3.5.0}/diff_diff/triple_diff.py +53 -30
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/two_stage.py +112 -1
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/two_stage_bootstrap.py +102 -6
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/two_stage_results.py +91 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/wooldridge.py +191 -24
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/wooldridge_results.py +22 -1
- {diff_diff-3.4.2 → diff_diff-3.5.0}/pyproject.toml +1 -1
- {diff_diff-3.4.2 → diff_diff-3.5.0}/rust/Cargo.lock +15 -15
- {diff_diff-3.4.2 → diff_diff-3.5.0}/rust/Cargo.toml +1 -1
- {diff_diff-3.4.2 → diff_diff-3.5.0}/rust/src/linalg.rs +37 -5
- {diff_diff-3.4.2 → diff_diff-3.5.0}/LICENSE +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/_backend.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/_guides_api.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/_nprobust_port.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/agent_workflow.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/bacon.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/bootstrap_utils.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/chaisemartin_dhaultfoeuille.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/chaisemartin_dhaultfoeuille_bootstrap.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/chaisemartin_dhaultfoeuille_results.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/continuous_did.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/continuous_did_bspline.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/continuous_did_results.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/datasets.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/diagnostics.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/efficient_did_covariates.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/efficient_did_weights.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/guides/__init__.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/guides/llms-autonomous.txt +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/guides/llms-practitioner.txt +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/had.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/had_pretests.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/honest_did.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/imputation.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/imputation_bootstrap.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/imputation_results.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/local_linear.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/prep.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/prep_dgp.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/pretrends.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/profile.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/results.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/spillover.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/stacked_did_results.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/staggered_results.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/survey.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/synthetic_did.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/trop.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/trop_global.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/trop_local.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/trop_results.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/twfe.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/utils.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/visualization/__init__.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/visualization/_common.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/visualization/_continuous.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/visualization/_diagnostic.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/visualization/_event_study.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/visualization/_power.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/visualization/_staggered.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/diff_diff/visualization/_synthetic.py +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/rust/build.rs +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/rust/src/bootstrap.rs +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/rust/src/lib.rs +0 -0
- {diff_diff-3.4.2 → diff_diff-3.5.0}/rust/src/trop.rs +0 -0
- {diff_diff-3.4.2 → 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
|
|
@@ -161,6 +161,7 @@ Full guide: `diff_diff.get_llm_guide("practitioner")`.
|
|
|
161
161
|
- [TwoStageDiD](https://diff-diff.readthedocs.io/en/stable/api/two_stage.html) - Gardner (2022) two-stage estimator with GMM sandwich variance
|
|
162
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`.
|
|
@@ -108,6 +108,7 @@ Full guide: `diff_diff.get_llm_guide("practitioner")`.
|
|
|
108
108
|
- [TwoStageDiD](https://diff-diff.readthedocs.io/en/stable/api/two_stage.html) - Gardner (2022) two-stage estimator with GMM sandwich variance
|
|
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
|
+
- [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
|
|
@@ -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(
|