Los dos modelos anteriores comparten una limitación de fondo: su frontera de decisión es un hiperplano — un corte recto en el espacio de features. Pero el churn de MercaFresh no siempre se deja cortar en línea recta: un cliente abandona si lleva mucho sin comprar y además su tendencia se desploma, o si es de plan básico y su gasto cae, y esas reglas con "y" y "o" dibujan fronteras escalonadas. Los árboles de decisión atacan el problema como lo haría un analista humano: encadenando preguntas. En esta lección verás cómo un algoritmo decide qué preguntar y en qué orden (impureza de Gini y entropía, con las cuentas hechas a mano), entrenarás y dibujarás un árbol sobre el churn de MercaFresh, y entenderás por qué este modelo tan interpretable es también un memorizador compulsivo si no se le pone freno.
Contenido
- La intuición: clasificar preguntando
- Cómo se elige cada división: impureza de Gini
- Entropía y ganancia de información
- Árboles de regresión (breve)
- Implementación con scikit-learn: churn de MercaFresh
- Interpretabilidad: reglas legibles y feature importance
- Hiperparámetros clave y la tendencia al sobreajuste
- Ventajas y limitaciones
La intuición: clasificar preguntando
Un árbol de decisión es una cascada de preguntas binarias sobre las features. Cada cliente entra por la raíz, responde preguntas y desciende hasta una hoja, que emite la predicción:
flowchart TD
A{"recencia_dias <= 45?"} -- "Si" --> B{"tendencia >= 0.8?"}
A -- "No" --> C{"pedidos_por_mes <= 1.5?"}
B -- "Si" --> D["Hoja: SE QUEDA<br/>(230 clientes, 96% fieles)"]
B -- "No" --> E["Hoja: RIESGO<br/>(45 clientes, 60% churn)"]
C -- "Si" --> F["Hoja: CHURN<br/>(180 clientes, 91% churn)"]
C -- "No" --> G{"plan = basico?"}
G -- "Si" --> H["Hoja: CHURN<br/>(90 clientes, 74% churn)"]
G -- "No" --> I["Hoja: RIESGO<br/>(55 clientes, 52% churn)"]
Observa tres cosas:
- Cada camino raíz→hoja es una regla de negocio legible: "si el cliente lleva más de 45 días sin comprar y hace ≤1.5 pedidos/mes, predice churn (91% de los históricos de esa hoja abandonaron)".
- Las hojas guardan proporciones, no solo etiquetas: el árbol también da probabilidades (la fracción de churn en la hoja).
- La frontera resultante es escalonada: cada pregunta corta el espacio con un plano perpendicular a un eje; la combinación forma regiones rectangulares. Es un modelo genuinamente no lineal sin necesidad de transformar features.
La pregunta del millón: dado el dataset, ¿qué pregunta poner en la raíz? ¿recencia <= 45 o gasto <= 20? ¿Y por qué 45 y no 60? El algoritmo necesita un criterio numérico para comparar preguntas candidatas.
Cómo se elige cada división: impureza de Gini
La idea: una buena pregunta separa a los clientes en grupos lo más puros posible — grupos donde casi todos son churn o casi todos son fieles. La impureza de Gini mide cuán mezclado está un grupo:
$$Gini = 1 - \sum_{k} p_k^2$$
donde $p_k$ es la proporción de cada clase en el grupo. Para dos clases:
| Composición del grupo | Cálculo | Gini | Lectura |
|---|---|---|---|
| 100% churn | $1 - 1^2 - 0^2$ | 0.0 | Puro: perfecto |
| 90% / 10% | $1 - 0.81 - 0.01$ | 0.18 | Casi puro |
| 50% / 50% | $1 - 0.25 - 0.25$ | 0.50 | Máxima mezcla: inútil |
Cálculo a mano completo. Nodo con 10 clientes de MercaFresh: 4 churn, 6 fieles. Gini inicial: $1 - 0.4^2 - 0.6^2 = 1 - 0.16 - 0.36 = 0.48$. Candidata: recencia_dias <= 60.
| Clientes | Churn | Fieles | Gini del grupo | |
|---|---|---|---|---|
| recencia ≤ 60 (izquierda) | 6 | 1 | 5 | $1 - (1/6)^2 - (5/6)^2 = 0.278$ |
| recencia > 60 (derecha) | 4 | 3 | 1 | $1 - (3/4)^2 - (1/4)^2 = 0.375$ |
Gini tras dividir = media ponderada por tamaño: $\frac{6}{10} \cdot 0.278 + \frac{4}{10} \cdot 0.375 = 0.167 + 0.150 = 0.317$.
La división reduce la impureza de 0.48 a 0.317: una ganancia de 0.163. El algoritmo repite este cálculo para cada feature y cada punto de corte posible, elige la división de mayor ganancia, y recurre sobre cada grupo hijo hasta que los nodos sean puros o se alcance un límite. Este procedimiento voraz (elegir siempre lo mejor localmente, sin mirar atrás) se llama CART y es el que implementa scikit-learn.
Entropía y ganancia de información
El criterio alternativo clásico viene de la teoría de la información. La entropía mide la incertidumbre de un grupo:
$$H = -\sum_{k} p_k \log_2 p_k$$
Con el mismo nodo de antes (4 churn, 6 fieles): $H = -0.4 \log_2 0.4 - 0.6 \log_2 0.6 = 0.529 + 0.442 = 0.971$ bits — casi la incertidumbre máxima (1 bit, la de un 50/50). Un grupo puro tiene entropía 0: no hay nada que adivinar. La ganancia de información de una división es la reducción de entropía, calculada con la misma media ponderada que hicimos con Gini.
¿Gini o entropía? En la práctica dan árboles casi idénticos:
| Criterio | Rango (2 clases) | Coste de cálculo | Uso |
|---|---|---|---|
| Gini | 0 – 0.5 | Menor (sin logaritmos) | Por defecto en sklearn |
| Entropía | 0 – 1 bit | Algo mayor | criterion="entropy" |
Quédate con la idea común: dividir es comprar pureza, y el árbol siempre compra donde la ganancia es máxima.
Árboles de regresión (breve)
El mismo mecanismo predice números: para el gasto mensual de 04-01, cada hoja predice la media del objetivo entre sus clientes, y la "impureza" a reducir es la varianza (el MSE dentro del nodo). La pregunta pedidos_por_mes <= 3.2 es buena si separa a los clientes en dos grupos de gastos internamente parecidos. DecisionTreeRegressor implementa esto con la misma interfaz. La predicción resultante es una función escalonada — constante dentro de cada región rectangular — lo que hace a estos árboles malos extrapoladores pero buenos capturadores de saltos y umbrales.
Implementación con scikit-learn: churn de MercaFresh
Reutilizamos el dataset de churn de la lección anterior (el df de 04-02). Un detalle liberador que retoma la tabla de 03-05: los árboles comparan cada feature consigo misma (recencia <= 45 no cambia si la recencia está en días o escalada), así que no necesitan escalado ni desasimetrización. Basta imputar nulos y codificar categóricas:
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder
from sklearn.tree import DecisionTreeClassifier, plot_tree, export_text
import matplotlib.pyplot as plt
num_cols = ["edad", "satisfaccion", "recencia_dias", "pedidos_por_mes",
"gasto_medio_pedido", "ratio_inactividad", "tendencia"]
# Preprocesador minimo para arboles: sin escalar, sin Yeo-Johnson (03-05)
prep_arbol = ColumnTransformer([
("num", SimpleImputer(strategy="median", add_indicator=True), num_cols),
("cat", OneHotEncoder(sparse_output=False, handle_unknown="ignore"), ["ciudad"]),
("ord", OrdinalEncoder(categories=[["basico", "estandar", "premium"]]), ["plan"]),
])
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42)
arbol = Pipeline([
("prep", prep_arbol),
("modelo", DecisionTreeClassifier(max_depth=3, min_samples_leaf=20,
random_state=42)),
])
arbol.fit(X_train, y_train)
print(f"Accuracy en test: {arbol.score(X_test, y_test):.2%}")
# Dibujar el arbol entrenado
nombres = arbol.named_steps["prep"].get_feature_names_out()
plt.figure(figsize=(16, 8))
plot_tree(arbol.named_steps["modelo"], feature_names=nombres,
class_names=["fiel", "churn"], filled=True, rounded=True)
plt.show()Lectura del código:
max_depth=3: como mucho 3 preguntas encadenadas — un árbol que cabe en una diapositiva. Enseguida veremos por qué limitar la profundidad no es opcional.min_samples_leaf=20: ninguna hoja puede quedarse con menos de 20 clientes; evita reglas basadas en anécdotas.plot_treepinta cada nodo con su pregunta, su Gini, cuántas muestras contiene y su reparto de clases — la teoría de los apartados 2 y 3, dibujada. Colores más intensos = nodos más puros.random_state=42: los empates entre divisiones igual de buenas se rompen al azar; fijar la semilla hace el árbol reproducible.
Interpretabilidad: reglas legibles y feature importance
Dos salidas del árbol valen oro en una reunión de negocio. La primera, las reglas en texto plano:
Cualquier responsable de retención entiende eso sin saber qué es un Gini. La segunda, la importancia de cada feature: cuánta reducción total de impureza aportaron las divisiones que la usan:
import pandas as pd
importancias = pd.Series(arbol.named_steps["modelo"].feature_importances_,
index=nombres).sort_values(ascending=False)
print(importancias.head(5).round(3))Si recencia_dias y tendencia dominan el ranking, el modelo confirma la tesis de negocio de 03-06: el churn se anuncia con silencio y enfriamiento. Cautela: las importancias suman 1 y se reparten entre features correlacionadas de forma algo arbitraria (mismo aviso que con los coeficientes de 04-01/04-02), y este método tiende a favorecer features con muchos valores distintos.
Hiperparámetros clave y la tendencia al sobreajuste
Aquí está el lado oscuro. Sin límites, el algoritmo divide hasta que cada hoja es pura — aunque para ello necesite una hoja por cliente. Ese árbol se sabe el entrenamiento de memoria: acierta el 100% en train y se desploma en test, porque sus últimas divisiones no capturan patrones sino ruido individual. Es el fenómeno de sobreajuste (overfitting), que diagnosticaremos con rigor en 06-05; los árboles son su ejemplo de manual.
sin_freno = Pipeline([("prep", prep_arbol),
("modelo", DecisionTreeClassifier(random_state=42))])
sin_freno.fit(X_train, y_train)
print(f"Train: {sin_freno.score(X_train, y_train):.2%}"
f" | Test: {sin_freno.score(X_test, y_test):.2%}")
# Tipico: Train: 100.00% | Test: bastante peor que el arbol podadoLos frenos (hiperparámetros de poda previa):
| Hiperparámetro | Qué limita | Efecto de apretarlo |
|---|---|---|
max_depth |
Preguntas encadenadas máximas | Árbol más simple y general, riesgo de quedarse corto |
min_samples_leaf |
Tamaño mínimo de hoja | Prohíbe reglas anecdóticas |
min_samples_split |
Tamaño mínimo para dividir un nodo | Similar, actúa antes |
ccp_alpha |
Poda posterior por coste-complejidad | Poda ramas que aportan poco |
Elegir estos valores sistemáticamente es materia de la optimización de hiperparámetros (07-05). Y un adelanto que explica media industria del ML: la mejor cura del sobreajuste de un árbol no es podarlo con más saña, sino promediar muchos árboles distintos — los ensembles tipo Random Forest (07-02) y el gradient boosting (07-03) nacen exactamente de ahí. En este curso, el árbol individual es la pieza; allí verás el edificio.
Ventajas y limitaciones
| Ventajas | Limitaciones |
|---|---|
| Interpretable: reglas legibles y dibujables | Sobreajusta con facilidad si no se poda |
| No necesita escalado ni transformaciones de forma (03-05) | Inestable: pequeños cambios en los datos pueden cambiar todo el árbol |
| Captura no linealidad e interacciones sin ingeniería previa | Fronteras solo perpendiculares a los ejes (las diagonales le cuestan escalones) |
| Maneja features numéricas y ordinales con naturalidad | Extrapola mal en regresión (predicción escalonada, plana fuera del rango) |
| Rápido en predicción | Un árbol solo rara vez es el modelo más preciso disponible |
Errores Comunes y Consejos
- Entrenar sin límites y presumir del 100% en train. Ese número no mide aprendizaje sino memoria. Compara siempre train contra test; una brecha grande es la alarma de sobreajuste (06-05).
- Escalar las features "por si acaso" con Pipeline compartido. No rompe nada, pero destroza la interpretabilidad: la regla
recencia <= 0.83(en unidades robustas) no la entiende nadie. Para árboles, deja las features en sus unidades naturales. - Tomar el árbol dibujado como verdad estable. Reentrenar con un 5% más de datos puede reorganizar ramas enteras. Las importancias suelen ser más estables que la estructura; las conclusiones de negocio, básate en ellas.
- Leer
feature_importances_como causalidad. Es reducción de impureza, no efecto causal — la misma cautela de 02-03 que arrastramos desde los coeficientes. - Consejo: empieza siempre con un árbol pequeño (
max_depth=3) y dibújalo. Aunque el modelo final sea otro, ese dibujo es la mejor herramienta de exploración y comunicación del proyecto: te dice qué features cortan y por dónde.
Ejercicios
Ejercicio 1. A mano: un nodo tiene 8 clientes (4 churn, 4 fieles). La división A separa en (3 churn, 1 fiel) y (1 churn, 3 fieles); la división B separa en (4 churn, 2 fieles) y (0 churn, 2 fieles). Calcula el Gini ponderado tras cada división y decide cuál elegiría el algoritmo.
Ejercicio 2. Entrena el árbol del churn con max_depth de 1 a 12 y dibuja accuracy en train y en test frente a la profundidad. Describe las tres zonas de la curva y localiza la profundidad razonable.
Ejercicio 3. Extrae con export_text la regla completa del camino que lleva a la hoja con mayor proporción de churn del árbol de max_depth=3, y tradúcela a una frase que pudiera aparecer en un informe para el equipo de retención de MercaFresh.
Soluciones
Ejercicio 1
Gini inicial: $1 - 0.5^2 - 0.5^2 = 0.5$.
- División A: cada hijo tiene Gini $1 - (3/4)^2 - (1/4)^2 = 0.375$. Ponderado: $\frac{4}{8}(0.375) + \frac{4}{8}(0.375) = 0.375$.
- División B: hijo izquierdo $1 - (4/6)^2 - (2/6)^2 = 0.444$; hijo derecho $1 - 0 - 1 = 0$ (puro). Ponderado: $\frac{6}{8}(0.444) + \frac{2}{8}(0) = 0.333$.
Gana B (0.333 < 0.375): aunque deja un hijo bastante mezclado, fabrica un nodo completamente puro, y al criterio le compensa. Lección: el algoritmo valora la pureza ponderada total, no el reparto equilibrado.
Ejercicio 2
import matplotlib.pyplot as plt
profundidades = range(1, 13)
acc_train, acc_test = [], []
for d in profundidades:
m = Pipeline([("prep", prep_arbol),
("modelo", DecisionTreeClassifier(max_depth=d, random_state=42))])
m.fit(X_train, y_train)
acc_train.append(m.score(X_train, y_train))
acc_test.append(m.score(X_test, y_test))
plt.plot(profundidades, acc_train, marker="o", label="train")
plt.plot(profundidades, acc_test, marker="s", label="test")
plt.xlabel("max_depth"); plt.ylabel("accuracy"); plt.legend(); plt.show()Tres zonas: (1) profundidades 1-2, ambas curvas bajas — el árbol es demasiado simple para el patrón (underfitting); (2) zona intermedia (típicamente 3-5), test alcanza su máximo; (3) a partir de ahí train sigue subiendo hacia el 100% mientras test se estanca o cae — el árbol memoriza ruido (overfitting). La profundidad razonable es la del máximo en test. Esta curva en U invertida es el retrato del compromiso sesgo-varianza que formalizaremos en 06-05.
Ejercicio 3
Localiza la hoja con class: churn y mayor pureza y encadena sus condiciones. Con los datos simulados, un resultado típico: recencia_dias > 52.5 y tendencia <= 0.74 y pedidos_por_mes <= 2.1. Traducción de informe: "El segmento de mayor riesgo son los clientes con más de 52 días sin comprar cuya actividad reciente es menos de tres cuartas partes de la habitual y que hacen dos pedidos al mes o menos: históricamente, la gran mayoría de estos clientes acaba abandonando. Recomendamos priorizarlos en la campaña de retención." La regla es accionable precisamente porque las features (03-06) se diseñaron con lectura de negocio.
Conclusión
Has añadido el primer modelo no lineal a tu caja de herramientas: el árbol clasifica encadenando preguntas, elige cada una comprando la máxima pureza (Gini o entropía — ya sabes hacer la cuenta a mano), se lee como reglas de negocio y ordena las features por importancia. También has visto su talón de Aquiles: sin max_depth y min_samples_leaf, memoriza en lugar de aprender — un anticipo del sobreajuste (06-05) y la motivación de los ensembles (07-02). Y una comodidad nueva: es el primer modelo del curso que no necesita escalado.
El árbol dibuja fronteras escalonadas, perpendiculares a los ejes. La siguiente lección ataca la geometría desde el ángulo opuesto: en lugar de trocear el espacio con preguntas, buscar directamente el mejor corte posible — el hiperplano que separa las clases con la máxima distancia de seguridad — y, cuando ningún corte recto baste, proyectar los datos a un espacio donde sí lo haya. Son las máquinas de soporte vectorial.
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
