Skip to content

Commit 9893454

Browse files
igerberclaude
andcommitted
Fix VCV index alignment, add stationarity warning for panel=False
VCV index: Build event_study_vcov_index from event times that actually contributed psi vectors (skipping NaN-only periods), not from all sorted_periods. Fixes misalignment when interior event times drop out. Stationarity: Document "stationary repeated cross-sections" in panel parameter docstring. Emit UserWarning on panel=False noting the stationarity assumption is not data-checkable. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent eac680e commit 9893454

2 files changed

Lines changed: 19 additions & 6 deletions

File tree

diff_diff/staggered.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,11 @@ class CallawaySantAnna(
183183
in IPW and DR estimation. Must be in ``(0, 0.5)``.
184184
panel : bool, default=True
185185
Whether the data is a balanced/unbalanced panel (units observed
186-
across multiple time periods). Set to ``False`` for repeated
187-
cross-sections where each observation has a unique unit ID and
188-
units do not repeat across periods. Uses cross-sectional DRDID
186+
across multiple time periods). Set to ``False`` for stationary
187+
repeated cross-sections where each observation has a unique unit
188+
ID and units do not repeat across periods. Requires that the
189+
cross-sectional samples are drawn from the same population in
190+
each period (stationarity). Uses cross-sectional DRDID
189191
(Sant'Anna & Zhao 2020, Section 4) with per-observation influence
190192
functions.
191193
@@ -1323,6 +1325,17 @@ def fit(
13231325
# Reset stale state from prior fit (prevents leaking event-study VCV)
13241326
self._event_study_vcov = None
13251327

1328+
if not self.panel:
1329+
warnings.warn(
1330+
"panel=False uses repeated cross-section DRDID estimators "
1331+
"(Sant'Anna & Zhao 2020, Section 4) which assume stationary "
1332+
"cross-sectional sampling: the population distribution of "
1333+
"(Y, X, G) must be stable across periods. This assumption "
1334+
"is not data-checkable.",
1335+
UserWarning,
1336+
stacklevel=2,
1337+
)
1338+
13261339
# Normalize empty covariates list to None
13271340
if covariates is not None and len(covariates) == 0:
13281341
covariates = None

diff_diff/staggered_aggregation.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,7 @@ def _aggregate_event_study(
627627
agg_ses_list = []
628628
agg_n_groups = []
629629
_psi_vectors = [] # Per-event-time combined IF vectors for VCV
630+
_psi_event_times = [] # Event times that contributed a psi column
630631
for e, effect_list in sorted_periods:
631632
gt_pairs = [x[0] for x in effect_list]
632633
effs = np.array([x[1] for x in effect_list])
@@ -666,6 +667,7 @@ def _aggregate_event_study(
666667
agg_ses_list.append(agg_se)
667668
agg_n_groups.append(len(effect_list))
668669
_psi_vectors.append(psi_e)
670+
_psi_event_times.append(e)
669671

670672
# Batch inference for all relative periods
671673
if not agg_effects_list:
@@ -753,9 +755,7 @@ def _aggregate_event_study(
753755

754756
# Store the event-time index that matches VCV columns (for subsetting
755757
# in HonestDiD when some event times are filtered out)
756-
self._event_study_vcov_index = (
757-
[e for e, _ in sorted_periods] if event_study_vcov is not None else None
758-
)
758+
self._event_study_vcov_index = _psi_event_times if event_study_vcov is not None else None
759759

760760
# Attach VCV to self for CallawaySantAnna to pick up
761761
self._event_study_vcov = event_study_vcov

0 commit comments

Comments
 (0)