Evaluation Metrics for Classification
Accuracy is the wrong metric more often than it is the right one. A model that predicts "healthy" for every patient in a dataset where 99% of patients are healthy scores 99% accuracy while being completely useless. Picking the right metric means picking it from the cost of each error type, not from convention.
Pick the metric from the cost of each error type, not from convention.

The confusion matrixโ
| Predicted positive | Predicted negative | |
|---|---|---|
| Actually positive | True positive (TP) | False negative (FN) |
| Actually negative | False positive (FP) | True negative (TN) |
Every classification metric below is computed from these four counts.
Accuracy and the imbalance trapโ
On a 99/1 imbalanced dataset, predicting the majority class every time scores 99% accuracy while catching zero of the minority class. Accuracy is only trustworthy when classes are roughly balanced and both error types cost about the same.
Precision and recallโ
Precision answers "of everything I flagged positive, how much was actually positive?" โ it protects against false alarms. Recall answers "of everything actually positive, how much did I catch?" โ it protects against missed cases. A spam filter favours precision (don't flag real mail); a cancer screen favours recall (don't miss real cases).
F1 and F-betaโ
is the harmonic mean of precision and recall โ punishing a large gap between them more than an arithmetic mean would. with weights recall more heavily; weights precision more heavily.
Specificity and sensitivityโ
Specificity is recall's mirror on the negative class โ "of everything actually negative, how much did I correctly call negative?"
The threshold is a choiceโ

Most classifiers output a probability, not a hard label; the 0.5 cutoff is a convention, not a law. Moving the threshold trades precision for recall in a predictable, continuous way โ the right threshold comes from the actual cost of each error type, decided during problem framing (The ML Workflow), not left at the library default.
ROC curve and AUCโ

The ROC curve plots true positive rate (recall) against false positive rate () as the threshold sweeps from 0 to 1. AUC (area under this curve) summarises performance across all thresholds at once โ 0.5 is random guessing, 1.0 is perfect separation.
Precision-recall curveโ
Plots precision against recall as the threshold sweeps. On heavily imbalanced data, the PR curve is more informative than ROC โ because ROC's false positive rate is normalised by the (huge) number of true negatives, it can look deceptively good even when precision is terrible, while the PR curve exposes that directly.
Multi-class averagingโ
- Micro: pool all TP/FP/FN across classes, then compute โ dominated by the largest classes.
- Macro: compute per-class, then average unweighted โ every class counts equally, regardless of size.
- Weighted: average per-class scores weighted by class frequency.
Calibrationโ
A model is well-calibrated if, among all predictions with confidence 0.8, roughly 80% are actually correct. A reliability diagram (predicted probability vs. observed frequency, binned) reveals over- or under-confidence โ a model can have high accuracy and still be badly calibrated, which matters whenever downstream decisions use the probability itself, not just the label.
| Symbol | Meaning |
|---|---|
| true/false positive/negative counts | |
| Precision, Recall | see above |
| F-beta's weighting between precision and recall |
Metric selection tableโ
| Which error hurts more? | Reach for |
|---|---|
| False positives (false alarms costly) | Precision, or a high threshold |
| False negatives (missed cases costly) | Recall, or a low threshold |
| Both matter, roughly equally | F1 |
| Comparing models across all thresholds, balanced data | ROC-AUC |
| Comparing models across all thresholds, imbalanced data | PR-AUC |
| Decisions depend on the probability itself | Calibration |
Code: confusion matrix, curves, threshold sweepโ
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (
confusion_matrix, classification_report, roc_curve, roc_auc_score,
precision_recall_curve,
)
X, y = make_classification(n_samples=2000, weights=[0.9, 0.1], random_state=0)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, stratify=y, random_state=0)
model = LogisticRegression().fit(X_train, y_train)
probs = model.predict_proba(X_test)[:, 1]
preds = (probs >= 0.5).astype(int)
print(confusion_matrix(y_test, preds))
print(classification_report(y_test, preds))
print("ROC-AUC:", roc_auc_score(y_test, probs))
# --- Sweep the decision threshold ---
print("\nthreshold | precision | recall")
precisions, recalls, thresholds = precision_recall_curve(y_test, probs)
for t in [0.1, 0.3, 0.5, 0.7, 0.9]:
idx = np.argmin(np.abs(thresholds - t))
print(f"{t:8.1f} | {precisions[idx]:.3f} | {recalls[idx]:.3f}")
Lowering the threshold from 0.5 toward 0.1 pushes recall up and precision down, and vice versa raising it toward 0.9 โ the table makes that trade concrete rather than abstract.
See alsoโ
- Evaluation Metrics for Regression โ the equivalent toolkit for continuous targets.
- Imbalanced Data โ the full treatment of imbalance this page only introduces.