InterviewPrepKit

Home / Coding / Machine Learning Coding / Metrics from Scratch / ROC-AUC Score

ROC-AUC Score

hard 00:00
Solving tips
  • AUC equals the probability that a random positive scores higher than a random negative — that identity is exactly the rank-based (Mann-Whitney U) formula.
  • Rank the scores, sum the ranks of the positive examples, then subtract the n_pos*(n_pos+1)/2 offset so ties among positives cancel out.
  • Handle equal scores with average (fractional) ranks; a tie between a positive and a negative should count as half a correctly ordered pair.

Implement the area under the ROC curve (ROC-AUC) for a binary classifier from scratch with NumPy, without any library metric. Instead of sweeping thresholds to trace the curve and integrating, use the fact that AUC is equivalent to a rank statistic, which is both faster and easier to get exactly right.

Definition

AUC equals the probability that a randomly chosen positive example is scored higher than a randomly chosen negative example. Counting over all positive-negative pairs and giving ties a weight of one half gives the Mann-Whitney U statistic. That statistic can be read directly off the ranks of the scores:

AUC = ( sum_of_ranks_of_positives - n_pos*(n_pos+1)/2 ) / ( n_pos * n_neg )

Here scores are ranked in ascending order starting at 1, n_pos is the number of positive labels, and n_neg the number of negatives. When several examples share the same score they receive the average of the ranks they would otherwise occupy.

Task

Complete roc_auc_score(y_true, y_scores) so it returns the ROC-AUC as a float. y_true holds labels in {0, 1} and y_scores holds real-valued predictions where a larger value means “more likely positive”. Use average ranks to break ties correctly. If either class is missing, AUC is undefined; return float("nan"). Do not call sklearn or any built-in AUC/ranking helper such as scipy.stats.rankdata.

Example

y_true = np.array([0, 0, 1, 1])
y_scores = np.array([0.1, 0.4, 0.35, 0.8])
roc_auc_score(y_true, y_scores)   # -> 0.75

# with a tie between one negative and one positive
y_true = np.array([0, 0, 1, 1])
y_scores = np.array([0.1, 0.4, 0.4, 0.8])
roc_auc_score(y_true, y_scores)   # -> 0.875

In the first case the sorted scores are 0.1(neg), 0.35(pos), 0.4(neg), 0.8(pos) with ranks 1,2,3,4; the positives sit at ranks 2 and 4, so AUC = (6 - 3) / 4 = 0.75.

Constraints

  • 1 <= n <= 10^6; use vectorized NumPy, not a Python loop over pairs (the naive pairwise count is O(n^2)).
  • Labels are exactly 0 or 1; scores fit in float64 and may contain duplicates.
  • Return float("nan") when n_pos == 0 or n_neg == 0.

Write your solution, then hit Run tests to check it — or get a mock grade from the AI coach.

The coach remembers this session — revise your code and ask again, and it grades your progress. It gives hints, not the answer.
Report a bug