Python · Course navigation

Evaluating classifiers: accuracy, precision, recall, F1

Build a confusion matrix and see why accuracy alone can be misleading for rare classes.

On this page
Before you start

Lists, zip, and basic arithmetic.

  • Build a confusion matrix and see why accuracy alone can be misleading for rare classes.
  • Predict the sample output and explain the state changes.
  • Complete the practice task and check the boundary cases.

The concept

Machine learning builds models from data, so judging a model is as important as building it. For a binary classifier the confusion matrix counts four outcomes: true positives (predicted positive and actually positive), false positives, false negatives, and true negatives. Every common metric is computed from these four counts.

Accuracy is the fraction of correct predictions. Precision, tp / (tp + fp), answers: when the model says positive, how often is it right? Recall, tp / (tp + fn), answers: of all the real positives, how many did the model find? The F1 score is the harmonic mean of precision and recall, which is high only when both are high. Which metric matters depends on the cost of each mistake: a spam filter wants high precision, while a medical screening test usually wants high recall.

Accuracy can be badly misleading when classes are imbalanced. The last example has 10 positives among 1000 cases. A useless model that always predicts negative is 99 percent accurate and has recall 0, because it finds none of the cases that matter. Evaluate on data the model did not train on, and for rare events look at precision and recall, not accuracy alone.

Code example

Python
def confusion(actual, predicted):
    tp = sum(a == 1 and p == 1 for a, p in zip(actual, predicted))
    fp = sum(a == 0 and p == 1 for a, p in zip(actual, predicted))
    fn = sum(a == 1 and p == 0 for a, p in zip(actual, predicted))
    tn = sum(a == 0 and p == 0 for a, p in zip(actual, predicted))
    return tp, fp, fn, tn


def metrics(actual, predicted):
    tp, fp, fn, tn = confusion(actual, predicted)
    accuracy = (tp + tn) / len(actual)
    precision = tp / (tp + fp) if tp + fp else 0.0
    recall = tp / (tp + fn) if tp + fn else 0.0
    f1 = 2 * precision * recall / (precision + recall) if precision + recall else 0.0
    return accuracy, precision, recall, f1


actual = [1, 1, 1, 1, 0, 0, 0, 0, 0, 0]
predicted = [1, 1, 0, 0, 0, 0, 0, 0, 1, 0]
tp, fp, fn, tn = confusion(actual, predicted)
print(f"tp={tp} fp={fp} fn={fn} tn={tn}")
print(*[round(value, 3) for value in metrics(actual, predicted)])

rare = [1] * 10 + [0] * 990
always_negative = [0] * 1000
accuracy, precision, recall, f1 = metrics(rare, always_negative)
print(accuracy, recall)
Output
tp=2 fp=1 fn=2 tn=5
0.7 0.667 0.5 0.571
0.99 0.0

Follow the code

Open the walkthrough to inspect the example one step at a time.

Show guided walkthrough
Guided walkthroughReady
1def confusion(actual, predicted):
2 tp = sum(a == 1 and p == 1 for a, p in zip(actual, predicted))
3 fp = sum(a == 0 and p == 1 for a, p in zip(actual, predicted))
4 fn = sum(a == 1 and p == 0 for a, p in zip(actual, predicted))
5 tn = sum(a == 0 and p == 0 for a, p in zip(actual, predicted))
6 return tp, fp, fn, tn
7
8
9def metrics(actual, predicted):
10 tp, fp, fn, tn = confusion(actual, predicted)
11 accuracy = (tp + tn) / len(actual)
12 precision = tp / (tp + fp) if tp + fp else 0.0
13 recall = tp / (tp + fn) if tp + fn else 0.0
14 f1 = 2 * precision * recall / (precision + recall) if precision + recall else 0.0
15 return accuracy, precision, recall, f1
16
17
18actual = [1, 1, 1, 1, 0, 0, 0, 0, 0, 0]
19predicted = [1, 1, 0, 0, 0, 0, 0, 0, 1, 0]
20tp, fp, fn, tn = confusion(actual, predicted)
21print(f"tp={tp} fp={fp} fn={fn} tn={tn}")
22print(*[round(value, 3) for value in metrics(actual, predicted)])
23
24rare = [1] * 10 + [0] * 990
25always_negative = [0] * 1000
26accuracy, precision, recall, f1 = metrics(rare, always_negative)
27print(accuracy, recall)

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
Count the cases that are positive and also predicted positive: the true positives.—
—
Of four real positives the model found 2 and missed 2. It also raised 1 false alarm among six negatives.—
tp=2 fp=1 fn=2 tn=5
Accuracy 0.7, precision 2/3, recall 1/2, and F1 0.571 (the harmonic mean).—
tp=2 fp=1 fn=2 tn=5
0.7 0.667 0.5 0.571
A model that always says negative is 99% accurate on this rare-event data but finds none of the positives: recall 0.—
tp=2 fp=1 fn=2 tn=5
0.7 0.667 0.5 0.571
0.99 0.0

Behind the scenes

How Python processes this code

Python runs this source through its interpreter. It creates objects for values and resolves names as each statement runs.

Cost and common mistakes

Computing the confusion matrix is O(n) for n predictions.

  • Accuracy hides failure on rare classes. Report precision and recall when classes are imbalanced.
  • Evaluating on the training data overstates performance. Keep a separate test set for the final estimate.
  • Precision and recall trade off against each other. Changing the decision threshold moves you along that trade-off.

Try it yourself

A screening test misses 20 of 100 sick patients and falsely flags 30 of 900 healthy ones. What is the recall?

Reveal answer and reasoning

Recall = tp / (tp + fn) = 80 / 100 = 0.8.

Further reading