{"spec_id":"calibration-curve","library":"altair","language":"python","code":"\"\"\" anyplot.ai\ncalibration-curve: Calibration Curve\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 94/100 | Updated: 2026-05-10\n\"\"\"\n\nimport os\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\nBRAND = \"#009E73\"  # Okabe-Ito position 1\nACCENT = \"#C475FD\"  # Okabe-Ito position 2\n\n# Data - Generate synthetic classification predictions\nnp.random.seed(42)\nn_samples = 2000\n\n# Simulate predictions from a slightly overconfident classifier\ny_true = np.random.binomial(1, 0.4, n_samples)\n# Create predictions correlated with true labels but with some noise\nbase_prob = y_true * 0.6 + (1 - y_true) * 0.3\nnoise = np.random.normal(0, 0.15, n_samples)\ny_prob = np.clip(base_prob + noise, 0.01, 0.99)\n\n# Calculate calibration curve manually (10 bins)\nn_bins = 10\nbin_edges = np.linspace(0, 1, n_bins + 1)\nprob_true = []\nprob_pred = []\n\nfor i in range(n_bins):\n    mask = (y_prob >= bin_edges[i]) & (y_prob < bin_edges[i + 1])\n    if mask.sum() > 0:\n        prob_pred.append(y_prob[mask].mean())\n        prob_true.append(y_true[mask].mean())\n\n# Create calibration data\ncalibration_df = pd.DataFrame({\"Mean Predicted Probability\": prob_pred, \"Fraction of Positives\": prob_true})\n\n# Calculate Brier score\nbrier_score = np.mean((y_prob - y_true) ** 2)\n\n# Create histogram data for predicted probabilities\nhist, bin_edges_hist = np.histogram(y_prob, bins=20)\nhist_df = pd.DataFrame({\"Probability\": (bin_edges_hist[:-1] + bin_edges_hist[1:]) / 2, \"Count\": hist})\n\n# Perfect calibration line\nperfect_df = pd.DataFrame({\"x\": [0, 1], \"y\": [0, 1]})\n\n# Calibration curve chart\ncalibration_line = (\n    alt.Chart(calibration_df)\n    .mark_line(color=BRAND, strokeWidth=4)\n    .encode(\n        x=alt.X(\"Mean Predicted Probability:Q\", scale=alt.Scale(domain=[0, 1]), title=\"Mean Predicted Probability\"),\n        y=alt.Y(\"Fraction of Positives:Q\", scale=alt.Scale(domain=[0, 1]), title=\"Fraction of Positives\"),\n    )\n)\n\ncalibration_points = (\n    alt.Chart(calibration_df)\n    .mark_point(color=BRAND, size=300, filled=True)\n    .encode(\n        x=alt.X(\"Mean Predicted Probability:Q\"),\n        y=alt.Y(\"Fraction of Positives:Q\"),\n        tooltip=[\"Mean Predicted Probability:Q\", \"Fraction of Positives:Q\"],\n    )\n)\n\n# Perfect calibration diagonal line\nperfect_line = (\n    alt.Chart(perfect_df)\n    .mark_line(color=ACCENT, strokeWidth=3, strokeDash=[8, 4])\n    .encode(x=alt.X(\"x:Q\"), y=alt.Y(\"y:Q\"))\n)\n\n# Main calibration chart with grid\ncalibration_chart = alt.layer(perfect_line, calibration_line, calibration_points).properties(\n    width=1600,\n    height=900,\n    title=alt.Title(\n        \"calibration-curve · altair · anyplot.ai\",\n        subtitle=f\"Brier Score: {brier_score:.4f}\",\n        fontSize=28,\n        subtitleFontSize=20,\n        color=INK,\n    ),\n)\n\n# Histogram chart (below)\nhistogram_chart = (\n    alt.Chart(hist_df)\n    .mark_bar(color=BRAND, opacity=0.8)\n    .encode(\n        x=alt.X(\"Probability:Q\", scale=alt.Scale(domain=[0, 1]), title=\"Predicted Probability\"),\n        y=alt.Y(\"Count:Q\", title=\"Count\"),\n    )\n    .properties(\n        width=1600, height=400, title=alt.Title(\"Distribution of Predicted Probabilities\", fontSize=24, color=INK)\n    )\n)\n\n# Combine charts vertically with shared configuration\ncombined_chart = (\n    alt.vconcat(calibration_chart, histogram_chart)\n    .resolve_scale(color=\"independent\")\n    .properties(background=PAGE_BG)\n    .configure_axis(\n        labelFontSize=18,\n        titleFontSize=22,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        domainColor=INK_SOFT,\n        gridColor=INK,\n        gridOpacity=0.15,\n    )\n    .configure_view(strokeWidth=0, fill=PAGE_BG)\n    .configure_title(color=INK)\n)\n\n# Save as PNG and HTML\ncombined_chart.save(f\"plot-{THEME}.png\", scale_factor=3.0)\ncombined_chart.save(f\"plot-{THEME}.html\")\n"}