Calibration Curve — Seaborn

A calibration curve (reliability diagram) visualizes how well the predicted probabilities of a binary classifier match actual outcomes. By plotting the fraction of positives against mean predicted probability in binned intervals, it reveals whether a model is well-calibrated, overconfident, or underconfident. A perfectly calibrated model follows the diagonal line where predicted probability equals observed frequency.

Calibration Curve rendered with Seaborn

Python source (Seaborn)

""" anyplot.ai
calibration-curve: Calibration Curve
Library: seaborn 0.13.2 | Python 3.13.13
Quality: 87/100 | Updated: 2026-05-10
"""

import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns


# Set seaborn style
sns.set_theme(style="whitegrid")

# Generate synthetic binary classification data
np.random.seed(42)
n_samples = 2000

# True labels - imbalanced for realism (35% positive class)
y_true = np.random.binomial(1, 0.35, n_samples)

# Simulate a well-calibrated classifier (predictions close to diagonal)
base_probs = y_true * 0.7 + (1 - y_true) * 0.3 + np.random.normal(0, 0.2, n_samples)
y_prob_calibrated = np.clip(base_probs, 0.01, 0.99)

# Simulate an overconfident classifier (S-shaped: below diagonal on left, above on right)
y_prob_overconfident = 1 / (1 + np.exp(-5 * (y_prob_calibrated - 0.5)))
y_prob_overconfident = np.clip(y_prob_overconfident, 0.01, 0.99)

# Simulate an underconfident classifier (inverted S: above diagonal on left, below on right)
y_prob_underconfident = 0.5 + (y_prob_calibrated - 0.5) * 0.35
y_prob_underconfident = np.clip(y_prob_underconfident, 0.01, 0.99)

# Compute calibration curves (bin predictions, compute fraction of positives)
n_bins = 10
bin_edges = np.linspace(0, 1, n_bins + 1)

# Well-calibrated model calibration curve
bin_indices_calib = np.digitize(y_prob_calibrated, bin_edges[1:-1])
prob_true_calib = [np.mean(y_true[bin_indices_calib == i]) for i in range(n_bins) if np.sum(bin_indices_calib == i) > 0]
prob_pred_calib = [
    np.mean(y_prob_calibrated[bin_indices_calib == i]) for i in range(n_bins) if np.sum(bin_indices_calib == i) > 0
]

# Overconfident model calibration curve
bin_indices_over = np.digitize(y_prob_overconfident, bin_edges[1:-1])
prob_true_over = [np.mean(y_true[bin_indices_over == i]) for i in range(n_bins) if np.sum(bin_indices_over == i) > 0]
prob_pred_over = [
    np.mean(y_prob_overconfident[bin_indices_over == i]) for i in range(n_bins) if np.sum(bin_indices_over == i) > 0
]

# Underconfident model calibration curve
bin_indices_under = np.digitize(y_prob_underconfident, bin_edges[1:-1])
prob_true_under = [np.mean(y_true[bin_indices_under == i]) for i in range(n_bins) if np.sum(bin_indices_under == i) > 0]
prob_pred_under = [
    np.mean(y_prob_underconfident[bin_indices_under == i]) for i in range(n_bins) if np.sum(bin_indices_under == i) > 0
]

# Calculate Brier scores (mean squared error of probability predictions)
brier_calib = np.mean((y_prob_calibrated - y_true) ** 2)
brier_over = np.mean((y_prob_overconfident - y_true) ** 2)
brier_under = np.mean((y_prob_underconfident - y_true) ** 2)

# Create figure with two subplots
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 9), gridspec_kw={"width_ratios": [2, 1]})

# Colors: Python Blue, Python Yellow, and a third colorblind-safe color
colors = ["#306998", "#FFD43B", "#8B4513"]

# Plot calibration curves using seaborn lineplot
sns.lineplot(
    x=prob_pred_calib,
    y=prob_true_calib,
    ax=ax1,
    marker="o",
    markersize=14,
    linewidth=3,
    color=colors[0],
    label=f"Well-Calibrated (Brier: {brier_calib:.3f})",
)
sns.lineplot(
    x=prob_pred_over,
    y=prob_true_over,
    ax=ax1,
    marker="s",
    markersize=12,
    linewidth=3,
    color=colors[1],
    label=f"Overconfident (Brier: {brier_over:.3f})",
)
sns.lineplot(
    x=prob_pred_under,
    y=prob_true_under,
    ax=ax1,
    marker="^",
    markersize=12,
    linewidth=3,
    color=colors[2],
    label=f"Underconfident (Brier: {brier_under:.3f})",
)

# Diagonal reference line for perfect calibration
ax1.plot([0, 1], [0, 1], "k--", linewidth=2, alpha=0.7, label="Perfectly Calibrated")

# Styling for calibration plot
ax1.set_xlabel("Mean Predicted Probability", fontsize=20)
ax1.set_ylabel("Fraction of Positives", fontsize=20)
ax1.set_title("calibration-curve · seaborn · pyplots.ai", fontsize=24, pad=15)
ax1.tick_params(axis="both", labelsize=16)
ax1.set_xlim(-0.02, 1.02)
ax1.set_ylim(-0.02, 1.02)
ax1.legend(fontsize=14, loc="lower right")
ax1.grid(True, alpha=0.3, linestyle="--")
ax1.set_aspect("equal")

# Histogram of predicted probabilities using seaborn
sns.histplot(
    y_prob_calibrated,
    ax=ax2,
    bins=20,
    color=colors[0],
    alpha=0.5,
    label="Well-Calibrated",
    edgecolor="white",
    linewidth=0.5,
)
sns.histplot(
    y_prob_overconfident,
    ax=ax2,
    bins=20,
    color=colors[1],
    alpha=0.5,
    label="Overconfident",
    edgecolor="white",
    linewidth=0.5,
)
sns.histplot(
    y_prob_underconfident,
    ax=ax2,
    bins=20,
    color=colors[2],
    alpha=0.5,
    label="Underconfident",
    edgecolor="white",
    linewidth=0.5,
)

# Styling for histogram
ax2.set_xlabel("Predicted Probability", fontsize=20)
ax2.set_ylabel("Count", fontsize=20)
ax2.set_title("Prediction Distribution", fontsize=22, pad=15)
ax2.tick_params(axis="both", labelsize=16)
ax2.legend(fontsize=12, loc="upper right")
ax2.grid(True, alpha=0.3, linestyle="--")

plt.tight_layout()
plt.savefig("plot.png", dpi=300, bbox_inches="tight")

Part of Calibration Curve on anyplot.ai.

Other implementations