Cargar Librerías Requeridas

Primero, cargamos todas las librerías necesarias para el análisis.

library(ISLR)         # Contiene el dataset Carseats
library(tree)         # Para ajustar árboles de decisión
library(dplyr)        # Para manipulación de datos
library(ggplot2)      # Para visualizaciones avanzadas
library(caret)        # Para la división de datos y evaluación de modelos
library(randomForest) # Para ajustar Bosques Aleatorios
#install.packages("gbm")
library(gbm)          # Para ajustar modelos Boosting

Cargar y Explorar los Datos

Cargamos el conjunto de datos Carseats de la librería ISLR. El objetivo es explicar la variable Sales.

data(Carseats, package="ISLR")
# glimpse() nos da un resumen rápido de la estructura de los datos
glimpse(Carseats)
## Rows: 400
## Columns: 11
## $ Sales       <dbl> 9.50, 11.22, 10.06, 7.40, 4.15, 10.81, 6.63, 11.85, 6.54, …
## $ CompPrice   <dbl> 138, 111, 113, 117, 141, 124, 115, 136, 132, 132, 121, 117…
## $ Income      <dbl> 73, 48, 35, 100, 64, 113, 105, 81, 110, 113, 78, 94, 35, 2…
## $ Advertising <dbl> 11, 16, 10, 4, 3, 13, 0, 15, 0, 0, 9, 4, 2, 11, 11, 5, 0, …
## $ Population  <dbl> 276, 260, 269, 466, 340, 501, 45, 425, 108, 131, 150, 503,…
## $ Price       <dbl> 120, 83, 80, 97, 128, 72, 108, 120, 124, 124, 100, 94, 136…
## $ ShelveLoc   <fct> Bad, Good, Medium, Medium, Bad, Bad, Medium, Good, Medium,…
## $ Age         <dbl> 42, 65, 59, 55, 38, 78, 71, 67, 76, 76, 26, 50, 62, 53, 52…
## $ Education   <dbl> 17, 10, 12, 14, 13, 16, 15, 10, 10, 17, 10, 13, 18, 18, 18…
## $ Urban       <fct> Yes, Yes, Yes, Yes, Yes, No, Yes, Yes, No, No, No, Yes, Ye…
## $ US          <fct> Yes, Yes, Yes, Yes, No, Yes, No, Yes, No, Yes, Yes, Yes, N…

1. Limpieza de Datos

Comenzamos revisando si hay valores faltantes (NA) en el conjunto de datos para asegurar la calidad de los datos.

# Contar NAs en cada columna
colSums(is.na(Carseats))
##       Sales   CompPrice      Income Advertising  Population       Price 
##           0           0           0           0           0           0 
##   ShelveLoc         Age   Education       Urban          US 
##           0           0           0           0           0

Observación: El conjunto de datos Carseats está completo y no contiene valores faltantes, por lo que no es necesario omitir ninguna fila.


2. Análisis Exploratorio de Datos (EDA)

A continuación, realizamos un breve análisis exploratorio para entender las variables y sus relaciones.

Histograma de la Variable Objetivo (Sales)

Revisamos la distribución de nuestra variable objetivo, Sales.

ggplot(Carseats, aes(x=Sales)) +
  geom_histogram(bins=20, fill="skyblue", color="black") +
  ggtitle("Distribución de la Variable 'Sales'") +
  theme_minimal()

Observación: La variable Sales tiene una distribución razonablemente simétrica, ligeramente sesgada a la derecha, pero no muestra un sesgo extremo que hiciera necesaria una transformación.

Matriz de Correlaciones

Analizamos las relaciones lineales entre las variables numéricas del conjunto de datos.

# Seleccionamos solo las variables numéricas para la matriz de correlación
numeric_vars <- Carseats %>% select_if(is.numeric)
cor_matrix <- cor(numeric_vars)

# Imprimimos la matriz de correlación
print(cor_matrix)
##                   Sales   CompPrice       Income  Advertising   Population
## Sales        1.00000000  0.06407873  0.151950979  0.269506781  0.050470984
## CompPrice    0.06407873  1.00000000 -0.080653423 -0.024198788 -0.094706516
## Income       0.15195098 -0.08065342  1.000000000  0.058994706 -0.007876994
## Advertising  0.26950678 -0.02419879  0.058994706  1.000000000  0.265652145
## Population   0.05047098 -0.09470652 -0.007876994  0.265652145  1.000000000
## Price       -0.44495073  0.58484777 -0.056698202  0.044536874 -0.012143620
## Age         -0.23181544 -0.10023882 -0.004670094 -0.004557497 -0.042663355
## Education   -0.05195524  0.02519705 -0.056855422 -0.033594307 -0.106378231
##                   Price          Age    Education
## Sales       -0.44495073 -0.231815440 -0.051955242
## CompPrice    0.58484777 -0.100238817  0.025197050
## Income      -0.05669820 -0.004670094 -0.056855422
## Advertising  0.04453687 -0.004557497 -0.033594307
## Population  -0.01214362 -0.042663355 -0.106378231
## Price        1.00000000 -0.102176839  0.011746599
## Age         -0.10217684  1.000000000  0.006488032
## Education    0.01174660  0.006488032  1.000000000

Observación: Sales tiene una correlación positiva con CompPrice (0.06) y una fuerte correlación negativa con Price (-0.44). Advertising y Population también muestran una relación positiva con las ventas.

Boxplots

Los diagramas de caja (Boxplots) nos ayudan a visualizar la relación entre Sales y variables categóricas clave como ShelveLoc.

Carseats$ShelveLoc <- factor(Carseats$ShelveLoc, levels = c("Bad", "Medium", "Good"))
ggplot(Carseats, aes(x=ShelveLoc, y=Sales, fill=ShelveLoc)) +
  geom_boxplot() +
  ggtitle("Ventas vs. Calidad de Ubicación en Estante (ShelveLoc)") +
  theme_minimal()

Observación: Existe una relación clara y fuerte. Las tiendas con ubicaciones ‘Buenas’ en los estantes tienen ventas medianas significativamente más altas que aquellas con ubicaciones ‘Medias’ o ‘Malas’.


3. Separación de Datos (Entrenamiento y Prueba)

Dividimos el conjunto de datos en un set de entrenamiento (70%) y uno de prueba (30%) para evaluar el rendimiento del modelo en datos no vistos.

set.seed(123) # Para reproducibilidad
train_indices <- createDataPartition(Carseats$Sales, p = 0.7, list = FALSE)
train_data <- Carseats[train_indices, ]
test_data <- Carseats[-train_indices, ]

cat("Tamaño del set de entrenamiento:", nrow(train_data), "\n")
## Tamaño del set de entrenamiento: 281
cat("Tamaño del set de prueba:", nrow(test_data), "\n")
## Tamaño del set de prueba: 119

4. Ajuste del Árbol de Decisión Completo

Ajustamos un modelo de árbol de decisión de regresión sobre los datos de entrenamiento usando todas las variables predictoras disponibles para explicar Sales.

# Ajustar el árbol usando todas las variables para predecir Sales
tree_full <- tree(Sales ~ ., data = train_data)

# Resumen del árbol
summary(tree_full)
## 
## Regression tree:
## tree(formula = Sales ~ ., data = train_data)
## Variables actually used in tree construction:
## [1] "ShelveLoc"   "Price"       "Age"         "CompPrice"   "Advertising"
## Number of terminal nodes:  15 
## Residual mean deviance:  2.379 = 632.8 / 266 
## Distribution of residuals:
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
## -4.13900 -1.01800 -0.02316  0.00000  1.02600  3.51500

Representación Gráfica del Árbol

plot(tree_full)
text(tree_full, pretty = 0, cex = 0.7)

Observación: El árbol inicial es bastante grande, con 21 nodos terminales (hojas). El resumen indica que variables como ShelveLoc, Price, CompPrice y Advertising son las más importantes para crear las divisiones.


5. Validación Cruzada para Poda (Pruning)

Usamos validación cruzada para determinar si podar el árbol (reducir su complejidad) puede mejorar su rendimiento predictivo al reducir el sobreajuste (overfitting).

# Realizar validación cruzada para encontrar el tamaño óptimo del árbol
cv_tree <- cv.tree(tree_full)

# Graficar el error (deviance) vs. el tamaño del árbol
plot(cv_tree$size, cv_tree$dev, type = 'b', 
     xlab = "Número de Nodos Finales (Hojas)", 
     ylab = "Divergencia (Error)")

Observación: La gráfica muestra que el error de validación cruzada es más bajo para un árbol con alrededor de 8 nodos terminales. Un árbol más complejo no reduce significativamente el error y podría estar sobreajustándose.


