Machine learning suena a magia negra. Redes neuronales, GPUs, tensores, gradientes. Todo muy intimidante. Pero la realidad es que el 80% de los problemas de ML en empresas se resuelven con modelos clásicos que caben en 30 líneas de código. Sin deep learning, sin clusters, sin GPU. Un random forest y datos decentes.
Vamos a construir un modelo que prediga si un cliente va a cancelar su suscripción (churn). Es un problema real que tienen todas las empresas con un modelo de suscripción: Netflix, Spotify, tu gimnasio. Y lo vamos a resolver con scikit-learn en menos líneas de las que tiene un email de empresa.
### El problema: predecir churn
Imagina que trabajas en una empresa SaaS. Tienes 5.000 clientes, y cada mes un 8% se da de baja. Tu jefe quiere saber QUIÉN se va a dar de baja el mes que viene para poder ofrecerles un descuento antes de que lo hagan. Eso es clasificación binaria: para cada cliente, predecir 0 (se queda) o 1 (se va).
### Los datos: qué necesitamos
Un modelo necesita dos cosas: features (las características de cada cliente que podrían predecir su comportamiento) y un target (lo que queremos predecir). En nuestro caso:
- Features (X): antigüedad en meses, gasto mensual, número de tickets de soporte, frecuencia de uso semanal, tipo de plan
- Target (y): churn — 1 si canceló en los últimos 30 días, 0 si no
1import pandas as pd2from sklearn.model_selection import train_test_split3from sklearn.ensemble import RandomForestClassifier4from sklearn.metrics import classification_report, accuracy_score56# --- 1. CARGAR DATOS ---7# En un caso real esto viene de tu data warehouse8# Aquí lo simulamos con make_classification9from sklearn.datasets import make_classification10import numpy as np1112np.random.seed(42)13X, y = make_classification(14 n_samples=5000,15 n_features=5,16 n_informative=3,17 n_redundant=1,18 weights=[0.92, 0.08], # 8% de churn, realista19 random_state=4220)2122# Darles nombre a las features para que tenga sentido23df = pd.DataFrame(X, columns=[24 'antiguedad_meses',25 'gasto_mensual',26 'tickets_soporte',27 'uso_semanal',28 'tipo_plan'29])30df['churn'] = y31print(f"Clientes: {len(df)} | Churn: {y.sum()} ({y.mean()*100:.1f}%)")
En producción, estos datos vienen de tu warehouse. Aquí los simulamos para que puedas ejecutar el código tal cual.
### El split: nunca evalúes con los datos de entrenamiento
La regla de oro del ML: separas los datos en dos grupos ANTES de entrenar. El modelo aprende con uno (train) y lo evalúas con el otro (test), que nunca ha visto. Es como estudiar con un examen y luego hacer un examen DIFERENTE. Si evalúas con los mismos datos que usaste para entrenar, el modelo "se sabe las respuestas" y la nota es mentira.
1# --- 2. SPLIT ---2X = df.drop('churn', axis=1)3y = df['churn']45X_train, X_test, y_train, y_test = train_test_split(6 X, y,7 test_size=0.2, # 20% para test8 random_state=42, # reproducibilidad9 stratify=y # mantener la proporción de churn en ambos10)1112print(f"Train: {len(X_train)} | Test: {len(X_test)}")13print(f"Churn en train: {y_train.mean()*100:.1f}%")14print(f"Churn en test: {y_test.mean()*100:.1f}%")
stratify=y es clave cuando las clases están desbalanceadas: asegura que el 8% de churn se mantiene en ambos conjuntos.
El error más común de principiantes: evaluar con los datos de entrenamiento y celebrar un 99% de accuracy. Eso no demuestra nada — el modelo se ha memorizado las respuestas. Siempre evalúa con datos que el modelo NO ha visto.
### Entrenar: una línea
Aquí viene la parte que parece que debería ser complicada y no lo es. Entrenar un modelo en scikit-learn es llamar a .fit(). Una línea. El random forest es un buen modelo por defecto: funciona bien sin tuning, maneja features de distinto tipo y no se deja engañar fácilmente por outliers.
1# --- 3. ENTRENAR ---2model = RandomForestClassifier(3 n_estimators=100, # 100 árboles4 random_state=425)6model.fit(X_train, y_train)78# Eso es todo. El modelo ya "sabe" los patrones.9print("Modelo entrenado ✓")
Una línea entrena 100 árboles de decisión y los combina. Eso es un random forest.
### Evaluar: las métricas que importan
Accuracy (% de aciertos totales) es la métrica más conocida, pero en problemas desbalanceados es engañosa. Si el 92% de tus clientes NO cancelan, un modelo que siempre dice "no cancela" tiene un 92% de accuracy sin haber aprendido nada. Lo que importa es: de los que dije que iban a cancelar, ¿cuántos acerté? (precision) y de los que realmente cancelaron, ¿cuántos pillé? (recall).
1# --- 4. EVALUAR ---2y_pred = model.predict(X_test)34print(f"Accuracy: {accuracy_score(y_test, y_pred):.3f}")5print()6print(classification_report(y_test, y_pred, target_names=['se queda', 'churn']))78# Salida típica:9# precision recall f1-score support10# se queda 0.97 0.99 0.98 92011# churn 0.82 0.65 0.73 8012# accuracy 0.96 1000
Precision: "de los que marqué como churn, ¿cuántos lo eran?". Recall: "de los que eran churn, ¿cuántos encontré?".
En churn, recall importa más que precision. Preferimos enviar un descuento a alguien que no iba a cancelar (falso positivo barato) que perder un cliente sin haberlo intentado (falso negativo caro). Ajusta el threshold de predicción para subir recall aunque baje precision.
### Interpretar: qué ha aprendido el modelo
Una ventaja del random forest sobre las redes neuronales: puedes ver qué features importan. Esto es oro para el equipo de negocio — no solo predices, sino que explicas POR QUÉ un cliente tiene riesgo.
1# --- BONUS: importancia de features ---2importancias = pd.Series(3 model.feature_importances_,4 index=X.columns5).sort_values(ascending=False)67print("\nImportancia de cada feature:")8print(importancias.to_string())910# Ejemplo de output:11# uso_semanal 0.3512# antiguedad_meses 0.2513# gasto_mensual 0.2014# tickets_soporte 0.1515# tipo_plan 0.05
feature_importances_ te dice qué variables pesan más en la decisión. Aquí "uso_semanal" es la clave.
### El código completo: 30 líneas
1"""Modelo de churn — versión completa en 30 líneas."""2import pandas as pd3import numpy as np4from sklearn.datasets import make_classification5from sklearn.model_selection import train_test_split6from sklearn.ensemble import RandomForestClassifier7from sklearn.metrics import classification_report89# 1. Datos10X, y = make_classification(n_samples=5000, n_features=5,11 n_informative=3, weights=[0.92, 0.08], random_state=42)12df = pd.DataFrame(X, columns=['antiguedad', 'gasto', 'tickets', 'uso', 'plan'])13df['churn'] = y1415# 2. Split16X_train, X_test, y_train, y_test = train_test_split(17 df.drop('churn', axis=1), df['churn'],18 test_size=0.2, stratify=df['churn'], random_state=42)1920# 3. Entrenar21model = RandomForestClassifier(n_estimators=100, random_state=42)22model.fit(X_train, y_train)2324# 4. Evaluar25y_pred = model.predict(X_test)26print(classification_report(y_test, y_pred, target_names=['stay', 'churn']))2728# 5. Interpretar29print(pd.Series(model.feature_importances_,30 index=X_train.columns).sort_values(ascending=False))
30 líneas. Sin magia, sin GPU, sin TensorFlow. Esto resuelve el 80% de los problemas de clasificación en empresa.
### Qué viene después
Este es el flujo básico. En un proyecto real tendrás que hacer feature engineering (crear mejores variables), cross-validation (evaluar de forma más robusta), hyperparameter tuning (ajustar los parámetros del modelo) y pensar en cómo servir el modelo en producción. Pero el esqueleto siempre es el mismo: datos → split → entrenar → evaluar.
Si te ha picado la curiosidad, el siguiente paso natural es probar con datos reales de tu empresa o con datasets públicos de Kaggle. Cambia el modelo (LogisticRegression, GradientBoosting), cambia las features, ajusta el threshold. El aprendizaje de verdad viene de experimentar, no de leer.
Consejo de veterano: antes de complicar el modelo, complica los datos. Feature engineering bueno con un random forest básico gana al mejor XGBoost con features malos. Primero invierte tiempo en entender el problema y crear variables que lo representen bien.