{"spec_id":"logistic-regression","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nlogistic-regression: Logistic Regression Curve Plot\nLibrary: letsplot 4.9.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-18\n\"\"\"\n\nimport os\nimport shutil\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import *\nfrom lets_plot.export import ggsave\nfrom sklearn.linear_model import LogisticRegression\n\n\nLetsPlot.setup_html()\n\n# Theme tokens (see prompts/default-style-guide.md \"Background\" + \"Theme-adaptive Chrome\")\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\n# Okabe-Ito palette (first two colors for binary classification)\nIMPRINT = [\"#009E73\", \"#C475FD\"]\n\n# Data - Generate binary classification data with clear sigmoidal relationship\nnp.random.seed(42)\nn_samples = 200\n\n# Feature: Study hours (0 to 10 hours)\nx = np.random.uniform(0, 10, n_samples)\n\n# True probability follows a logistic function\ntrue_prob = 1 / (1 + np.exp(-1.5 * (x - 5)))\n\n# Binary outcome (pass/fail exam based on study hours)\ny = (np.random.random(n_samples) < true_prob).astype(int)\n\n# Fit logistic regression model using sklearn\nX_reshaped = x.reshape(-1, 1)\nmodel = LogisticRegression()\nmodel.fit(X_reshaped, y)\n\n# Get model parameters for annotation\ncoef = model.coef_[0][0]\nintercept = model.intercept_[0]\naccuracy = model.score(X_reshaped, y)\n\n# Generate smooth curve for prediction\nx_line = np.linspace(0, 10, 200)\nX_line = x_line.reshape(-1, 1)\ny_prob = model.predict_proba(X_line)[:, 1]\n\n# Calculate confidence intervals using approximate standard error\nse = np.sqrt(y_prob * (1 - y_prob) / n_samples) * 2\nci_lower = np.clip(y_prob - 1.96 * se, 0, 1)\nci_upper = np.clip(y_prob + 1.96 * se, 0, 1)\n\n# Add jitter to y values for visibility\ny_jittered = y + np.random.normal(0, 0.03, n_samples)\ny_jittered = np.clip(y_jittered, -0.1, 1.1)\n\n# Create DataFrames\ndf_points = pd.DataFrame(\n    {\"Study Hours\": x, \"Probability\": y_jittered, \"Class\": [\"Pass\" if yi == 1 else \"Fail\" for yi in y]}\n)\n\ndf_curve = pd.DataFrame({\"Study Hours\": x_line, \"Probability\": y_prob})\n\ndf_ci = pd.DataFrame({\"Study Hours\": x_line, \"ci_lower\": ci_lower, \"ci_upper\": ci_upper})\n\n# Create plot with theme-adaptive styling\nanyplot_theme = theme(\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    panel_grid_major=element_line(color=INK, size=0.3),\n    panel_grid_minor=element_blank(),\n    axis_title=element_text(color=INK, size=20),\n    axis_text=element_text(color=INK_SOFT, size=16),\n    axis_line=element_line(color=INK_SOFT, size=0.5),\n    plot_title=element_text(color=INK, size=24),\n    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    legend_text=element_text(color=INK_SOFT, size=16),\n    legend_title=element_text(color=INK, size=18),\n)\n\nplot = (\n    ggplot()\n    # Confidence interval ribbon\n    + geom_ribbon(aes(x=\"Study Hours\", ymin=\"ci_lower\", ymax=\"ci_upper\"), data=df_ci, fill=IMPRINT[0], alpha=0.15)\n    # Logistic curve\n    + geom_line(aes(x=\"Study Hours\", y=\"Probability\"), data=df_curve, color=IMPRINT[0], size=2.5)\n    # Decision threshold line\n    + geom_hline(yintercept=0.5, linetype=\"dashed\", color=INK_SOFT, size=1, alpha=0.6)\n    # Data points with jitter\n    + geom_point(aes(x=\"Study Hours\", y=\"Probability\", color=\"Class\"), data=df_points, size=5, alpha=0.65)\n    # Colors for classes using Okabe-Ito\n    + scale_color_manual(values=IMPRINT)\n    # Labels with model annotation\n    + labs(\n        x=\"Study Hours\",\n        y=\"Probability\",\n        title=\"logistic-regression · python · letsplot · anyplot.ai\",\n        color=\"Class\",\n        subtitle=f\"Coefficient: {coef:.2f} | Accuracy: {accuracy:.1%}\",\n    )\n    # Y-axis from 0 to 1\n    + scale_y_continuous(limits=[-0.1, 1.1])\n    # Theme and size\n    + anyplot_theme\n    + ggsize(1600, 900)\n)\n\n# Save as PNG and HTML with theme suffix\nggsave(plot, f\"plot-{THEME}.png\", scale=3)\nggsave(plot, f\"plot-{THEME}.html\")\n\n# Move files from lets-plot-images subfolder to current directory\nif os.path.exists(\"lets-plot-images\"):\n    for filename in [f\"plot-{THEME}.png\", f\"plot-{THEME}.html\"]:\n        src = os.path.join(\"lets-plot-images\", filename)\n        if os.path.exists(src):\n            shutil.move(src, filename)\n"}