Skip to content

Commit 35ab5fe

Browse files
igerberclaude
andcommitted
Address PR #393 R1 P1: multi-baseline trends_linear UserWarning
CI reviewer flagged that by_path + trends_linear was ungated on all panels but only warned on F_g<3 exclusions, while the analogous by_path + controls path warns on multi-baseline switcher panels (D_{g,1} multiplicity) where R's per-path full-pipeline call diverges from Python's global-then-disaggregate architecture. The by_path docstring already acknowledged the "same multi-baseline divergence pattern as controls" (line 461-462), but the runtime warning was missing — a docstring-vs-implementation gap. Fix: mirror the controls warning at chaisemartin_dhaultfoeuille.py :1565-1584 inside the trends_linear block (right after the F_g<3 warning). Same predicate (switcher mask on first_switch_idx_arr, unique baseline check). Different message naming the trends_linear mechanism (full-pipeline including first-differencing per path subset). REGISTRY note updated to spell out the warning explicitly. Three regression tests added in TestByPathTrendsLinear, mirroring TestByPathControls: - test_multi_baseline_panel_emits_r_deviation_warning: panel with joiners and leavers must emit the warning - test_single_baseline_panel_does_not_emit_r_deviation_warning: single-baseline panel must not warn - test_single_baseline_heterogeneous_F_g_does_not_warn: pins the precise predicate (baseline multiplicity, NOT F_g multiplicity) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 0a2783a commit 35ab5fe

3 files changed

Lines changed: 187 additions & 1 deletion

File tree

diff_diff/chaisemartin_dhaultfoeuille.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,6 +1653,41 @@ def fit(
16531653
UserWarning,
16541654
stacklevel=2,
16551655
)
1656+
# Multi-baseline switcher panel detection (`by_path + trends_linear`):
1657+
# mirror the analogous warning fired by `by_path + controls` at
1658+
# `:1565-1584`. Python first-differences once globally before
1659+
# path enumeration; R `did_multiplegt_dyn(..., by_path, trends_lin)`
1660+
# re-runs the full pipeline (including first-differencing) per
1661+
# path's restricted subsample. On single-baseline switcher
1662+
# panels the two architectures coincide; on multi-baseline
1663+
# switcher panels (some switchers have `D_{g,1}=0`, others
1664+
# `D_{g,1}=1`) per-path point estimates can diverge — warn the
1665+
# user explicitly so they don't silently consume estimates
1666+
# that disagree with R. The check filters to switcher groups
1667+
# only (never-switchers / always-treated controls don't
1668+
# contribute to switcher baseline multiplicity).
1669+
if self.by_path is not None:
1670+
_switcher_mask_tl = first_switch_idx_arr >= 0
1671+
if _switcher_mask_tl.any():
1672+
_switcher_baselines_tl = baselines[_switcher_mask_tl]
1673+
if np.unique(_switcher_baselines_tl).size > 1:
1674+
warnings.warn(
1675+
"by_path + trends_linear: switcher baselines "
1676+
"D_{g,1} take multiple values in this panel. "
1677+
"Python first-differences once on the full "
1678+
"panel before path enumeration; R "
1679+
"`did_multiplegt_dyn(..., by_path, trends_lin)` "
1680+
"re-runs the full pipeline (including "
1681+
"first-differencing) on each path's restricted "
1682+
"subsample, so per-path point estimates can "
1683+
"diverge between Python and R on this panel. "
1684+
"See `docs/methodology/REGISTRY.md` "
1685+
"(`Note (Phase 3 by_path ...)` -> Per-path "
1686+
"linear-trends DID^{fd}) for the full "
1687+
"deviation contract.",
1688+
UserWarning,
1689+
stacklevel=2,
1690+
)
16561691
N_mat_orig = N_mat.copy()
16571692
Y_mat, N_mat = _compute_first_differenced_matrix(Y_mat, N_mat)
16581693

0 commit comments

Comments
 (0)