logo

Introducción

Los árboles de decisión o de clasificación son modelos predictivos formados por reglas sencillas de la forma: “si… entonces…”, con las que se consigue repartir las observaciones en función de sus atributos y predecir así el valor de la variable respuesta.

Los métodos basados en árboles se han convertido en uno de los referentes dentro del ámbito predictivo debido a los buenos resultados que generan en problemas muy diversos.

Ventajas

  • Son fáciles de interpretar y visualizar aun cuando las relaciones entre predictores son complejas.

  • Los árboles pueden, manejar tanto predictores numéricos como categóricos sin tener que crear variables dummy.

  • Al tratarse de métodos no paramétricos, no es necesario que se cumpla ningún tipo de distribución específica.

  • Por lo general, requieren mucha menos limpieza y preprocesado de los datos en comparación con otros métodos de aprendizaje estadístico (por ejemplo, no requieren estandarización).

  • No se ven muy influenciados por outliers.

  • Son muy útiles en la exploración de datos, permiten identificar de forma rápida y eficiente las variables (predictores) más importantes.

  • Son capaces de seleccionar predictores de forma automática.

  • Pueden aplicarse a problemas de regresión y clasificación.

Desventajas

  • La capacidad predictiva de los modelos basados en un único árbol es bastante inferior a la conseguida con otros modelos. Esto es debido a su tendencia al sobreajuste y alta varianza. Sin embargo, existen técnicas más complejas que, haciendo uso de la combinación de múltiples árboles (bagging, random forest, boosting), consiguen mejorar en gran medida este problema.

  • Son sensibles a datos de entrenamiento desbalanceados.

  • Cuando tratan con predictores continuos, pierden parte de su información al categorizarlos en el momento de la división de los nodos.

  • No son capaces de extrapolar fuera del rango de los predictores observado en los datos de entrenamiento.

¿Cómo funciona Árboles de decisión?

Paso 1. Preparación inicial y limpieza de los datos:

Como en cualquier modelo supervisado, el primer paso es asegurar que los datos estén en condiciones adecuadas:

  • Cargar los datos: importa el conjunto de datos y asegúrate de identificar correctamente la variable respuesta y las variables predictoras.

  • Explorar los datos: analiza la distribución de las clases, la presencia de valores atípicos, datos faltantes o categorías con muy pocas observaciones.

  • Codificar variables categóricas: aunque algunos algoritmos pueden manejar variables categóricas directamente, es buena práctica revisar la codificación.

En esta etapa también puedes realizar análisis exploratorio para entender qué variables podrían tener mayor relación con la variable respuesta.

Paso 2: Dividir los datos en conjunto de entrenamiento y prueba:

Divide el conjunto total en dos subconjuntos de forma aleatoria:

  • Un conjunto de entrenamiento (aproximadamente 70-80%), que se usa para ajustar el modelo.

  • Un conjunto de prueba (20-30%) que no se usará en la construcción inicial del modelo y servirá para evaluar el desempeño del mismo.

Una división adecuada permite evaluar la capacidad predictiva real del árbol, evitando un sobreajuste (overfitting). La división del conjunto de datos en entrenamiento y prueba puede realizarse fácilmente en R utilizando la función createFolds() del paquete caret, que permite crear particiones aleatorias y balanceadas de los datos.

Paso 3: Construcción del árbol de decisión:

En un árbol de decisión se encuentran los siguientes componentes:

  • Nodos: son las variables de entrada.
  • Ramas: representan los posibles valores de las variables de entrada.
  • Hojas: son los posibles valores de la variable de salida.

Como primer elemento de un árbol de decisión tenemos el nodo raíz que va a representar la variable de mayor relevancia en el proceso de clasificación.

Existen diferentes algoritmos para realizar el árbol de decisión. Veamos a continuación, dos de los más usados.

Árbol CART

Genera árboles binarios (cada nodo se divide en dos ramas). Este modelo admite variables de entrada y de salida nominales, ordinales y continuas, por lo que se pueden resolver tanto problemas de clasificación como de regresión.

El algoritmo utiliza el índice de Gini para calcular la medida de impureza:

\[G(x_i) = \sum_{j=i}^{M_i} p(x_{ij})G(x_{ij})\]

  • \(x_{ij}\) es el atributo empleado para ramificar el árbol.

  • \(j\) es el número de clases.

  • \(p(x_{ij})\) es la probabilidad de que \(x_{i}\) tome su \(j\)-ésimo valor.

