Skip to content

Automatic selection

Eleven calibrators is a catalog, not a recommendation. CalibratorSelector turns the catalog into a defensible choice by running the comparison the way the data-splitting chapter demands: every candidate scored only on data it did not fit, the criterion a strictly proper score by default, and the whole contest documented in a report a validator can re-derive.

The protocol

The selector receives the calibration data and a candidate list — by default Platt, temperature, beta ("abm"), isotonic, centered isotonic, equal-mass histogram binning, scaling-binning, and the inductive Venn–Abers; the heavier ensemble and spline methods join by explicit opt-in. It runs an inner stratified K-fold entirely within the calibration data: each candidate is fitted on the inner-training folds and produces predictions on the held-out folds, and only those out-of-fold predictions are scored. The default criterion is log loss, with Brier, ICI, smooth ECE, and ECE-sweep as deliberate alternatives — the metrics chapter's table explains why plain ECE and Hosmer–Lemeshow are not on that list. Alongside the criterion, each candidate's out-of-fold predictions pass through the guardrails (slope, intercept, Spiegelhalter), which do not affect the ranking but are flagged in the report — a candidate can win the score and still arrive with a warning attached.

Ties, within the resolution the fold spread supports, break toward fewer parameters: if beta cannot beat Platt by more than noise, the extra tail parameter was not estimable and parsimony takes it away; the same logic runs down the nested family to temperature and, at the limit, to the offset. The winner is then refitted on the full calibration set — the inner folds existed to rank, not to produce the deployed map — and returned alongside a SelectionReport: the ranked table of candidates with mean and standard deviation of the criterion across folds, guardrail flags, and the chosen-flag column.

Why the nesting is structural

The trap the selector exists to prevent — scoring candidates on the data they were fitted on — is described in the data-splitting chapter; the design point here is how it is prevented. The selector's scoring path receives out-of-fold predictions only; there is no code path by which an in-fold prediction reaches the criterion, so the leakage is not a documented misuse but an unrepresentable state, and test_no_leakage.py asserts it mechanically. This is worth a sentence of justification because the failure it forecloses is the quiet kind: selection bias does not crash, it just systematically crowns the most flexible candidate, and the deployed map is a little worse forever.

Reading a SelectionReport

The report is designed around three questions. Who won, and by how much? — read the mean criterion against the runner-up's, in units of the fold standard deviations printed beside them; a margin inside one standard deviation is a tie that parsimony already adjudicated. Is the winner healthy? — the guardrail columns answer for the winner what they answer for any calibrator, and a winning method with a failing intercept flag usually means the candidate menu should have included the offset composition. Was the contest fair to the data? — a report where every nonparametric method trails the parametric block is the selector saying the sample could not support flexibility, which on a few hundred low-rate observations (see the sizing guidance) is the expected verdict, not a malfunction. The report prints; nothing needs a plotting backend; and the selection plot exists for the deck where a table will not land.

In probcal

from probcal import CalibratorSelector

sel = CalibratorSelector().fit(s_cal, y_cal)      # default menu, log-loss criterion
print(sel.report_)                                # ranked table, guardrails, chosen
print(sel.best_name_)

p = sel.predict_proba(s_new)                      # the winner, refit on all data
print(sel.interpret())

# Restrict the menu (small samples) or switch the criterion deliberately:
from probcal import BetaCalibrator, PlattCalibrator, TemperatureCalibrator
sel = CalibratorSelector(
    candidates={"platt": PlattCalibrator(), "temp": TemperatureCalibrator(),
                "beta": BetaCalibrator()},
    scoring="brier",
).fit(s_cal, y_cal)

References

  • Roelofs, R., Cain, N., Shlens, J., Mozer, M. C. (2022). "Mitigating Bias in Calibration Error Estimation." AISTATS, PMLR 151, 4036–4054.