Árbol de Decisión
Aprende cómo funcionan los árboles de decisión, cómo construir árboles de clasificación y regresión en Python con scikit-learn, ajustar hiperparámetros y visualizarlos.
Un árbol de decisión es un algoritmo de aprendizaje automático supervisado que realiza predicciones aprendiendo una jerarquía de reglas if-then-else a partir de datos de entrenamiento. Cada nodo interno evalúa una característica, cada rama representa un resultado de esa evaluación, y cada nodo hoja contiene una predicción (una etiqueta de clase para clasificación, o un valor numérico para regresión).
Este capítulo cubre:
- Cómo los árboles de decisión dividen los datos usando medidas de impureza (Gini y entropía)
- Construcción de un árbol de clasificación y un árbol de regresión en Python con
scikit-learn - Control de la profundidad del árbol y prevención del sobreajuste con hiperparámetros
- Visualización e inspección de un árbol entrenado
- Ventajas, limitaciones y cuándo usar árboles de decisión
Cómo un Árbol de Decisión Divide los Datos
Durante el entrenamiento, el algoritmo examina cada característica y cada umbral posible para encontrar la división que más reduce la impureza — una medida de cuán mezcladas están las clases en un nodo.
Dos medidas de impureza son comunes en scikit-learn:
Impureza de Gini
La impureza de Gini mide la probabilidad de clasificar incorrectamente una muestra elegida al azar si se etiquetara según la distribución de clases en el nodo.
Gini(node) = 1 - Σ pᵢ²Un nodo puro (todas las muestras pertenecen a una sola clase) tiene Gini = 0. Un nodo con máxima mezcla tiene un Gini que se aproxima a 0.5 para clasificación binaria.
Entropía y Ganancia de Información
La entropía proviene de la teoría de la información. Se maximiza cuando las clases están distribuidas de forma equitativa y es cero cuando el nodo es puro.
Entropy(node) = -Σ pᵢ log₂(pᵢ)La ganancia de información es la reducción de entropía después de una división. El algoritmo elige la división que produce la mayor ganancia de información. En scikit-learn, se puede elegir entre las dos opciones mediante el parámetro criterion ("gini" es el valor predeterminado).
División Recursiva
La división se repite de forma recursiva en cada nodo hijo hasta que se cumple una condición de parada: el nodo es puro, ninguna característica mejora la impureza, o se alcanza un límite de profundidad o tamaño. Esto produce la estructura de árbol binario.
Árbol de Clasificación en Python
El conjunto de datos Iris tiene 150 muestras y 4 características numéricas. El objetivo es predecir una de tres especies de flores.
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
# Load dataset
data = load_iris()
X, y = data.data, data.target
# Split: 80 % train, 20 % test
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# Train — limit depth to 3 to keep the tree readable
clf = DecisionTreeClassifier(criterion="gini", max_depth=3, random_state=42)
clf.fit(X_train, y_train)
# Evaluate
y_pred = clf.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, y_pred):.2f}")
print(classification_report(y_test, y_pred, target_names=data.target_names))Salida esperada:
Accuracy: 1.00
precision recall f1-score support
setosa 1.00 1.00 1.00 10
versicolor 1.00 1.00 1.00 9
virginica 1.00 1.00 1.00 11
accuracy 1.00 30
macro avg 1.00 1.00 1.00 30
weighted avg 1.00 1.00 1.00 30El conjunto de datos Iris es linealmente separable con profundidad 3, por lo que el árbol logra una precisión perfecta en el conjunto de prueba. Los conjuntos de datos del mundo real serán más complejos.
Predicción de Nuevas Muestras
Después del entrenamiento, llama a predict() para clasificar nuevas observaciones y a predict_proba() para obtener las probabilidades de cada clase:
import numpy as np
# A new flower: sepal length 5.1, sepal width 3.5, petal length 1.4, petal width 0.2
new_sample = np.array([[5.1, 3.5, 1.4, 0.2]])
predicted_class = clf.predict(new_sample)
predicted_proba = clf.predict_proba(new_sample)
print("Predicted class:", data.target_names[predicted_class[0]])
print("Class probabilities:", predicted_proba)Salida esperada:
Predicted class: setosa
Class probabilities: [[1. 0. 0.]]Árbol de Regresión en Python
Los árboles de decisión también manejan objetivos continuos. Usa DecisionTreeRegressor en lugar de DecisionTreeClassifier.
from sklearn.tree import DecisionTreeRegressor
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np
# Synthetic regression dataset
X_reg, y_reg = make_regression(
n_samples=300, n_features=5, noise=20, random_state=42
)
X_train_r, X_test_r, y_train_r, y_test_r = train_test_split(
X_reg, y_reg, test_size=0.2, random_state=42
)
reg = DecisionTreeRegressor(max_depth=5, random_state=42)
reg.fit(X_train_r, y_train_r)
y_pred_r = reg.predict(X_test_r)
mse = mean_squared_error(y_test_r, y_pred_r)
r2 = r2_score(y_test_r, y_pred_r)
print(f"MSE : {mse:.2f}")
print(f"R² : {r2:.2f}")Un árbol de regresión divide los datos minimizando el error cuadrático medio (MSE) dentro de cada nodo y predice el valor medio del objetivo de todas las muestras de entrenamiento que llegan a una hoja.
Ajuste de Hiperparámetros
Sin límites, un árbol de decisión crecerá hasta que cada hoja sea pura, memorizando perfectamente el conjunto de entrenamiento (sobreajuste). Los hiperparámetros controlan la complejidad del árbol:
| Parámetro | Predeterminado | Efecto |
|---|---|---|
max_depth | None | Número máximo de niveles. Menor = árbol más simple. |
min_samples_split | 2 | Mínimo de muestras requeridas para dividir un nodo. Mayor = menos divisiones. |
min_samples_leaf | 1 | Mínimo de muestras requeridas en una hoja. Mayor = fronteras más suaves. |
max_features | None | Número de características a considerar en cada división (útil para selección de características). |
criterion | "gini" | Medida de impureza: "gini" o "entropy" para clasificadores; "squared_error" para regresores. |
Usa validación cruzada y búsqueda en rejilla para encontrar la mejor combinación:
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import GridSearchCV
data = load_iris()
X, y = data.data, data.target
param_grid = {
"max_depth": [2, 3, 4, 5, None],
"min_samples_split": [2, 5, 10],
"criterion": ["gini", "entropy"],
}
grid_search = GridSearchCV(
DecisionTreeClassifier(random_state=42),
param_grid,
cv=5,
scoring="accuracy",
)
grid_search.fit(X, y)
print("Best params :", grid_search.best_params_)
print(f"Best CV score: {grid_search.best_score_:.3f}")Salida esperada (los valores pueden variar ligeramente según la versión de scikit-learn):
Best params : {'criterion': 'gini', 'max_depth': 3, 'min_samples_split': 2}
Best CV score: 0.973Manejo de Características Categóricas
Los árboles de decisión de scikit-learn requieren entrada numérica. Codifica las columnas categóricas antes del entrenamiento:
- Categorías ordinales (por ejemplo, tamaño: pequeño < mediano < grande): usa
OrdinalEncoder. - Categorías nominales (por ejemplo, color: rojo, verde, azul): usa
OneHotEncoderpara evitar implicar un orden.
from sklearn.preprocessing import OrdinalEncoder
import numpy as np
# Encode only the categorical column; keep the numeric column as-is
sizes = np.array([["small"], ["large"], ["medium"], ["large"]])
weights = np.array([1.2, 3.4, 2.1, 4.0])
# Explicit category order: large=0, medium=1, small=2
enc = OrdinalEncoder(categories=[["large", "medium", "small"]])
sizes_encoded = enc.fit_transform(sizes)
X_encoded = np.column_stack([sizes_encoded, weights])
print(X_encoded)Salida esperada:
[[2. 1.2]
[0. 3.4]
[1. 2.1]
[0. 4. ]]Consulta el capítulo de Datos Categóricos para un recorrido completo.
Visualización de un Árbol de Decisión
Inspeccionar la estructura del árbol revela qué características generan más divisiones y hace que el modelo sea auditable.
Representación en Texto
from sklearn.tree import DecisionTreeClassifier, export_text
from sklearn.datasets import load_iris
data = load_iris()
clf = DecisionTreeClassifier(max_depth=2, random_state=42)
clf.fit(data.data, data.target)
print(export_text(clf, feature_names=list(data.feature_names)))Salida esperada:
|--- petal length (cm) <= 2.45
| |--- class: 0
|--- petal length (cm) > 2.45
| |--- petal width (cm) <= 1.75
| | |--- class: 1
| |--- petal width (cm) > 1.75
| | |--- class: 2Gráfico Visual
import matplotlib.pyplot as plt
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.datasets import load_iris
data = load_iris()
clf = DecisionTreeClassifier(max_depth=2, random_state=42)
clf.fit(data.data, data.target)
plt.figure(figsize=(10, 5))
plot_tree(
clf,
feature_names=data.feature_names,
class_names=data.target_names,
filled=True,
rounded=True,
)
plt.title("Iris Decision Tree (max_depth=2)")
plt.tight_layout()
plt.savefig("iris_tree.png", dpi=150)
plt.show()filled=True colorea cada nodo según su clase mayoritaria; los tonos más oscuros indican mayor pureza de clase.
Importancia de las Características
Después del entrenamiento, feature_importances_ asigna a cada característica una puntuación entre 0 y 1, donde un valor mayor significa que la característica contribuyó más a reducir la impureza en todas las divisiones:
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
import numpy as np
data = load_iris()
clf = DecisionTreeClassifier(max_depth=3, random_state=42)
clf.fit(data.data, data.target)
importances = clf.feature_importances_
for name, imp in sorted(
zip(data.feature_names, importances), key=lambda x: x[1], reverse=True
):
print(f"{name:30s}: {imp:.4f}")Salida esperada:
petal length (cm) : 0.5856
petal width (cm) : 0.4144
sepal length (cm) : 0.0000
sepal width (cm) : 0.0000Las características con una importancia de 0 nunca fueron usadas en ninguna división y podrían eliminarse para simplificar el modelo.
Ventajas y Limitaciones
Cuándo usar árboles de decisión
- Necesitas un modelo interpretable — las reglas se pueden imprimir en lenguaje natural.
- Tu conjunto de datos contiene una mezcla de características numéricas y categóricas (después de la codificación).
- Quieres una línea base rápida antes de probar métodos de ensamblado.
- La relación entre características y objetivo es no lineal o involucra interacciones.
Limitaciones
| Limitación | Mitigación |
|---|---|
| Se sobreajusta fácilmente sin ajuste | Limitar max_depth, min_samples_leaf; usar validación cruzada |
| Alta varianza (pequeños cambios en los datos → árbol diferente) | Usar métodos de ensamblado: Random Forest / Bootstrap Aggregation |
| Sesgado hacia características con más valores únicos | Usar max_features o normalizar los criterios de división |
| Mal desempeño al extrapolar más allá del rango de entrenamiento | Preferir modelos lineales para tareas de extrapolación |
| Solo divisiones alineadas con los ejes | Existen árboles oblicuos pero no están en scikit-learn |
Árboles de Decisión vs. Algoritmos Relacionados
| Algoritmo | Diferencia clave |
|---|---|
| Regresión Logística | Frontera lineal; mejor para datos linealmente separables; no maneja interacciones automáticamente |
| K-Vecinos más Cercanos | Basado en instancias; sin modelo explícito; requiere escalado de características |
| Árbol de Decisión | No lineal; sin necesidad de escalado; altamente interpretable |
| Random Forest (ver Bootstrap Aggregation) | Ensamblado de muchos árboles; varianza mucho menor; menos interpretable |
Conclusiones Clave
- Los árboles de decisión dividen los datos maximizando la ganancia de información (o minimizando la impureza de Gini) en cada nodo; el proceso se repite de forma recursiva.
DecisionTreeClassifieryDecisionTreeRegressoren scikit-learn comparten la misma API y los mismos nombres de hiperparámetros.- Siempre establece
max_depthomin_samples_leafpara prevenir el sobreajuste; ajústalos con búsqueda en rejilla y validación cruzada. feature_importances_revela en qué características se basa más el árbol — útil para la selección de características.- Los árboles individuales son una buena línea base interpretable, pero los métodos de ensamblado como Random Forest casi siempre superan su rendimiento en datos del mundo real.