De manera general, lo que hace este algoritmo es encontrar la variable independiente que mejor separa nuestros datos en grupos. Esta mejor separación es expresada con una regla. A cada regla corresponde un nodo.

Una vez hecho esto, los datos son separados (particionados) en grupos a partir de la regla obtenida. Después, para cada uno de los grupos resultantes, repite el mismo proceso. Se busca la variable que mejor separa los datos en grupos, se obtiene una regla, y se separan los datos. Hacemos esto de manera recursiva hasta que nos es imposible obtener una mejor separación. Cuando esto ocurre, el algoritmo se detiene. Cuando un grupo no puede ser partido mejor, se le llama nodo terminal u hoja.

Una característica muy importante en este algoritmo es que una vez que alguna variable ha sido elegida para separar los datos, ya no es usada de nuevo en los grupos que ha creado. Se buscan variables distintas que mejoren la separación de los datos.

En R, se encuentra programado en la librería rpart() . También se encuentra programado la visualización del árbol, en la librería rpart.plot() .

Árbol C5.0

El algoritmo C5 es uno de los algoritmos más utilizados en el ámbito de los árboles de decisión. La forma de inferir árboles de decisión a través de este algoritmo es el resultado de la evolución del algoritmo C4.5 (Quinlan, 1993) diseñado por el mismo autor.

Este algoritmo crea modelos de árbol de clasificación, permitiendo sólo variables de salida categórica. Las variables de entrada pueden ser de naturaleza continua o categórica. El algoritmo construye el árbol de decisión de manera descendente y empieza preguntándose, ¿qué atributo es el que debería ser colocado en la raíz del árbol. Para resolver esta cuestión cada atributo es evaluado a través de un test estadístico que determina cómo clasifica él solo los casos de entrenamiento. Cuando se selecciona el mejor atributo éste es colocado en la raíz del árbol. Entonces una rama y su nodo se crea para cada valor posible del atributo en cuestión. Los casos de entrenamiento son repartidos en los nodos descendentes de acuerdo al valor que tengan para el atributo de la raíz.

El proceso se repite para seleccionar un atributo que será ahora colocado en cada uno de los nodos generados. Generalmente el algoritmo se detiene cuando los casos de entrenamiento comparten el mismo valor para el atributo que está siendo probado. Es decir la ganancia es cero.

En R, se encuentra programado en la librería C50() .

Paso 4: Validar la estabilidad del modelo

Para validar el modelo se usa Validación Cruzada (K-fold Cross-validation) Ahora, realiza una validación cruzada K-fold para comprobar la estabilidad del modelo. La forma más usual que se hace es:

  • Divide tu conjunto de entrenamiento en \(k\) grupos.
  • Entrena el modelo en \(K−1\) grupos y prueba en el restante.
  • Repite el proceso hasta que cada grupo haya sido usado para prueba exactamente una vez.

Calcula el rendimiento promedio en términos de las métricas anteriores.

Paso 5: Interpretación de los resultados finales:

Finalmente, se analizan tanto el rendimiento como la estructura del árbol:

  • Matriz de confusión promedio obtenida por la validación cruzada.
  • Indicadores de desempeño promedio: exactitud, sensibilidad, especificidad, etc.
  • Interpretación visual del árbol.
  • Propón posibles mejoras, tales como:

Además, se pueden proponer mejoras al modelo, como:

  • Ajustar el parámetro de complejidad para controlar el sobreajuste.

  • Realizar una poda adicional para simplificar la estructura.

  • Incorporar nuevas variables o eliminar las menos relevantes.

Ejemplo de motivación

Para el ejemplo de la figura, suponemos que en el conjunto de entrenamiento esta compuesto por 14 observaciones pertenecientes a dos clases. La clase 1, está conformada por 8 círculos de color verde y la clase 2, conformada por 6 círculos de color lila. El objetivo del modelo es clasificar correctamente los puntos de acuerdo con su color, utilizando divisiones basadas en las variables predictoras.

Para este ejemplo, se han realizado tres iteraciones (ramas del árbol).

Representación como árbol

A la derecha se ve cómo esas divisiones del espacio se transforman en un árbol de decisión:

  • El nodo raíz (arriba) divide los datos según la variable \(x\).
  • Luego, cada rama evalúa otra condición basada en \(y\).
  • Finalmente, las hojas representan grupos de puntos con la misma clase predominante.

