fix: route TripleDifference power to panel DGP when n_periods > 2 by igerber · Pull Request #544 · igerber/diff-diff · GitHub
Skip to content

fix: route TripleDifference power to panel DGP when n_periods > 2#544

Merged
igerber merged 3 commits into
mainfrom
fix/ddd-power-panel-routing
Jun 23, 2026
Merged

fix: route TripleDifference power to panel DGP when n_periods > 2#544
igerber merged 3 commits into
mainfrom
fix/ddd-power-panel-routing

Conversation

@igerber

@igerber igerber commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Summary

  • simulate_power / simulate_mde / simulate_sample_size now route TripleDifference (DDD) power to the panel DGP generate_ddd_panel_data when n_periods > 2 (previously the _EstimatorProfile hard-coded the cross-sectional 2×2×2 generate_ddd_data, silently ignoring n_periods). The panel path honors n_periods/treatment_period, sizes the panel by n_units directly, and switches the sample-size search from the multiple-of-8 grid to a continuous step-1 search.
  • Emit a UserWarning when the estimator lacks cluster="unit" on the panel path (within-unit serial correlation makes unclustered SEs anti-conservative → overstated power); reject the cross-sectional-only n_per_cell key with a clear ValueError. treatment_fraction stays inert (balanced 2×2×2); group_frac/partition_frac are overridable via data_generator_kwargs.
  • Add a split-aware viable-N floor (_ddd_panel_viable_min_n, mirroring the DGP's rounded stratified allocation) so simulate_sample_size never probes an infeasible (empty-cell) n under skewed splits; raise a clear error when an n_range upper bound is below that floor.
  • Since simulate_power defaults to n_periods=4, the default DDD power call now uses the panel DGP (intentional — removes the prior "n_periods ignored" wart).

Resolves the deferred TODO.md row (Methodology/Correctness, "TripleDifference power auto-routing").

Methodology references (required if estimator / math changes)

  • Method name(s): PowerAnalysis (simulation path) — TripleDifference DGP routing
  • Paper / source link(s): Bloom (1995); Burlig, Preonas & Woerman (2020). DGP per generate_ddd_panel_data (DDD-CPT triple-interaction identification). No estimator math changed — this is DGP selection/wiring for the simulation power harness.
  • Any intentional deviations from the source (and why): None new. Documented in docs/methodology/REGISTRY.md §PowerAnalysis (**Note:** labels) — cross-sectional (n_periods ≤ 2) vs panel (> 2) routing + the cluster="unit" inference caveat.

Validation

  • Tests added/updated (tests/test_power.py): re-targeted two stale-warning tests + the n_per_cell collision test to the cross-sectional path; added panel-routing, missing-cluster warning, no-warning-with-cluster, n_per_cell-reject, and slow simulate_mde/simulate_sample_size panel tests (incl. unbalanced-split viable-floor + low-n_range guard). Full tests/test_power.py green (214 passed, incl. slow).
  • Backtest / simulation / notebook evidence: docs/tutorials/06_power_analysis.ipynb DDD section updated to teach the routing + showcase the panel path with cluster="unit"; executes end-to-end (all 32 code cells).

Security / privacy

  • Confirm no secrets/PII in this PR: Yes

Generated with Claude Code

igerber and others added 2 commits June 23, 2026 09:37
simulate_power/simulate_mde/simulate_sample_size silently ignored n_periods for
DDD because the _EstimatorProfile hard-coded the cross-sectional generate_ddd_data.
Route to generate_ddd_panel_data when n_periods > 2, honoring n_periods/
treatment_period and sizing the panel by n_units directly. simulate_sample_size
switches from the multiple-of-8 grid to a continuous step-1 search on the panel
path. Emit a UserWarning when the estimator lacks cluster="unit" (panel SEs are
anti-conservative and overstate power); reject the cross-sectional-only n_per_cell
key with a clear ValueError. treatment_fraction stays inert (balanced 2x2x2).

Docs: REGISTRY.md PowerAnalysis notes + tutorial 06 + CHANGELOG [Unreleased];
removes the resolved TODO row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses local codex P1/P2: simulate_sample_size advertised group_frac/
partition_frac overrides for the panel DDD path but hard-coded the search floor
at 16, which is infeasible for skewed splits (generate_ddd_panel_data requires
every (group,partition) cell non-empty — e.g. 0.1/0.1 needs n>=55). Add
_ddd_panel_viable_min_n() (mirrors the DGP's rounded stratified allocation) and
use it for min_n/abs_min on the panel path; raise a clear ValueError when an
n_range upper bound is below that floor. Regression tests for the unbalanced
split + low n_range guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

…oor helper

Addresses CI codex P2: _ddd_panel_viable_min_n() now validates group_frac/
partition_frac in (0, 1) up front (matching generate_ddd_panel_data) and raises
clearly if no feasible n is found within search_max, so an out-of-range split
surfaces the DGP's clear message instead of a misleading "n_range below the
minimum" bracketing error. Adds a unit test for the validation. Also clarifies
the tutorial-06 DDD support-table footnote (P3): simulate_sample_size starts at
the registry floor of 64, but an explicit n_range can reach the 16-unit floor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

@igerber igerber added the ready-for-ci Triggers CI test workflows label Jun 23, 2026
@igerber igerber merged commit 2293ba1 into main Jun 23, 2026
33 of 34 checks passed
@igerber igerber deleted the fix/ddd-power-panel-routing branch June 23, 2026 16:38
@igerber igerber mentioned this pull request Jun 25, 2026
wenddymacro pushed a commit to wenddymacro/diff-diff that referenced this pull request Jun 26, 2026
Release v3.5.3. Convert the curated [Unreleased] CHANGELOG section to the
3.5.3 release header (2026-06-25) and sync version strings across
__init__.py, pyproject.toml, rust/Cargo.toml, llms-full.txt, and CITATION.cff.

Highlights since 3.5.2: wild cluster bootstrap now imposes the null with
test-inversion CIs (igerber#543, igerber#546), TwoStageDiD methodology validation (igerber#545),
generate_synthetic_control_data + tutorial 25 (igerber#540), TripleDifference
panel-power routing (igerber#544), and degenerate-design SE guards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-ci Triggers CI test workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant