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
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…
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.
A continuación, realizamos un breve análisis exploratorio para entender las variables y sus relaciones.
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.
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.
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’.
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
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
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.
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.
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.
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’.
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.
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.
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
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.
Este análisis exploró la predicción de Sales utilizando árboles de decisión y bosques aleatorios.
Impulsores Clave de las Ventas:: Los factores más influyentes que impulsan las ventas de asientos de coche son la calidad de la ubicación en el estante (ShelveLoc) en la tienda y el precio (Price) del producto. El precio de la competencia (CompPrice) y el presupuesto de publicidad (Advertising) también son relevantes, pero son factores secundarios.
Rendimiento del Modelo:: Un solo árbol de decisión, cuando se optimiza mediante la poda, proporciona un modelo interpretable con un MSE de prueba de 5.72. Sin embargo, un método de ensamble como Random Forest ofrece una precisión predictiva superior, reduciendo el MSE a 2.53.
Perspectivas Accionables:: Una estrategia de negocio enfocada en asegurar ubicaciones ‘Buenas’ en los estantes y establecer un punto de precio competitivo (por ejemplo, por debajo de $135, como sugiere el árbol simplificado) probablemente generará las ventas más altas. Los modelos confirman que los precios altos están fuertemente asociados con menores ventas, especialmente cuando se combinan con una mala ubicación en el estante.