🎯 Section 3 · Model Evaluation Mastery 🟡 Intermediate MODULE 13

Classification Metrics — Deep Dive

⏱️ 26 min read
📖 Beyond Plain Accuracy
🧩 4 Quiz Questions
🏗️ 1 Challenge · 3 tasks
Your progress in Section 325%
🎯 What you'll learn: Section 2 leaned on .score() (plain accuracy) to check classifiers. This lesson goes much deeper: the confusion matrix and its four building blocks (TP/FP/TN/FN), precision, recall, and F1-score with their exact formulas, sklearn.metrics.confusion_matrix and classification_report, the ROC curve and AUC, and — maybe most important of all — how to decide whether precision or recall should be your priority for a given real problem.

Why Accuracy Alone Can Mislead You

Accuracy — (correct predictions) / (total predictions) — is easy to compute and easy to understand, which is why it's been this course's default metric so far. But accuracy has a blind spot: it treats every mistake the same, and it says nothing about WHICH kind of mistake a model tends to make.

Consider a classifier that predicts whether an email is spam. Two very different models could both score "90% accuracy" — one that correctly catches most spam while rarely bothering real email, and another that is great at recognizing real email but quietly lets more spam through. Accuracy alone can't tell those two models apart. The metrics in this lesson can.

⚠️
Accuracy gets especially unreliable with imbalanced classes
When one class vastly outnumbers another (say, 95% of transactions are legitimate and 5% are fraud, an illustrative split used for teaching), a model that just predicts "legitimate" every single time still scores 95% accuracy — while catching zero fraud. Lesson 14 covers this exact problem in depth. For now, just know that accuracy alone is never the full picture for a classifier.

The Confusion Matrix — TP, FP, TN, FN

Every other metric in this lesson is built from four counts. For a binary classifier (predicting a "positive" class, like "spam" or "has disease", against a "negative" class):

True Positive (TP)
Actually positive, and the model correctly predicted positive.
True Negative (TN)
Actually negative, and the model correctly predicted negative.
False Positive (FP)
Actually negative, but the model incorrectly predicted positive. Also called a "Type I error."
False Negative (FN)
Actually positive, but the model incorrectly predicted negative. Also called a "Type II error."
Predicted Negative
Predicted Positive
Actual Negative
TRUE NEGATIVE
TN
FALSE POSITIVE
FP
Actual Positive
FALSE NEGATIVE
FN
TRUE POSITIVE
TP

scikit-learn computes this directly from true and predicted labels with confusion_matrix:

confusion_matrix.py
PYTHON
from sklearn.metrics import confusion_matrix

# y_test and y_pred are 0/1 arrays: 1 = positive class (e.g. "spam")
cm = confusion_matrix(y_test, y_pred)
print(cm)

# [[85  5]
#  [10 50]]
# Illustrative output for a toy dataset. Rows = actual class (0, then 1),
# columns = predicted class (0, then 1), sorted ascending by default.
# -> TN=85, FP=5, FN=10, TP=50
📝
Reading confusion_matrix's row/column order
By default, confusion_matrix sorts labels ascending, so for binary 0/1 labels: row 0 = actual negatives, row 1 = actual positives; column 0 = predicted negatives, column 1 = predicted positives. cm[0][0]=TN, cm[0][1]=FP, cm[1][0]=FN, cm[1][1]=TP. Pass labels=[...] explicitly if you ever want a different, guaranteed order.

Precision, Recall, and F1-Score

These three metrics each combine TP/FP/TN/FN in a different way, answering three different plain-language questions.

Precision — "Of everything I flagged as positive, how much was right?"

Precision
Precision = TP / (TP + FP)
High precision means few false alarms — when the model says "positive," it's usually right.

Recall — "Of everything that actually was positive, how much did I catch?"

Recall (also called Sensitivity or True Positive Rate)
Recall = TP / (TP + FN)
High recall means few misses — the model rarely lets a real positive slip through undetected.

F1-Score — a single number balancing both

F1-Score (harmonic mean of Precision and Recall)
F1 = 2 × (Precision × Recall) / (Precision + Recall)
F1 is high only when BOTH precision and recall are reasonably high — a model that's great at one and terrible at the other still scores low.

