Skip to content

Matriz de confusión en Sklearn: cómo evaluar modelos de clasificación

Updated on

Tu modelo de clasificación reporta un 95% de accuracy, así que lo despliegas. Luego descubres que se le escapa el 80% de los casos positivos que realmente te importan: transacciones fraudulentas, diagnósticos de enfermedades, productos defectuosos. El accuracy por sí solo oculta información crítica sobre dónde y cómo falla un modelo.

Un único número de accuracy colapsa todos los tipos de error en una sola métrica. Un filtro de spam que deja pasar todos los correos de spam y clasifica correctamente todos los correos legítimos aún puede lograr un accuracy alto si el spam es solo el 5% del total. Necesitas ver el panorama completo: cuántos positivos detecta el modelo, cuántos negativos clasifica mal y en qué parte exacta se concentran los errores.

La matriz de confusión descompone el rendimiento del modelo en sus cuatro componentes: verdaderos positivos, verdaderos negativos, falsos positivos y falsos negativos. Junto con métricas derivadas como precisión, recall y F1-score, te ofrece una visión accionable de lo que tu modelo hace bien y mal. Scikit-learn proporciona confusion_matrix, classification_report y ConfusionMatrixDisplay para que este análisis sea directo.

📚

¿Qué es una matriz de confusión?

Una matriz de confusión es una tabla que compara las etiquetas predichas contra las etiquetas reales de un modelo de clasificación. Para clasificación binaria, es una cuadrícula 2x2:

Predicted PositivePredicted Negative
Actual PositiveTrue Positive (TP)False Negative (FN)
Actual NegativeFalse Positive (FP)True Negative (TN)

Cada celda cuenta el número de muestras que caen en esa categoría:

  • True Positive (TP): el modelo predijo positivo, y realmente era positivo. Correcto.
  • True Negative (TN): el modelo predijo negativo, y realmente era negativo. Correcto.
  • False Positive (FP): el modelo predijo positivo, pero realmente era negativo. Error de tipo I.
  • False Negative (FN): el modelo predijo negativo, pero realmente era positivo. Error de tipo II.

Matriz de confusión básica con Sklearn

from sklearn.metrics import confusion_matrix
import numpy as np
 
# Actual and predicted labels
y_true = [1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1]
y_pred = [1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1]
 
cm = confusion_matrix(y_true, y_pred)
print(cm)
# [[5 1]
#  [2 7]]

Cómo leer esta salida: sklearn organiza la matriz con fila 0 = real negativo, fila 1 = real positivo.

Predicted 0Predicted 1
Actual 0TN = 5FP = 1
Actual 1FN = 2TP = 7

Así que el modelo identificó correctamente 5 negativos y 7 positivos, mientras cometía 1 error de falso positivo y 2 errores de falso negativo.

Extraer valores individuales

from sklearn.metrics import confusion_matrix
 
y_true = [1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1]
y_pred = [1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1]
 
cm = confusion_matrix(y_true, y_pred)
tn, fp, fn, tp = cm.ravel()
 
print(f"True Negatives:  {tn}")
print(f"False Positives: {fp}")
print(f"False Negatives: {fn}")
print(f"True Positives:  {tp}")
# True Negatives:  5
# False Positives: 1
# False Negatives: 2
# True Positives:  7

Precisión, recall, F1-score y accuracy

Estas métricas se derivan directamente de la matriz de confusión:

MetricFormulaWhat It Answers
Accuracy(TP + TN) / (TP + TN + FP + FN)De todas las predicciones, ¿cuántas fueron correctas?
PrecisionTP / (TP + FP)De los positivos predichos, ¿cuántos eran realmente positivos?
Recall (Sensitivity)TP / (TP + FN)De los positivos reales, ¿cuántos capturamos?
SpecificityTN / (TN + FP)De los negativos reales, ¿cuántos identificamos correctamente?
F1-Score2 * (Precision * Recall) / (Precision + Recall)Media armónica de precisión y recall
from sklearn.metrics import (
    accuracy_score, precision_score, recall_score, f1_score
)
 
y_true = [1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1]
y_pred = [1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1]
 
