Skip to content

Commit a351e2e

Browse files
igerberclaude
andcommitted
Fix CI review R3: add l_vec to compute_honest_did, end-to-end tests
- Add l_vec parameter to compute_honest_did() so the advertised custom-target path actually works (was missing from wrapper) - Add test_honest_did_custom_l_vec_on_impact: l_vec=[1,0] targets on-impact effect, asserts original_estimate matches DID_1 - Add test_honest_did_with_trends_nonparam: end-to-end trends_nonparam + honest_did=True integration Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ad7698c commit a351e2e

File tree

2 files changed

+51
-1
lines changed

2 files changed

+51
-1
lines changed

diff_diff/honest_did.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2669,6 +2669,7 @@ def compute_honest_did(
26692669
method: str = "relative_magnitude",
26702670
M: float = 1.0,
26712671
alpha: float = 0.05,
2672+
l_vec: Optional[np.ndarray] = None,
26722673
) -> HonestDiDResults:
26732674
"""
26742675
Convenience function for computing Honest DiD bounds.
@@ -2683,6 +2684,12 @@ def compute_honest_did(
26832684
Restriction parameter.
26842685
alpha : float
26852686
Significance level.
2687+
l_vec : np.ndarray, optional
2688+
Weight vector defining the scalar target ``theta = l_vec' tau``
2689+
over post-treatment horizons. Length must equal the number of
2690+
post-treatment periods. ``None`` (default) uses equal weights
2691+
(uniform average). To target the on-impact effect only (R's
2692+
default), pass ``np.array([1, 0, ..., 0])``.
26862693
26872694
Returns
26882695
-------
@@ -2694,7 +2701,7 @@ def compute_honest_did(
26942701
>>> bounds = compute_honest_did(event_study_results, method='relative_magnitude', M=1.0)
26952702
>>> print(f"Robust CI: [{bounds.ci_lb:.3f}, {bounds.ci_ub:.3f}]")
26962703
"""
2697-
honest = HonestDiD(method=method, M=M, alpha=alpha)
2704+
honest = HonestDiD(method=method, M=M, alpha=alpha, l_vec=l_vec)
26982705
return honest.fit(results)
26992706

27002707

tests/test_chaisemartin_dhaultfoeuille.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3385,6 +3385,49 @@ def test_honest_did_original_estimate_is_post_average(self):
33853385
avg = np.mean([es[h]["effect"] for h in sorted(es.keys())])
33863386
np.testing.assert_allclose(hd.original_estimate, avg, rtol=1e-10)
33873387

3388+
def test_honest_did_custom_l_vec_on_impact(self):
3389+
"""compute_honest_did with l_vec=[1,0] targets on-impact effect."""
3390+
from diff_diff.honest_did import compute_honest_did
3391+
3392+
df = self._make_data()
3393+
with warnings.catch_warnings():
3394+
warnings.simplefilter("ignore")
3395+
r = ChaisemartinDHaultfoeuille(seed=1).fit(
3396+
df, "outcome", "group", "period", "treatment",
3397+
L_max=2,
3398+
)
3399+
# l_vec=[1, 0] targets only DID_1 (on-impact, R's default)
3400+
bounds = compute_honest_did(r, l_vec=np.array([1.0, 0.0]))
3401+
np.testing.assert_allclose(
3402+
bounds.original_estimate,
3403+
r.event_study_effects[1]["effect"],
3404+
rtol=1e-10,
3405+
)
3406+
3407+
def test_honest_did_with_trends_nonparam(self):
3408+
"""End-to-end trends_nonparam + honest_did=True."""
3409+
rng = np.random.RandomState(42)
3410+
rows = []
3411+
for g in range(40):
3412+
state = g % 4
3413+
switches = g < 20
3414+
for t in range(7):
3415+
d = 1 if (switches and t >= 3) else 0
3416+
y = 10 + 2.0 * t + 5.0 * d + rng.normal(0, 0.5)
3417+
rows.append({
3418+
"group": g, "period": t, "treatment": d,
3419+
"outcome": y, "state": state,
3420+
})
3421+
df = pd.DataFrame(rows)
3422+
with warnings.catch_warnings():
3423+
warnings.simplefilter("ignore")
3424+
r = ChaisemartinDHaultfoeuille(seed=1).fit(
3425+
df, "outcome", "group", "period", "treatment",
3426+
L_max=2, trends_nonparam="state", honest_did=True,
3427+
)
3428+
assert r.honest_did_results is not None
3429+
assert np.isfinite(r.honest_did_results.ci_lb)
3430+
33883431

33893432
# =============================================================================
33903433
# Summary Phase 3 Rendering

0 commit comments

Comments
 (0)