Probability Calibration
Probability calibration for classifiers: reliability diagrams, Brier score, log loss, sigmoid and isotonic recalibration, and practical monitoring.
Machine Learning from ScratchAll chapters Probability CalibrationFull chapter · Sections in this chapter1 / 10 ↓
Reading tools Definitions, links & notes
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Probability Calibration
A classifier that returns predict_proba gives us estimated probabilities, but those estimates are not automatically calibrated. A model may say 0.90 for a group of cases in which only 70 percent are positive. The ranking may still be excellent, and the classifier may still be useful for sorting cases, but the number 0.90 cannot be read as a 90 percent chance. Probability calibration is the problem of making predicted probabilities match observed frequencies.
Many decisions use the probability itself. A fraud team may set a review threshold from expected costs, a hospital may use a risk score to allocate follow-up care, and a lending model may be monitored by comparing predicted default rates with realized default rates. In each case, a score of 0.2 should mean roughly a 20 percent event rate, not merely “lower than a score of 0.3.” Calibration is also distinct from discrimination. A model can rank positives above negatives very well while being badly overconfident, and a model can be perfectly calibrated while having no ability to separate the two classes.
Calibration is a property of the model and the population together, not a property of the model alone. A calibrator learned on one population can fail on another. We therefore treat calibration as a monitored, evidence-based step: fit it on representative held-out data, evaluate it on untouched data, and check whether the reliability holds across important subgroups and over time.
Advantages
Calibrated probabilities can be used directly in expected-value calculations. If a predicted probability is 0.08 and the loss from a missed positive case is much larger than the cost of an unnecessary review, we can compare expected costs rather than guessing at an arbitrary threshold. This is the practical reason calibration matters in credit, insurance, medicine, fraud, churn, and maintenance. For a decision based on the score, calibration supports expected-cost calculations under the stated cost model. It does not establish each individual's risk given every feature, or justify combining probabilities without assumptions about their dependence.
Post-hoc calibration can be applied to many classifiers without retraining them. It learns a mapping from one raw score to an estimated probability using a separate calibration sample. This works for naive Bayes, support vector machines, random forests, boosted trees, and neural networks. It does not require retraining the original model, and it can be applied after model selection. Conditional-independence violations can make naive Bayes scores too extreme. A fitted recalibrator may improve their probability quality, but that must be checked on untouched data.
Calibration also supports communication and monitoring. A reliability diagram gives a direct visual answer to the question “when the model says 30 percent, how often is the event true?” Proper scores such as Brier score and log loss summarize probability quality in a single number. These diagnostics make it easier to compare models on the scale that decisions use, and they expose failures that accuracy and AUC can hide.
Disadvantages
Calibration requires held-out data that are representative of the population where the probabilities will be used. If the calibration set is small, the fitted mapping is noisy, especially in the tails. Isotonic recalibration has flexible monotone fitted values at the observed scores; our predictor interpolates between those scores. With few observations, it can overfit the calibration sample and produce unstable probabilities on new cases. The two-parameter sigmoid limits flexibility, which can help with a smaller sample, but it can underfit complex patterns and still be poorly estimated.
A strictly increasing recalibration map preserves score order and ROC-AUC. Isotonic maps can create ties, which can change AUC substantially in either direction. An unconstrained sigmoid can also reverse order if its fitted slope is negative. These mappings use only the original score; they cannot recover feature information that score has discarded. That mapping leaves the base fit and its training labels unchanged. With clean, representative calibration labels, it can still correct some probability-scale distortions caused by label noise or a nonrepresentative training sample.
Calibration is also population-specific. A mapping that is valid on one hospital, region, customer segment, or time period may be invalid on another. Under distribution shift, the relationship between predicted probability and outcome can change, and a previously calibrated model can become miscalibrated. For these reasons, calibration should be checked on untouched data, across subgroups, and over time rather than assumed to hold permanently.
Formula
Calibration is easiest to understand as a promise about conditional frequency. Imagine collecting every case for which the model says 0.30 and asking what fraction of those cases turned out positive. A calibrated model keeps that promise: the fraction should be about 0.30. To state the promise formally, let the binary outcome be , and let a model produce a score for observation . We treat as a claimed probability that . Population calibration means that the conditional positive probability equals the forecast, almost surely under the forecast distribution. Among independently sampled cases from the target population, a finite group's observed event fraction fluctuates around that probability:
where:
- : the binary outcome, with for a positive case and for a negative case
- : the model's predicted probability for a case
- : a specific predicted-probability value at which we inspect the conditional positive rate
This condition applies across the probability scale. Equivalently, for a calibrated forecast ,
where:
- : the binary outcome
- : the calibrated forecast probability, treated as a random variable
- : the conditional positive rate as a function of the forecast
Taking expectations over the forecast distribution gives a useful consequence:
where:
- : the overall positive rate (base rate) of the population
- : the conditional positive rate given forecast
- : the average predicted probability across the population
So a calibrated model must have average predicted probability equal to the base rate. This is necessary but not sufficient. A constant predictor that always outputs the base rate is calibrated, but it has no discrimination.
Calibration, discrimination, and proper scoring rules
A model can be useful in two different ways, and calibration addresses only one of them. Calibration asks whether the probability scale matches event rates: among cases assigned 0.8, is the positive-event rate about 80 percent? Discrimination asks whether the model separates positives from negatives: do positives tend to receive higher scores than negatives? AUC measures ranking:
Here and are independent draws from the positive and negative populations, both of which must occur, and is the score. AUC gives half credit to a tie. It depends only on score order and ties, so any strictly increasing transformation of the scores leaves AUC unchanged. A model can have high AUC and poor calibration. For example, it might assign scores near 0.99 to positives and near 0.98 to negatives. The ranking is nearly perfect, but the probabilities are not trustworthy. Conversely, the constant population base-rate forecast is calibrated and has AUC equal to 0.5 when both classes occur.
If we want a single number to reward honest probabilities, we need a loss whose best possible report is the truth. That is the idea of a proper scoring rule. For a binary outcome with true probability , a scoring rule is proper if the following inequality holds for every reported probability :
where:
- : the score assigned to reported probability under outcome
- : the true probability that
- : the reported probability we are evaluating
- : the expectation over outcomes drawn from a Bernoulli distribution with success probability
For a strictly proper rule, equality holds only when . The Brier score is the mean squared error of the probabilities:
where:
- : the number of cases
- : the predicted probability for case
- : the observed outcome for case , coded as 1 for positive and 0 for negative
To see why it is proper, fix a true probability and suppose we report . The expected squared error is
where:
- : the reported probability
- : the outcome, which is 1 with probability and 0 with probability
- : the true probability that
The second term does not depend on , and the first term is minimized at . Log loss is the negative log likelihood:
For a true probability and reported , the expected log loss is
where:
- : the true probability that
- : the reported probability
For and , differentiating with respect to gives
The minimum is unique because the second derivative is in the interior. For or , the minimum occurs at the matching endpoint, using the usual limit. Log loss therefore has the same properness property. It penalizes confident mistakes much more strongly than the Brier score because the logarithm is unbounded near zero. Thresholded accuracy cannot identify the true probability: many reports on the same side of the threshold give the same label. At threshold one half, the corresponding zero-one loss is weakly proper under the definition above, but it is not strictly proper. AUC is a pairwise ranking measure rather than a per-observation Bernoulli loss , and it does not identify the numerical probability scale.
The properness condition has a visual signature: for a fixed true probability, the expected Brier score is smallest when the reported probability equals the truth. The next figure draws that expected loss for three true probabilities and marks each minimum.
PythonIn [3]Expand
import numpy as np
q_grid = np.linspace(0.01, 0.99, 200)
true_probs = [0.2, 0.5, 0.8]
brier_curves = [(q_grid - p) ** 2 + p * (1.0 - p) for p in true_probs]VisualizationOut [4]Expand

