La finalidad del presente estudio es la de estimar el dato Valor medio de la vivienda (Median House Value) teniendo en cuenta todas las variables incluidas en dataset.
Para cumplimentar dicho objetivo correremos 2 modelos de machine learning y al final compararemos sus métricas para determinar cual de los mismo predice mejor el campo mencionado.
Los modelos que utilizaremos son los siguientes: * Decision Tree (DT) sin tunning * Random Forest (RF) sin tunning
library(tidymodels)
library(tidyverse)
library(vip)
library(magrittr)
library(corrr)
library(MASS)
library(rpart)
library(rpart.plot)
houses <- read_csv("https://raw.githubusercontent.com/data-datum/datasets/main/california_houses.csv")
## Rows: 20640 Columns: 14
## -- Column specification --------------------------------------------------------
## Delimiter: ","
## dbl (14): Median_House_Value, Median_Income, Median_Age, Tot_Rooms, Tot_Bedr...
##
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.
Estudiamos mediante diferentes comandos el interior del dataset para comprender el contenido del mismo.
head(houses)
## # A tibble: 6 x 14
## Median_House_Value Median_Income Median_Age Tot_Rooms Tot_Bedrooms Population
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 452600 8.33 41 880 129 322
## 2 358500 8.30 21 7099 1106 2401
## 3 352100 7.26 52 1467 190 496
## 4 341300 5.64 52 1274 235 558
## 5 342200 3.85 52 1627 280 565
## 6 269700 4.04 52 919 213 413
## # i 8 more variables: Households <dbl>, Latitude <dbl>, Longitude <dbl>,
## # Distance_to_coast <dbl>, Distance_to_LA <dbl>, Distance_to_SanDiego <dbl>,
## # Distance_to_SanJose <dbl>, Distance_to_SanFrancisco <dbl>
glimpse(houses)
## Rows: 20,640
## Columns: 14
## $ Median_House_Value <dbl> 452600, 358500, 352100, 341300, 342200, 26970~
## $ Median_Income <dbl> 8.3252, 8.3014, 7.2574, 5.6431, 3.8462, 4.036~
## $ Median_Age <dbl> 41, 21, 52, 52, 52, 52, 52, 52, 42, 52, 52, 5~
## $ Tot_Rooms <dbl> 880, 7099, 1467, 1274, 1627, 919, 2535, 3104,~
## $ Tot_Bedrooms <dbl> 129, 1106, 190, 235, 280, 213, 489, 687, 665,~
## $ Population <dbl> 322, 2401, 496, 558, 565, 413, 1094, 1157, 12~
## $ Households <dbl> 126, 1138, 177, 219, 259, 193, 514, 647, 595,~
## $ Latitude <dbl> 37.88, 37.86, 37.85, 37.85, 37.85, 37.85, 37.~
## $ Longitude <dbl> -122.23, -122.22, -122.24, -122.25, -122.25, ~
## $ Distance_to_coast <dbl> 9263.041, 10225.733, 8259.085, 7768.087, 7768~
## $ Distance_to_LA <dbl> 556529.2, 554279.9, 554610.7, 555194.3, 55519~
## $ Distance_to_SanDiego <dbl> 735501.8, 733236.9, 733525.7, 734095.3, 73409~
## $ Distance_to_SanJose <dbl> 67432.52, 65049.91, 64867.29, 65287.14, 65287~
## $ Distance_to_SanFrancisco <dbl> 21250.21, 20880.60, 18811.49, 18031.05, 18031~
El dataset corresponde a los precios de propiedades del estado de California y algunas estadísticas resumidas sobre ellas basadas en los datos del censo de 1990.
Las variables contenidas en el dataset son las siguientes:
Más información sobre el dataset disponible en: https://www.kaggle.com/datasets/fedesoriano/california-housing-prices-data-extra-features
Para correr los modelos de ML dividimos el dataset en 2 dataset con una proporción de 75% y 25% (train - test).
set.seed(1234)
p_split <- houses %>%
initial_split(prop = 0.75)
p_train <- training(p_split)
p_test <- testing(p_split)
glimpse(p_train)
## Rows: 15,480
## Columns: 14
## $ Median_House_Value <dbl> 165500, 225200, 125500, 206900, 114600, 17500~
## $ Median_Income <dbl> 4.6389, 4.0603, 2.4167, 3.4808, 3.0924, 2.562~
## $ Median_Age <dbl> 17, 37, 31, 45, 14, 52, 16, 15, 23, 43, 36, 3~
## $ Tot_Rooms <dbl> 1145, 2059, 1014, 944, 2391, 1114, 2512, 539,~
## $ Tot_Bedrooms <dbl> 209, 349, 252, 178, 451, 206, 356, 71, 835, 2~
## $ Population <dbl> 499, 825, 1064, 533, 798, 425, 795, 287, 2357~
## $ Households <dbl> 202, 334, 247, 193, 308, 207, 353, 66, 823, 1~
## $ Latitude <dbl> 33.94, 33.83, 34.03, 33.81, 37.29, 37.73, 33.~
## $ Longitude <dbl> -118.17, -118.10, -118.17, -118.20, -119.56, ~
## $ Distance_to_coast <dbl> 21064.3345, 10513.2231, 28063.1951, 7476.0654~
## $ Distance_to_LA <dbl> 14208.836, 28041.689, 7225.341, 27235.138, 37~
## $ Distance_to_SanDiego <dbl> 165280.04, 151557.98, 173589.00, 155354.70, 5~
## $ Distance_to_SanJose <dbl> 505621.00, 519121.29, 498068.93, 514914.25, 2~
## $ Distance_to_SanFrancisco <dbl> 573641.78, 587133.23, 566101.74, 582890.50, 2~
Para hacer la validación cruzada vamos a dividir los datos de TRAIN en 3-folds con 5 repeticiones.
p_folds <- vfold_cv(p_train, v=3, repeats = 5)
p_folds
## # 3-fold cross-validation repeated 5 times
## # A tibble: 15 x 3
## splits id id2
## <list> <chr> <chr>
## 1 <split [10320/5160]> Repeat1 Fold1
## 2 <split [10320/5160]> Repeat1 Fold2
## 3 <split [10320/5160]> Repeat1 Fold3
## 4 <split [10320/5160]> Repeat2 Fold1
## 5 <split [10320/5160]> Repeat2 Fold2
## 6 <split [10320/5160]> Repeat2 Fold3
## 7 <split [10320/5160]> Repeat3 Fold1
## 8 <split [10320/5160]> Repeat3 Fold2
## 9 <split [10320/5160]> Repeat3 Fold3
## 10 <split [10320/5160]> Repeat4 Fold1
## 11 <split [10320/5160]> Repeat4 Fold2
## 12 <split [10320/5160]> Repeat4 Fold3
## 13 <split [10320/5160]> Repeat5 Fold1
## 14 <split [10320/5160]> Repeat5 Fold2
## 15 <split [10320/5160]> Repeat5 Fold3
Verificamos los splits de validación cruzada, son 3 folds que se repiten 5 veces con los siguientes campos (analysis - entrenamiento / assess - validación)
p_folds$splits
## [[1]]
## <Analysis/Assess/Total>
## <10320/5160/15480>
##
## [[2]]
## <Analysis/Assess/Total>
## <10320/5160/15480>
##
## [[3]]
## <Analysis/Assess/Total>
## <10320/5160/15480>
##
## [[4]]
## <Analysis/Assess/Total>
## <10320/5160/15480>
##
## [[5]]
## <Analysis/Assess/Total>
## <10320/5160/15480>
##
## [[6]]
## <Analysis/Assess/Total>
## <10320/5160/15480>
##
## [[7]]
## <Analysis/Assess/Total>
## <10320/5160/15480>
##
## [[8]]
## <Analysis/Assess/Total>
## <10320/5160/15480>
##
## [[9]]
## <Analysis/Assess/Total>
## <10320/5160/15480>
##
## [[10]]
## <Analysis/Assess/Total>
## <10320/5160/15480>
##
## [[11]]
## <Analysis/Assess/Total>
## <10320/5160/15480>
##
## [[12]]
## <Analysis/Assess/Total>
## <10320/5160/15480>
##
## [[13]]
## <Analysis/Assess/Total>
## <10320/5160/15480>
##
## [[14]]
## <Analysis/Assess/Total>
## <10320/5160/15480>
##
## [[15]]
## <Analysis/Assess/Total>
## <10320/5160/15480>
Los datos del archivo TEST quedan aparte de la validación cruzada y no son incluidos hasta el final del modelo.
head(p_test)
## # A tibble: 6 x 14
## Median_House_Value Median_Income Median_Age Tot_Rooms Tot_Bedrooms Population
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 358500 8.30 21 7099 1106 2401
## 2 352100 7.26 52 1467 190 496
## 3 341300 5.64 52 1274 235 558
## 4 342200 3.85 52 1627 280 565
## 5 241400 3.12 52 3104 687 1157
## 6 261100 3.69 52 3549 707 1551
## # i 8 more variables: Households <dbl>, Latitude <dbl>, Longitude <dbl>,
## # Distance_to_coast <dbl>, Distance_to_LA <dbl>, Distance_to_SanDiego <dbl>,
## # Distance_to_SanJose <dbl>, Distance_to_SanFrancisco <dbl>
Eliminamos las correlaciones, centramos y escalamos los datos. Variable a predecir: Median_House_Value contra el resto de las otras variables.
recipe_dt <- p_train %>%
recipe(Median_House_Value~.) %>% #la variable que voy a predecir
step_corr(all_predictors()) %>% #elimino las correlaciones
step_center(all_predictors(), -all_outcomes()) %>% #centrado
step_scale(all_predictors(), -all_outcomes()) %>% #escalado
prep()
Verificamos como ha quedado la receta creada.
recipe_dt
##
## -- Recipe ----------------------------------------------------------------------
##
## -- Inputs
## Number of variables by role
## outcome: 1
## predictor: 13
##
## -- Training information
## Training data contained 15480 data points and no incomplete rows.
##
## -- Operations
## * Correlation filter on: Tot_Bedrooms, Households, ... | Trained
## * Centering for: Median_Income, Median_Age, Tot_Rooms, ... | Trained
## * Scaling for: Median_Income, Median_Age, Tot_Rooms, ... | Trained
set.seed(123)
tree_spec <- decision_tree() %>% #arboles de decisión
set_engine("rpart") %>% #librería rpart
set_mode("regression") #regresión
tree_spec
## Decision Tree Model Specification (regression)
##
## Computational engine: rpart
Para trabajar de manera ordenada utilizamos creamos un workflow.
tree_wf <- workflow() %>%
add_recipe(recipe_dt) %>% #agrego la receta
add_model(tree_spec) #agrego el modelo
# Imprimo el workflow
tree_wf
## == Workflow ====================================================================
## Preprocessor: Recipe
## Model: decision_tree()
##
## -- Preprocessor ----------------------------------------------------------------
## 3 Recipe Steps
##
## * step_corr()
## * step_center()
## * step_scale()
##
## -- Model -----------------------------------------------------------------------
## Decision Tree Model Specification (regression)
##
## Computational engine: rpart
Entrenamos el modelo.
set.seed(123)
tree_fit <- tree_spec %>%
fit(Median_House_Value ~ ., data = p_train)
#imprimo el modelo
tree_fit
## parsnip model object
##
## n= 15480
##
## node), split, n, deviance, yval
## * denotes terminal node
##
## 1) root 15480 2.056409e+14 206412.6
## 2) Median_Income< 5.0973 12342 1.021372e+14 173689.9
## 4) Distance_to_coast>=46565.56 3780 8.251357e+12 106757.2 *
## 5) Distance_to_coast< 46565.56 8562 6.947525e+13 203239.7
## 10) Median_Income< 3.10635 3680 2.130515e+13 165254.1 *
## 11) Median_Income>=3.10635 4882 3.885766e+13 231872.8
## 22) Distance_to_coast>=6010.353 3865 2.228468e+13 215696.6 *
## 23) Distance_to_coast< 6010.353 1017 1.171804e+13 293349.0 *
## 3) Median_Income>=5.0973 3138 3.831050e+13 335113.7
## 6) Median_Income< 6.81955 2134 1.926659e+13 294102.5
## 12) Distance_to_coast>=10099.55 1444 1.002909e+13 270931.5 *
## 13) Distance_to_coast< 10099.55 690 6.839741e+12 342593.9 *
## 7) Median_Income>=6.81955 1004 7.825881e+12 422282.7 *
Incluimos la columna de la predicción (.pred_tree) en los datos de TRAIN para luego compara con el valor verdadero.
results <- p_train %>%
bind_cols(predict(tree_fit, p_train) %>%
rename(.pred_tree = .pred))
Verificamos como quedo el nuevo dataset.
head(results)
## # A tibble: 6 x 15
## Median_House_Value Median_Income Median_Age Tot_Rooms Tot_Bedrooms Population
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 165500 4.64 17 1145 209 499
## 2 225200 4.06 37 2059 349 825
## 3 125500 2.42 31 1014 252 1064
## 4 206900 3.48 45 944 178 533
## 5 114600 3.09 14 2391 451 798
## 6 175000 2.56 52 1114 206 425
## # i 9 more variables: Households <dbl>, Latitude <dbl>, Longitude <dbl>,
## # Distance_to_coast <dbl>, Distance_to_LA <dbl>, Distance_to_SanDiego <dbl>,
## # Distance_to_SanJose <dbl>, Distance_to_SanFrancisco <dbl>, .pred_tree <dbl>
Calculamos la métricas del modelo.
metrics(results, truth = Median_House_Value, estimate = .pred_tree)
## # A tibble: 3 x 3
## .metric .estimator .estimate
## <chr> <chr> <dbl>
## 1 rmse standard 75506.
## 2 rsq standard 0.571
## 3 mae standard 56192.
Ejecutamos la predicción propiamente dicha del modelo de ML. Los resultados finales son los que compararemos con el proximo modelo a ejecutar para ver cual de los 2 predice mejor la variable.
results_test <- p_test %>%
bind_cols(predict(tree_fit, p_test) %>%
rename(.pred_tree = .pred))
Verificamos las primera filas del dataset de test con las predicciones en la columna final.
head(results_test)
## # A tibble: 6 x 15
## Median_House_Value Median_Income Median_Age Tot_Rooms Tot_Bedrooms Population
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 358500 8.30 21 7099 1106 2401
## 2 352100 7.26 52 1467 190 496
## 3 341300 5.64 52 1274 235 558
## 4 342200 3.85 52 1627 280 565
## 5 241400 3.12 52 3104 687 1157
## 6 261100 3.69 52 3549 707 1551
## # i 9 more variables: Households <dbl>, Latitude <dbl>, Longitude <dbl>,
## # Distance_to_coast <dbl>, Distance_to_LA <dbl>, Distance_to_SanDiego <dbl>,
## # Distance_to_SanJose <dbl>, Distance_to_SanFrancisco <dbl>, .pred_tree <dbl>
Corremos las métricas del modelo final.
MI_DT_METRICAS <- metrics(results_test, truth = Median_House_Value, estimate = .pred_tree)
MI_DT_METRICAS
## # A tibble: 3 x 3
## .metric .estimator .estimate
## <chr> <chr> <dbl>
## 1 rmse standard 77809.
## 2 rsq standard 0.549
## 3 mae standard 57319.
Ploteamos el árbol del modelo.
tree_fit %>%
extract_fit_engine() %>%
rpart.plot(roundint = FALSE, extra=1)
Ploteamos las predicciones y el valor verdadero de los resultados del dataset TEST.
results_test %>%
ggplot(aes(Median_House_Value, .pred_tree)) +
geom_abline(lty = 2, color = "black", size = 1) +
geom_point(size = 2, alpha = 0.4, show.legend = FALSE, color = "salmon") +
theme_light() +
labs(title="Propiedades del estado de California",
subtitle= "Predicción vs. Valor Medio de la Vivienda (ML - DT sin tunning)",
x = "Median House Value",
y = "Predicción",
caption = "Fuente: https://www.kaggle.com/fedesoriano/california-housing-prices-data-extra-features ")
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## i Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
Variable a predecir: Median_House_Value contra el resto de las otras variables.
recipe_rf <- p_train %>%
recipe(Median_House_Value~.) %>%
step_corr(all_predictors()) %>% #elimino las correlaciones
step_center(all_predictors(), -all_outcomes()) %>% #centrado
step_scale(all_predictors(), -all_outcomes()) %>% #escalado
prep()
rf_spec <- rand_forest() %>%
set_engine("ranger") %>%
set_mode("regression")
rf_spec
## Random Forest Model Specification (regression)
##
## Computational engine: ranger
Creamos un workflow para trabajar de manera ordenada.
rf_wf <- workflow() %>%
add_recipe(recipe_rf) %>%
add_model(rf_spec)
rf_wf
## == Workflow ====================================================================
## Preprocessor: Recipe
## Model: rand_forest()
##
## -- Preprocessor ----------------------------------------------------------------
## 3 Recipe Steps
##
## * step_corr()
## * step_center()
## * step_scale()
##
## -- Model -----------------------------------------------------------------------
## Random Forest Model Specification (regression)
##
## Computational engine: ranger
set.seed(123)
rf_res <- rf_wf %>%
fit_resamples(
p_folds,
control = control_resamples(save_pred = TRUE)
)
## Warning: package 'ranger' was built under R version 4.3.2
glimpse(rf_res)
## Rows: 15
## Columns: 6
## $ splits <list> [<vfold_split[10320 x 5160 x 15480 x 14]>], [<vfold_spli~
## $ id <chr> "Repeat1", "Repeat1", "Repeat1", "Repeat2", "Repeat2", "R~
## $ id2 <chr> "Fold1", "Fold2", "Fold3", "Fold1", "Fold2", "Fold3", "Fo~
## $ .metrics <list> [<tbl_df[2 x 4]>], [<tbl_df[2 x 4]>], [<tbl_df[2 x 4]>],~
## $ .notes <list> [<tbl_df[0 x 3]>], [<tbl_df[0 x 3]>], [<tbl_df[0 x 3]>],~
## $ .predictions <list> [<tbl_df[5160 x 4]>], [<tbl_df[5160 x 4]>], [<tbl_df[516~
rf_res %>%
collect_metrics()
## # A tibble: 2 x 6
## .metric .estimator mean n std_err .config
## <chr> <chr> <dbl> <int> <dbl> <chr>
## 1 rmse standard 50442. 15 174. Preprocessor1_Model1
## 2 rsq standard 0.810 15 0.00131 Preprocessor1_Model1
final_model <- finalize_model(rf_spec, select_best(rf_res, "rmse"))
final_model
## Random Forest Model Specification (regression)
##
## Computational engine: ranger
El que tiene el RMSE mas bajo el es modelo que posee menor error por lo tanto es el que ajusta mejor la predicción.
final_rs <- last_fit(final_model, Median_House_Value ~ ., p_split)
final_rs
## # Resampling results
## # Manual resampling
## # A tibble: 1 x 6
## splits id .metrics .notes .predictions .workflow
## <list> <chr> <list> <list> <list> <list>
## 1 <split [15480/5160]> train/test spl~ <tibble> <tibble> <tibble> <workflow>
MII_RF_METRICAS <- final_rs %>%
collect_metrics()
MII_RF_METRICAS
## # A tibble: 2 x 4
## .metric .estimator .estimate .config
## <chr> <chr> <dbl> <chr>
## 1 rmse standard 45460. Preprocessor1_Model1
## 2 rsq standard 0.848 Preprocessor1_Model1
Verificamos la diferencia entre las predicciones y la variable analizada.
final_rs %>%
collect_predictions()
## # A tibble: 5,160 x 5
## id .pred .row Median_House_Value .config
## <chr> <dbl> <int> <dbl> <chr>
## 1 train/test split 417173. 2 358500 Preprocessor1_Model1
## 2 train/test split 406694. 3 352100 Preprocessor1_Model1
## 3 train/test split 342056. 4 341300 Preprocessor1_Model1
## 4 train/test split 268262. 5 342200 Preprocessor1_Model1
## 5 train/test split 244060. 8 241400 Preprocessor1_Model1
## 6 train/test split 271396. 10 261100 Preprocessor1_Model1
## 7 train/test split 225722. 11 281500 Preprocessor1_Model1
## 8 train/test split 267100. 12 241800 Preprocessor1_Model1
## 9 train/test split 151927. 19 158700 Preprocessor1_Model1
## 10 train/test split 138314. 20 162900 Preprocessor1_Model1
## # i 5,150 more rows
Graficamos las predicciones del modelo vs. el valor medio de las viviendas.
collect_predictions(final_rs) %>%
ggplot(aes(Median_House_Value, .pred)) +
geom_abline(lty = 2, color = "black", size = 1) +
geom_point(alpha = 0.3, color = "salmon") +
coord_fixed() +
theme_light() +
labs(title="Propiedades del estado de California",
subtitle= "Estimación Valor Medio de la Vivienda (ML - RF sin tunning)",
x = "Median House Value",
y = "Predicción",
caption = "Fuente: https://www.kaggle.com/fedesoriano/california-housing-prices-data-extra-features ")
Ploteamos las variables mas importantes del modelo. En el gráfico se puede observar que la mas importante es Median_Income seguida de Distance_to_coast
final_model %>%
set_engine("ranger", importance = "permutation") %>%
fit(Median_House_Value ~ .,
data = juice(recipe_rf)) %>%
vip(geom = "point") +
theme_light() +
labs(title="Estimación Valor Medio de la Vivienda (ML - RF sin tunning)",
subtitle= "Variables mas importante",
y = "Importacia",
x = "Variable",
caption = "Fuente: https://www.kaggle.com/fedesoriano/california-housing-prices-data-extra-features ")
Para final el presente estudio compararemos las métricas obtenidas por los 2 modelos (DT sin tunning y RF sin tunning) para comprobar cual de los 2 tiene menor error por lo que ajusta mejor la predicción de la variable elegida.
MI_DT_METRICAS
## # A tibble: 3 x 3
## .metric .estimator .estimate
## <chr> <chr> <dbl>
## 1 rmse standard 77809.
## 2 rsq standard 0.549
## 3 mae standard 57319.
MII_RF_METRICAS
## # A tibble: 2 x 4
## .metric .estimator .estimate .config
## <chr> <chr> <dbl> <chr>
## 1 rmse standard 45460. Preprocessor1_Model1
## 2 rsq standard 0.848 Preprocessor1_Model1
Se puede observa el RSQ (R2) es mas elevado en el modelo 2 (RF sin tuning). Este valor no suele tenerse muy en cuenta en modelos de ML. Respecto al RMSE (raíz error cuadrático medio) es mas bajo en el modelo 2 (RF sin tuning) por lo que comparando ambos, si hay que optar por uno de ambos, se elegiría el Modelo 2 ya que tiene menos error y ajusta mejor la predicción de la variable Median House Value.
Final TP.-