Evaluation Metrics

A model is only as good as the way it is measured. This page explains the main metrics for classification, regression and ranking, what each one rewards, and how to choose the one that matches the real cost of mistakes.


1. What Evaluation Metrics Are

An evaluation metric turns a model's predictions on held-out data into a number that says how good it is. The metric you optimise and report decides which model you ship, so choosing it is a design decision, not a formality. Metrics differ by task: classification compares predicted labels or scores to true classes, regression measures numeric error, and ranking measures whether the most relevant items appear near the top.

Choosing a Metric Family Classification precision, recall, F1 ROC AUC, PR AUC, log loss Regression MAE, RMSE, MAPE R squared Ranking precision@k, recall@k MRR, NDCG Pick the metric that matches the cost of mistakes, then report a baseline beside it.

Every metric should be computed on data the model has not seen during training or tuning: a held-out test set, cross-validation folds, or a time-based split for anything temporal.

2. Why It Matters

A single headline number hides trade-offs. A fraud model with 99.9% accuracy may catch no fraud at all when only 0.1% of transactions are fraudulent. A forecast with low average error may be badly wrong on the high-value days that matter. Good metric choice aligns the model with the real cost of errors, makes comparisons between models fair, and gives monitoring something meaningful to track after deployment.

3. Classification Metrics

The Confusion Matrix Predicted positive Predicted negative Actual positive Actual negative TP = 80 correct alarm FN = 20 missed FP = 40 false alarm TN = 860 correct silence recall = 80 / 100 = 0.80 precision = 80 / 120 = 0.67 accuracy = 940 / 1000 = 0.94 F1 = 0.73 With 10% positives, predicting all negative already scores 0.90 accuracy.

All threshold-based classification metrics come from the four cells of the confusion matrix. Accuracy = (TP + TN) / total. Precision = TP / (TP + FP): of the items flagged, how many were right. Recall (sensitivity, true positive rate) = TP / (TP + FN): of the real positives, how many were found. Specificity = TN / (TN + FP). F1 is the harmonic mean 2PR / (P + R), which is low unless both precision and recall are reasonable; Fβ weights recall β times as heavily as precision.

Most classifiers output a score, and a threshold converts it to a label. Raising the threshold usually increases precision and decreases recall. Threshold-free metrics summarise all thresholds at once:

ROC and Precision-Recall Curves false positive rate true positive rate ROC recall precision Precision-Recall diagonal = random (AUC 0.5) baseline = positive rate Each point on a curve is one threshold; the area summarises all thresholds.

ROC AUC plots true positive rate against false positive rate; its area equals the probability that a randomly chosen positive is scored above a randomly chosen negative. It is insensitive to class balance, which makes it look optimistic on rare-event problems. PR AUC (average precision) plots precision against recall; its baseline is the positive rate, so it is more informative when positives are rare. Log loss (cross-entropy) evaluates the probabilities themselves and punishes confident mistakes heavily; pair it with a calibration plot when probabilities drive decisions. For multi-class problems, compute per-class metrics and average them: macro averaging treats classes equally, micro pools all decisions, weighted weights by class frequency.

Choosing the operating threshold is a separate step from choosing the model. A principled approach assigns a cost to each kind of error (for example, a missed fraud costs the transaction amount, a false alert costs an analyst's time) and picks the threshold that minimises expected cost on validation data. Alternatively, fix a constraint such as "recall at least 0.9" and maximise precision subject to it. Revisit the threshold when the class balance changes.

4. Regression Metrics

With errors ei = yi − ŷi: MAE = mean |ei| is in the target's units and robust to outliers. RMSE = √(mean ei2) penalises large errors more and is what least-squares models minimise. MAPE = mean |ei / yi| is scale-free but undefined at zero and asymmetric; sMAPE and weighted MAPE (sum of absolute errors over sum of actuals) are common fixes. R2 = 1 − SSres/SStot compares the model with predicting the mean; it can be negative on test data. For forecasting, also compare against a naive baseline such as "same as last week" (MASE does this formally).

5. Ranking Metrics

Search, recommendation and retrieval for RAG are judged on order. Precision@k is the share of the top k results that are relevant; recall@k is the share of all relevant items found in the top k. MRR (mean reciprocal rank) averages 1/rank of the first relevant result, rewarding getting one good answer to the top. NDCG@k handles graded relevance: DCG = Σ (2reli − 1) / log2(i + 1), divided by the DCG of the ideal ordering so scores fall between 0 and 1. MAP averages precision at each relevant position.

6. Worked Example

The script computes the main metrics of each family with scikit-learn and reproduces the confusion matrix above (pip install scikit-learn numpy).

import numpy as np
from sklearn.metrics import (confusion_matrix, precision_score, recall_score, f1_score,
    accuracy_score, roc_auc_score, average_precision_score, log_loss,
    mean_absolute_error, mean_squared_error, r2_score, ndcg_score)

# Classification: 100 positives, 900 negatives
rng = np.random.default_rng(0)
y = np.r_[np.ones(100), np.zeros(900)].astype(int)
pred = np.r_[np.ones(80), np.zeros(20), np.ones(40), np.zeros(860)].astype(int)
score = np.clip(pred * 0.6 + rng.uniform(0, 0.4, 1000), 0.001, 0.999)

print(confusion_matrix(y, pred, labels=[1, 0]))   # [[80 20] [40 860]]
print("acc %.3f  P %.3f  R %.3f  F1 %.3f" % (accuracy_score(y, pred),
      precision_score(y, pred), recall_score(y, pred), f1_score(y, pred)))
print("ROC AUC %.3f  PR AUC %.3f  log loss %.3f" % (roc_auc_score(y, score),
      average_precision_score(y, score), log_loss(y, score)))

# Regression
y_true = np.array([100, 150, 200, 250, 300.0])
y_hat  = np.array([110, 140, 210, 230, 330.0])
print("MAE %.1f  RMSE %.1f  R2 %.3f" % (mean_absolute_error(y_true, y_hat),
      np.sqrt(mean_squared_error(y_true, y_hat)), r2_score(y_true, y_hat)))

# Ranking: graded relevance of 5 results for one query, and the model's scores
relevance = np.array([[3, 2, 0, 0, 1]])
scores    = np.array([[0.9, 0.3, 0.8, 0.1, 0.5]])
print("NDCG@5 %.3f" % ndcg_score(relevance, scores, k=5))

Note that accuracy (0.94) looks excellent while precision is only 0.67: one in three alerts is false. Whether that is acceptable depends on what an alert costs compared with a missed case, which is a business question, not a statistical one.

7. Metric Comparison

MetricTaskRangeUse whenAvoid when
AccuracyClassification0–1Classes balanced, errors cost the sameRare positives
Precision / Recall / F1Classification0–1Positive class matters; costs asymmetricYou need threshold-free comparison
ROC AUCClassification0.5–1 (useful range)Ranking quality, balanced-ish dataHeavy imbalance
PR AUCClassificationbaseline–1Rare-event detectionNegatives matter as much
Log lossClassification0–∞Probabilities drive decisionsOnly labels matter
MAE / RMSERegression0–∞ (units)Typical error; RMSE when big errors hurtComparing across scales
MAPERegression0–∞ (%)Business-friendly percentagesTargets near zero
NDCG@k / MRRRanking0–1Order of results mattersUnordered set outputs

8. Pitfalls

10. Further Reading