fix: CalibrationCurve non-{0,1} binary crash and Weakspots custom-metric override#542
fix: CalibrationCurve non-{0,1} binary crash and Weakspots custom-metric override#542hunner wants to merge 2 commits into
Conversation
A two-class target encoded outside {0, 1} (e.g. {0, 4} or string labels)
passed the multiclass guard but hit sklearn's calibration_curve without a
pos_label, raising "y_true takes value in {0, 4} and pos_label is not
specified". Resolve the positive class to the largest label, matching the
convention in _diagnosis_metrics.resolve_averaging. pos_label landed in
scikit-learn 1.1, so it is bound only when calibration_curve's signature
exposes it; for {0, 1} the curve is byte-identical to the previous default.
Found during the empirical review of PR #534 (ZD-704, shipped in v2.13.7).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
apply_averaging rebinds any callable whose signature exposes `average`,
including user metrics like functools.partial(f1_score, average="weighted")
(a partial still exposes the parameter), silently replacing the caller's
averaging with the resolved default. Only rebind when the caller did not
supply custom metrics; custom callables own their kwargs. The defaults path
is unchanged, so multiclass/{0,4} targets still get macro/binary rebinding.
OverfitDiagnosis/RobustnessDiagnosis take a metric name string, not
callables, so they are unaffected.
Found during the empirical review of PR #534 (ZD-704, shipped in v2.13.7).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PR SummaryThis PR enhances both the CalibrationCurve and WeakspotsDiagnosis functionality to better handle non-standard binary encodings and custom metrics. Key changes include:
Test Suggestions
|
Pull Request Description
What and why?
Two latent bugs found while empirically reviewing PR #534 (the ZD-704 multiclass fix, shipped in v2.13.6/v2.13.7), reported in that PR's approving review but merged unaddressed. Neither affects the ZD-704 customer (multiclass model, default metrics) — both are follow-on issues of the same family. Tracked as sc-17261.
1.
CalibrationCurvecrashes on binary targets encoded outside{0, 1}#534 added a multiclass-only skip (
len(np.unique(dataset.y)) > 2→SkipTestError), but a two-class target whose labels aren't{0, 1}(e.g.{0, 4}, string labels) sails past that guard into sklearn'scalibration_curvewith nopos_label, hitting the exact cryptic error the guard was meant to eliminate:2.
WeakspotsDiagnosissilently overrides user-supplied custom metricsapply_averaging(from #534's shared_diagnosis_metrics.py) rebinds every callable in the metric registry that exposes anaverageparameter — including user-supplied ones.functools.partial(f1_score, average="weighted")still exposesaverageviainspect.signature, so it gets silently rebound to the resolved averaging (e.g. macro), changing the reported score without any error:What changed
CalibrationCurve.py— passespos_label=np.unique(dataset.y).max()tocalibration_curve, but only when the installed sklearn's signature exposespos_label(added in scikit-learn 1.1; the library'spyproject.tomlhas no lower bound, so an unconditional pass would break{0,1}targets on older installs — a regression). The guard mirrors the existing signature-inspection convention in_diagnosis_metrics.bind_averaging.{0,1}behavior is byte-identical (verified against rawcalibration_curveoutput).WeakspotsDiagnosis.py— averaging rebinding now only applies when the caller didn't pass custommetrics(i.e. only the defaults get rebound); custom callables keep their own kwargs.How to test
New coverage:
CalibrationCurveon{0,4}and string-label binary (no raise) plus{0,1}unchanged (compared against rawcalibration_curve);WeakspotsDiagnosiswith a custompartial(f1_score, average="weighted")metric asserting the weighted value lands in the results (with a check that weighted ≠ macro on the test data, so the assertion is meaningful), plus confirmation that default runs still get macro/pos_label rebinding for multiclass and non-{0,1} binary. 41 tests passed across the touched + adjacent (Overfit/Robustness) modules; the three new tests were confirmed to fail against unfixed source with the exact reported errors before the fix, then pass after.What needs special review?
pos_label=np.unique(dataset.y).max()convention followsresolve_averaging's existing "largest label is positive" rule from Fix multiclass / non-{0,1}-label crashes across the sklearn classification tests (ZD-704) #534 — for string labels this is lexicographic max ("good" > "bad"), worth a sanity check against expectations.inspect.signaturecheck forpos_label) rather than an unconditional pass — deliberate, to avoid breaking{0,1}on pre-1.1 sklearn given the unboundedpyproject.tomlconstraint; CI's actual resolved floor (scikit-learn 1.6.1 on Python 3.9 peruv.lock) means this costs nothing there but protects the documented floor.OverfitDiagnosis/RobustnessDiagnosistake a metric name string rather than callables, so they're unaffected by the custom-metrics-override issue — confirmed while implementing, not otherwise touched.Dependencies, breaking changes, and deployment notes
None.
{0,1}CalibrationCurveand default-metricWeakspotsDiagnosisbehavior are unchanged; only the two previously-broken/incorrect paths change.Separately, implementing this surfaced an unrelated pre-existing bug in
WeakspotsDiagnosis's pass/fail check (crashes when a custommetrics=subset is passed without matchingthresholds=) — filed and fixed independently as sc-17267 / PR #541, not part of this PR.Release notes
Fixed
CalibrationCurveraising a sklearnpos_labelerror on binary targets not encoded as{0, 1}, and fixedWeakspotsDiagnosissilently overriding user-supplied custom metric averaging (e.g. apartial(f1_score, average="weighted")was previously rebound to macro without warning).Checklist
Closes sc-17261
🤖 Generated with Claude Code