DEV Community

Cover image for What is a Confusion Matrix? Explained for Developers
Placide
Placide

Posted on

What is a Confusion Matrix? Explained for Developers

When I trained my first classification model, I made a rookie mistake — I looked at the accuracy score, saw 95%, and thought I was done.

I wasn't.

My model was predicting the majority class almost every time and completely ignoring the minority class. The accuracy looked great. The model was useless.

That's when I discovered the confusion matrix — the tool that exposes what accuracy hides. And once I understood it, I could never go back to just checking accuracy alone.

This article explains confusion matrices the way I wish someone had explained them to me — through a developer's lens, with real code, and zero unnecessary math.

The Problem With Accuracy Alone

Let's say you're building a model to detect fraudulent bank transactions. Your dataset has:

  • 9,700 legitimate transactions

  • 300 fraudulent transactions

A model that predicts "legitimate" for every single transaction achieves 97% accuracy.

That sounds incredible. But it catches zero fraud. It's completely worthless for the actual problem.

Accuracy lies when your classes are imbalanced. The confusion matrix tells the truth.

What is a Confusion Matrix?

A confusion matrix is a table that breaks down your model's predictions into four categories — showing not just how many it got right, but what kind of right and wrong.

For a binary classification problem (two classes: Positive and Negative):

                    PREDICTED
                 Positive  Negative
ACTUAL Positive |   TP   |   FN   |
       Negative |   FP   |   TN   |
Enter fullscreen mode Exit fullscreen mode

Let's define each cell:

TP — True Positive
The model predicted Positive and it WAS Positive.
→ Predicted fraud, it WAS fraud.

TN — True Negative
The model predicted Negative and it WAS Negative.
→ Predicted legitimate, it WAS legitimate.

FP — False Positive (Type I Error)
The model predicted Positive but it was actually Negative.
→ Predicted fraud, but it was legitimate.
→ Also called a "false alarm"

FN — False Negative (Type II Error)
The model predicted Negative but it was actually Positive.
→ Predicted legitimate, but it was actually fraud.
→ Also called a "missed detection"

A Real Example — Fraud Detection

Let's make this concrete with Python and scikit-learn:


import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix, classification_report
from sklearn.model_selection import train_test_split
import seaborn as sns
import matplotlib.pyplot as plt

# Simulate imbalanced fraud detection dataset
np.random.seed(42)
n_legitimate = 9700
n_fraud = 300

# Legitimate transactions — low amounts, normal patterns
legitimate = np.column_stack([
    np.random.normal(50, 20, n_legitimate),   # amount
    np.random.normal(0.1, 0.05, n_legitimate) # risk_score
])

# Fraudulent transactions — high amounts, unusual patterns
fraud = np.column_stack([
    np.random.normal(800, 200, n_fraud),  # amount
    np.random.normal(0.9, 0.05, n_fraud)  # risk_score
])

X = np.vstack([legitimate, fraud])
y = np.array([0] * n_legitimate + [1] * n_fraud)  # 0=legitimate, 1=fraud

# Split data
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

# Train model
model = LogisticRegression()
model.fit(X_train, y_train)

# Predict
y_pred = model.predict(X_test)

# Confusion Matrix
cm = confusion_matrix(y_test, y_pred)
print("Confusion Matrix:")
print(cm)
print(f"\nAccuracy: {(y_pred == y_test).mean():.4f}")
print("\nDetailed Report:")
print(classification_report(y_test, y_pred, target_names=['Legitimate', 'Fraud']))
Enter fullscreen mode Exit fullscreen mode

Output:

Confusion Matrix:
[[1938    2]
 [   1   59]]

Accuracy: 0.9985

Detailed Report:
              precision    recall  f1-score
  Legitimate     1.00       1.00      1.00
       Fraud     0.97       0.98      0.98
Enter fullscreen mode Exit fullscreen mode

Now let's visualize it:

# Visualize the confusion matrix
plt.figure(figsize=(8, 6))
sns.heatmap(
    cm,
    annot=True,
    fmt='d',
    cmap='Blues',
    xticklabels=['Predicted Legitimate', 'Predicted Fraud'],
    yticklabels=['Actual Legitimate', 'Actual Fraud']
)
plt.title('Confusion Matrix — Fraud Detection Model')
plt.ylabel('Actual Label')
plt.xlabel('Predicted Label')
plt.tight_layout()
plt.savefig('confusion_matrix.png', dpi=150)
plt.show()
Enter fullscreen mode Exit fullscreen mode

Reading our confusion matrix:

[[1938    2]
 [   1   59]]
Enter fullscreen mode Exit fullscreen mode
  • 1938 — True Negatives (correctly identified legitimate)
  • 2 — False Positives (legitimate flagged as fraud)
  • 1 — False Negative (fraud that slipped through )
  • 59 — True Positives (correctly caught fraud)

That one False Negative is the number that matters most here — one fraudulent transaction our model missed. Accuracy alone would never have shown us that.

The Four Metrics That Come From a Confusion Matrix

1. Accuracy

Accuracy = (TP + TN) / (TP + TN + FP + FN)
Enter fullscreen mode Exit fullscreen mode

Overall correctness. Misleading for imbalanced datasets.

