ML/AIMLE·AIMachine Learning Engineering
Proyecto · Telecomunicaciones · Completado

Predicción de bajas de clientes

Qué clientes de una operadora están a punto de irse, a quién merece la pena llamar y cuánto dinero ahorra hacerlo bien: un proyecto de machine learning completo, del problema de negocio a la API, con resultados reales.

Dataset IBM Telco Customer Churn (7.043 clientes) · scikit-learn 1.8 y XGBoost 3.2 · random_state = 42 · Todas las cifras se reproducen con notebooks/churn_telco.py

  1. Problema
  2. Datos
  3. Exploración
  4. Preprocesado
  5. Modelos candidatos
  6. Optimización
  7. Evaluación en test
  8. Umbral de negocio
  9. Interpretación
  10. Calculadora de riesgo
  11. Despliegue
  12. Resultados y conclusiones

01Problema

Una operadora de telecomunicaciones pierde cada mes una parte de sus clientes (churn). Conseguir un cliente nuevo cuesta bastante más que conservar uno, así que el equipo de retención quiere llamar a tiempo a quienes están a punto de irse con una oferta. Pero llamar a todos es caro, y llamar al azar no sirve.

Objetivo: dar a cada cliente una probabilidad de baja y decidir a quién contactar para que la campaña gane el máximo dinero posible.

PreguntaRespuesta en este proyecto
Tipo de problemaClasificación binaria (se va / se queda) con clases desbalanceadas: 26,5 % de bajas
Métrica técnicaROC-AUC (ordenar bien por riesgo) y PR-AUC (calidad al buscar la clase minoritaria)
Métrica de negocioBeneficio de la campaña de retención, según un umbral de decisión
RestricciónEl equipo comercial tiene que entender por qué el modelo marca a un cliente

02Datos

Dataset IBM Telco Customer Churn: 7.043 clientes de una operadora ficticia de California, publicado por IBM como ejemplo (licencia Apache 2.0). Cada fila es un cliente con 19 variables y la etiqueta Churn (si se dio de baja en el último mes).

GrupoVariables
PerfilGénero, mayor de 65 años, pareja, personas a cargo
ContratoAntigüedad en meses (tenure), tipo de contrato, factura electrónica, método de pago
ServiciosTeléfono, varias líneas, tipo de internet (DSL o fibra), seguridad, copia de seguridad, protección de dispositivos, soporte técnico, TV y películas en streaming
FacturaciónCuota mensual y total facturado

Primer problema de calidad: TotalCharges se carga como texto porque 11 clientes lo tienen en blanco. Los 11 tienen antigüedad 0: son altas recientes que aún no han recibido ninguna factura. Se rellena con 0, que es su valor real, en vez de borrar las filas o usar la mediana.

notebooks/churn_telco.py
df = pd.read_csv("data/telco_churn.csv")
df.shape                                                  # (7043, 21)
(df["TotalCharges"].str.strip() == "").sum()              # 11, todos con tenure = 0
df["TotalCharges"] = pd.to_numeric(df["TotalCharges"], errors="coerce").fillna(0.0)
df["Churn"] = (df["Churn"] == "Yes").astype(int)
df["Churn"].mean()                                        # 0.265

03Exploración

Antes de entrenar nada: ¿qué clientes se van más? La tasa global es del 26,5 %, pero cambia muchísimo según el grupo.

Mes a mes42,7 %
Un año11,3 %
Dos años2,8 %
Tasa de bajas por tipo de contrato. Sin permanencia, casi la mitad se va. Es la señal más clara del dataset.
0–6 meses52,9 %
7–12 meses35,9 %
13–24 meses28,7 %
25–48 meses20,4 %
49–72 meses9,5 %
Tasa de bajas por antigüedad. Los primeros meses son críticos: más de la mitad de los clientes de 0 a 6 meses se va.
VariableGrupo con más bajasGrupo con menos bajas
InternetFibra óptica: 41,9 %Sin internet: 7,4 %
Método de pagoCheque electrónico: 45,3 %Tarjeta automática: 15,2 %
Soporte técnicoSin soporte: 41,6 %Con soporte: 15,2 %
EdadMayores de 65: 41,7 %Resto: 23,6 %
FacturaElectrónica: 33,6 %En papel: 16,3 %

Dos avisos para las etapas siguientes: tenure y TotalCharges están muy correlacionadas (r = 0,83), porque el total es aproximadamente la cuota por los meses; y la fibra tiene más bajas, pero también es más cara. Muchas variables cuentan la misma historia, y eso complicará leer los coeficientes del modelo.

¿Correlación o causa? Los clientes con fibra se van más, pero eso no significa que la fibra provoque bajas: puede deberse a su precio o a la competencia en esas zonas. Lo explica el nivel 3 de estadística.

04Preprocesado

  • División estratificada 80/20 antes de mirar nada más: 5.634 clientes para entrenar y 1.409 para el test final, ambos con un 26,5 % de bajas. El test se guarda bajo llave hasta el final.
  • Numéricas (antigüedad, cuota, total): estandarizadas.
  • Categóricas (16 columnas): one-hot; las de sí/no se quedan en una sola columna. En total, 40 columnas.
  • Todo dentro de un Pipeline: el escalado y la codificación se aprenden solo con los datos de entrenamiento de cada pliegue, sin fugas de información del test.
