Árboles de decisión

Este documento te muestra paso a paso como realizar e implementar un árbol de desición a partir de una base de datos en formato .csv, este archivo podrás descargarlo desde la sección de material de consulta de la semana 6.

Paso 1: Preparación del entorno

Instala los paquetes necesarios antes de comenzar, asegúrate de que los paquetes rpart, rpart.plot, y caret estén instalados. Puedes instalarlos desde el ménu de “Tools”–>“Install packages”. En caso de que te marque error al intentar instalar los paquetes y específicamente en los avisos te diga que se necesita RTools, tendrás que proceder con su instalación,para ello deberás de ir al siguiente link: https://cran.rstudio.com/bin/windows/Rtools/, una vez ahí selecciona la versión correspondiente de acuerdo a tu versión de Rstudio. Si no te marcó error y pudiste instalar los paquetes podrás continuar con la práctica sin problemas.

Ya con los paquetes instalados deberás importarlos para poder hacer uso de ellos, para lo cual debes utilizar las siguientes líneas de código:

library(ggplot2)
library(lattice)
library(rpart)
library(rpart.plot)
library(caret)

Paso 2: Cargar y explorar los datos

Usaremos un conjunto de datos de ejemplo llamado trainFixed.csv, que contiene información sobre los pasajeros del famoso barco Titanic. El objetivo es predecir si un pasajero sobrevivió o no en función de características como el sexo, la clase, la edad, etc. El archivo original se llama Train.csv, el cual pudes encontrar en Kaggle, en el siguente link: https://www.kaggle.com/competitions/titanic/data, a este archivo original(train.csv) se le eliminaron las columnas de PassengerID, Nombre y el ticket, dado que son datos irrelevantes no deben ser considerados para la generación del modelo de desición, adicionalmente se eliminó la columna de cabina por que el porcentaje de registros sin datos es superior a los que si la tienen.

# Cargar el archivo CSV en R
datos <- read.csv("trainFixed.csv")

# Mostrar las primeras filas del conjunto de datos
head(datos)
##   Survived Pclass    Sex Age SibSp Parch    Fare Embarked
## 1        0      3   male  22     1     0  7.2500        S
## 2        1      1 female  38     1     0 71.2833        C
## 3        1      3 female  26     0     0  7.9250        S
## 4        1      1 female  35     1     0 53.1000        S
## 5        0      3   male  35     0     0  8.0500        S
## 6        0      3   male  NA     0     0  8.4583        Q

Explorar los datos: Es importante entender las características de los datos. Usa las siguientes funciones para obtener una visión general:

summary(datos)  # Resumen estadístico de las variables
##     Survived          Pclass          Sex                 Age       
##  Min.   :0.0000   Min.   :1.000   Length:891         Min.   : 0.42  
##  1st Qu.:0.0000   1st Qu.:2.000   Class :character   1st Qu.:20.12  
##  Median :0.0000   Median :3.000   Mode  :character   Median :28.00  
##  Mean   :0.3838   Mean   :2.309                      Mean   :29.70  
##  3rd Qu.:1.0000   3rd Qu.:3.000                      3rd Qu.:38.00  
##  Max.   :1.0000   Max.   :3.000                      Max.   :80.00  
##                                                      NA's   :177    
##      SibSp           Parch             Fare          Embarked        
##  Min.   :0.000   Min.   :0.0000   Min.   :  0.00   Length:891        
##  1st Qu.:0.000   1st Qu.:0.0000   1st Qu.:  7.91   Class :character  
##  Median :0.000   Median :0.0000   Median : 14.45   Mode  :character  
##  Mean   :0.523   Mean   :0.3816   Mean   : 32.20                     
##  3rd Qu.:1.000   3rd Qu.:0.0000   3rd Qu.: 31.00                     
##  Max.   :8.000   Max.   :6.0000   Max.   :512.33                     
## 
str(datos)      # Estructura de los datos
## 'data.frame':    891 obs. of  8 variables:
##  $ Survived: int  0 1 1 1 0 0 0 0 1 1 ...
##  $ Pclass  : int  3 1 3 1 3 3 1 3 3 2 ...
##  $ Sex     : chr  "male" "female" "female" "female" ...
##  $ Age     : num  22 38 26 35 35 NA 54 2 27 14 ...
##  $ SibSp   : int  1 1 0 1 0 0 0 3 0 1 ...
##  $ Parch   : int  0 0 0 0 0 0 0 1 2 0 ...
##  $ Fare    : num  7.25 71.28 7.92 53.1 8.05 ...
##  $ Embarked: chr  "S" "C" "S" "S" ...

