v0.6.9¶
Patch release from the requalification of a CDC field report ("0.6.8 CMP categorical issue"): the alleged 0.6.4→0.6.8 categorical→LR regression was disproven — the chain is behavior-identical across versions — but the investigation surfaced a family of silent categorical-transform failure modes plus one latent crash, all fixed here. Mapped WOE values are byte-identical on every input; the only behavior change is new warnings, a new stats attribute, and a config shape that previously raised TypeError now working.
G18 Categorical Transform Guards (MonotoneWOEBinner.apply_woe)¶
- Field case: an upstream data-prep regression destroyed a categorical column (float32, 99.995% NaN, 2 surviving all-bad rows — the report's "N=2, WOE=5.04" phantom bin). SMF transformed it in silence: 99.99% of rows quietly took the [Missing]-bin WOE and the N38 unseen warning read "2/24716 rows (0.0%) affected" — technically true, diagnostically useless. The equivalence check the field team ran (WOE-table diff) is structurally blind to this: under artifact reuse the exported table is fit-frozen, so table-identical ≠ matrix-identical.
- New stats:
_categorical_transform_stats[feat]perapply_woecall —total_rows,missing_rows,missing_rate,fit_missing_rate(recovered from the fittedwoe_table+sv_table),exact_match_rows,fallback_match_rows,unmatched_rows. Reset per call, like_unseen_category_stats. Equivalence regressions can assert on it directly. - Missing-rate drift guard: transform missing rate ≥ 50% while ≥ 30pp above fit time →
RuntimeWarningnaming both rates ("upstream column likely broken, renamed or re-typed; feature near-constant after transform"). This is the exact field shape. - str()-fallback tripwire: any row matched only through the
str(value)fallback →RuntimeWarning. The fallback bridges fit/transform dtype drift (int codes vs zero-padded strings) but can silently mis-map when renderings collide — and int/str mixed columns mis-map with no unseen warning at all, because every rendering is "seen". Bridges get their own alarm. - Both guards respect
unseen_category_policy="silent"; stats are always recorded. Warning class isRuntimeWarning, consistent with the N38 unseen guard in the same method.
CMP Monotone Self-Fit: init/fit Param Split¶
- Defect:
CreditModelPipeline._fit_woe's self-fit path passed the wholemonotone_woe_paramsdict intoMonotoneWOEBinner.__init__, so fit()-only keys —n_jobs,chi2_p,chi2_init_size— raisedTypeError. (The screening-sidefit_screening_woe_engineand FVP's_fit_monotone_binneralready split init vs fit keys.) Field configs carryingn_jobsonly survived by always taking the artifact-reuse path. - Fix: the self-fit path now routes
chi2_binning/chi2_p/chi2_init_size/n_jobstofit(). Configs without these keys are byte-identical; configs with them go from crashing to working (andchi2_p/chi2_init_size/n_jobsbecome effective on this path for the first time).
Behavior Notes¶
- No numeric output changes anywhere: warn-vs-silent transform outputs are regression-asserted array-equal on healthy and broken inputs; the CMP self-fit split is regression-asserted
woe_table-identical against a directly fitted reference binner. - Practical guidance for A/B reruns (from the field case): health-check input columns against the frozen baseline (dtype, missing rate, unique values) and diff the transform output (or read
_categorical_transform_stats), not just the WOE table.
Validation¶
- Full local pytest suite:
840 passed, 0 skipped(9 new regressions: healthy-path silence, stats reset, field-shape drift warning with both rates, drift suppression when fit matches, silent-policy stats retention, dtype-flip fallback warning + value identity, warn/silent byte parity, CMP self-fit fit-only keys, CMP self-fit reference parity). - CI green across the legacy/modern/bleeding × Python 3.11/3.12 matrix, source package checks, and version-consistency check.
- Source package verification and wheel/sdist build passed (
tasks.py verify).