Se comienza el análisis propuesto cargando los paquetes necesarios, leyendo el conjunto de datos y mostrando la estructura del mismo a fin de conocer los atributos sobre los que se van a trabajar. Se elimina el atributo id y se transforman a factores las variables l3 y property_type.
# Borrar variables del ambiente
rm(list = objects())
# Carga de paquetes necesarios para hacer los gráficos
require(Cairo)
require(ggfortify)
require(ggpubr)
require(Hmisc)
require(knitr)
require(tidyverse)
# Uso de Cairo para renderizar los gráficos.
options(bitmapType = "cairo")
# Cargar conjunto de datos de precios de propiedades.
datos <- base::readRDS(file = "ar_properties.rds") %>%
# Pasamos las variables categorias l3 y property_type a factor
dplyr::mutate(l3 = as.factor(l3), property_type = as.factor(property_type)) %>%
# Eliminamos el id, dado que no se va a utilizar en los ajustes
dplyr::select(-id)
# Mostrar estructura
head(datos)
## # A tibble: 6 x 7
## l3 rooms bathrooms surface_total surface_covered price property_type
## <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <fct>
## 1 Velez… 3 2 95 69 199900 Casa
## 2 Nuñez 1 1 44 38 147000 Departamento
## 3 Almag… 1 1 40 37 92294 Departamento
## 4 Almag… 1 1 49 44 115000 Departamento
## 5 Almag… 1 1 40 37 77000 Departamento
## 6 Almag… 1 1 40 37 88900 Departamento
Se procede a crear el modelo con las variables propuestas, dos de las cuales (property_type y l3) son categóricas. La función lm automáticamente maneja estas variables transformándolas en N-1 dummies para el caso de factores con N niveles.
# Ajustamos un modelo lineal múltiple
modelo.multiple.1 <- stats::lm(formula = price ~ ., data = datos)
# Pasamos los coeficientes a un data.frame para trabajarlos mejor
tabla.modelo.multiple.1 <- broom::tidy(modelo.multiple.1)
# Mostramos una tabla con los valores de R^2
knitr::kable(
broom::glance(modelo.multiple.1) %>%
dplyr::select(r.squared, adj.r.squared),
col.names = c("R^2", "R^2 ajustado")
)
| R^2 | R^2 ajustado |
|---|---|
| 0.7764191 | 0.7761168 |
El valor de \(R^2\) ajustado es de aproximadamente 0.78, lo que implica que se explica el 78% de la variable de respuesta. Las variables categóricas l3 y property_type fueron automáticamente transformadas en dummies para poder ser ajustadas por el modelo lineal. A fin de evitar problemas de multicolinealidad, las variables con N categorías fueron transformadas en N-1 atributos binarios. En el caso del atributo l3, el barrio de Abasto (por ser el primer nivel de la variable categórica) no figura como dummy, así como también sucede en el caso de las casas para el atributo property_type. Esto significa que, si todas las variables dummies valen 0, el modelo lineal resultante es aplicable a casas del barrio de Abasto. A continuación, se analizarán respectivamente los coeficientes para a las variables numéricas, dummies asociadas a property_type y dummies asociadas a l3.
knitr::kable(
dplyr::filter(tabla.modelo.multiple.1,
is.na(stringr::str_match(term, "l3")) &
is.na(stringr::str_match(term, "property_type"))),
col.names = c("Covariable", "Coeficiente", "Desvío Estándar", "Estadístico t", "P-Valor")
)
| Covariable | Coeficiente | Desvío Estándar | Estadístico t | P-Valor |
|---|---|---|---|---|
| (Intercept) | -109406.6105 | 4788.67371 | -22.846954 | 0 |
| rooms | -3961.2733 | 444.57682 | -8.910211 | 0 |
| bathrooms | 34040.9842 | 644.28057 | 52.835652 | 0 |
| surface_total | 919.0846 | 23.52494 | 39.068514 | 0 |
| surface_covered | 1457.1785 | 28.73285 | 50.714724 | 0 |
Se observa que todos los coeficientes asociados a variables numéricas son altamente significativos, al igual que el intercept (\(\beta_0\)). Los valores de los coeficientes asociados a cantidad de baños y superficie (total y cubierta) son positivos, indicando un incremento en el precio esperado de acuerdo al monto indicado por cada coeficiente por cada incremento en una unidad de cada variable. Además, el coeficiente asociado a la superficie cubierta es mayor que el asociado a la superficie total, lo cual indica que aumentar un metro cuadrado la superficie cubierta aumenta el precio esperado más que incrementar un metro cuadrado la superficie total (sin distinción de tipo). Sin embargo, el coeficiente asociado a la cantidad de dormitorios es negativo, indicando que incrementar en una unidad la cantidad de dormitorios produce una disminución en el precio esperado del inmueble. Esto de por si es un poco difícil de interpretar, dado que a priori se esperaría lo contrario.
knitr::kable(
dplyr::filter(tabla.modelo.multiple.1,
! is.na(stringr::str_match(term, "property_type"))) %>%
dplyr::mutate(property_type = stringr::str_match(term, "property_type(.+)")[,2]) %>%
dplyr::select(property_type, estimate, std.error, statistic, p.value),
col.names = c("Tipo de propieadad", "Coeficiente", "Desvío Estándar", "Estadístico t", "P-Valor")
)
| Tipo de propieadad | Coeficiente | Desvío Estándar | Estadístico t | P-Valor |
|---|---|---|---|---|
| Departamento | 92653.32 | 2191.226 | 42.28377 | 0 |
| PH | 46779.37 | 2274.940 | 20.56291 | 0 |
En el caso del tipo de propiedad, vemos que en ambos casos los coeficientes asociados son altamente significativos y positivos. El valor de cada uno de los coeficientes indica el incremento en el precio esperado de un departamento o PH respecto de una casa que está en el mismo barrio y tiene exactamente los mismos valores de superficie total, cubierta, cantidad de dormitorios y cantidad de baños.
knitr::kable(
dplyr::filter(tabla.modelo.multiple.1,
! is.na(stringr::str_match(term, "l3"))) %>%
dplyr::mutate(l3 = stringr::str_match(term, "l3(.+)")[,2]) %>%
dplyr::select(l3, estimate, std.error, statistic, p.value) %>%
dplyr::arrange(estimate),
col.names = c("Barrio", "Coeficiente", "Desvío Estándar", "Estadístico t", "P-Valor")
)
| Barrio | Coeficiente | Desvío Estándar | Estadístico t | P-Valor |
|---|---|---|---|---|
| Villa Soldati | -136489.9104 | 18944.293 | -7.2048037 | 0.0000000 |
| Villa Lugano | -83039.1752 | 6533.350 | -12.7100463 | 0.0000000 |
| Pompeya | -79977.1680 | 8035.741 | -9.9526807 | 0.0000000 |
| Catalinas | -76321.9516 | 33563.737 | -2.2739408 | 0.0229741 |
| Boca | -47540.5983 | 7076.199 | -6.7183810 | 0.0000000 |
| Constitución | -47292.9782 | 6321.627 | -7.4811400 | 0.0000000 |
| Parque Patricios | -36808.0245 | 5973.289 | -6.1621035 | 0.0000000 |
| Tribunales | -34608.1688 | 8924.626 | -3.8778283 | 0.0001055 |
| Parque Avellaneda | -34398.9543 | 7598.086 | -4.5273183 | 0.0000060 |
| Mataderos | -33863.4291 | 5424.790 | -6.2423487 | 0.0000000 |
| Villa Riachuelo | -32775.6553 | 17171.096 | -1.9087690 | 0.0562981 |
| Monserrat | -32431.4882 | 5228.464 | -6.2028715 | 0.0000000 |
| Congreso | -32314.9707 | 5494.752 | -5.8810608 | 0.0000000 |
| Once | -30757.8292 | 5456.508 | -5.6369073 | 0.0000000 |
| Centro / Microcentro | -29046.4887 | 6781.800 | -4.2830057 | 0.0000185 |
| Floresta | -28315.6460 | 5069.382 | -5.5856210 | 0.0000000 |
| San Nicolás | -26247.5534 | 5168.960 | -5.0779177 | 0.0000004 |
| Velez Sarsfield | -25943.6879 | 8303.755 | -3.1243321 | 0.0017832 |
| Balvanera | -24788.2748 | 4551.651 | -5.4459968 | 0.0000001 |
| San Cristobal | -23739.7493 | 4955.133 | -4.7909411 | 0.0000017 |
| Parque Chacabuco | -22537.8253 | 5314.363 | -4.2409266 | 0.0000223 |
| Flores | -22510.2684 | 4536.153 | -4.9624141 | 0.0000007 |
| Versalles | -22232.1321 | 6758.403 | -3.2895540 | 0.0010042 |
| Liniers | -20080.3410 | 5366.272 | -3.7419536 | 0.0001828 |
| Villa General Mitre | -19170.0826 | 6802.247 | -2.8181987 | 0.0048315 |
| Boedo | -19034.3756 | 5219.539 | -3.6467541 | 0.0002659 |
| Paternal | -13314.5004 | 5189.694 | -2.5655654 | 0.0103039 |
| Parque Centenario | -12288.2978 | 5016.446 | -2.4496025 | 0.0143051 |
| Barracas | -10128.2420 | 5351.063 | -1.8927533 | 0.0583970 |
| Villa Real | -8823.3661 | 8745.561 | -1.0088965 | 0.3130296 |
| Monte Castro | -8770.7193 | 5949.630 | -1.4741621 | 0.1404448 |
| Villa Luro | -7579.1058 | 5404.779 | -1.4022970 | 0.1608333 |
| Villa Santa Rita | -5767.7095 | 6383.856 | -0.9034836 | 0.3662740 |
| San Telmo | -5653.8495 | 4877.121 | -1.1592597 | 0.2463564 |
| Almagro | -4520.0439 | 4295.239 | -1.0523383 | 0.2926499 |
| Villa del Parque | -3290.1721 | 4866.591 | -0.6760733 | 0.4989975 |
| Agronomía | 623.5302 | 8846.143 | 0.0704861 | 0.9438071 |
| Villa Crespo | 1595.2587 | 4317.544 | 0.3694829 | 0.7117695 |
| Parque Chas | 5195.2620 | 7542.968 | 0.6887557 | 0.4909805 |
| Caballito | 6220.1481 | 4301.294 | 1.4461109 | 0.1481529 |
| Villa Pueyrredón | 10516.8000 | 5349.558 | 1.9659194 | 0.0493139 |
| Chacarita | 11903.3891 | 5299.017 | 2.2463391 | 0.0246870 |
| Villa Devoto | 13301.3902 | 4807.083 | 2.7670401 | 0.0056590 |
| Villa Ortuzar | 18667.6118 | 6829.180 | 2.7335071 | 0.0062688 |
| Saavedra | 19492.0048 | 4914.178 | 3.9664831 | 0.0000731 |
| Retiro | 26067.3977 | 5281.273 | 4.9358169 | 0.0000008 |
| Villa Urquiza | 30648.4307 | 4418.906 | 6.9357511 | 0.0000000 |
| Colegiales | 34073.0178 | 4816.540 | 7.0741683 | 0.0000000 |
| Coghlan | 40820.5486 | 5462.899 | 7.4723241 | 0.0000000 |
| Barrio Norte | 49921.8081 | 4417.818 | 11.3001051 | 0.0000000 |
| Nuñez | 56958.4220 | 4559.686 | 12.4917429 | 0.0000000 |
| Recoleta | 64088.2172 | 4360.342 | 14.6979792 | 0.0000000 |
| Palermo | 66169.5774 | 4221.499 | 15.6744275 | 0.0000000 |
| Belgrano | 69648.1237 | 4283.554 | 16.2594252 | 0.0000000 |
| Las Cañitas | 90455.9016 | 5883.382 | 15.3748142 | 0.0000000 |
| Puerto Madero | 259015.8326 | 5095.118 | 50.8360828 | 0.0000000 |
Finalmente, se analizan los coeficientes asociados a los barrios. Hay varios que son altamente significativos, otros que son significativos y algunos que directamente no lo son. Para el caso de los coeficientes que son significativos (o muy significativos), la interpretación es análoga que para el tipo de propiedad. Es decir, que los mismos representan el aumento/disminución en el precio esperado de una propiedad que está en un barrio determinado respecto de otra del mismo tipo (casa, departamento o PH) ubicada en Abasto y con los mismos valores de superficie total, cubierta, cantidad de dormitorios y cantidad de baños.
Para el caso de los coeficientes que no son significativos, la interpretación que se puede dar es que el precio esperado de una propiedad ubicada en uno de esos barrios no es significativamente diferente al de otra propiedad con las mismas características ubicada en Abasto. Esto sucede porque el test de hipótesis con \(H_0\): \(\beta_i\) = 0, no puede rechazar la hipótesis nula con el nivel de significancia de 0.05.
Se busca determinar cuál de los siguientes dos inmuebles es más conveniente para vender:
# Generar data frame para predecir el precio del caso 1
caso.1 <- data.frame(
l3 = "Abasto",
property_type = "Departamento",
surface_total = 120,
surface_covered = 120,
rooms = 3,
bathrooms = 2
)
# Generar prediccion para caso 1
predicciones <- as.data.frame(predict(modelo.multiple.1, caso.1, interval = "predict")) %>%
dplyr::mutate(propiedad = "Caso 1")
# Generar data frame para predecir el precio del caso 2
caso.2 <- data.frame(
l3 = "Balvanera",
property_type = "PH",
surface_total = 100,
surface_covered = 80,
rooms = 2,
bathrooms = 3
)
# Mostrar precio esperado para el caso 2 con intervalo de predicción
predicciones %<>% dplyr::bind_rows(
as.data.frame(predict(modelo.multiple.1, caso.2, interval = "predict")) %>%
dplyr::mutate(propiedad = "Caso 2")
) %>% dplyr::select(propiedad, fit, lwr, upr)
# Mostrar tabla
knitr::kable(
predicciones, col.names = c("Caso", "Precio estimado", "Límite inferior IP", "Límite superior IP")
)
| Caso | Precio estimado | Límite inferior IP | Límite superior IP |
|---|---|---|---|
| Caso 1 | 324596.4 | 193837.52 | 455355.3 |
| Caso 2 | 215267.6 | 84678.03 | 345857.2 |
Se observa entonces que el valor esperado para el precio del caso 1 (departamento ubicado en Abasto) es significativamente superior al del segundo caso, por lo que lo torna más preferible en términos de monto.
Ahora se realiza otro modelo lineal eliminando el barrio (atributo l3).
# Ajustamos un modelo lineal múltiple sin considerar l3
modelo.multiple.2 <- stats::lm(formula = price ~ ., data = dplyr::select(datos, -l3))
# Mostramos una tabla con los valores de R^2
knitr::kable(
broom::glance(modelo.multiple.2) %>%
dplyr::select(r.squared, adj.r.squared),
col.names = c("R^2", "R^2 ajustado")
)
| R^2 | R^2 ajustado |
|---|---|
| 0.6831564 | 0.6831149 |
Se observa que el valor del \(R^2\) ajustado (aproximadamente 0.68) ha disminuido respecto del caso anterior, indicando que se está logrando explicar una menor proporción de la variabilidad. La razón por la cual esto sucede es que el barrio aporta información valiosa para la predicción del precio esperado, tal como se observó en el modelo anterior.
# Mostramos una tabla con los coeficientes de las covariables
knitr::kable(
broom::tidy(modelo.multiple.2),
col.names = c("Covariable", "Coeficiente", "Desvío Estándar", "Estadístico t", "P-Valor")
)
| Covariable | Coeficiente | Desvío Estándar | Estadístico t | P-Valor |
|---|---|---|---|---|
| (Intercept) | -131096.8632 | 2750.50032 | -47.66292 | 0 |
| rooms | -13348.5337 | 519.01612 | -25.71892 | 0 |
| bathrooms | 42664.6783 | 756.36774 | 56.40732 | 0 |
| surface_total | 877.0286 | 27.58646 | 31.79199 | 0 |
| surface_covered | 1783.7960 | 33.52548 | 53.20717 | 0 |
| property_typeDepartamento | 135177.4653 | 2513.92781 | 53.77142 | 0 |
| property_typePH | 68598.5225 | 2677.46081 | 25.62074 | 0 |
En relación a los coeficientes, observamos que son todos muy significativos y se conserva la misma estructura de signos y orden para los mismos:
A continuación, se realizarán otros modelos sobre la base de nuevos conjuntos de datos resultantes de un proceso de feature engineering.
Se creará un nuevo modelo que, en vez de considerar los barrios, considere categorías de barrios agrupados en 3 categorías de acuerdo al precio promedio del metro cuadrado. A continuación, se realizará un análisis explorador para determinar los puntos de corte para las categorías.
datos.precio.m2.barrio <- datos %>%
# Obtener precio por metro cuadrado para cada propiedad (tomando la superficie total como indicador de superficie)
dplyr::mutate(price_m2 = price / surface_total) %>%
# Agrupar por barrio (l3)
dplyr::group_by(l3) %>%
# Calcular el valor promedio de precio por metro cuadrado y la cantidad de casos para cada barrio
dplyr::summarise(mean_price_m2 = mean(price_m2),
cantidad = dplyr::n()) %>%
# Ordenar los barrios por precio por metro cuadrado promedio de forma ascendente
dplyr::arrange(mean_price_m2)
# Mostrar grafico con precios
ggplot2::ggplot(data = datos.precio.m2.barrio %>%
# Cambiar orden del factor para que ordene por precio
dplyr::mutate(l3 = factor(l3, levels = l3)) %>%
dplyr::arrange(mean_price_m2)) +
ggplot2::geom_bar(mapping = ggplot2::aes(x = l3, y = mean_price_m2), fill = "darkslategray4", stat = "identity") +
ggplot2::labs(title = "Precio por metro cuadrado promedio", subtitle = "Barrios de Capital Federal",
x = "Barrio", y = "Precio por metro cuadrado (USD)") +
ggplot2::theme_bw() +
ggplot2::theme(
plot.title = ggplot2::element_text(hjust = 0.5),
plot.subtitle = ggplot2::element_text(hjust = 0.5),
axis.text.x = ggplot2::element_text(angle = 90)
)
En el gráfico anterior se muestran los precios por metro cuadrado promedio para cada barrio. Inicialmente se podría pensar que la clasificación de los barrios se podría obtener separando por terciles considerando los precios mostrados. Dado que hay 57 barrios, obtendríamos 3 categorías, cada una de las cuales agruparía 19 barrios. Sin embargo, las categorías alto, medio y bajo contendrían una cantidad muy dispar de propiedades, a saber:
# Calcular cantidad de propiedades considerando solamente terciles de precios promedio por metro cuadrado.
cantidad.propiedades <- dplyr::pull(datos.precio.m2.barrio, cantidad)
categorias.tentativas <- data.frame(
categoria = c("Bajo", "Medio", "Alto"),
cantidad = c(sum(cantidad.propiedades[1:19]), sum(cantidad.propiedades[20:38]), sum(cantidad.propiedades[39:57]))
)
# Mostrar tabla
knitr::kable(
categorias.tentativas,
col.names = c("Categoría", "Cantidad de propiedades")
)
| Categoría | Cantidad de propiedades |
|---|---|
| Bajo | 4008 |
| Medio | 8955 |
| Alto | 32941 |
Esto se debe a que los barrios con mayor cantidad de propiedades son aquellos con precios promedio del metro cuadrado más elevado. Para obtener una categorización más equilibrada en cantidad de casos, reagrupamos considerando la tabla ordenada de precios por metro cuadrado, pero asignando categorías con el objetivo de obtener conjuntos con cardinalidad lo más parecida posible. Para ello, creamos un nuevo atributo que indique la cantidad acumulada de propiedades partiendo del barrio con precio promedio más bajo hasta aquél con precio promedio más elevado.
Si denominamos CT a la cantidad total de propiedades y CA a la cantidad acumulada de propiedades por barrio, y recordando que la acumulación de casos comienza con los barrios más baratos, entonces iterando sobre la tabla obtenida definimos las categorías mediante el siguiente criterio:
\[CA \le \frac{CT}{3} => Categoría = "Bajo"\] \[\frac{CT}{3} \lt CA \le \frac{2CT}{3} => Categoría = "Medio"\] \[CA \gt \frac{2CT}{3} => Categoría = "Alto"\]
# Creamos los grupos
grupos.barrios <- datos.precio.m2.barrio %>%
dplyr::mutate(
# Obtenermos la cantidad acumulada de propiedades para cada barrio
# Recordar que la tabla estaba ordenada de precios mas bajos a mas elevados
cantidad_acumulada = cumsum(cantidad),
# Considerando CA = cantidad acumulada de propiedades por barrio
# y CT = cantidad total de propiedades
barrio = dplyr::case_when(
# Si CA <= 1/3 * CT => Categoria es "Bajo"
cantidad_acumulada <= (nrow(datos) / 3) ~ "Bajo",
# Si 1/3 * CT <= CA <= 2/3 * CT => Categoria es "Medio"
cantidad_acumulada <= (2 * nrow(datos) / 3) ~ "Medio",
# Sino (CA > 2/3 * CT) => Categoria es "Alto"
TRUE ~ "Alto"
)
) %>% dplyr::mutate(barrio = factor(barrio, c("Medio", "Bajo", "Alto")))
# Generamos el nuevo set de datos
datos.barrios <- datos %>%
dplyr::inner_join(grupos.barrios, by = c("l3")) %>%
dplyr::select(-l3, -mean_price_m2, -cantidad, -cantidad_acumulada)
# Mostramos la cantidad de propiedades por categoria de barrio
categorias.definitivas <- datos.barrios %>%
dplyr::group_by(barrio) %>%
dplyr::summarise(cantidad = dplyr::n())
knitr::kable(
categorias.definitivas,
col.names = c("Categoría", "Cantidad de propiedades")
)
| Categoría | Cantidad de propiedades |
|---|---|
| Medio | 16781 |
| Bajo | 13520 |
| Alto | 15603 |
Si bien la distribución no es exactamente equilibrada, es bastante más pareja que en el caso inicial. A continuación, se procede a realizar otro modelo en base a este nuevo conjunto de datos.
# Ajustamos un modelo lineal múltiple
modelo.multiple.3 <- stats::lm(formula = price ~ ., data = datos.barrios)
# Mostramos una tabla con los valores de R^2
knitr::kable(
broom::glance(modelo.multiple.3) %>%
dplyr::select(r.squared, adj.r.squared),
col.names = c("R^2", "R^2 ajustado")
)
| R^2 | R^2 ajustado |
|---|---|
| 0.7458978 | 0.7458535 |
# Mostramos una tabla con los coeficientes de las covariables
knitr::kable(
broom::tidy(modelo.multiple.3),
col.names = c("Covariable", "Coeficiente", "Desvío Estándar", "Estadístico t", "P-Valor")
)
| Covariable | Coeficiente | Desvío Estándar | Estadístico t | P-Valor |
|---|---|---|---|---|
| (Intercept) | -97497.711 | 2541.71115 | -38.35908 | 0 |
| rooms | -8124.566 | 467.47649 | -17.37962 | 0 |
| bathrooms | 36486.268 | 680.40314 | 53.62449 | 0 |
| surface_total | 937.020 | 24.71263 | 37.91664 | 0 |
| surface_covered | 1534.218 | 30.12212 | 50.93328 | 0 |
| property_typeDepartamento | 96118.329 | 2284.33255 | 42.07721 | 0 |
| property_typePH | 49221.118 | 2405.50782 | 20.46184 | 0 |
| barrioBajo | -32720.985 | 833.21774 | -39.27063 | 0 |
| barrioAlto | 57711.000 | 800.43392 | 72.09964 | 0 |
De acuerdo a la salida, se observa que el valor de \(R^2\) es de aproximadamente 0.75, en contraposición al valor de 0.78 observado en el modelo con todos los barrios. Todas las variables resultan altamente significativas, observándose el mismo patrón de signo para las variables numéricas que para el caso con todos los barrios. También se observa que el coeficiente asociado a la superficie cubierta es mayor que aquel asociado a la superficie total. Los coeficientes para los tipos de propiedad siguen siendo positivos, siendo el mayor de ellos el asociado a departamentos (también al igual que para el caso inicial).
Pero en este nuevo modelo aparecen coeficientes asociados a las propiedades con categorías de barrios con precio alto y bajo. Se eligió adrede como primera categoría del factor a la clase media a fin de que fuera eliminada y se pudieran comparar los coeficientes asociados a las otras dos en relación a la primera. Se observa que las propiedades de barrios de categoría baja tienen, efectivamente, un coeficiente negativo y los de categoría alta, un coeficiente positivo. Este coeficiente indica el monto en el cual se incrementa/disminuye el precio esperado de una propiedad de un barrio de cierta categoría en relación a otra de categoría media, del mismo tipo (departamento, casa o PH) y con los mismos parámetros de superficie total, cubierta, cantidad de dormitorios y de baños.
Este modelo tiene un \(R^2\) ajustado levemente menor al original. Pero por otro lado, es mucho más simple si se tiene en cuenta la menor cantidad de covariables. La simplicidad puede considerarse una ventaja siguiendo el principio de la navaja de Ockham. A priori, esto podría indicar que este modelo es preferible al inicial. Sin embargo, debe tenerse en cuenta que sea ha perdido granularidad en la definición de la ubicación, ya que pasamos de contar con información del barrio de la Ciudad de Buenos Aires a tener solamente 3 categorías definidas por un criterio ad hoc. Además, en este modelo más simple, también es necesario conocer el barrio para luego poder obtener la categoría del mismo. Teniendo en cuenta este último argumento y considerando que el modelo original tiene un \(R^2\) ajustado levemente mayor, lo consideramos más útil y preferible en relación al modelo basado en categorías de barrios.
A continuación, definimos la variable surface_patio como la diferencia entre surface_total y surface_covered. En ningún caso se observa que surface_covered < surface_total (probablemente esas observaciones hayan sido eliminadas a priori). En casos como éstos se podría optar por eliminar dichas observaciones (si éstas fueran pocas), dado que proveen información inconsistente y errónea.
# Generamos el nuevo set de datos, segun se indica.
datos.patio <- datos.barrios %>%
# Creamos la nueva variable surface_patio
dplyr::mutate(surface_patio = surface_total - surface_covered) %>%
# Eliminamos la variable surface_total
dplyr::select(-surface_total)
# Ajustamos un modelo lineal múltiple
modelo.multiple.4 <- stats::lm(formula = price ~ ., data = datos.patio)
# Mostramos una tabla con los valores de R^2
knitr::kable(
broom::glance(modelo.multiple.4) %>%
dplyr::select(r.squared, adj.r.squared),
col.names = c("R^2", "R^2 ajustado")
)
| R^2 | R^2 ajustado |
|---|---|
| 0.7458978 | 0.7458535 |
# Mostramos una tabla con los coeficientes de las covariables
knitr::kable(
broom::tidy(modelo.multiple.4),
col.names = c("Covariable", "Coeficiente", "Desvío Estándar", "Estadístico t", "P-Valor")
)
| Covariable | Coeficiente | Desvío Estándar | Estadístico t | P-Valor |
|---|---|---|---|---|
| (Intercept) | -97497.711 | 2541.71115 | -38.35908 | 0 |
| rooms | -8124.566 | 467.47649 | -17.37962 | 0 |
| bathrooms | 36486.268 | 680.40314 | 53.62449 | 0 |
| surface_covered | 2471.238 | 15.91386 | 155.28844 | 0 |
| property_typeDepartamento | 96118.329 | 2284.33255 | 42.07721 | 0 |
| property_typePH | 49221.118 | 2405.50782 | 20.46184 | 0 |
| barrioBajo | -32720.985 | 833.21774 | -39.27063 | 0 |
| barrioAlto | 57711.000 | 800.43392 | 72.09964 | 0 |
| surface_patio | 937.020 | 24.71263 | 37.91664 | 0 |
Este modelo tiene exactamente el mismo \(R^2\) ajustado que el anterior y los mismos coeficientes para todas las variables, excepto aquellos asociados a variables de superficie. Estos nuevos valores de coeficientes se pueden interpretar como el incremento en el precio esperado de la propiedad por cada metro cuadrado adicional de superficie cubierta (surface_covered) o descubierta (surface_patio). Ahora es más clara la interpretación de los coeficientes según el tipo de superficie.
Es interesante notar que el coeficiente asociado a surface_patio es exactamente igual al asociado en el modelo anterior a surface_total. Además, el coeficiente asociado a surface_covered en el nuevo modelo, es la suma de los coeficientes que en el modelo anterior correspondían a surface_total y surface_covered. Si bien parece un hecho curioso, desde el punto de vista matemático es absolutamente esperable dada la definición de surface_patio. Si partimos de la definición de surface_patio y del modelo anterior, podemos desarrollar del siguiente modo:
\[SurfaceTotal = SurfaceCovered + SurfacePatio\]
\[Price = \beta_0 + \beta_{sc} * SurfaceCovered + \beta_{st} * SurfaceTotal + ...\]
\[ = \beta_0 + \beta_{sc} * SurfaceCovered + \beta_{st} * (SurfacePatio + SurfaceCovered) + ...\]
\[= \beta_0 + (\beta_{sc} + \beta_{st}) * SurfaceCovered + \beta_{st} * SurfacePatio + ... \]
De este modo, se entiende porqué el coeficiente de surface_covered en el nuevo modelo es la suma de los dos coeficientes de superficies del modelo anterior y porqué el coeficiente de surface_patio es igual al de surface_total del modelo previo.
Se analizarán los residuos del modelo previo de modo de verificar si se cumplen o no los supuestos del modelo de regresión lineal. Los supuestos se basan en afirmaciones sobre los errores, los cuales son totalmente desconocidos. Solamente contamos con información acerca de los residuos. Para validar los supuestos, vamos a analizar la normalidad y homocedasticidad de estos últimos.
# Se utiliza el paquete ggfortify para realizar gráficos de los residuos.
autoplot(modelo.multiple.4, which = c(2,1,3), size = 0.2, label.angle = 45, ncol = 1,
label.hjust = 0.5, smooth.colour = "tomato", colour = "darkslategray4",
ad.colour = "black", as.linetype = "dotted", ad.size = 0.4) +
ggplot2::theme(
plot.title = ggplot2::element_text(hjust = 0.5)
)
En el primer gráfico podemos observar un QQ-Plot entre los cuantiles de los residuos escalados (eje Y) y los cuantiles teóricos de una distribución normal (eje X). Observamos que los cuantiles teóricos extremos difieren sustancialmente de los cuantiles correspondientes a los residuos estandarizados, indicando un alejamiento de los mismos respecto de la distribución normal. Sin embargo, dada la gran cantidad de observaciones con la que se cuenta, una versión extendida del Teorema Central del Límite garantiza que los estimadores de mínimos cuadrados para los coeficientes asociados a las covariables tienen distribución de muestreo aproximadamente normal.
El segundo gráfico muestra la asociación entre los residuos del modelo y los valores ajustados de la variable de respuesta. Si los residuos fueran homocedásticos, se esperaría observar una nube de puntos uniformemente distribuida. Sin embargo, esto no sucede. En el tercer gráfico se observa la relación entre la raíz cuadrada de los módulos de los residuos estandarizados y los valores ajustados. Vemos claramente una tendencia lineal a aumentar el valor absoluto de los residuos conforme aumenta el valor ajustado de la variable de respuesta.
Estas últimas observaciones nos permiten concluir que los residuos no son homocedásticos. Para abordar este problema se podrían aplicar transformaciones a las covariables que permitan alcanzar la homocedasticidad. También debiera considerarse el agregado de términos de interacción entre las mismas. A continuación, se analizará un modelo que considera la transformación de algunas covariables y la variable de respuesta.
Se realizará el siguiente modelo de regresión lineal:
\[log(Price) = \beta_0 + \beta_1log(Rooms) + \beta_2log(Bathrooms) + \beta_3log(SCovered) + \beta_4PType + \beta_5Barrio + \beta_6SPatio\]
# Ajustamos un modelo lineal múltiple de acuerdo a lo indicado
modelo.multiple.5 <- stats::lm(formula = log(price) ~ log(rooms)+log(bathrooms)+log(surface_covered)+property_type+barrio+surface_patio,
data = datos.patio)
# Mostramos una tabla con los valores de R^2
knitr::kable(
broom::glance(modelo.multiple.5) %>%
dplyr::select(r.squared, adj.r.squared),
col.names = c("R^2", "R^2 ajustado")
)
| R^2 | R^2 ajustado |
|---|---|
| 0.8147792 | 0.8147469 |
Se observa que el valor de \(R^2\) ajustado para este modelo se ha incrementado levemente a un valor de 0.81, superando a todos los modelos anteriores. En relación a los coeficientes, todos son altamente significativos. El análisis de los mismos se dividirá en dos partes. Primero se analizarán los coeficientes asociados a covariables transformadas utilizando el logaritmo natural. Luego, se analizarán los coeficientes asociados a variables no transformadas.
# Mostramos una tabla con los coeficientes de las covariables
knitr::kable(
broom::tidy(modelo.multiple.5),
col.names = c("Covariable", "Coeficiente", "Desvío Estándar", "Estadístico t", "P-Valor")
)
| Covariable | Coeficiente | Desvío Estándar | Estadístico t | P-Valor |
|---|---|---|---|---|
| (Intercept) | 8.5245081 | 0.0188407 | 452.450894 | 0 |
| log(rooms) | -0.0420512 | 0.0038477 | -10.928934 | 0 |
| log(bathrooms) | 0.1828668 | 0.0038781 | 47.153751 | 0 |
| log(surface_covered) | 0.8075750 | 0.0045297 | 178.283286 | 0 |
| property_typeDepartamento | 0.2138996 | 0.0073602 | 29.061490 | 0 |
| property_typePH | 0.0551692 | 0.0077841 | 7.087436 | 0 |
| barrioBajo | -0.1863452 | 0.0027244 | -68.397696 | 0 |
| barrioAlto | 0.2161888 | 0.0026181 | 82.575975 | 0 |
| surface_patio | 0.0041152 | 0.0000810 | 50.812287 | 0 |
Para el primer caso, las covariables han sido transformadas mediante el uso del logaritmo natural. Dado que la variable de respuesta también ha sido transformada utilizando la misma función, estamos ante un modelo denominado de elasticidad constante o log-log. En estos modelos, cada coeficiente de las covariables representa la elasticidad de la variable de respuesta respecto a la covariable en cuestión. Se interpreta que por cada aumento de 1% en el valor de la covariable i, hay un aumento de \(\beta_i\)% en el precio esperado del inmueble. Como en los casos anteriores, la cantidad de dormitorios presenta un coeficiente negativo y la cantidad de baños y superficie cubierta tienen coeficientes positivos.
En el segundo caso, las covariables no han sido transformadas. Dado que la variable de respuesta ha sido transformada utilizando el logaritmo natural, estamos frente a una relación log-nivel. En este tipo de modelos, cada coeficiente de las covariables representa la semielasticidad de la variable de respuesta respecto a la covariable en cuestión. En estos casos, se interpreta que, por cada aumento en 1 unidad de la covariable i, hay un aumento de \(100\beta_i\)% en el precio esperado del inmueble. Si bien los coeficientes han variado respecto del modelo anterior, el signo y ordenamiento de los mismos ha mantenido la misma estructura. A continuación, también se realizará un análisis de los residuos.
# Se utiliza el paquete ggfortify para realizar gráficos de los residuos.
autoplot(modelo.multiple.5, which = c(2,1,3), size = 0.2, label.angle = 45, ncol = 1,
label.hjust = 0.5, smooth.colour = "tomato", colour = "darkslategray4",
ad.colour = "black", as.linetype = "dotted", ad.size = 0.4) +
ggplot2::theme(
plot.title = ggplot2::element_text(hjust = 0.5)
)
Del análisis de los gráficos anteriores, se destacan dos aspectos importantes. Lo primero que se observa en el QQ-plot, es que los cuantiles extremos asociados a los residuos estandarizados presentan menores discrepancias respecto a los cuantiles teóricos que en el modelo anterior. Esto indica que los residuos presentan una distribución más cercana a la normal. Si bien esto es importante, se explicó anteriormente que este supuesto no es crítico para modelos construidos a partir de gran cantidad de datos.
Sin embargo, lo más importante es que los residuos de este modelo presentan mayores signos de homocedasticidad. El segundo gráfico muestra que la nube de puntos ahora es más uniforme (aunque aún conserva cierta estructura). El tercer gráfico muestra que, si bien aún hay una tendencia al aumento de los valores de los residuos para mayores valores ajustados, la misma es menos marcada que en el modelo anterior.
A continuación, se ejecutan 3 modelos anidados, uno para cada tipo de propiedad.
# Se anidan los datos por tipo de propiedad
datos.por.tipo.propiedad <- datos.patio %>%
dplyr::group_by(property_type) %>%
tidyr::nest()
# Definimos la funcion de modelado
funcion_modelo <- function(datos) {
return (stats::lm(price ~ ., data = datos))
}
# Se generan los modelos anidados
resultados.modelos.tipo.propiedad <- datos.por.tipo.propiedad %>%
dplyr::mutate(modelo = purrr::map(data, funcion_modelo),
tdy = purrr::map(modelo, broom::tidy),
agmnt = purrr::map2(modelo, data, broom::augment))
# Comparación de R^2
purrr::map_dfr(resultados.modelos.tipo.propiedad$modelo, broom::glance) %>%
dplyr::mutate(property_type = resultados.modelos.tipo.propiedad$property_type) %>%
dplyr::select(property_type, adj.r.squared) %>%
knitr::kable(x = ., col.names = c("Tipo de propiedad", "R^2 ajustado"))
| Tipo de propiedad | R^2 ajustado |
|---|---|
| Casa | 0.5765191 |
| Departamento | 0.7735095 |
| PH | 0.6871225 |
Lo primero que se puede observar es que el modelo para los departamentos ha mejorado levemente el valor de \(R^2\) ajustado en relación al modelo de base (aquél que consideraba la división entre la superficie cubierta de la no cubierta). Contrariamente, para los modelos asociados a casas y PH los valores de \(R^2\) han disminuido. Incluso, como se observa en la tabla siguiente, la covariable rooms no es estadísticamente significativa para el modelo de los PH y muy poco significativa para el modelo de las casas. Esto significa que las mismas variables regresoras no tienen el mismo poder predictivo para los distintos tipos de propiedades.
# Comparación de coeficientes
resultados.modelos.tipo.propiedad %>%
tidyr::unnest(tdy) %>%
dplyr::select(property_type, term, estimate, p.value) %>%
knitr::kable(x = ., col.names = c("Tipo de propiedad", "Coeficiente", "Valor", "P-Valor"))
| Tipo de propiedad | Coeficiente | Valor | P-Valor |
|---|---|---|---|
| Casa | (Intercept) | 82262.7745 | 0.0000000 |
| Casa | rooms | 5832.7239 | 0.0200717 |
| Casa | bathrooms | 29576.3945 | 0.0000000 |
| Casa | surface_covered | 1127.7678 | 0.0000000 |
| Casa | barrioBajo | -69062.3480 | 0.0000000 |
| Casa | barrioAlto | 98147.6945 | 0.0000000 |
| Casa | surface_patio | 548.9056 | 0.0000000 |
| Departamento | (Intercept) | -10275.7547 | 0.0000000 |
| Departamento | rooms | -13306.8192 | 0.0000000 |
| Departamento | bathrooms | 32419.5396 | 0.0000000 |
| Departamento | surface_covered | 2872.8465 | 0.0000000 |
| Departamento | barrioBajo | -28855.5383 | 0.0000000 |
| Departamento | barrioAlto | 55811.5761 | 0.0000000 |
| Departamento | surface_patio | 1081.5423 | 0.0000000 |
| PH | (Intercept) | 55905.0173 | 0.0000000 |
| PH | rooms | 372.9077 | 0.7261202 |
| PH | bathrooms | 22086.9882 | 0.0000000 |
| PH | surface_covered | 1360.2549 | 0.0000000 |
| PH | barrioBajo | -46167.9112 | 0.0000000 |
| PH | barrioAlto | 48126.6277 | 0.0000000 |
| PH | surface_patio | 647.5559 | 0.0000000 |
Dejando de lado el intercepto y la cantidad de dormitorios, vemos que el resto de las variables conserva la misma estructura de signo y orden. La superficie cubierta tiene un coeficiente mayor que la superficie descubierta, el coeficiente asociado a barrios de precio bajo es negativo y el asociado a barrios de precio bajo es positivo. Esta es una regularidad que se ha mantenido a lo largo de todos los modelos.
Sin embargo, debe notarse que las mismas variables en distintos modelos tienen valores diferentes. No tiene demasiado sentido compararlos a todos, pero a modo de ejemplo se observa que la brecha en relación a los coeficientes asociados a las categorías de barrios es más pronunciada para las casas que para los PH y en menor medida para los departamentos.