How to read a confusion matrix in Machine Learning
After training a classification model, the most important question is simple: where does it get things right, and where does it fail? The confusion matrix answers that in a single table and is the foundation for metrics like accuracy, precision and recall. Learning to read a confusion matrix in Machine Learning saves you from the common trap of trusting accuracy alone, which is often misleading.
Prerequisites
- Python 3 installed on your computer.
- The scikit-learn and matplotlib libraries (install them with
pip install scikit-learn matplotlib). - Basic notions of classification: a model that predicts one of two classes (0 or 1).
- You do not need your own data, because we will generate a sample dataset.
Step 1: Train a simple classification model
First we need a model and some predictions to analyse. To keep the example reproducible, we generate a binary dataset with make_classification and train a logistic regression. The random_state makes sure you get exactly the same numbers as in this tutorial.
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
# Binary dataset with 30% of class 1 (imbalanced)
X, y = make_classification(n_samples=1000, n_features=10, n_informative=5,
weights=[0.7, 0.3], random_state=42)
# Split into train (80%) and test (20%)
X_treino, X_teste, y_treino, y_teste = train_test_split(
X, y, test_size=0.2, random_state=42)
modelo = LogisticRegression(max_iter=10000)
modelo.fit(X_treino, y_treino)Step 2: Generate the confusion matrix
With the model trained, we predict on the test data and compare the predictions against the real values. The confusion_matrix function returns a table that crosses what actually happened with what the model predicted.
from sklearn.metrics import confusion_matrix
y_previsto = modelo.predict(X_teste)
matriz = confusion_matrix(y_teste, y_previsto)
print(matriz)In this example, the result is:
[[127 6]
[ 35 32]]Step 3: Read the four quadrants of the confusion matrix
In binary classification, the rows represent the actual class and the columns the class predicted by the model. Each cell has a name and a concrete meaning:
- True Negatives (TN) = 127: they were class 0 and the model got it right (top-left corner).
- False Positives (FP) = 6: they were class 0, but the model said 1 (false alarm).
- False Negatives (FN) = 35: they were class 1, but the model said 0 (real cases that slipped through).
- True Positives (TP) = 32: they were class 1 and the model got it right (bottom-right corner).
You can extract the four values straight into variables with ravel(), which is handy for computing the metrics next:
vn, fp, fn, vp = matriz.ravel()
print(vn, fp, fn, vp) # 127 6 35 32Step 4: Compute accuracy, precision and recall
The four numbers give you the essential metrics. Notice how each one tells a different story about the same model:
- Accuracy = (TP + TN) / total = (32 + 127) / 200 = 0.80. The overall share of correct predictions.
- Precision = TP / (TP + FP) = 32 / 38 = 0.84. Of the times the model predicted 1, how many were right.
- Recall = TP / (TP + FN) = 32 / 67 = 0.48. Of the real class 1 cases, how many the model caught.
You do not need to do this by hand: scikit-learn computes everything at once with classification_report.
from sklearn.metrics import classification_report
print(classification_report(y_teste, y_previsto))Now notice the most important point in this example: accuracy is 80%, which looks good, but recall for class 1 is only 48%. In other words, the model misses more than half of the real positive cases. This is exactly the kind of error that the confusion matrix reveals and that accuracy alone hides.
Step 5: Visualise the confusion matrix
A chart makes reading immediate, especially to share with colleagues or drop into a report. ConfusionMatrixDisplay draws the same table with colours directly from the predictions.
import matplotlib.pyplot as plt
from sklearn.metrics import ConfusionMatrixDisplay
ConfusionMatrixDisplay.from_predictions(y_teste, y_previsto)
plt.show()Verify the result
To confirm everything is correct, add up the four cells: 127 + 6 + 35 + 32 = 200, which is exactly the number of test samples. If the sum matches the size of your test set, the matrix is well built. Also compare the precision and recall values you worked out by hand with those shown in the classification_report: they should match to two decimal places.
Conclusion
With these steps you moved from a single percentage to a full reading of the model's behaviour, telling apart the errors that cost the most (here, the false negatives). The natural next step is to try adjusting the decision threshold or to look at the F1-score, which balances precision and recall, and then repeat the analysis for problems with more than two classes. Which metric matters most in your problem: avoiding false positives, or not letting false negatives slip through?