Skip to content

Commit 1febbb1

Browse files
igerberclaude
andcommitted
Address PR #374 R5 P3: stale to_dataframe docstring + Mammen/Webb coverage
P3 #1: ``to_dataframe`` method docstring at ``chaisemartin_dhaultfoeuille_results.py:1375-1379`` listed the pre-change ``level="by_path"`` schema (no ``cband_*`` columns) even though the implementation now returns them. Updated the bullet to include ``cband_lower / cband_upper``, document the negative-horizon placebo convention, and document the NaN-on-absent-band behavior. P3 #2: ``TestByPathSupTBands::test_path_sup_t_seed_reproducibility`` only exercised the default ``rademacher`` weight family. Parameterized over ``["rademacher", "mammen", "webb"]`` to pin that the per-path sup-t branch correctly threads ``self.bootstrap_weights`` through ``_generate_psu_or_group_weights`` for all three multiplier families the feature advertises. The existing OVERALL machinery handles all three uniformly, but the per-path surface lacked direct coverage. Each variant must produce a finite, reproducible crit on the standard 3-path fixture. 17 tests pass on TestByPathSupTBands (was 15: +2 new parameterized variants on the existing seed_reproducibility test). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2df79a0 commit 1febbb1

2 files changed

Lines changed: 47 additions & 7 deletions

File tree

diff_diff/chaisemartin_dhaultfoeuille_results.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,10 +1373,19 @@ def to_dataframe(self, level: str = "overall") -> pd.DataFrame:
13731373
- ``"design2"``: Design-2 switch-in/switch-out descriptive
13741374
summary. Available when ``design2=True``.
13751375
- ``"by_path"``: one row per (path, horizon) when
1376-
``by_path=k`` was passed to the estimator. Columns include
1376+
``by_path=k`` was passed to the estimator. Columns:
13771377
``path``, ``frequency_rank``, ``n_groups``, ``horizon``,
13781378
``effect``, ``se``, ``t_stat``, ``p_value``,
1379-
``conf_int_lower``, ``conf_int_upper``, ``n_obs``.
1379+
``conf_int_lower``, ``conf_int_upper``, ``n_obs``,
1380+
``cband_lower``, ``cband_upper``. The ``horizon`` column
1381+
takes negative ints for placebo rows when
1382+
``placebo=True``. The ``cband_*`` columns mirror the
1383+
OVERALL ``level="event_study"`` schema (joint sup-t
1384+
simultaneous bands); they are populated for positive-
1385+
horizon rows of paths with a finite per-path sup-t crit
1386+
(``n_bootstrap > 0``) and NaN otherwise (placebo rows,
1387+
unbanded paths, or the requested-but-empty fallback
1388+
DataFrame).
13801389
13811390
Returns
13821391
-------

tests/test_chaisemartin_dhaultfoeuille.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5661,23 +5661,54 @@ def test_path_sup_t_crit_finite_and_positive(self):
56615661
assert entry["method"] == "multiplier_bootstrap"
56625662
assert entry["n_valid_horizons"] >= 2
56635663

5664-
def test_path_sup_t_seed_reproducibility(self):
5665-
"""Same seed -> bit-identical ``crit_value`` for every path."""
5664+
@pytest.mark.parametrize("bootstrap_weights", ["rademacher", "mammen", "webb"])
5665+
def test_path_sup_t_seed_reproducibility(self, bootstrap_weights):
5666+
"""Same seed -> bit-identical ``crit_value`` for every path,
5667+
across all three multiplier-weight families. Pins that the
5668+
per-path sup-t branch correctly threads ``bootstrap_weights``
5669+
through ``_generate_psu_or_group_weights`` and that
5670+
Rademacher / Mammen / Webb each produce a finite, reproducible
5671+
crit (the helper handles all three uniformly under the
5672+
existing OVERALL sup-t machinery; this is a per-path direct
5673+
regression on that contract)."""
56665674
data = _by_path_three_path_data()
56675675
_est_a, res_a = self._fit_with_bootstrap(
5668-
data, by_path=3, L_max=3, n_bootstrap=200, seed=42
5676+
data,
5677+
by_path=3,
5678+
L_max=3,
5679+
n_bootstrap=200,
5680+
seed=42,
5681+
bootstrap_weights=bootstrap_weights,
56695682
)
56705683
_est_b, res_b = self._fit_with_bootstrap(
5671-
data, by_path=3, L_max=3, n_bootstrap=200, seed=42
5684+
data,
5685+
by_path=3,
5686+
L_max=3,
5687+
n_bootstrap=200,
5688+
seed=42,
5689+
bootstrap_weights=bootstrap_weights,
56725690
)
56735691
assert res_a.path_sup_t_bands is not None
56745692
assert res_b.path_sup_t_bands is not None
56755693
assert set(res_a.path_sup_t_bands.keys()) == set(res_b.path_sup_t_bands.keys())
5694+
# At least one path should produce a finite crit on this fixture
5695+
# (3 paths each with 3 valid horizons under all three weight
5696+
# families); pinning that the new dispatch path actually fires
5697+
# for `mammen` / `webb`, not just `rademacher`.
5698+
assert len(res_a.path_sup_t_bands) >= 1, (
5699+
f"bootstrap_weights={bootstrap_weights}: expected at least "
5700+
f"one path with a finite crit; got empty dict"
5701+
)
56765702
for path in res_a.path_sup_t_bands:
56775703
crit_a = res_a.path_sup_t_bands[path]["crit_value"]
56785704
crit_b = res_b.path_sup_t_bands[path]["crit_value"]
5705+
assert np.isfinite(crit_a), (
5706+
f"bootstrap_weights={bootstrap_weights} path={path}: "
5707+
f"crit_value not finite ({crit_a})"
5708+
)
56795709
assert crit_a == crit_b, (
5680-
f"path={path}: seed-pinned crits diverge: {crit_a} vs {crit_b}"
5710+
f"bootstrap_weights={bootstrap_weights} path={path}: "
5711+
f"seed-pinned crits diverge: {crit_a} vs {crit_b}"
56815712
)
56825713

56835714
def test_path_sup_t_skipped_when_path_has_only_one_valid_horizon(self):

0 commit comments

Comments
 (0)