accuracy = (1938 + 59) / (1938 + 59 + 2 + 1)
# = 0.9985 → 99.85%
Enter fullscreen mode Exit fullscreen mode

2. Precision

Precision = TP / (TP + FP)
Enter fullscreen mode Exit fullscreen mode

Of all the transactions the model flagged as fraud — how many actually were fraud?

precision = 59 / (59 + 2)
# = 0.967 → 96.7%
Enter fullscreen mode Exit fullscreen mode

High precision = few false alarms. Important when false alarms are costly (e.g. blocking a legitimate customer's card).

3. Recall (Sensitivity)

Recall = TP / (TP + FN)
Enter fullscreen mode Exit fullscreen mode

Of all the actual fraud cases — how many did the model catch?

recall = 59 / (59 + 1)
# = 0.983 → 98.3%
Enter fullscreen mode Exit fullscreen mode

High recall = few missed detections. Critical when missing a positive case is dangerous (e.g. missing actual fraud, or missing a disease).

4. F1 Score

F1 = 2 × (Precision × Recall) / (Precision + Recall)
Enter fullscreen mode Exit fullscreen mode

The harmonic mean of precision and recall. Use this when you need a single number that balances both.

f1 = 2 * (0.967 * 0.983) / (0.967 + 0.983)
# = 0.975 → 97.5%
Enter fullscreen mode Exit fullscreen mode

Precision vs Recall — The Trade-off Every Developer Must Understand

This is the most important concept that comes out of the confusion matrix — and it's a trade-off you'll face in almost every real-world ML problem.

Increasing Recall (catch more positives)
→ You'll also increase False Positives (more false alarms)
→ Precision goes down

Increasing Precision (fewer false alarms)
→ You'll also increase False Negatives (miss more positives)
→ Recall goes down

You can control this trade-off by adjusting the model's decision threshold:

# Default threshold is 0.5
y_pred_default = model.predict(X_test)

# Lower threshold — catch more fraud (higher recall, lower precision)
y_pred_proba = model.predict_proba(X_test)[:, 1]
y_pred_sensitive = (y_pred_proba >= 0.3).astype(int)

# Higher threshold — fewer false alarms (higher precision, lower recall)
y_pred_precise = (y_pred_proba >= 0.7).astype(int)

print("Default (0.5):")
print(confusion_matrix(y_test, y_pred_default))

print("\nSensitive (0.3) — catches more fraud:")
print(confusion_matrix(y_test, y_pred_sensitive))

print("\nPrecise (0.7) — fewer false alarms:")
print(confusion_matrix(y_test, y_pred_precise))
Enter fullscreen mode Exit fullscreen mode

Multi-Class Confusion Matrix

The confusion matrix extends naturally to more than two classes. Instead of a 2x2 table you get an NxN table — one row and column per class:

from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier

# Multi-class example with Iris dataset
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, test_size=0.2, random_state=42
)

clf = RandomForestClassifier(random_state=42)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)

cm = confusion_matrix(y_test, y_pred)
print("Multi-class Confusion Matrix:")
print(cm)

# Visualize
plt.figure(figsize=(8, 6))
sns.heatmap(
    cm,
    annot=True,
    fmt='d',
    cmap='Greens',
    xticklabels=iris.target_names,
    yticklabels=iris.target_names
)
plt.title('Confusion Matrix — Iris Classification')
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.tight_layout()
plt.show()
Enter fullscreen mode Exit fullscreen mode

Output:

[[10  0  0]
 [ 0  9  0]
 [ 0  0 11]]
Enter fullscreen mode Exit fullscreen mode

A perfect diagonal means perfect predictions — every actual class was predicted correctly. Off-diagonal values show where the model confused one class for another.

Quick Reference — When to Use Which Metric

Is your dataset balanced?
  → Accuracy is fine as a starting point

Is your dataset imbalanced?
  → Never rely on accuracy alone
  → Use Precision, Recall, and F1

Is missing a positive case catastrophic?
  → Maximize Recall
  → (medical diagnosis, fraud, safety systems)

Are false alarms costly or annoying?
  → Maximize Precision
  → (spam filters, content moderation)

Do you need one balanced metric?
  → Use F1 Score

Are you comparing models overall?
  → Use ROC-AUC (area under the ROC curve)
Enter fullscreen mode Exit fullscreen mode

As a Developer — How I Think About This

The confusion matrix changed how I think about model quality. I now ask four questions before trusting any classification model:

1. What does a False Positive cost?
Someone gets an annoying email? Fine. A legitimate customer gets their card blocked? Not fine.

2. What does a False Negative cost?
A spam email gets through? Annoying. A fraudulent transaction gets through? Expensive.

3. What's the class balance?
If 99% of my data is one class — accuracy is meaningless. Period.

4. What threshold makes sense for this business problem?
The default 0.5 is rarely the right answer. Tune it based on the actual cost of each type of error.

The confusion matrix isn't just a tool for data scientists. It's essential knowledge for any developer building or consuming ML models — because it's the difference between a model that looks good and one that actually works for the problem at hand.

Accuracy is a headline. The confusion matrix is the full story.

Next time you train a classifier — don't stop at accuracy. Open the matrix and read what your model is really doing.

Top comments (0)