print(f"Accuracy:  {accuracy_score(y_true, y_pred):.4f}")
print(f"Precision: {precision_score(y_true, y_pred):.4f}")
print(f"Recall:    {recall_score(y_true, y_pred):.4f}")
print(f"F1-Score:  {f1_score(y_true, y_pred):.4f}")
# Accuracy:  0.8000
# Precision: 0.8750
# Recall:    0.7778
# F1-Score:  0.8235

Cuándo priorizar precisión vs recall

ScenarioPrioritizeWhy
Detección de spamPrecisionLos falsos positivos (correo legítimo marcado como spam) molestan a los usuarios
Cribado de enfermedadesRecallLos falsos negativos (enfermedad no detectada) son peligrosos
Detección de fraudeRecallPerder fraudes es más costoso que investigar falsas alarmas
Resultados de buscadorPrecisionLos resultados irrelevantes degradan la experiencia
Detección de defectos en fabricaciónRecallQue productos defectuosos lleguen a clientes es costoso
Recomendación de contenidoPrecisionRecomendaciones irrelevantes reducen el engagement

Informe de clasificación

classification_report de Sklearn calcula precisión, recall, F1-score y support (número de ocurrencias reales) para cada clase en una sola llamada:

from sklearn.metrics import classification_report
 
y_true = [1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1]
y_pred = [1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1]
 
print(classification_report(y_true, y_pred, target_names=['Negative', 'Positive']))

Salida:

              precision    recall  f1-score   support

    Negative       0.71      0.83      0.77         6
    Positive       0.88      0.78      0.82         9

    accuracy                           0.80        15
   macro avg       0.80      0.81      0.80        15
weighted avg       0.81      0.80      0.80        15
  • macro avg: media no ponderada entre clases. Trata todas las clases por igual.
  • weighted avg: media ponderada por el support de cada clase. Tiene en cuenta el desbalance de clases.
  • support: número de muestras reales en cada clase.

Visualizar la matriz de confusión

Usar ConfusionMatrixDisplay

from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
import matplotlib.pyplot as plt
 
# Load and split data
data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    data.data, data.target, test_size=0.2, random_state=42
)
 
# Train model
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
 
# Plot confusion matrix
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(
    confusion_matrix=cm,
    display_labels=data.target_names
)
disp.plot(cmap='Blues')
plt.title('Breast Cancer Classification')
plt.tight_layout()
plt.savefig('confusion_matrix.png', dpi=150)
plt.show()

Usar Seaborn Heatmap

Para más personalización, usa seaborn directamente:

from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
 
# Load, split, train
data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    data.data, data.target, test_size=0.2, random_state=42
)
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
 
# Create confusion matrix
cm = confusion_matrix(y_test, y_pred)
 
# Plot with seaborn
plt.figure(figsize=(8, 6))
sns.heatmap(
    cm,
    annot=True,
    fmt='d',
    cmap='Blues',
    xticklabels=data.target_names,
    yticklabels=data.target_names,
    square=True,
    linewidths=0.5
)
plt.xlabel('Predicted Label')
plt.ylabel('True Label')
plt.title('Confusion Matrix - Breast Cancer Classification')
plt.tight_layout()
plt.savefig('confusion_matrix_seaborn.png', dpi=150)
plt.show()

Matriz de confusión normalizada

Los conteos brutos pueden ser engañosos cuando las clases tienen tamaños distintos. La normalización muestra proporciones:

from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
import matplotlib.pyplot as plt
 
data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    data.data, data.target, test_size=0.2, random_state=42
)
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
 
# Normalized confusion matrix (by true labels)
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
 
# Raw counts
ConfusionMatrixDisplay.from_predictions(
    y_test, y_pred,
    display_labels=data.target_names,
    cmap='Blues',
    ax=axes[0]
)
axes[0].set_title('Raw Counts')
 
# Normalized (rows sum to 1)
ConfusionMatrixDisplay.from_predictions(
    y_test, y_pred,
    display_labels=data.target_names,
    normalize='true',
    cmap='Blues',
    values_format='.2%',
    ax=axes[1]
)
axes[1].set_title('Normalized by True Label')
 
plt.tight_layout()
plt.savefig('confusion_matrix_normalized.png', dpi=150)
plt.show()

