test: anchor LPDiD regression-adjustment SE against Stata teffects (first Stata parity arm)#709
Conversation
Overall assessment: ✅ Looks goodExecutive summary
Methodology
The post/pre clean-control filters and Code QualityNo findings. PerformanceNo findings. The library fit is module-scoped and reused across parameterized tests. MaintainabilityNo findings. Tech Debt
SecurityNo secrets, unsafe external commands, or sensitive data found. Documentation/Tests
Static syntax, JSON validity, diff hygiene, and documentation dependency paths passed. |
…irst Stata parity arm) The LP-DiD (Dube, Girardi, Jorda & Taylor 2025) regression-adjustment SE carries no finite-sample factor and has no runnable R reference (alexCardazzi/lpdid does direct covariate inclusion, not RA). It was previously pinned against itself and MC-coverage-validated; REGISTRY LPDiD Deviation 2 justified the no-factor convention by *inferring* from comments in the authors' .do files that Stata `teffects` applies no factor. This adds the repo's first Stata parity arm to convert that inference into a measurement. `benchmarks/stata/generate_lpdid_ra_golden.do` independently reconstructs each horizon's clean sample (porting the R prep/clean_h recipe to tsset + L./F.; tsfill mirrors R fill_gaps() across the deliberate interior gap) and runs `teffects ra (Dy x i.time) (tdiff), atet vce(cluster unit)`. The library RA IF SE matches teffects to ~1e-16 at all 7 event-study horizons; the SE ratio is a flat 1.0 (no G/(G-1), no (n-1)/(n-k)), confirming the convention. teffects is native to Stata (no SSC dependency), so `version 19` pins the numerical behavior; the running edition is derived from c(MP)/c(SE). Stata is node-locked and cannot run in CI, so -- exactly like the R arm -- the golden is committed and the test skips when absent; the CI-runnable guards (RA_SE_PIN + coverage_lpdid_ra.py) are unchanged. tests/test_lpdid_ra_stata_parity.py (29 cases) gates per horizon, each as an absolute bound (rel=0): Stata point vs the R-anchored ra_cov[h][0] (1e-10) AND a direct library-ATET vs Stata-ATET check (1e-6 cross-platform, so Python<->Stata point agreement is direct, not transitive through R); Stata (e(N), e(N_clust)) vs the library (n_obs, n_clusters) (exact, to narrow the "point matches on a different sample" hole); and library SE vs teffects SE (1e-7 cross-platform; local ~1e-16). Together these strongly corroborate -- not formally prove -- the same clean sample. No diff_diff/ source changed. Docs updated: REGISTRY Deviation 2 (inferred -> measured), DEFERRED.md row (resolved; optional R follow-up only), dube-2025-review.md, benchmarks.rst + benchmarks/README.md (Stata arm), doc-deps.yaml (register benchmarks/stata/README.md), CHANGELOG. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X9m4hRxwqRnEJsv5ZhHY3i
6e9ece3 to
cf1cd76
Compare
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall assessment: ✅ Looks goodExecutive summary
Methodology
Code QualityNo findings. PerformanceNo findings. The library fit is module-scoped and reused. MaintainabilityNo findings. Tech Debt
SecurityNo secrets, unsafe external commands, or sensitive data found. Documentation/Tests
Static Python syntax, JSON validity, diff hygiene, sample counts, and committed numerical anchors passed. Full pytest execution was unavailable because the review environment lacks NumPy, pandas, SciPy, and pytest. |
Summary
benchmarks/stata/generate_lpdid_ra_golden.doindependently reconstructs each horizon's clean sample (porting the Rprep/clean_hrecipe totsset+L./F.;tsfillmirrors Rfill_gaps()across the deliberate interior gap) and runsteffects ra (Dy x i.time) (tdiff), atet vce(cluster unit).teffectsto ~1e-16 at all 7 event-study horizons; the SE ratio is a flat 1.0 (noG/(G-1), no(n-1)/(n-k)) — confirming the no-finite-sample-factor convention.teffectsis native to Stata (no SSC dependency), soversion 19pins numerical behavior. Stata is node-locked and cannot run in CI, so — exactly like the R arm — the golden is committed and the test skips when absent; the CI-runnable guards (RA_SE_PIN+coverage_lpdid_ra.py) are unchanged.diff_diff/source changed.Methodology references
LPDiDregression-adjustment event-study SE (covariate path,reweight=True).teffects ra (Dhy i.time) (dtreat) if D.treat==1 | Fh.treat==0, atet vce(cluster unit). Reference implementation: Statateffects ra(native)..docomments) to measured: the no-finite-sample-factor RA convention is now confirmed against a runnableteffectsto machine precision.Validation
tests/test_lpdid_ra_stata_parity.py(22 cases) — three gates per horizon, each a purely absolute bound: point vs the R-anchoredra_cov[h][0](1e-10), Stata(e(N), e(N_clust))vs the library(n_obs, n_clusters)(exact), and library SE vsteffectsSE (1e-7 cross-platform; ~1e-16 local). Strong corroboration of the same realized clean sample (not a formal membership proof).RA_SE_PIN) and doc-IA / doc-deps / refresh gates all pass.Security / privacy
🤖 Generated with Claude Code
https://claude.ai/code/session_01X9m4hRxwqRnEJsv5ZhHY3i