Introducción

En este trabajo se usa la base de datos de ofertas de vivienda en Cali para estimar modelos de regresión lineal simple y múltiple. Escogí el barrio Ciudad Jardín porque tiene bastantes ofertas y porque hay casas y apartamentos en cantidades parecidas, lo que sirve para usar el tipo de vivienda como variable dummy.

La variable que se quiere explicar es el precio de la vivienda en millones de pesos.

Carga y filtro de los datos

library(readxl)
library(dplyr)
library(ggplot2)

datos <- read_excel("Datos_Vivienda.xlsx",
    col_types = c("text", "text", "text",
        "numeric", "numeric", "numeric",
        "numeric", "numeric", "text", "text",
        "numeric", "numeric"))

id = 1:dim(datos)[1]
datos = data.frame(id, datos)

# Filtro por el barrio Ciudad Jardín
datos_sub <- datos %>% filter(tolower(trimws(Barrio)) == "ciudad jardin")

nrow(datos_sub)
## [1] 540
table(datos_sub$Tipo)
## 
## Apartamento        Casa 
##         230         310

Quedan 540 ofertas: 310 casas y 230 apartamentos.

Antes de modelar hice una limpieza básica:

  • No uso la variable piso porque tiene muchos datos vacíos y además no aplica para casas.
  • Quito las filas sin dato de parqueaderos.
  • Quito las filas con 0 baños o 0 habitaciones porque parecen errores de digitación.
  • El estrato viene como texto, así que lo paso a número.
datos_sub <- datos_sub %>%
  filter(!is.na(parqueaderos), Banos > 0, Habitaciones > 0) %>%
  mutate(Estrato = as.numeric(Estrato),
         Tipo = factor(Tipo))

nrow(datos_sub)
## [1] 515
summary(datos_sub[, c("precio_millon", "Area_contruida", "parqueaderos", "Banos", "Habitaciones")])
##  precio_millon    Area_contruida    parqueaderos        Banos      
##  Min.   : 180.0   Min.   :  50.0   Min.   : 1.000   Min.   :1.000  
##  1st Qu.: 466.5   1st Qu.: 133.0   1st Qu.: 2.000   1st Qu.:3.000  
##  Median : 680.0   Median : 200.0   Median : 2.000   Median :4.000  
##  Mean   : 755.4   Mean   : 255.4   Mean   : 2.755   Mean   :4.192  
##  3rd Qu.: 900.0   3rd Qu.: 330.0   3rd Qu.: 4.000   3rd Qu.:5.000  
##  Max.   :1900.0   Max.   :1586.0   Max.   :10.000   Max.   :9.000  
##   Habitaciones  
##  Min.   :1.000  
##  1st Qu.:3.000  
##  Median :4.000  
##  Mean   :3.763  
##  3rd Qu.:4.000  
##  Max.   :9.000

Después de la limpieza quedan 515 viviendas. El precio va desde 180 hasta 1900 millones y el área desde 50 hasta 1586 m². La mitad de las viviendas tiene menos de 200 m², pero hay algunas casas muy grandes.

Exploración

cor(datos_sub[, c("precio_millon", "Area_contruida", "parqueaderos", "Banos", "Habitaciones", "Estrato")])
##                precio_millon Area_contruida parqueaderos     Banos Habitaciones
## precio_millon      1.0000000      0.7315459    0.7071535 0.6428504   0.51054250
## Area_contruida     0.7315459      1.0000000    0.6526906 0.6239281   0.54967284
## parqueaderos       0.7071535      0.6526906    1.0000000 0.5395097   0.45223583
## Banos              0.6428504      0.6239281    0.5395097 1.0000000   0.72214899
## Habitaciones       0.5105425      0.5496728    0.4522358 0.7221490   1.00000000
## Estrato            0.1797282      0.1445882    0.1362386 0.1488046   0.06455348
##                   Estrato
## precio_millon  0.17972824
## Area_contruida 0.14458822
## parqueaderos   0.13623861
## Banos          0.14880457
## Habitaciones   0.06455348
## Estrato        1.00000000

Las variables que más se relacionan con el precio son el área construida (0.73), los parqueaderos (0.71) y los baños (0.64). Por eso escogí esas tres para los modelos simples. El estrato casi no se relaciona con el precio porque en Ciudad Jardín casi todo es estrato 6.

ggplot(datos_sub, aes(x = Area_contruida, y = precio_millon, color = Tipo)) +
  geom_point(alpha = 0.6) +
  geom_smooth(method = "lm", se = FALSE) +
  labs(title = "Precio vs área construida en Ciudad Jardín",
       x = "Área construida (m2)", y = "Precio (millones)") +
  theme_minimal()