Aquí podemos ver que la variable Survived se está leyendo como un valor entero, cuando en realidad debería ser categórica, ya que el 1 indica que el pasajero sobrevivió y el 0 que no. Tenemos dos opciones: realizar este cambio directamente en el archivo Excel o convertir estos datos en factores utilizando R. Dado que la primera opción es sencilla, en este caso haremos la conversión usando R.

Paso 3: Conversión de datos al tipo adecuado de ser necesario

# Convertir Survived a factor en todo el conjunto de datos
datos$Survived <- factor(datos$Survived, levels = c(0, 1), labels = c("No", "Si"))

Si hubiese variables categóricas que también deban ser convertidas a factores, se debe hacer el procedimiento para cada una.

Paso 4: Dividir los datos en entrenamiento y prueba

Para entrenar el árbol de decisión y evaluar su rendimiento, dividiremos los datos en un conjunto de entrenamiento y otro de prueba.

Usaremos la función createDataPartition del paquete caret para dividir los datos, asignando el 70% para entrenamiento y el 30% para prueba:

set.seed(123)  # Fijar la semilla para que tus resultados coincidad con los de este manual
indice <- createDataPartition(datos$Survived, p = 0.7, list = FALSE)
entrenamiento <- datos[indice, ]
prueba <- datos[-indice, ]

Paso 5: Construir el árbol de decisión

Utiliza la función rpart() para construir el árbol de decisión. Esta función crea el árbol basado en la variable objetivo, en este caso, Survived, que indica si un pasajero sobrevivió o no. Todas las demás variables del conjunto de datos se utilizan como predictores.

La sintaxis básica de rpart() es:

modelo_arbol <- rpart(Survived ~ ., data = entrenamiento, method = "class")

Desglose de la instrucción

  1. modelo_arbol <-:

    • Esto es simplemente una asignación de valor en R. Significa que el resultado del modelo que se construya será almacenado en la variable modelo_arbol. Esta variable contendrá el árbol de decisión que será utilizado para hacer predicciones.
  2. rpart(Survived ~ ., data = entrenamiento, method = "class"):

    • Aquí es donde se construye el árbol de decisión utilizando la función rpart(). Vamos a desglosar los argumentos de la función:

    • Survived ~ .:

      • Esto es una fórmula que indica cuál es la variable dependiente o variable objetivo (Survived) y qué otras variables se usarán como predictores o variables independientes (las que aparecen después del símbolo ~).

      • El . (punto) significa que se usarán todas las demás columnas del conjunto de datos entrenamiento como predictores. En este caso, R utilizará todas las variables de entrenamiento excepto Survived para predecir el valor de Survived.

    • data = entrenamiento:

      • Este argumento indica el conjunto de datos que se usará para construir el modelo. En este caso, estamos utilizando los datos almacenados en el dataframe entrenamiento.
    • method = "class":

      • Este parámetro indica el tipo de modelo que estamos construyendo. La opción "class" se utiliza cuando la variable objetivo es categórica, es decir, cuando estamos tratando con un problema de clasificación.

      • En este caso, Survived es una variable categórica (con valores como “sí” o “no”, por ejemplo), por lo que usamos "class". Si estuviéramos construyendo un árbol de decisión para predecir un valor continuo, como un precio o una cantidad, usaríamos "anova" en lugar de "class".

Paso 6: Visualizacíón del modelo generado

rpart.plot(modelo_arbol)

Paso 7: Evaluar el rendimiento del modelo Realizando predicciones en el conjunto de prueba:

Para realizar las predicciónes hacemos uso de la función predict y le damos como argumentos el modelo del árbole generado, seguido de los datos a los cuales se les aplicará la predicción y finalmente en type se denota si la predición es categórica o contiua.

predicciones <- predict(modelo_arbol, prueba, type = "class")

Una vez realizadas las predicciones, generaremos una matriz de confusión para evaluar el rendimiento del modelo. Esta matriz nos permitirá conocer cuántas predicciones fueron correctas, así como el número de falsos positivos y falsos negativos.

Para generar esta matriz, se utiliza la función confusionMatrix(), enviando como parámetros las predicciones realizadas y los valores reales del conjunto de prueba.

