Curva AUC - ROC
Aprende a calcular y graficar curvas AUC-ROC en Python con sklearn. Comprende TPR, FPR, umbrales y cuándo usar AUC en lugar de precisión.
La curva AUC-ROC (Área Bajo la Curva Característica Operativa del Receptor) es una de las métricas más importantes para evaluar modelos de clasificación binaria. Captura la capacidad de un modelo para separar ejemplos positivos y negativos a través de todos los posibles umbrales de decisión, brindando una imagen mucho más completa que una única puntuación de precisión.
Este capítulo cubre:
- Qué es la curva ROC y cómo se construye
- Cómo interpretar TPR, FPR y los términos de la matriz de confusión que los sustentan
- Cómo calcular AUC-ROC con
scikit-learnde Python - Cómo graficar la curva ROC con
matplotlib - Cuándo AUC-ROC es la métrica adecuada y cuándo no lo es
Términos clave: TP, FP, TN, FN
Antes de adentrarnos en la curva en sí, es importante entender los cuatro resultados posibles que puede producir un clasificador binario. Para cualquier predicción, la etiqueta real es positiva (P) o negativa (N), y la etiqueta predicha también es positiva o negativa:
| Predicho Positivo | Predicho Negativo | |
|---|---|---|
| Real Positivo | Verdadero Positivo (TP) | Falso Negativo (FN) |
| Real Negativo | Falso Positivo (FP) | Verdadero Negativo (TN) |
Estas cuatro celdas son los bloques fundamentales de todas las métricas de evaluación tratadas en este capítulo. Para una introducción más detallada, consulta el capítulo de la Matriz de Confusión.
¿Qué es la curva ROC?
Un clasificador suele generar una puntuación de probabilidad (un número entre 0 y 1) en lugar de una etiqueta definitiva. Se elige un umbral — por ejemplo, 0.5 — y todo lo que esté por encima del umbral se predice como positivo.
Cambiar el umbral desplaza el equilibrio entre capturar verdaderos positivos y marcar incorrectamente verdaderos negativos:
- Un umbral muy bajo captura más positivos (alta recuperación), pero también genera más falsas alarmas.
- Un umbral muy alto es más selectivo (alta precisión), pero pierde positivos reales.
La curva ROC representa esta disyuntiva para todos los umbrales posibles a la vez:
- Eje X — Tasa de Falsos Positivos (FPR): qué fracción de los negativos reales se marca erróneamente como positiva.
- Eje Y — Tasa de Verdaderos Positivos (TPR), también llamada Recall o Sensibilidad: qué fracción de los positivos reales se identifica correctamente.
TPR = TP / (TP + FN) y FPR = FP / (FP + TN)
Cada punto de la curva ROC corresponde a un valor de umbral. En conjunto, trazan un camino desde (0, 0) — el umbral más selectivo, que no predice nada como positivo — hasta (1, 1) — el umbral menos selectivo, que predice todo como positivo.
¿Qué es AUC?
El AUC (Área Bajo la Curva) resume toda la curva ROC en un único número entre 0 y 1:
| AUC | Significado |
|---|---|
| 1.0 | Clasificador perfecto — separa cada positivo de cada negativo |
| 0.5 | Sin habilidad — equivalente a adivinar al azar |
| < 0.5 | Peor que el azar (las probabilidades predichas están invertidas) |
| 0.7 – 0.8 | Aceptable para muchos problemas prácticos |
| 0.8 – 0.9 | Bueno |
| > 0.9 | Excelente |
Intuitivamente, AUC es la probabilidad de que el modelo clasifique un ejemplo positivo elegido al azar más alto que un ejemplo negativo elegido al azar.
Cálculo de AUC-ROC con scikit-learn
El módulo sklearn.metrics proporciona roc_curve() para obtener los valores de TPR/FPR por umbral y roc_auc_score() para obtener el valor único de AUC.
Ejemplo mínimo funcional
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score, roc_curve
# 1. Create a toy binary-classification dataset
X, y = make_classification(n_samples=500, n_features=10, random_state=42)
# 2. Split into train / test sets
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42
)
# 3. Train a logistic regression model
model = LogisticRegression(random_state=42)
model.fit(X_train, y_train)
# 4. Get probability scores for the positive class (column 1)
y_proba = model.predict_proba(X_test)[:, 1]
# 5. Compute AUC
auc = roc_auc_score(y_test, y_proba)
print(f"AUC-ROC: {auc:.3f}")
# 6. Get the (fpr, tpr, thresholds) arrays for plotting
fpr, tpr, thresholds = roc_curve(y_test, y_proba)
print(f"Number of threshold points: {len(thresholds)}")La salida será similar a:
AUC-ROC: 0.944
Number of threshold points: 30El valor exacto varía ligeramente según la versión de scikit-learn, pero debería estar en el rango de 0.90–0.97 para este conjunto de datos.
Graficando la curva ROC
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score, roc_curve
X, y = make_classification(n_samples=500, n_features=10, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
model = LogisticRegression(random_state=42)
model.fit(X_train, y_train)
y_proba = model.predict_proba(X_test)[:, 1]
auc = roc_auc_score(y_test, y_proba)
fpr, tpr, _ = roc_curve(y_test, y_proba)
plt.figure(figsize=(7, 5))
plt.plot(fpr, tpr, color="steelblue", lw=2, label=f"ROC curve (AUC = {auc:.2f})")
plt.plot([0, 1], [0, 1], color="gray", linestyle="--", label="Random baseline (AUC = 0.50)")
plt.xlabel("False Positive Rate (FPR)")
plt.ylabel("True Positive Rate (TPR)")
plt.title("ROC Curve")
plt.legend(loc="lower right")
plt.tight_layout()
plt.savefig("roc_curve.png", dpi=120)
plt.show()La línea diagonal discontinua representa a un clasificador aleatorio. Cuanto más se curve la curva ROC hacia la esquina superior izquierda, mejor es el modelo.
Comparación de múltiples modelos
Un flujo de trabajo común es entrenar varios modelos y comparar sus curvas ROC en la misma gráfica:
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score, roc_curve
import matplotlib.pyplot as plt
X, y = make_classification(n_samples=500, n_features=10, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
models = {
"Logistic Regression": LogisticRegression(random_state=42),
"Decision Tree": DecisionTreeClassifier(max_depth=3, random_state=42),
}
plt.figure(figsize=(7, 5))
for name, clf in models.items():
clf.fit(X_train, y_train)
y_proba = clf.predict_proba(X_test)[:, 1]
fpr, tpr, _ = roc_curve(y_test, y_proba)
auc = roc_auc_score(y_test, y_proba)
plt.plot(fpr, tpr, lw=2, label=f"{name} (AUC = {auc:.2f})")
plt.plot([0, 1], [0, 1], "k--", label="Random (AUC = 0.50)")
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.title("ROC Curve Comparison")
plt.legend(loc="lower right")
plt.tight_layout()
plt.savefig("roc_comparison.png", dpi=120)
plt.show()El modelo con el área mayor bajo su curva suele ser la mejor opción para ese conjunto de datos. Para un flujo de selección de modelos riguroso, combina esto con validación cruzada.
Elección del punto de operación (umbral)
El AUC resume todos los umbrales, pero en algún momento necesitarás elegir un único umbral para producción. Dos estrategias comunes:
1. Estadístico J de Youden
El estadístico J de Youden maximiza TPR − FPR, encontrando el punto único de la curva ROC que se encuentra más lejos de la diagonal:
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_curve
import numpy as np
X, y = make_classification(n_samples=500, n_features=10, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
model = LogisticRegression(random_state=42)
model.fit(X_train, y_train)
y_proba = model.predict_proba(X_test)[:, 1]
fpr, tpr, thresholds = roc_curve(y_test, y_proba)
j_scores = tpr - fpr
best_idx = int(np.argmax(j_scores))
best_threshold = thresholds[best_idx]
print(f"Best threshold (Youden's J): {best_threshold:.3f}")
print(f"TPR at best threshold: {tpr[best_idx]:.3f}")
print(f"FPR at best threshold: {fpr[best_idx]:.3f}")2. Umbral basado en el negocio
En la detección de fraudes puedes tolerar más falsos positivos para atrapar más fraudes (umbral más bajo). En el cribado médico es posible que quieras muy pocos falsos negativos (también umbral más bajo). Elegir el umbral es en última instancia una decisión de negocio informada por el coste de cada tipo de error.
AUC-ROC frente a otras métricas
| Métrica | Mejor cuando... | Precaución con... |
|---|---|---|
| Accuracy | Las clases están balanceadas | Engañosa en conjuntos de datos desequilibrados |
| Precision / Recall | Te importa principalmente una clase | Requiere un umbral |
| F1-Score | Balance armónico de precisión y recall | Sigue dependiendo del umbral |
| AUC-ROC | Comparar modelos o ajustar umbrales | No significativa para multiclase sin extensión |
| AUC-PR (Precision-Recall AUC) | Fuerte desequilibrio de clases (positivos raros) | Menos intuitiva que ROC |
Para datos severamente desequilibrados — por ejemplo, 1 % de positivos — la curva AUC-PR (precisión frente a recall) suele ser más informativa que la AUC-ROC, porque la FPR puede parecer pequeña incluso cuando muchos negativos están marcados incorrectamente.
Errores comunes
Pasar etiquetas definitivas en lugar de probabilidades. roc_auc_score necesita puntuaciones de probabilidad, no etiquetas 0/1. Usa model.predict_proba(X_test)[:, 1], no model.predict(X_test).
Olvidar especificar la clase positiva. Por defecto, roc_curve y roc_auc_score tratan la etiqueta entera mayor como positiva. Pasa pos_label=1 explícitamente cuando tus etiquetas no sean enteros 0/1.
Sobreajuste del AUC en el conjunto de entrenamiento. Evalúa siempre en un conjunto de prueba separado o usa validación cruzada para obtener una estimación fiable del AUC. El capítulo de Grid Search muestra cómo pasar scoring='roc_auc' directamente a GridSearchCV.
AUC ≈ 0.5 no siempre significa que el modelo es malo. Puede significar que los ejemplos positivos y negativos se solapan genuinamente en el espacio de características, o que tus características no son informativas para la tarea.
Ejemplo práctico: predicción de enfermedades
El siguiente ejemplo completo utiliza el conjunto de datos de cáncer de mama incluido en scikit-learn:
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import roc_auc_score, roc_curve
import matplotlib.pyplot as plt
# Load dataset (569 samples, 30 features, binary labels: malignant=0 / benign=1)
data = load_breast_cancer()
X, y = data.data, data.target
# Scale features — important for logistic regression
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_train, X_test, y_train, y_test = train_test_split(
X_scaled, y, test_size=0.2, random_state=42, stratify=y
)
classifiers = {
"Logistic Regression": LogisticRegression(max_iter=1000, random_state=42),
"Random Forest": RandomForestClassifier(n_estimators=100, random_state=42),
}
plt.figure(figsize=(7, 5))
for name, clf in classifiers.items():
clf.fit(X_train, y_train)
y_proba = clf.predict_proba(X_test)[:, 1]
auc = roc_auc_score(y_test, y_proba)
fpr, tpr, _ = roc_curve(y_test, y_proba)
plt.plot(fpr, tpr, lw=2, label=f"{name} (AUC = {auc:.3f})")
plt.plot([0, 1], [0, 1], "k--", label="Random (AUC = 0.50)")
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.title("ROC Curve — Breast Cancer Dataset")
plt.legend(loc="lower right")
plt.tight_layout()
plt.savefig("breast_cancer_roc.png", dpi=120)
plt.show()Ambos clasificadores deberían producir puntuaciones AUC superiores a 0.98 en este conjunto de datos, confirmando que las características del cáncer de mama son muy predictivas.
Temas relacionados
- Matriz de Confusión — los bloques fundamentales TP/FP/TN/FN usados a lo largo de este capítulo
- Regresión Logística — el modelo más común emparejado con la evaluación AUC-ROC
- Validación Cruzada — cómo obtener estimaciones fiables de AUC que generalicen más allá de una única división entrenamiento/prueba
- Grid Search — cómo ajustar hiperparámetros con
scoring='roc_auc' - Árbol de Decisión — otro clasificador binario cuyas salidas de probabilidad pueden evaluarse con AUC-ROC
Resumen
| Concepto | Punto clave |
|---|---|
| Curva ROC | Representa TPR frente a FPR en cada umbral de decisión |
| AUC | Área bajo la curva ROC; 1.0 = perfecto, 0.5 = aleatorio |
roc_auc_score | Pasa puntuaciones de probabilidad, no etiquetas definitivas |
roc_curve | Devuelve arrays (fpr, tpr, thresholds) para graficar |
| Selección de umbral | Usa el estadístico J de Youden o el conocimiento del dominio para elegir un umbral de producción |
| Cuándo preferir AUC-PR | Conjuntos de datos severamente desequilibrados con positivos raros |
La AUC-ROC te proporciona un único número independiente del umbral que resume la capacidad discriminativa de tu modelo en todos los puntos de operación. Úsala para comparar modelos, ajustar hiperparámetros y comunicar la calidad del clasificador — luego elige el umbral específico que se ajuste a la tolerancia de tu aplicación a los falsos positivos frente a los falsos negativos.