El parámetro normalize acepta tres opciones:

ValueNormalizationUse Case
'true'Filas suman 1 (divide por el conteo de la clase real)Ver recall por clase
'pred'Columnas suman 1 (divide por el conteo de la clase predicha)Ver precisión por clase
'all'Todas las celdas suman 1 (divide por el total)Ver la distribución global

Matriz de confusión multiclase

La matriz de confusión se extiende de forma natural a más de dos clases. Cada fila representa una clase real y cada columna representa una clase predicha:

from sklearn.metrics import confusion_matrix, classification_report, ConfusionMatrixDisplay
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
 
# Load iris dataset (3 classes)
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, test_size=0.3, random_state=42
)
 
# Train and predict
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
 
# Confusion matrix
cm = confusion_matrix(y_test, y_pred)
print("Confusion Matrix:")
print(cm)
 
# Classification report
print("\nClassification Report:")
print(classification_report(
    y_test, y_pred,
    target_names=iris.target_names
))
 
# Visualize
disp = ConfusionMatrixDisplay(
    confusion_matrix=cm,
    display_labels=iris.target_names
)
disp.plot(cmap='Blues')
plt.title('Iris Classification - 3 Classes')
plt.tight_layout()
plt.savefig('multi_class_confusion_matrix.png', dpi=150)
plt.show()

Estrategias de promediado multiclase

Al calcular precisión, recall y F1 para problemas multiclase, debes elegir un método de promediado:

from sklearn.metrics import precision_score, recall_score, f1_score
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
 
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, test_size=0.3, random_state=42
)
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
 
for avg in ['micro', 'macro', 'weighted']:
    p = precision_score(y_test, y_pred, average=avg)
    r = recall_score(y_test, y_pred, average=avg)
    f1 = f1_score(y_test, y_pred, average=avg)
    print(f"{avg:8s} -- Precision: {p:.4f}, Recall: {r:.4f}, F1: {f1:.4f}")
AverageMethodBest For
microTP, FP, FN totales en todas las clasesCuando importa el desbalance de clases
macroMedia no ponderada por claseCuando todas las clases son igual de importantes
weightedMedia ponderada por support de claseOpción por defecto para datasets desbalanceados

Ejemplo completo: evaluación end-to-end de clasificación

from sklearn.metrics import (
    confusion_matrix, classification_report, ConfusionMatrixDisplay,
    accuracy_score, precision_score, recall_score, f1_score, roc_auc_score
)
from sklearn.model_selection import train_test_split
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.datasets import load_breast_cancer
import matplotlib.pyplot as plt
import numpy as np
 
# Load data
data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    data.data, data.target, test_size=0.2, random_state=42, stratify=data.target
)
 
# Build pipeline
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('model', GradientBoostingClassifier(
        n_estimators=200, max_depth=3, random_state=42
    ))
])
 
# Train
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
y_prob = pipeline.predict_proba(X_test)[:, 1]
 
# Confusion matrix
cm = confusion_matrix(y_test, y_pred)
tn, fp, fn, tp = cm.ravel()
 
print("=" * 50)
print("MODEL EVALUATION REPORT")
print("=" * 50)
print(f"\nConfusion Matrix:")
print(f"  TP={tp}, FP={fp}")
print(f"  FN={fn}, TN={tn}")
print(f"\nAccuracy:  {accuracy_score(y_test, y_pred):.4f}")
print(f"Precision: {precision_score(y_test, y_pred):.4f}")
print(f"Recall:    {recall_score(y_test, y_pred):.4f}")
print(f"F1-Score:  {f1_score(y_test, y_pred):.4f}")
print(f"ROC-AUC:   {roc_auc_score(y_test, y_prob):.4f}")
print(f"\nDetailed Report:")
print(classification_report(y_test, y_pred, target_names=data.target_names))
 
# Visualize
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
ConfusionMatrixDisplay.from_predictions(
    y_test, y_pred, display_labels=data.target_names,
    cmap='Blues', ax=axes[0]
)
axes[0].set_title('Raw Counts')
 