confusionMatrix(predicciones, prueba$Survived)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  No  Si
##         No 134  32
##         Si  30  70
##                                           
##                Accuracy : 0.7669          
##                  95% CI : (0.7114, 0.8164)
##     No Information Rate : 0.6165          
##     P-Value [Acc > NIR] : 1.294e-07       
##                                           
##                   Kappa : 0.5052          
##                                           
##  Mcnemar's Test P-Value : 0.8989          
##                                           
##             Sensitivity : 0.8171          
##             Specificity : 0.6863          
##          Pos Pred Value : 0.8072          
##          Neg Pred Value : 0.7000          
##              Prevalence : 0.6165          
##          Detection Rate : 0.5038          
##    Detection Prevalence : 0.6241          
##       Balanced Accuracy : 0.7517          
##                                           
##        'Positive' Class : No              
## 

De los parámetros devueltos destacaremos 3:

  1. Accuracy: porcentaje de predicciones correctas que realizó el modelo.

  2. Sensitivity: porcentaje de los casos positivos reales fueron correctamente identificados por el modelo.

  3. Specificity:porcentaje de los casos negativos reales que el modelo clasificó correctamente.

De acuerdo con nuestra práctica, el modelo tiene una precisión general del 76%. Sin embargo, la sensibilidad muestra un 81% al identificar correctamente a quienes no sobrevivieron. Por otro lado, la especificidad es del 68%, lo que indica que el modelo no es tan confiable al predecir quién sobrevivió. En resumen, el modelo tiene mayor precisión al determinar cuándo alguien no sobrevivió, pero es menos eficaz al predecir la supervivencia.

Paso 8: Podar el árbol de decisión

Para tener información de la contrucción del modelo del árbol emplearemos la función printcp(), los resultados nos ayudarán a decidir los parámetros adecuados para proceder al podado de un árbol

printcp(modelo_arbol)
## 
## Classification tree:
## rpart(formula = Survived ~ ., data = entrenamiento, method = "class")
## 
## Variables actually used in tree construction:
## [1] Age    Fare   Pclass Sex    SibSp 
## 
## Root node error: 240/625 = 0.384
## 
## n= 625 
## 
##         CP nsplit rel error  xerror     xstd
## 1 0.470833      0   1.00000 1.00000 0.050662
## 2 0.031250      1   0.52917 0.52917 0.041915
## 3 0.029167      3   0.46667 0.54167 0.042279
## 4 0.016667      4   0.43750 0.50417 0.041158
## 5 0.012500      5   0.42083 0.48750 0.040632
## 6 0.010000      9   0.37083 0.49583 0.040898

Interpretación de cada columna:

  1. CP (Complexity Parameter o Parámetro de Complejidad):

    • El CP es un parámetro de complejidad que controla la poda del árbol. Cuanto mayor sea el valor de CP, más sencillo será el árbol porque eliminará ramas con poca mejora.

    • El CP es el criterio usado para podar ramas que no reducen suficientemente el error relativo del árbol.

    • Cómo usarlo: Puedes elegir el valor de CP donde el error de validación cruzada (xerror) es mínimo para podar el árbol de forma óptima.

  2. nsplit:

    • Esta columna indica el número de divisiones (nodos) realizadas en el árbol en cada paso. A medida que nsplit aumenta, el árbol se vuelve más complejo.

    • Cómo usarlo: Esto te muestra cuántas divisiones adicionales se hacen en el árbol en cada paso.

  3. rel error (Relative Error):

    • El error relativo es el error que tiene el modelo en cada paso de la construcción del árbol, en relación con el error del árbol sin ninguna división (el nodo raíz). El valor inicial del nodo raíz es 1.0.

    • A medida que se agregan más divisiones, el error relativo disminuye.

    • Cómo usarlo: Un error relativo menor indica un mejor ajuste a los datos de entrenamiento, pero un error demasiado bajo puede significar sobreajuste.

  4. xerror (Cross-Validation Error o Error de Validación Cruzada):

    • El error de validación cruzada mide qué tan bien el árbol generaliza en datos nuevos. Se calcula mediante validación cruzada y es el error estimado del modelo en datos que no ha visto.

    • Cómo usarlo: El valor más bajo de xerror indica el tamaño óptimo del árbol, donde el modelo tiene un buen equilibrio entre ajuste a los datos de entrenamiento y generalización a nuevos datos.

  5. xstd (Standard Deviation of xerror o Desviación Estándar del Error de Validación Cruzada):

    • xstd es la desviación estándar asociada con el error de validación cruzada. Esto te da una idea de la variabilidad del error estimado.

    • Cómo usarlo: Si la diferencia entre los valores de xerror es pequeña en relación con la desviación estándar, puede no valer la pena hacer divisiones adicionales.

