docs(dml): tutorial 32 - double ML DiD (numbers-locked) - #800
Conversation
Fulfills the CONTRIBUTING new-estimator-checklist tutorial obligation for DMLDiD (TODO row DML PR-B1, removed here) now that survey/cluster support (#796) completed the estimator surface. docs/tutorials/32_dml_did.ipynb (16 md + 15 code cells, committed outputs; executed once via nbconvert on the pure-Python backend): - A 600-unit staggered panel DGP with nonlinear confounding in BOTH nuisances (expit-of-quadratic assignment; nonlinear covariate-dependent trend), engineered so the bias demo is robust: across 30 seeds the linear outcome learner's mean bias is +0.284 (t = 16.9) while sieve and a hand-rolled duck-typed PolynomialRidge learner are unbiased (t = 0.3). At the locked display seed: linear 2.5909 vs truth 2.2388 (4.7 SE off), sieve 2.2804 / PolynomialRidge 2.2818 (within ~1 SE). - The comparison estimand is overall_att with the DGP-implied truth computed from the fit's own per-cell n_treated weights (the panel lane's _aggregate_simple recipe). The narrative presents Chang's moment as a DISTINCT score family from the Sant'Anna-Zhao DR score (REGISTRY DR-score families note; conditional g-hat(X) vs unconditional treated share p-hat) and carries the Theorem-1 caveat: recovery works via double robustness with the propensity deliberately misspecified in every arm, so the table's SEs/CIs are illustrative, not theory-backed. - Cross-fit diagnostics (p_hat / n_clipped / fold losses + the rate-condition caveat; n_clipped=0 framed as no-trimming-required, not established overlap), event-study/group aggregation, HonestDiD (universal-base main fit; relative-magnitude restriction stated in consecutive-first-difference units; robust CI [2.1393, 2.5677] at M=1.0, qualified as an API illustration), and a varying-base bootstrap fit for sup-t bands (universal-base reference cells have structurally NaN bootstrap SEs, so the sup-t demo keeps the default base; point estimates bit-identical; nominal-coverage caveat carried). - panel=False RCS lane (A2.3 warning as the teaching beat) + a brief survey_design= mention (PSU-cohesive folds, df = n_PSU - n_strata = 16), and seed semantics via two explicit seeds (no committed seed=None cell). Registration: index.rst Advanced Methods card + toctree (meta count set to the post-add directory total 33, correcting a pre-existing off-by-one), tutorials README catalog entries for 31 (previously missing) and 32, doc-deps tutorial entries on dml_did.py / dml_did_results.py / _learners.py, CHANGELOG, and a dev-status kernelspec-note correction (10 of 33 committed notebooks carry the plain python3 kernelspec the t31/t32 hygiene tests pin). tests/test_t32_dml_did_drift.py (20 tests, 0.3s): rendered-surface quote pins (including semantic pins for the score-family distinction, RM units, and coverage qualifications), full DGP + fit re-derivation (sections 3/4/6/7 including the survey lane; the sup-t cband golden forces the NumPy multiplier-weight backend, since Rust draws a different equally-valid matrix from the same seed per the REGISTRY weight-backend identity Note — verified under both backends), ALL_CODE_CELL_HASHES source pins with a mutation negative control, source-fragment sync pins for the duplicated DGP/learner, an RCS warning gate that requires exactly one Assumption 2.3 UserWarning and fails on any other warning, and the kernelspec/no-error hygiene test. Verified: nbmake run green on DIFF_DIFF_BACKEND=python; drift suite green under both the Rust and pure-Python backends; docs-IA + doc-deps-integrity green; Sphinx -W build green.
Overall assessment✅ Looks good — no unmitigated P0/P1 findings. Executive summary
Methodology
Code QualityNo findings. PerformanceNo findings. MaintainabilityNo findings. The comprehensive cell hashes and focused numerical re-derivations appropriately support the numbers-locked workflow. Tech DebtNo findings. Removing the tutorial TODO is supported by the delivered notebook, registrations, and drift suite. SecurityNo findings. No secrets or sensitive data were identified. Documentation/Tests
|
…ment the non-copyable-learner fallback PR #800 review P2s: (1) the bootstrap fit keeps the default varying base while the analytical fit is universal-base, so only the overall ATT and post-treatment cells are bit-identical - the prose now says so and flags the differing pre-treatment rows as a base-regime change, not a bootstrap effect; (2) a learner that cannot be deep-copied is not rejected - it is reused across folds with a loud UserWarning relying on fit-reset behavior, so the prose now documents the fallback and the leak risk for stateful non-copyable learners instead of claiming deepcopy is mandatory (_crossfit._fresh_learner contract). Markdown-only; code cells, outputs, and hashes untouched.
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall assessment✅ Looks good — no unmitigated P0/P1 findings. Executive summary
Methodology
Code QualityNo findings. PerformanceNo findings. MaintainabilityNo findings. Tech Debt
SecurityNo findings. Documentation/Tests
|
Summary
docs/tutorials/32_dml_did.ipynb(numbers-locked; committed outputs): the DMLDiD walkthrough required by the new-estimator checklist, now that feat(dml): DMLDiD survey-design and cluster support on both lanes (DML survey PR) #796 completed the survey/cluster surface.PolynomialRidgelearner (2.2818) recover it via double robustness — robustness verified across 30 seeds in prototyping (linear mean bias +0.284, t = 16.9; sieve/poly t = 0.3).panel=FalseRCS lane with its Assumption 2.3 warning as a teaching beat, a briefsurvey_design=example (PSU-cohesive folds, df = n_PSU − n_strata = 16), and seed semantics via two explicit seeds.dml_did.py/dml_did_results.py/_learners.py, CHANGELOG, TODO row DML PR-B1 removed, and a dev-status kernelspec-note correction.tests/test_t32_dml_did_drift.py(20 tests, ~0.3s): rendered-surface quote pins (including semantic pins for the Chang-vs-Sant'Anna-Zhao score-family distinction and the coverage qualifications), full DGP + fit re-derivation for sections 3/4/6/7 (survey lane included),ALL_CODE_CELL_HASHESsource pins with a mutation negative control, source-fragment sync pins for the duplicated DGP/learner, an RCS warning gate requiring exactly one Assumption 2.3UserWarningand failing on any other warning, and kernelspec/no-error hygiene. The sup-t cband golden forces the NumPy multiplier-weight backend (Rust draws a different, equally valid matrix from the same seed per the REGISTRY weight-backend identity Note); the suite passes under both backends.Methodology references (required if estimator / math changes)
Validation
tests/test_t32_dml_did_drift.py(new, 20 tests; green under both the Rust and pure-Python backends).tests/test_docs_ia.py+tests/test_doc_deps_integrity.pygreen after registration.DIFF_DIFF_BACKEND=pythonand verified with a one-notebook nbmake run; every quoted number validated in prototype scripts before locking (30-seed robustness sweep for the bias demo); Sphinx-Wdocs build green.Security / privacy