{"spec_id":"roc-curve","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nroc-curve: ROC Curve with AUC\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 93/100 | Updated: 2026-05-09\n\"\"\"\n\nimport os\nimport sys\n\n\ncurrent_dir = sys.path[0] if sys.path and sys.path[0] else \".\"\nif current_dir in sys.path:\n    sys.path.remove(current_dir)\nsys.path.insert(0, \"/dev/null\")\n\nimport altair as alt\n\n\nsys.path = [p for p in sys.path if p != \"/dev/null\"]\nif current_dir not in sys.path:\n    sys.path.insert(0, current_dir)\n\nimport numpy as np\nimport pandas as pd\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_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Okabe-Ito palette\nBRAND = \"#009E73\"  # Position 1\nSECONDARY = \"#C475FD\"  # Position 2\nNEUTRAL = INK_MUTED  # Diagonal reference line\n\n# Data - Generate synthetic classification scores and compute ROC curve\nnp.random.seed(42)\nn_samples = 500\nn_thresholds = 200\n\n# Simulate two models with different performance levels\ny_true = np.concatenate([np.zeros(n_samples // 2), np.ones(n_samples // 2)])\nscores_model1 = np.where(y_true == 1, np.random.beta(5, 2, n_samples), np.random.beta(2, 5, n_samples))\nscores_model2 = np.where(y_true == 1, np.random.beta(3, 2, n_samples), np.random.beta(2, 3, n_samples))\n\n# Compute ROC curve for Model 1\nthresholds = np.linspace(0, 1, n_thresholds)\ntpr1_list, fpr1_list = [], []\nfor thresh in thresholds:\n    y_pred = (scores_model1 >= thresh).astype(int)\n    tp = np.sum((y_pred == 1) & (y_true == 1))\n    fp = np.sum((y_pred == 1) & (y_true == 0))\n    fn = np.sum((y_pred == 0) & (y_true == 1))\n    tn = np.sum((y_pred == 0) & (y_true == 0))\n    tpr1_list.append(tp / (tp + fn) if (tp + fn) > 0 else 0)\n    fpr1_list.append(fp / (fp + tn) if (fp + tn) > 0 else 0)\nfpr1 = np.array(fpr1_list)\ntpr1 = np.array(tpr1_list)\n\n# Compute ROC curve for Model 2\ntpr2_list, fpr2_list = [], []\nfor thresh in thresholds:\n    y_pred = (scores_model2 >= thresh).astype(int)\n    tp = np.sum((y_pred == 1) & (y_true == 1))\n    fp = np.sum((y_pred == 1) & (y_true == 0))\n    fn = np.sum((y_pred == 0) & (y_true == 1))\n    tn = np.sum((y_pred == 0) & (y_true == 0))\n    tpr2_list.append(tp / (tp + fn) if (tp + fn) > 0 else 0)\n    fpr2_list.append(fp / (fp + tn) if (fp + tn) > 0 else 0)\nfpr2 = np.array(fpr2_list)\ntpr2 = np.array(tpr2_list)\n\n# Compute AUC using trapezoidal rule\nauc1 = -np.trapezoid(tpr1, fpr1)\nauc2 = -np.trapezoid(tpr2, fpr2)\n\n# Create labels for legend\nlabel1 = f\"Strong Model (AUC = {auc1:.2f})\"\nlabel2 = f\"Weak Model (AUC = {auc2:.2f})\"\nlabel_random = \"Random (AUC = 0.50)\"\n\n# Create DataFrames for Altair\ndf_model1 = pd.DataFrame({\"fpr\": fpr1, \"tpr\": tpr1, \"Model\": label1})\ndf_model2 = pd.DataFrame({\"fpr\": fpr2, \"tpr\": tpr2, \"Model\": label2})\ndf_roc = pd.concat([df_model1, df_model2], ignore_index=True)\ndf_diagonal = pd.DataFrame({\"fpr\": [0, 1], \"tpr\": [0, 1], \"Model\": label_random})\n\n# ROC curves with interactivity\nroc_lines = (\n    alt.Chart(df_roc)\n    .mark_line(strokeWidth=4)\n    .encode(\n        x=alt.X(\"fpr:Q\", title=\"False Positive Rate\", scale=alt.Scale(domain=[0, 1])),\n        y=alt.Y(\"tpr:Q\", title=\"True Positive Rate\", scale=alt.Scale(domain=[0, 1])),\n        color=alt.Color(\"Model:N\", scale=alt.Scale(domain=[label1, label2], range=[BRAND, SECONDARY])),\n        tooltip=[\"fpr:Q\", \"tpr:Q\", \"Model:N\"],\n    )\n)\n\n# Diagonal reference line\ndiagonal_line = (\n    alt.Chart(df_diagonal)\n    .mark_line(strokeWidth=3, strokeDash=[8, 6])\n    .encode(x=\"fpr:Q\", y=\"tpr:Q\", color=alt.value(NEUTRAL), tooltip=alt.value(None))\n)\n\n# Combine and style\nchart = (\n    (roc_lines + diagonal_line)\n    .properties(\n        width=1400, height=1400, background=PAGE_BG, title=alt.Title(\"roc-curve · altair · anyplot.ai\", fontSize=28)\n    )\n    .configure_title(color=INK, anchor=\"middle\", fontWeight=\"normal\")\n    .configure_axis(\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK,\n        gridOpacity=0.10,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        labelFontSize=18,\n        titleFontSize=22,\n        titlePadding=15,\n        labelPadding=10,\n    )\n    .configure_legend(\n        fillColor=\"#FFFDF6\" if THEME == \"light\" else \"#242420\",\n        strokeColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        titleFontSize=18,\n        labelFontSize=16,\n    )\n    .configure_view(fill=PAGE_BG, stroke=INK_SOFT)\n    .interactive()\n)\n\n# Save outputs\nchart.save(f\"plot-{THEME}.png\", scale_factor=3.0)\nchart.save(f\"plot-{THEME}.html\")\n"}