En el gráfico se ve que a mayor área mayor precio, pero la línea de los apartamentos es más inclinada que la de las casas. También se ve que los puntos se van abriendo a medida que aumenta el área, es decir, hay más variación en las viviendas grandes. Esto puede generar problemas de heterocedasticidad en los modelos.

1. Modelación simple

Para validar los supuestos se usan los cuatro gráficos de residuos:

  • Residuos vs ajustados: si la relación es lineal, los puntos deben estar repartidos alrededor de cero, sin formar curvas.
  • Q-Q: si los residuos son normales, los puntos deben quedar sobre la línea.
  • Scale-Location: si la varianza es constante (homocedasticidad), la línea roja debe ser más o menos plana.
  • Residuos vs Leverage: sirve para ver si hay puntos que influyen demasiado en el modelo.

Además, para la normalidad se usa la prueba de Shapiro-Wilk (H0: los residuos son normales). Si el p-valor es mayor a 0.05 se cumple el supuesto.

Modelo 1: Precio ~ Área construida

mod1 <- lm(precio_millon ~ Area_contruida, data = datos_sub)
summary(mod1)
## 
## Call:
## lm(formula = precio_millon ~ Area_contruida, data = datos_sub)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1006.90  -158.20   -47.47    87.21  1045.01 
## 
## Coefficients:
##                 Estimate Std. Error t value Pr(>|t|)    
## (Intercept)    361.61807   19.79987   18.26   <2e-16 ***
## Area_contruida   1.54179    0.06344   24.30   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 258.2 on 513 degrees of freedom
## Multiple R-squared:  0.5352, Adjusted R-squared:  0.5343 
## F-statistic: 590.6 on 1 and 513 DF,  p-value: < 2.2e-16

Interpretación

precio = 361.62 + 1.54 * Area_contruida

Por cada metro cuadrado adicional de área construida, el precio aumenta en promedio 1.54 millones. El intercepto no tiene una interpretación real porque no existen viviendas de 0 m². Los dos coeficientes son significativos y el R² es 0.535, o sea que el área explica más o menos el 53% de la variación del precio.

Validación de supuestos

par(mfrow = c(2, 2))
plot(mod1)

shapiro.test(residuals(mod1))
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(mod1)
## W = 0.89102, p-value < 2.2e-16
  • Residuos vs ajustados: la línea roja se curva hacia abajo en los valores altos, entonces el modelo no está capturando bien las viviendas más grandes.
  • Q-Q: los extremos se salen bastante de la línea, entonces los residuos no son normales (Shapiro p < 0.05).
  • Scale-Location: la línea sube bastante, lo que indica heterocedasticidad: el error es más grande en las viviendas más caras.
  • Leverage: hay una casa de 1586 m² que influye mucho en la recta.

El modelo no cumple normalidad ni homocedasticidad, así que se prueba una transformación logarítmica.

Transformación logarítmica

Como el área y el precio son positivos y tienen valores muy dispersos, se aplica logaritmo a las dos variables (modelo log-log).

mod1_log <- lm(log(precio_millon) ~ log(Area_contruida), data = datos_sub)
summary(mod1_log)
## 
## Call:
## lm(formula = log(precio_millon) ~ log(Area_contruida), data = datos_sub)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.89543 -0.16081 -0.01881  0.14323  0.81145 
## 
## Coefficients:
##                     Estimate Std. Error t value Pr(>|t|)    
## (Intercept)          2.87401    0.09874   29.11   <2e-16 ***
## log(Area_contruida)  0.67970    0.01834   37.05   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2544 on 513 degrees of freedom
## Multiple R-squared:  0.728,  Adjusted R-squared:  0.7275 
## F-statistic:  1373 on 1 and 513 DF,  p-value: < 2.2e-16

Interpretación

En un modelo log-log el coeficiente es una elasticidad. Si el área aumenta 1%, el precio aumenta en promedio 0.68%. Como es menor a 1, el precio no crece en la misma proporción que el área: las viviendas más grandes tienen un precio por m² más bajo. El R² sube a 0.728.

par(mfrow = c(2, 2))
plot(mod1_log)

shapiro.test(residuals(mod1_log))
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(mod1_log)
## W = 0.98737, p-value = 0.0001956

Con la transformación los gráficos mejoran bastante. Los residuos quedan más repartidos alrededor de cero, el Q-Q está mucho más pegado a la línea y la casa de 1586 m² ya no influye tanto. La línea del Scale-Location queda mucho más plana, entonces la heterocedasticidad se reduce. El Shapiro todavía da menor a 0.05, pero el p-valor mejora mucho (pasa de menos de 2.2e-16 a 0.0002). Hay que tener en cuenta que con más de 500 datos esta prueba es muy estricta. El modelo log-log es mejor que el lineal.

