Logistic Regression Curve Plot in plotnine (Python)

The same plot in 14 other libraries — Python: Altair, Bokeh, lets-plot, Matplotlib, Plotly, Pygal, Seaborn; R: ggplot2; Julia: Makie.jl; JavaScript: Chart.js, D3.js, Apache ECharts, Highcharts, MUI X Charts. Compare all 15 side by side: Logistic Regression Curve Plot in Python, R, Julia and JavaScript.

A logistic regression visualization showing the characteristic S-shaped (sigmoid) probability curve for binary classification. The plot displays data points colored by their binary class, the fitted logistic curve representing predicted probabilities, confidence intervals around the curve, and a dashed decision threshold line at probability 0.5. This visualization is essential for understanding how a logistic model maps continuous input features to class probabilities.

Logistic Regression Curve Plot rendered with plotnine

Renders

Python source (plotnine)

""" anyplot.ai
logistic-regression: Logistic Regression Curve Plot
Library: plotnine 0.15.4 | Python 3.13.13
Quality: 94/100 | Updated: 2026-05-18
"""

import os

import numpy as np
import pandas as pd
import statsmodels.api as sm
from plotnine import (
    aes,
    element_line,
    element_rect,
    element_text,
    geom_hline,
    geom_line,
    geom_point,
    geom_ribbon,
    ggplot,
    labs,
    position_jitter,
    scale_color_manual,
    theme,
    theme_minimal,
)


# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"

# imprint semantic anchors
FAIL_COLOR = "#AE3030"  # imprint red - fail
PASS_COLOR = "#009E73"  # imprint green - pass
CURVE_COLOR = "#4467A3"  # imprint blue - regression curve

# Data - Exam score vs Pass/Fail outcome
np.random.seed(42)
n_samples = 150

# Generate exam scores with different distributions for pass/fail
scores_fail = np.random.normal(45, 12, 60)  # Lower scores tend to fail
scores_pass = np.random.normal(70, 10, 90)  # Higher scores tend to pass
scores = np.concatenate([scores_fail, scores_pass])
outcomes = np.concatenate([np.zeros(60), np.ones(90)])

# Add some noise to outcomes for realism
flip_indices = np.random.choice(n_samples, size=15, replace=False)
outcomes[flip_indices] = 1 - outcomes[flip_indices]

# Clip scores to reasonable range
scores = np.clip(scores, 20, 100)

# Fit logistic regression using statsmodels
X = sm.add_constant(scores)
model = sm.Logit(outcomes, X).fit(disp=0)

# Create smooth curve for predictions with confidence intervals
x_curve = np.linspace(20, 100, 200)
X_curve = sm.add_constant(x_curve)
predictions = model.get_prediction(X_curve)
y_pred = predictions.predicted
conf_int = predictions.conf_int(alpha=0.05)
y_lower = conf_int[:, 0]
y_upper = conf_int[:, 1]

# Create dataframes
df_points = pd.DataFrame(
    {"score": scores, "outcome": outcomes, "class": ["Fail" if o == 0 else "Pass" for o in outcomes]}
)

df_curve = pd.DataFrame({"score": x_curve, "probability": y_pred, "lower": y_lower, "upper": y_upper})

# Theme
anyplot_theme = theme(
    figure_size=(16, 9),
    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
    panel_background=element_rect(fill=PAGE_BG),
    panel_grid_major=element_line(color=INK, size=0.3, alpha=0.08),
    panel_grid_minor=element_line(color=INK, size=0.2, alpha=0.04),
    panel_border=element_rect(color=INK_SOFT, fill=None, size=0.5),
    axis_title=element_text(size=20, color=INK),
    axis_text=element_text(size=16, color=INK_SOFT),
    axis_line=element_line(color=INK_SOFT, size=0.5),
    plot_title=element_text(size=24, color=INK),
    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),
    legend_text=element_text(size=16, color=INK_SOFT),
    legend_title=element_text(size=18, color=INK),
)

# Create plot
plot = (
    ggplot()
    # Confidence interval ribbon
    + geom_ribbon(data=df_curve, mapping=aes(x="score", ymin="lower", ymax="upper"), alpha=0.25, fill=CURVE_COLOR)
    # Fitted logistic curve
    + geom_line(data=df_curve, mapping=aes(x="score", y="probability"), color=CURVE_COLOR, size=2)
    # Decision threshold line at p=0.5
    + geom_hline(yintercept=0.5, linetype="dashed", color=INK_SOFT, size=1, alpha=0.6)
    # Data points with jitter
    + geom_point(
        data=df_points,
        mapping=aes(x="score", y="outcome", color="class"),
        size=4,
        alpha=0.6,
        position=position_jitter(width=0, height=0.03),
    )
    # Colors
    + scale_color_manual(values={"Fail": FAIL_COLOR, "Pass": PASS_COLOR})
    # Labels
    + labs(
        title="logistic-regression · python · plotnine · anyplot.ai",
        x="Exam Score (points)",
        y="Probability of Passing",
        color="Outcome",
    )
    + theme_minimal()
    + anyplot_theme
)

# Save
plot.save(f"plot-{THEME}.png", dpi=300)

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/logistic-regression/plotnine/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "logistic-regression",
  "language": "python",
  "library": "plotnine",
  "page": "https://anyplot.ai/logistic-regression/python/plotnine",
  "hub": "https://anyplot.ai/logistic-regression",
  "code_json": "https://api.anyplot.ai/specs/logistic-regression/plotnine/code",
  "spec_json": "https://api.anyplot.ai/specs/logistic-regression",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/logistic-regression/python/plotnine/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/logistic-regression/python/plotnine/plot-dark.png",
  "quality_score": 94.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Logistic Regression Curve Plot on anyplot.ai.

Other implementations