notebooks/churn_telco.py
num = ["tenure", "MonthlyCharges", "TotalCharges"]
cat = [c for c in X.columns if c not in num]
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42)

prep = ColumnTransformer([
    ("num", StandardScaler(), num),
    ("cat", OneHotEncoder(handle_unknown="ignore", drop="if_binary"), cat),
])

05Modelos candidatos

Cuatro candidatos, de menos a más complejo, comparados con validación cruzada estratificada de 5 pliegues sobre el conjunto de entrenamiento. La línea base siempre predice «se queda» y sirve para saber de dónde partimos.

ModeloROC-AUC (5 pliegues)PR-AUC
Línea base0,5000,265
Regresión logística0,846 ± 0,0130,662
Random Forest (400 árboles)0,844 ± 0,0100,658
XGBoost (300 árboles, profundidad 4)0,844 ± 0,0090,660

Resultado importante: los tres modelos reales empatan. La diferencia entre ellos (0,002) es mucho menor que lo que varía cada uno de un pliegue a otro (±0,01). En datos tabulares pequeños y con relaciones casi lineales, un modelo sencillo suele bastar. Los artículos de regresión logística, Random Forest y gradient boosting explican cada uno.

06Optimización

Búsqueda en rejilla con la misma validación cruzada:

ModeloHiperparámetros probadosMejoresROC-AUC
Regresión logísticaC ∈ {0,01; 0,03; 0,1; 0,3; 1; 3}C = 3 (poca regularización)0,846
XGBoostárboles {200, 400} · tasa {0,02; 0,05} · profundidad {2, 3, 4}400 árboles, tasa 0,02, profundidad 20,850

Afinar XGBoost gana 0,006 de AUC, y el ganador usa árboles de profundidad 2, casi lineales: otra pista de que el problema no necesita un modelo muy complejo.

07Evaluación en test

El conjunto de test se usa una sola vez, con los dos finalistas:

ModeloROC-AUCPR-AUCPrecisión (umbral 0,5)Recall (umbral 0,5)
Regresión logística0,8420,63166,1 %56,4 %
XGBoost0,8450,66266,2 %51,3 %

Rinden prácticamente igual. Nos quedamos con la regresión logística: da probabilidades bien calibradas (Brier 0,138), se explica fácilmente al equipo comercial y se despliega en unos pocos kilobytes.

¿Sirve para ordenar a los clientes?

Decil 1 (más riesgo)73,8 %
Decil 258,2 %
Decil 340,4 %
Decil 434 %
Decil 524,8 %
Decil 616,4 %
Decil 711,3 %
Decil 83,5 %
Decil 91,4 %
Decil 101,4 %
Clientes de test ordenados por probabilidad de baja y partidos en 10 grupos. En el 10 % de más riesgo se va el 73,8 %; en el de menos, el 1,4 %.

El 20 % de clientes con más riesgo contiene casi la mitad de todas las bajas (49,7 %). Esa capacidad de ordenar es lo que mide el ROC-AUC y lo que hace útil al modelo.

Con el umbral por defecto (0,5)

Predice «se queda»Predice «se va»
Se quedó927 ✓108 (falsa alarma)
Se fue163 (baja no detectada)211 ✓

Se escapan 163 de las 374 bajas del test. ¿Es el umbral correcto? Depende de lo que cueste cada error.

08Umbral de negocio

El 0,5 es arbitrario. El umbral correcto sale de comparar costes. Con estos supuestos (cámbialos por los de tu empresa en el cuaderno):

  • Contactar a un cliente con una oferta cuesta 50 $.
  • De los clientes que iban a irse, la oferta retiene al 30 %.
  • Un cliente retenido conserva 12 meses de su cuota.

Llamar a quien no se iba a ir cuesta 50 $; no llamar a quien se va cuesta, de media, un 30 % de un año de cuota. Los dos errores no valen lo mismo, y la curva de beneficio lo refleja:

0 k$20 k$40 k$60 k$0,050,100,150,200,250,300,350,400,450,500,550,600,650,700,750,800,85umbral de probabilidad para contactaróptimo 0,250,5
Beneficio de la campaña sobre los 1.409 clientes de test según el umbral. El óptimo se eligió con predicciones de validación cruzada del entrenamiento (no con el test), y en test también es el mejor.
EstrategiaClientes contactadosBajas alcanzadasBeneficio
No hacer nada000 $
Contactar a todos1.40937427.524 $
Modelo, umbral 0,531921143.841 $
Modelo, umbral 0,2561030451.954 $
Predice «se queda»Predice «se va»
Se quedó729 ✓306 (falsa alarma)
Se fue70 (baja no detectada)304 ✓

Con el umbral 0,25 se detecta el 81,3 % de las bajas, a cambio de que la mitad de las llamadas sean a clientes que no se iban a ir (precisión 49,8 %). Con estos costes compensa: el beneficio es un 18 % mayor que con 0,5 y casi el doble que llamando a todos.

