Skip to content

Commit 904a4d7

Browse files
igerberclaude
andcommitted
fix: enforce CS panel-mode alignment between estimator and DGP
P1: validate that CallawaySantAnna.panel matches the survey DGP's panel flag in both directions: - CS(panel=True) + DGP panel=False -> rejected - CS(panel=False) + default DGP (panel=True) -> rejected - Panel check now runs even with empty data_generator_kwargs Add regression tests for both misaligned CS combinations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a39a96f commit 904a4d7

2 files changed

Lines changed: 45 additions & 7 deletions

File tree

diff_diff/power.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1943,16 +1943,31 @@ def simulate_power(
19431943
f"the input treatment_effect under covariate-interaction "
19441944
f"heterogeneity, which would make bias/coverage/RMSE misleading."
19451945
)
1946-
# Block panel=False for panel-only estimators. Only
1947-
# CallawaySantAnna supports repeated cross-sections.
1948-
if not data_gen_kwargs.get("panel", True):
1949-
_RCS_SUPPORTED = frozenset({"CallawaySantAnna"})
1950-
if estimator_name not in _RCS_SUPPORTED:
1946+
1947+
# Enforce panel-mode alignment between DGP and estimator.
1948+
# Runs even with empty data_gen_kwargs to catch CS(panel=False) + default DGP.
1949+
if use_survey_dgp:
1950+
dgp_panel = data_gen_kwargs.get("panel", True)
1951+
est_panel = getattr(estimator, "panel", True)
1952+
if not dgp_panel:
1953+
if estimator_name != "CallawaySantAnna":
19511954
raise ValueError(
19521955
f"panel=False (repeated cross-sections) is not supported "
19531956
f"with {estimator_name} under survey_config. Only "
1954-
f"{sorted(_RCS_SUPPORTED)} support repeated cross-sections."
1957+
f"CallawaySantAnna supports repeated cross-sections."
1958+
)
1959+
if est_panel:
1960+
raise ValueError(
1961+
"data_generator_kwargs has panel=False but "
1962+
"CallawaySantAnna.panel=True. Use "
1963+
"CallawaySantAnna(panel=False) to match."
19551964
)
1965+
elif estimator_name == "CallawaySantAnna" and not est_panel:
1966+
raise ValueError(
1967+
"CallawaySantAnna(panel=False) requires "
1968+
"data_generator_kwargs={'panel': False} to generate "
1969+
"repeated cross-section data."
1970+
)
19561971

19571972
# SyntheticDiD placebo variance requires n_control > n_treated.
19581973
# Check after merging data_generator_kwargs so overrides of n_treated

tests/test_power.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2435,7 +2435,7 @@ def test_survey_rejects_panel_false_for_panel_only(self):
24352435
)
24362436

24372437
def test_survey_allows_panel_false_for_cs(self):
2438-
"""panel=False allowed for CallawaySantAnna (supports RCS)."""
2438+
"""panel=False allowed for CallawaySantAnna(panel=False) (supports RCS)."""
24392439
result = simulate_power(
24402440
CallawaySantAnna(panel=False),
24412441
treatment_effect=3.0,
@@ -2447,6 +2447,29 @@ def test_survey_allows_panel_false_for_cs(self):
24472447
)
24482448
assert 0 <= result.power <= 1
24492449

2450+
def test_survey_rejects_cs_panel_mismatch_dgp_rcs(self):
2451+
"""CS(panel=True) + DGP panel=False rejected."""
2452+
with pytest.raises(ValueError, match="CallawaySantAnna.panel=True"):
2453+
simulate_power(
2454+
CallawaySantAnna(), # panel=True by default
2455+
data_generator_kwargs={"panel": False},
2456+
survey_config=_SURVEY_CFG,
2457+
n_simulations=1,
2458+
seed=42,
2459+
**_SIM_KW,
2460+
)
2461+
2462+
def test_survey_rejects_cs_panel_mismatch_est_rcs(self):
2463+
"""CS(panel=False) + default DGP (panel=True) rejected."""
2464+
with pytest.raises(ValueError, match="panel=False.*requires"):
2465+
simulate_power(
2466+
CallawaySantAnna(panel=False),
2467+
survey_config=_SURVEY_CFG,
2468+
n_simulations=1,
2469+
seed=42,
2470+
**_SIM_KW,
2471+
)
2472+
24502473
# -- Closed-form deff tests --
24512474

24522475
def test_closed_form_deff_default(self):

0 commit comments

Comments
 (0)