Skip to content

Commit d8f6520

Browse files
igerberclaude
andcommitted
Fix CS reg nuisance IF scaling, add SE scale-invariance test
- Remove spurious /n_c from regression nuisance IF correction in _outcome_regression survey covariate branch. asy_lin_rep_reg is already per-observation, so dividing by n_c double-scaled. - Add test_reg_covariates_survey_se_scale_invariance: verifies ATT and SE are invariant to weight rescaling for reg+covariates+survey. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b5c5fc9 commit d8f6520

2 files changed

Lines changed: 36 additions & 1 deletion

File tree

diff_diff/staggered.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1633,7 +1633,7 @@ def _outcome_regression(
16331633
# Influence function (survey-weighted)
16341634
inf_treated = sw_t_norm * (treated_residuals - att)
16351635
inf_control = (
1636-
-sw_c_norm * (control_change - np.dot(X_c, beta)) + inf_control_reg_corr / n_c
1636+
-sw_c_norm * (control_change - np.dot(X_c, beta)) + inf_control_reg_corr
16371637
)
16381638
inf_func = np.concatenate([inf_treated, inf_control])
16391639

tests/test_survey_phase4.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,41 @@ def test_reg_covariates_survey_works(self, staggered_survey_data, survey_design_
658658
)
659659
assert np.isfinite(result.overall_att)
660660

661+
def test_reg_covariates_survey_se_scale_invariance(self, staggered_survey_data):
662+
"""SE for reg + covariates + survey must be invariant to weight rescaling."""
663+
data = staggered_survey_data.copy()
664+
data["x1"] = np.random.default_rng(42).normal(0, 1, len(data))
665+
data["weight2"] = data["weight"] * 4.3
666+
sd1 = SurveyDesign(weights="weight")
667+
sd2 = SurveyDesign(weights="weight2")
668+
est = CallawaySantAnna(estimation_method="reg")
669+
r1 = est.fit(
670+
data,
671+
"outcome",
672+
"unit",
673+
"period",
674+
"first_treat",
675+
covariates=["x1"],
676+
aggregate="simple",
677+
survey_design=sd1,
678+
)
679+
r2 = est.fit(
680+
data,
681+
"outcome",
682+
"unit",
683+
"period",
684+
"first_treat",
685+
covariates=["x1"],
686+
aggregate="simple",
687+
survey_design=sd2,
688+
)
689+
assert np.isclose(
690+
r1.overall_att, r2.overall_att, atol=1e-8
691+
), "ATT not scale-invariant for reg+cov+survey"
692+
assert np.isclose(
693+
r1.overall_se, r2.overall_se, atol=1e-8
694+
), f"SE not scale-invariant for reg+cov+survey: {r1.overall_se} vs {r2.overall_se}"
695+
661696
def test_weighted_logit(self, staggered_survey_data, survey_design_weights_only):
662697
"""Propensity scores should change with survey weights (IPW path)."""
663698
r_unw = CallawaySantAnna(estimation_method="ipw").fit(

0 commit comments

Comments
 (0)