@@ -1469,6 +1469,57 @@ def test_plot_event_study_propagates_weights_kwarg(self) -> None:
14691469 "leads."
14701470 )
14711471
1472+ def test_plot_event_study_cohort_share_to_cell_round_trip_restores_placebo_leads (
1473+ self ,
1474+ ) -> None :
1475+ """CI R2 P1 fix: ``plot_event_study()`` reverse direction (cohort_share → cell).
1476+
1477+ Codex caught a stale-cache hazard: my earlier fix re-aggregated
1478+ on ``weights="cohort_share"`` but skipped re-aggregation on the
1479+ default ``weights="cell"`` path when the cached ``event_study_effects``
1480+ was already populated. A user calling ``plot_event_study(weights=
1481+ "cohort_share")`` (which restricts to k>=0) and then
1482+ ``plot_event_study()`` (default cell weights) would silently
1483+ plot the stale cohort-share-keyed data. The fix unconditionally
1484+ re-aggregates on every call.
1485+
1486+ This test exercises the reverse direction by:
1487+ 1. First call: ``plot_event_study(weights="cohort_share")`` —
1488+ caches cohort-share keys (k >= 0 only)
1489+ 2. Second call: ``plot_event_study()`` (default cell weights) —
1490+ must restore the full event range including k < 0 placebo leads
1491+ """
1492+ from unittest .mock import patch
1493+
1494+ rng = np .random .default_rng (_BASE_SEED_SECTION8 + 16 )
1495+ panel = _make_three_cohort_four_period_panel (rng , n_per_cohort = 80 , sigma = 0.05 )
1496+ with warnings .catch_warnings ():
1497+ warnings .filterwarnings ("ignore" , category = UserWarning )
1498+ res = WooldridgeDiD (method = "ols" , control_group = "never_treated" ).fit (
1499+ panel , outcome = "y" , unit = "unit" , time = "time" , cohort = "cohort"
1500+ )
1501+ # Step 1: plot under cohort_share — caches k>=0 keys
1502+ with warnings .catch_warnings ():
1503+ warnings .filterwarnings ("ignore" , category = UserWarning )
1504+ with patch ("diff_diff.visualization.plot_event_study" ):
1505+ res .plot_event_study (weights = "cohort_share" )
1506+ assert res .event_study_effects is not None
1507+ cohort_share_keys = sorted (res .event_study_effects .keys ())
1508+ assert all (k >= 0 for k in cohort_share_keys ), (
1509+ f"DGP precondition: cohort_share path must restrict to k>=0; "
1510+ f"got { cohort_share_keys } "
1511+ )
1512+ # Step 2: plot under default weights="cell" — must restore k<0 leads
1513+ with patch ("diff_diff.visualization.plot_event_study" ):
1514+ res .plot_event_study () # default weights="cell"
1515+ assert res .event_study_effects is not None
1516+ cell_keys = sorted (res .event_study_effects .keys ())
1517+ assert any (k < 0 for k in cell_keys ), (
1518+ f"plot_event_study() (cell weights) after a cohort_share "
1519+ f"call must restore k<0 placebo leads; got { cell_keys } . "
1520+ f"Stale cohort_share cache was reused — CI R2 P1 fix regressed."
1521+ )
1522+
14721523 def test_cohort_trends_true_plus_bootstrap_preserves_bootstrap_se (self ) -> None :
14731524 """R5 P1 fix: ``cohort_trends=True`` + ``n_bootstrap > 0`` runs cleanly.
14741525
0 commit comments