Cada observación sigue un camino desde la raíz hasta una hoja, aplicando las reglas sucesivamente hasta llegar a una predicción.

Ejemplos en R

Ejemplo 1:

Solución paso a paso:

Paso 1

Preparación inicial y limpieza de los datos:

Para estos datos la exploración no es necesaria, pero cuando se tengan datos reales es muy importante realizarla.

head(iris)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1          5.1         3.5          1.4         0.2  setosa
## 2          4.9         3.0          1.4         0.2  setosa
## 3          4.7         3.2          1.3         0.2  setosa
## 4          4.6         3.1          1.5         0.2  setosa
## 5          5.0         3.6          1.4         0.2  setosa
## 6          5.4         3.9          1.7         0.4  setosa

Paso 2

Dividir los datos en conjunto de entrenamiento y prueba

Mediante un muestreo aleatorio, separamos el conjunto de entrenamiento y en conjunto de prueba. Supongamos en 5 grupos, 4 para entrenamiento y 1 para prueba.

set.seed(2025)
library(caret)
folds         <- createFolds(iris$Species, k = 5)
entrenamiento <- iris[-folds[[5]],]
prueba        <- iris[folds[[5]],]

Paso 3

Construcción del árbol de decisión

Construimos el modelo ajustado con los datos de entrenamiento. Para este ejemplo utilizaremos el árbol CART.

library(rpart)
modelo1 <- rpart(Species ~ ., data = entrenamiento)
modelo1
## n= 120 
## 
## node), split, n, loss, yval, (yprob)
##       * denotes terminal node
## 
## 1) root 120 80 setosa (0.33333333 0.33333333 0.33333333)  
##   2) Petal.Length< 2.45 40  0 setosa (1.00000000 0.00000000 0.00000000) *
##   3) Petal.Length>=2.45 80 40 versicolor (0.00000000 0.50000000 0.50000000)  
##     6) Petal.Width< 1.75 44  4 versicolor (0.00000000 0.90909091 0.09090909) *
##     7) Petal.Width>=1.75 36  0 virginica (0.00000000 0.00000000 1.00000000) *

Ahora, visualizamos el árbol de decisión

library(rpart.plot)     
rpart.plot(modelo1)

En este gráfico, cada uno de los rectángulos representa un nodo del árbol, con su regla de clasificación. Cada nodo está coloreado de acuerdo a la categoría mayoritaria entre los datos que agrupa. Esta es la categoría que ha predicho el modelo para ese grupo. Dentro del rectángulo de cada nodo se muestra qué proporción de casos pertenecen a cada categoría y la proporción del total de datos que han sido agrupados allí.

Paso 4

Validar la estabilidad del modelo

No se aplico en este ejemplo, pero se podría hacer la validación haciendo 5 replicas del proceso, tomando en cada iteración como conjunto de entrenamiento cada una de las particiones creadas.

Paso 5

Interpretación de los resultados finales

Podemos observar en la tabla, en las filas la clasificación real y en las columnas la predicción. La tasa de aciertos del modelo respecto a las observaciones de entrenamiento sería:

Podemos calcular la predicción para los datos de entrenamiento.

pred<- predict(modelo1,entrenamiento[,-5],type="class")
tt<- table(pred,entrenamiento[,5])
tt
##             
## pred         setosa versicolor virginica
##   setosa         40          0         0
##   versicolor      0         40         4
##   virginica       0          0        36
# Tasa de aciertos
TA <- sum(diag(tt))/sum(tt) 
paste0("Tasa de aciertos con los datos de entrenamiento: ", round(TA,4)*100, "%", sep="")
## [1] "Tasa de aciertos con los datos de entrenamiento: 96.67%"

Podemos hablar de una precisión de aproximadamente el \(97\%\). Ahora, para analizar la calidad del modelo, veamos que tan buena es la predicción para el conjunto de prueba.

Pred    <- predict(modelo1, prueba[,-5],type="class")
table   <- table(prueba[,5],Pred)
table
##             Pred
##              setosa versicolor virginica
##   setosa         10          0         0
##   versicolor      0          9         1
##   virginica       0          1         9
# Tasa de aciertos
TAP <- sum(diag(table))/sum(table) 
paste0("Tasa de aciertos con los datos de prueba: ", round(TAP,4)*100, "%", sep="")
## [1] "Tasa de aciertos con los datos de prueba: 93.33%"

