|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | calibration-curve: Calibration Curve |
3 | | -Library: letsplot 4.8.2 | Python 3.13.11 |
4 | | -Quality: 91/100 | Created: 2025-12-26 |
| 3 | +Library: letsplot 4.9.0 | Python 3.13.13 |
| 4 | +Quality: 91/100 | Updated: 2026-05-10 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
| 8 | + |
7 | 9 | import numpy as np |
8 | 10 | import pandas as pd |
9 | 11 | from lets_plot import * |
| 12 | +from lets_plot.export import ggsave |
10 | 13 |
|
11 | 14 |
|
12 | 15 | LetsPlot.setup_html() |
13 | 16 |
|
| 17 | +# Theme tokens |
| 18 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 19 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 20 | +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
| 21 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 22 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 23 | +INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" |
| 24 | +RULE = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)" |
| 25 | + |
| 26 | +BRAND = "#009E73" # Okabe-Ito position 1 — first series |
| 27 | +SECONDARY = "#D55E00" # Okabe-Ito position 2 — reference line |
| 28 | + |
14 | 29 | # Data - Generate realistic binary classification predictions |
15 | 30 | np.random.seed(42) |
16 | 31 | n_samples = 1000 |
|
70 | 85 | {"mean_predicted": mean_predicted, "fraction_positive": fraction_positive, "bin_count": bin_counts} |
71 | 86 | ) |
72 | 87 | df_calibration = df_calibration.dropna() |
| 88 | +df_calibration["tooltip"] = df_calibration.apply( |
| 89 | + lambda row: ( |
| 90 | + f"Predicted: {row['mean_predicted']:.3f}\nObserved: {row['fraction_positive']:.3f}\nBin size: {int(row['bin_count'])}" |
| 91 | + ), |
| 92 | + axis=1, |
| 93 | +) |
73 | 94 |
|
74 | 95 | # Create dataframe for diagonal (perfect calibration) |
75 | 96 | df_diagonal = pd.DataFrame({"x": [0, 1], "y": [0, 1]}) |
|
78 | 99 | hist_bins = 20 |
79 | 100 | hist_counts, hist_edges = np.histogram(y_prob, bins=hist_bins, range=(0, 1)) |
80 | 101 | hist_centers = (hist_edges[:-1] + hist_edges[1:]) / 2 |
81 | | -df_histogram = pd.DataFrame( |
82 | | - { |
83 | | - "prob_center": hist_centers, |
84 | | - "count": hist_counts / hist_counts.max(), # Normalize for subplot |
85 | | - } |
86 | | -) |
| 102 | +df_histogram = pd.DataFrame({"prob_center": hist_centers, "count": hist_counts / hist_counts.max()}) |
87 | 103 |
|
88 | 104 | # Plot |
89 | 105 | plot = ( |
90 | 106 | ggplot() |
91 | 107 | # Perfect calibration diagonal line |
92 | | - + geom_line(aes(x="x", y="y"), data=df_diagonal, color="#888888", size=1.5, linetype="dashed") |
| 108 | + + geom_line(aes(x="x", y="y"), data=df_diagonal, color=INK_SOFT, size=1.5, linetype="dashed") |
93 | 109 | # Calibration curve |
94 | | - + geom_line(aes(x="mean_predicted", y="fraction_positive"), data=df_calibration, color="#306998", size=2) |
| 110 | + + geom_line(aes(x="mean_predicted", y="fraction_positive"), data=df_calibration, color=BRAND, size=2) |
95 | 111 | + geom_point( |
96 | | - aes(x="mean_predicted", y="fraction_positive"), data=df_calibration, color="#306998", size=5, alpha=0.9 |
| 112 | + aes(x="mean_predicted", y="fraction_positive", tooltip="tooltip"), |
| 113 | + data=df_calibration, |
| 114 | + color=BRAND, |
| 115 | + size=5, |
| 116 | + alpha=0.9, |
97 | 117 | ) |
98 | 118 | # Histogram bars at bottom showing prediction distribution |
99 | 119 | + geom_bar( |
100 | | - aes(x="prob_center", y="count"), data=df_histogram, stat="identity", fill="#FFD43B", alpha=0.6, width=0.045 |
| 120 | + aes(x="prob_center", y="count"), data=df_histogram, stat="identity", fill=INK_MUTED, alpha=0.4, width=0.045 |
101 | 121 | ) |
102 | 122 | # Labels and styling |
103 | 123 | + labs( |
104 | 124 | x="Mean Predicted Probability", |
105 | 125 | y="Fraction of Positives", |
106 | | - title=f"calibration-curve · letsplot · pyplots.ai\nBrier Score: {brier_score:.4f} | ECE: {ece:.4f}", |
| 126 | + title=f"calibration-curve · letsplot · anyplot.ai\nBrier Score: {brier_score:.4f} | ECE: {ece:.4f}", |
107 | 127 | ) |
108 | 128 | + scale_x_continuous(limits=[0, 1], breaks=[0, 0.2, 0.4, 0.6, 0.8, 1.0]) |
109 | 129 | + scale_y_continuous(limits=[0, 1], breaks=[0, 0.2, 0.4, 0.6, 0.8, 1.0]) |
110 | | - + theme_minimal() |
111 | 130 | + theme( |
112 | | - plot_title=element_text(size=22), |
113 | | - axis_title=element_text(size=18), |
114 | | - axis_text=element_text(size=14), |
115 | | - panel_grid_major=element_line(color="#CCCCCC", size=0.5), |
| 131 | + plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), |
| 132 | + panel_background=element_rect(fill=PAGE_BG), |
| 133 | + panel_grid_major=element_line(color=RULE, size=0.4), |
116 | 134 | panel_grid_minor=element_blank(), |
| 135 | + axis_title=element_text(size=20, color=INK), |
| 136 | + axis_text=element_text(size=16, color=INK_SOFT), |
| 137 | + axis_line=element_line(color=INK_SOFT, size=0.6), |
| 138 | + plot_title=element_text(size=24, color=INK), |
| 139 | + axis_ticks_length_x=6, |
| 140 | + axis_ticks_length_y=6, |
117 | 141 | ) |
118 | 142 | + ggsize(1600, 900) |
119 | 143 | ) |
120 | 144 |
|
121 | 145 | # Save outputs |
122 | | -ggsave(plot, "plot.png", path=".", scale=3) |
123 | | -ggsave(plot, "plot.html", path=".") |
| 146 | +ggsave(plot, f"plot-{THEME}.png", path=".", scale=3) |
| 147 | +ggsave(plot, f"plot-{THEME}.html", path=".") |
0 commit comments