Using the illustrative confusion matrix from Section 2 (TN=85, FP=5, FN=10, TP=50):

compute_metrics.py
PYTHON
from sklearn.metrics import precision_score, recall_score, f1_score, accuracy_score

print("Accuracy:", accuracy_score(y_test, y_pred))
print("Precision:", precision_score(y_test, y_pred))
print("Recall:", recall_score(y_test, y_pred))
print("F1-score:", f1_score(y_test, y_pred))

# Accuracy:  0.900   -> (85+50) / (85+5+10+50)
# Precision: 0.909   -> 50 / (50+5)
# Recall:    0.833   -> 50 / (50+10)
# F1-score:  0.870   -> 2 * (0.909*0.833) / (0.909+0.833)
Precision and recall are usually in tension
Push a model to flag MORE things as positive (to raise recall) and it typically also raises false positives, LOWERING precision — and vice versa. There's rarely a free lunch where both go up together; Section 6 below is entirely about how to choose which one matters more for a given problem.

classification_report — Every Metric at Once

Instead of calling four separate functions, classification_report prints precision, recall, F1, and support (the number of true instances) for every class in one readable block — plus accuracy and two kinds of averages.

classification_report.py
PYTHON
from sklearn.metrics import classification_report

print(classification_report(y_test, y_pred, target_names=["Not Spam", "Spam"]))
classification_report() output — illustrative toy-dataset numbers
precisionrecallf1-scoresupport
Not Spam0.890.940.9290
Spam0.910.830.8760
accuracy0.90150
macro avg0.900.890.89150
weighted avg0.900.900.90150
📊
support
How many actual instances of that class appeared in y_test — useful context for judging how much a class's score should be trusted.
⚖️
macro avg
The plain average of each class's score, treating every class equally regardless of its support (size).
📐
weighted avg
The average of each class's score, weighted by its support — larger classes count for more.
🔀
Per-class precision/recall
Each class gets its OWN precision and recall, computed by treating that class as "positive" and all others as "negative."

ROC Curve and AUC

Precision, recall, and F1 all depend on one fixed decision threshold — usually "predict positive if predicted probability > 0.5". The ROC curve (Receiver Operating Characteristic) instead shows how a classifier performs across EVERY possible threshold at once.

📈
True Positive Rate (TPR)
Exactly the same as Recall: TP / (TP + FN). Plotted on the y-axis.
📉
False Positive Rate (FPR)
FP / (FP + TN) — the fraction of actual negatives incorrectly flagged as positive. Plotted on the x-axis.

The ROC curve traces (FPR, TPR) pairs as the decision threshold sweeps from 0 to 1. roc_curve computes the raw points; roc_auc_score computes the Area Under that Curve as one summary number.

roc_auc.py
PYTHON
from sklearn.metrics import roc_curve, roc_auc_score

# predict_proba returns [P(class 0), P(class 1)] per row — take column 1
y_probs = model.predict_proba(X_test)[:, 1]

fpr, tpr, thresholds = roc_curve(y_test, y_probs)
auc = roc_auc_score(y_test, y_probs)

print(f"AUC: {auc:.3f}")
# AUC: 0.923   (illustrative — one toy-dataset run, not a benchmark claim)
Rough, commonly-used AUC interpretation heuristic
AUC rangeGeneral interpretation
0.50No better than random guessing
0.50 – 0.70Poor
0.70 – 0.80Fair
0.80 – 0.90Good
0.90 – 1.00Excellent
📝
Why AUC is threshold-independent
Because the ROC curve is built by sweeping ALL possible thresholds, roc_auc_score summarizes a model's ability to RANK positives above negatives overall, without committing to any single cutoff. That makes it useful for comparing two models' general discriminative power, even before deciding where to set the operating threshold in Section 6.
⚠️
roc_auc_score needs probabilities, not hard predictions
Pass predict_proba(...)[:, 1] (or decision_function(...) for models without predict_proba) — NOT model.predict(X_test). Hard 0/1 predictions collapse every threshold into one, which defeats the entire point of the ROC curve.

Precision vs. Recall — Which Should You Prioritize?

