Add doubly robust covariates path to EfficientDiD#225
Conversation
Implements Phase 2 of the Chen, Sant'Anna & Xie (2025) methodology: when covariates are provided, uses outcome regression (OLS) and propensity score ratios (logit) for doubly robust ATT estimation. The estimator is consistent if either the outcome model or the propensity model is correctly specified. New module efficient_did_covariates.py contains the DR math functions. The no-covariates closed-form path remains unchanged as the default. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
Overall Assessment ⛔ Blocker Executive Summary
Methodology
Code Quality No additional findings beyond the methodology blocker above. Performance No separate performance finding. Fixing the Maintainability
Tech Debt
Security No findings. Documentation/Tests
Path to Approval
|
…rim to results
P0 fix: estimate_propensity_ratio() now derives D labels from
mask_g[combined_mask] instead of concatenating ones/zeros, ensuring
correct alignment regardless of unit ordering. Also short-circuits
r_{g,g}(X) = 1 for same-cohort comparisons under PT-All.
P1 fix: pscore_trim is now preserved in EfficientDiDResults, matching
the staggered_results pattern for parameter traceability.
P1 fix: add shuffled-unit regression test that would have caught the
label bug, plus aggregation tests (event_study, group, all) and
PT-All bootstrap test for full covariate-path coverage.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
/ai-review |
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
Path to Approval
|
…ng DGP - Document propensity-ratio constant-fallback as deviation in REGISTRY.md - Fix confounding DGP: X-dependent trends apply to all groups (not just treated), x1 distribution shifts by group; conditional PT now holds - Test asserts DR bias < nocov bias against known true ATT - Add logit-failure regression test via mock - Clarify docstring: covariate path uses unconditional Omega*, does not achieve full semiparametric efficiency bound - Fix pre-existing E402 lint: move edid_dgp import to top of file Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
/ai-review |
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment ✅ Looks good Executive Summary
Methodology
Code Quality No findings. Performance No findings. Maintainability No findings. Tech Debt
Security No findings. Documentation/Tests
|
Implements paper-faithful Section 4 nuisance estimation: Sieve-based propensity ratio (Eq 4.1-4.2): - Closed-form linear system per polynomial degree K - AIC/BIC model selection across sieve dimensions - Ratio clipping to [1/ratio_clip, ratio_clip] for positivity - Eliminates logit convergence/separation issues entirely Kernel-smoothed conditional Omega*(X) (Eq 3.12): - Nadaraya-Watson estimator with Gaussian kernel and local means - Silverman bandwidth by default, user-overridable - Per-unit efficient weights w(X_i) from Omega*(X_i) inverse - Plug-in EIF valid under Neyman orthogonality (Remark 4.2) New params: sieve_k_max, sieve_criterion, ratio_clip, kernel_bandwidth Removed: pscore_trim (replaced by ratio_clip) Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
/ai-review |
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment ⛔ Blocker Executive Summary
Methodology Affected method:
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
Path to Approval
|
P0 fix: EIF now centers on scalar ATT (EIF_i = w_i @ gen_out_i - ATT) instead of per-pair means, ensuring mean(EIF) ≈ 0 when weights vary by unit. Added unit test verifying mean-zero property. P1 fix: Add overlap warning when sieve ratios require clipping, with test asserting the warning fires on near-separation covariates. P1 fix: Document unconditional-pi Omega* as deviation in REGISTRY.md; downgrade "efficient" claim in docstring for covariate path. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
/ai-review |
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment Executive Summary
Methodology Affected method:
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
Path to Approval
|
Validate kernel_bandwidth (None or finite > 0), ratio_clip (finite > 1.0), and sieve_k_max (None or positive int) in _validate_params(). Rejects NaN, Inf, zero, and negative values with clear ValueError messages. Add pytest.raises tests for all invalid parameter combinations. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
/ai-review |
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment ✅ Looks good Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
|
…ep 4)
Replace unconditional cohort fractions pi_g in Omega*(X) with per-unit
sieve-estimated inverse propensities s_hat_{g'}(X) = 1/p_{g'}(X), matching
Eq 3.12 and the paper's algorithm step 4.
The inverse propensity sieve uses the same polynomial basis and AIC/BIC
selection as the ratio estimator. FOC: (Psi_{g'}' Psi_{g'}) beta =
Psi_all.sum(axis=0) — same closed-form linear system structure.
This eliminates the last documented methodology deviation from the paper.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
/ai-review |
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment Executive Summary
Methodology Affected method(s): Efficient Difference-in-Differences with covariates, especially the DR nuisance-estimation and efficient-weighting path.
Code Quality No findings. Performance No findings. Maintainability No findings. Tech Debt No findings. Security No findings. Documentation/Tests
Path to Approval
|
P1 fix: estimate_inverse_propensity_sieve() now emits UserWarning on all-K fallback (matching the ratio sieve pattern). Documented in REGISTRY.md alongside the existing ratio fallback note. Add TestSieveFallbacks class with targeted tests forcing tiny groups (n_basis >= n_group) for both ratio and inverse propensity sieve helpers. Extend EfficientDiDResults attribute docs to cover estimation_path, sieve_k_max, sieve_criterion, ratio_clip, and kernel_bandwidth. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
/ai-review |
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
Path to Approval
|
Symmetric with the inverse propensity fallback: emit UserWarning when no sieve candidate succeeds and ratio falls back to constant 1. Updated test to assert the warning fires. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
/ai-review |
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment ✅ Looks good Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
|
Broaden sieve fallback descriptions from "singular basis matrix" / "exception-triggered" to "when no sieve degree K succeeds" — covers both the n_basis >= n_group early-exit and singular system cases. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
/ai-review |
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment Executive Summary
Methodology
Path to Approval
Code Quality No findings. Performance No findings. Maintainability No findings. Tech Debt No findings. Security No findings. Documentation/Tests No findings in the added test coverage. Static review only: I could not execute |
…ocstrings The paper's Section 4 describes flexible nonparametric nuisance estimation for m_hat, while our implementation uses linear OLS. Add REGISTRY.md note and align class/module docstrings: the DR property ensures consistency, but OLS does not generically guarantee the semiparametric efficiency bound unless the conditional mean is linear in covariates. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
/ai-review |
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment Executive Summary
Methodology
Code Quality No findings. Performance No findings. Maintainability No findings. Tech Debt No additional findings. I did not find a matching mitigation entry under TODO.md:44, so the P1 above remains untracked and unmitigated. Security No findings. Documentation/Tests
Path to Approval
|
…string P1 fix: estimate_inverse_propensity_sieve() now warns when s_hat values are clipped to [1, n], matching the ratio path's overlap diagnostics. Documented in REGISTRY.md. P3 fix: module-level docstring in efficient_did.py now qualifies the covariate path, consistent with the class docstring and REGISTRY.md. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
/ai-review |
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment✅ Looks good Executive Summary
MethodologyAffected method: No unmitigated methodology findings. The step-4 clipping behavior is now both warned and documented, so under the review rubric it is no longer an undocumented deviation. The remaining changed estimator pieces I checked are consistent with the source material and the Methodology Registry. (cowles.yale.edu) Code QualityNo findings. PerformanceNo findings. MaintainabilityNo findings. Tech DebtNo findings. SecurityNo findings. Documentation/Tests
Validation note: static review only. I could not run the test suite here because |
Summary
efficient_did_covariates.pywith pure-function DR math: outcome regression via OLS, propensity score ratios via logit, per-unit generated outcomes (Eq 4.4), unconditional Omega*, and EIF computationcovariates=["x1", "x2"]is passed tofit(), automatically uses the DR path;covariates=None(default) uses the existing no-covariates closed-form path unchangedpscore_trimparameter with overlap diagnostics via_check_propensity_diagnostics()estimation_pathfield toEfficientDiDResults("nocov"or"dr")Methodology references (required if estimator / math changes)
Validation
tests/test_efficient_did.py(17 new tests),tests/helpers/edid_dgp.py(extended)Security / privacy
Generated with Claude Code