Skip to content

Commit 1f8a537

Browse files
igerberclaude
andcommitted
Fix HonestDiD: reference-aware pre/post split, replicate df=0 sentinel
P0 fix: When survey_metadata has replicate_method but df_survey=None (rank-deficient replicate design), set df_survey=0 sentinel so _get_critical_value returns NaN. Prevents finite HonestDiD bounds when CS inference is NaN. P1 fix: Infer omitted reference period from n_groups=0 entry in event_study_effects (handles anticipation>0 where ref=-1-anticipation). Split pre/post relative to reference, not hardcoded at t<0/t>=0. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e9995ef commit 1f8a537

1 file changed

Lines changed: 24 additions & 7 deletions

File tree

diff_diff/honest_did.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -612,10 +612,13 @@ def _extract_event_study_params(
612612
# Fallback: diagonal from SEs
613613
sigma = np.diag(np.array(ses) ** 2)
614614

615-
# Extract survey df if available
615+
# Extract survey df. Replicate designs with undefined df → sentinel 0.
616616
df_survey = None
617617
if hasattr(results, "survey_metadata") and results.survey_metadata is not None:
618-
df_survey = getattr(results.survey_metadata, "df_survey", None)
618+
sm = results.survey_metadata
619+
df_survey = getattr(sm, "df_survey", None)
620+
if df_survey is None and getattr(sm, "replicate_method", None) is not None:
621+
df_survey = 0
619622

620623
return (
621624
beta_hat,
@@ -665,9 +668,18 @@ def _extract_event_study_params(
665668
}
666669
rel_times = sorted(event_effects.keys())
667670

668-
# Split into pre and post
669-
pre_times = [t for t in rel_times if t < 0]
670-
post_times = [t for t in rel_times if t >= 0]
671+
# Infer the omitted reference period from the n_groups=0 entry
672+
# (injected by _aggregate_event_study for universal base).
673+
# Default to e=-1 if no reference found (varying base).
674+
ref_period = -1
675+
for t, data in results.event_study_effects.items():
676+
if data.get("n_groups", 1) == 0:
677+
ref_period = t
678+
break
679+
680+
# Split relative to the reference period, not hardcoded at 0
681+
pre_times = [t for t in rel_times if t < ref_period]
682+
post_times = [t for t in rel_times if t > ref_period]
671683

672684
effects = []
673685
ses = []
@@ -731,10 +743,15 @@ def _extract_event_study_params(
731743
"or use balance_e to restrict to a balanced subset."
732744
)
733745

734-
# Extract survey df
746+
# Extract survey df. For replicate designs with undefined df
747+
# (rank <= 1), use sentinel df=0 so _get_critical_value returns
748+
# NaN, matching the safe_inference contract.
735749
df_survey = None
736750
if hasattr(results, "survey_metadata") and results.survey_metadata is not None:
737-
df_survey = getattr(results.survey_metadata, "df_survey", None)
751+
sm = results.survey_metadata
752+
df_survey = getattr(sm, "df_survey", None)
753+
if df_survey is None and getattr(sm, "replicate_method", None) is not None:
754+
df_survey = 0 # undefined replicate df → NaN inference
738755

739756
return (
740757
beta_hat,

0 commit comments

Comments
 (0)