A confusion matrix shows exactly how a classification model is right and wrong. Instead of reducing performance to one score, it counts correct predictions and each type of mistake. In this guide, you will build, plot, normalize, and interpret confusion matrices in Python with scikit-learn.
What Is a Confusion Matrix?
A confusion matrix compares the true class of every observation with the class predicted by a model. Rows represent actual classes and columns represent predicted classes in scikit-learn. Correct predictions appear on the main diagonal, while errors appear outside that diagonal.
| Predicted Negative | Predicted Positive | |
|---|---|---|
| Actual Negative | True Negative (TN) | False Positive (FP) |
| Actual Positive | False Negative (FN) | True Positive (TP) |
- True negative: the observation is negative and the model predicts negative.
- False positive: the observation is negative but the model predicts positive.
- False negative: the observation is positive but the model predicts negative.
- True positive: the observation is positive and the model predicts positive.
Complete Python Example
The following reproducible example trains logistic regression on scikit-learn's breast cancer dataset. Scaling and the classifier are placed in a scikit-learn Pipeline so preprocessing is learned only from the training data.
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix
X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.20,
stratify=y,
random_state=42
)
model = Pipeline([
("scale", StandardScaler()),
("model", LogisticRegression(max_iter=2000))
])
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
cm = confusion_matrix(y_test, y_pred)
print(cm)
With the specified random seed, this example produces:
[[41 1]
[ 1 71]]
The model correctly classified 41 negative examples and 71 positive examples. It produced one false positive and one false negative. Keep the class order in mind: by default, scikit-learn sorts labels and uses that order for both rows and columns.
Extract TN, FP, FN, and TP
For a binary 2 × 2 matrix, use ravel() to unpack the four counts:
tn, fp, fn, tp = cm.ravel()
print("True negatives:", tn)
print("False positives:", fp)
print("False negatives:", fn)
print("True positives:", tp)
This shortcut is for binary classification. A multiclass matrix contains more than four cells, so it cannot be unpacked into only TN, FP, FN, and TP.
Plot a Confusion Matrix
ConfusionMatrixDisplay.from_predictions() creates a labeled Matplotlib visualization directly from the true and predicted targets:
import matplotlib.pyplot as plt
from sklearn.metrics import ConfusionMatrixDisplay
ConfusionMatrixDisplay.from_predictions(
y_test,
y_pred,
display_labels=["malignant", "benign"],
cmap="Blues",
values_format="d"
)
plt.title("Confusion Matrix")
plt.tight_layout()
plt.show()
A strong model has large values on the diagonal and small values outside it. However, the cost of each off-diagonal error may be very different. One false negative can matter more than many false positives in a high-risk screening problem.
Normalize the Matrix
Raw counts are useful, but they can be misleading when classes contain very different numbers of observations. Row normalization shows the proportion of each actual class assigned to every predicted class.
ConfusionMatrixDisplay.from_predictions(
y_test,
y_pred,
display_labels=["malignant", "benign"],
normalize="true",
cmap="Blues",
values_format=".2f"
)
plt.title("Normalized Confusion Matrix")
plt.tight_layout()
plt.show()
| normalize value | Meaning | Useful for |
|---|---|---|
None | Raw counts | Understanding the number of cases |
"true" | Normalize each actual-class row | Comparing class-specific recall |
"pred" | Normalize each predicted-class column | Understanding prediction composition |
"all" | Divide by the entire sample count | Viewing each cell's share of all cases |
Calculate Accuracy, Precision, Recall, and F1
The four matrix counts generate several familiar classification metrics:
Accuracy = (TP + TN) / (TP + TN + FP + FN)
Precision = TP / (TP + FP)
Recall = TP / (TP + FN)
F1 score = 2 × Precision × Recall / (Precision + Recall)
from sklearn.metrics import (
accuracy_score,
precision_score,
recall_score,
f1_score,
classification_report
)
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:", f1_score(y_test, y_pred))
print(classification_report(y_test, y_pred))
For a deeper comparison of these scores, thresholds, and imbalanced-data behavior, read Accuracy vs Precision vs Recall vs F1 vs ROC AUC.
Confusion Matrix for Multiclass Classification
In multiclass classification, the matrix has one row and one column for every class. The diagonal still contains correct predictions. Each off-diagonal cell shows one specific confusion, such as actual class A predicted as class B.
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
labels = ["cat", "dog", "rabbit"]
y_true = ["cat", "cat", "dog", "dog", "rabbit", "rabbit"]
y_pred = ["cat", "dog", "dog", "dog", "rabbit", "cat"]
cm = confusion_matrix(y_true, y_pred, labels=labels)
print(cm)
ConfusionMatrixDisplay.from_predictions(
y_true,
y_pred,
labels=labels,
display_labels=labels,
cmap="Purples"
)
Pass an explicit labels order when a stable row and column order matters. This is particularly helpful when comparing matrices across several models or data splits.
How the Decision Threshold Changes the Matrix
Many binary classifiers first produce a probability or decision score and then convert it into a class. A threshold of 0.50 is common, but it is not automatically the best business threshold.
y_score = model.predict_proba(X_test)[:, 1]
threshold = 0.35
y_pred_custom = (y_score >= threshold).astype(int)
print(confusion_matrix(y_test, y_pred_custom))
Lowering the threshold usually predicts more positives. This can reduce false negatives and increase recall, but may also create more false positives and lower precision. Choose the threshold using validation data and a clearly defined cost for each error type.
Common Confusion Matrix Mistakes
- Reversing axes: confirm whether rows are actual or predicted. In scikit-learn, rows are true classes and columns are predicted classes.
- Assuming class 1 is always the important class: inspect the target definition and the label order.
- Reporting only accuracy: two models with equal accuracy can make very different and differently costly errors.
- Ignoring class imbalance: include a normalized matrix and class-specific precision and recall.
- Evaluating training data: use held-out data or out-of-fold predictions.
- Tuning on the test set: this leaks information and makes reported performance too optimistic.
- Comparing raw counts across different sample sizes: compare normalized matrices or rates.
Which Errors Should You Minimize?
| Situation | Often more costly | Metric to watch |
|---|---|---|
| Disease screening | False negative | Recall / sensitivity |
| Spam filter for important mail | False positive | Precision or specificity |
| Fraud detection | Depends on fraud loss and investigation cost | Precision-recall trade-off |
| Balanced classes with similar error costs | Both | Accuracy and macro metrics |
The correct target is determined by the real-world cost of a mistake, not by a universal rule. Translate each matrix cell into an operational consequence before selecting a model.
Frequently Asked Questions
Is a confusion matrix only for binary classification?
No. It supports binary and multiclass classification. Multilabel problems can use a separate 2 × 2 matrix for each label with multilabel_confusion_matrix.
Why does my confusion matrix look good while the model is poor?
Raw counts can hide poor minority-class performance. Normalize the matrix, inspect per-class recall and precision, and compare against a simple baseline.
Should I use training or test predictions?
Use unseen validation or test predictions for evaluation. A training-set matrix mainly tells you how well the model fits data it has already seen.
Can I compare confusion matrices from different models?
Yes, provided they use the same evaluation observations, class order, and decision rules. Normalized matrices make comparisons easier when sample counts differ.
No comments:
Post a Comment