Evaluación de la oferta inmobiliaria urbana

Problema

Hace dos días, María recibió una carta solicitando asesoría para la compra de dos viviendas por parte de una compañía internacional que desea ubicar a dos de sus empleados con sus familias en la ciudad. Las solicitudes incluyen las siguientes condiciones:

Informe ejecutivo de recomendaciones

Casas Zona Norte

De acuerdo con la solicitud de la compañía internacional realizada a María, después de realizar el análisis de datos, se encuentran 6 ofertas que cumplen con las características del requerimiento del cliente para la zona norte. Se priorizó en este caso el presupuesto como la variable para determinar las ofertas, considerando que ya se tiene un crédito pre-aprobado por ese valor. En segundo lugar, se tuvo en cuenta que se cumpliera con el número de habitaciones solicitado, considerando que la compañía desea ubicar a los empleados y a sus familias, por lo que la disponibilidad de cuartos es relevante para este caso.

Bajo esos dos criterios principales, se encontró que para el presupuesto planteado en este caso hay 6 ofertas de casas que cumplen con los requerimientos y que mejoran otros aspectos del inmueble como el área construida, el número de baños y de parqueaderos, por lo que la empresa internacional tendrá diferentes opciones que se adapten a las características de las familias de sus empleados.

Apartamentos Zona Sur

De acuerdo con la solicitud de la compañía internacional realizada a María, después de realizar el análisis de datos, se encuentran 5 ofertas que cumplen con las características del requerimiento del cliente para la zona norte. Se priorizó en este caso el presupuesto como la variable para determinar las ofertas, considerando que ya se tiene un crédito pre-aprobado por 850 millones. Dado que el presupuesto es amplio, se consideraron apartamentos que tuvieran un presupuesto menor y que a la vez cumplieran con tener 300 metros cuadrados o más construidos, porque esto implicaría una mejor de espacio por un presupuesto igual o menor.

Respecto al número de habitaciones, solo hay una oferta que coincide con lo solicitado. Si bien, esta variable es relevante, dado el tipo de vivienda (apartamento), es posible que 5 habitaciones sea un número más escaso, a menos que se traten de viviendas de lujo, por lo que, para entregar un mayor número de opciones se flexibilizó esta condición.

Bajo los criterios mencionados, se encontró que para el presupuesto planteado en este caso hay 5 ofertas de apartamentos que cumplen con los requerimientos de presupuesto. Sin embargo, de estas se seleccionaron 3 particularmente, por la coherencia de la información, dado que las demás resultaban inconsistentes en la relación entre precios y área construida y que mejoran otros aspectos del inmueble como el área construida, por lo que la empresa internacional tendrá opciones que se adapten a las características de las familias de sus empleados.

Anexos

Pasos sugeridos:

  1. Realice un filtro a la base de datos e incluya solo las ofertas de : base1: casas, de la zona norte de la ciudad. Presente los primeros 3 registros de las bases y algunas tablas que comprueben la consulta. (Adicional un mapa con los puntos de las bases. Discutir si todos los puntos se ubican en la zona correspondiente o se presentan valores en otras zonas, por que?).

  2. Realice un análisis exploratorio de datos enfocado en la correlación entre la variable respuesta (precio de la casa) en función del área construida, estrato, numero de baños, numero de habitaciones y zona donde se ubica la vivienda. Use gráficos interactivos con el paquete plotly e interprete los resultados.

  3. Estime un modelo de regresión lineal múltiple con las variables del punto anterior (precio = f(área construida, estrato, número de cuartos, número de parqueaderos, número de baños ) ) e interprete los coeficientes si son estadísticamente significativos. Las interpretaciones deber están contextualizadas y discutir si los resultados son lógicos. Adicionalmente interprete el coeficiente R2 y discuta el ajuste del modelo e implicaciones (que podrían hacer para mejorarlo).

  4. Realice la validación de supuestos del modelo e interprete los resultados (no es necesario corregir en caso de presentar problemas, solo realizar sugerencias de que se podría hacer).

  5. Con el modelo identificado debe predecir el precio de la vivienda con las características de la primera solicitud.

  6. Con las predicciones del modelo sugiera potenciales ofertas que responda a la solicitud de la vivienda 1. Tenga encuentra que la empresa tiene crédito pre-aprobado de máximo 350 millones de pesos. Realice un análisis y presente en un mapa al menos 5 ofertas potenciales que debe discutir.

  7. Realice los pasos del 1 al 6. Para la segunda solicitud que tiene un crédito pre-aprobado por valor de $850 millones.

Proceso

A continuación se presenta a manera de anexo el detalle del procesamiento de datos y de los análisis realizados.

Solicitud 1: Casas zona norte

Filtro de los datos

De acuerdo con las indicaciones se realizará el filtro de casas, de la zona norte de la ciudad, conservando el resto de variables del dataset.

vivienda_1 <- vivienda %>% filter(
        tipo == "Casa",
        zona == "Zona Norte")

head(vivienda_1)

Mapa del norte de Cali

Para identificar las propiedades que pertenecen a la zona norte de la ciudad de Cali, se tomó información del mapa de Zonas Geográficas de Cali de la Infraestructura de Datos Espaciales de Santiago de Cali - IDESC. Tomando como referencia este mapa, pese a que no contaba con las coordenadas específicas para crear el polígono de la zona, se podían identificar los barrios, por lo que se tomaron los nombres de los barrios en el perímetro de la zona y se buscaron las coordenadas aproximadas de los extremos de la zona identificados en el mapa para crear el polígono, utilizando Google maps.

Link Mapa de Zonas Geográficas: https://idesc.cali.gov.co/download/mapas/zonas_geograficas.pdf

viv_mapa1 <- vivienda_1 %>% select(latitud,longitud)


# Definir polígono de la zona norte
zona_norte <- list(
    lon = c (-76.519295, -76.476058, -76.492045, -76.507260, -76.509397, -76.529789, -76.529046, -76.533983,
             -76.538559, -76.532022, -76.530050, -76.534790, -76.540641, -76.521301, -76.518343, -76.519295),
    lat= c(3.454446, 3.468228, 3.505582, 3.494095, 3.490089, 3.497610, 3.489589, 3.492937, 3.489572, 3.483108,
            3.475960, 3.464275, 3.451531, 3.460680, 3.454782, 3.454446) 
)

# Crear mapa con viviendas + sombreado de zona norte
map1 <- leaflet(viv_mapa1) %>%
  addTiles() %>%
  addMarkers(
    lng = ~longitud,
    lat = ~latitud,
    popup = ~as.character(latitud)
  ) %>%
  addPolygons(
    lng = zona_norte$lon,
    lat = zona_norte$lat,
    fillColor = "blue",
    fillOpacity = 0.2,
    color = "darkblue",
    weight = 2,
    popup = "Zona Norte de Cali"
  )

map1

Teniendo en cuenta el anterior mapa, se identifica que múltiples propiedades identificadas como pertenecientes a la zona norte, realmente se encuentran ubicadas en otras zonas de la ciudad. Teniendo en cuenta el ejercicio previo con la base de datos, esto se puede explicar por el hecho de que existen barrios con nombres similares que se ubican en zonas distintos de la ciudad, en general la variable Barrios tenía muchos errores tipográficos y nombres que no se podían identificar con plenitud. Si esta variable fue el criterio para asignar la zona de la ciudad, existe la posibilidad de que algunos barrios se confundieran con otros similares de la zona norte y por ello quedaran mal clasificados.

Depuración base de datos

Teniendo que hay observaciones de la base que se encuentran por fuera de la zona norte de la ciudad, a continuación, se depuran las viviendas con base en el área del polígono definida para la zona norte, con el fin de realizar los siguientes análisis y estimaciones basados en la correcta clasificación de las viviendas.

# Crear un polígono a partir de la variable de coordenadas "zona_norte"
znorte_pol <- st_polygon(list(cbind(zona_norte$lon, zona_norte$lat))) %>%
  st_sfc(crs = 4326) # Define el Sistema de referencia de coordenadas para que las identifique como latitud y longitud

# Se convierte la base de viviendas en un objeto sf con puntos
viv_sf <- st_as_sf(vivienda_1, coords = c("longitud", "latitud"), crs = 4326)

# 3. Filtrar solo los puntos que están dentro del polígono
vivienda_norte <- viv_sf %>%
  filter(st_within(geometry, znorte_pol, sparse = FALSE))

# Validar el mapa ajustado
map2 <- leaflet(vivienda_norte) %>%
  addTiles() %>%
    addMarkers(
    data = vivienda_norte,
    popup = ~paste("Precio:", preciom)
  ) %>%
  addPolygons(
    lng = zona_norte$lon,
    lat = zona_norte$lat,
    fillColor = "blue",
    fillOpacity = 0.2,
    color = "darkblue",
    weight = 2,
    popup = "Zona Norte de Cali"
  )

map2

Después de esta depuración, se puede validar en el mapa que la base solo contiene las viviendas en la zona norte, de acuerdo con el polígono especificado. La base después de esta depuración contiene 524 viviendas.

Análisis exploratorio

Para analizar la correlación entre la variable de precio de las casas en función de las variables de área construida, estrato, número de baños, número de habitaciones y zona de la vivienda, se presentan a continuación los diagramas de dispersión para evaluar la correlación entre pares de variables.

# Diagramas de dispersión por pares de variables cuantitativas

#transformar "vivienda_norte" a un data frame eliminando la variable de geometría

vivienda_ngraf <- st_drop_geometry(vivienda_norte)

#Crear diagramas de dispersión
p1 <- plot_ly(vivienda_ngraf, x = ~areaconst, y = ~preciom, type = "scatter", mode = "markers") %>%
   layout(
    title = "Precio vs Área Construida",
    xaxis = list(title = "Área construida (m²)"),
    yaxis = list(title = "Precio (millones)")
  )
p2 <- plot_ly(vivienda_ngraf, x = ~banios, y = ~preciom, type = "scatter", mode = "markers") %>%
   layout(
    title = "Precio vs Número de baños",
    xaxis = list(title = "Número de baños"),
    yaxis = list(title = "Precio (millones)")
  )
p3 <- plot_ly(vivienda_ngraf, x = ~habitaciones, y = ~preciom, type = "scatter", mode = "markers") %>%
   layout(
    title = "Precio vs Número de habitaciones",
    xaxis = list(title = "Número de habitaciones"),
    yaxis = list(title = "Precio (millones)")
  )

# Poner todos los gráficos en un Panel 2x2 con todos al tiempo
panel1 <-  subplot(p1, p2, p3, nrows = 2, margin = 0.05, shareX = FALSE, shareY = FALSE, titleX = TRUE, titleY = TRUE)

panel1

Los gráficos anteriores muestran que existe una relación positiva entre precio y área construida, porque a medida que incrementa el área, el precio se comporta de la misma manera. Por el contrario, con variables como el número de baños o habitaciones, no es posible establecer esta relación, porque visualmente no se encuentra que a medida que incrementa el número de baños o habitaciones el precio se comporte de la misma manera. Para comprobar numéricamente lo anterior, se genera la matriz de correlaciones, visualizada en mapa de calor.

# Matriz de correlaciones

var_num <- vivienda_norte %>% select(preciom, areaconst, banios, habitaciones) #seleccionar solo variables cuantitativas
var_num <- st_drop_geometry(var_num)
mat_corr <- cor(var_num)

p4 <- plot_ly(
  x = colnames(mat_corr), y = rownames(mat_corr),
  z = mat_corr, type = "heatmap"
)
p4

El mapa de calor de las correlaciones permite comprobar que existe una correlación lineal más fuerte que con otras variables entre el precio y el área construida, así como la falta de correlación lineal del precio con el número de baños y habitaciones.

En cuanto a las variables cualitativas el análisis de correlación se presentará a traves de boxplot que permitan ver los cambios por las diferentes categorías.

#Convertir variable "estrato" en as.factor, para que no sea considerada como una variable continua sino una numérica ordinal y que se interprete cada número del estrato como un nivel

#Precio vs estrato
box1 <- vivienda_norte
box1 <- st_drop_geometry(box1)
box1$estrato <- as.factor(box1$estrato)

p5 <- plot_ly(box1, y = ~preciom, color = ~estrato, type = "box") %>%
   layout(
    title = "Precio vs Estrato",
    xaxis = list(title = "Estrato"),
    yaxis = list(title = "Precio (millones)")
  )

p5

Los boxplot de este gráfico permiten observar que el precio de la vivienda tiene una diferenciación por estrato, particularmente al comparar las viviendas estrato 3 y estrato 6, que se encuentran en rangos muy distintos de precio y aún la mayoría los outliers encontrados en el estrato 3, alcanzan apenas el rango mínimo de precios encontrados en el estrato 6. Por otra parte, se encuentra que entre estratos 4 y 5 existe cierta similitud de precio y es necesario revisar los outliers de estos estratos, dado que algunos de ellos superan los precios encontrados para el estrato 6.

#Precio vs Zona
p6 <- plot_ly(box1, y = ~preciom, color = ~zona, type = "box") %>%
   layout(
    title = "Precio vs Zona",
    xaxis = list(title = "Precio"),
    yaxis = list(title = "zona")
  )

p6

Con relación a la zona, dado que solo está filtrada la zona norte de la ciudad, se encuentra que la mayor cantidad de viviendas, independientemente de su estrato, se encuentran en un precio inferior a los 550 millones, por lo que el análisis anterior por estrato permite establecer de mejor manera cómo manejar estos datos.

Modelo de Regresión Lineal múltiple

Teniendo en cuenta la base anterior sin modificar sus datos atípicos, se realiza el modelo de regresión lineal múltiple, separando inicialmente un set de entrenamiento y prueba.

#Fijar semilla y seleccionar del dataset solo las variables necesarias
set.seed(123)

data_modelo1 <- vivienda_norte %>% select(preciom, areaconst, estrato, banios, habitaciones, parqueaderos, geometry) 
data_modelo1$estrato <- as.factor(data_modelo1$estrato)

data_modelo1 <- data_modelo1 %>% na.omit() # se omiten los valores nulos de la variable "parqueaderos"

# Separar set de entrenamiento y de prueba
n <- nrow(data_modelo1)
set_train <- sample(seq_len(n), size = floor(0.7*n))
data_train <- data_modelo1[set_train, ]
data_test  <- data_modelo1[-set_train, ]

#Modelo de regresión lineal múltiple
rlm_sol1 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = data_train)
summary(rlm_sol1)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = data_train)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -653.01  -76.80  -13.22   41.06  948.81 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   61.61994   36.08378   1.708  0.08900 .  
## areaconst      0.62017    0.06905   8.981  < 2e-16 ***
## estrato4     101.08207   33.23503   3.041  0.00262 ** 
## estrato5     145.32559   31.19963   4.658 5.33e-06 ***
## estrato6     348.53243   56.12057   6.210 2.35e-09 ***
## habitaciones   1.89239    7.59997   0.249  0.80358    
## parqueaderos  41.23366    7.90108   5.219 3.93e-07 ***
## banios         2.04703   10.39279   0.197  0.84402    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 153.6 on 237 degrees of freedom
## Multiple R-squared:  0.6047, Adjusted R-squared:  0.593 
## F-statistic: 51.79 on 7 and 237 DF,  p-value: < 2.2e-16

Análisis del modelo

Los resultados de los coeficientes al ser positivos indican que por cada unidad adicional de cada variable (m2, baño, estrato, habitación o parqueadero) el precio tiende a incrementar en el valor indicado para cada variable. En este caso se encuentra que son muy significativas las variables de área construida, estrato 5 y 6 y el número de parqueaderos, dado que el valor p de estos coeficientes es inferior a 0.05. Sin embargo, dado lo observado en el análisis exploratorio resulta contradictorio que la variable de área construida sea la que representa el menor aumento sobre la variable precio.

El \(R^2 ajustado= 0.593\) indica que el 59.3% del precio de la vivienda se encuentra explicado por las variables utilizadas en el modelo para el set de entrenamiento. Al no ser muy alto puede indicar que se están incluyendo variables que no explican el modelo-

Validación de supuestos

Para validar los supuestos de los residuales relacionados con la normalidad, homocedasticidad (varianza constante) e independencia se realizan los gráficos y se aplican los estadísticos de prueba

Gráficos
par(mfrow = c(2, 2))

plot(rlm_sol1)

En el gráfico de residuales vs valores ajustados (superior izquierda), se espera que todas las observaciones se acerquen a 0 a lo largo del eje y que estos valores no tengan una forma diferente a la lineal. Por el gráfico observado se podría poner en cuestionamiento el supuesto de linealidad del modelo dado que hay varios residuales que se encuentran muy alejados de 0 y no se presenta un comportamiento homogéneo en todo el gráfico. Sin embargo, el gráfico de residuales estandarizados (superior derecha), donde ya se normalizan los residuos y se comparan frente a los cuantiles de una distribución normal teórica, se observa que la mayoría de las observaciones se ajusta a la línea de referencia (es decir, coinciden los valores empiricos con los teóricos) con excepción de algunos residuos al inicio y final de la recta, lo que permitiría establecer que podría no cumplirse el supuesto de normalidad de los residuos.

En el gráfico 3 (inferior izquierda) donde se evalúa la homogeneidad de varianza u homocedasticidad, se espera que los puntos se encuentren alrededor de la recta, sin que se presenten tendencias, lo cual se observa que no se cumple del todo porque a medida que aumentan los valores ajustados la raíz de los residuales estandarizados crece y se aleja de la recta. Por lo cual se podría poner en cuestionamiento el cumplimiento del supuesto de homocedasticidad.

En el gráfico 4 (inferior derecha), donde se analizan los puntos atípicos influyente en los estimadores,comparando los residuales estandarizados con el “leverage” o apalancamiento (distancia de los valores de las variables independientesde una observación respecto de las demás) se encuentra que existen puntos que se encuentran muy alejados de la recta, por lo que se considera que estos valores pueden afectar la estimación de los estimadores (\(\beta\)).

Test de evaluación de supuestos

Para confirmar las afirmaciones anteriores, se realizan los test de evaluación.

# Homogeneidad de la Varianza - Breusch-Pagan Test
bptest(rlm_sol1)
## 
##  studentized Breusch-Pagan test
## 
## data:  rlm_sol1
## BP = 49.071, df = 7, p-value = 2.197e-08

El resultado indica que los residuales no se distribuyen con la misma varianza, dado el \(p-value = 2.19e-08\) es inferior a a 0.05.

# Normalidad de los residuos - Shapiro Wilk
#Se utiliza este test dado que la muestra a evaluar es pequeña y esta prueba es más robusta
residuales = resid(rlm_sol1) # se realiza sobre los residuales del modelo

shapiro.test(residuales)
## 
##  Shapiro-Wilk normality test
## 
## data:  residuales
## W = 0.82858, p-value = 9.556e-16

El resultado indica que se rechaza la hipótesis nula de normalidad de los errores, toda vez que el \(p-value = 9.556e-16\), que es menor al criterio de aceptación de 0.05.

# Independencia de las observaciones - Test de Durbin-Watson
dwtest(rlm_sol1)
## 
##  Durbin-Watson test
## 
## data:  rlm_sol1
## DW = 1.9605, p-value = 0.3743
## alternative hypothesis: true autocorrelation is greater than 0

El resultado del estadístico de este test indica que no hay autocorrelación entre los residuos del modelo (cercano a 2), así mismo porque el \(p-value = 0.3743\), y al ser > 0.05 no se rechaza la hipótesis nula, sugiriendo que los residuos son independientes

Predicción del modelo en el set de prueba

Para evaluar el desempeño del modelo se realiza la predicción sobre el set de prueba

# Predicciones en test y métricas
prediccion1 <- predict(rlm_sol1, newdata = data_test)

RMSE <- sqrt(mean((data_test$preciom - prediccion1)^2))
MAE  <- mean(abs(data_test$preciom - prediccion1))
R2_oos <- cor(data_test$preciom, prediccion1)^2

c(RMSE = RMSE, MAE = MAE, R2_test = R2_oos)
##        RMSE         MAE     R2_test 
## 143.6412092 105.5985252   0.5319299

El \(R^2\) indica que más del 50% de la variación de los precios se explica por las variables incluidas en el conjunto de prueba, al ser similar al estimado en el modelo realizado con el test de entrenamiento, se puede concluir que el modelo no pierde en gran medida la bondad de ajuste con los datos de prueba.

No obstante los indicadores RMSE y MAE, son muy elevados, indican que las predicciones en el precio de vivienda se desviarían entre 105 y 143 millones del valor real. Considerando los rangos de precio del dataset, es una desviación muy alta del valor real.

Posibles ajustes

Teniendo en cuenta los diferentes resultados de los diferentes estadísticos para evaluar el ajuste del modelo y el cumplimiento de los supuestos se pueden establecer las siguientes recomendaciones.

  1. Dado que existe una potencial correlación entre las variables de número de baños y habitaciones (Correlación = 0.60), es pertinente evaluar la inclusión de una de las dos variables porque la inclusión de las dos puede estar ocasionando problemas en el ajuste del modelo y de colinealidad.
  2. Revisar los puntos atípicos influyentes de las predictoras y validar si corresponden a errores de la base de datos y si no es posible imputarlos con algún método, eliminar estas observaciones porque estos afectan la estimación de los estimadores \(\beta\) del modelo. Dado que en los boxplot se observa que los datos atípicos por estrato no son numerosos, es viable realizar una revisión de cada caso para no perder observaciones del dataset. Evaluar la influencia de los atípicos influyentes sobre el ajuste del modelo puede ser orientar la decisión de eliminarlos o imputar con algún criterio alguna medida de tendencia central.
  3. Transformar la variable de precio de la vivienda en logaritmo, dado que convierte la relación entre esta variable y las predictoras en una relación más lineal que puede mejorar el ajuste del modelo, así como su interpretación, porque el incremento en las unidades de las predictoras se evaluará como un porcentaje de aumento en el precio.

Predicción del precio de vivienda: Solicitud 1

#Crear un dataframe con las características de la solicitud 1 - Estrato 4


viv_sol11 <- data.frame(
  areaconst   = 200,
  estrato     = 4,  
  habitaciones= 4,  
  parqueaderos= 1,  
  banios      = 2
)
viv_sol11$estrato = as.factor(viv_sol11$estrato)

# Realizar la predicción 
pred_v11 <- predict(rlm_sol1, newdata = viv_sol11,
                       interval = "prediction", level = 0.95)

#Crear un dataframe con las características de la solicitud 1 - Estrato 5

viv_sol12 <- data.frame(
  areaconst   = 200,
  estrato     = 5,  
  habitaciones= 4,  
  parqueaderos= 1,  
  banios      = 2
)
viv_sol12$estrato = as.factor(viv_sol12$estrato)


# Realizar la predicción 
pred_v12 <- predict(rlm_sol1, newdata = viv_sol12,
                       interval = "prediction", level = 0.95)

pred_v11
##        fit      lwr      upr
## 1 339.6331 32.90234 646.3638
pred_v12
##        fit      lwr      upr
## 1 383.8766 78.03504 689.7182

De acuerdo con las predicciones del modelo, una vivienda con las características en mención para estrato 4 costaría 339 millones y en estrato 5, 383 millones. Teniendo en cuenta esto, es posible encontrar ofertas de vivienda para el presupuesto preaprobado.

Potenciales ofertas: Solicitud 1

Teniendo en cuenta las predicciones del modelo, se filtra de la base con la cual se realizó el modelo las viviendas teniendo en cuenta como parámetro principal que el presupuesto es de 350 millones, siguiendo por el número de habitaciones. A partir de ello se entregan las siguientes ofertas que igualan o mejoran algunas de las características solicitadas por el cliente respecto a baños, parqueaderos y metros cuadrados en los estratos sugeridos.

oferta_norte <- vivienda_norte %>% filter(
  preciom == 350,
  estrato == 4 | estrato == 5,
  habitaciones == 4,
  areaconst >= 200,
  parqueaderos != 0
  )

# Ubicar las ofertas en un mapa
map3 <- leaflet(oferta_norte) %>%
  addTiles() %>%
    addMarkers(
    data = oferta_norte,
    popup = ~paste(
      "Precio:", preciom, "millones<br>",
      "Área:", areaconst, "m²<br>",
      "Baños:", banios, "<br>",
      "Habitaciones:", habitaciones, "<br>",
      "Parqueaderos:", parqueaderos)
  ) %>%
  addPolygons(
    lng = zona_norte$lon,
    lat = zona_norte$lat,
    fillColor = "blue",
    fillOpacity = 0.2,
    color = "darkblue",
    weight = 2,
    popup = "Zona Norte de Cali"
  )

map3

Solicitud 2: Apartamentos zona sur

Filtro de los datos

De acuerdo con las indicaciones se realizará el filtro de apartamentos, de la zona sur de la ciudad, conservando el resto de variables del dataset.

vivienda_2 <- vivienda %>% filter(
        tipo == "Apartamento",
        zona == "Zona Sur")

head(vivienda_2)

Mapa del sur de Cali

Para identificar las propiedades que pertenecen a la zona sur de la ciudad de Cali, se realizó el mismo proceso que con el mapa de la zona norte, tomando la información del IDESC. Se tomaron los nombres de los barrios en el perímetro de la zona y se buscaron las coordenadas aproximadas de los extremos de la zona identificados en el mapa para crear el polígono, utilizando Google maps.

viv_mapa2 <- vivienda_2 %>% select(latitud,longitud)


# Definir polígono de la zona sur
zona_sur <- list(
    lon = c (-76.548965, -76.548943, -76.547634, -76.544179, -76.544994, -76.542613, -76.548150, -76.538742, 
             -76.522861, -76.522690, -76.513624, -76.509988, -76.504117, -76.506448, -76.500798, -76.500896,
             -76.513837, -76.518670, -76.522610, -76.525119, -76.537250, -76.535371, -76.549851, -76.551367,
             -76.546362, -76.544776, -76.548965),
    lat= c(3.370223, 3.370866, 3.371316, 3.371573, 3.373843, 3.375364, 3.415824, 3.410299,
           3.405674, 3.404914, 3.393563, 3.389131, 3.380565, 3.377228, 3.360660, 3.351828,
           3.327841, 3.317778, 3.317490, 3.319911, 3.321327, 3.333007, 3.331669, 3.342185, 
           3.354903, 3.365190, 3.370223)
) 


# Crear mapa con viviendas + sombreado de zona norte
map4 <- leaflet(viv_mapa2) %>%
  addTiles() %>%
  addMarkers(
    lng = ~longitud,
    lat = ~latitud,
    popup = ~as.character(latitud)
  ) %>%
  addPolygons(
    lng = zona_sur$lon,
    lat = zona_sur$lat,
    fillColor = "orange",
    fillOpacity = 0.2,
    color = "darkred",
    weight = 2,
    popup = "Zona Sur de Cali"
  )

map4

Teniendo en cuenta el anterior mapa, se identifica que múltiples propiedades identificadas como pertenecientes a la zona sur, realmente se encuentran ubicadas en otras zonas de la ciudad.

Depuración base de datos

Teniendo que hay observaciones de la base que se encuentran por fuera de la zona sur de la ciudad, a continuación, se depuran las viviendas con base en el área del polígono definida para la zona sur, con el fin de realizar los siguientes análisis y estimaciones basados en la correcta clasificación de las viviendas.

# Crear un polígono a partir de la variable de coordenadas "zona_sur"
zsur_pol <- st_polygon(list(cbind(zona_sur$lon, zona_sur$lat))) %>%
  st_sfc(crs = 4326) # Define el Sistema de referencia de coordenadas para que las identifique como latitud y longitud

# Se convierte la base de viviendas en un objeto sf con puntos
viv_sf2 <- st_as_sf(vivienda_2, coords = c("longitud", "latitud"), crs = 4326)

# 3. Filtrar solo los puntos que están dentro del polígono
vivienda_sur <- viv_sf2 %>%
  filter(st_within(geometry, zsur_pol, sparse = FALSE))

# Validar el mapa ajustado
map5 <- leaflet(vivienda_sur) %>%
  addTiles() %>%
    addMarkers(
    data = vivienda_sur,
    popup = ~paste("Precio:", preciom)
  ) %>%
  addPolygons(
    lng = zona_sur$lon,
    lat = zona_sur$lat,
    fillColor = "orange",
    fillOpacity = 0.2,
    color = "darkred",
    weight = 2,
    popup = "Zona Sur de Cali"
  )

map5

Después de esta depuración, se puede validar en el mapa que la base solo contiene las viviendas en la zona norte, de acuerdo con el polígono especificado. La base después de esta depuración contiene 1908 viviendas.

Análisis exploratorio

Para analizar la correlación entre la variable de precio de los apartamentos en función de las variables de área construida, estrato, número de baños, número de habitaciones y zona de la vivienda, se presentan a continuación los diagramas de dispersión para evaluar la correlación entre pares de variables.

# Diagramas de dispersión por pares de variables cuantitativas

#transformar "vivienda_norte" a un data frame eliminando la variable de geometría

vivienda_sg <- st_drop_geometry(vivienda_sur)

#Crear diagramas de dispersión
p7 <- plot_ly(vivienda_sg, x = ~areaconst, y = ~preciom, type = "scatter", mode = "markers") %>%
   layout(
    xaxis = list(title = "Área construida (m²)"),
    yaxis = list(title = "Precio (millones)")
  )
p8 <- plot_ly(vivienda_sg, x = ~banios, y = ~preciom, type = "scatter", mode = "markers") %>%
   layout(
    xaxis = list(title = "Número de baños"),
    yaxis = list(title = "Precio (millones)")
  )
p9 <- plot_ly(vivienda_sg, x = ~habitaciones, y = ~preciom, type = "scatter", mode = "markers") %>%
   layout(
    title = "Precio vs Área Construida, Número de habitaciones y baños",
    xaxis = list(title = "Número de habitaciones"),
    yaxis = list(title = "Precio (millones)")
  )

# Poner todos los gráficos en un Panel 2x2 con todos al tiempo
panel2 <-  subplot(p7, p8, p9, nrows = 2, margin = 0.05, shareX = FALSE, shareY = FALSE, titleX = TRUE, titleY = TRUE)

panel2

Los gráficos anteriores muestran que existe una relación positiva entre precio y área construida, porque a medida que incrementa el área, el precio se comporta de la misma manera, pero en este caso en dimensiones más amplias, porque un aumento de pocos metros representa en algunos casos incrementos muy grandes del precio. Por el contrario, con variables como el número de baños o habitaciones, no es posible establecer esta relación, porque visualmente no se encuentra que a medida que incrementa el número de baños o habitaciones el precio se comporte de la misma manera. Para comprobar numéricamente lo anterior, se genera la matriz de correlaciones, visualizada en mapa de calor.

# Matriz de correlaciones

var_num2 <- vivienda_sur %>% select(preciom, areaconst, banios, habitaciones) #seleccionar solo variables cuantitativas
var_num2 <- st_drop_geometry(var_num2)
mat_corr2 <- cor(var_num2)

p10 <- plot_ly(
  x = colnames(mat_corr2), y = rownames(mat_corr2),
  z = mat_corr2, type = "heatmap"
)
p10

El mapa de calor de las correlaciones permite comprobar que existe una correlación lineal muy entre precio y área construida y precio y número de baños. No existe una correlación lineal fuerte entre el precio y número de habitaciones.

En cuanto a las variables cualitativas el análisis de correlación se presentará a traves de boxplot que permitan ver los cambios por las diferentes categorías.

#Convertir variable "estrato" en as.factor, para que no sea considerada como una variable continua sino una numérica ordinal y que se interprete cada número del estrato como un nivel

#Precio vs estrato
box2 <- vivienda_sur
box2 <- st_drop_geometry(box2)
box2$estrato <- as.factor(box2$estrato)

p11 <- plot_ly(box2, y = ~preciom, color = ~estrato, type = "box") %>%
   layout(
    title = "Precio vs Estrato",
    xaxis = list(title = "Estrato"),
    yaxis = list(title = "Precio (millones)")
  )

p11

Los boxplot de este gráfico permiten observar que el precio de la vivienda tiene una diferenciación muy alta por estrato, particularmente al comparar las viviendas estrato 3 y estrato 6, que se encuentran en rangos muy distintos de precio. El outliers más extremo del estrato 3, alcanza a superar la mediana del precio en el estrato 6. Por otra parte. Así mismo en los estratos 4 y 5 existe las diferencias de precio están marcadas y se observan múltiples outliers que es necesario revisar.

#Precio vs Zona
p12 <- plot_ly(box2, y = ~preciom, color = ~zona, type = "box") %>%
   layout(
    title = "Precio vs Zona",
    xaxis = list(title = "Precio"),
    yaxis = list(title = "zona")
  )

p12

Con relación a la zona, dado que solo está filtrada la zona norte de la ciudad, se encuentra que la mayor cantidad de viviendas, independientemente de su estrato, se encuentran en un precio inferior a los 350 millones, por lo que el análisis anterior por estrato permite establecer de mejor manera cómo manejar estos datos. Se resalta que se perciben muchos outlier sobre este conjunto de apartamentos.

Modelo de Regresión Lineal múltiple

Teniendo en cuenta la base anterior sin modificar sus datos atípicos, se realiza el modelo de regresión lineal múltiple, separando inicialmente un set de entrenamiento y prueba.

#Fijar semilla y seleccionar del dataset solo las variables necesarias
set.seed(123)

data_modelo2 <- vivienda_sur %>% select(preciom, areaconst, estrato, banios, habitaciones, parqueaderos, geometry) 
data_modelo2$estrato <- as.factor(data_modelo2$estrato)

data_modelo2 <- data_modelo2 %>% na.omit() # se omiten los valores nulos de la variable "parqueaderos" 
#el dataset queda con 1666 observaciones

# Separar set de entrenamiento y de prueba
n2 <- nrow(data_modelo2)
set_train2 <- sample(seq_len(n), size = floor(0.7*n))
data_train2 <- data_modelo2[set_train, ]
data_test2  <- data_modelo1[-set_train, ]

#Modelo de regresión lineal múltiple
rlm_sol2 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = data_train2)
summary(rlm_sol2)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = data_train2)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -433.51  -44.68   -8.68   39.88  694.65 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -24.1317    54.2331  -0.445   0.6568    
## areaconst      2.6089     0.2002  13.029  < 2e-16 ***
## estrato4     -10.0321    39.7960  -0.252   0.8012    
## estrato5       5.4728    39.1645   0.140   0.8890    
## estrato6      98.0535    40.4397   2.425   0.0161 *  
## habitaciones -29.5283    14.5889  -2.024   0.0441 *  
## parqueaderos  24.3987    15.2360   1.601   0.1106    
## banios        47.5572    10.9724   4.334 2.16e-05 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 110.8 on 237 degrees of freedom
## Multiple R-squared:  0.8333, Adjusted R-squared:  0.8284 
## F-statistic: 169.2 on 7 and 237 DF,  p-value: < 2.2e-16

Análisis del modelo

Los resultados de los coeficientes no son coherentes con la lógica, particularmente en las variables “estrato 4” y “habitaciones”, porque indica que si incrementa una habitación o si cambia al estrato 4 el precio de la vivienda se reduce, lo cual es poco coherente, dado que un incremento en estrato o características implica mayor área construida y ubicación, por tanto no es lógico que el precio disminuya. En este caso se encuentra que son muy significativas las variables de área construida y el número de baños, dado que el valor p de estos coeficientes es inferior a 0.05. Sin embargo, dado lo observado en el análisis exploratorio resulta contradictorio que la variable de área construida sea la que representa el menor aumento sobre la variable precio.

El \(R^2 ajustado= 0.8284\) es considerablemente alto e indica que el 82.8% del precio de la vivienda se encuentra explicado por las variables utilizadas en el modelo para el set de entrenamiento.

Validación de supuestos

Para validar los supuestos de los residuales relacionados con la normalidad, homocedasticidad (varianza constante) e independencia se realizan los gráficos y se aplican los estadísticos de prueba

Gráficos
par(mfrow = c(2, 2))

plot(rlm_sol2)

En el gráfico de residuales vs valores ajustados (superior izquierda), se espera que todas las observaciones se acerquen a 0 a lo largo del eje y que estos valores no tengan una forma diferente a la lineal. Por el gráfico observado se podría poner en cuestionamiento el supuesto de linealidad del modelo dado que hay varios residuales que se encuentran muy alejados de 0 y no se presenta un comportamiento homogéneo en todo el gráfico. Sin embargo, el gráfico de residuales estandarizados (superior derecha), donde ya se normalizan los residuos y se comparan frente a los cuantiles de una distribución normal teórica, se observa que una cantidad considerable de las observaciones se ajusta a la línea de referencia (es decir, coinciden los valores empiricos con los teóricos), pero también hay un número alto de residuos al inicio y final de la recta que no se ajustan, lo que permitiría establecer que podría no cumplirse el supuesto de normalidad de los residuos.

En el gráfico 3 (inferior izquierda) donde se evalúa la homogeneidad de varianza u homocedasticidad, se espera que los puntos se encuentren alrededor de la recta, sin que se presenten tendencias, lo cual se observa que no se cumple del todo porque a medida que aumentan los valores ajustados la raíz de los residuales estandarizados crece y se aleja de la recta. Por lo cual se podría poner en cuestionamiento el cumplimiento del supuesto de homocedasticidad.

En el gráfico 4 (inferior derecha), donde se analizan los puntos atípicos influyente en los estimadores,comparando los residuales estandarizados con el “leverage” o apalancamiento (distancia de los valores de las variables independientesde una observación respecto de las demás) se encuentra que existen puntos que se encuentran muy alejados de la recta, por lo que se considera que estos valores pueden afectar la estimación de los estimadores (\(\beta\)).

Test de evaluación de supuestos

Para confirmar las afirmaciones anteriores, se realizan los test de evaluación.

# Homogeneidad de la Varianza - Breusch-Pagan Test
bptest(rlm_sol2)
## 
##  studentized Breusch-Pagan test
## 
## data:  rlm_sol2
## BP = 47.326, df = 7, p-value = 4.822e-08

El resultado indica que los residuales no se distribuyen con la misma varianza, dado el \(p-value = 4.822e-08\) es inferior a a 0.05.

# Normalidad de los residuos - Shapiro Wilk
#Se utiliza este test dado que la muestra a evaluar es pequeña y esta prueba es más robusta
residuales2 = resid(rlm_sol2) # se realiza sobre los residuales del modelo

shapiro.test(residuales2)
## 
##  Shapiro-Wilk normality test
## 
## data:  residuales2
## W = 0.82873, p-value = 9.718e-16

El resultado indica que se rechaza la hipótesis nula de normalidad de los errores, toda vez que el \(p-value = 9.718e-16\), que es menor al criterio de aceptación de 0.05.

# Independencia de las observaciones - Test de Durbin-Watson
dwtest(rlm_sol2)
## 
##  Durbin-Watson test
## 
## data:  rlm_sol2
## DW = 1.9833, p-value = 0.4463
## alternative hypothesis: true autocorrelation is greater than 0

El resultado del estadístico de este test indica que no hay autocorrelación entre los residuos del modelo (cercano a 2), así mismo porque el \(p-value = 0.4463\), y al ser > 0.05 no se rechaza la hipótesis nula, sugiriendo que los residuos son independientes.

Predicción del modelo en el set de prueba

Para evaluar el desempeño del modelo se realiza la predicción sobre el set de prueba

# Predicciones en test y métricas
prediccion2 <- predict(rlm_sol2, newdata = data_test2)

RMSE_2 <- sqrt(mean((data_test2$preciom - prediccion2)^2))
MAE_2  <- mean(abs(data_test2$preciom - prediccion2))
R2_oos_2 <- cor(data_test2$preciom, prediccion2)^2

c(RMSE = RMSE_2, MAE = MAE_2, R2_test = R2_oos_2)
##        RMSE         MAE     R2_test 
## 423.7282749 329.8758791   0.5763333

El \(R^2\) indica que el 56.6% de la variación de los precios se explica por las variables incluidas en el conjunto de prueba, al ser inferior al estimado en el modelo realizado con el test de entrenamiento, se puede concluir que el modelo pierde en gran medida la bondad de ajuste con los datos de prueba y por tanto el modelo podría estar sobreajustado.

Los indicadores RMSE y MAE, son muy elevados, indican que las predicciones en el precio de vivienda se desviarían entre 329 y 423 millones del valor real. Considerando los rangos de precio del dataset, es una desviación muy alta del valor real.

Posibles ajustes

Teniendo en cuenta los diferentes resultados de los diferentes estadísticos para evaluar el ajuste del modelo y el cumplimiento de los supuestos se pueden establecer las siguientes recomendaciones.

  1. Dado que existe una potencial correlación entre las variables de número de baños y área construida (Correlación = 0.65), es pertinente evaluar modelos incluyendo solo una de las dos variables para analizar el ajusto del modelo, poruqe dada su correlación puede estar ocasionando problemas en el ajuste y de colinealidad.

  2. Revisar los puntos atípicos influyentes de las predictoras y validar si corresponden a errores de la base de datos y si no es posible imputarlos con algún método, eliminar estas observaciones porque estos afectan la estimación de los estimadores \(\beta\) del modelo. Dado que en los boxplot se observa que los datos atípicos por estrato son numerosos, es viable realizar una revisón general e imputar como criterio alguna medida de tendencia central.

  3. En este modelo se presentaron muchas variable que no se consideraron significativas para explicar el precio, por lo que se podría realizar pruebas excluyendo estas variables para evaluar cómo cambian el \(R^2\) y los otros indicadores evaluados en el conjunto de prueba.

  4. Transformar la variable de precio de la vivienda en logaritmo, dado que convierte la relación entre esta variable y las predictoras en una relación más lineal que puede mejorar el ajuste del modelo, así como su interpretación, porque el incremento en las unidades de las predictoras se evaluará como un porcentaje de aumento en el precio.

Predicción del precio de vivienda: Solicitud 2

#Crear un dataframe con las características de la solicitud 1 - Estrato 4

viv_sol21 <- data.frame(
  areaconst   = 300,
  estrato     = 5,  
  habitaciones= 5,  
  parqueaderos= 3,  
  banios      = 3
)
viv_sol21$estrato = as.factor(viv_sol21$estrato)

# Realizar la predicción 
pred_v21 <- predict(rlm_sol2, newdata = viv_sol21,
                       interval = "prediction", level = 0.95)

#Crear un dataframe con las características de la solicitud 1 - Estrato 5

viv_sol22 <- data.frame(
  areaconst   = 300,
  estrato     = 6,  
  habitaciones= 5,  
  parqueaderos= 3,  
  banios      = 3
)
viv_sol22$estrato = as.factor(viv_sol22$estrato)


# Realizar la predicción 
pred_v22 <- predict(rlm_sol2, newdata = viv_sol22,
                       interval = "prediction", level = 0.95)

pred_v21
##        fit     lwr      upr
## 1 832.2486 600.769 1063.728
pred_v22
##        fit     lwr      upr
## 1 924.8293 694.293 1155.366

De acuerdo con las predicciones del modelo, una vivienda con las características en mención para estrato 5 costaría 832 millones y en estrato 6, 924 millones. Teniendo en cuenta esto, es posible encontrar ofertas de vivienda para el presupuesto preaprobado (850 millones), pero en el estrato 5.

Potenciales ofertas: Solicitud 2

Teniendo en cuenta las predicciones del modelo, se filtra de la base con la cual se realizó el modelo las viviendas teniendo en cuenta como parámetro principal que el presupuesto es de 850 millones, siguiendo por el número de habitaciones. A partir de ello se entregan las siguientes ofertas que igualan o mejoran algunas de las características solicitadas por el cliente respecto a baños, parqueaderos y metros cuadrados en los estratos sugeridos.

oferta_sur <- vivienda_sur %>% filter(
  preciom <= 850,
  estrato == 5 | estrato == 6,
  areaconst >= 300,
  parqueaderos != 0
  )

# Ubicar las ofertas en un mapa
map6 <- leaflet(oferta_sur) %>%
  addTiles() %>%
    addMarkers(
    data = oferta_sur,
    popup = ~paste(
      "Precio:", preciom, "millones<br>",
      "Área:", areaconst, "m²<br>",
      "Baños:", banios, "<br>",
      "Habitaciones:", habitaciones, "<br>",
      "Parqueaderos:", parqueaderos)
  ) %>%
  addPolygons(
    lng = zona_sur$lon,
    lat = zona_sur$lat,
    fillColor = "orange",
    fillOpacity = 0.2,
    color = "darkred",
    weight = 2,
    popup = "Zona Sur de Cali"
  )

map6