Reliability diagrams and Brier score
The formulas above describe an ideal conditional frequency. In practice we estimate that frequency by grouping predictions into bins and comparing the average prediction in each bin with the observed positive rate. A reliability diagram is the graphical version of that comparison, and it is often the first diagnostic we inspect. Suppose bin contains cases, with mean predicted probability
and observed positive frequency
where:
- : the index of the reliability bin
- : the number of cases in bin
- : the predicted probability for case
- : the observed outcome for case
- : the mean predicted positive-event probability in bin
- : the observed positive rate in bin , rather than thresholded classification accuracy
We plot on the horizontal axis and on the vertical axis. The diagonal is perfect calibration. A point below the diagonal means the model overpredicts the positive-event rate; a point above means it underpredicts that rate. Calling either direction overconfidence across the whole binary probability scale would confuse positive-event risk with confidence in the more likely class. The number of bins and the binning strategy matter: equal-width bins can put very few observations in extreme bins, while equal-frequency bins can hide poor calibration near the extremes. A reliability diagram is a diagnostic, not a single-number score.
The expected calibration error summarizes the same binned comparison:
where:
- : the number of bins
- : the number of cases in bin
- : the total number of cases
- : the observed positive rate in bin
- : the mean predicted probability in bin
ECE depends on the binning scheme. Merging bins can cancel oppositely signed gaps and reduce ECE, even when the individual forecasts are unchanged. Tied scores can also make quantile-bin counts unequal or reduce the number of distinct bins. It should not be used as the sole calibration metric. For binned forecasts, where each case in bin is represented by the bin mean , the Brier score decomposes as
where:
- : the Brier score of the binned forecasts
- : the reliability term, the calibration penalty
- : the resolution term, the discrimination reward
- : the score of the empirical constant base-rate forecast
and the reliability term is
the resolution term is
where:
- : the number of cases in bin
- : the total number of cases
- : the observed positive rate in bin
- : the overall base rate, the fraction of positive cases
and the uncertainty term is
This identity is exact for the forecasts after every score in a bin is replaced by that bin's mean. It need not equal the Brier score of the original varying scores. Reliability is a penalty: it is large when predicted and observed frequencies disagree. Resolution is large when bins have event rates far from the overall rate. It measures this bin-level refinement, rather than ROC-AUC. Uncertainty is the Brier score of the empirical constant base-rate forecast on these observations. A useful summary is the Brier skill score relative to the base-rate forecast:
where:
- : the Brier skill score relative to the base-rate forecast
- : the Brier score of the model being evaluated
- : the Brier score of the reference base-rate forecast, required to be positive
BSS is undefined when the reference score is zero. A reference using the evaluation sample's own base rate is a descriptive benchmark; a deployable constant forecast must be estimated before final evaluation.
Sigmoid and isotonic recalibration
Once the reliability diagram shows miscalibration, the next question is how to repair the probability scale without discarding the model. The repair is a mapping from raw score to calibrated probability, learned on held-out data. Sigmoid recalibration, often called Platt scaling, fits a two-parameter monotone map from the raw score to a calibrated probability. When the raw model outputs a probability , we can work on its logit scale:
where:
- : the raw probability output by the base model for case
- : the logit of that probability, which is the input scale for sigmoid recalibration
- : the log-odds transformation
The calibrated probability is
where:
- : the calibrated probability for case
- : the logit of the raw model probability
- : the slope parameter on the logit scale
- : the intercept parameter on the logit scale
- : the logistic sigmoid function
The parameters and are fitted on the calibration set by minimizing log loss:
where:
- : the negative log likelihood on the calibration set as a function of the slope and intercept
- : the number of calibration cases
- : the calibrated probability for case , equal to
- : the observed outcome for case
The derivative of the logistic sigmoid satisfies , and the derivative of is . Applying the chain rule gives
where:
- : the negative log likelihood from above
- : the calibrated probability for case
- : the observed outcome for case
- : the logit of the raw model probability for case
Setting these derivatives to zero gives the first-order conditions. The general first-order system has no direct closed-form solution, so we use a numerical optimizer such as BFGS. With separated calibration labels, an unregularized finite optimum need not exist. When and , the map is the identity. With and , the map moves probabilities toward one half; with and , it moves them toward the endpoints. A nonzero intercept shifts the curve. Positive preserves order, negative reverses it, and gives a constant. Our unconstrained fit must therefore be checked before invoking an order-preservation claim. If the raw score is a margin rather than a probability, as with a support vector machine, the same idea applies directly as .
Isotonic recalibration is nonparametric. It seeks a nondecreasing function that minimizes
where:
- : the fitted nondecreasing mapping from raw score to calibrated probability
- : the fitted calibrated probability for case
- : the raw score for case
- : the observed outcome for case
- : the number of calibration cases
- : the monotonicity constraint ensuring a higher raw score does not produce a lower calibrated probability
The monotonicity constraint encodes the reasonable requirement that a higher raw score should not produce a lower calibrated probability. The pool adjacent violators algorithm solves this problem. We first aggregate equal scores so they receive the same fitted value, sort the distinct scores, and merge adjacent weighted blocks whenever their means violate monotonicity. The fitted value for each block is its weighted mean outcome. PAVA gives block-constant fitted values at the observed scores. Our predictor linearly interpolates between those score thresholds; any monotone extension matching the fitted points has the same training objective. Isotonic regression is flexible and can correct complex miscalibration, but it requires more calibration data and can overfit. Outside the range of observed scores it must extrapolate, typically by holding the boundary value constant.
Both methods are post hoc. They use the calibration set to learn a mapping and then apply that mapping to new scores. They leave the base model fixed and use only its score. The mapping can alter ties or score orientation, but supplies no new feature information.
Mathematical properties
A calibrated model satisfies . This follows from the law of total expectation. Calibration is not preserved by arbitrary transformations: if is calibrated and we apply a nonlinear function , the transformed value need not be calibrated. Discrimination is different: a strictly increasing transformation preserves both ranking and existing ties, hence AUC. Finite-precision saturation or clipping can introduce extra ties.
Proper scoring rules assess the numerical probability forecast. A model can improve its Brier score by becoming better calibrated, by becoming better at separating classes, or both. For the constant population forecast , the expected Brier score is . A finite-sample score need not equal this expectation. A model with strong discrimination but poor calibration can have a worse Brier score than a calibrated model with weaker discrimination. This is why we report calibration diagnostics and proper scores together rather than relying on a single number.
Example
A small numerical example makes the decomposition concrete and shows why calibration can improve proper scores without changing the ranking. Suppose a model produces ten predictions and we group them into two reliability bins. Every case in the high-score bin receives probability 0.9; four of its five cases are positive. Every case in the low-score bin receives probability 0.6; one of its five cases is positive. Using only bin means would not determine the original unbinned scores. We fit and score the illustrative mapping on these same ten cases to show the arithmetic; the later implementation uses separate calibration and test partitions.
| Bin | Mean predicted | Positives | Observed rate | |
|---|---|---|---|---|
| High | 5 | 0.9 | 4 | 0.8 |
| Low | 5 | 0.6 | 1 | 0.2 |
The raw Brier score is the average squared difference between each predicted probability and its outcome.
In the low bin, one case has outcome 1 and four have outcome 0:
The overall Brier score is
Now we decompose it. The reliability term is
The resolution term is
The uncertainty term is
where is the base rate, the fraction of positive cases in the full sample. So
which matches the direct Brier score. The expected calibration error is
The raw log loss is
Now apply an isotonic recalibration. The high bin maps to 0.8 and the low bin maps to 0.2, because those are the observed rates in the two monotone blocks. The calibrated Brier score becomes
The calibrated log loss becomes
Both proper scores improve substantially. The ranking, however, does not improve. There are four positive cases in the high bin and one positive case in the low bin, with one negative case in the high bin and four negative cases in the low bin. Pairing each positive with each negative gives 25 pairs. Predictions are tied within each bin both before and after this mapping. There are sixteen cross-bin pairs with the positive in the high bin and the negative in the low bin, four within-high-bin tied pairs, four within-low-bin tied pairs, and one cross-bin pair with the positive in the low bin and the negative in the high bin. Treating ties as one-half gives
The calibrated scores preserve this ranking, so AUC remains 0.8. This example shows the central distinction: calibration repairs the probability scale, while discrimination is about the ordering. That is why we inspect both calibration and ranking.
The additive decomposition can be read as a signed account: reliability adds penalty, resolution subtracts a reward, and uncertainty is the empirical constant base-rate term. The next figure shows those terms for the two-bin example and checks that they add to the raw Brier score.
PythonIn [5]Expand
n_high, n_low = 5, 5
pred_high, pred_low = 0.9, 0.6
pos_high, pos_low = 4, 1
obs_high = pos_high / n_high
obs_low = pos_low / n_low
base_rate = (pos_high + pos_low) / (n_high + n_low)
rel_example = (
n_high * (pred_high - obs_high) ** 2 + n_low * (pred_low - obs_low) ** 2
) / (n_high + n_low)
res_example = (
n_high * (obs_high - base_rate) ** 2 + n_low * (obs_low - base_rate) ** 2
) / (n_high + n_low)
unc_example = base_rate * (1.0 - base_rate)
bs_raw_example = rel_example - res_example + unc_example
decomp_labels = ["Reliability", "Resolution", "Uncertainty", "Total Brier"]
decomp_values = [rel_example, -res_example, unc_example, bs_raw_example]VisualizationOut [6]Expand

