W3docs

Á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        30

El 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ámetroPredeterminadoEfecto
max_depthNoneNúmero máximo de niveles. Menor = árbol más simple.
min_samples_split2Mínimo de muestras requeridas para dividir un nodo. Mayor = menos divisiones.
min_samples_leaf1Mínimo de muestras requeridas en una hoja. Mayor = fronteras más suaves.
max_featuresNoneNú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.973

Manejo 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 OneHotEncoder para 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: 2

Grá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.0000

Las 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ónMitigación
Se sobreajusta fácilmente sin ajusteLimitar 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 únicosUsar max_features o normalizar los criterios de división
Mal desempeño al extrapolar más allá del rango de entrenamientoPreferir modelos lineales para tareas de extrapolación
Solo divisiones alineadas con los ejesExisten árboles oblicuos pero no están en scikit-learn

Árboles de Decisión vs. Algoritmos Relacionados

AlgoritmoDiferencia clave
Regresión LogísticaFrontera lineal; mejor para datos linealmente separables; no maneja interacciones automáticamente
K-Vecinos más CercanosBasado en instancias; sin modelo explícito; requiere escalado de características
Árbol de DecisiónNo 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.
  • DecisionTreeClassifier y DecisionTreeRegressor en scikit-learn comparten la misma API y los mismos nombres de hiperparámetros.
  • Siempre establece max_depth o min_samples_leaf para 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.
Was this page helpful?