En la lección 04-02 descubrimos que la regresión logística no predice clases, sino probabilidades (predict_proba), y que el umbral de 0.5 con el que se convierten en "churn / no churn" es una decisión nuestra — una palanca de negocio. En la lección anterior comparamos modelos con ese umbral clavado en 0.5; en esta lo liberamos. La curva ROC evalúa un clasificador en todos los umbrales a la vez, y el AUC resume esa curva en un solo número que permite comparar modelos independientemente del umbral. Después haremos el camino inverso: elegir el umbral operativo concreto que conviene a la campaña de retención de MercaFresh según sus costes y su presupuesto. Cerraremos con la curva precision-recall, la alternativa preferible bajo desbalanceo extremo, y con las limitaciones del AUC.
Contenido
- El umbral de decisión como parámetro libre
- TPR y FPR: las dos coordenadas de la ROC
- Construcción de la curva ROC punto a punto (a mano)
- AUC: interpretación probabilística
- ROC y AUC en scikit-learn: comparación de modelos
- Elegir el umbral operativo según costes de negocio
- La curva precision-recall como alternativa
- Limitaciones del AUC
El umbral de decisión como parámetro libre
Recordemos el mecanismo de 04-02:
probs = modelo.predict_proba(X_test)[:, 1] # P(churn) de cada cliente
y_pred_05 = (probs >= 0.5).astype(int) # umbral por defecto
y_pred_03 = (probs >= 0.3).astype(int) # umbral más "paranoico"Bajar el umbral a 0.3 hace que el modelo señale como churn a más clientes: caza más fugas reales (sube el recall) a costa de más falsas alarmas (baja la precision). Subirlo a 0.7 hace lo contrario. El mismo modelo entrenado genera infinitos clasificadores distintos, uno por umbral. Evaluar solo el de 0.5 es mirar un fotograma de una película; la curva ROC muestra la película entera.
TPR y FPR: las dos coordenadas de la ROC
Para cada umbral se calculan dos tasas a partir de la matriz de confusión de 06-02:
- TPR (tasa de verdaderos positivos) = TP / (TP + FN). Es exactamente el recall: de los clientes que se van, ¿qué fracción detectamos? Queremos que sea alta.
- FPR (tasa de falsos positivos) = FP / (FP + TN). De los clientes fieles, ¿a qué fracción molestamos con una falsa alarma? Queremos que sea baja.
Nótese la elegancia: cada tasa se calcula dentro de su propia clase (TPR entre los positivos reales, FPR entre los negativos reales), por lo que ninguna depende de la proporción de churn del dataset — un detalle que será relevante al final de la lección.
Construcción de la curva ROC punto a punto (a mano)
Nada fija mejor la idea que calcularla a mano. Tomemos 10 clientes de test de MercaFresh con sus probabilidades de churn según el modelo, ordenados de mayor a menor probabilidad (4 churn reales, 6 fieles):
| Cliente | P(churn) | Real |
|---|---|---|
| C1 | 0.92 | Churn |
| C2 | 0.81 | Churn |
| C3 | 0.74 | Fiel |
| C4 | 0.66 | Churn |
| C5 | 0.55 | Fiel |
| C6 | 0.47 | Fiel |
| C7 | 0.39 | Churn |
| C8 | 0.30 | Fiel |
| C9 | 0.18 | Fiel |
| C10 | 0.05 | Fiel |
Ahora deslizamos el umbral desde arriba: para cada valor, todo cliente con probabilidad ≥ umbral se predice como churn, y contamos TP, FP → TPR = TP/4, FPR = FP/6:
| Umbral | Predichos churn | TP | FP | TPR | FPR |
|---|---|---|---|---|---|
| > 0.92 | (nadie) | 0 | 0 | 0.00 | 0.00 |
| 0.92 | C1 | 1 | 0 | 0.25 | 0.00 |
| 0.81 | C1–C2 | 2 | 0 | 0.50 | 0.00 |
| 0.74 | C1–C3 | 2 | 1 | 0.50 | 0.17 |
| 0.66 | C1–C4 | 3 | 1 | 0.75 | 0.17 |
| 0.55 | C1–C5 | 3 | 2 | 0.75 | 0.33 |
| 0.47 | C1–C6 | 3 | 3 | 0.75 | 0.50 |
| 0.39 | C1–C7 | 4 | 3 | 1.00 | 0.50 |
| 0.30 | C1–C8 | 4 | 4 | 1.00 | 0.67 |
| 0.18 | C1–C9 | 4 | 5 | 1.00 | 0.83 |
| 0.05 | todos | 4 | 6 | 1.00 | 1.00 |
La curva ROC es el dibujo de estos puntos (FPR en el eje X, TPR en el eje Y), desde (0,0) — umbral imposible de superar: nadie es señalado — hasta (1,1) — umbral cero: todos señalados. Cada vez que el deslizamiento "traga" un churn real, la curva sube; cada vez que traga un fiel, avanza a la derecha. Un modelo bueno ordena a los churn por delante de los fieles, así que su curva sube pronto y mucho antes de avanzar: se pega a la esquina superior izquierda.
Dos referencias en el gráfico:
- La diagonal de (0,0) a (1,1) es el azar: un "modelo" que asignara probabilidades aleatorias sube y avanza al mismo ritmo. Toda curva útil debe quedar por encima; una curva por debajo de la diagonal indica un modelo que ordena al revés (¡invertir sus predicciones lo volvería útil!).
- El punto ideal es (0, 1): 100 % de fugas detectadas, 0 % de fieles molestados. Ningún modelo real lo alcanza, pero la distancia a esa esquina resume la calidad de cada umbral.
AUC: interpretación probabilística
El AUC (Area Under the Curve) es el área bajo la curva ROC: un número entre 0 y 1 que condensa el rendimiento en todos los umbrales.
| AUC | Lectura |
|---|---|
| 0.5 | Azar puro (la diagonal) |
| 0.6–0.7 | Discriminación débil |
| 0.7–0.8 | Aceptable |
| 0.8–0.9 | Buena |
| > 0.9 | Excelente (¡verifica que no haya fuga de datos, 06-01!) |
Su interpretación más útil es probabilística: el AUC es la probabilidad de que, tomando al azar un cliente que se fue y uno fiel, el modelo asigne mayor probabilidad de churn al que se fue. Es decir, mide la calidad del ranking, no de la clasificación. Comprobémoslo con nuestra tabla: hay 4 × 6 = 24 pares (churn, fiel) posibles; contando cuántos ordena bien el modelo (el churn con mayor probabilidad que el fiel): C1 y C2 superan a los 6 fieles (12 pares), C4 supera a 5 (todos menos C3), C7 supera a 3 (C8, C9, C10). Total: 12 + 5 + 3 = 20 pares bien ordenados de 24 → AUC = 20/24 ≈ 0.83, que coincide exactamente con el área bajo la curva escalonada anterior.
Esta lectura explica por qué el AUC es ideal para comparar modelos antes de decidir el umbral: un modelo con mejor AUC ordena mejor a los clientes por riesgo, y esa ordenación es la materia prima de cualquier campaña ("llamar primero a los de más riesgo").
ROC y AUC en scikit-learn: comparación de modelos
from sklearn.metrics import roc_curve, roc_auc_score
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
modelos = {
"Regresión logística": Pipeline([("sc", StandardScaler()),
("clf", LogisticRegression(max_iter=1000))]),
"Árbol (max_depth=5)": DecisionTreeClassifier(max_depth=5, random_state=42),
}
plt.figure(figsize=(6, 6))
for nombre, m in modelos.items():
m.fit(X_train, y_train)
probs = m.predict_proba(X_test)[:, 1] # necesitamos probabilidades, no clases
fpr, tpr, umbrales = roc_curve(y_test, probs)
auc = roc_auc_score(y_test, probs)
plt.plot(fpr, tpr, label=f"{nombre} (AUC = {auc:.3f})")
plt.plot([0, 1], [0, 1], "k--", label="Azar (AUC = 0.5)") # la diagonal
plt.xlabel("FPR (fieles molestados)")
plt.ylabel("TPR / Recall (fugas detectadas)")
plt.title("Curvas ROC — churn MercaFresh")
plt.legend()
plt.show()Detalles importantes:
roc_curvedevuelve tres arrays:fpr,tpry losumbralescorrespondientes a cada punto — los usaremos ahora para elegir el umbral operativo.- A
roc_auc_scorese le pasan las probabilidades, nuncapredict()(con clases 0/1 la "curva" tendría un solo punto útil y el AUC saldría distorsionado). - Para modelos sin
predict_probabien calibrado (comoSVC), se usadecision_function, que también ordena. - Si dos curvas se cruzan, un modelo puede ser mejor en la zona de FPR bajo y el otro en la de FPR alto: el AUC global puede empatar mientras que, para tu rango operativo, uno de los dos sea claramente preferible. Mirar la curva, no solo el número.
Elegir el umbral operativo según costes de negocio
El AUC compara modelos; para usar el modelo elegido hay que fijar un umbral. Escenario MercaFresh: el equipo de retención puede llamar a 600 clientes al mes (presupuesto limitado) de una cartera de 10.000, con churn esperado del 20 %. Dos enfoques complementarios:
Enfoque 1 — capacidad: si solo puedes llamar a 600, el umbral es simplemente el que señala a los 600 clientes de mayor probabilidad:
import numpy as np
probs_todos = modelo.predict_proba(X_cartera)[:, 1]
umbral_capacidad = np.sort(probs_todos)[-600] # prob. del cliente nº 600 por riesgo
print(f"Umbral operativo: {umbral_capacidad:.2f}")Enfoque 2 — costes: con costes explícitos (llamada + vale ≈ 15 €; cliente perdido ≈ 300 € de valor anual; la retención funciona, digamos, en el 30 % de los contactados a tiempo), se puede calcular el beneficio esperado de cada umbral y quedarse con el máximo:
fpr, tpr, umbrales = roc_curve(y_test, probs)
n_pos, n_neg = y_test.sum(), (1 - y_test).sum()
coste_contacto, valor_cliente, efic = 15, 300, 0.30
beneficio = (tpr * n_pos * efic * valor_cliente # fugas salvadas
- (tpr * n_pos + fpr * n_neg) * coste_contacto) # coste de contactar
mejor = np.argmax(beneficio)
print(f"Umbral óptimo: {umbrales[mejor]:.2f} (TPR={tpr[mejor]:.2f}, FPR={fpr[mejor]:.2f})")La conclusión habitual en churn: el umbral óptimo queda muy por debajo de 0.5 (a menudo 0.2–0.35), porque un FN cuesta 20 veces más que un FP. El 0.5 por defecto no es neutral: presupone costes simétricos que casi nunca son los del negocio. Y una nota metodológica: el umbral se elige con datos de validación, no de test (regla de 06-01: el test no participa en decisiones).
La curva precision-recall como alternativa
Con desbalanceo fuerte (positivos al 1–5 %: fraude, enfermedades raras... o un churn mensual muy bajo), la ROC puede pintar un panorama demasiado optimista: como el FPR se calcula sobre la enorme clase negativa, un FPR "bajo" del 5 % puede significar miles de falsas alarmas que sepultan a los verdaderos positivos. La curva precision-recall (precision en Y, recall en X, un punto por umbral) usa la precision en lugar del FPR, y la precision sí siente cada falso positivo directamente.
from sklearn.metrics import precision_recall_curve, average_precision_score
prec, rec, umb = precision_recall_curve(y_test, probs)
ap = average_precision_score(y_test, probs) # análogo al AUC para esta curva
plt.plot(rec, prec, label=f"AP = {ap:.3f}")
plt.xlabel("Recall"); plt.ylabel("Precision"); plt.legend(); plt.show()Regla práctica: churn al 20 % → la ROC funciona bien; positivos por debajo del ~5 % o interés centrado exclusivamente en la clase minoritaria → añade (o prioriza) la curva precision-recall. Aquí su línea base no es la diagonal, sino la horizontal en la prevalencia (0.20 en nuestro caso): esa es la precision de señalar al azar.
Limitaciones del AUC
- Es ciego a los costes: resume todos los umbrales por igual, incluidos los que nunca usarías. Dos modelos con el mismo AUC pueden rendir muy distinto en tu zona operativa (FPR bajo, si el presupuesto es corto).
- No mide calibración: evalúa el orden, no si "0.7" significa realmente un 70 % de probabilidad. Un modelo puede tener AUC 0.85 con probabilidades sistemáticamente infladas; si vas a usar las probabilidades como tales (cálculos de beneficio esperado), la calibración importa.
- Optimista bajo desbalanceo extremo, como acabamos de ver: complementa con precision-recall.
- Un número no sustituye la curva: con curvas que se cruzan, el resumen escalar oculta la diferencia que te afecta.
- Nada de esto lo invalida: como métrica de comparación de modelos independiente del umbral, sigue siendo el estándar (y por eso
scoring="roc_auc"encaja de forma natural en la validación cruzada de 06-03).
Errores Comunes y Consejos
- Pasar
predict()en vez depredict_proba()aroc_auc_score. Con clases duras el AUC se calcula sobre un ranking de solo dos valores y sale artificialmente bajo. Probabilidades siempre. - Quedarse con el umbral 0.5 "porque es el defecto". En problemas con costes asimétricos (casi todos los reales), 0.5 es una elección arbitraria heredada, no una decisión.
- Elegir el umbral mirando el test. El umbral es una decisión más del modelado: se toma con validación (o CV) y se verifica una única vez en test.
- Comparar AUCs con tres decimales. La lección 06-03 aplica también aquí: un AUC de 0.842 no es mejor que uno de 0.838 sin mirar la variabilidad entre folds.
- Usar ROC como única lente con desbalanceo extremo. Acompáñala de la curva precision-recall.
- Consejo: entrega al negocio la curva con dos o tres puntos operativos anotados ("con umbral 0.3: detectamos el 78 % de las fugas llamando a 1.900 clientes") — es infinitamente más accionable que "AUC = 0.84".
Ejercicios
Ejercicio 1
Con esta mini-tabla (3 churn reales, 3 fieles), calcula los puntos (FPR, TPR) para los umbrales 0.9, 0.6 y 0.3, y el AUC por el método de contar pares bien ordenados.
| Cliente | P(churn) | Real |
|---|---|---|
| A | 0.90 | Churn |
| B | 0.70 | Fiel |
| C | 0.60 | Churn |
| D | 0.40 | Churn |
| E | 0.20 | Fiel |
| F | 0.10 | Fiel |
Ejercicio 2
El modelo de churn de MercaFresh tiene AUC = 0.84. Explica a un directivo, sin tecnicismos, qué significa ese 0.84, y por qué ese número por sí solo no dice cuántos clientes hay que llamar cada mes.
Ejercicio 3
Escribe el código que, dado probs (probabilidades sobre validación) e y_val, encuentra el umbral más bajo cuyo recall es ≥ 0.80 y reporta cuántos clientes habría que contactar con él (sobre una cartera de 10.000 con la misma distribución). Pista: roc_curve te da tpr y umbrales alineados.
Soluciones
Solución 1. Positivos = 3 (A, C, D), negativos = 3 (B, E, F).
- Umbral 0.9 → señalados {A}: TP = 1, FP = 0 → (FPR, TPR) = (0, 0.33).
- Umbral 0.6 → señalados {A, B, C}: TP = 2, FP = 1 → (0.33, 0.67).
- Umbral 0.3 → señalados {A, B, C, D}: TP = 3, FP = 1 → (0.33, 1.00).
Pares (churn, fiel) = 9. Bien ordenados: A supera a B, E, F (3); C supera a E, F (2); D supera a E, F (2). Total 7/9 → AUC ≈ 0.78.
Solución 2. "Si cogemos al azar un cliente que acabó dándose de baja y otro que se quedó, el modelo le asigna más riesgo al que se dio de baja el 84 % de las veces: ordena bien a los clientes por riesgo de fuga." No dice a cuántos llamar porque el AUC evalúa la ordenación completa, sin fijar ningún punto de corte: cuántos llamamos depende de dónde cortemos esa lista ordenada, y esa es una decisión de negocio (presupuesto de la campaña, coste por contacto, valor del cliente), no una propiedad del modelo.
Solución 3.
import numpy as np
from sklearn.metrics import roc_curve
fpr, tpr, umbrales = roc_curve(y_val, probs)
# Primer punto (recorriendo umbrales de mayor a menor) con recall >= 0.80:
idx = np.argmax(tpr >= 0.80) # primer índice que cumple la condición
umbral_op = umbrales[idx]
print(f"Umbral operativo: {umbral_op:.3f} (TPR={tpr[idx]:.2f}, FPR={fpr[idx]:.2f})")
# Fracción de la cartera señalada con ese umbral:
frac_contactada = (probs >= umbral_op).mean()
print(f"Contactos sobre 10.000 clientes: {frac_contactada * 10000:.0f}")Como roc_curve ordena los umbrales de mayor a menor y tpr crece a lo largo del array, el primer índice con tpr >= 0.80 corresponde al umbral más alto (y por tanto con menos contactos) que alcanza ese recall; es el punto más barato que cumple el objetivo de detección.
Conclusión
Hemos liberado el umbral de decisión: TPR y FPR describen cada posible corte, la curva ROC los dibuja todos (construida a mano, punto a punto, sobre diez clientes de MercaFresh), y el AUC la resume con una interpretación memorable — la probabilidad de rankear bien un par churn/fiel. Con roc_curve y roc_auc_score comparamos modelos más allá del 0.5 por defecto, elegimos el umbral operativo con criterios de negocio (presupuesto de la campaña, coste de cada tipo de error) y aprendimos cuándo la curva precision-recall es mejor lente y qué no cuenta el AUC. Ya tenemos el arsenal completo de evaluación; queda la pregunta de diagnóstico que lo atraviesa todo: cuando un modelo rinde mal — o sospechosamente bien — ¿es porque memoriza o porque no aprende lo suficiente? Overfitting y underfitting, el diagnóstico central de todo el Machine Learning, cierran el módulo en la próxima lección.
Curso de Machine Learning
Módulo 1: Introducción al Machine Learning
- ¿Qué es el Machine Learning?
- Historia y evolución del Machine Learning
- Tipos de Machine Learning
- Aplicaciones del Machine Learning
- El flujo de trabajo de un proyecto de Machine Learning
Módulo 2: Fundamentos de Estadística y Probabilidad
- Conceptos básicos de estadística
- Distribuciones de probabilidad
- Correlación y covarianza
- Inferencia estadística
- Teorema de Bayes
Módulo 3: Preprocesamiento de Datos
- Limpieza de datos
- Manejo de datos faltantes
- Transformación de datos
- Codificación de variables categóricas
- Normalización y estandarización
- Ingeniería de características
Módulo 4: Algoritmos de Machine Learning Supervisado
- Regresión lineal
- Regresión logística
- Árboles de decisión
- Máquinas de soporte vectorial (SVM)
- K-Vecinos más cercanos (K-NN)
- Naive Bayes
- Redes neuronales
Módulo 5: Algoritmos de Machine Learning No Supervisado
- Clustering: K-means
- Clustering jerárquico
- Análisis de componentes principales (PCA)
- Análisis de agrupamiento DBSCAN
- Visualización de datos con t-SNE y UMAP
Módulo 6: Evaluación y Validación de Modelos
- División de datos: entrenamiento, validación y prueba
- Métricas de evaluación
- Validación cruzada
- Curva ROC y AUC
- Overfitting y underfitting
Módulo 7: Técnicas Avanzadas y Optimización
- Regularización: Ridge, Lasso y Elastic Net
- Ensemble Learning
- Gradient Boosting
- Redes neuronales profundas (Deep Learning)
- Optimización de hiperparámetros
Módulo 8: Implementación y Despliegue de Modelos
- Frameworks y bibliotecas populares
- Implementación de modelos en producción
- Mantenimiento y monitoreo de modelos
- Consideraciones éticas y de privacidad
Módulo 9: Proyectos Prácticos
- Proyecto 1: Predicción de precios de viviendas
- Proyecto 2: Clasificación de imágenes
- Proyecto 3: Análisis de sentimientos en redes sociales
- Proyecto 4: Detección de fraudes
- Proyecto 5: Segmentación de clientes