Implementation in NumPy, SciPy, and Scikit-learn
We now build the calibration workflow from primitives and compare it with standard library implementations. The implementation follows the logic of the formulas: fit the base classifier on training data, fit each calibrator on a separate calibration partition, and evaluate the results on an untouched test partition. We fit a Gaussian naive Bayes classifier. To make a controlled scale error visible, we then multiply its predicted log odds by two and convert them back to probabilities. This deliberately sharpens the scores without adding feature information; it is a synthetic demonstration, not a claim that this classifier is always overconfident.
The next cells import the libraries and set the data-generation seed, then generate and split the data. The calibration and test predictions use rows held out from base-model fitting.
PythonIn [7]Expand
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from book_plot_style import (
PALETTE,
polish_axes,
use_book_style,
)
from scipy.optimize import minimize
from scipy.special import expit
from sklearn.datasets import make_classification
from sklearn.isotonic import IsotonicRegression
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import GaussianNB
RANDOM_STATE = 42
EPS = 1e-12
SCORE_SHARPNESS = 2.0PythonIn [8]Expand
X, y = make_classification(
n_samples=12000,
n_features=12,
n_informative=6,
n_redundant=2,
n_clusters_per_class=2,
class_sep=0.9,
flip_y=0.04,
random_state=RANDOM_STATE,
)
X_train, X_temp, y_train, y_temp = train_test_split(
X, y, test_size=0.60, stratify=y, random_state=RANDOM_STATE
)
X_cal, X_test, y_cal, y_test = train_test_split(
X_temp, y_temp, test_size=0.50, stratify=y_temp, random_state=RANDOM_STATE
)
base = GaussianNB()
base.fit(X_train, y_train)
p_cal_base = base.predict_proba(X_cal)[:, 1]
p_test_base = base.predict_proba(X_test)[:, 1]Shared input checks reject invalid probabilities, nonbinary outcomes and mismatched row counts before arithmetic can silently broadcast them. We then define numerical clipping, probability scores and reliability-bin summaries. The metric table is then displayed from variables so the comparison is computed rather than typed.
PythonIn [9]Expand
def probability_vector(p):
p = np.asarray(p, dtype=float)
if (
p.ndim != 1
or p.size == 0
or not np.isfinite(p).all()
or np.any((p < 0) | (p > 1))
):
raise ValueError(
"Probabilities must be a nonempty finite vector in [0, 1]."
)
return p
def binary_pairs(y, p):
p = probability_vector(p)
y = np.asarray(y, dtype=float)
if (
y.ndim != 1
or y.shape != p.shape
or not np.isfinite(y).all()
or not np.isin(y, [0, 1]).all()
):
raise ValueError(
"Binary outcomes must have the same one-dimensional shape as probabilities."
)
return y, p
def clip_prob(p, eps=EPS):
p = probability_vector(p)
if not np.isfinite(eps) or not 0 < eps < 0.5:
raise ValueError("eps must be finite and between zero and one half.")
return np.clip(p, eps, 1.0 - eps)
def brier_score(y, p):
y, p = binary_pairs(y, p)
return float(np.mean((p - y) ** 2))
def log_loss_score(y, p):
y, p = binary_pairs(y, p)
p = clip_prob(p)
return float(-np.mean(y * np.log(p) + (1.0 - y) * np.log1p(-p)))
def reliability_table(y, p, n_bins=10, strategy="quantile"):
y, p = binary_pairs(y, p)
if (
not isinstance(n_bins, (int, np.integer))
or isinstance(n_bins, bool)
or n_bins < 1
):
raise ValueError("n_bins must be a positive integer.")
if strategy == "quantile":
edges = np.quantile(p, np.linspace(0.0, 1.0, n_bins + 1))
edges = np.unique(edges)
elif strategy == "uniform":
edges = np.linspace(0.0, 1.0, n_bins + 1)
else:
raise ValueError("strategy must be 'quantile' or 'uniform'")
if len(edges) < 2:
edges = np.array([0.0, 1.0])
bin_idx = np.digitize(p, edges[1:-1], right=False)
rows = []
for b in range(len(edges) - 1):
mask = bin_idx == b
if not np.any(mask):
continue
rows.append(
{
"bin": b,
"n": int(mask.sum()),
"mean_pred": float(p[mask].mean()),
"observed": float(y[mask].mean()),
}
)
return pd.DataFrame(rows)
def expected_calibration_error(y, p, n_bins=10, strategy="quantile"):
tab = reliability_table(y, p, n_bins=n_bins, strategy=strategy)
n = len(y)
return float(
np.sum((tab["n"] / n) * np.abs(tab["observed"] - tab["mean_pred"]))
)PythonIn [10]Expand
def logit_clipped(p, eps=EPS):
p = clip_prob(p, eps)
return np.log(p / (1.0 - p))
def fit_sigmoid(p, y):
y, p = binary_pairs(y, p)
if np.unique(y).size != 2:
raise ValueError("Sigmoid fitting requires both outcome classes.")
z = logit_clipped(p)
def objective(theta):
a, b = theta
eta = a * z + b
return np.mean(np.logaddexp(0.0, eta) - y * eta)
def gradient(theta):
a, b = theta
error = expit(a * z + b) - y
return np.array([np.mean(error * z), np.mean(error)])
result = minimize(
objective,
x0=np.array([1.0, 0.0]),
jac=gradient,
method="BFGS",
options={"gtol": 1e-7, "maxiter": 1000},
)
if (
not result.success
or not np.isfinite(result.x).all()
or not np.isfinite(result.fun)
):
raise RuntimeError(
"Sigmoid optimization did not return a finite successful fit."
)
return result.x
def predict_sigmoid(p, theta):
theta = np.asarray(theta, dtype=float)
if theta.shape != (2,) or not np.isfinite(theta).all():
raise ValueError("theta must contain two finite coefficients.")
a, b = theta
return expit(a * logit_clipped(p) + b)The isotonic calibrator uses the pool adjacent violators algorithm. We first aggregate tied scores, then merge adjacent blocks whenever their observed rates decrease. The fitted block means are the calibrated probabilities. Prediction uses linear interpolation between the unique score thresholds and holds the boundary values constant outside the observed range.
PythonIn [11]Expand
def fit_isotonic(p, y):
y, p = binary_pairs(y, p)
order = np.argsort(p)
p_sorted = p[order]
y_sorted = y[order]
unique_p, inv = np.unique(p_sorted, return_inverse=True)
sum_y = np.bincount(inv, weights=y_sorted)
counts = np.bincount(inv)
blocks = []
for i in range(len(unique_p)):
blocks.append([sum_y[i], counts[i], i, i])
while len(blocks) >= 2:
prev = blocks[-2]
curr = blocks[-1]
if prev[0] / prev[1] <= curr[0] / curr[1]:
break
blocks.pop()
blocks.pop()
merged = [prev[0] + curr[0], prev[1] + curr[1], prev[2], curr[3]]
blocks.append(merged)
fitted = np.empty(len(unique_p), dtype=float)
for s, w, start, end in blocks:
fitted[start : end + 1] = s / w
return unique_p, fitted
def predict_isotonic(p, iso):
p = probability_vector(p)
unique_p, fitted = iso
return np.interp(p, unique_p, fitted, left=fitted[0], right=fitted[-1])We sharpen the base scores on the logit scale, then fit both calibrators using only the calibration labels and evaluate on the test partition. The Brier score and log loss are computed from the test labels only. AUC is included to compare ranking with probability quality.
PythonIn [12]Expand
p_cal_raw = expit(SCORE_SHARPNESS * logit_clipped(p_cal_base))
p_test_raw = expit(SCORE_SHARPNESS * logit_clipped(p_test_base))
theta = fit_sigmoid(p_cal_raw, y_cal)
iso = fit_isotonic(p_cal_raw, y_cal)
p_test_sig = predict_sigmoid(p_test_raw, theta)
p_test_iso = predict_isotonic(p_test_raw, iso)
metrics = pd.DataFrame(
{
"model": ["Raw", "Sigmoid", "Isotonic"],
"Brier": [
brier_score(y_test, p_test_raw),
brier_score(y_test, p_test_sig),
brier_score(y_test, p_test_iso),
],
"Log loss": [
log_loss_score(y_test, p_test_raw),
log_loss_score(y_test, p_test_sig),
log_loss_score(y_test, p_test_iso),
],
"AUC": [
roc_auc_score(y_test, p_test_raw),
roc_auc_score(y_test, p_test_sig),
roc_auc_score(y_test, p_test_iso),
],
}
)PythonIn [13]Expand
def summarize_metrics(table):
return table.round(4)
def format_calibration_summary(theta, y_cal, y_test):
return (
f"Sigmoid calibration: a = {theta[0]:.3f}, b = {theta[1]:.3f}\n"
f"Calibration set size: {len(y_cal)}; test set size: {len(y_test)}"
)| model | Brier | Log loss | AUC | |
|---|---|---|---|---|
| 0 | Raw | 0.1609 | 0.5497 | 0.8691 |
| 1 | Sigmoid | 0.1473 | 0.4548 | 0.8691 |
| 2 | Isotonic | 0.1478 | 0.4560 | 0.8670 |
The following display reports the fitted sigmoid coefficients and the actual partition sizes.
The fitted sigmoid and isotonic maps estimate a correction to the probability scale. The next figure shows how each map transforms raw predicted probabilities on the test range, with the identity line as a reference for no change.
PythonIn [15]Expand
p_grid = np.linspace(float(p_test_raw.min()), float(p_test_raw.max()), 300)
sig_grid = predict_sigmoid(p_grid, theta)
iso_grid = predict_isotonic(p_grid, iso)VisualizationOut [16]Expand

