Skip to content

Commit aaa2b20

Browse files
igerberclaude
andcommitted
Fix reference-period detection (effect=0.0+NaN SE), warn on bootstrap diagonal VCV
Reference inference: distinguish the injected universal-base reference (effect=0.0, se=NaN, n_groups=0) from empty bins that also have n_groups=0 but NaN effect. Prevents mis-splitting when empty bins exist. Bootstrap HonestDiD: emit UserWarning when using diagonal fallback (cross-event covariance unavailable). Test asserts warning is raised. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4d65f75 commit aaa2b20

2 files changed

Lines changed: 33 additions & 5 deletions

File tree

diff_diff/honest_did.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -668,11 +668,17 @@ def _extract_event_study_params(
668668
}
669669
rel_times = sorted(event_effects.keys())
670670

671-
# Infer the omitted reference period from the n_groups=0 entry
672-
# (injected by _aggregate_event_study for universal base).
671+
# Infer the omitted reference period from the normalization
672+
# marker injected by _aggregate_event_study for universal base.
673+
# The reference has the exact signature: effect=0.0, se=NaN, n_groups=0.
674+
# Other empty bins may also have n_groups=0 but with NaN effect.
673675
ref_period = None
674676
for t, data in results.event_study_effects.items():
675-
if data.get("n_groups", 1) == 0:
677+
if (
678+
data.get("n_groups", 1) == 0
679+
and data.get("effect", None) == 0.0
680+
and not np.isfinite(data.get("se", 0.0))
681+
):
676682
ref_period = t
677683
break
678684

@@ -720,6 +726,22 @@ def _extract_event_study_params(
720726
else:
721727
sigma = np.diag(np.array(ses) ** 2)
722728
else:
729+
# No full VCV available. Check if this is a bootstrap fit
730+
# (VCV was cleared to prevent mixing analytical/bootstrap).
731+
if (
732+
hasattr(results, "bootstrap_results")
733+
and results.bootstrap_results is not None
734+
):
735+
import warnings
736+
737+
warnings.warn(
738+
"HonestDiD on bootstrap-fitted CallawaySantAnna results "
739+
"uses a diagonal covariance matrix (cross-event-time "
740+
"covariance is not available from bootstrap). For full "
741+
"covariance structure, use analytical SEs (n_bootstrap=0).",
742+
UserWarning,
743+
stacklevel=4,
744+
)
723745
sigma = np.diag(np.array(ses) ** 2)
724746

725747
# Validate the full event-time grid is consecutive.

tests/test_honest_did.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,9 +1295,15 @@ def test_bootstrap_fit_clears_analytical_vcov(self):
12951295
# (prevents HonestDiD from mixing analytical VCV with bootstrap SEs)
12961296
assert cs_result.event_study_vcov is None
12971297

1298-
# HonestDiD should still work (falls back to diagonal from bootstrap SEs)
1298+
# HonestDiD should warn about diagonal fallback but still work
12991299
honest = HonestDiD(method="relative_magnitude", M=1.0)
1300-
h_result = honest.fit(cs_result)
1300+
import warnings as _w
1301+
1302+
with _w.catch_warnings(record=True) as caught:
1303+
_w.simplefilter("always")
1304+
h_result = honest.fit(cs_result)
1305+
diag_warnings = [w for w in caught if "diagonal covariance" in str(w.message)]
1306+
assert len(diag_warnings) >= 1
13011307
assert np.isfinite(h_result.original_se)
13021308
assert h_result.original_se > 0
13031309

0 commit comments

Comments
 (0)