Skip to content

Commit d1f2bb4

Browse files
igerberclaude
andcommitted
test(staggered): handle materialized NaN cells in ported CS tests
Three test_csdid_ported.py tests relied on non-estimable (g,t) cells being OMITTED from group_time_effects; with the new NaN-cell materialization those cells are present as NaN, so the tests' membership / golden-iteration guards no longer skip them. Update them to preserve their intent on the FINITE cells: - test_some_units_treated_first_period: a first-period cohort (no base period) is now all-NaN (missing_period) rather than absent -> assert it is all-NaN. - test_zero_pretreatment_outcomes: skip NaN pre-cells (the last cohort under not_yet_treated has no controls); finite pre-cells are still ~0. - test_golden_fewer_periods: skip NaN cells (gapped panel where base g-1 is unobserved -> missing_period; R falls back to an available base) -> R-parity on the finite cells. No source change; the cells are correctly non-estimable, only now visible. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 9d57e76 commit d1f2bb4

1 file changed

Lines changed: 22 additions & 3 deletions

File tree

tests/test_csdid_ported.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -633,9 +633,16 @@ def test_some_units_treated_first_period(self):
633633
time="period",
634634
first_treat="first_treat",
635635
)
636-
# G=2 should be excluded (no pre-treatment period available)
637-
groups_in_results = set(k[0] for k in results.group_time_effects.keys())
638-
assert 2 not in groups_in_results, "G=2 treated in first period should be excluded"
636+
# G=2 is treated in the first observed period, so it has no valid base
637+
# period -> all its (g,t) cells are non-estimable. They are now materialized
638+
# as NaN entries (skip_reason="missing_period") rather than omitted, so G=2
639+
# contributes no FINITE estimate (the prior "excluded from the analysis"
640+
# intent: it is not silently dropped, but it is never estimated).
641+
g2_cells = [v for (g, t), v in results.group_time_effects.items() if g == 2]
642+
assert g2_cells, "G=2 cells should be materialized (as NaN), not silently dropped"
643+
assert all(
644+
np.isnan(v["effect"]) and v["skip_reason"] == "missing_period" for v in g2_cells
645+
), "G=2 (no pre-treatment period) must be all-NaN (missing_period), never estimated"
639646

640647

641648
class TestCSDIDBugFixRegressions:
@@ -774,6 +781,11 @@ def test_zero_pretreatment_outcomes(self):
774781
gt = results.group_time_effects
775782
pre_effects = {k: v for k, v in gt.items() if k[1] < k[0]}
776783
for (g, t), eff in pre_effects.items():
784+
# Non-estimable pre-cells are now materialized as NaN (e.g. the last
785+
# cohort under not_yet_treated has no controls); skip them. Finite
786+
# pre-treatment cells (DiD of 0-0 vs 0-0) must still be ~0.
787+
if np.isnan(eff["effect"]):
788+
continue
777789
assert abs(eff["effect"]) < 0.01, (
778790
f"Pre-treatment ATT(g={g}, t={t}) = {eff['effect']:.4f}, " "expected 0"
779791
)
@@ -1199,6 +1211,13 @@ def test_golden_fewer_periods(self, golden_values):
11991211
g, t = int(g), int(t)
12001212
if (g, t) in results.group_time_effects:
12011213
py_att = results.group_time_effects[(g, t)]["effect"]
1214+
# Skip cells we materialize as non-estimable (e.g. a gapped panel
1215+
# where the base period g-1 is not observed -> missing_period). R
1216+
# falls back to an available base and reports a value where our
1217+
# impl does not; compare only cells both estimate (R-parity on the
1218+
# finite cells, which is what this golden test pins).
1219+
if not np.isfinite(py_att):
1220+
continue
12021221
r_att = r_gt["att"][i]
12031222
assert abs(py_att - r_att) < 0.05, (
12041223
f"Fewer periods ATT(g={g},t={t}): " f"Py={py_att:.4f}, R={r_att:.4f}"

0 commit comments

Comments
 (0)