Encontramos que el modelo logra predecir aproximadamente un \(93\%\) los datos de prueba.

Ejemplo 2:

Ahora, apliquemos el algoritmo C.5 para los mismos datos. Construimos el modelo ajustado con los datos de entrenamiento.

Solución paso a paso:

Paso 3

Construcción del árbol de decisión

library(C50)
modelo2 <- C5.0(Species ~ ., data = entrenamiento)
modelo2
## 
## Call:
## C5.0.formula(formula = Species ~ ., data = entrenamiento)
## 
## Classification Tree
## Number of samples: 120 
## Number of predictors: 4 
## 
## Tree size: 4 
## 
## Non-standard options: attempt to group attributes

Visualizamos el árbol de decisión

plot(modelo2)

Para detallar un nodo en particular, esta visualización permite:

plot(modelo2, subtree=3)  

Paso 5

Interpretación de los resultados finales

La predicción para los datos de entrenamiento.

pred<- predict(modelo2,entrenamiento[,-5],type="class")
tt<- table(pred,entrenamiento[,5])
tt
##             
## pred         setosa versicolor virginica
##   setosa         40          0         0
##   versicolor      0         40         1
##   virginica       0          0        39
# Tasa de aciertos
TA <- sum(diag(tt))/sum(tt) 
paste0("Tasa de aciertos con los datos de entrenamiento: ", round(TA,4)*100, "%", sep="")
## [1] "Tasa de aciertos con los datos de entrenamiento: 99.17%"

Predicción para el conjunto de prueba.

Pred2    <- predict(modelo2, prueba[,-5],type="class")
table    <- table(prueba[,5],Pred2)
table   
##             Pred2
##              setosa versicolor virginica
##   setosa          9          1         0
##   versicolor      0          8         2
##   virginica       0          1         9
# Tasa de aciertos
TAP <- sum(diag(table))/sum(table) 
paste0("Tasa de aciertos con los datos de prueba: ", round(TAP,4)*100, "%", sep="")
## [1] "Tasa de aciertos con los datos de prueba: 86.67%"

Ejemplo 3:

Los datos corresponden a trece características químicas de tres tipos de vinos. La primera columna es el tipo de vino y las otras trece son sus características.

download.file("https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data", "wine.data")
vino <- read.table("wine.data", sep = ",", header = F)
head(vino)
##   V1    V2   V3   V4   V5  V6   V7   V8   V9  V10  V11  V12  V13  V14
## 1  1 14.23 1.71 2.43 15.6 127 2.80 3.06 0.28 2.29 5.64 1.04 3.92 1065
## 2  1 13.20 1.78 2.14 11.2 100 2.65 2.76 0.26 1.28 4.38 1.05 3.40 1050
## 3  1 13.16 2.36 2.67 18.6 101 2.80 3.24 0.30 2.81 5.68 1.03 3.17 1185
## 4  1 14.37 1.95 2.50 16.8 113 3.85 3.49 0.24 2.18 7.80 0.86 3.45 1480
## 5  1 13.24 2.59 2.87 21.0 118 2.80 2.69 0.39 1.82 4.32 1.04 2.93  735
## 6  1 14.20 1.76 2.45 15.2 112 3.27 3.39 0.34 1.97 6.75 1.05 2.85 1450

Solución paso a paso:

Paso 1

Preparación inicial y limpieza de los datos:

Estos datos no tienen nombre en las columnas y es importante convertir en factor la variable de clasificación.

names    <- c("tipo","C1","C2","C3","C4","C5","C6","C7","C8","C9","C10","C11","C12","C13")
colnames(vino) <- names
vino$tipo <- as.factor(vino$tipo)
dim(vino)
## [1] 178  14

Paso 2

Dividir los datos en conjunto de entrenamiento y prueba

Mediante un muestreo aleatorio, separamos el conjunto de entrenamiento y en conjunto de prueba. Supongamos en 7 grupos, 6 para entrenamiento y 1 para prueba.

set.seed(2025)
library(caret)
folds         <- createFolds(vino$tipo, k = 7)
entrenamiento <- vino[-folds[[7]],]
prueba        <- vino[folds[[7]],]
# Etiquetas
entrenamiento_labels <- vino$tipo[-folds[[7]]]
prueba_labels        <- vino$tipo[folds[[7]]]

Paso 3

Construcción del árbol de decisión

Construimos el modelo ajustado con los datos de entrenamiento.

arbol_1 <- rpart(tipo ~ ., data = entrenamiento)
rpart.plot(arbol_1)

Paso 4

Validar la estabilidad del modelo

Aplicamos validación cruzada y jugamos un poco con el parámetro de número de grupos para buscar la mejor estabilidad.

library(caret)
set.seed(2025)
train_control <- trainControl(method="cv",number=10,savePredictions = TRUE)

arbol_cv <- train(tipo ~ ., data=cbind(prueba, tipo=prueba_labels), 
                method = "C5.0", trControl = train_control, 
                 tuneLength = 10)

# Matriz de confusión usando predicciones guardadas por caret
confusionMatrix(arbol_cv$pred$pred, arbol_cv$pred$obs)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   1   2   3
##          1 320  20   0
##          2   0 340 136
##          3   0  40 144
## 
## Overall Statistics
##                                          
##                Accuracy : 0.804          
##                  95% CI : (0.778, 0.8282)
##     No Information Rate : 0.4            
##     P-Value [Acc > NIR] : < 2.2e-16      
##                                          
##                   Kappa : 0.6981         
##                                          
##  Mcnemar's Test P-Value : NA             
## 
## Statistics by Class:
## 
##                      Class: 1 Class: 2 Class: 3
## Sensitivity            1.0000   0.8500   0.5143
## Specificity            0.9706   0.7733   0.9444
## Pos Pred Value         0.9412   0.7143   0.7826
## Neg Pred Value         1.0000   0.8855   0.8333
## Prevalence             0.3200   0.4000   0.2800
## Detection Rate         0.3200   0.3400   0.1440
## Detection Prevalence   0.3400   0.4760   0.1840
## Balanced Accuracy      0.9853   0.8117   0.7294

Paso 5

Comparar desempeño de CART vs. C5.0

# Arbol CART
arbol_cart <- train(tipo ~ ., data=cbind(prueba, tipo=prueba_labels), 
                method = "rpart", trControl = train_control, 
                 tuneLength = 10)

# Árbol C5.0
arbol_c50 <- train(tipo ~ ., data=cbind(prueba, tipo=prueba_labels), 
                method = "C5.0", trControl = train_control, 
                 tuneLength = 10)

Puedes comparar el rendimiento usando las métricas de validación cruzada con la funciónresamples().

# Comparar modelos
comparacion <- resamples(list(CART = arbol_cart, C5.0 = arbol_c50))

# Resumen de métricas
summary(comparacion)
## 
## Call:
## summary.resamples(object = comparacion)
## 
## Models: CART, C5.0 
## Number of resamples: 10 
## 
## Accuracy 
##           Min.   1st Qu.    Median Mean   3rd Qu. Max. NA's
## CART 0.5000000 0.6666667 0.6666667 0.75 0.9166667    1    0
## C5.0 0.6666667 0.7500000 1.0000000 0.90 1.0000000    1    0
## 
## Kappa 
##      Min. 1st Qu. Median      Mean 3rd Qu. Max. NA's
## CART  0.0     0.5    0.5 0.5555556     0.5    1    1
## C5.0  0.5     0.5    1.0 0.8333333     1.0    1    1
# Boxplots de exactitud (u otras métricas)
bwplot(comparacion, metric = "Accuracy")

dotplot(comparacion, metric = "Accuracy")

bwplot(comparacion, metric = "Kappa")

Interpretación de la comparación:

1. Precisión: El modelo C5.0 supera consistentemente a CART en términos de precisión en la mayoría de los pliegues:

  • Tiene una mayor media de precisión (0.90 vs. 0.75).

  • La mediana de C5.0 es 1.0, lo que significa que en al menos 5 de los 10 folds clasificó perfectamente.

  • CART tuvo más variabilidad y peores desempeños en algunos folds (mínimo 0.50).

2. Kappa: Mide el acuerdo entre lo predicho y lo observado, ajustando por azar. Aquí, también C5.0 es mejor:

  • Mayor Kappa medio (0.83 vs. 0.56).

  • Mayor mediana y menor variabilidad.

  • En al menos la mitad de los folds, C5.0 tuvo un desempeño perfecto (Kappa = 1).

Conclusión final: A partir de la validación cruzada con 10 folds, se observa que el modelo C5.0 supera consistentemente a CART tanto en precisión como en estabilidad. Esto sugiere que, al menos para este conjunto de datos, C5.0 es una opción más efectiva para tareas de clasificación.