Technology

The Confusion Matrix: Precision, Recall, and F1 Made Simple

Abhay Abhay 4 min read
The Confusion Matrix: Precision, Recall, and F1 Made Simple
Photo by JJ Ying on Unsplash

Imagine you build a model to detect a rare disease that shows up in 1% of patients. You hit the green button, and your accuracy reads 99%. Pop the champagne, right?

Not quite. A model that simply predicts “healthy” for every single person would also score 99% — and it would never catch a single sick patient. That uncomfortable little fact is called the accuracy paradox, and it’s exactly why seasoned ML folks don’t trust accuracy alone. To really know whether your classifier is brilliant or just lucky, you need the confusion matrix and the trio of metrics that fall out of it: precision, recall, and F1.

The four boxes that explain everything

A confusion matrix is just a 2x2 grid comparing what your model predicted against what was actually true. Every prediction lands in one of four boxes:

  • True Positive (TP): said positive, was positive. (Flagged sick, was sick.)
  • True Negative (TN): said negative, was negative. (Said healthy, was healthy.)
  • False Positive (FP): said positive, was negative — a false alarm. (Flagged sick, was fine.)
  • False Negative (FN): said negative, was positive — a miss. (Said healthy, was actually sick.)

That’s the whole vocabulary. The trick to remembering it: the second word is what your model said, the first word is whether it was right. Statisticians call FP and FN “Type I” and “Type II” errors, but “false alarm” and “miss” are far harder to forget.

Precision vs. recall: two different anxieties

From those four boxes, two metrics do the heavy lifting.

Precision asks: of everything I flagged as positive, how much was actually positive?

Precision = TP / (TP + FP)

It’s the metric you care about when false alarms are expensive. A spam filter is the classic case: you’d rather let an occasional spam slip into the inbox than send your boss’s job offer to the junk folder. Misfiling real email erodes trust fast, so spam filters chase high precision.

Recall asks: of everything that was actually positive, how much did I catch?

Recall = TP / (TP + FN)

This is what matters when misses are expensive. Cancer screening is the textbook example: a false alarm means an extra (stressful, but survivable) test, while a miss means an undiagnosed tumor. Here you happily tolerate false positives to drive false negatives toward zero.

Notice they’re answering different fears. Precision frets about crying wolf; recall frets about the wolf you didn’t see.

The tradeoff you can’t escape

Here’s the catch: precision and recall pull against each other. Most classifiers don’t output a hard yes/no — they output a probability, and you choose a threshold (often 0.5) to convert it into a decision.

Crank the threshold down to 0.2 and the model flags almost everything: recall soars (you miss nothing), but precision tanks (tons of false alarms). Crank it up to 0.8 and the model only commits when it’s very sure: precision climbs, recall drops (you miss the borderline cases). You can’t max both — you’re sliding a dial, trading one anxiety for the other.

So which dial setting is “correct”? That’s not a math question; it’s a domain question. Spam filter? Slide toward precision. Tumor detector? Slide toward recall.

F1: the peacemaker

When you need a single number to rank models — and you don’t want one metric quietly bragging while the other is in flames — reach for F1, the harmonic mean of precision and recall:

F1 = 2 * (Precision * Recall) / (Precision + Recall)

Why harmonic and not the plain average? Because the harmonic mean punishes imbalance. A model with 100% precision and 1% recall has an arithmetic mean of ~50% (looks fine!) but an F1 of about 2% (the truth). F1 only gets high when both numbers are high, which is precisely why it shines on imbalanced data where accuracy lies to your face.

Doing it in Python

You don’t compute any of this by hand. scikit-learn hands you the whole report in two lines:

from sklearn.metrics import classification_report, confusion_matrix

# y_test = true labels, y_pred = model predictions
print(confusion_matrix(y_test, y_pred))
# [[945  10]    -> TN=945, FP=10
#  [ 12  33]]   -> FN=12,  TP=33

print(classification_report(y_test, y_pred))
#               precision    recall  f1-score   support
#            0       0.99      0.99      0.99       955
#            1       0.77      0.73      0.75        45
#     accuracy                           0.98      1000
#    macro avg       0.88      0.86      0.87      1000
# weighted avg       0.98      0.98      0.98      1000

Look at that: 98% accuracy, but class 1 — the rare, interesting one — only scrapes an F1 of 0.75. The support column tells you why: 955 vs. 45 samples. Accuracy was busy congratulating the model on the easy majority class while the minority class limped along. The classification report is the flashlight that exposes it.

The takeaway

Next time a model hands you a glowing accuracy score, don’t believe it until you’ve checked the confusion matrix. Then run this three-step gut check:

  1. Print classification_report and read the per-class precision, recall, and F1 — never just the headline accuracy.
  2. Decide which error hurts more. False alarms costly? Optimize precision. Misses costly? Optimize recall. Need balance? Watch F1.
  3. Tune the threshold to your domain, not to the default 0.5 — the right dial setting is the one that matches the real-world cost of being wrong.

Accuracy tells you how often you’re right. Precision, recall, and F1 tell you how you’re wrong — and that’s the part that actually keeps your model honest.

More posts