A Receiver Operating Characteristic (ROC) curve visualizes the performance of a binary classifier by plotting the True Positive Rate (TPR) against the False Positive Rate (FPR) at various classification thresholds. The Area Under the Curve (AUC) provides a single metric summarizing model performance, where 1.0 indicates perfect classification and 0.5 represents random guessing.

""" anyplot.ai
roc-curve: ROC Curve with AUC
Library: altair 6.1.0 | Python 3.13.13
Quality: 93/100 | Updated: 2026-05-09
"""
import os
import sys
current_dir = sys.path[0] if sys.path and sys.path[0] else "."
if current_dir in sys.path:
sys.path.remove(current_dir)
sys.path.insert(0, "/dev/null")
import altair as alt
sys.path = [p for p in sys.path if p != "/dev/null"]
if current_dir not in sys.path:
sys.path.insert(0, current_dir)
import numpy as np
import pandas as pd
# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
# Okabe-Ito palette
BRAND = "#009E73" # Position 1
SECONDARY = "#C475FD" # Position 2
NEUTRAL = INK_MUTED # Diagonal reference line
# Data - Generate synthetic classification scores and compute ROC curve
np.random.seed(42)
n_samples = 500
n_thresholds = 200
# Simulate two models with different performance levels
y_true = np.concatenate([np.zeros(n_samples // 2), np.ones(n_samples // 2)])
scores_model1 = np.where(y_true == 1, np.random.beta(5, 2, n_samples), np.random.beta(2, 5, n_samples))
scores_model2 = np.where(y_true == 1, np.random.beta(3, 2, n_samples), np.random.beta(2, 3, n_samples))
# Compute ROC curve for Model 1
thresholds = np.linspace(0, 1, n_thresholds)
tpr1_list, fpr1_list = [], []
for thresh in thresholds:
y_pred = (scores_model1 >= thresh).astype(int)
tp = np.sum((y_pred == 1) & (y_true == 1))
fp = np.sum((y_pred == 1) & (y_true == 0))
fn = np.sum((y_pred == 0) & (y_true == 1))
tn = np.sum((y_pred == 0) & (y_true == 0))
tpr1_list.append(tp / (tp + fn) if (tp + fn) > 0 else 0)
fpr1_list.append(fp / (fp + tn) if (fp + tn) > 0 else 0)
fpr1 = np.array(fpr1_list)
tpr1 = np.array(tpr1_list)
# Compute ROC curve for Model 2
tpr2_list, fpr2_list = [], []
for thresh in thresholds:
y_pred = (scores_model2 >= thresh).astype(int)
tp = np.sum((y_pred == 1) & (y_true == 1))
fp = np.sum((y_pred == 1) & (y_true == 0))
fn = np.sum((y_pred == 0) & (y_true == 1))
tn = np.sum((y_pred == 0) & (y_true == 0))
tpr2_list.append(tp / (tp + fn) if (tp + fn) > 0 else 0)
fpr2_list.append(fp / (fp + tn) if (fp + tn) > 0 else 0)
fpr2 = np.array(fpr2_list)
tpr2 = np.array(tpr2_list)
# Compute AUC using trapezoidal rule
auc1 = -np.trapezoid(tpr1, fpr1)
auc2 = -np.trapezoid(tpr2, fpr2)
# Create labels for legend
label1 = f"Strong Model (AUC = {auc1:.2f})"
label2 = f"Weak Model (AUC = {auc2:.2f})"
label_random = "Random (AUC = 0.50)"
# Create DataFrames for Altair
df_model1 = pd.DataFrame({"fpr": fpr1, "tpr": tpr1, "Model": label1})
df_model2 = pd.DataFrame({"fpr": fpr2, "tpr": tpr2, "Model": label2})
df_roc = pd.concat([df_model1, df_model2], ignore_index=True)
df_diagonal = pd.DataFrame({"fpr": [0, 1], "tpr": [0, 1], "Model": label_random})
# ROC curves with interactivity
roc_lines = (
alt.Chart(df_roc)
.mark_line(strokeWidth=4)
.encode(
x=alt.X("fpr:Q", title="False Positive Rate", scale=alt.Scale(domain=[0, 1])),
y=alt.Y("tpr:Q", title="True Positive Rate", scale=alt.Scale(domain=[0, 1])),
color=alt.Color("Model:N", scale=alt.Scale(domain=[label1, label2], range=[BRAND, SECONDARY])),
tooltip=["fpr:Q", "tpr:Q", "Model:N"],
)
)
# Diagonal reference line
diagonal_line = (
alt.Chart(df_diagonal)
.mark_line(strokeWidth=3, strokeDash=[8, 6])
.encode(x="fpr:Q", y="tpr:Q", color=alt.value(NEUTRAL), tooltip=alt.value(None))
)
# Combine and style
chart = (
(roc_lines + diagonal_line)
.properties(
width=1400, height=1400, background=PAGE_BG, title=alt.Title("roc-curve · altair · anyplot.ai", fontSize=28)
)
.configure_title(color=INK, anchor="middle", fontWeight="normal")
.configure_axis(
domainColor=INK_SOFT,
tickColor=INK_SOFT,
gridColor=INK,
gridOpacity=0.10,
labelColor=INK_SOFT,
titleColor=INK,
labelFontSize=18,
titleFontSize=22,
titlePadding=15,
labelPadding=10,
)
.configure_legend(
fillColor="#FFFDF6" if THEME == "light" else "#242420",
strokeColor=INK_SOFT,
labelColor=INK_SOFT,
titleColor=INK,
titleFontSize=18,
labelFontSize=16,
)
.configure_view(fill=PAGE_BG, stroke=INK_SOFT)
.interactive()
)
# Save outputs
chart.save(f"plot-{THEME}.png", scale_factor=3.0)
chart.save(f"plot-{THEME}.html")
Part of ROC Curve with AUC on anyplot.ai.