Skip to content

Precision, Recall, and F1-Score

What you’ll learn

  • what precision and recall each answer, in plain language
  • why you can’t maximize both at once — the precision/recall trade-off
  • how moving the decision threshold trades one for the other
  • when to reach for F1, and when a single balanced number is the wrong goal

Precision

Precision answers:

“Of all the times I predicted positive, how many were actually correct?”

precision = TP / (TP + FP)precision = TP / (TP + FP)

High precision means few false alarms — when the model says “positive,” it’s usually right.

Recall

Recall (also called sensitivity or the true positive rate) answers:

“Of all the actual positives out there, how many did I catch?”

recall = TP / (TP + FN)recall = TP / (TP + FN)

High recall means you miss fewer real positive cases.

diagram Where precision and recall come from mermaid
Both metrics are read off the same confusion matrix, just dividing by a different denominator.

The trade-off: why you can’t have both

Géron’s book illustrates this by imagining instances ranked by their classifier score, from lowest to highest, with a threshold arrow somewhere in the middle. Everything to the right of the threshold is predicted positive; everything to the left is predicted negative.

  • Raise the threshold → fewer things get called “positive,” so the ones that do are more likely to be correct (precision goes up) — but some real positives that scored lower than the new threshold get missed (recall goes down).
  • Lower the threshold → the opposite: more real positives get caught (recall up), but more false alarms slip through too (precision down).

This is why precision and recall move in opposite directions as you slide the threshold — it’s called the precision/recall trade-off, and it’s unavoidable. You can’t raise both at once by moving the threshold alone; you can only choose which one matters more for your problem.

sketch Sliding the threshold trades precision for recall p5.js
The threshold automatically sweeps back and forth across the ranked scores; watch precision and recall move in opposite directions as it crosses each point. Drag to override.

F1-score

Sometimes you want one number that balances both. The F1 score is the harmonic mean of precision and recall — not the plain average. The harmonic mean punishes low values much more than a plain average would, so F1 is only high when both precision and recall are reasonably high:

F1 = 2 * (precision * recall) / (precision + recall)F1 = 2 * (precision * recall) / (precision + recall)

When to prefer which

  • fraud detection: usually prioritize recall — missing real fraud is costlier than a few extra false alarms
  • video recommendations for kids: prioritize precision — better to reject some good videos (lower recall) than let one bad one slip through
  • shoplifting surveillance: Géron’s example — 30% precision is fine if recall is 99%; a few false alerts beat letting real shoplifters go
  • spam filtering: usually balance with F1, or lean toward precision (don’t block real emails)

Scikit-learn example

Precision, recall, and F1 on a small prediction set
from sklearn.metrics import precision_score, recall_score, f1_score, confusion_matrix
import numpy as np
 
y_true = np.array([1, 1, 1, 1, 1, 0, 0, 0, 0, 0])
y_pred = np.array([1, 1, 1, 0, 0, 0, 0, 1, 0, 0])
 
print(confusion_matrix(y_true, y_pred))
# [[4 1]
#  [2 3]]
 
print("precision:", precision_score(y_true, y_pred))
# precision: 0.75
 
print("recall:", recall_score(y_true, y_pred))
# recall: 0.6
 
print("f1:", round(f1_score(y_true, y_pred), 4))
# f1: 0.6667
Precision, recall, and F1 on a small prediction set
from sklearn.metrics import precision_score, recall_score, f1_score, confusion_matrix
import numpy as np
 
y_true = np.array([1, 1, 1, 1, 1, 0, 0, 0, 0, 0])
y_pred = np.array([1, 1, 1, 0, 0, 0, 0, 1, 0, 0])
 
print(confusion_matrix(y_true, y_pred))
# [[4 1]
#  [2 3]]
 
print("precision:", precision_score(y_true, y_pred))
# precision: 0.75
 
print("recall:", recall_score(y_true, y_pred))
# recall: 0.6
 
print("f1:", round(f1_score(y_true, y_pred), 4))
# f1: 0.6667

Notice this matches Géron’s book almost exactly in spirit: his 5-detector on MNIST scored 72.9% precision and 75.6% recall — a classifier whose accuracy looked great (over 93%!) turned out to be correct only 3 out of 4 times it claimed “5”, and it only caught about 3 out of 4 actual 5s.

Choosing a threshold for a target precision

Scikit-learn’s precision_recall_curve()precision_recall_curve() computes precision and recall at every possible threshold, so you can pick the lowest threshold that reaches a target precision — exactly the technique Géron uses to build a “90% precision classifier”:

Find the threshold for 90% precision
from sklearn.metrics import precision_recall_curve
import numpy as np
 
y_true = np.array([0, 0, 0, 1, 1, 1, 1, 1, 1, 1])
y_scores = np.array([-3, -1, 0.5, 0.2, 1, 2, 3, 4, 5, 6])
 
precisions, recalls, thresholds = precision_recall_curve(y_true, y_scores)
 
threshold_90 = thresholds[np.argmax(precisions >= 0.90)]
print("Threshold for >=90% precision:", threshold_90)
# Threshold for >=90% precision: 1.0
Find the threshold for 90% precision
from sklearn.metrics import precision_recall_curve
import numpy as np
 
y_true = np.array([0, 0, 0, 1, 1, 1, 1, 1, 1, 1])
y_scores = np.array([-3, -1, 0.5, 0.2, 1, 2, 3, 4, 5, 6])
 
precisions, recalls, thresholds = precision_recall_curve(y_true, y_scores)
 
threshold_90 = thresholds[np.argmax(precisions >= 0.90)]
print("Threshold for >=90% precision:", threshold_90)
# Threshold for >=90% precision: 1.0

Mini-checkpoint

If you raise the threshold from 0.5 to 0.9:

  • precision usually goes (up / down)?
  • recall usually goes (up / down)?

(Precision up, recall down — and precision can occasionally dip locally even as the threshold rises, while recall only ever goes down or stays flat.)

🧪 Try It Yourself

Exercise 1 – Compute Precision and Recall from Counts

Exercise 2 – Score a Real Prediction Set

Exercise 3 – Find a Threshold for 90% Precision

Next

Continue to The ROC Curve and AUC to see the threshold trade-off plotted a different way — and how to compare classifiers independent of any single threshold.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did