ConfusionMatrixDisplay.from_predictions(
    y_test, y_pred, display_labels=data.target_names,
    normalize='true', values_format='.1%', cmap='Blues', ax=axes[1]
)
axes[1].set_title('Normalized')
plt.tight_layout()
plt.savefig('full_evaluation.png', dpi=150)
plt.show()

Explorar resultados de clasificación con PyGWalker

Después de construir tu matriz de confusión, profundiza en las malas clasificaciones explorando los datos en bruto de forma interactiva. PyGWalker (opens in a new tab) convierte los resultados de tus predicciones en una interfaz de analítica visual de arrastrar y soltar en Jupyter:

import pandas as pd
import pygwalker as pyg
 
# Build results DataFrame with features and predictions
results = pd.DataFrame(X_test, columns=data.feature_names)
results['actual'] = y_test
results['predicted'] = y_pred
results['correct'] = y_test == y_pred
results['confidence'] = y_prob
 
# Launch interactive exploration
walker = pyg.walk(results)

Filtra por muestras mal clasificadas, compara distribuciones de features entre los grupos TP/FP/FN/TN e identifica patrones que expliquen dónde le cuesta al modelo.

Para iterar sobre experimentos de clasificación en Jupyter —ajustar umbrales, probar distintos modelos o explorar combinaciones de features— RunCell (opens in a new tab) proporciona un agente de IA que acelera el ciclo de experimentación.

FAQ

What is a confusion matrix in sklearn?

Una matriz de confusión es una tabla que muestra los conteos de predicciones correctas e incorrectas para cada clase. En sklearn, confusion_matrix(y_true, y_pred) devuelve un array 2D de numpy donde las filas representan las clases reales y las columnas representan las clases predichas. Para clasificación binaria, muestra verdaderos positivos, verdaderos negativos, falsos positivos y falsos negativos.

How do I read a confusion matrix?

En la matriz de confusión de sklearn, las filas son las etiquetas reales y las columnas son las etiquetas predichas. Para clasificación binaria: arriba-izquierda son los verdaderos negativos (TN), arriba-derecha son los falsos positivos (FP), abajo-izquierda son los falsos negativos (FN) y abajo-derecha son los verdaderos positivos (TP). Los elementos de la diagonal son predicciones correctas.

What is the difference between precision and recall?

La precisión mide cuántos de los positivos predichos son realmente positivos (TP / (TP + FP)). El recall mide cuántos de los positivos reales capturó el modelo (TP / (TP + FN)). La precisión responde “cuando el modelo dice positivo, ¿con qué frecuencia acierta?”, mientras que el recall responde “de todos los positivos reales, ¿cuántos encontró el modelo?”.

When should I use F1-score instead of accuracy?

Usa F1-score cuando tus clases están desbalanceadas. Si el 95% de las muestras son negativas, un modelo que siempre predice negativo obtiene 95% de accuracy pero 0% de recall en positivos. El F1-score es la media armónica de precisión y recall, así que penaliza modelos que sacrifican una métrica por la otra.

How do I plot a confusion matrix in Python?

Usa ConfusionMatrixDisplay.from_predictions(y_true, y_pred) como método más rápido. Para más personalización, calcula la matriz con confusion_matrix() y dibújala con seaborn.heatmap(). Ambos enfoques soportan matrices normalizadas, mapas de color personalizados y etiquetas de clase.

What does normalize='true' do in ConfusionMatrixDisplay?

Configurar normalize='true' divide cada fila por el número total de muestras reales de esa clase, de modo que cada fila suma 1. Esto muestra el recall por clase como porcentaje. Usa normalize='pred' para ver precisión por clase, o normalize='all' para ver la proporción global.

Conclusión

La matriz de confusión es la base de la evaluación de modelos de clasificación. El accuracy por sí solo no es suficiente: necesitas ver los tipos específicos de errores que comete tu modelo. Usa confusion_matrix y classification_report de sklearn para obtener el panorama completo, visualiza con ConfusionMatrixDisplay o con heatmaps de seaborn para presentaciones e informes, y normaliza cuando los tamaños de clase difieran. Elige tu métrica principal en función del coste de negocio de cada tipo de error: precisión cuando los falsos positivos son caros, recall cuando los falsos negativos son peligrosos y F1-score cuando necesitas una medida equilibrada.

📚