Skip to content

Commit 41a323e

Browse files
igerberclaude
andcommitted
R2 local Codex polish: pin the SAME compound contrast as the R fixture
Local Codex R2 returned ✅ with 1 substantive P3: the new MPD parity test was fitting MPD without an explicit `post_periods=` argument, so MPD's default "last half of periods" rule selected `[3, 4]` on the 4-period fixture while the R generator defines the parity contrast over `[2, 3, 4]` (per `post_interaction_names` in the JSON). The Satterthwaite DOFs happened to coincide here (~8.1) which masked the estimand mismatch — the test would have stayed green if MPD silently fit the wrong contrast. Fix: derive `post_periods` from the golden JSON's `post_interaction_names` field and pass it explicitly to MPD.fit(). The test now asserts that MPD computes `avg_att` over the exact same contrast vector R uses for the Wald_test DOF target. This makes the test a genuine estimand-level parity pin rather than just a DOF-magnitude smell check. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 96ce1c5 commit 41a323e

1 file changed

Lines changed: 10 additions & 0 deletions

File tree

‎tests/test_estimators_vcov_type.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,13 +582,23 @@ def test_multi_period_cluster_hc2_bm_avg_att_uses_clubsandwich_dof(self):
582582
)
583583
# MPD parameterization with `fixed_effects=["unit"]` matches the R
584584
# generator's `factor(unit)` term (the cluster column is the unit).
585+
# Derive `post_periods` from the R `post_interaction_names` so the
586+
# contrast we compare to is the SAME `c_avg = (1/n_post) Σ e_p` that
587+
# the R generator builds. Without this, MPD defaults to the
588+
# last-half-of-periods rule and computes avg_att over [3, 4] on this
589+
# 4-period panel, but the R fixture's `c_avg` is over [2, 3, 4] —
590+
# the DOFs happen to coincide here but the avg_att estimands differ.
591+
post_periods = [
592+
int(name.rsplit("_", 1)[1]) for name in d["post_interaction_names"]
593+
]
585594
res = MultiPeriodDiD(vcov_type="hc2_bm", cluster="unit").fit(
586595
data,
587596
outcome="y",
588597
treatment="treated",
589598
time="period",
590599
fixed_effects=["unit"],
591600
reference_period=int(d["reference_period"]),
601+
post_periods=post_periods,
592602
unit="unit",
593603
)
594604
assert np.isfinite(res.avg_att) and np.isfinite(res.avg_se)

0 commit comments

Comments
 (0)