{"spec_id":"logistic-regression","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nlogistic-regression: Logistic Regression Curve Plot\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 86/100 | Updated: 2026-05-18\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\n\n\n# Theme-adaptive colors\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\n# Okabe-Ito palette\nIMPRINT = (\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\")\n\n# Data - Medical diagnosis based on biomarker level\nnp.random.seed(42)\nn_samples = 150\n\n# Generate biomarker levels (0-100 scale)\nbiomarker_levels = np.concatenate(\n    [\n        np.random.normal(30, 15, n_samples // 2),  # Lower levels (mostly negative)\n        np.random.normal(70, 15, n_samples // 2),  # Higher levels (mostly positive)\n    ]\n)\nbiomarker_levels = np.clip(biomarker_levels, 0, 100)\n\n# Generate binary outcomes with logistic probability\ntrue_probs = 1 / (1 + np.exp(-0.08 * (biomarker_levels - 50)))\ny = (np.random.random(n_samples) < true_probs).astype(int)\n\n# Fit logistic regression using gradient descent\nX = (biomarker_levels - biomarker_levels.mean()) / biomarker_levels.std()\nb0, b1 = 0.0, 0.0\nlearning_rate = 0.1\nfor _ in range(1000):\n    z = b0 + b1 * X\n    p = 1 / (1 + np.exp(-np.clip(z, -500, 500)))\n    grad_b0 = np.mean(p - y)\n    grad_b1 = np.mean((p - y) * X)\n    b0 -= learning_rate * grad_b0\n    b1 -= learning_rate * grad_b1\n\n# Generate smooth curve for predictions\nx_curve = np.linspace(0, 100, 100)\nx_curve_norm = (x_curve - biomarker_levels.mean()) / biomarker_levels.std()\ny_proba = 1 / (1 + np.exp(-np.clip(b0 + b1 * x_curve_norm, -500, 500)))\n\n# Confidence interval (approximate using binomial SE)\nse = np.sqrt(y_proba * (1 - y_proba) / n_samples) * 1.5\nci_lower = np.clip(y_proba - 1.96 * se, 0, 1)\nci_upper = np.clip(y_proba + 1.96 * se, 0, 1)\n\n# Jitter y values for visibility\ny_jittered = y + np.random.uniform(-0.025, 0.025, n_samples)\n\n# Custom style for large canvas 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\nchart = pygal.XY(\n    width=4800,\n    height=2700,\n    style=custom_style,\n    title=\"logistic-regression · pygal · pyplots.ai\",\n    x_title=\"Biomarker Level\",\n    y_title=\"Probability of Disease\",\n    show_dots=True,\n    stroke=True,\n    show_x_guides=True,\n    show_y_guides=True,\n    dots_size=8,\n    stroke_style={\"width\": 4},\n    range=(0, 1.05),\n    xrange=(-5, 105),\n    explicit_size=True,\n    legend_at_bottom=True,\n    legend_box_size=28,\n    truncate_legend=-1,\n    print_values=False,\n)\n\n# Add logistic regression curve (main feature) - Okabe-Ito position 1 (green)\ncurve_points = [(float(x_curve[i]), float(y_proba[i])) for i in range(len(x_curve))]\nchart.add(\"Logistic Fit\", curve_points, stroke_style={\"width\": 5}, dots_size=0, show_dots=False, color=IMPRINT[0])\n\n# Add confidence interval bounds - lighter/dashed variations\nci_upper_pts = [(float(x_curve[i]), float(ci_upper[i])) for i in range(0, len(x_curve), 2)]\nci_lower_pts = [(float(x_curve[i]), float(ci_lower[i])) for i in range(0, len(x_curve), 2)]\nchart.add(\n    \"95% CI Upper\",\n    ci_upper_pts,\n    stroke_style={\"width\": 2, \"dasharray\": \"8,4\"},\n    dots_size=0,\n    show_dots=False,\n    color=IMPRINT[0],\n)\nchart.add(\n    \"95% CI Lower\",\n    ci_lower_pts,\n    stroke_style={\"width\": 2, \"dasharray\": \"8,4\"},\n    dots_size=0,\n    show_dots=False,\n    color=IMPRINT[0],\n)\n\n# Add decision threshold line (y = 0.5)\nthreshold_pts = [(0.0, 0.5), (100.0, 0.5)]\nchart.add(\n    \"Threshold (p=0.5)\",\n    threshold_pts,\n    stroke_style={\"width\": 3, \"dasharray\": \"12,6\"},\n    dots_size=0,\n    show_dots=False,\n    color=INK_MUTED,\n)\n\n# Add data points - Negative (Class 0) - Okabe-Ito position 2 (vermillion)\nnegative_pts = [(float(biomarker_levels[i]), float(y_jittered[i])) for i in range(n_samples) if y[i] == 0]\nchart.add(\"Negative (0)\", negative_pts, stroke=False, dots_size=12, color=IMPRINT[1])\n\n# Add data points - Positive (Class 1) - Okabe-Ito position 3 (blue)\npositive_pts = [(float(biomarker_levels[i]), float(y_jittered[i])) for i in range(n_samples) if y[i] == 1]\nchart.add(\"Positive (1)\", positive_pts, stroke=False, dots_size=12, color=IMPRINT[2])\n\n# Save as PNG and HTML with theme suffix\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}