ConsoleOut [17]Expand
Sigmoid calibration: a = 0.483, b = -0.117 Calibration set size: 3600; test set size: 3600
The fitted sigmoid coefficients describe the change in log odds. With zero intercept, a positive slope below one moves probabilities toward one half, while a slope above one moves them toward the endpoints. A nonzero intercept also shifts the curve, so the slope alone does not describe the adjustment. The identity mapping has slope one and intercept zero.
The binning sensitivity table repeats the expected calibration error for several bin counts. The numbers are not expected to be identical. A diagnostic that changes with the binning scheme should not be treated as a definitive model score.
PythonIn [18]Expand
ece_rows = []
for name, p in [
("Raw", p_test_raw),
("Sigmoid", p_test_sig),
("Isotonic", p_test_iso),
]:
for bins in [5, 10, 20]:
ece_rows.append(
{
"model": name,
"bins": bins,
"ECE": expected_calibration_error(
y_test, p, n_bins=bins, strategy="quantile"
),
}
)
ece_table = pd.DataFrame(ece_rows)| bins | 5 | 10 | 20 |
|---|---|---|---|
| model | |||
| Isotonic | 0.0067 | 0.0151 | 0.0202 |
| Raw | 0.1049 | 0.1049 | 0.1049 |
| Sigmoid | 0.0193 | 0.0201 | 0.0257 |
We can compare our implementation with scikit-learn. The reference sigmoid fit is a logistic regression on the clipped logit, and the reference isotonic fit uses IsotonicRegression with boundary clipping. Small differences can arise from numerical optimization and interpolation, but the calibrated probabilities should be close. The maximum absolute differences check agreement on these test scores. Optimization tolerances and the reference logistic regression penalty can leave a small gap; agreement on this dataset is a useful implementation check.
PythonIn [20]Expand
iso_ref = IsotonicRegression(out_of_bounds="clip").fit(p_cal_raw, y_cal)
p_test_iso_ref = iso_ref.predict(p_test_raw)
z_cal = logit_clipped(p_cal_raw).reshape(-1, 1)
z_test = logit_clipped(p_test_raw).reshape(-1, 1)
lr = LogisticRegression(C=1e6, solver="lbfgs", max_iter=1000)
lr.fit(z_cal, y_cal)
p_test_sig_ref = lr.predict_proba(z_test)[:, 1]ConsoleOut [21]Expand
Max absolute difference, sigmoid: 0.000044 Max absolute difference, isotonic: 0.000000
Key Parameters
Below are the main parameters that affect how the calibrators work and how reliable the resulting probabilities are.
Calibration-set size and support across the score range are important practical constraints. Sigmoid recalibration has only two parameters and can work with a relatively modest calibration sample, but isotonic recalibration can fit many blocks and needs more data to avoid overfitting. There is no universal threshold that works for every problem; the relevant question is whether the calibration set contains enough positives and negatives in the score regions that matter for decisions.
The choice is a flexibility tradeoff. Sigmoid fits a smooth two-parameter curve, which can miss a more irregular calibration relationship. Isotonic fits a nondecreasing relationship with plateaus that can create ties; it can overfit a small calibration sample. Check support across the score range and compare methods using validation data that remains separate from final evaluation.
The reliability-diagram parameters are the number of bins and the binning strategy. Quantile bins put roughly the same number of observations in each bin, which stabilizes the observed rates but can widen bins in sparse probability regions. Uniform bins keep the probability axis evenly spaced but can produce noisy extreme bins. The ECE depends on both choices, so we report it alongside Brier score and log loss rather than in isolation.
EPS clips only logits and log-loss evaluation, so the computed log loss is that of the clipped forecast. The ideal log loss can be infinite at a confidently wrong endpoint. Brier scoring uses the original validated probabilities. SCORE_SHARPNESS is fixed at two for this controlled example; it is not selected using the test labels.
Key Methods
The following are the main functions we use for calibrating and evaluating probabilities.
-
fit_sigmoidandpredict_sigmoidimplement Platt scaling on the logit scale. -
fit_isotonicandpredict_isotonicimplement the pool adjacent violators algorithm and monotone interpolation. -
brier_scorecomputes the exact binary squared-error score, whilelog_loss_scoreevaluates the numerically clipped forecast on the test set. -
reliability_tablebins predictions for reliability diagrams. -
expected_calibration_errorsummarizes the binned gaps. -
roc_auc_scoremeasures discrimination and is used to verify that calibration has not been mistaken for a ranking improvement.
Visualizing Probability Calibration
The reliability diagram is the main diagnostic for calibration. We compute it on the untouched test set. The deliberately sharpened raw scores illustrate scale miscalibration. The sigmoid map estimates a smooth correction, but its curve on the separate test partition need not lie on the diagonal. Isotonic recalibration can bend more sharply, but it can also create flat segments where many scores receive the same calibrated probability.
PythonIn [22]Expand
reliability_curves = [
(name, reliability_table(y_test, p, n_bins=10, strategy="quantile"))
for name, p in [
("Raw", p_test_raw),
("Sigmoid", p_test_sig),
("Isotonic", p_test_iso),
]
]VisualizationOut [23]Expand

