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.
flowchart LR CM["Confusion matrix
(TP, FP, FN, TN)"] --> P["Precision = TP / (TP + FP)"] CM --> R["Recall = TP / (TP + FN)"] P --> F["F1 = harmonic mean of P and R"] R --> F
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.
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
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.6667from 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.6667Notice 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”:
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.0from 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.0Mini-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 coffeeWas this page helpful?
Let us know how we did