De acuerdo a nuestros datos obtenidos, el mínimo de xerror lo tenemos en la fila 5, con un valor de CP = 0.0125, valor que deberemos emplear para haecr la poda y hacer menos complejo nuestro árbol.

Para realizar esto ocupamos la siguiente línea de código:

modelo_podado <- prune(modelo_arbol, cp = 0.0125)

A continuación procedemos a visualizar el árbol podado para poder comparar visualmente los cabios comparado con el árbol original. Para ello ocupamos la siguiente líne de código:

rpart.plot(modelo_podado)

Paso 9: Evaluar el modelo podado

Para poder evaluar el modelo podado procedemos a realizar las predicciones, tal como se realizó con el árbol original.

predicciones_podadas <- predict(modelo_podado, prueba, type = "class")

Se procede a generar la matriz de confusión para ver si la eficiencia mejoró o empeoró, esto nos ayudará a determinar si es mejor nuestro árbol original o el podado.

confusionMatrix(predicciones_podadas, prueba$Survived)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  No  Si
##         No 152  46
##         Si  12  56
##                                           
##                Accuracy : 0.782           
##                  95% CI : (0.7274, 0.8301)
##     No Information Rate : 0.6165          
##     P-Value [Acc > NIR] : 5.932e-09       
##                                           
##                   Kappa : 0.5078          
##                                           
##  Mcnemar's Test P-Value : 1.470e-05       
##                                           
##             Sensitivity : 0.9268          
##             Specificity : 0.5490          
##          Pos Pred Value : 0.7677          
##          Neg Pred Value : 0.8235          
##              Prevalence : 0.6165          
##          Detection Rate : 0.5714          
##    Detection Prevalence : 0.7444          
##       Balanced Accuracy : 0.7379          
##                                           
##        'Positive' Class : No              
## 

Paso 10: Hacer una predicción propia

El objetivo de generar un árbol de decisión basado en datos históricos es poder predecir la variable objetivo cuando se presenta un nuevo caso. Para lograr esto, es necesario identificar las variables que describen el fenómeno y que influyen en la variable independiente. En nuestro ejemplo, imaginemos que el Titanic zarpa nuevamente y vuelve a hundirse. Utilizando los datos históricos, realizaremos una predicción sobre si un nuevo pasajero sobreviviría o no.

Primero, definimos los valores de las características relevantes para ese pasajero y luego ejecutamos la predicción. Esto se realiza con las siguientes líneas de código:

# Crear un nuevo dataframe con los valores que deseas predecir
nuevos_valores <- data.frame(
  Pclass = 2,         # Clase del pasajero
  Sex = "male",       # Sexo del pasajero
  Age = 30,           # Edad
  Fare = 10.5,        # Tarifa
  Embarked = "S",     # Puerto de embarque C = Cherbourg, Q = Queenstown, S = Southampton
  SibSp = 1,          # Numero de hermanos y conyugues en el barco
  Parch = 0           # Numero de padres o hijos en el barco
)

# Mostrar el nuevo dataframe
nuevos_valores
##   Pclass  Sex Age Fare Embarked SibSp Parch
## 1      2 male  30 10.5        S     1     0
# Realizar la predicción con el árbol entrenado
prediccion_manual <- predict(modelo_arbol, nuevos_valores, type = "class")

# Mostrar la predicción
prediccion_manual
##  1 
## No 
## Levels: No Si

En la salida podemos observar tres lineas:

Conclusión

Compara el rendimiento antes y después de la poda: Discute si el rendimiento del modelo mejoró tras la poda. Observa si el árbol podado se ajusta mejor a los datos de prueba y si se redujo el sobreajuste.

Discusión sobre la simplicidad del árbol: Compara visualmente el árbol original con el árbol podado y evalúa si el árbol más pequeño generaliza mejor sin perder precisión.

¿Con qué árbol es mejor realizar nuestras predicciones y porqué? ¿con el primer árbol o con el podado?