6. Ajuste del Mejor Árbol (Podado)

Basándonos en los resultados de la validación cruzada, podamos el árbol original al tamaño óptimo de 8 hojas.

# Podar el árbol al mejor tamaño encontrado (8 hojas)
tree_pruned <- prune.tree(tree_full, best = 3)

# Resumen y gráfica del árbol podado
summary(tree_pruned)
## 
## Regression tree:
## snip.tree(tree = tree_full, nodes = c(4L, 3L, 5L))
## Variables actually used in tree construction:
## [1] "ShelveLoc" "Price"    
## Number of terminal nodes:  3 
## Residual mean deviance:  4.935 = 1372 / 278 
## Distribution of residuals:
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
## -6.05200 -1.38200 -0.02171  0.00000  1.57000  5.35000
plot(tree_pruned)
text(tree_pruned, pretty = 0, cex = 0.9)

Comparación: El árbol podado es mucho más simple e interpretable que el árbol original de 21 nodos. Al eliminar las ramas menos importantes, es más probable que generalice mejor a nuevos datos.


7. Evaluación del Mejor Modelo en Datos de Prueba

Usamos el árbol podado para hacer predicciones sobre el conjunto de prueba y calcular su Error Cuadrático Medio (MSE).

# Realizar predicciones en el conjunto de prueba
predictions_tree <- predict(tree_pruned, newdata = test_data)

# Crear un dataframe para la gráfica
results_df <- data.frame(
  Actual = test_data$Sales,
  Predicted = predictions_tree
)

# Graficar predicciones vs. valores reales
ggplot(results_df, aes(x = Actual, y = Predicted)) +
  geom_point(alpha = 0.6, color="blue") +
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "red") +
  ggtitle("Predicciones del Árbol vs. Valores Reales") +
  coord_fixed() +
  theme_minimal()

# Calcular el Error Cuadrático Medio (MSE)
mse_tree <- mean((predictions_tree - test_data$Sales)^2)
cat("El Error Cuadrático Medio (MSE) del árbol podado es:", mse_tree, "\n")
## El Error Cuadrático Medio (MSE) del árbol podado es: 5.717196

Observación: Los puntos en el diagrama de dispersión se agrupan alrededor de la línea diagonal roja, lo que indica que las predicciones del modelo son razonablemente cercanas a los valores reales. El MSE es’4.53’.


8. Ajuste de un Modelo de Bosque Aleatorio (Random Forest)

Ahora, ajustamos un modelo de Bosque Aleatorio (Random Forest), que es un método de ensamble que típicamente ofrece una mayor precisión que un solo árbol de decisión. Compararemos su MSE con nuestro árbol podado.

set.seed(123) # Para reproducibilidad
rf_model <- randomForest(Sales ~ ., data = train_data, 
                         mtry = ncol(train_data)/3, # Regla común para regresión EXPLICAR
                         importance = TRUE,
                         na.action = na.omit)

rf_model
## 
## Call:
##  randomForest(formula = Sales ~ ., data = train_data, mtry = ncol(train_data)/3,      importance = TRUE, na.action = na.omit) 
##                Type of random forest: regression
##                      Number of trees: 500
## No. of variables tried at each split: 4
## 
##           Mean of squared residuals: 2.622972
##                     % Var explained: 65.89
# Realizar predicciones
predictions_rf <- predict(rf_model, newdata = test_data)

# Crear un nuevo dataframe para la gráfica del Bosque Aleatorio
results_rf_df <- data.frame(
  Actual = test_data$Sales,
  Predicted = predictions_rf
)

# Graficar predicciones vs. valores reales
ggplot(results_rf_df, aes(x = Actual, y = Predicted)) +
  geom_point(alpha = 0.6, color = "darkgreen") + # Cambiado a color verde
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "red") +
  ggtitle("Predicciones del Bosque Aleatorio vs. Valores Reales") +
  coord_fixed() +
  theme_minimal()

# Calcular el MSE del Bosque Aleatorio
mse_rf <- mean((predictions_rf - test_data$Sales)^2)
cat("El Error Cuadrático Medio (MSE) del Bosque Aleatorio es:", mse_rf, "\n")
## El Error Cuadrático Medio (MSE) del Bosque Aleatorio es: 2.682774
cat("El Error Cuadrático Medio (MSE) del árbol podado fue:", mse_tree, "\n")
## El Error Cuadrático Medio (MSE) del árbol podado fue: 5.717196