Modelo 2: Precio ~ Parqueaderos

mod2 <- lm(precio_millon ~ parqueaderos, data = datos_sub)
summary(mod2)
## 
## Call:
## lm(formula = precio_millon ~ parqueaderos, data = datos_sub)
## 
## Residuals:
##    Min     1Q Median     3Q    Max 
## -724.7 -161.7  -88.7  100.8  950.3 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)    256.71      24.98   10.28   <2e-16 ***
## parqueaderos   181.00       7.99   22.65   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 267.8 on 513 degrees of freedom
## Multiple R-squared:  0.5001, Adjusted R-squared:  0.4991 
## F-statistic: 513.1 on 1 and 513 DF,  p-value: < 2.2e-16

Interpretación

precio = 256.71 + 181.00 * parqueaderos

Cada parqueadero adicional aumenta el precio en promedio 181 millones. Este valor es alto porque las viviendas con más parqueaderos normalmente también son más grandes, entonces el coeficiente está recogiendo ese efecto. El intercepto (256.71) sería el precio promedio de una vivienda sin parqueadero. El R² es 0.50.

par(mfrow = c(2, 2))
plot(mod2)

shapiro.test(residuals(mod2))
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(mod2)
## W = 0.8798, p-value < 2.2e-16

Como los parqueaderos son números enteros, los residuos se ven en columnas. Las columnas de más parqueaderos tienen más dispersión, y el Q-Q se sale en la parte de arriba. No se cumple normalidad (Shapiro p < 0.05) y la línea del Scale-Location sube, así que tampoco hay homocedasticidad.

Transformación logarítmica

Como hay viviendas con 0 parqueaderos y el logaritmo de 0 no existe, solo se le saca logaritmo al precio (modelo log-lin).

mod2_log <- lm(log(precio_millon) ~ parqueaderos, data = datos_sub)
summary(mod2_log)
## 
## Call:
## lm(formula = log(precio_millon) ~ parqueaderos, data = datos_sub)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1.04442 -0.22717 -0.05265  0.21405  1.01214 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   5.86986    0.03233  181.56   <2e-16 ***
## parqueaderos  0.23201    0.01034   22.43   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.3466 on 513 degrees of freedom
## Multiple R-squared:  0.4952, Adjusted R-squared:  0.4942 
## F-statistic: 503.3 on 1 and 513 DF,  p-value: < 2.2e-16

Interpretación

En un modelo log-lin, cada parqueadero adicional aumenta el precio en (e^0.232 - 1) = 26% aproximadamente. El R² queda casi igual (0.495).

par(mfrow = c(2, 2))
plot(mod2_log)

shapiro.test(residuals(mod2_log))
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(mod2_log)
## W = 0.98167, p-value = 4.438e-06

Con la transformación las columnas de residuos quedan con una dispersión más pareja. La línea del Scale-Location se mantiene estable en la mayoría de los datos y solo sube al final, donde hay muy pocas viviendas, entonces la heterocedasticidad mejora. La normalidad mejora (Shapiro pasa a 0.000004), pero todavía no se cumple. El modelo log-lin es mejor.

Modelo 3: Precio ~ Baños

mod3 <- lm(precio_millon ~ Banos, data = datos_sub)
summary(mod3)
## 
## Call:
## lm(formula = precio_millon ~ Banos, data = datos_sub)
## 
## Residuals:
##    Min     1Q Median     3Q    Max 
## -561.3 -192.9  -62.1  121.7 1146.3 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   46.032     39.448   1.167    0.244    
## Banos        169.214      8.902  19.008   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 290.1 on 513 degrees of freedom
## Multiple R-squared:  0.4133, Adjusted R-squared:  0.4121 
## F-statistic: 361.3 on 1 and 513 DF,  p-value: < 2.2e-16

Interpretación

precio = 46.03 + 169.21 * Banos

Cada baño adicional aumenta el precio en promedio 169 millones. El intercepto no es significativo (p = 0.244), lo cual tiene sentido porque no hay viviendas sin baños. El R² es 0.413, el más bajo de los tres modelos.

par(mfrow = c(2, 2))
plot(mod3)

shapiro.test(residuals(mod3))
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(mod3)
## W = 0.9041, p-value < 2.2e-16

Pasa algo parecido al modelo de parqueaderos: la dispersión aumenta con el número de baños y el Q-Q se sale en la cola de arriba. No se cumple normalidad ni homocedasticidad.

Transformación logarítmica

mod3_log <- lm(log(precio_millon) ~ Banos, data = datos_sub)
summary(mod3_log)
## 
## Call:
## lm(formula = log(precio_millon) ~ Banos, data = datos_sub)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1.14111 -0.24952 -0.06676  0.19517  1.20831 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  5.52794    0.04800  115.18   <2e-16 ***
## Banos        0.23404    0.01083   21.61   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.353 on 513 degrees of freedom
## Multiple R-squared:  0.4765, Adjusted R-squared:  0.4755 
## F-statistic: 466.9 on 1 and 513 DF,  p-value: < 2.2e-16

Interpretación

Cada baño adicional aumenta el precio en (e^0.234 - 1) = 26% aproximadamente. El R² sube de 0.413 a 0.477.

par(mfrow = c(2, 2))
plot(mod3_log)

shapiro.test(residuals(mod3_log))
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(mod3_log)
## W = 0.9735, p-value = 4.919e-08

Aquí la transformación funciona todavía mejor: la línea del Scale-Location queda plana, o sea que la varianza de los errores es constante, y además mejora el R². La normalidad mejora aunque sigue sin cumplirse. El modelo log-lin es mejor.

Resumen de los modelos simples

Modelo R² Normalidad (Shapiro) Homocedasticidad (gráficos)
Precio ~ Área 0.535 No No
log(Precio) ~ log(Área) 0.728 No, pero mejora Mejora
Precio ~ Parqueaderos 0.500 No No
log(Precio) ~ Parqueaderos 0.495 No, pero mejora Mejora
Precio ~ Baños 0.413 No No
log(Precio) ~ Baños 0.477 No, pero mejora Sí

En los tres casos el logaritmo ayudó a cumplir mejor los supuestos. El mejor modelo simple es el log-log con el área. Ninguna variable sola explica todo el precio, así que vale la pena usar varias al mismo tiempo.

2. Modelación múltiple

Modelo múltiple 1 (con variable dummy)

Se incluyen todas las variables y el tipo de vivienda como dummy. R crea la variable TipoCasa (1 si es casa, 0 si es apartamento), así que la categoría base es apartamento.

mod4 <- lm(precio_millon ~ Area_contruida + parqueaderos + Banos + Habitaciones + Estrato + Tipo,
           data = datos_sub)
summary(mod4)
## 
## Call:
## lm(formula = precio_millon ~ Area_contruida + parqueaderos + 
##     Banos + Habitaciones + Estrato + Tipo, data = datos_sub)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -868.70 -119.73  -41.72   71.04 1004.90 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)    -218.51794  167.44436  -1.305   0.1925    
## Area_contruida    0.77798    0.08204   9.483  < 2e-16 ***
## parqueaderos     89.09425    9.00138   9.898  < 2e-16 ***
## Banos            64.58189   11.08122   5.828 9.97e-09 ***
## Habitaciones     -4.52138   14.18274  -0.319   0.7500    
## Estrato          49.46668   28.12833   1.759   0.0792 .  
## TipoCasa        -25.13991   25.49034  -0.986   0.3245    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 221.7 on 508 degrees of freedom
## Multiple R-squared:  0.6608, Adjusted R-squared:  0.6568 
## F-statistic: 164.9 on 6 and 508 DF,  p-value: < 2.2e-16

Interpretación

Cada coeficiente se interpreta dejando las demás variables constantes (ceteris paribus):

  • Área: cada m² adicional sube el precio 0.78 millones. Es menos que en el modelo simple (1.54), porque ahora parte del efecto lo explican los parqueaderos y los baños.
  • Parqueaderos: cada parqueadero sube el precio 89 millones.
  • Baños: cada baño sube el precio 65 millones.
  • Habitaciones: no es significativa (p = 0.75).
  • Estrato: solo es significativo al 10% (p = 0.079).
  • TipoCasa: no es significativa (p = 0.32). En este modelo no hay diferencia clara entre casa y apartamento.

El R² ajustado es 0.657, mejor que cualquiera de los modelos simples lineales.

Selección de variables (Step by Step)

mod4_step <- step(mod4, direction = "both")
## Start:  AIC=5570.16
## precio_millon ~ Area_contruida + parqueaderos + Banos + Habitaciones + 
##     Estrato + Tipo
## 
##                  Df Sum of Sq      RSS    AIC
## - Habitaciones    1      4994 24966167 5568.3
## - Tipo            1     47794 25008968 5569.1
## <none>                        24961174 5570.2
## - Estrato         1    151963 25113137 5571.3
## - Banos           1   1668965 26630139 5601.5
## - Area_contruida  1   4418786 29379960 5652.1
## - parqueaderos    1   4813743 29774916 5659.0
## 
## Step:  AIC=5568.27
## precio_millon ~ Area_contruida + parqueaderos + Banos + Estrato + 
##     Tipo
## 
##                  Df Sum of Sq      RSS    AIC
## - Tipo            1     55197 25021365 5567.4
## <none>                        24966167 5568.3
## - Estrato         1    157098 25123265 5569.5
## + Habitaciones    1      4994 24961174 5570.2
## - Banos           1   2164051 27130218 5609.1
## - Area_contruida  1   4443106 29409274 5650.6
## - parqueaderos    1   4811202 29777370 5657.0
## 
## Step:  AIC=5567.4
## precio_millon ~ Area_contruida + parqueaderos + Banos + Estrato
## 
##                  Df Sum of Sq      RSS    AIC
## <none>                        25021365 5567.4
## + Tipo            1     55197 24966167 5568.3
## - Estrato         1    159796 25181160 5568.7
## + Habitaciones    1     12397 25008968 5569.1
## - Banos           1   2172816 27194180 5608.3
## - Area_contruida  1   4441735 29463100 5649.6
## - parqueaderos    1   4757293 29778658 5655.0
summary(mod4_step)
## 
## Call:
## lm(formula = precio_millon ~ Area_contruida + parqueaderos + 
##     Banos + Estrato, data = datos_sub)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -855.61 -115.12  -38.65   77.36  991.47 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)    -227.05086  163.27215  -1.391   0.1649    
## Area_contruida    0.75608    0.07946   9.515  < 2e-16 ***
## parqueaderos     88.20024    8.95695   9.847  < 2e-16 ***
## Banos            59.50806    8.94200   6.655 7.33e-11 ***
## Estrato          50.57305   28.02252   1.805   0.0717 .  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 221.5 on 510 degrees of freedom
## Multiple R-squared:  0.6599, Adjusted R-squared:  0.6573 
## F-statistic: 247.4 on 4 and 510 DF,  p-value: < 2.2e-16

El método step va quitando variables mientras el AIC baje. Primero quitó Habitaciones, que probablemente aporta la misma información que los baños (su correlación es 0.72). Después quitó TipoCasa. El modelo final queda:

precio = -227.05 + 0.756 * Area + 88.20 * parqueaderos + 59.51 * Banos + 50.57 * Estrato

El R² ajustado se mantiene en 0.657 con menos variables, así que el modelo quedó más simple sin perder explicación.

Validación de supuestos

par(mfrow = c(2, 2))
plot(mod4_step)

shapiro.test(residuals(mod4_step))
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(mod4_step)
## W = 0.88376, p-value < 2.2e-16

Los problemas son parecidos a los de los modelos simples: los residuos forman un embudo y la línea del Scale-Location sube (heterocedasticidad). El Q-Q y el Shapiro (p < 0.05) muestran que los residuos no son normales.

Transformación logarítmica

Se repite el modelo con logaritmo en el precio y en el área. Las demás variables se dejan igual porque son conteos.

mod4_log <- lm(log(precio_millon) ~ log(Area_contruida) + parqueaderos + Banos + Habitaciones + Estrato + Tipo,
               data = datos_sub)
mod4_log_step <- step(mod4_log, direction = "both")
## Start:  AIC=-1495.58
## log(precio_millon) ~ log(Area_contruida) + parqueaderos + Banos + 
##     Habitaciones + Estrato + Tipo
## 
##                       Df Sum of Sq    RSS     AIC
## <none>                             27.466 -1495.6
## - Habitaciones         1    0.1535 27.619 -1494.7
## - Estrato              1    0.3015 27.767 -1492.0
## - Tipo                 1    0.6601 28.126 -1485.3
## - Banos                1    1.3732 28.839 -1472.5
## - parqueaderos         1    2.9943 30.460 -1444.3
## - log(Area_contruida)  1   16.0716 43.537 -1260.3
summary(mod4_log_step)
## 
## Call:
## lm(formula = log(precio_millon) ~ log(Area_contruida) + parqueaderos + 
##     Banos + Habitaciones + Estrato + Tipo, data = datos_sub)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.93691 -0.13285 -0.02248  0.12603  0.90688 
## 
## Coefficients:
##                      Estimate Std. Error t value Pr(>|t|)    
## (Intercept)          2.957486   0.205245  14.410  < 2e-16 ***
## log(Area_contruida)  0.531217   0.030811  17.241  < 2e-16 ***
## parqueaderos         0.070888   0.009526   7.442 4.27e-13 ***
## Banos                0.060979   0.012100   5.040 6.50e-07 ***
## Habitaciones        -0.025008   0.014843  -1.685 0.092643 .  
## Estrato              0.069901   0.029601   2.361 0.018579 *  
## TipoCasa            -0.099058   0.028349  -3.494 0.000517 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2325 on 508 degrees of freedom
## Multiple R-squared:  0.775,  Adjusted R-squared:  0.7724 
## F-statistic: 291.7 on 6 and 508 DF,  p-value: < 2.2e-16

Interpretación

En este caso el step no quitó ninguna variable. Dejando lo demás constante:

  • log(Área): si el área aumenta 1%, el precio aumenta 0.53%.
  • Parqueaderos: cada uno sube el precio cerca de 7.3%.
  • Baños: cada uno sube el precio cerca de 6.3%.
  • Habitaciones: cada una baja el precio 2.5%, pero solo es significativa al 10%. Puede ser porque si el área es la misma, más habitaciones significa habitaciones más pequeñas.
  • Estrato: cada nivel sube el precio cerca de 7.2%.
  • TipoCasa: una casa vale en promedio 9.4% menos que un apartamento con las mismas características (p = 0.0005).

Lo interesante es que la dummy no era significativa en el modelo lineal y en el logarítmico sí. En porcentajes se ve mejor la diferencia entre casas y apartamentos. El R² ajustado es 0.772.

par(mfrow = c(2, 2))
plot(mod4_log_step)

shapiro.test(residuals(mod4_log_step))
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(mod4_log_step)
## W = 0.97543, p-value = 1.315e-07

Con el logaritmo el embudo de los residuos casi desaparece y el Q-Q queda más cerca de la línea. El Shapiro todavía da menor a 0.05, pero el p-valor mejora mucho (de menos de 2.2e-16 a 0.0000001). Es mejor el modelo logarítmico.

Modelo múltiple 2 (con interacción)

En el gráfico del inicio se vio que la pendiente de las casas es diferente a la de los apartamentos. Para tener eso en cuenta, en este modelo se agrega una interacción entre el área y el tipo de vivienda, parecido al ejemplo de clase con zona y área.

mod5 <- lm(precio_millon ~ Area_contruida * Tipo + parqueaderos + Banos + Habitaciones + Estrato,
           data = datos_sub)
mod5_step <- step(mod5, direction = "both")
## Start:  AIC=5529.61
## precio_millon ~ Area_contruida * Tipo + parqueaderos + Banos + 
##     Habitaciones + Estrato
## 
##                       Df Sum of Sq      RSS    AIC
## - Habitaciones         1      1848 22983573 5527.7
## - Estrato              1     62928 23044654 5529.0
## <none>                             22981726 5529.6
## - Banos                1    731050 23712775 5543.7
## - Area_contruida:Tipo  1   1979448 24961174 5570.2
## - parqueaderos         1   4183361 27165087 5613.7
## 
## Step:  AIC=5527.65
## precio_millon ~ Area_contruida + Tipo + parqueaderos + Banos + 
##     Estrato + Area_contruida:Tipo
## 
##                       Df Sum of Sq      RSS    AIC
## - Estrato              1     64885 23048458 5527.1
## <none>                             22983573 5527.7
## + Habitaciones         1      1848 22981726 5529.6
## - Banos                1    934318 23917891 5546.2
## - Area_contruida:Tipo  1   1982594 24966167 5568.3
## - parqueaderos         1   4181891 27165464 5611.7
## 
## Step:  AIC=5527.11
## precio_millon ~ Area_contruida + Tipo + parqueaderos + Banos + 
##     Area_contruida:Tipo
## 
##                       Df Sum of Sq      RSS    AIC
## <none>                             23048458 5527.1
## + Estrato              1     64885 22983573 5527.7
## + Habitaciones         1      3804 23044654 5529.0
## - Banos                1    951182 23999640 5545.9
## - Area_contruida:Tipo  1   2074807 25123265 5569.5
## - parqueaderos         1   4218370 27266828 5611.7
summary(mod5_step)
## 
## Call:
## lm(formula = precio_millon ~ Area_contruida + Tipo + parqueaderos + 
##     Banos + Area_contruida:Tipo, data = datos_sub)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -812.09 -119.88  -27.98   69.23  971.02 
## 
## Coefficients:
##                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)             -87.1978    37.0882  -2.351   0.0191 *  
## Area_contruida            2.3194     0.2403   9.651  < 2e-16 ***
## TipoCasa                248.1720    47.2755   5.249 2.24e-07 ***
## parqueaderos             83.7615     8.6783   9.652  < 2e-16 ***
## Banos                    43.6916     9.5330   4.583 5.77e-06 ***
## Area_contruida:TipoCasa  -1.5768     0.2329  -6.769 3.58e-11 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 212.8 on 509 degrees of freedom
## Multiple R-squared:  0.6868, Adjusted R-squared:  0.6837 
## F-statistic: 223.2 on 5 and 509 DF,  p-value: < 2.2e-16

Interpretación

El step quitó Habitaciones y Estrato. La interacción es significativa, entonces el efecto del área sí cambia según el tipo de vivienda:

  • En apartamentos, cada m² adicional sube el precio 2.32 millones.
  • En casas, cada m² adicional sube el precio 2.32 - 1.58 = 0.74 millones.

O sea que en Ciudad Jardín el metro cuadrado de apartamento vale bastante más que el de casa. El R² ajustado es 0.684.

par(mfrow = c(2, 2))
plot(mod5_step)

shapiro.test(residuals(mod5_step))
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(mod5_step)
## W = 0.895, p-value < 2.2e-16

Este modelo tiene los mismos problemas que el lineal anterior: los residuos no son normales (Shapiro p < 0.05) y se sigue viendo el embudo de heterocedasticidad.

Transformación logarítmica

mod5_log <- lm(log(precio_millon) ~ log(Area_contruida) * Tipo + parqueaderos + Banos + Habitaciones + Estrato,
               data = datos_sub)
mod5_log_step <- step(mod5_log, direction = "both")
## Start:  AIC=-1548.15
## log(precio_millon) ~ log(Area_contruida) * Tipo + parqueaderos + 
##     Banos + Habitaciones + Estrato
## 
##                            Df Sum of Sq    RSS     AIC
## <none>                                  24.704 -1548.2
## - Habitaciones              1    0.0975 24.802 -1548.1
## - Estrato                   1    0.1530 24.857 -1547.0
## - Banos                     1    0.7041 25.409 -1535.7
## - log(Area_contruida):Tipo  1    2.7614 27.466 -1495.6
## - parqueaderos              1    3.3724 28.077 -1484.2
summary(mod5_log_step)
## 
## Call:
## lm(formula = log(precio_millon) ~ log(Area_contruida) * Tipo + 
##     parqueaderos + Banos + Habitaciones + Estrato, data = datos_sub)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.84751 -0.12716 -0.01073  0.11442  0.86808 
## 
## Coefficients:
##                               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                   1.867886   0.242722   7.696 7.41e-14 ***
## log(Area_contruida)           0.784130   0.044545  17.603  < 2e-16 ***
## TipoCasa                      1.709476   0.241741   7.072 5.11e-12 ***
## parqueaderos                  0.075396   0.009063   8.319 8.23e-16 ***
## Banos                         0.044456   0.011695   3.801 0.000161 ***
## Habitaciones                 -0.019956   0.014107  -1.415 0.157798    
## Estrato                       0.050013   0.028225   1.772 0.077005 .  
## log(Area_contruida):TipoCasa -0.350697   0.046585  -7.528 2.37e-13 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2207 on 507 degrees of freedom
## Multiple R-squared:  0.7976, Adjusted R-squared:  0.7948 
## F-statistic: 285.5 on 7 and 507 DF,  p-value: < 2.2e-16

Interpretación

En la versión logarítmica el step deja todas las variables. Ahora los coeficientes del área son elasticidades:

  • En apartamentos, si el área sube 1%, el precio sube 0.78%.
  • En casas, si el área sube 1%, el precio sube 0.78 - 0.35 = 0.43%.

Se confirma lo mismo que en el modelo lineal: en las casas el tamaño pesa menos en el precio. Cada parqueadero sube el precio cerca de 7.8% y cada baño cerca de 4.5%. Este modelo tiene el R² ajustado más alto de todo el trabajo (0.795).

par(mfrow = c(2, 2))
plot(mod5_log_step)

shapiro.test(residuals(mod5_log_step))
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(mod5_log_step)
## W = 0.98103, p-value = 3.015e-06

De todos los modelos, este es el que tiene mejores gráficos. El Q-Q está casi sobre la línea y los residuos no muestran un patrón claro. La línea del Scale-Location sube un poco, pero mucho menos que en la versión lineal. El Shapiro sigue dando menor a 0.05, pero con un p-valor mucho más alto (0.000003).

Predicción hipotética

Quiero estimar el precio de una casa y de un apartamento en Ciudad Jardín con estas características: 200 m², 2 parqueaderos, 3 baños, 3 habitaciones y estrato 6.

En los modelos con logaritmo la predicción sale en log, así que se usa exp() para pasarla a millones.

nueva <- data.frame(Area_contruida = c(200, 200),
                    parqueaderos = c(2, 2),
                    Banos = c(3, 3),
                    Habitaciones = c(3, 3),
                    Estrato = c(6, 6),
                    Tipo = c("Casa", "Apartamento"))

# Modelo múltiple 1 lineal
predict(mod4_step, nueva, interval = "prediction")
##       fit      lwr      upr
## 1 582.528 146.4991 1018.557
## 2 582.528 146.4991 1018.557
# Modelo múltiple 1 logarítmico
exp(predict(mod4_log_step, nueva, interval = "prediction"))
##        fit      lwr      upr
## 1 567.9764 358.8851 898.8869
## 2 627.1199 396.3805 992.1763
# Modelo múltiple 2 logarítmico
exp(predict(mod5_log_step, nueva, interval = "prediction"))
##        fit      lwr      upr
## 1 600.7597 388.4364  929.141
## 2 697.0260 450.5320 1078.381

Interpretación

  • El modelo 1 lineal da lo mismo para la casa y el apartamento (582 millones) porque el step quitó la variable Tipo. Además el intervalo es muy amplio (de 146 a 1019 millones).
  • El modelo 1 logarítmico estima 568 millones para la casa y 627 millones para el apartamento.
  • El modelo 2 logarítmico estima 601 millones para la casa y 697 millones para el apartamento.

En los modelos logarítmicos el apartamento sale más caro, lo cual coincide con lo que se encontró antes: con el mismo tamaño, el m² de apartamento vale más. Los intervalos también son más angostos que en el modelo lineal.

Métricas de evaluación (MAE)

El MAE (error absoluto medio) dice en promedio cuántos millones se equivoca el modelo al predecir el precio. Entre más bajo, mejor.

Para calcularlo se dividen los datos: con el 80% se estiman los modelos y con el otro 20% se prueba qué tan bien predicen.

set.seed(123)
filas <- sample(1:nrow(datos_sub), size = round(0.8 * nrow(datos_sub)))
entrenamiento <- datos_sub[filas, ]
prueba <- datos_sub[-filas, ]

# Se vuelven a estimar los modelos con los datos de entrenamiento
e1 <- lm(precio_millon ~ Area_contruida, data = entrenamiento)
e2 <- lm(log(precio_millon) ~ log(Area_contruida), data = entrenamiento)
e3 <- lm(formula(mod4_step), data = entrenamiento)
e4 <- lm(formula(mod4_log_step), data = entrenamiento)
e5 <- lm(formula(mod5_log_step), data = entrenamiento)

real <- prueba$precio_millon

MAE <- data.frame(
  Modelo = c("Simple lineal (área)", "Simple log-log (área)", "Múltiple 1 lineal",
             "Múltiple 1 logarítmico", "Múltiple 2 logarítmico"),
  MAE = c(mean(abs(real - predict(e1, prueba))),
          mean(abs(real - exp(predict(e2, prueba)))),
          mean(abs(real - predict(e3, prueba))),
          mean(abs(real - exp(predict(e4, prueba)))),
          mean(abs(real - exp(predict(e5, prueba))))))
MAE
Modelo MAE
Simple lineal (área) 170.1133
Simple log-log (área) 148.4031
Múltiple 1 lineal 159.2722
Múltiple 1 logarítmico 132.6572
Múltiple 2 logarítmico 136.0722

Interpretación

  • El modelo simple lineal se equivoca en promedio 170 millones por vivienda.
  • El modelo múltiple 1 logarítmico es el que menos se equivoca: 133 millones en promedio.
  • El modelo múltiple 2 logarítmico queda muy cerca, con 136 millones.
  • En todos los casos la versión con logaritmo predice mejor que la lineal, y los modelos múltiples predicen mejor que los simples.

Conclusiones

  • El área construida es la variable que más explica el precio de las viviendas en Ciudad Jardín, seguida de los parqueaderos y los baños. El estrato aporta poco porque casi todo el barrio es estrato 6.
  • La transformación logarítmica mejoró todos los modelos: los supuestos se cumplen mejor, aumenta el R² y baja el MAE. Esto tiene sentido porque en vivienda las características cambian el precio más en porcentaje que en una cantidad fija.
  • La variable dummy mostró que, con las mismas características, una casa vale cerca de 9% menos que un apartamento. El modelo con interacción explica por qué: en las casas cada m² adicional aumenta menos el precio.
  • El modelo con mejor R² es el múltiple 2 logarítmico (0.795) y el que menor MAE tuvo fue el múltiple 1 logarítmico (133 millones). Los dos son buenos y la diferencia entre ellos es pequeña.
  • Una limitación es que la base no tiene información de cosas como acabados, antigüedad o si la vivienda está en conjunto cerrado, que también influyen en el precio. Además, en ningún modelo se cumple del todo la normalidad ni la homocedasticidad.