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

  1. La intuición: clasificar preguntando
  2. Cómo se elige cada división: impureza de Gini
  3. Entropía y ganancia de información
  4. Árboles de regresión (breve)
  5. Implementación con scikit-learn: churn de MercaFresh
  6. Interpretabilidad: reglas legibles y feature importance
  7. Hiperparámetros clave y la tendencia al sobreajuste
  8. 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_tree pinta 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:

print(export_text(arbol.named_steps["modelo"], feature_names=list(nombres)))
|--- num__recencia_dias <= 52.50
|   |--- num__tendencia >  0.74
|   |   |--- class: fiel
|   ...

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 podado

Los 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

print(export_text(arbol.named_steps["modelo"], feature_names=list(nombres)))

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

Módulo 2: Fundamentos de Estadística y Probabilidad

Módulo 3: Preprocesamiento de Datos

Módulo 4: Algoritmos de Machine Learning Supervisado

Módulo 5: Algoritmos de Machine Learning No Supervisado

Módulo 6: Evaluación y Validación de Modelos

Módulo 7: Técnicas Avanzadas y Optimización

Módulo 8: Implementación y Despliegue de Modelos

Módulo 9: Proyectos Prácticos

Módulo 10: Recursos Adicionales

© Copyright 2026. Todos los derechos reservados