Skip to content

Add LPDiD non-absorbing R-parity (independent feols Eq.12/13), Phase C2#587

Merged
igerber merged 3 commits into
mainfrom
feature/lpdid-nonabsorbing-rparity
Jun 30, 2026
Merged

Add LPDiD non-absorbing R-parity (independent feols Eq.12/13), Phase C2#587
igerber merged 3 commits into
mainfrom
feature/lpdid-nonabsorbing-rparity

Conversation

@igerber

@igerber igerber commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • Phase C2 of the LPDiD initiative: a self-generated R-parity validation harness for the C1 non-absorbing modes (non_absorbing="first_entry" Eq. 12 / "effect_stabilization" Eq. 13). Verify-and-document PR — no estimator change.
  • Both modes are anchored on an independent fixest::feols reconstruction of the paper's Eq. 12 and Eq. 13 clean-sample restrictions (explicit per-window indexing, derived from the paper). Variance-weighted point and SE match the library to ~1e-13 / ~1e-15; the effect_stabilization reweighted point matches and its SE is pinned as a regression guard (a small weighted-cluster convention difference vs feols). The recipe's independence was demonstrated when an earlier draft's Eq. 12 control off-by-one diverged from the already-correct library and was corrected against the paper, plus a hand-computed Python micro-check.
  • alexCardazzi/lpdid's nonabsorbing_lag is NOT a faithful Eq. 13 (it clamps off-switches via treat_diff[<0]<-0, reuses a forward placebo window, and NA-excludes pre-panel-treated rows where the library clamps pre-min_t to untreated). It diverges ~0.01-0.05 from Eq. 13 even on a monotone no-off-switch panel, so it is recorded in the golden meta as a divergent third-party reference, not a parity gate (the alexCardazzi-pooled precedent from B2). The library's "no treatment change" (both directions) and backward placebo window are the more paper-faithful choices.
  • A separate non-absorbing panel + golden keep the B2 absorbing goldens byte-identical (verified). first_entry (Eq. 12) has no R-package analogue and is anchored on the independent feols recipe only.

Methodology references

  • Method name(s): LPDiD non-absorbing modes (entry-effect estimands) — R-parity validation
  • Paper / source link(s): Dube, A., Girardi, D., Jordà, Ò., & Taylor, A. M. (2025). A Local Projections Approach to Difference-in-Differences. Journal of Applied Econometrics, 40(5), 741-758. https://doi.org/10.1002/jae.70000 (Section 4.2 / online Appendix C; Eq. 12 first-time entry, Eq. 13 effect stabilization). Reference R: an independent fixest::feols reconstruction (primary anchor) + alexCardazzi/lpdid (pinned ba64563, recorded as a divergent reference).
  • Intentional deviations (documented in docs/methodology/REGISTRY.md ## LPDiD Deviation Claude/setup pip install k2 m4j #4 + the golden meta):
    • alexCardazzi nonabsorbing_lag diverges from the paper's Eq. 13 (off-switch clamp + forward placebo window + boundary NA-exclude) → recorded as a divergent reference, not gated; the library is the more paper-faithful.
    • Reweighted effect_stabilization SE: point matches feols; SE pinned (small weighted-cluster convention difference).
    • first_entry (Eq. 12) has no R-package analogue → independent feols recipe + hand-computed Python micro-check.
    • Appendix-C exit-event dynamics and the Stata canonical SE/RA remain deferred follow-ups (TODO.md).

Validation

  • Tests added/updated: tests/test_methodology_lpdid.py — new TestLPDiDNonAbsorbingParityR (4 tests: first_entry point+SE, effect_stabilization point+SE, effect_stabilization reweighted point + pinned SE, and the alexCardazzi-divergence documentation check). 12 methodology tests total (8 absorbing unchanged + 4 new), green on both the pure-Python and Rust backends. Regenerate via Rscript benchmarks/R/generate_lpdid_golden.R (the absorbing B2 goldens are verified byte-identical after regeneration).
  • Backtest / simulation / notebook evidence: N/A — the validation is the R-parity harness (independent feols recipe + the alexCardazzi reference).

Security / privacy

  • Confirm no secrets/PII in this PR: Yes

Generated with Claude Code

…hase C2

Pin the C1 non-absorbing modes (non_absorbing="first_entry"/"effect_stabilization")
against an INDEPENDENT fixest::feols reconstruction of the paper's Eq. 12 / Eq. 13
clean-sample restrictions (explicit per-window indexing, derived from the paper).
Variance-weighted point and SE match the library to ~1e-13/~1e-15; the reweighted
point matches and its SE is pinned as a regression guard (a small weighted-cluster
convention difference vs feols). The recipe's independence was demonstrated when an
earlier draft's Eq. 12 control off-by-one diverged from the already-correct library
and was corrected against the paper, plus a hand-computed Python micro-check.

alexCardazzi/lpdid's nonabsorbing_lag is NOT a faithful Eq. 13 (it clamps
treat_diff[<0]<-0 so its clean-control window blocks only turn-ons; it reuses a
forward placebo window; it NA-excludes pre-panel-treated rows where the library
clamps pre-min_t to untreated). It diverges ~0.01-0.05 from Eq. 13 even on a
monotone no-off-switch panel, so it is recorded in the golden meta as a divergent
third-party reference, not a parity gate (the alexCardazzi-pooled precedent).