The reliability diagram shows how closely predicted probabilities track observed rates across the probability scale.
The histogram shows where the probability mass sits. An overconfident classifier often places many test cases near zero or one. Calibration moves some of that mass toward the middle, but it does not necessarily make the distribution symmetric. The shape of the calibrated distribution depends on the fitted map and on the raw scores. We should not interpret a concentration near the base rate as a failure by itself; the relevant question is whether the observed rates in each region match the predicted probabilities.
VisualizationOut [24]Expand

The score comparison makes the distinction between calibration and discrimination concrete. The Brier score and log loss are proper scores that reward both calibration and separation. AUC is a ranking metric. A strictly increasing map preserves AUC before numerical clipping or saturation; nondecreasing isotonic maps can change ties. Proper scores may improve or worsen on new cases. In the chart, lower bars indicate better probability quality. Compare the methods on each score, then use the separate AUC table to check ranking. An estimated recalibrator can worsen a test score, and isotonic plateaus can change AUC by creating ties. If a method appears to improve AUC substantially, we should check for leakage, a change in the test set, or a nonmonotone map that is doing something other than calibration.
VisualizationOut [25]Expand

Practical Implications
Calibration is warranted when the probability itself is an input to a decision, rather than only a device for ordering cases. Expected-cost thresholds, risk communication, and monitoring predicted event rates use the number on the probability scale. A capacity-constrained queue may instead need only ranking when it selects a fixed number of otherwise comparable cases. When review decisions depend on case-specific expected costs or benefits, probability values matter as well. In a fraud queue, for example, an expected-cost rule needs a score that can be multiplied by review cost and missed-loss; in credit or insurance, predicted default or claim rates are compared with realized rates across segments; in clinical settings, a risk score may be shown to patients or used to allocate follow-up care. When the only requirement is to rank cases from highest to lowest risk, calibration is optional and AUC or another ranking measure is the more direct diagnostic.
A useful practice is to report calibration and discrimination together. The reliability diagram shows where the probability scale fails, Brier score and log loss summarize the size of the failure in proper scoring terms, and AUC shows whether the ordering remains useful. The expected calibration error can be included for communication, but it is bin-dependent and should not be the only calibration number. A model that improves Brier score after recalibration has not necessarily improved its ranking; strictly increasing maps preserve ranking, while isotonic ties or a negative sigmoid slope can change it.
To fit a calibrator, use a calibration partition that is separate from both training and final test data. When labeled data are scarce, cross-fitting can generate out-of-fold scores for calibration, but the final evaluation must still use labels that were not used to fit the base model or the calibrator. For modest calibration sets, a two-parameter sigmoid is a lower-flexibility starting point when both classes and the relevant score range are represented. Its predictions outside that range still need validation. Isotonic recalibration becomes more attractive when the calibration set is large enough to support stable block estimates and the reliability diagram shows a pattern that a logistic map cannot capture. A reasonable diagnostic starting point is ten quantile bins, repeated with five and twenty bins to reveal sensitivity; inspect the number of observations and events per bin before interpreting a gap.
Computational cost depends on the workload. Sigmoid calibration optimizes two parameters on a one-dimensional score, so each objective evaluation is linear in the calibration-set size. Isotonic calibration requires sorting the scores and running the pool adjacent violators algorithm, which is dominated by the sort and uses linear memory. Proper scores cost . After computing bin edges, this teaching reliability loop scans the observations once per bin, costing for bins; quantile-edge construction adds order-statistic work. For a fixed small bin count it scales linearly with . For very large datasets, the calibration sample can be capped or stratified, provided that the score regions used in decisions still contain enough positives and negatives. In deployment, a sigmoid calibrator is stored as two numbers, while an isotonic calibrator is stored as a set of thresholds and fitted values; sigmoid evaluation is constant work per case, while isotonic prediction searches the stored thresholds.
Several important failures are procedural. Fitting a calibrator on training predictions and then evaluating on those same cases measures in-sample fit. It does not show that calibration has corrected base-model overfitting or that the mapping will generalize. Evaluating repeatedly on the final test set while choosing among calibrators also leaks information and makes the reported score optimistic. Another frequent mistake is treating calibration as a substitute for discrimination: a monotone map cannot separate classes that the base model already ranks together. Isotonic calibration can overfit a small calibration set, create flat regions, and produce ties, while any calibrator must extrapolate outside the observed score range; isotonic methods typically hold the boundary value constant. Finally, ECE alone can be reduced by coarse binning or by avoiding extreme probabilities, so it should be read alongside proper scores and the reliability diagram.
Calibration splits, subgroup checks, and distribution shift
The calibration set must resemble the population where the probabilities will be used. Keep the label definition and preprocessing consistent, and cover the score-outcome relationships expected in deployment; a calibrator learned on a convenient sample can be systematically wrong on the target population. Check the positive and negative counts in the score regions used for decisions. No fixed sample count guarantees a stable sigmoid fit. Both score spread and positive/negative counts matter, while the flexible isotonic fit generally needs more information across the score range. Reporting bootstrap intervals for bin-level observed rates, or repeating the calibration split, helps distinguish a real calibration gap from sampling noise.
Aggregate calibration can hide subgroup failures. A model may be calibrated overall while being overconfident for one age band, region, product, or facility. When subgroup sample sizes permit, compute reliability diagrams and proper scores within the subgroups that matter for deployment, and inspect whether the gaps are stable rather than isolated to sparse bins. If a subgroup is large enough and the gap persists, options include group-specific calibration, adding the subgroup variable to the calibration model, or collecting more representative data. These choices affect fairness and governance, so they should be documented rather than absorbed into an aggregate metric.
Distribution shift is the main reason calibration should be treated as temporary evidence. Under covariate shift, the input distribution changes while the outcome distribution conditional on the inputs stays the same. Under label shift, the class base rate changes while the input distribution conditional on the class stays the same. Concept shift changes the relationship between inputs and outcomes. Any of these can invalidate a previously fitted map. If labels from the target population are available, use them to check calibration and refit the mapping when that evidence supports an update. If only the base rate is known to have changed, a prior-shift correction may help, but it does not guarantee that the full reliability curve is correct. After deployment, monitor reliability diagrams, Brier score, log loss, AUC, and subgroup gaps, and version the calibrator alongside the base model. Reassess calibration when the population or label definition changes, and revalidate the decision rule when costs or decision context change. Recalibrate when evidence from the target population shows that the relationship between scores and outcomes needs updating. Do not claim that calibration remains valid after arbitrary shift without evidence from the shifted population.
Summary
Probability calibration makes predicted probabilities match observed frequencies. It is separate from discrimination: a model can rank cases well while being overconfident, and a model can be calibrated while having no ranking power. Reliability diagrams inspect calibration, while Brier score and log loss evaluate overall probability quality. Brier score and log loss are proper scoring rules, meaning they are minimized in expectation by the true probability. AUC measures ranking and is not a calibration metric. The expected calibration error is a useful diagnostic, but it depends on binning and is not proper.
Sigmoid recalibration fits a two-parameter logistic map on the logit scale; its limited flexibility can help with smaller samples, but estimation still needs adequate class and score support. Isotonic recalibration uses the pool adjacent violators algorithm for monotone block estimates and interpolates between score thresholds for prediction. Both methods are post hoc, need separate calibration data, and consume only the existing model score. Strictly increasing recalibration preserves ranking. Ties and negative fitted slopes need separate checks; calibration should be evaluated for probability reliability rather than promoted as a ranking improvement.
The practical workflow is to train the base model, calibrate on a separate representative partition, and evaluate on untouched test data with reliability diagrams, Brier score, log loss, AUC, and subgroup checks. Calibration is a property of the model-data pair, not a permanent guarantee. It should be monitored over time and reassessed after population or base-rate changes. Re-estimate the probability map when evidence from the target population supports an update, and revalidate the decision rule when costs or decision context change.
Quiz
Ready to test your understanding? Take this quick quiz to reinforce what you've learned about probability calibration.
Probability Calibration Quiz
Join the discussionAsk a question or share what you learned.
Keep exploring
Reference
Citation details
Cite or share this article.


Comments
Add to the conversation
A question, a correction, or an example from your own work.
No comments yet. Be the first to share your thoughts!