El presente análisis tiene como finalidad estudiar el precio de ventas de casas en el condado de King, Washington durante los años 2014 y 2015, los datos se extrajeron de la reconocida comunidad de científicos de datos Kaggle.
La meta será desarrollar un modelo de apredizaje supervisado que nos permita predecir el valor de venta de una determinada casa en el condado de King, de acuerdo a ciertas variables / características de la casa que se encuentran en los datos. Para esto haremos uso del conocido modelo de regresión lineal multivariante y, además, se utilizarán modelos de ML (Random Forest y XGboost).
Se hará uso del ecosistema de paquetes que ofrece Tidymodels para el desarrollo de los distintos modelos de aprendizaje supervisado.
En total hay 21 columnas el los datos, las cuáles se describen a continuación:
| Name | house |
| Number of rows | 21613 |
| Number of columns | 21 |
| _______________________ | |
| Column type frequency: | |
| character | 1 |
| numeric | 19 |
| POSIXct | 1 |
| ________________________ | |
| Group variables | None |
Variable type: character
| skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
|---|---|---|---|---|---|---|---|
| id | 0 | 1 | 10 | 10 | 0 | 21436 | 0 |
Variable type: numeric
| skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 |
|---|---|---|---|---|---|---|---|---|---|
| price | 0 | 1 | 540088.14 | 367127.20 | 75000.00 | 321950.00 | 450000.00 | 645000.00 | 7700000.00 |
| bedrooms | 0 | 1 | 3.37 | 0.93 | 0.00 | 3.00 | 3.00 | 4.00 | 33.00 |
| bathrooms | 0 | 1 | 2.11 | 0.77 | 0.00 | 1.75 | 2.25 | 2.50 | 8.00 |
| sqft_living | 0 | 1 | 2079.90 | 918.44 | 290.00 | 1427.00 | 1910.00 | 2550.00 | 13540.00 |
| sqft_lot | 0 | 1 | 15106.97 | 41420.51 | 520.00 | 5040.00 | 7618.00 | 10688.00 | 1651359.00 |
| floors | 0 | 1 | 1.49 | 0.54 | 1.00 | 1.00 | 1.50 | 2.00 | 3.50 |
| waterfront | 0 | 1 | 0.01 | 0.09 | 0.00 | 0.00 | 0.00 | 0.00 | 1.00 |
| view | 0 | 1 | 0.23 | 0.77 | 0.00 | 0.00 | 0.00 | 0.00 | 4.00 |
| condition | 0 | 1 | 3.41 | 0.65 | 1.00 | 3.00 | 3.00 | 4.00 | 5.00 |
| grade | 0 | 1 | 7.66 | 1.18 | 1.00 | 7.00 | 7.00 | 8.00 | 13.00 |
| sqft_above | 0 | 1 | 1788.39 | 828.09 | 290.00 | 1190.00 | 1560.00 | 2210.00 | 9410.00 |
| sqft_basement | 0 | 1 | 291.51 | 442.58 | 0.00 | 0.00 | 0.00 | 560.00 | 4820.00 |
| yr_built | 0 | 1 | 1971.01 | 29.37 | 1900.00 | 1951.00 | 1975.00 | 1997.00 | 2015.00 |
| yr_renovated | 0 | 1 | 84.40 | 401.68 | 0.00 | 0.00 | 0.00 | 0.00 | 2015.00 |
| zipcode | 0 | 1 | 98077.94 | 53.51 | 98001.00 | 98033.00 | 98065.00 | 98118.00 | 98199.00 |
| lat | 0 | 1 | 47.56 | 0.14 | 47.16 | 47.47 | 47.57 | 47.68 | 47.78 |
| long | 0 | 1 | -122.21 | 0.14 | -122.52 | -122.33 | -122.23 | -122.12 | -121.32 |
| sqft_living15 | 0 | 1 | 1986.55 | 685.39 | 399.00 | 1490.00 | 1840.00 | 2360.00 | 6210.00 |
| sqft_lot15 | 0 | 1 | 12768.46 | 27304.18 | 651.00 | 5100.00 | 7620.00 | 10083.00 | 871200.00 |
Variable type: POSIXct
| skim_variable | n_missing | complete_rate | min | max | median | n_unique |
|---|---|---|---|---|---|---|
| date | 0 | 1 | 2014-05-02 | 2015-05-27 | 2014-10-16 | 372 |
Haciendo uso de la función skim del paquete skimr se genera un resumen general de todas las variables, de este resumen se destacan los siguientes puntos:
Para comenzar con el análsis exploratorio de los datos, es bueno visualizar la distribución de la variable respuesta o target que queremos estudiar.
Se observa que los datos están sesgados a la derecha, realizar una transformación logarítmica ayudara a estabilizar la varianza y a poder visualizar mejor la distribución de los precios.
Una vez realizada la transformación, se observa que igual permanece una cola a derecha que indica que varias casas tienen un precio de venta muy por encima del promedio del resto de las casas en los datos. Este tipo de situación son complicadas para un modelo lineal debido a que conllevan a incumplir con el supuesto de presencia de valores atípicos o influénciales.
En la descripción de los datos, se mencionan ciertas variables que indican la calidad de ciertas características de las casas en ventas. A continuación, se realizan varios gráficos para entender la relación de estas variables discretas con el precio de venta de las casas.
La mayoría de las casas se concentran en las categorías siete, ocho y nueve. Es claro que se evidencia un crecimiento en la mediana del precio de venta en las casas con mayor categoría de construcción, sin embargo, hay ciertas categorías que parecen englobar muy poca cantidad de casas.
La mayoria de las casas se concentran en el nivel “0” de calidad, además, se contempla que la mediana del precio de venta aumenta de acuerdo al nivel de calidad de la casa.
En los datos se encuentran variables relacionadas con ciertas caracteristicas de la casas, como el número de cuarto, baños, pisos, el areá en general de la casa, etc. Se procede a realizar un analisis de correlación para cuantificar la relación de estas variables con le precio de venta de la casas.
La variable que presenta mayor relación lineal con el precio de venta de las casas es Sqft_living (el número de pies cuadrados del espacio habitable de la casa), hay algunas correlacionea altas entre las variables explicativas como Sqft_living y Sqft_above.
Haciendo uso de las variables latitud y longitud se puede realizar un gráfico del mapa del condado de King donde podamos observar cómo se distribuye el precio medio de venta al rededor del espacio geográfico.
Realizar modelos de aprendizaje supervisado haciendo uso de todos los datos disponibles no suele ser una buena opción debido a que esto suele causar problemas de “sobre ajuste”. Es mejor particionar los datos en dos conjuntos, una para entrenar los modelos y otro para probar o testear los modelos, esto nos permitirá saber el desempeño de los modelos en datos nuevos.
# Partición 80% / 20%
set.seed(1234)
split <- initial_split(data = house, prop = 0.80, strata = price)
# Datos de entrenamiento
train_set <- training(split)
dim(train_set)
## [1] 17289 21
# Datos de prueba
test_set <- testing(split)
dim(test_set)
## [1] 4324 21
Aparte de particionar los datos en conjuntos de entrenamiento y prueba se particionara el conjunto de entrenamiento en 5 subconjuntos de datos uniformes, esto permite tener un mejor entendimiento del comportamiento de los modelos.
El proceso es ajustar los modelos en uno de los subconjuntos y utilizar los otros cuatros para computar métricas de rendimiento, este proceso se repite hasta que todos los subconjuntos sean utilizados para ajustar los modelos. Por lo general, luego se toma la media de las métricas de rendimiento de este proceso.
set.seed(1234)
folds <- vfold_cv(train_set, strata = price, v = 5)
folds
## # 5-fold cross-validation using stratification
## # A tibble: 5 x 2
## splits id
## <list> <chr>
## 1 <split [13830/3459]> Fold1
## 2 <split [13830/3459]> Fold2
## 3 <split [13830/3459]> Fold3
## 4 <split [13833/3456]> Fold4
## 5 <split [13833/3456]> Fold5
En base a lo observado en el análisis exploratorio se creará un récipe con el preprocesamiento haciendo uso de ingeniería de variables para mejorar el desempeño de los algoritmos de Machine Learning.
A continuación, se explica los pasos que se incluirán en el récipe para el preprocesamiento de los datos:
recipe <- recipe(price ~ ., data = train_set) %>%
update_role(id, new_role = "id variable") %>%
step_mutate_at(date, fn = as_date) %>%
step_mutate_at(floors, fn = round) %>%
step_corr(all_numeric_predictors(), threshold = 0.85) %>%
step_mutate(
is_renovated = if_else(yr_renovated == 0, "No", "Yes"),
waterfront = if_else(waterfront == 0, "No", "Yes"),
house_age = if_else(year(date) - yr_built < 0, 0, year(date) - yr_built),
have_basement = if_else(sqft_basement == 0, "No", "Yes")
) %>%
step_string2factor(is_renovated, waterfront, have_basement, levels = c("Yes", "No")) %>%
step_filter(!(bedrooms %in% c(33, 0)), bathrooms != 0) %>%
step_date(date, features = c("quarter", "week", "month"), label = F) %>%
step_rm(date) %>%
step_dummy(all_nominal_predictors())
Usando las funciones bake y prep se puede apreciar el resultado del preprocesamiento de los datos de entrenamiento.
# Resultado de preprocesamiento
bake(prep(recipe), new_data = NULL) %>%
glimpse()
## Rows: 17,276
## Columns: 25
## $ id <fct> 5631500400, 1321400060, 2008000270, 2414600126, 00160~
## $ bedrooms <dbl> 2, 3, 3, 3, 2, 3, 5, 2, 3, 4, 3, 3, 3, 4, 3, 3, 4, 3,~
## $ bathrooms <dbl> 1.00, 2.25, 1.50, 1.00, 1.00, 1.00, 2.50, 1.50, 2.00,~
## $ sqft_lot <dbl> 10000, 6819, 9711, 7470, 9850, 9774, 6300, 9643, 4697~
## $ floors <dbl> 1, 2, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 2, 2, 1, 2, 1,~
## $ view <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,~
## $ condition <dbl> 3, 3, 3, 3, 4, 4, 3, 3, 5, 2, 4, 3, 3, 3, 3, 3, 3, 4,~
## $ grade <dbl> 6, 7, 7, 7, 7, 7, 8, 7, 6, 7, 6, 6, 7, 7, 8, 7, 7, 7,~
## $ sqft_above <dbl> 770, 1715, 1060, 1050, 1200, 1250, 2270, 1070, 1710, ~
## $ sqft_basement <dbl> 0, 0, 0, 730, 0, 0, 0, 0, 0, 330, 360, 0, 0, 0, 0, 0,~
## $ yr_built <dbl> 1933, 1995, 1963, 1960, 1921, 1969, 1995, 1985, 1941,~
## $ yr_renovated <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,~
## $ zipcode <dbl> 98028, 98003, 98198, 98146, 98002, 98003, 98092, 9803~
## $ lat <dbl> 47.7379, 47.3097, 47.4095, 47.5123, 47.3089, 47.3343,~
## $ long <dbl> -122.233, -122.327, -122.315, -122.337, -122.210, -12~
## $ sqft_living15 <dbl> 2720, 2238, 1650, 1780, 1060, 1280, 2240, 1220, 1030,~
## $ sqft_lot15 <dbl> 8062, 6819, 9711, 8113, 5095, 8850, 7005, 8386, 4705,~
## $ price <dbl> 180000, 257500, 291850, 229500, 189000, 230000, 28500~
## $ house_age <dbl> 82, 19, 52, 55, 93, 46, 19, 29, 73, 46, 55, 49, 59, 6~
## $ date_quarter <int> 1, 2, 1, 2, 4, 2, 3, 2, 4, 1, 3, 1, 3, 3, 2, 3, 4, 3,~
## $ date_week <dbl> 8, 26, 3, 15, 49, 17, 27, 20, 44, 7, 30, 12, 37, 33, ~
## $ date_month <dbl> 2, 6, 1, 4, 12, 4, 7, 5, 11, 2, 7, 3, 9, 8, 4, 8, 10,~
## $ waterfront_No <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,~
## $ is_renovated_No <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,~
## $ have_basement_No <dbl> 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1,~
A continuación, se procede a realizar el entrenamiento de los modelos en la validación cruzada. Para medir el rendimiento de los modelos se hará uso del error cuadrático medio o rmse por sus siglas en inglés, y de un diagrama de dispersión para poder ver visualmente la concordancia de los valores predecidos con los reales.
### Modelos ###
# Regresión lineal
lm_mod <- linear_reg(engine = "lm")
# Random Forest
rf_mod <- rand_forest(trees = 1000, engine = "ranger", mode = "regression")
# Xgbooot
xg_mod <- boost_tree(mode = "regression", engine = "xgboost", trees = 1000)
wf_recipe <- workflow() %>%
add_recipe(recipe)
El modelo que logra minimizar la métrica de error seleccionada (rmse) es el XGBoost, sin embargo, se observa que los tres modelos tienen problemas en captar las características de ciertas casas con los precios de venta más altos. Se procede a ajustar el modelo XGBoost en los datos de prueba debido a que es el modelo que presento mejor rendimiento en la validación cruzada.
Por ultimo utilizaremos el modelo seleccionado en los datos de prueba para ver su performance final, ciertamente estos resultados pueden ser mejorados realizando un tuneo de los hiperparámetros de los modelos de ML pero esto se hará en análisis posteriores.