{"spec_id":"calibration-curve","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\ncalibration-curve: Calibration Curve\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 94/100 | Updated: 2026-05-10\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\nfrom plotly.subplots import make_subplots\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\"\nGRID = \"rgba(26,26,23,0.10)\" if THEME == \"light\" else \"rgba(240,239,232,0.10)\"\nBRAND = \"#009E73\"\nSECONDARY = \"#C475FD\"\nNEUTRAL = \"#1A1A1A\" if THEME == \"light\" else \"#E8E8E0\"\n\n# Data - simulate predictions from two classifiers\nnp.random.seed(42)\nn_samples = 2000\n\n# Generate ground truth with varying base rates\nbase_prob = np.random.uniform(0.1, 0.9, n_samples)\ny_true = (np.random.random(n_samples) < base_prob).astype(int)\n\n# Well-calibrated model: predictions close to true probabilities\ny_prob_calibrated = base_prob + np.random.normal(0, 0.1, n_samples)\ny_prob_calibrated = np.clip(y_prob_calibrated, 0.01, 0.99)\n\n# Overconfident model: pushes predictions toward 0 and 1\ny_prob_overconfident = np.where(\n    base_prob > 0.5,\n    0.5 + (base_prob - 0.5) * 1.8 + np.random.normal(0, 0.05, n_samples),\n    0.5 - (0.5 - base_prob) * 1.8 + np.random.normal(0, 0.05, n_samples),\n)\ny_prob_overconfident = np.clip(y_prob_overconfident, 0.01, 0.99)\n\n# Compute calibration curves manually (10 uniform bins)\nn_bins = 10\nbin_edges = np.linspace(0, 1, n_bins + 1)\n\n# Calibrated model calibration curve\nprob_true_cal = []\nprob_pred_cal = []\nfor i in range(n_bins):\n    if i == n_bins - 1:\n        mask = (y_prob_calibrated >= bin_edges[i]) & (y_prob_calibrated <= bin_edges[i + 1])\n    else:\n        mask = (y_prob_calibrated >= bin_edges[i]) & (y_prob_calibrated < bin_edges[i + 1])\n    if np.sum(mask) > 0:\n        prob_true_cal.append(np.mean(y_true[mask]))\n        prob_pred_cal.append(np.mean(y_prob_calibrated[mask]))\n\n# Overconfident model calibration curve\nprob_true_over = []\nprob_pred_over = []\nfor i in range(n_bins):\n    if i == n_bins - 1:\n        mask = (y_prob_overconfident >= bin_edges[i]) & (y_prob_overconfident <= bin_edges[i + 1])\n    else:\n        mask = (y_prob_overconfident >= bin_edges[i]) & (y_prob_overconfident < bin_edges[i + 1])\n    if np.sum(mask) > 0:\n        prob_true_over.append(np.mean(y_true[mask]))\n        prob_pred_over.append(np.mean(y_prob_overconfident[mask]))\n\n# Calculate Brier scores (mean squared error of predictions)\nbrier_cal = np.mean((y_prob_calibrated - y_true) ** 2)\nbrier_over = np.mean((y_prob_overconfident - y_true) ** 2)\n\n# Create subplots: calibration curve on top, histogram below\nfig = make_subplots(\n    rows=2, cols=1, row_heights=[0.7, 0.3], vertical_spacing=0.12, subplot_titles=(\"\", \"Prediction Distribution\")\n)\n\n# Diagonal reference line (perfect calibration)\nfig.add_trace(\n    go.Scatter(\n        x=[0, 1],\n        y=[0, 1],\n        mode=\"lines\",\n        name=\"Perfect Calibration\",\n        line=dict(color=NEUTRAL, width=3, dash=\"dash\"),\n        showlegend=True,\n    ),\n    row=1,\n    col=1,\n)\n\n# Well-calibrated model\nfig.add_trace(\n    go.Scatter(\n        x=prob_pred_cal,\n        y=prob_true_cal,\n        mode=\"lines+markers\",\n        name=f\"Calibrated Model (Brier: {brier_cal:.3f})\",\n        line=dict(color=BRAND, width=4),\n        marker=dict(size=14, symbol=\"circle\"),\n    ),\n    row=1,\n    col=1,\n)\n\n# Overconfident model\nfig.add_trace(\n    go.Scatter(\n        x=prob_pred_over,\n        y=prob_true_over,\n        mode=\"lines+markers\",\n        name=f\"Overconfident Model (Brier: {brier_over:.3f})\",\n        line=dict(color=SECONDARY, width=4),\n        marker=dict(size=14, symbol=\"diamond\"),\n    ),\n    row=1,\n    col=1,\n)\n\n# Histogram for calibrated model predictions\nfig.add_trace(\n    go.Histogram(\n        x=y_prob_calibrated,\n        name=\"Calibrated\",\n        marker=dict(color=BRAND, line=dict(color=INK_SOFT, width=1)),\n        opacity=0.7,\n        nbinsx=20,\n        showlegend=False,\n    ),\n    row=2,\n    col=1,\n)\n\n# Histogram for overconfident model predictions\nfig.add_trace(\n    go.Histogram(\n        x=y_prob_overconfident,\n        name=\"Overconfident\",\n        marker=dict(color=SECONDARY, line=dict(color=INK_SOFT, width=1)),\n        opacity=0.7,\n        nbinsx=20,\n        showlegend=False,\n    ),\n    row=2,\n    col=1,\n)\n\n# Update layout\nfig.update_layout(\n    title=dict(text=\"calibration-curve · plotly · anyplot.ai\", font=dict(size=28, color=INK), x=0.5, xanchor=\"center\"),\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font=dict(color=INK),\n    legend=dict(\n        font=dict(size=16, color=INK_SOFT),\n        x=0.02,\n        y=0.98,\n        xanchor=\"left\",\n        yanchor=\"top\",\n        bgcolor=ELEVATED_BG,\n        bordercolor=INK_SOFT,\n        borderwidth=1,\n    ),\n    barmode=\"overlay\",\n    margin=dict(l=100, r=80, t=120, b=80),\n)\n\n# Update axes for calibration curve (row 1)\nfig.update_xaxes(\n    title=dict(text=\"Mean Predicted Probability\", font=dict(size=22, color=INK)),\n    tickfont=dict(size=18, color=INK_SOFT),\n    range=[0, 1],\n    dtick=0.1,\n    gridcolor=GRID,\n    gridwidth=1,\n    linecolor=INK_SOFT,\n    row=1,\n    col=1,\n)\nfig.update_yaxes(\n    title=dict(text=\"Fraction of Positives\", font=dict(size=22, color=INK)),\n    tickfont=dict(size=18, color=INK_SOFT),\n    range=[0, 1],\n    dtick=0.1,\n    gridcolor=GRID,\n    gridwidth=1,\n    linecolor=INK_SOFT,\n    row=1,\n    col=1,\n)\n\n# Update axes for histogram (row 2)\nfig.update_xaxes(\n    title=dict(text=\"Predicted Probability\", font=dict(size=20, color=INK)),\n    tickfont=dict(size=16, color=INK_SOFT),\n    range=[0, 1],\n    dtick=0.1,\n    gridcolor=GRID,\n    gridwidth=1,\n    linecolor=INK_SOFT,\n    row=2,\n    col=1,\n)\nfig.update_yaxes(\n    title=dict(text=\"Count\", font=dict(size=20, color=INK)),\n    tickfont=dict(size=16, color=INK_SOFT),\n    linecolor=INK_SOFT,\n    row=2,\n    col=1,\n)\n\n# Update subplot title font\nfig.update_annotations(font=dict(size=22, color=INK))\n\n# Save as PNG and HTML with theme-suffixed filenames\nfig.write_image(f\"plot-{THEME}.png\", width=1600, height=900, scale=3)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}