Skip to content

Commit 88eaac4

Browse files
feat(matplotlib): implement calibration-curve (#2364)
## Implementation: `calibration-curve` - matplotlib Implements the **matplotlib** version of `calibration-curve`. **File:** `plots/calibration-curve/implementations/matplotlib.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20528201914)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent d40f18f commit 88eaac4

2 files changed

Lines changed: 144 additions & 0 deletions

File tree

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
""" pyplots.ai
2+
calibration-curve: Calibration Curve
3+
Library: matplotlib 3.10.8 | Python 3.13.11
4+
Quality: 93/100 | Created: 2025-12-26
5+
"""
6+
7+
import matplotlib.pyplot as plt
8+
import numpy as np
9+
10+
11+
# Data: Simulate predictions from classifiers with different calibration properties
12+
np.random.seed(42)
13+
n_samples = 2000
14+
n_bins = 10
15+
16+
# Generate ground truth - imbalanced to be realistic (35% positive rate)
17+
y_true = np.random.binomial(1, 0.35, n_samples)
18+
19+
# Well-calibrated model: predictions closely match true probability
20+
# Using logistic transformation with moderate noise
21+
logits_calibrated = 1.2 * (y_true * 2 - 1) + np.random.normal(0, 1.0, n_samples)
22+
y_prob_calibrated = 1 / (1 + np.exp(-logits_calibrated))
23+
24+
# Overconfident model: pushes predictions toward 0 and 1 (sigmoid with steeper slope)
25+
logits_over = 2.0 * (y_true * 2 - 1) + np.random.normal(0, 0.5, n_samples)
26+
y_prob_overconfident = 1 / (1 + np.exp(-logits_over))
27+
28+
# Underconfident model: predictions clustered toward 0.5 (flatter sigmoid)
29+
logits_under = 0.5 * (y_true * 2 - 1) + np.random.normal(0, 0.8, n_samples)
30+
y_prob_underconfident = 1 / (1 + np.exp(-logits_under))
31+
32+
# Calculate calibration curves for each model
33+
bin_edges = np.linspace(0, 1, n_bins + 1)
34+
35+
# Well-calibrated model calibration curve
36+
bin_idx_cal = np.digitize(y_prob_calibrated, bin_edges[1:-1])
37+
prob_true_cal = [np.mean(y_true[bin_idx_cal == i]) for i in range(n_bins) if np.sum(bin_idx_cal == i) > 0]
38+
prob_pred_cal = [np.mean(y_prob_calibrated[bin_idx_cal == i]) for i in range(n_bins) if np.sum(bin_idx_cal == i) > 0]
39+
40+
# Overconfident model calibration curve
41+
bin_idx_over = np.digitize(y_prob_overconfident, bin_edges[1:-1])
42+
prob_true_over = [np.mean(y_true[bin_idx_over == i]) for i in range(n_bins) if np.sum(bin_idx_over == i) > 0]
43+
prob_pred_over = [
44+
np.mean(y_prob_overconfident[bin_idx_over == i]) for i in range(n_bins) if np.sum(bin_idx_over == i) > 0
45+
]
46+
47+
# Underconfident model calibration curve
48+
bin_idx_under = np.digitize(y_prob_underconfident, bin_edges[1:-1])
49+
prob_true_under = [np.mean(y_true[bin_idx_under == i]) for i in range(n_bins) if np.sum(bin_idx_under == i) > 0]
50+
prob_pred_under = [
51+
np.mean(y_prob_underconfident[bin_idx_under == i]) for i in range(n_bins) if np.sum(bin_idx_under == i) > 0
52+
]
53+
54+
# Calculate Brier scores (mean squared error of probability predictions)
55+
brier_cal = np.mean((y_prob_calibrated - y_true) ** 2)
56+
brier_over = np.mean((y_prob_overconfident - y_true) ** 2)
57+
brier_under = np.mean((y_prob_underconfident - y_true) ** 2)
58+
59+
# Create figure with two subplots: calibration curve and histogram
60+
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(16, 9), gridspec_kw={"height_ratios": [3, 1]})
61+
62+
# Primary colors from style guide
63+
python_blue = "#306998"
64+
python_yellow = "#FFD43B"
65+
third_color = "#E377C2" # Colorblind-safe pink/magenta
66+
67+
# Plot calibration curves
68+
ax1.plot([0, 1], [0, 1], "k--", linewidth=2, label="Perfect Calibration", alpha=0.7)
69+
ax1.plot(
70+
prob_pred_cal,
71+
prob_true_cal,
72+
"o-",
73+
color=python_blue,
74+
linewidth=3,
75+
markersize=12,
76+
label=f"Well-Calibrated (Brier: {brier_cal:.3f})",
77+
)
78+
ax1.plot(
79+
prob_pred_over,
80+
prob_true_over,
81+
"s-",
82+
color=python_yellow,
83+
linewidth=3,
84+
markersize=12,
85+
label=f"Overconfident (Brier: {brier_over:.3f})",
86+
)
87+
ax1.plot(
88+
prob_pred_under,
89+
prob_true_under,
90+
"^-",
91+
color=third_color,
92+
linewidth=3,
93+
markersize=12,
94+
label=f"Underconfident (Brier: {brier_under:.3f})",
95+
)
96+
97+
# Style calibration plot
98+
ax1.set_xlabel("Mean Predicted Probability", fontsize=20)
99+
ax1.set_ylabel("Fraction of Positives", fontsize=20)
100+
ax1.set_title("calibration-curve · matplotlib · pyplots.ai", fontsize=24)
101+
ax1.tick_params(axis="both", labelsize=16)
102+
ax1.legend(fontsize=16, loc="lower right")
103+
ax1.grid(True, alpha=0.3, linestyle="--")
104+
ax1.set_xlim(0, 1)
105+
ax1.set_ylim(0, 1)
106+
107+
# Histogram of predicted probabilities
108+
ax2.hist(y_prob_calibrated, bins=20, alpha=0.6, color=python_blue, label="Well-Calibrated", edgecolor="white")
109+
ax2.hist(y_prob_overconfident, bins=20, alpha=0.6, color=python_yellow, label="Overconfident", edgecolor="white")
110+
ax2.hist(y_prob_underconfident, bins=20, alpha=0.6, color=third_color, label="Underconfident", edgecolor="white")
111+
ax2.set_xlabel("Predicted Probability", fontsize=20)
112+
ax2.set_ylabel("Count", fontsize=20)
113+
ax2.tick_params(axis="both", labelsize=16)
114+
ax2.legend(fontsize=14, loc="upper right")
115+
ax2.grid(True, alpha=0.3, linestyle="--")
116+
117+
plt.tight_layout()
118+
plt.savefig("plot.png", dpi=300, bbox_inches="tight")
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
library: matplotlib
2+
specification_id: calibration-curve
3+
created: '2025-12-26T19:38:04Z'
4+
updated: '2025-12-26T19:44:38Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20528201914
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: 3.10.8
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/calibration-curve/matplotlib/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/calibration-curve/matplotlib/plot_thumb.png
12+
preview_html: null
13+
quality_score: 93
14+
review:
15+
strengths:
16+
- Excellent multi-model comparison showing well-calibrated, overconfident, and underconfident
17+
classifiers
18+
- Includes histogram subplot as suggested in spec for showing prediction distributions
19+
- Brier scores integrated into legend for quick comparison
20+
- Clean separation of calibration curve calculation logic
21+
- Colorblind-friendly palette with distinct marker shapes for each model
22+
- Professional layout with appropriate subplot height ratios
23+
weaknesses:
24+
- Axis labels lack units or additional context
25+
- Could use more distinctive matplotlib features like fill_between for confidence
26+
bands

0 commit comments

Comments
 (0)