There's no universally "correct" metric to optimize — the right choice depends entirely on which TYPE of mistake, false positives or false negatives, is more costly for the specific problem. Two classic contrasting examples:

📧
Spam Filter
A false positive (real email marked spam) can mean a missed job offer or important message. Prioritize precision — be conservative about what gets flagged.
🩺
Disease Screening
A false negative (sick patient told they're healthy) can mean a missed diagnosis with serious consequences. Prioritize recall — cast a wide net, even at the cost of more false alarms to follow up on.
⚖️
Balanced Cases
When both error types carry similar real-world cost, F1-score (or accuracy, if classes are reasonably balanced — Lesson 14) is a more appropriate single number to optimize.
🎚️
You can move the tradeoff — after training
Because most classifiers output a probability before applying the default 0.5 threshold, you can shift the precision/recall balance WITHOUT retraining, just by changing the cutoff: a lower threshold (e.g. flag positive above 0.3 probability) raises recall at the cost of precision; a higher threshold does the opposite. The ROC curve from Section 5 is exactly the tool for visualizing that tradeoff across every possible cutoff before picking one.

Reasoning through this tradeoff — rather than defaulting to accuracy — is the single biggest mindset shift this lesson aims to build. The right metric always follows from the real-world cost of each type of mistake, never from habit.

Lesson Summary

The confusion matrix breaks every prediction into TP, FP, TN, or FN — the foundation for every metric here.
Precision = TP/(TP+FP) — how trustworthy a "positive" prediction is. Recall = TP/(TP+FN) — how many real positives get caught.
F1-score is the harmonic mean of precision and recall — high only when both are reasonably strong.
classification_report prints all three, per class, plus macro/weighted averages, in one call.
The ROC curve and AUC summarize performance across every threshold, not just one fixed cutoff.
Choose precision when false positives are costlier; choose recall when false negatives are costlier.
🧩 Knowledge Check — Lesson 13
4 questions on the confusion matrix, precision/recall/F1, and ROC/AUC.
1. A model predicts "not fraud" for a transaction that was actually fraudulent. What is this called?
2. Which formula correctly defines Precision?
3. For a disease-screening test, why would you generally prioritize recall over precision?
4. What input does roc_auc_score need to compute AUC correctly?
💪
Try It Yourself — Lesson 13
Practice reading real classification metrics · Intermediate Level

Use any binary classifier from Section 2 (e.g. LogisticRegression, SVC(probability=True), or GradientBoostingClassifier) on a dataset of your choice for these tasks.

Task 1: Full metrics report 📋

Train a classifier, generate predictions on a test set, and print confusion_matrix, classification_report, and the individual precision_score/recall_score/f1_score. Confirm the individual scores match what classification_report shows for the positive class.
Task 2: Plot an ROC curve 📈

Using roc_curve's returned fpr and tpr arrays, plot them with matplotlib (plt.plot(fpr, tpr)), add a diagonal reference line from (0,0) to (1,1) representing random guessing, and print the AUC in the plot title.
Task 3: Shift the decision threshold 🎚️

Instead of the default 0.5 cutoff, manually threshold predict_proba(X_test)[:, 1] at 0.3 and again at 0.7 (e.g. (probs > 0.3).astype(int)). Recompute precision and recall at each threshold. Confirm that the lower threshold raises recall (and lowers precision) compared to the higher one.
💡 Show hints if you're stuck
  • Task 1: Remember classification_report's numbers for the positive class should exactly match precision_score/recall_score/f1_score called directly — they're computed the same way.
  • Task 2: plt.plot([0,1],[0,1],'r--') draws the "random guessing" diagonal — a real model's ROC curve should bow up and to the left of it.
  • Task 3: A LOWER threshold means more predictions get labeled positive overall, which typically increases recall (catches more true positives) but also increases false positives, lowering precision — exactly the tradeoff from Section 6.
Finished this lesson?
Mark it complete to track your progress.
🎉

Lesson 13 Complete!

You can now read a confusion matrix, compute precision/recall/F1 by hand and with scikit-learn, generate a full classification_report, understand ROC/AUC, and reason about which metric a real problem calls for. Next up: what happens to all of this when your classes are wildly imbalanced.

Module 13 of 24 Section 3 — Model Evaluation Mastery