@@ -96,6 +96,20 @@ All notable changes to StatsPAI will be documented in this file.
9696
9797### Fixed
9898
99+ - ** ⚠️ Correctness fix (pandas ≥ 3.0): ` sp.horowitz_manski ` bounds silently
100+ collapsed to ` 0.0 ` /` 0.0 ` when a covariate stratum mapped to NaN.** The
101+ internal ` _create_strata ` helper discretises continuous covariates with
102+ ` pd.qcut ` , then cast the bin labels with ` .astype(str) ` . On a degenerate
103+ covariate (e.g. a constant column, where ` qcut(..., duplicates='drop') `
104+ yields all-NaN) pandas < 3.0 stringified NaN to ` "nan" ` — accidentally
105+ forming one valid stratum — but pandas ≥ 3.0 preserves it as ` <NA> ` , so every
106+ per-stratum mask matched nothing and the bounds summed to zero with no error.
107+ NaN strata are now bucketed into an explicit ` -1 ` sentinel stratum, recovering
108+ the correct closed-form bounds. The analytic guard
109+ (` tests/test_tierD_bounds_analytic.py::TestHorowitzManskiAnalytic::test_single_stratum_matches_closed_form ` )
110+ pins the single-stratum case to the closed form on both pandas 2.x and 3.x.
111+ Numerics are unchanged on pandas < 3.0.
112+
99113- ** ⚠️ Functionality fix: ` sp.blp ` was non-functional on every estimation
100114 path.** The GMM objective called ` _gmm_objective(..., maxiter=1000) ` but the
101115 parameter is named ` maxiter_inner ` , so every ` sp.blp ` call raised
@@ -344,9 +358,9 @@ All notable changes to StatsPAI will be documented in this file.
344358 the seed-42 fixture — ` |Δ coefficient| = 1.1e-16 ` and `|Δ standard error| =
345359 1.4e-17` , i.e. one float64 unit in the last place. ` doubleml` remains * not*
346360 a runtime dependency. The measured numbers, software versions, and the
347- divergence discussion are recorded in a new * Double Machine Learning Parity *
348- section of ` docs/joss_validation_dossier .md ` , with a one-command reproduce
349- path added to ` docs/joss_reviewer_guide.md ` . Verified by installing the
361+ divergence discussion are recorded in the source-audit evidence trail
362+ ( ` docs/jss_source_audit_dossier .md ` plus the parity artifacts under
363+ ` tests/external_parity/ ` ) . Verified by installing the
350364 extra and running both ` tests/external_parity/test_dml_python_parity.py ` and
351365 ` tests/reference_parity/test_dml_parity.py ` (55 DML tests green).
352366
@@ -639,8 +653,8 @@ All notable changes to StatsPAI will be documented in this file.
639653
640654### Docs
641655
642- - Reviewer-facing validation docs (` docs/joss_reviewer_guide .md ` ,
643- ` docs/joss_validation_dossier .md` , ` README.md ` ) refreshed: the focused
656+ - Reviewer-facing validation docs (` docs/jss_source_audit_dossier .md ` ,
657+ ` Paper-JSS/README .md` , ` README.md ` ) refreshed: the focused
644658 reviewer follow-up regression command is documented, the
645659 ` tests/test_joss_reviewer_followups.py ` compatibility path is restored for
646660 the public review thread (delegating to
@@ -683,16 +697,17 @@ All notable changes to StatsPAI will be documented in this file.
683697 longer the validation estimand. Users who reported a causal-forest ATE
684698 from ` average_treatment_effect ` should re-run.
685699 - On a clean-overlap DGP (` e(X)∈[0.30,0.70] ` , known ATE = 1) the AIPW
686- ATE recovers the truth within 1.5 SE and agrees with ` grf ` at
687- ` rel = 0.037 ` (` z = 0.69 ` combined SE). For the JSS source snapshot,
700+ ATE recovers the truth within 0.2 SE and agrees with ` grf ` at
701+ ` rel = 0.001 ` (` z = 0.019 ` combined SE); the ATT agrees at
702+ ` rel = 0.024 ` (` z = 0.40 ` ). For the JSS source snapshot,
688703 ` 13_causal_forest ` is now a T3 combined-Monte-Carlo-error pass:
689704 the row is like-for-like AIPW versus ` grf ` and is graded against
690705 combined sampling error, not sold as deterministic machine-precision
691- equality. The strictness-tier denominator is
692- ` 12 / 27 / 10 / 2 on the 51 R-joined modules` : the forest row shares
693- the methodological/T4 bucket with the remaining documented classical-SCM
694- non-uniqueness gap, but it is the only row in that bucket graded as a
695- T3 combined-Monte-Carlo-error pass .
706+ equality. The strictness-tier denominator is
707+ `41 / 12 / 1 / 1 on the 55 R-joined modules`: the forest row is now
708+ the only moderate-stochastic T3 row, and the remaining
709+ methodological/T4 bucket is the documented classical-SCM
710+ non-uniqueness/reference-disagreement gap .
696711 - Guards: ` tests/reference_parity/test_causal_forest_aipw_recovery.py `
697712 (recovery against truth, no R needed) and the tightened
698713 ` tests/reference_parity/test_grf_parity.py ` (combined-SE parity vs a
@@ -703,8 +718,10 @@ All notable changes to StatsPAI will be documented in this file.
703718 module ` tests/r_parity/52_scm_unique ` : on a DGP whose synthetic-control
704719 weights are uniquely identified (treated unit exactly a convex
705720 combination of donors in the pre-period), ` sp.synth(method="classic") `
706- recovers the exact weights and gap (pre-RMSE = 0) and agrees with
707- ` Synth::synth ` to 0.7 %. For the ambiguous Basque-data row, the parity
721+ recovers the exact weights and gap (pre-RMSE = 0) and agrees with
722+ `Synth::synth` at machine-level point precision after fixing the
723+ predictor-weight vector and tightening the inner `ipop` QP controls.
724+ For the ambiguous Basque-data row, the parity
708725 harness keeps the native default visible as a documented
709726 donor-weight-non-uniqueness/reference-disagreement gap. On the same ADH
710727 special-predictor specification, native StatsPAI tracks Stata ` synth `
@@ -793,22 +810,22 @@ All notable changes to StatsPAI will be documented in this file.
793810 than only frozen.
794811- ** ` sp.validation_report(collect_tests=True) ` ** — shells out to
795812 ` pytest --collect-only ` and returns the authoritative, parametrize-expanded
796- parity test counts (124 reference-parity, 50 external-parity, 12 coverage
813+ parity test counts (124 reference-parity, 52 external-parity, 12 coverage
797814 Monte Carlo on the current source snapshot); a regression test pins those three to the
798815 JSS manuscript headline so a parity test added/removed without updating the
799816 paper fails CI. Default ` validation_report() ` path is unchanged (fast,
800817 metadata-only).
801818- ** Strictness-tier breakdown in the Track A parity tables
802819 (` tests/r_parity/compare.py ` )** — each module is classified by its
803- registered point-estimate tolerance into machine / iterative / moderate /
804- methodological/ T4 tiers (12 / 27 / 10 / 2 on the 51 R-joined modules), shown
805- in the Markdown ledger and the LaTeX appendix caption so a machine-precision
806- match is not flattened together with a deliberately loose
820+ registered point-estimate tolerance into machine-level / iterative /
821+ moderate / methodological- T4 tiers (41 / 12 / 1 / 1 on the 55
822+ R-joined modules), shown in the Markdown ledger and the LaTeX appendix
823+ caption so a machine-level point-estimate match is not flattened together with a deliberately loose
807824 stochastic or documented-convention tolerance.
808825- ** Stata leg brought to the same rigor as R (` tests/stata_parity/ ` )** —
809826 ` _common.do ` now writes an inline ` provenance ` block (engine version,
810827 edition, OS) onto every ` *_Stata.json ` ; ` verify_reproduce_stata.py ` re-runs
811- each ` .do ` on the committed CSV bytes and confirms all 44 Stata modules
828+ each ` .do ` on the committed CSV bytes and confirms all 53 Stata modules
812829 reproduce ** bit-for-bit** (worst rel 0) under Stata 18 MP, including the
813830 iterative-optimiser commands (` set seed 42 ` + deterministic solvers);
814831 ` _capture_stata_env.do ` + ` _gen_stata_env.py ` pin the engine and the
@@ -822,7 +839,7 @@ All notable changes to StatsPAI will be documented in this file.
822839
823840### Changed — Track A R golden values regenerated under the locked environment
824841
825- - The current R parity ledger covers 51 rendered R-joined modules under
842+ - The current R parity ledger covers 55 rendered R-joined modules under
826843 R 4.5.2 with the ` renv.lock ` package set so each is self-describing.
827844 The material parity-status movement is the added ` 52_scm_unique `
828845 counterpart for classical SCM: an identified synthetic-control DGP now
0 commit comments