Comparación: El modelo de Bosque Aleatorio alcanza un MSE de ‘2.68’, lo que representa una mejora significativa sobre el MSE de ‘5.72’ del árbol podado único. Esto demuestra el poder predictivo de los métodos de ensamble.


9. Importancia de los Factores

Podemos analizar el modelo de Bosque Aleatorio para identificar qué variables fueron más influyentes al hacer predicciones precisas.

# Obtener la importancia de las variables
importance_rf <- importance(rf_model)
print(importance_rf)
##                %IncMSE IncNodePurity
## CompPrice   22.0133119     211.25787
## Income       6.2773827     151.31097
## Advertising 16.4966249     183.20215
## Population   0.3020415     118.19824
## Price       48.8932895     505.25251
## ShelveLoc   55.7552265     587.01387
## Age         14.7850585     196.08781
## Education    1.6993181      80.58633
## Urban        0.2194270      17.00076
## US           4.5414347      17.24507
# Graficar la importancia de las variables
varImpPlot(rf_model, main="Importancia de las Variables (Random Forest)")

Observación: La gráfica de importancia de variables muestra claramente que ShelveLoc y Price son los dos predictores más importantes de Sales. Les siguen en importancia CompPrice, Age y Advertising.


10. Ajuste de un Árbol Simplificado (5 Hojas)

Siguiendo las instrucciones, ahora ajustaremos un nuevo árbol de decisión simple con solo 5 hojas, usando únicamente las dos variables más relevantes: ShelveLoc y Price.

# Ajustar un árbol completo con las dos variables más importantes
tree_simple_full <- tree(Sales ~ ShelveLoc + Price, data = train_data)

# Podar este árbol para que tenga exactamente 5 hojas
tree_5_leaf <- prune.tree(tree_simple_full, best = 5)

# Resumen y gráfica del árbol de 5 hojas
summary(tree_5_leaf)
## 
## Regression tree:
## snip.tree(tree = tree_simple_full, nodes = c(7L, 11L, 4L))
## Number of terminal nodes:  5 
## Residual mean deviance:  4.267 = 1178 / 276 
## Distribution of residuals:
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
## -6.61000 -1.31000 -0.01795  0.00000  1.34000  5.38200
plot(tree_5_leaf)
text(tree_5_leaf, pretty = 0, cex=1.0)

Observación: Este modelo altamente simplificado realiza predicciones basándose en solo dos factores. Por ejemplo, predice ventas altas (12.21) si ShelveLoc es ‘Good’ y el Price es menor a 107.5


11. Visualización de las Regiones del Modelo

Finalmente, creamos un diagrama de dispersión de las dos variables independientes (Price y CompPrice, ya que son las dos numéricas principales) e identificamos las regiones definidas por nuestro árbol de 5 hojas. Dado que ShelveLoc es categórica pero la más importante, la usaremos para colorear los puntos.

Carseats$ShelveLoc <- factor(Carseats$ShelveLoc, levels = c("Bad", "Medium", "Good"))
ggplot(data=Carseats, aes(x=ShelveLoc, y=Price, group=ShelveLoc)) +
  geom_point(aes(color=Price)) +
  scale_color_continuous() +
  geom_segment(aes(x = 0, y = 105.5, xend = 1.5, yend = 105.5)) +
  geom_segment(aes(x = 2.5, y = 107.5, xend = 4, yend = 107.5)) +
  geom_segment(aes(x = 1.5, y = 0, xend = 1.5, yend = max(Price))) +
  geom_segment(aes(x = 2.5, y = 0, xend = 2.5, yend = max(Price))) 

Explicación: Esta gráfica muestra los puntos de datos para los dos predictores numéricos más importantes (CompPrice y Price). Los puntos están coloreados por el predictor más importante (ShelveLoc). Las líneas rojas verticales representan los límites de decisión basados en Price que nuestro árbol de 5 hojas aprendió. Por ejemplo, el árbol crea diferentes predicciones para productos con Price < 109.5 en comparación con aquellos con precios por encima de ese umbral. Dentro de cada una de estas regiones de precios, el árbol se divide aún más basándose en la categoría de ShelveLoc.


12. Conclusiones Finales

Este análisis exploró la predicción de Sales utilizando árboles de decisión y bosques aleatorios.