Ojo: el 30 % de éxito es un supuesto. Antes de lanzar la campaña a gran escala hay que medirlo con un experimento: ofrecer la promoción a un grupo al azar de clientes de riesgo y compararlo con otro grupo al que no se le ofrece (un test A/B, como en el nivel 3 de estadística).

09Interpretación

¿Qué pesa más en la predicción? La importancia por permutación desordena una variable cada vez y mide cuánto empeora el ROC-AUC en test:

Antigüedad−0,176
Cuota mensual−0,085
Tipo de internet−0,078
Tipo de contrato−0,034
Total facturado−0,016
Películas en streaming−0,011
TV en streaming−0,010
Caída del ROC-AUC al desordenar cada variable. La antigüedad es, con diferencia, la más informativa.

Cuidado al leer los coeficientes. En el modelo completo, la cuota mensual tiene un coeficiente negativo (−1,32), como si pagar más retuviera. No es así: la cuota es casi la suma de los servicios contratados, que también están en el modelo, y la fibra sola ya suma +0,90. Cuando las variables se solapan (colinealidad), los coeficientes se reparten el efecto de formas poco intuitivas. La importancia por permutación y la exploración de la etapa 3 son más fiables para explicar el modelo.

Traducción para el equipo comercial: el riesgo se concentra en clientes nuevos, sin permanencia, con fibra, sin servicios de seguridad o soporte y que pagan con cheque electrónico. Acciones posibles: ofrecer permanencia con descuento en los primeros meses, incluir soporte técnico en la oferta y facilitar el paso a domiciliación.

10Calculadora de riesgo

Prueba un modelo simplificado con 7 variables clave (ROC-AUC en test 0,835, frente a 0,842 del completo). Se calcula en tu navegador con los coeficientes del modelo entrenado.

–probabilidad de baja

La marca de la barra es el umbral de 0,25 de la etapa 8. Sin internet, la seguridad y el soporte no se aplican y se ignoran.

11Despliegue

El modelo (el Pipeline completo, con el preprocesado incluido) se guarda con joblib y se sirve con una API de FastAPI. Pydantic valida cada petición: un tipo de contrato mal escrito devuelve un error 422 en vez de una predicción absurda.

notebooks/api/churn_api.py
@app.post("/prediccion", response_model=Prediccion)
def prediccion(cliente: Cliente):
    p = float(MODELO.predict_proba(pd.DataFrame([cliente.model_dump()]))[0, 1])
    return Prediccion(probabilidad_baja=round(p, 4), contactar=p >= UMBRAL,
                      umbral=UMBRAL, version_modelo=VERSION)
Respuestas reales de la API (probada con TestClient)
POST /prediccion   {"tenure": 3, "Contract": "Month-to-month", "InternetService": "Fiber optic", ...}
→ {"probabilidad_baja": 0.689, "contactar": true, "umbral": 0.25, "version_modelo": "churn-logistica-1.0"}

POST /prediccion   {"tenure": 60, "Contract": "Two year", "TechSupport": "Yes", ...}
→ {"probabilidad_baja": 0.0419, "contactar": false, ...}

En producción

  • Uso por lotes: la campaña no necesita tiempo real. Un proceso nocturno puntúa a todos los clientes y deja la lista priorizada en el CRM.
  • Monitorización: vigilar cada mes la distribución de las variables (deriva de datos) y comparar la tasa real de bajas por decil con la predicha.
  • Reentrenamiento trimestral, o cuando cambien las tarifas: el modelo aprendió con los precios y las ofertas de un momento concreto.
  • Retroalimentación: los clientes contactados cambian su comportamiento, así que hay que guardar quién recibió oferta para no contaminar el siguiente entrenamiento.

12Resultados y conclusiones

Resultado
Modelo elegidoRegresión logística (C = 3) con preprocesado en Pipeline
Calidad en testROC-AUC 0,842 · PR-AUC 0,631
OrdenaciónEl 20 % de más riesgo concentra el 49,7 % de las bajas
DecisiónContactar si la probabilidad ≥ 0,25: se alcanza el 81,3 % de las bajas
Impacto estimado51.954 $ en 1.409 clientes (unos 36.900 $ por cada 1.000), con los supuestos de la etapa 8
DespliegueAPI FastAPI validada con Pydantic; puntuación por lotes nocturna

Lo que enseña este proyecto

  1. Un modelo sencillo bien evaluado vale más que uno complejo sin medir. Regresión logística, Random Forest y XGBoost empatan.
  2. El umbral es una decisión de negocio, no técnica. Cambiarlo de 0,5 a 0,25 aporta más que cualquier ajuste de hiperparámetros.
  3. Explicar con cuidado. Las variables solapadas hacen engañosos los coeficientes: hay que validarlos con la exploración y la importancia por permutación.
  4. Predecir no es actuar. El valor real de la campaña solo se sabe con un experimento controlado.

Código completo, ejecutable en Colab con los datos incluidos: