Neural Mastery
← Practice
ROC AUC From Scratch (Rank-Based)hard

ROC AUC From Scratch (Rank-Based)

hard Classical ML from Scratch 20–25 min

NumPy allowed · plain Python earns 10 bonus points

Implement ROC AUC via the exact rank-based (Mann-Whitney U) formula, which is mathematically identical to the trapezoidal-rule area under the ROC curve but needs no threshold sweep -- and is exactly what "AUC is the probability a random positive scores higher than a random negative" means, made computational.

Task

Implement compute_auc(y_true, y_scores): rank all scores ascending (tied scores share the average of their tied rank positions), sum the ranks belonging to positive examples, then apply AUC = (sum_ranks_pos - n_pos*(n_pos+1)/2) / (n_pos*n_neg).

Function Signature

compute_auc(y_true: list[int], y_scores: list[float]) -> float

Examples

Example 1: No Ties, One Discordant Pair
Input: y_true = [0, 0, 1, 1] y_scores = [0.1, 0.4, 0.35, 0.8]
Output: 0.75
Explanation: 3 of 4 positive/negative pairs are correctly ordered
Example 2: Interleaved Labels
Input: y_true = [1, 0, 1, 0] y_scores = [0.9, 0.8, 0.7, 0.6]
Output: 0.75

Constraints

  • y_true must contain both 0 and 1 -- raise ValueError otherwise (AUC is undefined for a single class).
  • Tied scores must share the AVERAGE of the rank positions they span (this is what correctly gives a tied pos/neg pair a "half win" instead of an arbitrary full win or loss).
  • Ranks are 1-indexed over the ascending-sorted scores.
Python3Saved ✓
def compute_auc(y_true: list[int], y_scores: list[float]) -> float:
"""y_true: 0/1 labels. y_scores: a predicted score per example (higher
= more likely positive). Return the ROC AUC using the Mann-Whitney
rank formula: AUC = (sum of ranks of the positive examples -
n_pos*(n_pos+1)/2) / (n_pos * n_neg), where ranks are 1-indexed over
y_scores sorted ascending, with tied scores given the AVERAGE rank of
their tied positions. Raise ValueError if y_true has only one class."""
# Your implementation here
pass

Case 1: No Ties, One Discordant Pair
Input: y_true = [0, 0, 1, 1] y_scores = [0.1, 0.4, 0.35, 0.8]
Expected: 0.75
Case 2: Interleaved Labels
Input: y_true = [1, 0, 1, 0] y_scores = [0.9, 0.8, 0.7, 0.6]
Expected: 0.75