""" ROC curve illustration for ROLL-AOC. Shows how ROLL-AOC converts the single-threshold ROLL into AUC optimization: - ROC curve traced by sweeping threshold τ - 10 evenly-spaced FPR sample points (as ROLL evaluates in miniature) - Green shaded region = AUC (area under curve, what we maximize) - Red shaded region = AOC = 1 − AUC (area over curve, the loss we minimize) - Vertical segments = per-threshold loss 1 − TPR(αᵢ), one per sampled FPR Run from the thesis root: nix develop --command python3 content/method/figures/roll_aoc_roc.py """ import numpy as np import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt from scipy.stats import norm OUT_DIR = "content/method/figures" # --- Parameters (same distributions as roll_principle.py) ------------------ mu0, sig0 = 0.0, 1.00 mu1, sig1 = 1.5, 1.00 # --- ROC curve ------------------------------------------------------------- # Sweep τ from high to low → FPR rises from 0 to 1 tau_grid = np.linspace(norm.ppf(0.9999, mu0, sig0), norm.ppf(0.0001, mu0, sig0), 3000) fpr_curve = 1.0 - norm.cdf(tau_grid, mu0, sig0) tpr_curve = 1.0 - norm.cdf(tau_grid, mu1, sig1) # Pin endpoints so the fill covers the full unit square correctly fpr_full = np.concatenate([[0.0], fpr_curve, [1.0]]) tpr_full = np.concatenate([[0.0], tpr_curve, [1.0]]) auc = float(np.trapezoid(tpr_full, fpr_full)) # --- 10 sample FPR points: αᵢ = i/11 for i = 1…10 ------------------------ n_samples = 10 alpha_s = np.array([(i / (n_samples + 1)) for i in range(1, n_samples + 1)]) tpr_s = np.interp(alpha_s, fpr_full, tpr_full) # --- Palette (matches roll_principle.py) ----------------------------------- C_POS = "#CC6633" # ROC curve (positive-class colour) C_GREEN = "#44AA77" # AUC fill C_RED = "#BB3333" # AOC fill / vertical segments C_DOT = "#333333" # sample dots # ── Figure ───────────────────────────────────────────────────────────────── fig, ax = plt.subplots(figsize=(4.8, 4.4)) # Random-classifier diagonal ax.plot([0, 1], [0, 1], color="#AAAAAA", linestyle="--", linewidth=0.9, zorder=1, label="Random classifier") # AUC — green fill under ROC curve ax.fill_between(fpr_full, 0, tpr_full, color=C_GREEN, alpha=0.25, zorder=2, label=rf"AUC $\approx$ {auc:.2f} (maximize)") # AOC — red fill above ROC curve ax.fill_between(fpr_full, tpr_full, 1.0, color=C_RED, alpha=0.22, zorder=2, label=r"AOC $= 1-\mathrm{AUC}$ (minimize)") # ROC curve itself ax.plot(fpr_full, tpr_full, color=C_POS, linewidth=2.0, zorder=3, label="ROC curve") # Per-threshold loss segments: vertical from TPR(αᵢ) up to 1 for a_i, t_i in zip(alpha_s, tpr_s): ax.plot([a_i, a_i], [t_i, 1.0], color=C_RED, linewidth=1.0, alpha=0.55, zorder=4) # Sample dots on the ROC curve ax.scatter(alpha_s, tpr_s, color=C_DOT, s=35, zorder=5, label=r"Sampled $\alpha_i$ (ROLL evaluations)") # --- Axes / labels --------------------------------------------------------- ax.set_xlabel(r"FPR $(\alpha)$", fontsize=9) ax.set_ylabel(r"TPR", fontsize=9) ax.set_xlim(0.0, 1.0) ax.set_ylim(0.0, 1.0) ax.set_aspect("equal") ax.tick_params(labelsize=8) ax.spines[["top", "right"]].set_visible(False) ax.legend(fontsize=7.5, loc="lower right", framealpha=0.88) fig.tight_layout() fig.savefig(f"{OUT_DIR}/roll_aoc_roc.pdf", bbox_inches="tight") fig.savefig(f"{OUT_DIR}/roll_aoc_roc.png", bbox_inches="tight", dpi=180) print(f"Saved to {OUT_DIR}/roll_aoc_roc.{{pdf,png}}") print(f"AUC = {auc:.4f} AOC = {1-auc:.4f}")