{"spec_id":"calibration-curve","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\ncalibration-curve: Calibration Curve\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-10\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\nIMPRINT = (\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\")\n\n# Data: Generate synthetic binary classification with realistic calibration\nnp.random.seed(42)\nn_samples = 2000\nn_bins = 10\n\n# Generate true probabilities spread across 0-1 range\ntrue_prob = np.random.beta(2, 2, n_samples)\ny_true = (np.random.random(n_samples) < true_prob).astype(int)\n\n# Model 1: Well-calibrated model (Logistic Regression style)\nnoise1 = np.random.randn(n_samples) * 0.08\ny_prob_model1 = np.clip(true_prob + noise1, 0.01, 0.99)\n\n# Model 2: Overconfident model (Random Forest / Neural Network style)\ny_prob_model2 = 1 / (1 + np.exp(-12 * (true_prob - 0.5)))\ny_prob_model2 = np.clip(y_prob_model2 + np.random.randn(n_samples) * 0.02, 0.02, 0.98)\n\n# Compute calibration data inline\nbin_edges = np.linspace(0, 1, n_bins + 1)\n\n# Model 1 calibration\nbin_indices1 = np.digitize(y_prob_model1, bin_edges[1:-1])\nmean_pred1 = []\nfrac_pos1 = []\nfor i in range(n_bins):\n    mask = bin_indices1 == i\n    if mask.sum() > 0:\n        mean_pred1.append(np.mean(y_prob_model1[mask]))\n        frac_pos1.append(np.mean(y_true[mask]))\n\n# Model 2 calibration\nbin_indices2 = np.digitize(y_prob_model2, bin_edges[1:-1])\nmean_pred2 = []\nfrac_pos2 = []\nfor i in range(n_bins):\n    mask = bin_indices2 == i\n    if mask.sum() > 0:\n        mean_pred2.append(np.mean(y_prob_model2[mask]))\n        frac_pos2.append(np.mean(y_true[mask]))\n\n# Compute Brier scores\nbrier1 = np.mean((y_prob_model1 - y_true) ** 2)\nbrier2 = np.mean((y_prob_model2 - y_true) ** 2)\n\n# Custom style with theme-adaptive colors\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=IMPRINT,\n    title_font_size=28,\n    label_font_size=22,\n    major_label_font_size=18,\n    legend_font_size=16,\n    value_font_size=14,\n    stroke_width=3,\n)\n\n# Create XY chart for calibration curve\nchart = pygal.XY(\n    style=custom_style,\n    width=4800,\n    height=2700,\n    title=\"calibration-curve · pygal · anyplot.ai\",\n    x_title=\"Mean Predicted Probability\",\n    y_title=\"Fraction of Positives\",\n    show_dots=True,\n    dots_size=16,\n    stroke_style={\"width\": 5},\n    show_x_guides=True,\n    show_y_guides=True,\n    x_value_formatter=lambda x: f\"{x:.1f}\",\n    range=(0, 1),\n    xrange=(0, 1),\n    legend_at_bottom=True,\n    legend_at_bottom_columns=3,\n    legend_box_size=28,\n    truncate_legend=-1,\n    margin=50,\n    margin_top=80,\n    margin_bottom=200,\n)\n\n# Perfect calibration line (diagonal reference)\nperfect_calibration = [\n    {\"value\": (0, 0), \"label\": \"Perfect calibration reference\"},\n    {\"value\": (0.25, 0.25), \"label\": \"Predicted = Observed\"},\n    {\"value\": (0.5, 0.5), \"label\": \"Ideal: 50% predicted → 50% positive\"},\n    {\"value\": (0.75, 0.75), \"label\": \"Predicted = Observed\"},\n    {\"value\": (1.0, 1.0), \"label\": \"Perfect calibration reference\"},\n]\nchart.add(\"Perfect Calibration\", perfect_calibration, stroke_dasharray=\"15,8\", dots_size=0, stroke_style={\"width\": 4})\n\n# Model 1 calibration curve - well-calibrated\nmodel1_points = [{\"value\": (0.0, 0.0), \"label\": \"Curve start\"}]\nmodel1_points.extend(\n    [\n        {\"value\": (pred, obs), \"label\": f\"Bin: {pred:.2f} pred → {obs:.2f} actual ({int(obs * 100)}% positive)\"}\n        for pred, obs in zip(mean_pred1, frac_pos1, strict=False)\n    ]\n)\nmodel1_points.append({\"value\": (1.0, 1.0), \"label\": \"Curve end\"})\nchart.add(f\"Logistic Regression (Brier: {brier1:.3f})\", model1_points)\n\n# Model 2 calibration curve - overconfident\nmodel2_points = [{\"value\": (0.0, 0.0), \"label\": \"Curve start\"}]\nmodel2_points.extend(\n    [\n        {\"value\": (pred, obs), \"label\": f\"Bin: {pred:.2f} pred → {obs:.2f} actual (Δ={pred - obs:+.2f})\"}\n        for pred, obs in zip(mean_pred2, frac_pos2, strict=False)\n    ]\n)\nmodel2_points.append({\"value\": (1.0, 1.0), \"label\": \"Curve end\"})\nchart.add(f\"Overconfident Model (Brier: {brier2:.3f})\", model2_points)\n\n# Save output files\noutput_dir = os.path.dirname(os.path.abspath(__file__))\nchart.render_to_png(os.path.join(output_dir, f\"plot-{THEME}.png\"))\nchart.render_to_file(os.path.join(output_dir, f\"plot-{THEME}.html\"))\n"}