Verify-and-document PR, no estimator change. Separate non-absorbing panel + golden
keep the B2 absorbing goldens byte-identical (verified).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Overall Assessment
✅ Looks good — no unmitigated P0/P1 findings.

Executive Summary

  • Affected methods: LPDiD non-absorbing parity validation for first_entry Eq. 12 and effect_stabilization Eq. 13.
  • No estimator implementation changed; the new R recipe matches the documented clean-sample contracts in REGISTRY.md and diff_diff/lpdid.py.
  • Documented deviations from R, including alexCardazzi/lpdid divergence and the reweighted SE pin, are recorded in REGISTRY.md / golden metadata, so they are P3 informational.
  • Minor documentation/evidence cleanup is recommended.
  • I could not run tests locally because this sandbox lacks pytest and pandas.

Methodology

  • Severity: P3 informational
    Impact: The alexCardazzi nonabsorbing_lag divergence and effect_stabilization reweighted SE mismatch are documented deviations, not defects under the review rules. The PR records them in docs/methodology/REGISTRY.md:L1862 and the golden metadata at benchmarks/R/generate_lpdid_golden.R:L453-L471.
    Concrete fix: No required change.

  • Severity: P3
    Impact: The metadata/comment says alexCardazzi diverges “even on a monotone no-off-switch panel,” but the committed generator’s non-absorbing panel includes reversal/re-entry units at benchmarks/R/generate_lpdid_golden.R:L347-L348, and the test only asserts divergence on the mixed committed panel at tests/test_methodology_lpdid.py:L343-L351.
    Concrete fix: Either add a small monotone-only fixture/assertion for that claim, or soften the wording to the evidence actually pinned by this fixture.

Code Quality

  • No findings.

Performance

  • No findings. The added fixture is small and the new tests add a limited number of LPDiD fits.

Maintainability

  • Severity: P3
    Impact: The generator header still lists only the absorbing outputs at benchmarks/R/generate_lpdid_golden.R:L24-L26, but the script now also writes lpdid_nonabsorbing_panel.csv and lpdid_nonabsorbing_golden.json at benchmarks/R/generate_lpdid_golden.R:L352-L354 and L483-L484.
    Concrete fix: Add the two non-absorbing output files to the header comment.

Tech Debt

  • Severity: P3 informational
    Impact: Remaining non-absorbing exit-event dynamics and Stata canonical SE/RA gaps are tracked as deferred work in TODO.md:L120-L121 and docs/methodology/REGISTRY.md:L1882.
    Concrete fix: No required change for this PR.

Security

  • No findings. The new data file appears synthetic and no secrets/PII are introduced.

Documentation/Tests

  • Severity: P3
    Impact: Some user-facing docs still describe PR-C2/R-package parity as a future follow-up after this PR adds it: CHANGELOG.md:L21-L30 and docs/api/lpdid.rst:L21-L29. This is confusing but not a correctness issue.
    Concrete fix: Update those lines to say R parity is now covered for the non-absorbing entry-effect event-study paths, while exit dynamics, Stata canonical SE/RA, and survey support remain deferred.

…le notes

- Pin the "alexCardazzi diverges even without off-switches" claim with committed
  evidence: the generator now records `meta.alex_monotone_post_divergence` (max
  |alex - Eq.13| over post horizons on the monotone no-off-switch sub-panel,
  0.0344), and the test asserts it > 1e-3. The divergence is not only off-switch
  handling.
- Generator header lists the two new non-absorbing output files.
- Drop the now-stale "R-package parity (PR-C2) is a follow-up" wording from the C1
  CHANGELOG entry and docs/api/lpdid.rst (this work adds it); exit-event dynamics,
  the Stata canonical SE, and survey support remain the deferred follow-ups.

Absorbing B2 goldens remain byte-identical after regeneration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: b29cc455afd0df2dc23abeea1010ec19cc4765a3


Overall Assessment

✅ Looks good — no unmitigated P0/P1 findings.

Executive Summary

  • Affected methods: LPDiD non-absorbing validation for first_entry Eq. 12 and effect_stabilization Eq. 13.
  • No estimator implementation changed; the new R harness and tests target parity evidence only.
  • The independent feols clean-sample recipe matches the documented Eq. 12/13 contracts in docs/methodology/REGISTRY.md:L1831-L1841 and the implementation masks in diff_diff/lpdid.py:L291-L320.
  • Prior P3 items appear addressed: generator output header is updated, API/changelog no longer call PR-C2 future work, and the monotone alex divergence is now pinned in metadata/tests.
  • I could not run tests locally because this sandbox lacks pytest and numpy.

