diff-diff 3.4.0__tar.gz → 3.4.2__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.0 → diff_diff-3.4.2}/PKG-INFO +2 -2
- {diff_diff-3.4.0 → diff_diff-3.4.2}/README.md +1 -1
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/__init__.py +1 -1
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/chaisemartin_dhaultfoeuille.py +11 -6
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/chaisemartin_dhaultfoeuille_results.py +8 -3
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/conley.py +70 -19
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/estimators.py +7 -2
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/guides/llms-full.txt +26 -4
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/guides/llms.txt +1 -5
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/had.py +63 -7
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/had_pretests.py +68 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/honest_did.py +115 -52
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/imputation.py +127 -1
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/imputation_bootstrap.py +75 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/imputation_results.py +90 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/linalg.py +531 -130
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/results.py +19 -1
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/spillover.py +708 -74
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/stacked_did.py +276 -22
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/stacked_did_results.py +29 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/staggered.py +424 -7
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/staggered_bootstrap.py +47 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/staggered_results.py +49 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/sun_abraham.py +452 -45
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/survey.py +195 -4
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/triple_diff.py +181 -28
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/twfe.py +179 -88
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/two_stage.py +1182 -99
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/utils.py +113 -50
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/wooldridge.py +806 -106
- diff_diff-3.4.2/diff_diff/wooldridge_results.py +850 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/pyproject.toml +1 -1
- {diff_diff-3.4.0 → diff_diff-3.4.2}/rust/Cargo.lock +9 -9
- {diff_diff-3.4.0 → diff_diff-3.4.2}/rust/Cargo.toml +1 -1
- diff_diff-3.4.0/diff_diff/wooldridge_results.py +0 -373
- {diff_diff-3.4.0 → diff_diff-3.4.2}/LICENSE +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/_backend.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/_guides_api.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/_nprobust_port.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/_reporting_helpers.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/agent_workflow.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/bacon.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/bootstrap_utils.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/business_report.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/chaisemartin_dhaultfoeuille_bootstrap.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/continuous_did.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/continuous_did_bspline.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/continuous_did_results.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/datasets.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/diagnostic_report.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/diagnostics.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/efficient_did.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/efficient_did_bootstrap.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/efficient_did_covariates.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/efficient_did_results.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/efficient_did_weights.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/guides/__init__.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/guides/llms-autonomous.txt +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/guides/llms-practitioner.txt +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/local_linear.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/power.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/practitioner.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/prep.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/prep_dgp.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/pretrends.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/profile.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/staggered_aggregation.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/staggered_triple_diff.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/staggered_triple_diff_results.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/synthetic_did.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/trop.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/trop_global.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/trop_local.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/trop_results.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/two_stage_bootstrap.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/two_stage_results.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/visualization/__init__.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/visualization/_common.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/visualization/_continuous.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/visualization/_diagnostic.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/visualization/_event_study.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/visualization/_power.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/visualization/_staggered.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/diff_diff/visualization/_synthetic.py +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/rust/build.rs +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/rust/src/bootstrap.rs +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/rust/src/lib.rs +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/rust/src/linalg.rs +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/rust/src/trop.rs +0 -0
- {diff_diff-3.4.0 → diff_diff-3.4.2}/rust/src/weights.rs +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: diff-diff
|
|
3
|
-
Version: 3.4.
|
|
3
|
+
Version: 3.4.2
|
|
4
4
|
Classifier: Development Status :: 5 - Production/Stable
|
|
5
5
|
Classifier: Intended Audience :: Science/Research
|
|
6
6
|
Classifier: Operating System :: OS Independent
|
|
@@ -159,7 +159,7 @@ 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
|
|
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
164
|
- [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
165
|
- [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
|
|
@@ -106,7 +106,7 @@ 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
|
|
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
|
- [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
112
|
- [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
|
|
@@ -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
|
|
@@ -365,6 +365,61 @@ def _uniform_kernel(u: np.ndarray) -> np.ndarray:
|
|
|
365
365
|
return (np.abs(u) <= 1.0).astype(np.float64)
|
|
366
366
|
|
|
367
367
|
|
|
368
|
+
def _serial_bartlett_kernel_matrix(t_codes: np.ndarray, L: int) -> np.ndarray:
|
|
369
|
+
"""Within-unit Newey-West (1987) Bartlett HAC kernel matrix for serial
|
|
370
|
+
correlation in panel data, indexed by panel-wide dense time codes.
|
|
371
|
+
|
|
372
|
+
Returns the K matrix with ``K[i, j] = 1 - |t_i - t_j| / (L + 1)`` for
|
|
373
|
+
``0 < |t_i - t_j| <= L``, else 0. The lag-0 diagonal is excluded so
|
|
374
|
+
callers can add this to a spatial within-period meat without
|
|
375
|
+
double-counting the diagonal.
|
|
376
|
+
|
|
377
|
+
Uses the 1-D radial pairwise form (matches conleyreg::time_dist), NOT
|
|
378
|
+
Conley 1999 Eq 3.14's 2-D separable product window — see the methodology
|
|
379
|
+
lock at :func:`_compute_conley_meat` for context.
|
|
380
|
+
"""
|
|
381
|
+
t = t_codes.astype(np.float64, copy=False)
|
|
382
|
+
lag_mat = np.abs(t[:, None] - t[None, :])
|
|
383
|
+
return ((lag_mat <= L) & (lag_mat != 0)).astype(np.float64) * (1.0 - lag_mat / (L + 1.0))
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _validate_meat_psd(
|
|
387
|
+
M: np.ndarray,
|
|
388
|
+
*,
|
|
389
|
+
error_msg: str,
|
|
390
|
+
warning_template: str,
|
|
391
|
+
stacklevel: int = 3,
|
|
392
|
+
) -> None:
|
|
393
|
+
"""Finite + PSD guard for sandwich meat matrices. Raises ``ValueError``
|
|
394
|
+
on non-finite entries; warns ``UserWarning`` when ``min(eigvalsh(M)) <
|
|
395
|
+
-1e-12``.
|
|
396
|
+
|
|
397
|
+
Parameters
|
|
398
|
+
----------
|
|
399
|
+
error_msg
|
|
400
|
+
Message passed to ``ValueError`` on non-finite entries.
|
|
401
|
+
warning_template
|
|
402
|
+
Format string for the negative-eigenvalue warning. May contain an
|
|
403
|
+
``{eigval}`` placeholder; the caller embeds ``{eigval:.2e}`` directly
|
|
404
|
+
in the template so the helper formats the minimum eigenvalue with
|
|
405
|
+
scientific notation.
|
|
406
|
+
stacklevel
|
|
407
|
+
Frame count from inside ``_validate_meat_psd``: ``stacklevel=N``
|
|
408
|
+
attributes the warning to the Nth caller above the helper itself.
|
|
409
|
+
Default 3 covers a single intermediate frame (the helper's direct
|
|
410
|
+
caller's caller); pass an explicit value matching call-site depth.
|
|
411
|
+
"""
|
|
412
|
+
if not np.all(np.isfinite(M)):
|
|
413
|
+
raise ValueError(error_msg)
|
|
414
|
+
eigvals = np.linalg.eigvalsh(M)
|
|
415
|
+
if eigvals.size and eigvals.min() < -1e-12:
|
|
416
|
+
warnings.warn(
|
|
417
|
+
warning_template.format(eigval=eigvals.min()),
|
|
418
|
+
UserWarning,
|
|
419
|
+
stacklevel=stacklevel,
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
|
|
368
423
|
def _compute_spatial_bartlett_meat_sparse(
|
|
369
424
|
S: np.ndarray,
|
|
370
425
|
coords: np.ndarray,
|
|
@@ -957,37 +1012,33 @@ def _compute_conley_meat(
|
|
|
957
1012
|
mask_u = unit_arr == u_val
|
|
958
1013
|
scores_u = scores[mask_u]
|
|
959
1014
|
# 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
|
-
)
|
|
1015
|
+
K_u = _serial_bartlett_kernel_matrix(time_codes[mask_u], L)
|
|
965
1016
|
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
1017
|
# PSD guard. Neither the uniform kernel (Conley 1999 fn 11) nor the
|
|
973
1018
|
# radial 1-D Bartlett specialization is formally PSD-guaranteed —
|
|
974
1019
|
# Conley's explicit PSD Bartlett formula (Eq 3.14) is the 2-D separable
|
|
975
1020
|
# product window, not the 1-D radial pairwise form that R `conleyreg`,
|
|
976
1021
|
# Stata `acreg`, and this implementation use. Check both kernels.
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1022
|
+
# ``{eigval:.2e}`` is a literal placeholder for ``_validate_meat_psd``;
|
|
1023
|
+
# only ``{kernel!r}`` is interpolated by the f-string here.
|
|
1024
|
+
_validate_meat_psd(
|
|
1025
|
+
meat,
|
|
1026
|
+
error_msg=(
|
|
1027
|
+
"Conley meat contains non-finite values; check residuals and "
|
|
1028
|
+
"score matrix for NaN/Inf."
|
|
1029
|
+
),
|
|
1030
|
+
warning_template=(
|
|
980
1031
|
f"Conley meat with conley_kernel={kernel!r} has a materially "
|
|
981
|
-
|
|
1032
|
+
"negative eigenvalue ({eigval:.2e}); the variance "
|
|
982
1033
|
"estimator is not guaranteed PSD on this design. Both "
|
|
983
1034
|
"supported kernels (radial bartlett and uniform) are "
|
|
984
1035
|
"practitioner specializations of Conley 1999 and are not "
|
|
985
1036
|
"formally PSD-guaranteed; consider varying conley_cutoff_km "
|
|
986
1037
|
"or reviewing the design for collinearity / degenerate "
|
|
987
|
-
"residual structure."
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1038
|
+
"residual structure."
|
|
1039
|
+
),
|
|
1040
|
+
stacklevel=4,
|
|
1041
|
+
)
|
|
991
1042
|
|
|
992
1043
|
return meat
|
|
993
1044
|
|
|
@@ -1891,8 +1891,13 @@ class MultiPeriodDiD(DifferenceInDifferences):
|
|
|
1891
1891
|
else:
|
|
1892
1892
|
# Cluster-aware CR2 BM Satterthwaite DOF for per-coefficient
|
|
1893
1893
|
# AND post-period-average compound contrast (Gate 6 lift).
|
|
1894
|
-
#
|
|
1895
|
-
#
|
|
1894
|
+
# This branch is guarded above by `not _use_survey_vcov`,
|
|
1895
|
+
# so when reached, `survey_weights` is None (survey designs
|
|
1896
|
+
# always route through the TSL path). The clubSandwich
|
|
1897
|
+
# WLS-CR2 port lifted `_compute_cr2_bm_contrast_dof` to
|
|
1898
|
+
# accept `weights=`, but no MPD entry point currently
|
|
1899
|
+
# passes non-None weights here — `weights=None` is the
|
|
1900
|
+
# de facto contract on this code path today.
|
|
1896
1901
|
_dof_all = _compute_cr2_bm_contrast_dof(
|
|
1897
1902
|
X_kept,
|
|
1898
1903
|
effective_cluster_ids,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> A Python library for Difference-in-Differences causal inference analysis. Provides sklearn-like estimators with statsmodels-style output for econometric analysis.
|
|
4
4
|
|
|
5
|
-
- Version: 3.4.
|
|
5
|
+
- Version: 3.4.2
|
|
6
6
|
- Repository: https://github.com/igerber/diff-diff
|
|
7
7
|
- License: MIT
|
|
8
8
|
- Dependencies: numpy, pandas, scipy (no statsmodels dependency)
|
|
@@ -188,7 +188,7 @@ CallawaySantAnna(
|
|
|
188
188
|
anticipation: int = 0, # Anticipation periods
|
|
189
189
|
estimation_method: str = "dr", # "dr", "ipw", or "reg"
|
|
190
190
|
alpha: float = 0.05,
|
|
191
|
-
cluster: str | None = None, #
|
|
191
|
+
cluster: str | None = None, # Cluster col; activates CR1 on the IF via synthesized SurveyDesign(psu=col). None → per-unit IF.
|
|
192
192
|
n_bootstrap: int = 0, # 0 = analytical SEs, 999+ recommended
|
|
193
193
|
bootstrap_weights: str | None = None, # "rademacher", "mammen", or "webb"
|
|
194
194
|
seed: int | None = None,
|
|
@@ -196,6 +196,7 @@ CallawaySantAnna(
|
|
|
196
196
|
base_period: str = "varying", # "varying" or "universal"
|
|
197
197
|
cband: bool = True, # Simultaneous confidence bands
|
|
198
198
|
pscore_trim: float = 0.01, # Propensity score trimming bound
|
|
199
|
+
vcov_type: str = "hc1", # {"hc1"} only — IF-based variance per Callaway & Sant'Anna (2021). Analytical-sandwich {classical, hc2, hc2_bm} and conley REJECTED at __init__ (see REGISTRY.md IF-vs-sandwich subsection).
|
|
199
200
|
)
|
|
200
201
|
```
|
|
201
202
|
|
|
@@ -335,10 +336,11 @@ SunAbraham(
|
|
|
335
336
|
control_group: str = "never_treated", # "never_treated" or "not_yet_treated"
|
|
336
337
|
anticipation: int = 0,
|
|
337
338
|
alpha: float = 0.05,
|
|
338
|
-
cluster: str | None = None, # Defaults to unit-level clustering
|
|
339
|
+
cluster: str | None = None, # Defaults to unit-level clustering (dropped on explicit vcov_type='hc2' / 'classical')
|
|
339
340
|
n_bootstrap: int = 0, # 0 = analytical cluster-robust SEs
|
|
340
341
|
seed: int | None = None,
|
|
341
342
|
rank_deficient_action: str = "warn",
|
|
343
|
+
vcov_type: str = "hc1", # {"classical","hc1","hc2","hc2_bm"}; classical/hc2/hc2_bm route through full-dummy saturated design. survey_design=... is rejected for classical/hc2/hc2_bm (use hc1 default for surveys)
|
|
342
344
|
)
|
|
343
345
|
```
|
|
344
346
|
|
|
@@ -377,12 +379,14 @@ ImputationDiD(
|
|
|
377
379
|
anticipation: int = 0,
|
|
378
380
|
alpha: float = 0.05,
|
|
379
381
|
cluster: str | None = None, # Defaults to unit-level clustering
|
|
382
|
+
vcov_type: str = "hc1", # {"hc1"} only — IF-based variance per Borusyak et al. (2024) Theorem 3
|
|
380
383
|
n_bootstrap: int = 0, # 0 = analytical (Theorem 3 variance)
|
|
381
384
|
bootstrap_weights: str = "rademacher", # "rademacher", "mammen", or "webb"
|
|
382
385
|
seed: int | None = None,
|
|
383
386
|
rank_deficient_action: str = "warn",
|
|
384
387
|
horizon_max: int | None = None, # Max event-study horizon
|
|
385
388
|
aux_partition: str = "cohort_horizon", # "cohort_horizon", "cohort", or "horizon"
|
|
389
|
+
pretrends: bool = False, # Include pre-treatment horizons in event study
|
|
386
390
|
)
|
|
387
391
|
```
|
|
388
392
|
|
|
@@ -400,6 +404,7 @@ imp.fit(
|
|
|
400
404
|
covariates: list[str] = None,
|
|
401
405
|
aggregate: str = None, # None, "simple", "event_study", "group", or "all"
|
|
402
406
|
balance_e: int = None,
|
|
407
|
+
survey_design: SurveyDesign = None, # Optional design-based inference (pweight + analytical strata/PSU/FPC or replicate BRR/Fay/JK1/JKn/SDR)
|
|
403
408
|
) -> ImputationDiDResults
|
|
404
409
|
```
|
|
405
410
|
|
|
@@ -575,6 +580,7 @@ TripleDifference(
|
|
|
575
580
|
estimation_method: str = "dr", # "dr", "reg", or "ipw"
|
|
576
581
|
robust: bool = True,
|
|
577
582
|
cluster: str | None = None,
|
|
583
|
+
vcov_type: str = "hc1", # {"hc1"} only — IF-based variance per Ortiz-Villavicencio & Sant'Anna (2025). Analytical-sandwich {classical, hc2, hc2_bm} and conley REJECTED at __init__ (see REGISTRY.md IF-vs-sandwich subsection).
|
|
578
584
|
alpha: float = 0.05,
|
|
579
585
|
pscore_trim: float = 0.01,
|
|
580
586
|
rank_deficient_action: str = "warn",
|
|
@@ -742,6 +748,7 @@ StackedDiD(
|
|
|
742
748
|
alpha: float = 0.05,
|
|
743
749
|
anticipation: int = 0,
|
|
744
750
|
rank_deficient_action: str = "warn",
|
|
751
|
+
vcov_type: str = "hc1", # {"hc1","hc2_bm"}; classical/hc2 rejected (intrinsically clustered), conley deferred. survey_design=... requires hc1
|
|
745
752
|
)
|
|
746
753
|
```
|
|
747
754
|
|
|
@@ -965,6 +972,13 @@ WooldridgeDiD(
|
|
|
965
972
|
bootstrap_weights: str = "rademacher",
|
|
966
973
|
seed: int | None = None,
|
|
967
974
|
rank_deficient_action: str = "warn",
|
|
975
|
+
vcov_type: str = "hc1", # {"classical","hc1","hc2","hc2_bm"}; OLS path only.
|
|
976
|
+
# hc1 (default) preserves prior bit-equal within-transform CR1.
|
|
977
|
+
# hc2_bm auto-routes to full-dummy + clubSandwich WLS-CR2 algebra.
|
|
978
|
+
# classical/hc2 auto-drop the unit auto-cluster (one-way only);
|
|
979
|
+
# explicit cluster="X" + one-way raises at the linalg validator.
|
|
980
|
+
# conley deferred. method != "ols" requires hc1 (QMLE CR2-BM TBD).
|
|
981
|
+
# survey_design= requires hc1 (survey TSL overrides analytical).
|
|
968
982
|
)
|
|
969
983
|
```
|
|
970
984
|
|
|
@@ -1267,7 +1281,15 @@ ImputationDiDResults, TwoStageDiDResults, StackedDiDResults, and EfficientDiDRes
|
|
|
1267
1281
|
|
|
1268
1282
|
Each event study effect dict contains: `effect`, `se`, `t_stat`, `p_value`, `conf_int`, `n_obs` (or `n_groups`).
|
|
1269
1283
|
|
|
1270
|
-
**
|
|
1284
|
+
**Variance metadata** (`ImputationDiDResults` carries these; other staggered Results may surface a subset):
|
|
1285
|
+
|
|
1286
|
+
| Attribute | Type | Description |
|
|
1287
|
+
|-----------|------|-------------|
|
|
1288
|
+
| `vcov_type` | `str` | Variance estimator family (`"hc1"` for IF-based estimators; permanently narrow on `ImputationDiD` / `CallawaySantAnna` / `TripleDifference` per IF-vs-sandwich taxonomy) |
|
|
1289
|
+
| `cluster_name` | `str | None` | Effective cluster column name (e.g. `"unit"` for default `cluster=None`); `None` under survey designs (the survey block already names PSU/strata) |
|
|
1290
|
+
| `n_clusters` | `int | None` | Number of effective clusters; `None` under survey designs |
|
|
1291
|
+
|
|
1292
|
+
**Methods:** `summary()`, `print_summary()`, `to_dataframe()`, `to_dict()` (flat dict of headline aliases + `vcov_type` + conditional `cluster_name`/`n_clusters`/`n_bootstrap`/`inference_method`)
|
|
1271
1293
|
|
|
1272
1294
|
### ContinuousDiDResults
|
|
1273
1295
|
|
|
@@ -10,10 +10,6 @@ diff-diff offers 17 estimators covering basic 2x2 DiD, modern staggered adoption
|
|
|
10
10
|
- Source: https://github.com/igerber/diff-diff
|
|
11
11
|
- Docs: https://diff-diff.readthedocs.io/en/stable/
|
|
12
12
|
|
|
13
|
-
## Agent Quickstart
|
|
14
|
-
|
|
15
|
-
LLM agents: call `diff_diff.agent_workflow(df, unit=..., time=..., treatment=..., outcome=...)` first. It prints the recommended 5-step workflow (`profile_panel` → `get_llm_guide` → `<Estimator>().fit` → `practitioner_next_steps` → `BusinessReport`) with your column names wired in.
|
|
16
|
-
|
|
17
13
|
## Practitioner Workflow (based on Baker et al. 2025)
|
|
18
14
|
|
|
19
15
|
IMPORTANT: For rigorous DiD analysis, follow these 8 steps. Skipping
|
|
@@ -62,7 +58,7 @@ Full practitioner guide: call `diff_diff.get_llm_guide("practitioner")`
|
|
|
62
58
|
- [SunAbraham](https://diff-diff.readthedocs.io/en/stable/api/staggered.html): Sun & Abraham (2021) interaction-weighted estimator for heterogeneity-robust event studies
|
|
63
59
|
- [ImputationDiD](https://diff-diff.readthedocs.io/en/stable/api/imputation.html): Borusyak, Jaravel & Spiess (2024) imputation estimator — most efficient under homogeneous effects
|
|
64
60
|
- [TwoStageDiD](https://diff-diff.readthedocs.io/en/stable/api/two_stage.html): Gardner (2022) two-stage estimator with GMM sandwich variance
|
|
65
|
-
- [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-control; reuses `conley_coords` for ring construction; handles non-staggered and staggered timing
|
|
61
|
+
- [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-control; reuses `conley_coords` for ring construction; handles non-staggered and staggered timing; supports `SurveyDesign(weights, strata, psu, fpc)` under `vcov_type="hc1"` with optional `cluster=<col>` for CR1 via Gerber (2026) Binder TSL (Wave E.1) and under `vcov_type="conley"` via a panel-aware stratified-Conley sandwich on per-period PSU totals (Wave E.2 cross-sectional `conley_lag_cutoff=0`) extended in Wave E.2 follow-up to `conley_lag_cutoff > 0` via panel-block composition with within-PSU serial Bartlett HAC (Newey-West 1987 separable form; `lag>0` requires an effective PSU via explicit `survey_design.psu` or injected `cluster=<col>`), both composed with the Wave D Gardner GMM correction; `SurveyDesign.subpopulation()` preserves full-design `n_psu` / `df_survey` via zero-padded scores at the meat-helper boundary (Wave E.3, R `svyrecvar(subset())` form) (replicate weights queued as follow-up)
|
|
66
62
|
- [SyntheticDiD](https://diff-diff.readthedocs.io/en/stable/api/estimators.html): Synthetic DiD combining standard DiD and synthetic control methods for few treated units
|
|
67
63
|
- [TripleDifference](https://diff-diff.readthedocs.io/en/stable/api/triple_diff.html): Triple difference (DDD) estimator for designs requiring two criteria for treatment eligibility
|
|
68
64
|
- [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
|
|
@@ -2595,6 +2595,50 @@ class HeterogeneousAdoptionDiD:
|
|
|
2595
2595
|
|
|
2596
2596
|
Notes
|
|
2597
2597
|
-----
|
|
2598
|
+
**Non-testable assumptions (paper Section 3.1.2).** Point identification
|
|
2599
|
+
of ``WAS_{d_lower}`` on the Design 1 family
|
|
2600
|
+
(``continuous_near_d_lower`` and ``mass_point``) requires Assumption 6
|
|
2601
|
+
in addition to parallel trends; sign identification requires
|
|
2602
|
+
Assumption 5. Neither is testable via pre-trends:
|
|
2603
|
+
|
|
2604
|
+
- Assumption 5 (sign identification): the boundary slope-ratio
|
|
2605
|
+
``lim_{d down d_lower} E(TE_2 | D_2 <= d) / WAS < E(D_2) / d_lower``
|
|
2606
|
+
relates the conditional expectation near the boundary to the
|
|
2607
|
+
overall WAS; it cannot be inferred from pre-period outcome
|
|
2608
|
+
trajectories alone.
|
|
2609
|
+
- Assumption 6 (point identification): the counterfactual-mean
|
|
2610
|
+
alignment ``lim_{d down d_lower} E[Y_2(d_lower) - Y_2(0) | D_2 <= d]
|
|
2611
|
+
= E[Y_2(d_lower) - Y_2(0)]`` is a statement about an unobserved
|
|
2612
|
+
counterfactual at the support infimum.
|
|
2613
|
+
|
|
2614
|
+
The fit() method emits a ``UserWarning`` whenever ``resolved_design``
|
|
2615
|
+
is on the Design 1 family (``continuous_near_d_lower`` or
|
|
2616
|
+
``mass_point``) so users are not silently led to interpret point
|
|
2617
|
+
estimates as full point identification. The available pre-tests
|
|
2618
|
+
verify ADJACENT identifying conditions:
|
|
2619
|
+
|
|
2620
|
+
- :func:`diff_diff.qug_test`: Theorem 4 / Design 1' support-infimum
|
|
2621
|
+
null ``d_lower = 0`` (adjacent evidence on the ``d_lower = 0``
|
|
2622
|
+
clause of Assumption 4 only, NOT a test of the full Assumption 4
|
|
2623
|
+
statement which also covers boundary-density positivity,
|
|
2624
|
+
conditional-mean smoothness, conditional-variance regularity, and
|
|
2625
|
+
bandwidth conditions).
|
|
2626
|
+
- :func:`diff_diff.stute_test` / :func:`diff_diff.yatchew_hr_test`:
|
|
2627
|
+
Assumption 8 linearity of ``E[ΔY | D_2]`` in ``D_2`` (residuals
|
|
2628
|
+
from ``dy ~ 1 + d``).
|
|
2629
|
+
- :func:`diff_diff.joint_pretrends_test`: Assumption 7
|
|
2630
|
+
mean-independence pre-trends across multi-period placebos
|
|
2631
|
+
(intercept-only residual form via ``null_form="mean_independence"``;
|
|
2632
|
+
the raw ``stute_test`` / ``yatchew_hr_test`` helpers do NOT cover
|
|
2633
|
+
Assumption 7 on their own).
|
|
2634
|
+
|
|
2635
|
+
None of these test Assumptions 5 or 6 directly. The Assumption 5/6
|
|
2636
|
+
non-testability caveat is surfaced by the Design 1 fit-time
|
|
2637
|
+
``UserWarning`` and by T21 (HAD pretest workflow tutorial) prose,
|
|
2638
|
+
NOT by the composite workflow verdict string (which only flags the
|
|
2639
|
+
Assumption 7 step-2 gap on the two-period ``aggregate="overall"``
|
|
2640
|
+
path).
|
|
2641
|
+
|
|
2598
2642
|
**Diagnostics coverage.** ``HeterogeneousAdoptionDiDResults.bandwidth_diagnostics``
|
|
2599
2643
|
and ``.bias_corrected_fit`` are populated only on the continuous
|
|
2600
2644
|
paths; both are ``None`` on the mass-point path (which is parametric
|
|
@@ -2831,13 +2875,22 @@ class HeterogeneousAdoptionDiD:
|
|
|
2831
2875
|
first_treat_col : str or None
|
|
2832
2876
|
Optional first-treatment column (the period at which each
|
|
2833
2877
|
unit first receives treatment; ``0`` for never-treated).
|
|
2834
|
-
Required on the event-study path when the panel has more
|
|
2835
|
-
than two distinct first-treat values (staggered timing):
|
|
2836
|
-
the estimator auto-filters to the last-treatment cohort
|
|
2837
|
-
with a ``UserWarning`` per paper Appendix B.2 prescription.
|
|
2838
2878
|
For common-adoption panels the column is optional; when
|
|
2839
2879
|
omitted, the event-study path infers the first-treatment
|
|
2840
|
-
period ``F`` from the dose invariant.
|
|
2880
|
+
period ``F`` from the dose invariant. **Staggered-timing
|
|
2881
|
+
contract (HAD Appendix B.2):**
|
|
2882
|
+
|
|
2883
|
+
- **`first_treat_col` supplied + multiple cohorts detected**:
|
|
2884
|
+
auto-filter to the last-treatment cohort + never-treated
|
|
2885
|
+
units with a ``UserWarning`` naming kept / dropped counts.
|
|
2886
|
+
- **`first_treat_col` omitted + multiple distinct first-
|
|
2887
|
+
positive-dose cohorts inferred from the dose path**: the
|
|
2888
|
+
estimator FAIL-CLOSES with ``ValueError`` directing the
|
|
2889
|
+
user to either pass ``first_treat_col`` (activates the
|
|
2890
|
+
auto-filter) or use :class:`ChaisemartinDHaultfoeuille`
|
|
2891
|
+
(``did_multiplegt_dyn``) for full staggered support. See
|
|
2892
|
+
REGISTRY § "Library extension: Staggered-timing fail-
|
|
2893
|
+
closed" for the rationale on raising vs. warning.
|
|
2841
2894
|
aggregate : {"overall", "event_study"}
|
|
2842
2895
|
``"overall"`` (default): returns a single-period
|
|
2843
2896
|
:class:`HeterogeneousAdoptionDiDResults` (Phase 2a). Requires
|
|
@@ -2847,8 +2900,11 @@ class HeterogeneousAdoptionDiD:
|
|
|
2847
2900
|
event-time WAS estimates on the multi-period panel (paper
|
|
2848
2901
|
Appendix B.2). Requires more than two time periods. Pointwise
|
|
2849
2902
|
CIs per horizon; joint cross-horizon covariance is deferred
|
|
2850
|
-
to a follow-up PR. Staggered-timing panels
|
|
2851
|
-
|
|
2903
|
+
to a follow-up PR. Staggered-timing panels: see the
|
|
2904
|
+
``first_treat_col`` contract above (auto-filter to last
|
|
2905
|
+
cohort + never-treated with ``UserWarning`` when supplied;
|
|
2906
|
+
fail-closed ``ValueError`` when omitted on a staggered
|
|
2907
|
+
panel).
|
|
2852
2908
|
survey_design : SurveyDesign or None, keyword-only
|
|
2853
2909
|
Survey design (sampling weights + optional strata / PSU / FPC)
|
|
2854
2910
|
for design-based inference. Supported on ALL design × aggregate
|
|
@@ -1349,6 +1349,22 @@ def qug_test(
|
|
|
1349
1349
|
|
|
1350
1350
|
Notes
|
|
1351
1351
|
-----
|
|
1352
|
+
**Scope (what this test does NOT cover).** ``qug_test`` tests the
|
|
1353
|
+
Theorem 4 / Design 1' support-infimum null ``H_0: d_lower = 0``. It
|
|
1354
|
+
does not validate the full Assumption 4 (Assumption 4 also requires
|
|
1355
|
+
positive boundary density, twice-differentiable conditional-mean,
|
|
1356
|
+
bounded continuous conditional-variance, and bandwidth regularity —
|
|
1357
|
+
QUG is adjacent evidence on the ``d_lower = 0`` clause only). It
|
|
1358
|
+
does NOT and CANNOT test Assumptions 5 and 6 from the same paper
|
|
1359
|
+
(Section 3.1.2), which are required for sign identification (A5) and
|
|
1360
|
+
point identification (A6) of ``WAS_{d_lower}`` on the Design 1 family
|
|
1361
|
+
(``d_lower > 0``). Assumptions 5 and 6 are statements about
|
|
1362
|
+
conditional expectations near the support boundary and about
|
|
1363
|
+
counterfactual-mean alignment respectively; they are non-testable via
|
|
1364
|
+
pre-trends. See :class:`HeterogeneousAdoptionDiD` class docstring
|
|
1365
|
+
Notes for the full statement and T21 (HAD pretest workflow tutorial)
|
|
1366
|
+
for the verdict-language convention that surfaces this gap.
|
|
1367
|
+
|
|
1352
1368
|
Tie-break: when ``D_{(1)} == D_{(2)}`` the statistic is undefined.
|
|
1353
1369
|
The test returns ``t_stat=NaN, p_value=NaN, reject=False`` with a
|
|
1354
1370
|
``UserWarning`` rather than raising.
|
|
@@ -1636,6 +1652,23 @@ def stute_test(
|
|
|
1636
1652
|
|
|
1637
1653
|
Notes
|
|
1638
1654
|
-----
|
|
1655
|
+
**Scope (what this test does NOT cover).** ``stute_test`` targets
|
|
1656
|
+
paper Assumption 8 (linearity of ``E[ΔY | D_2]`` in ``D_2``) — the
|
|
1657
|
+
raw helper always fits ``dy ~ 1 + d`` and tests the linearity null;
|
|
1658
|
+
it does NOT target Assumption 7 mean-independence pre-trends on its
|
|
1659
|
+
own. For Assumption 7 mean-independence (residuals from intercept-
|
|
1660
|
+
only ``dy ~ 1``), use :func:`joint_pretrends_test` (which routes
|
|
1661
|
+
``null_form="mean_independence"`` into the joint CvM core). It does
|
|
1662
|
+
NOT and CANNOT test Assumptions 5 and 6 from de Chaisemartin et al.
|
|
1663
|
+
(2026) Section 3.1.2, which are required for sign / point
|
|
1664
|
+
identification of ``WAS_{d_lower}`` on the Design 1 family
|
|
1665
|
+
(``d_lower > 0``). Assumptions 5/6 are non-testable via pre-trends
|
|
1666
|
+
(boundary-conditional expectations and counterfactual-mean alignment
|
|
1667
|
+
statements); they are surfaced by the Design 1 fit-time
|
|
1668
|
+
``UserWarning`` and by T21 tutorial prose, NOT by the workflow
|
|
1669
|
+
verdict string. See :class:`HeterogeneousAdoptionDiD` class
|
|
1670
|
+
docstring Notes for the full statement.
|
|
1671
|
+
|
|
1639
1672
|
Sample-size gate: below ``G = 10`` the CvM statistic is not
|
|
1640
1673
|
well-calibrated. In that case the function emits ``UserWarning`` and
|
|
1641
1674
|
returns all-NaN inference rather than raising.
|
|
@@ -2112,6 +2145,20 @@ def yatchew_hr_test(
|
|
|
2112
2145
|
|
|
2113
2146
|
Notes
|
|
2114
2147
|
-----
|
|
2148
|
+
**Scope (what this test does NOT cover).** ``yatchew_hr_test`` targets
|
|
2149
|
+
paper Assumption 8 (linearity of ``E[ΔY | D_2]`` in ``D_2``) under
|
|
2150
|
+
``null="linearity"`` (default); ``null="mean_independence"`` swaps
|
|
2151
|
+
the residual definition to intercept-only ``dy ~ 1`` for R parity
|
|
2152
|
+
with ``YatchewTest::yatchew_test(order=0)`` on pre-trend placebos.
|
|
2153
|
+
It does NOT and CANNOT test Assumptions 5 and 6 from de
|
|
2154
|
+
Chaisemartin et al. (2026) Section 3.1.2, which are required for
|
|
2155
|
+
sign / point identification of ``WAS_{d_lower}`` on the Design 1
|
|
2156
|
+
family (``d_lower > 0``). Assumptions 5/6 are non-testable via
|
|
2157
|
+
pre-trends; they are surfaced by the Design 1 fit-time
|
|
2158
|
+
``UserWarning`` and by T21 tutorial prose, NOT by the workflow
|
|
2159
|
+
verdict string. See :class:`HeterogeneousAdoptionDiD` class
|
|
2160
|
+
docstring Notes for the full statement.
|
|
2161
|
+
|
|
2115
2162
|
Sample-size gate: below ``G = 3`` the difference-variance estimator
|
|
2116
2163
|
is undefined; the function emits ``UserWarning`` and returns NaN
|
|
2117
2164
|
rather than raising.
|
|
@@ -4548,6 +4595,27 @@ def did_had_pretest_workflow(
|
|
|
4548
4595
|
|
|
4549
4596
|
Notes
|
|
4550
4597
|
-----
|
|
4598
|
+
**Scope (what this composite workflow does NOT cover).** The
|
|
4599
|
+
component pretests target the Theorem 4 / Design 1' support-infimum
|
|
4600
|
+
null (QUG: ``d_lower = 0``, adjacent evidence on the ``d_lower = 0``
|
|
4601
|
+
clause of Assumption 4 only — does not validate boundary density,
|
|
4602
|
+
conditional-mean smoothness, or variance regularity), Assumption 7
|
|
4603
|
+
(joint Stute pre-trends: mean-independence of placebo first-
|
|
4604
|
+
differences from dose), and Assumption 8 (Yatchew / joint
|
|
4605
|
+
homogeneity: linearity of treatment effects in dose). The workflow
|
|
4606
|
+
does NOT and CANNOT test Assumptions 5 and 6
|
|
4607
|
+
from de Chaisemartin et al. (2026) Section 3.1.2, which are required
|
|
4608
|
+
for sign / point identification of ``WAS_{d_lower}`` on the Design 1
|
|
4609
|
+
family (``d_lower > 0``). Assumptions 5/6 are non-testable via
|
|
4610
|
+
pre-trends. The composite verdict string does NOT mention
|
|
4611
|
+
Assumptions 5 or 6 — it only flags the Assumption 7 step-2 gap on
|
|
4612
|
+
the two-period ``aggregate="overall"`` path. The Assumption 5/6
|
|
4613
|
+
caveat is surfaced separately by (a) the
|
|
4614
|
+
``HeterogeneousAdoptionDiD.fit()`` fit-time ``UserWarning`` (which
|
|
4615
|
+
fires whenever the resolved design is Design 1 family —
|
|
4616
|
+
``continuous_near_d_lower`` or ``mass_point``) and (b) T21 (HAD
|
|
4617
|
+
pretest workflow tutorial) tutorial prose.
|
|
4618
|
+
|
|
4551
4619
|
Survey/weighted data (Phase 4.5 C): under ``survey=`` or ``weights=``,
|
|
4552
4620
|
the workflow:
|
|
4553
4621
|
|