Introducción

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.

Descripción de los datos

En total hay 21 columnas el los datos, las cuáles se describen a continuación:

  • ID: Correlativo númerico que distingue cada una de las casas vendidas.
  • Date: Fecha en la cual fue vendida la casa.
  • Price: Precio de venta de la casa (en dólares norteamericanos).
  • Bedrooms: Número de cuartos en la casa.
  • Bathrooms: Número de baños disponibles en la casa, donde un valor de 0.5 representa un baño con inodoro pero sin ducha.
  • Sqft_living: Número de pies cuadrados del espacio habitable de la casa.
  • Sqft_lot: Pies cuadrados del espacio total del terreno donde se ubica la casa.
  • Floors: Cantidad de pisos en la casa.
  • Waterfront: Variable que indica la presencia o no de vista al mar en la casa.
  • View: Índice del 0 al 4 que indica que tan buena es la vista de la propiedad.
  • Condition: Índice del 1 al 5 para calificar la condición actual de la casa.
  • Grade: Índice del 1 al 13, el cuál califica el nivel de calidad de construcción de la casa.
  • Sqft_above: Cantidad de pies cuadrados del espacio interior de la casa. que está sobre el nivel del suelo.
  • Sqft_basement: Los pies cuadrados del espacio interior de la casa. que está por debajo del nivel del suelo.
  • Yr_built: El año en fue construida la casa.
  • Yr_renovated: Año de la última renovación de la casa.
  • Zipcode: Código postal del areá donde se encuentra la casa.
  • Lat: Latitud de la ubicación de la casa.
  • Long: Longitud de la ubicación de la casa.
  • Sqft_living15: Los pies cuadrados de espacio habitable de la casa interior para los 15 vecinos más cercanos.
  • Sqft_lot15: Los metros cuadrados de los terrenos de los 15 vecinos más cercanos.

Overview del dataset

Data summary
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:

  • No hay valores perdidos en ninguna variable.
  • Hay inconsistencias en las variables que contabizan el número de habitaciones y baños de las casas, hay registros con 0 baños y habitaciones, además, existe un registro con 33 habitaciones.

Análisis exploratorio

Distribución de los precios

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.

Calidad de la casa

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.

Calidad de construcción

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.

Condición actual

Calidad de la vista

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.

Características 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.

Análisis de correlación

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.

Ubicación física

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.

Partición de los datos

Entrenamiento y prueba

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

Validación cruzada

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

Ingeniería de variables

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.

Récipe de preprocesamiento

A continuación, se explica los pasos que se incluirán en el récipe para el preprocesamiento de los datos:

  • Se define el rol de la variable “id” como una variable identificativa de esta forma no será considerada como un variable explicativa.
  • Se aplica la función “as_date” a la variable date para poder corregir su formato y que sea interpretada como una fecha.
  • Se corrige la variable “floors” la cual indica el número de casas, debido a que hay casos con número de pisos no enteros.
  • Se crean tres variables del tipo dicotómicas que identifican si la casa fue renovada alguna vez, si posee vista al mar y si posee alguna habitación por debajo del nivel del suelo, además, se crea una variable discreta con la edad en años de la casa a la fecha de venta.
  • Se convierten a factor las variables dicotómicas creadas en el paso anterior.
  • Se eliminar lis registros con inconsistencias observados en el análisis exploratorio de los datos.
  • A partir de la variable fecha se extraen distintos features para usarlos como variables explicativas.
  • Se remueve de los datos la variable fecha.
  • Se transforman a dummy todas las variables de tipo factor en 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,~

Rendimiendo en validación cruzada

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)

Regresión lineal

Bosque aleatorio

XGBoost

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.

Rendimiento en conjunto de prueba

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.