Methodology

  • Severity: P3 informational
    Impact: The alexCardazzi/lpdid divergence and effect_stabilization reweighted SE mismatch are explicitly documented deviations, not defects under the review rules. The registry labels this under **Deviation from R:** at docs/methodology/REGISTRY.md:L1862, and the golden metadata records the divergent alex reference plus the reweighted-SE convention difference at benchmarks/data/lpdid_nonabsorbing_golden.json:L12-L17.
    Concrete fix: No required change.

Code Quality

No findings.

Performance

No findings. The new fixture is small, and the added parity tests fit a bounded set of event-study horizons.

Maintainability

No findings. The generator now documents both absorbing and non-absorbing outputs at benchmarks/R/generate_lpdid_golden.R:L24-L28.

Tech Debt

  • Severity: P3 informational
    Impact: Remaining non-absorbing exit-event dynamics, interior-gap support, and external Stata-only SE/RA validation are tracked in TODO.md:L97-L98 and TODO.md:L120-L121, so they are not blockers for this PR.
    Concrete fix: No required change.

Security

No findings. The added CSV fixture appears synthetic and contains no secrets or PII.

Documentation/Tests

  • Severity: P3
    Impact: One registry edge-case sentence still says the non-absorbing pre-panel boundary convention is “an assumption to confirm against R in PR-C2” even though PR-C2 now documents the convention and alex divergence at docs/methodology/REGISTRY.md:L1862 and benchmarks/data/lpdid_nonabsorbing_golden.json:L16-L17. This is stale wording, not a correctness issue.
    Concrete fix: Update docs/methodology/REGISTRY.md:L1853 to say PR-C2 documented the boundary convention as a known alex divergence.

…review P3)

REGISTRY's non-absorbing edge-case bullet said the pre-panel boundary convention
was "an assumption to confirm against R in PR-C2"; PR-C2 has now documented it as
a known divergence from alexCardazzi (which NA-excludes such first-rows). Point to
Deviation #4 instead of the now-complete forward reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: e8eb117dfab8c2eecd765c6defd410c933f902b6


Overall Assessment

✅ Looks good — no unmitigated P0/P1 findings.

Executive Summary

  • Affected methods: LPDiD non-absorbing entry-effect validation for first_entry Eq. 12 and effect_stabilization Eq. 13.
  • No estimator implementation changes are introduced; this PR adds R/golden validation fixtures and docs.
  • The independent feols recipe’s clean-sample masks align with the Methodology Registry’s Eq. 12/13 contract and the existing implementation masks.
  • The prior stale registry wording is resolved at docs/methodology/REGISTRY.md:L1853.
  • I could not run pytest locally: pytest is not installed, and a direct Python smoke check also failed because numpy is unavailable.

Methodology

  • Severity: P3 informational
    Impact: The alexCardazzi/lpdid nonabsorbing_lag mismatch and the effect_stabilization reweighted-SE convention difference are documented deviations, not defects under the review rules. The registry labels this as Deviation from R at docs/methodology/REGISTRY.md:L1862, and the golden metadata records the same at benchmarks/data/lpdid_nonabsorbing_golden.json:L12-L17.
    Concrete fix: No action required.

  • Severity: P3 informational
    Impact: The R reconstruction at benchmarks/R/generate_lpdid_golden.R:L365-L418 matches the registry’s Eq. 12/13 sample definitions at docs/methodology/REGISTRY.md:L1831-L1841 and the implementation masks at diff_diff/lpdid.py:L267-L327.
    Concrete fix: No action required.

Code Quality

No findings.

Performance

No findings. The new committed fixture is small: 840 rows across 60 units.

Maintainability

No findings. The generator documents the separate absorbing/non-absorbing outputs at benchmarks/R/generate_lpdid_golden.R:L24-L28.

Tech Debt

  • Severity: P3 informational
    Impact: Remaining LPDiD non-absorbing exit-event dynamics, interior-gap support, and Stata-only SE/RA external-reference gaps are tracked in TODO.md:L97-L98 and TODO.md:L120-L121, and the registry also marks exit-event/Stata canonical follow-up at docs/methodology/REGISTRY.md:L1882.
    Concrete fix: No action required for this PR.

Security

No findings. The added CSV fixture appears synthetic and contains no secrets or PII.

Documentation/Tests

  • Severity: P3 informational
    Impact: Prior stale documentation has been addressed: the boundary-convention text now says PR-C2 documented the known alexCardazzi/lpdid divergence at docs/methodology/REGISTRY.md:L1853. New tests gate first_entry point+SE, effect_stabilization point+SE, reweighted point plus pinned SE, and the documented alex divergence at tests/test_methodology_lpdid.py:L290-L355.
    Concrete fix: No action required.

  • Severity: P3 informational
    Impact: I could not execute the test suite in this sandbox because pytest is missing, and direct import-based verification also failed due to missing numpy.
    Concrete fix: Run pytest -q tests/test_methodology_lpdid.py in the project’s normal test environment.

@igerber igerber added the ready-for-ci Triggers CI test workflows label Jun 29, 2026
@igerber igerber merged commit 21f0c30 into main Jun 30, 2026
31 of 32 checks passed
@igerber igerber deleted the feature/lpdid-nonabsorbing-rparity branch June 30, 2026 10:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-ci Triggers CI test workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant