Introducción

C&A (Casas y Apartamentos) es una agencia inmobiliaria fundada en la ciudad de Cali por María Quintero, quien cuenta con más de 10 años de experiencia en el sector. La agencia dispone de un equipo de profesionales altamente calificados para asesorar en la compra de viviendas, tanto nuevas como usadas, para clientes nacionales e internacionales.

El objetivo de este informe es proporcionar recomendaciones claras para la adquisición de dos inmuebles en la ciudad de Cali para la compañía internacional “X”, considerando las características y condiciones específicas de cada propiedad.

Los resultados principales se presentan en el cuerpo ejecutivo del informe, mientras que los anexos incluyen todas las estimaciones, análisis, validaciones y comparaciones de modelos que respaldan dichas recomendaciones.

Solicitudes

library(knitr)
library(kableExtra)
viviendas <- data.frame(
  Característica = c("Tipo", "Área construida (m²)", "Parqueaderos", "Baños", 
                     "Habitaciones", "Estrato", "Zona", "Crédito preaprobado"),
  Vivienda_1 = c("Casa", 200, 1, 2, 4, "4–5", "Norte", "350 millones"),
  Vivienda_2 = c("Apartamento", 300, 3, 3, 5, "5–6", "Sur", "850 millones"),
  stringsAsFactors = FALSE
)
kable(viviendas, "html", caption = "<b>Tabla1. Solicitudes de vivienda</b>") %>%
  kable_styling(full_width = FALSE, position = "center",
                bootstrap_options = c("striped", "hover", "condensed")) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50") %>% 
  row_spec(1:8, background = rep(c("#ECF0F1", "#FFFFFF"), 4)) %>%        
  column_spec(1, bold = TRUE, color = "#2C3E50")                          
Tabla1. Solicitudes de vivienda
Característica Vivienda_1 Vivienda_2
Tipo Casa Apartamento
Área construida (m²) 200 300
Parqueaderos 1 3
Baños 2 3
Habitaciones 4 5
Estrato 4–5 5–6
Zona Norte Sur
Crédito preaprobado 350 millones 850 millones
vivienda <- readRDS("vivienda_consolidada.rds")
norte_final <- readRDS("vivienda_norte_final.rds")
sur_final <- readRDS("vivienda_sur_final.rds")

viviendas_filtradas <- subset(vivienda,
                              areaconst >= 200 &
                                parqueaderos >= 1 &
                                banios >= 2 &
                                habitaciones >= 4 &
                                estrato %in% c(4,5) &
                                zona_nueva == "Norte" &
                                preciom <= 350 &
                                tipo == "Casa")
viviendas_filtradas2 <- subset(vivienda,
                               areaconst >= 300 &
                                 parqueaderos >= 3 &
                                 banios >= 3 &
                                 habitaciones >= 5 &
                                 estrato %in% c(5,6) &
                                 zona_nueva == "Sur" &
                                 preciom <= 850 &
                                 tipo == "Apartamento")
resumen <- data.frame(
  Solicitud = c("Norte_Casa", "Sur_Apartamento"),
  n_ofertas = c(nrow(viviendas_filtradas),
                  nrow(viviendas_filtradas2)),
  precio_promedio = c(mean(viviendas_filtradas$preciom),
                       mean(viviendas_filtradas2$preciom))
)
kable(resumen, "html", 
      caption = "<b>Tabla 2. Resumen de ofertas de vivienda</b>") %>%
  kable_styling(full_width = FALSE, position = "center",
                bootstrap_options = c("striped", "hover", "condensed")) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50") %>% 
  row_spec(1:2, background = rep(c("#ECF0F1", "#FFFFFF"), 1)) %>%
  column_spec(1, bold = TRUE, color = "#2C3E50")
Tabla 2. Resumen de ofertas de vivienda
Solicitud n_ofertas precio_promedio
Norte_Casa 30 329.4333
Sur_Apartamento 0 NaN

La Tabla 2 presenta un resumen del número de ofertas de vivienda y del precio promedio según los requerimientos del cliente en términos de área construida, zona, tipo de inmueble, habitaciones, baños y parqueaderos, cumpliendo con los criterios solicitados o superiores en cuanto a área, parqueaderos, habitaciones y baños.

Para la solicitud de casa en la zona Norte, con los requerimientos del cliente se evidencian 30 ofertas con un precio promedio de 329,5 millones, por debajo del crédito aprobado.

En cuanto a los requerimientos de apartamento en la zona Sur, no se encontraron ofertas que cumplan con todos los criterios del cliente. Las dos ofertas que sí cumplen los requisitos tienen un precio de 1.150 millones, muy por encima de la restricción presupuestal de 850 millones. Si no se considera el tipo de inmueble y se incluyen casas, el número de ofertas disponibles asciende a 47.

Evaluación presupuesto

Para evaluar el presupuesto, se analizan los requerimientos tanto en el modelo general (Modelo 1)Anexo 16 como en el modelo específico para cada área (Modelo 2 y 3) . Esto permite identificar de manera más precisa qué viviendas cumplen con los criterios del cliente y cómo varía la disponibilidad y el precio promedio según las características particulares de cada zona o tipo de inmueble.

options(contrasts = c("contr.treatment", "contr.poly"))
vivienda <- readRDS("vivienda_consolidada.rds")
norte_final <- readRDS("vivienda_norte_final.rds")
sur_final <- readRDS("vivienda_sur_final.rds")
vivienda$estrato <- factor(vivienda$estrato)
vivienda$tipo1   <- factor(vivienda$tipo)
vivienda$estrato <- as.factor(vivienda$estrato)
modelo_global_log <- lm(
  log(preciom) ~ log(areaconst) + estrato + banios + 
    habitaciones + parqueaderos + zona_nueva + tipo + 
    estrato:tipo + zona_nueva:tipo, 
  data = vivienda
)
viviendas_prueba <- data.frame(
  id = paste0("Vivienda_", 1:4),
  tipo = c("Casa", "Casa", "Apartamento", "Apartamento"),
  areaconst = c(200, 200, 300, 300),
  parqueaderos = c(1, 1, 3, 3),
  banios = c(2, 2, 3, 3),
  habitaciones = c(4, 4, 5, 5),
  estrato = as.factor(c(4, 5, 5, 6)),
  zona_nueva = as.factor(c("Norte", "Norte", "Sur", "Sur"))
)
pred_log_pi <- predict(modelo_global_log, newdata = viviendas_prueba, interval = "prediction", level = 0.95)
pred_pi <- exp(pred_log_pi)
tabla_global <- data.frame(
  id = viviendas_prueba$id,
  tipo = viviendas_prueba$tipo,
  zona = viviendas_prueba$zona_nueva,
  estrato = viviendas_prueba$estrato,
  prediccion = round(exp(pred_log_pi[, "fit"]), 2),
  lwr = round(exp(pred_log_pi[, "lwr"]), 2),
  upr = round(exp(pred_log_pi[, "upr"]), 2)
)
kable(tabla_global, "html", caption = "<b>Tabla 3. Evaluación de solicitudes en el modelo global</b>") %>%
  kable_styling(full_width = FALSE, position = "center",
                bootstrap_options = c("striped", "hover", "condensed")) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50") %>% 
  row_spec(1:nrow(tabla_global), background = rep(c("#ECF0F1", "#FFFFFF"), length.out = nrow(tabla_global))) %>%
  column_spec(1:4, bold = TRUE, color = "#2C3E50")
Tabla 3. Evaluación de solicitudes en el modelo global
id tipo zona estrato prediccion lwr upr
Vivienda_1 Casa Norte 4 295.93 184.67 474.22
Vivienda_2 Casa Norte 5 330.50 206.27 529.54
Vivienda_3 Apartamento Sur 5 569.85 355.74 912.80
Vivienda_4 Apartamento Sur 6 789.59 492.90 1264.85

Según el modelo global, los presupuestos planteados por los clientes son realistas dentro de los rangos estimados. Sin embargo, existe una probabilidad significativa de que el monto preaprobado no sea suficiente para adquirir el inmueble deseado.

Esto se evidencia en la solicitud 2 (Vivienda 4), un apartamento en la zona Sur, estrato 6, cuyas características coinciden con los requerimientos del cliente. La predicción central del precio es de 789,59 millones, pero su límite superior del intervalo de predicción alcanza los 1.264,85 millones, lo que refleja que, como se observa en la base de datos, los inmuebles con estas características pueden costar hasta 1.150 millones, superando el crédito aprobado.

norte_final <- readRDS("vivienda_norte_final.rds")
norte_final$estrato <- factor(norte_final$estrato)
norte_final$tipo   <- factor(norte_final$tipo)
modelo_norte_r <- lm(log(preciom) ~ poly(areaconst,2) + estrato + 
                       + banios + parqueaderos+habitaciones,
                     data = norte_final)
viviendas_prueba_n <- data.frame(
  id = paste0("Vivienda_", 1:2),
  areaconst = c(200, 200),
  parqueaderos = c(1, 1),
  banios = c(2, 2),
  habitaciones = c(4, 4),
  estrato = as.factor(c(4, 5)) 
)
pred_log_pi_norte <- predict(modelo_norte_r, newdata = viviendas_prueba_n, interval = "prediction", level = 0.95)
pred_pi_norte <- exp(pred_log_pi_norte)
tabla_norte <- data.frame(
  id = viviendas_prueba_n$id,
  estrato = viviendas_prueba_n$estrato,
  prediccion = round(exp(pred_log_pi_norte[, "fit"]), 2),
  lwr = round(exp(pred_log_pi_norte[, "lwr"]), 2),
  upr = round(exp(pred_log_pi_norte[, "upr"]), 2)
)

kable(tabla_norte, "html", caption = "<b>Tabla 4. Estimación de precios - Zona Norte</b>") %>%
  kable_styling(full_width = FALSE, position = "center",
                bootstrap_options = c("striped", "hover", "condensed")) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50") %>%
  row_spec(1:nrow(tabla_norte), background = rep(c("#ECF0F1", "#FFFFFF"), length.out = nrow(tabla_norte))) %>%
  column_spec(1:2, bold = TRUE, color = "#2C3E50")
Tabla 4. Estimación de precios - Zona Norte
id estrato prediccion lwr upr
Vivienda_1 4 327.73 202.45 530.53
Vivienda_2 5 361.46 223.55 584.46

La Tabla 4 presenta la estimación de la solicitud para un inmueble tipo casa en la zona Norte, utilizando el Anexo 8, que se ajusta únicamente con datos de casas en esta zona. Al emplear intervalos de predicción al 95%, se observa que los rangos estimados se ajustan, en general, a la restricción presupuestaria del cliente. No obstante, el presupuesto podría resultar ligeramente insuficiente para un inmueble de estrato 5, donde la predicción puntual es de 361,46 millones y el límite superior alcanza los 584 millones.

sur_final <- readRDS("vivienda_sur_final.rds")
options(contrasts = c("contr.treatment", "contr.poly"))
sur_final$estrato <- factor(sur_final$estrato)
sur_final$tipo   <- factor(sur_final$tipo)
modelo_sur2 <- lm(log(preciom) ~  log(areaconst) + estrato + 
                    banios +  habitaciones + parqueaderos, 
                  data = sur_final)
viviendas_prueba_s <- data.frame(
  
  id = paste0("Vivienda_", 1:2),
  
  areaconst = c(300, 300),
  
  parqueaderos = c(3, 3),
  
  banios = c(3, 3),
  
  habitaciones = c(5, 5),
  
  estrato = as.factor(c(5, 6)) 
  
)

pred_log_pi_sur <- predict(modelo_sur2, newdata = viviendas_prueba_s, interval = "prediction", level = 0.95)
pred_pi_sur <- exp(pred_log_pi_sur)
tabla_sur <- data.frame(
  id = viviendas_prueba_s$id,
  estrato = viviendas_prueba_s$estrato,
  prediccion = round(exp(pred_log_pi_sur[, "fit"]), 2),
  lwr = round(exp(pred_log_pi_sur[, "lwr"]), 2),
  upr = round(exp(pred_log_pi_sur[, "upr"]), 2)
)
kable(tabla_sur, "html", caption = "<b>Tabla 5. Estimación de precios  – Zona Sur</b>") %>%
  kable_styling(full_width = FALSE, position = "center",
                bootstrap_options = c("striped", "hover", "condensed")) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50") %>%
  row_spec(1:nrow(tabla_sur), background = rep(c("#ECF0F1", "#FFFFFF"), length.out = nrow(tabla_sur))) %>%
  column_spec(1:2, bold = TRUE, color = "#2C3E50")
Tabla 5. Estimación de precios – Zona Sur
id estrato prediccion lwr upr
Vivienda_1 5 664.35 452.88 974.56
Vivienda_2 6 862.48 588.00 1265.10

Finalmente, la Tabla 5 presenta la estimación de la solicitud para un inmueble tipo apartamento en la zona Norte, utilizando el Modelo 2 Anexo 7, que se ajusta únicamente con datos de apartamentos en esta zona. Al emplear intervalos de predicción al 95%, se observa que los rangos estimados se ajustan, en general, a la restricción presupuestaria del cliente. No obstante, el presupuesto podría quedarse corto, y, como se evidenció, no existen ofertas de inmuebles con las características solicitadas dentro del crédito planteado.

Recomendaciones.

Solicitud 1. Inmueble Zona norte - casa.

Para responder la solicitud 1, correspondiente a una casa en la zona norte con ciertas características, se filtraron los inmuebles que cumplen con las condiciones solicitadas. Posteriormente, con base en el Modelo 3, se estimo el precio predicho de cada inmueble y se comparó con su precio de mercado.

La estrategia para encontrar las mejores alternativas fue la siguiente: se estableció un tope presupuestario de 400 millones, suponiendo cierta capacidad de negociación por parte del cliente cuyo presupuesto inicial es de 350 millones. A partir de este criterio se optó por dos tipos de alternativas.

En primer lugar, se seleccionaron tres ofertas extremas cuyo precio de mercado se encuentra muy por debajo del precio predicho por el modelo, lo que indica posibles inmuebles subvalorados y, por tanto, oportunidades potenciales para el cliente, al permitir adquirir una propiedad por un valor inferior al estimado por el modelo.

Adicionalmente, se incluyeron dos opciones más conservadoras, caracterizadas por residuos negativos más pequeños: una con un precio cercano al límite superior del presupuesto (aproximadamente 370 millones, considerando una posible negociación) y otra claramente por debajo (320 millones). Esta selección responde a que el modelo no captura algunas variables no observadas, como la antigüedad del inmueble o la calidad de los acabados. En este sentido, las alternativas conservadoras ofrecen mayor seguridad, ya que es posible que los precios considerablemente bajos de las tres primeras propiedades reflejen precisamente la ausencia de estos atributos adicionales.

viviendas_en <- subset(vivienda,
                       areaconst >= 200 &
                         parqueaderos >= 1 &
                         banios >= 2 &
                         habitaciones >= 4 &
                         estrato %in% c(4,5) &
                         zona_nueva == "Norte" &
                         tipo == "Casa")
############################################
viviendas_en <- subset(vivienda,
                       areaconst >= 200 &
                         parqueaderos >= 1 &
                         banios >= 2 &
                         habitaciones >= 4 &
                         estrato %in% c(4,5) &
                         zona_nueva == "Norte" &
                         tipo == "Casa")
############################################

pred_log <- predict(modelo_norte_r, newdata = viviendas_en)
pred_precio <- exp(pred_log)
residuos_real <- viviendas_en$preciom - pred_precio
residuo_pct <- 100 * residuos_real / pred_precio

viviendas_en$residuo <- round(residuos_real, 2)
viviendas_en$residuo_pct <- round(residuo_pct, 2)
viviendas_en$precio_predicho <- round(pred_precio, 2)
viviendas_en <- viviendas_en[, c("id", "estrato", "preciom", "precio_predicho", "residuo", "residuo_pct",
                                 "areaconst", "parqueaderos", "banios", "habitaciones", "zona_nueva", "tipo", "barrio", 
                                 "longitud", "latitud")]
viviendas_400 <- subset(viviendas_en, preciom <= 400)
subset_gangas <- viviendas_400 %>%
  dplyr::filter(id %in% c(1020, 1009, 3101, 1835, 1144))
tabla_gangas <- subset_gangas[, c("id", "estrato", "preciom", "precio_predicho","barrio")]
kable(tabla_gangas,
      caption = "<b>Tabla 6 - Inmuebles Zona norte - casas</b>",
      col.names = c("ID", "Estrato", "P de mercado (millones)", "Precio Predicho", "Barrio")) %>%
  kable_styling(full_width = FALSE, position = "center",
                bootstrap_options = c("striped","hover","condensed")) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50")
Tabla 6 - Inmuebles Zona norte - casas
ID Estrato P de mercado (millones) Precio Predicho Barrio
1009 5 250 417.29 el bosque
1020 4 230 390.02 la merced
1144 4 320 365.15 la merced
3101 5 340 548.13 san vicente
1835 4 370 393.50 vipasa

La tabla 6 presenta 5 ofertas de inmuebles a la solicitud 1 (Zona norte - Casa):

Inmueble ID 1009: El inmueble presenta un precio de 250 millones y un precio estimado mediante el modelo de 417.29 millones, lo que representa una diferencia de -167.29 millones en términos absolutos y de -40.09% en términos relativos. Cuenta con un área de 243 metros cuadrados, superior a los 200 metros solicitados por el cliente, 1 parqueadero, 4 baños y 5 habitaciones. Además, se localiza en el barrio El Bosque de la ciudad de Cali, correspondiente a estrato 5.

Inmueble ID 1020: El inmueble presenta un precio de 230 millones y un precio estimado mediante el modelo de 390.02 millones, lo que representa una diferencia de -160.02 millones en términos absolutos y -41.03% en términos relativos. Cuenta con un área de 250 metros cuadrados, superior a los 200 metros solicitados por el cliente, 2 parqueaderos, 3 baños y 5 habitaciones. Además, se localiza en el barrio La Merced de la ciudad de Cali, correspondiente a estrato 4.

Inmueble ID 1144: (Conservadora) El inmueble presenta un precio de 320 millones y un precio estimado mediante el modelo de 365.15 millones, lo que representa una diferencia de -45.15 millones en términos absolutos y -12.36% en términos relativos. Cuenta con un área de 200 metros cuadrados, cumpliendo con el mínimo solicitado por el cliente, 2 parqueaderos, 4 baños y 4 habitaciones. Además, se localiza en el barrio La Merced de la ciudad de Cali, correspondiente a estrato 4.

Inmueble ID 3101: El inmueble presenta un precio de 340 millones y un precio estimado mediante el modelo de 548.13 millones, lo que representa una diferencia de -208.13 millones en términos absolutos y -37.97% en términos relativos. Cuenta con un área de 355 metros cuadrados, considerablemente superior a los 200 metros solicitados por el cliente, 2 parqueaderos, 5 baños y 8 habitaciones. Además, se localiza en el barrio San Vicente de la ciudad de Cali, correspondiente a estrato 5.

Inmueble ID 1835: (Conservadora) El inmueble presenta un precio de 370 millones y un precio estimado mediante el modelo de 393.50 millones, lo que representa una diferencia de -23.50 millones en términos absolutos y -5.97% en términos relativos. Cuenta con un área de 264 metros cuadrados, superior a los 200 metros solicitados por el cliente, 1 parqueadero, 4 baños y 5 habitaciones. Además, se localiza en el barrio Vipasa de la ciudad de Cali, correspondiente a estrato 4. Es necesario negociar el precio.

subset_gangas$popup <- paste0(
  "<b>ID:</b> ", subset_gangas$id,
  "<br><b>Barrio:</b> ", subset_gangas$barrio,
  "<br><b>Estrato:</b> ", subset_gangas$estrato,
  "<br><b>Precio mercado:</b> ", subset_gangas$preciom, " millones",
  "<br><b>Precio predicho:</b> ", round(subset_gangas$precio_predicho,2), " millones",
  "<br><b>Área:</b> ", subset_gangas$areaconst, " m²",
  "<br><b>Habitaciones:</b> ", subset_gangas$habitaciones,
  "<br><b>Baños:</b> ", subset_gangas$banios
)
library(leaflet)

leaflet(subset_gangas) %>%
  addProviderTiles(providers$CartoDB.Positron) %>% 
  setView(lng = -76.53, lat = 3.45, zoom = 12) %>%   
  addCircleMarkers(
    lng = ~longitud,
    lat = ~latitud,
    radius = 7,
    color = "red",
    fillOpacity = 0.8,
    popup = ~popup
  )

Solicitud 2. Inmueble Zona sur - Apartamento.

Para responder la solicitud 2, correspondiente a un apartamento en la zona Sur con ciertas características, se filtraron los inmuebles que cumplen con las condiciones solicitadas. Posteriormente, con base en el Modelo 2, se estimó el precio predicho de cada inmueble y se comparó con su precio de mercado. Dada la escasez de inmuebles con dichas características, se redujo en una unidad el requerimiento de habitaciones, parqueaderos y baños, con el fin de ampliar el conjunto de posibles alternativas.

Al evaluar y seleccionar los inmuebles más prometedores para el cliente, se eligieron tres opciones: dos cuyo precio de mercado se encuentra muy por debajo del precio predicho por el modelo, lo que sugiere posibles oportunidades de compra, y un inmueble cuyo valor de mercado se ubica alrededor de los 950 millones, superando en aproximadamente 100 millones el monto del credito aprobado. No obstante, este inmueble podría considerarse en caso de que el cliente logre negociar un aumento en el desembolso del crédito o una reducción en el precio con el vendedor o ambos al tiempo.

Dada la escasez de inmuebles con estas características bajo la restricción presupuestaria del cliente, las alternativas identificadas corresponden a ofertas relativamente agresivas, por lo que se recomienda realizar un peritaje de valuación antes de concretar cualquier decisión de compra.

viviendas_se <-          subset(vivienda,
                         areaconst >= 280 &
                         parqueaderos >= 2 &
                         banios >= 2 &
                         habitaciones >= 4 &
                         estrato %in% c(5,6) &
                         zona_nueva == "Sur" &
                         tipo == "Apartamento")
pred_log <- predict(modelo_sur2, newdata = viviendas_se)
viviendas_se$precio_predicho <- exp(pred_log)
viviendas_se$residuo <- viviendas_se$preciom - viviendas_se$precio_predicho
viviendas_se$residuo_pct <- 100 * viviendas_se$residuo / viviendas_se$precio_predicho
viviendas_se_925 <- subset(viviendas_se, preciom <= 925)
subset_sur_gangas <- viviendas_se %>%
  dplyr::filter(id %in% c(4952, 4394, 6275))
tabla_sur_gangas <- subset_sur_gangas[, c("id", "estrato", "preciom", "precio_predicho", "barrio")]

kable(tabla_sur_gangas,
      caption = "<b>Tabla 7 - Inmuebles Zona Sur - apartamentos</b>",
      col.names = c("ID", "Estrato", "P de mercado (millones)", "Precio Predicho", "Barrio")) %>%
  kable_styling(full_width = FALSE, position = "center",
                bootstrap_options = c("striped","hover","condensed")) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50")
Tabla 7 - Inmuebles Zona Sur - apartamentos
ID Estrato P de mercado (millones) Precio Predicho Barrio
6275 6 950 1321.6079 ciudad jardín
4394 5 690 886.5940 el ingenio
4952 5 650 971.6003 el ingenio

Dado que la oferta de apartamentos en la zona sur resultó limitada, se amplió el análisis al mercado de apartamentos en la zona occidente Anexo 9 con el fin de identificar alternativas adicionales dentro del rango presupuestario del cliente.

viviendas_occidente <- subset(vivienda,
                              zona_nueva == "Occidente" &
                                tipo == "Apartamento")
viviendas_occidente$estrato <- as.factor(viviendas_occidente$estrato)
modelo_occ <- lm(log(preciom) ~ poly(areaconst,3) + habitaciones + banios + parqueaderos + estrato , data = viviendas_occidente)
viviendas_oc <-          subset(vivienda,
                                areaconst >= 280 &
                                  parqueaderos >= 3 &
                                  banios >= 3 &
                                  habitaciones >= 5 &
                                  estrato %in% c(5,6) &
                                  zona_nueva == "Occidente" &
                                  tipo == "Apartamento")

pred_log <- predict(modelo_occ, newdata = viviendas_oc)
viviendas_oc$precio_predicho <- exp(pred_log)
viviendas_oc$residuo <- viviendas_oc$preciom - viviendas_oc$precio_predicho
viviendas_oc$residuo_pct <- 100 * viviendas_oc$residuo / viviendas_oc$precio_predicho
viviendas_oc_925 <- subset(viviendas_oc, preciom < 925)
tabla_gangas <- viviendas_oc_925[, c("id", "estrato", "preciom", "precio_predicho", "barrio")]
tabla_gangas$precio_predicho <- round(tabla_gangas$precio_predicho, 2)
tabla_gangas <- tabla_gangas[order(tabla_gangas$preciom), ]
kable(tabla_gangas,
      caption = "<b>Tabla 8 - Apartamentos grandes en el occidente</b>",
      col.names = c("ID", "Estrato", "P de mercado (millones)", "Precio Predicho", "Barrio")) %>%
  kable_styling(full_width = FALSE,
                position = "center",
                bootstrap_options = c("striped","hover","condensed")) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50")
Tabla 8 - Apartamentos grandes en el occidente
ID Estrato P de mercado (millones) Precio Predicho Barrio
6600 7512 5 670 751.49 seminario
3211 7182 5 730 1721.17 guadalupe

Inmueble ID 3211(occidente muy arriesgada): El apartamento presenta un precio de 730 millones y un precio estimado mediante el modelo de 1721.17 millones, lo que representa una diferencia de -991.17 millones en términos absolutos y -57.59% en términos relativos. Cuenta con un área de 573 metros cuadrados, superior a los 300 metros solicitados por el cliente, 3 parqueaderos, 8 baños y 5 habitaciones. Además, se localiza en el barrio Guadalupe de la ciudad de Cali, correspondiente a estrato 5. Este apartamento se localiza en la frontera de la zona occidente y sur por lo que cumple las demandas del cliente, no obstante dado su bajo precio comparado con predicho, es una opción demasiado arriesgada.

Inmueble ID 6600 (Occidente): El inmueble presenta un precio de 670 millones y un precio estimado mediante el modelo de 751.49 millones, lo que representa una diferencia de -81.49 millones en términos absolutos y -10.84% en términos relativos. Cuenta con un área de 300 metros cuadrados igual a los solicitados por el cliente, 3 parqueaderos, 5 baños y 6 habitaciones. Además, se localiza en el barrio Seminario de la ciudad de Cali, correspondiente a estrato 5.

Inmueble ID 6275: El inmueble presenta un precio de 950 millones y un precio estimado mediante el modelo de 1321.61 millones, lo que representa una diferencia de -371.61 millones en términos absolutos y -28.12% en términos relativos. Cuenta con un área de 330 metros cuadrados, superior a los 300 metros solicitados por el cliente, 4 parqueaderos, 6 baños y 4 habitaciones (Una menos que la solicitada por el cliente). Además, se localiza en el barrio Ciudad Jardín de la ciudad de Cali, correspondiente a estrato 6. Dado su valor, es necesario revisar si hay capacidad de negociación o aumento del cupo aprobado por el banco.

Inmueble ID 4394 : El inmueble presenta un precio de 690 millones y un precio estimado mediante el modelo de 886.59 millones, lo que representa una diferencia de -196.59 millones en términos absolutos y -22.17% en términos relativos. Cuenta con un área de 486 metros cuadrados, superior a los 300 metros solicitados por el cliente, 2 parqueaderos(-1 que lo solicitado por el cliente), 4 baños y 4 habitaciones (-1 solicitada). Además, se localiza en el barrio El Ingenio de la ciudad de Cali, correspondiente a estrato 5.

Inmueble ID 4952: El inmueble presenta un precio de 650 millones y un precio estimado mediante el modelo de 971.60 millones, lo que representa una diferencia de -321.60 millones en términos absolutos y -33.10% en términos relativos. Cuenta con un área de 600 metros cuadrados, el doble de la solicitud del cliente, 2 parqueaderos (-1 menos que lo solicitado), 4 baños y 5 habitaciones. Además, se localiza en el barrio El Ingenio de la ciudad de Cali, correspondiente a estrato 5.

viviendas_oc_925 <- rbind(viviendas_oc_925, subset_sur_gangas)

viviendas_oc_925$popup <- paste0(
  "<b>ID:</b> ", viviendas_oc_925$id,
  "<br><b>Barrio:</b> ", viviendas_oc_925$barrio,
  "<br><b>Estrato:</b> ", viviendas_oc_925$estrato,
  "<br><b>Precio mercado:</b> ", viviendas_oc_925$preciom, " millones",
  "<br><b>Precio predicho:</b> ", round(viviendas_oc_925$precio_predicho,2), " millones",
  "<br><b>Área:</b> ", viviendas_oc_925$areaconst, " m²",
  "<br><b>Habitaciones:</b> ", viviendas_oc_925$habitaciones,
  "<br><b>Baños:</b> ", viviendas_oc_925$banios
)

leaflet(viviendas_oc_925) %>%
  addProviderTiles(providers$CartoDB.Positron) %>% 
  setView(lng = -76.53, lat = 3.45, zoom = 12) %>%   
  addCircleMarkers(
    lng = ~longitud,
    lat = ~latitud,
    radius = 7,
    color = "red",
    fillOpacity = 0.8,
    popup = ~popup
  )

Conclusiones

En el apartado de recomendaciones se presentan al cliente las respuestas a las dos solicitudes.

Para la solicitud correspondiente a casas en la zona Norte, se proponen un total de cinco inmuebles que satisfacen las necesidades del cliente. De estos, tres representan una gran oportunidad de negocio según el modelo, dado que su precio se encuentra subvaluado. Además, se incluyen dos inmuebles con un perfil más conservador, que no implican riesgo para el negocio y cuyo precio se encuentra cercano al valor estimado por el modelo.

En cuanto a la solicitud número 2, correspondiente a apartamentos en la zona Sur, la decisión fue más compleja, por lo que se tuvo que relajar parcialmente las restricciones del cliente. Como resultado, tres inmuebles en la zona Sur presentan algunas características por debajo de las solicitadas; de ellos, dos se encuentran por debajo de la restricción presupuestaria del cliente y uno por encima.

Por tal motivo, se presentan al cliente dos ofertas adicionales en la zona Occidente, cercanas al polígono de la zona Sur, que se ajustan a sus preferencias y satisfacen las características solicitadas. Estos propiedades representan, segun los diferentes modelos, una excelente oportunidad de negocio, sin embargo, debido a su bajo precio, requieren medidas de validación adicionales. Se recomienda el acompañamiento de peritos de evaluación y la revisión de los antecedentes judiciales de todas las ofertas.

Adicionalmente, si el cliente desea una opción más conservadora, podría optar por buscar casas en dicho sector con las características que solicita, ya que existe una mayor oferta y estas se ajustan mejor a la restricción presupuestaria del cliente.

Limitaciones

Dado que el modelo solo incluye variables como preciom, área construida, número de baños, habitaciones, parqueaderos, y variables categóricas como zona, barrio y estrato, no puede capturar de manera adecuada otros factores importantes que afectan el precio, como los acabados y la antigüedad de los inmuebles. Además, la ausencia de un componente espacial puede hacer que se ignore alguna variable geográfica que influya en los precios.

Adicionalmente, la variable zona no estaba correctamente clasificada, por lo que se procedió a reclasificar las viviendas siguiendo polígonos de zonas propuestos por el distrito Anexo 10. No obstante, esta distinción entre zonas (Sur, Occidente, etc.) es subjetiva y se desdibuja especialmente en las fronteras; para el cliente, la zona Sur podría abarcar ciertos sectores que no coinciden exactamente con la clasificación utilizada.

Anexo

Anexo 1: Datos

La base de inmuebles habitacionales de la agencia C&A contiene 8,319 observaciones de propiedades en la ciudad de Cali. Para el presente análisis, y dado que se busca ofrecer recomendaciones específicas con características y ubicaciones particulares, la base se filtró por zona y tipo de vivienda: para la solicitud 1 se seleccionaron únicamente casas ubicadas en la zona Norte, mientras que para la solicitud 2 se consideraron apartamentos ubicados en la zona Sur. Este filtrado permite que los modelos de predicción que se vayan a utilizar se ajusten a las condiciones de cada solicitud.

Despues del filtrado por tipo y zona, y tras establecer un poligono en el area basandose en una propuesta de distribución geográfica del distrito de la Alcaldía de Cali, se cuenta con 580 observaciones de casas en la zona norte. Para la zona sur y los apartamentos, se parte de la misma propuesta del distrito de Cali, construyendo un polígono aproximado en la zona sur, donde se contabilizan 1.999 inmuebles. (Esto luego construir una nueva variable zona usando las cordenadas longitud y latitud reportadas y asignando cada inmueble a su ubicación dentro de un poligono de zona)

Ilustración A1. Mapa de los inmuebles en la norte y casas.

library(paqueteMODELOS)
library(dplyr)
library(mice)
library(tidyr)
library(kableExtra)
library(ggplot2)
library(scales)
library(robustbase)
library(sf)
library(leaflet)
library(isotree)

norte_casa <- readRDS("vivienda_norte_casas.rds")
poligono_norte_clean <- st_read("norte.kml", quiet = TRUE) %>% 
  st_transform(4326) %>% 
  st_zm()

norte_casa_sf <- st_as_sf(
  norte_casa,
  coords = c("longitud", "latitud"),
  crs = 4326,
  remove = FALSE
) %>% 
  st_zm()
norte_final <- norte_casa_sf[
  st_intersects(norte_casa_sf, poligono_norte_clean, sparse = FALSE)[, 1], 
]

map_norte <- leaflet(data = norte_final) %>%
  addTiles() %>%
  addPolygons(
    data = poligono_norte_clean,
    color = "black",
    weight = 2,
    fillOpacity = 0.1,
    group = "Límite Norte"
  ) %>%
  addCircleMarkers(
    radius = 4,
    color = "blue",
    stroke = FALSE,
    fillOpacity = 0.7,
    popup = ~paste0(
      "<b>Barrio:</b> ", barrio, "<br>",
      "<b>Precio:</b> ", preciom, "<br>",
      "<b>Área:</b> ", areaconst
    ),
    group = "Casas"
  ) %>%
  addLegend(position = "bottomright", 
            colors = "blue", 
            labels = "Casas Norte (Validadas)")
map_norte

La Ilustración A1 muestra los inmuebles ubicados en la zona norte y de tipo casa. El polígono fue construido en Google Maps tomando como referencia los mapas distritales de la Alcaldía de Cali. Dado que se trata de una reconstrucción basada en cartografía previa de la Alcaldía y en Google Maps, su delimitación es aproximada y puede presentar imprecisiones sobre todo en los limites al norte donde hay expansiones de la ciudad.

Ilustración A2. Mapa de los inmuebles en la zona sur y de tipo apartamentos.

datos_sur <- readRDS("vivienda_sur_apartamentos.rds")
poligono_sur_clean <- st_read("sur.kml", quiet = TRUE) %>% 
  st_transform(4326) %>% 
  st_zm() 
sur_apartamento_sf <- st_as_sf(datos_sur, 
                               coords = c("longitud", "latitud"), 
                               crs = 4326, 
                               remove = FALSE) %>% 
  st_zm()
indices_sur_dentro <- st_intersects(sur_apartamento_sf, poligono_sur_clean, sparse = FALSE)
sur_final <- sur_apartamento_sf[indices_sur_dentro[, 1], ]

map_sur <- leaflet(data = sur_final) %>%
  addTiles() %>%  
  addPolygons(data = poligono_sur_clean, 
              color = "red",       
              weight = 2, 
              fillOpacity = 0.1,
              group = "Polígono Sur") %>%
  addCircleMarkers(
    radius = 4,
    color = "green", 
    stroke = FALSE,
    fillOpacity = 0.7,
    popup = ~paste0("<b>Barrio:</b> ", barrio, "<br>",
                    "<b>Precio:</b> ", preciom, "<br>",
                    "<b>Estrato:</b> ", estrato),
    group = "Apartamentos"
  ) %>%
  addLegend(position = "bottomright", 
            colors = "green", 
            labels = "Apartamentos Sur (Limpios)")
map_sur

La Ilustración A2 muestra los inmuebles ubicados en la zona sur y de tipo apartamento. El polígono fue construido en Google Maps tomando como referencia los mapas distritales de la Alcaldía de Cali. Dado que se trata de una reconstrucción basada en cartografía previa de la Alcaldía y en Google Maps, su delimitación es aproximada y puede presentar imprecisiones sobre todo en los limites al sur donde hay expansiones de la ciudad.

Anexo 2. Exploración de datos

library(here)
##################################################
vivienda1 <- readRDS("vivienda_modelo_final.rds")
norte_final1 <- readRDS("vivienda_norte_casas.rds")     
sur_final1 <- readRDS("vivienda_sur_apartamentos.rds")

##################################################
resumen_df <- function(df, nombre) {
  df %>%
    summarise(
      conjunto = nombre,
      n_inmuebles = n(),
      precio_promedio = mean(preciom, na.rm = TRUE),
      area_promedio = mean(areaconst, na.rm = TRUE),
      banios_promedio = mean(banios, na.rm = TRUE),
      habitaciones_promedio = mean(habitaciones, na.rm = TRUE)
    )
}
tabla_resumen <- bind_rows(
  resumen_df(vivienda1, "Total"),
  resumen_df(norte_final1, "Zona Norte - Casa"),
  resumen_df(sur_final1, "Zona Sur - Apartamento")
)
tabla_resumen %>%
  kable(
    "html",
    caption = "<b>Tabla A1. Resumen general de inmuebles</b>",
    col.names = c(
      "Conjunto",
      "# Inmuebles",
      "Precio Promedio (Millones)",
      "Área Promedio",
      "Baños Promedio",
      "Habitaciones Promedio"
    ),
    digits = 1
  ) %>%
  kable_styling(
    full_width = FALSE,
    position = "center",
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50") %>%
  row_spec(
    1:nrow(tabla_resumen),
    background = rep(c("#ECF0F1", "#FFFFFF"), length.out = nrow(tabla_resumen))
  ) %>%
  column_spec(1, bold = TRUE, color = "#2C3E50")
Tabla A1. Resumen general de inmuebles
Conjunto # Inmuebles Precio Promedio (Millones) Área Promedio Baños Promedio Habitaciones Promedio
Total 8319 433.9 174.9 3.1 3.6
Zona Norte - Casa 580 433.1 260.2 3.5 4.6
Zona Sur - Apartamento 1999 318.3 99.7 2.6 3.0

La tabla A1 presenta un resumen general de la población de inmuebles registrada, mostrando el número total de inmuebles (desglosado total o por zona y tipo según las solicitudes), el precio promedio de los inmuebles en millones, el área promedio en metros cuadrados, así como el promedio de baños, parqueaderos y habitaciones.

vivienda_long <- vivienda1 %>%
  select(areaconst, banios, habitaciones, parqueaderos, estrato, preciom) %>%
  pivot_longer(cols = everything(),
               names_to = "variable",
               values_to = "valor") %>%
  filter(!is.na(valor))
vivienda_long <- vivienda_long %>%
  mutate(tipo_var = ifelse(variable %in% c("banios", "habitaciones", "parqueaderos", "estrato"),
                           "discreta", "continua"))
ggplot(vivienda_long, aes(x = valor)) +
  geom_histogram(data = subset(vivienda_long, tipo_var == "continua"),
                 bins = 30, fill = "gray", color = "black") +
  geom_histogram(data = subset(vivienda_long, tipo_var == "discreta"),
                 binwidth = 1, fill = "gray", color = "black") +
  facet_wrap(~variable, scales = "free_x") +  
  scale_x_continuous(labels = comma) +
  labs(title = "Ilustración A3. Histogramas variables (Base completa)",
       x = "Valor",
       y = "Frecuencia") +
  theme_minimal()

La ilustración A3 presenta el histograma de algunas variables de la base de datos completa. Se observan distribuciones poco normales a través del diagnóstico gráfico de estas variables. Además, evidencia que la base de datos está limitada a los estratos 3, 4, 5 y 6.

vivienda_long <- sur_final1 %>%
  select(areaconst, banios, habitaciones, parqueaderos, estrato, preciom) %>%
  pivot_longer(cols = everything(),
               names_to = "variable",
               values_to = "valor") %>%
  filter(!is.na(valor))
vivienda_long <- vivienda_long %>%
  mutate(tipo_var = ifelse(variable %in% c("banios", "habitaciones", "parqueaderos", "estrato"),
                           "discreta", "continua"))
ggplot(vivienda_long, aes(x = valor)) +
  geom_histogram(data = subset(vivienda_long, tipo_var == "continua"),
                 bins = 30, fill = "gray", color = "black") +
  geom_histogram(data = subset(vivienda_long, tipo_var == "discreta"),
                 binwidth = 1, fill = "gray", color = "black") +
  facet_wrap(~variable, scales = "free_x") +  
  scale_x_continuous(labels = comma) +
  labs(title = "Ilustración A4. Histogramas variables (Zona Sur - Apartamentos)",
       x = "Valor",
       y = "Frecuencia") +
  theme_minimal()

vivienda_long <- norte_final1 %>%
  select(areaconst, banios, habitaciones, parqueaderos, estrato, preciom) %>%
  pivot_longer(cols = everything(),
               names_to = "variable",
               values_to = "valor") %>%
  filter(!is.na(valor))
vivienda_long <- vivienda_long %>%
  mutate(tipo_var = ifelse(variable %in% c("banios", "habitaciones", "parqueaderos", "estrato"),
                           "discreta", "continua"))
ggplot(vivienda_long, aes(x = valor)) +
  geom_histogram(data = subset(vivienda_long, tipo_var == "continua"),
                 bins = 30, fill = "gray", color = "black") +
  geom_histogram(data = subset(vivienda_long, tipo_var == "discreta"),
                 binwidth = 1, fill = "gray", color = "black") +
  facet_wrap(~variable, scales = "free_x") +  
  scale_x_continuous(labels = comma) +
  labs(title = "Ilustración A5. Histogramas variables (Zona Norte - Casas)",
       x = "Valor",
       y = "Frecuencia") +
  theme_minimal()

En las ilustraciones A4 y A5 se presentan los histogramas de las variables correspondientes a las bases filtradas: Zona Norte – Casa y Zona Sur – Apartamento. Visualmente, se evidencian algunas diferencias en las distribuciones de área construida y estrato; otras diferencias no son tan evidentes mediante un análisis gráfico. Es natural que el área construida sea menor en los apartamentos, ya que tienden a ser más pequeños que las casas.

library(plotly)


vivienda1$estrato <- as.factor(vivienda1$estrato)
vivienda1$zona_nueva <- as.factor(vivienda1$zona_nueva)
vivienda1$tipo <- as.factor(vivienda1$tipo)

fig <- plot_ly(vivienda1, 
               x = ~zona_nueva, 
               y = ~preciom, 
               color = ~zona_nueva, 
               type = "box",
               boxpoints = "suspectedoutliers") 

fig <- fig %>% layout(
  title = "<b>Análisis Exploratorio Interactivo</b>",
  yaxis = list(title = "Valor Cuantitativo"),
  xaxis = list(title = "Categoría Seleccionada"),
  updatemenus = list(

    list(
      y = 1.15, x = 0.2,
      buttons = list(
        list(method = "restyle",
             args = list("x", list(vivienda1$zona_nueva)),
             label = "Categoría: Zona"),
        list(method = "restyle",
             args = list("x", list(vivienda1$tipo)),
             label = "Categoría: Tipo"),
        list(method = "restyle",
             args = list("x", list(vivienda1$estrato)),
             label = "Categoría: Estrato")
      )
    ),

    list(
      y = 1.15, x = 0.7,
      buttons = list(
        list(method = "restyle",
             args = list("y", list(vivienda1$preciom)),
             label = "Medida: Precio"),
        list(method = "restyle",
             args = list("y", list(vivienda1$areaconst)),
             label = "Medida: Área"),
        list(method = "restyle",
             args = list("y", list(vivienda1$parqueaderos)),
             label = "Medida: Parqueaderos"),
        list(method = "restyle",
             args = list("y", list(vivienda1$banios)),
             label = "Medida: Baños"),
        list(method = "restyle",
             args = list("y", list(vivienda1$habitaciones)),
             label = "Medida: Habitaciones")
      )
    )
  )
)

fig

Anexo 3. Limpieza de datos.

En esta etapa de limpieza se emplearán dos criterios. Primero, se eliminarán las observaciones con baños o habitaciones en cero, dado que estos valores no son realistas para inmuebles habitacionales. Luego, se revisarán los inmuebles con precios por metro cuadrado muy bajos o extremadamente altos. Se considera que nn el análisis de inmuebles, medidas univariadas como boxplots o boxplots ajustados por simetría, así como técnicas más avanzadas como isolation forest, pueden identificar valores atípicos que, aunque difieren del resto de las observaciones, representan propiedades válidas, por ejemplo, inmuebles de lujo, por lo que no se aplicarán estas técnicas, en su lugar se utilizará la distancia de Cook y la estabilidad de los coeficientes posmodelado para documentar y, si es necesario, eliminar observaciones problemáticas.

Anexo 4. Datos faltantes.

Solo se encontraron valores faltantes en dos variables: piso y parqueaderos. En la base global, el porcentaje de faltantes fue 31.44% para piso y 18.87% para parqueaderos. En la base zona sur – apartamentos, los porcentajes fueron 24.03% para piso y 14.83% para parqueaderos. En la zona norte – casas, los faltantes alcanzaron 42.28% para piso y 32.46% para parqueaderos.

Piso: La variable piso fue eliminada del análisis en los tres datasets, dado que presenta un alto porcentaje de valores faltantes y su imputación resulta compleja. No se puede determinar con certeza si los NA corresponden a pisos bajos (piso 1) o si fueron datos no registrados.

Parqueadero: En la variable parqueadero se evidencia un panorama complejo, que dificulta considerablemente su imputación. En primer lugar, la variable no presenta valores cero en su distribución; suponer que todos los inmuebles habitacionales cuentan con parqueadero sería un supuesto demasiado heroico y, por lo tanto, se descarta. Adicionalmente, en términos porcentuales, el número de NA disminuye conforme aumenta el estrato: el 52.5% de las viviendas de estrato 3 no tienen información de parqueaderos, el 22.5% de las observaciones de estrato 4 presentan NA, y solo el 8% y 5.67% para los estratos 5 y 6, respectivamente. Esto nos puede llevar a sospechar que la base contiene ceros que fueron registrados como NA.

La siguiente tarea consiste, por consiguiente, en codificar la variable y revisar, mediante un examen visual a través de un boxplot, si existen diferencias en el precio entre las observaciones que contienen NA en parqueaderos y las que no

vivienda1 <- vivienda1 %>%
  filter(banios > 0, habitaciones > 0)

sur_final1 <- sur_final1 %>%
  filter(banios > 0, habitaciones > 0)

norte_final1 <- norte_final1 %>%
  filter(banios > 0, habitaciones > 0)

#####################################################

vivienda <- vivienda %>%
  mutate(precio_m2 = preciom / areaconst)

sur_final1 <- sur_final1 %>%
  mutate(precio_m2 = preciom / areaconst)

norte_final1 <- norte_final1 %>%
  mutate(precio_m2 = preciom / areaconst)

##################################################
umbral_min <- 0.5     
umbral_max <- 20     
vivienda <- vivienda %>%
  mutate(outlier_precio_m2 = precio_m2 < umbral_min | precio_m2 > umbral_max)

sur_final1 <- sur_final1 %>%
  mutate(outlier_precio_m2 = precio_m2 < umbral_min | precio_m2 > umbral_max)

norte_final1 <- norte_final1 %>%
  mutate(outlier_precio_m2 = precio_m2 < umbral_min | precio_m2 > umbral_max)


vivienda1 <- vivienda1 %>% 
  mutate(parqueadero_flag = ifelse(is.na(parqueaderos) | parqueaderos == 0, FALSE, TRUE))

# 2. Graficar usando el objeto correcto (vivienda1)
ggplot(vivienda1, aes(x = parqueadero_flag, y = preciom, fill = parqueadero_flag)) +
  geom_boxplot(outlier.shape = NA, alpha = 0.6) + 
  scale_y_continuous(labels = comma, limits = c(0, 1500)) + 
  scale_fill_manual(values = c("#D3D3D3", "#4A4A4A"), 
                    labels = c("Sin Parqueadero/NA", "Con Parqueadero")) + 
  labs(
    title = "Ilustración A6. Comparación de Precios por Disponibilidad de Parqueadero",
    subtitle = "",
    x = "",
    y = "Precio ",
    fill = ""
  ) +
  theme_minimal(base_size = 14) +
  theme(legend.position = "bottom")

La ilustración A6 muestra el precio de los inmuebles agrupado según si cuentan con parqueaderos o no. Se observa que la mediana de las observaciones sin parqueaderos es menor que la de aquellas que sí los tienen, lo que refuerza la idea de que los NA podrían corresponder a ceros mal codificados

wilcox.test(preciom ~ parqueadero_flag, data = vivienda1)
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  preciom by parqueadero_flag
## W = 2720160, p-value < 2.2e-16
## alternative hypothesis: true location shift is not equal to 0

El test de Wilcoxon (empleado tras evidenciar la no normalidad en la distribución del precio) indica que existe una diferencia estadísticamente significativa entre las medianas de los grupos, es decir, las medianas de los inmuebles con y sin parqueadero no son iguales (p-value < 2.2e-16).

options(scipen = 999)
vivienda1$parqueadero_flag <- as.factor(vivienda1$parqueadero_flag)
modelo_1 <- lm(log(preciom) ~ log(areaconst) + habitaciones + banios + 
               estrato + zona_nueva + parqueadero_flag, 
               data = vivienda1)
summary(modelo_1)
## 
## Call:
## lm(formula = log(preciom) ~ log(areaconst) + habitaciones + banios + 
##     estrato + zona_nueva + parqueadero_flag, data = vivienda1)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -1.4856 -0.1605 -0.0077  0.1566  1.5130 
## 
## Coefficients:
##                             Estimate Std. Error t value             Pr(>|t|)
## (Intercept)                 2.557754   0.028754  88.952 < 0.0000000000000002
## log(areaconst)              0.534836   0.007098  75.349 < 0.0000000000000002
## habitaciones               -0.028498   0.003007  -9.476 < 0.0000000000000002
## banios                      0.088581   0.003482  25.439 < 0.0000000000000002
## estrato4                    0.262443   0.009805  26.765 < 0.0000000000000002
## estrato5                    0.462550   0.010138  45.626 < 0.0000000000000002
## estrato6                    0.818439   0.012341  66.321 < 0.0000000000000002
## zona_nuevaCentro Historico -0.004058   0.016894  -0.240             0.810198
## zona_nuevaNorte            -0.013030   0.010312  -1.264             0.206436
## zona_nuevaOccidente         0.051122   0.010734   4.763           0.00000194
## zona_nuevaOriente          -0.063362   0.017072  -3.711             0.000207
## zona_nuevaSur              -0.023645   0.009879  -2.393             0.016717
## parqueadero_flagTRUE        0.078878   0.007943   9.930 < 0.0000000000000002
##                               
## (Intercept)                ***
## log(areaconst)             ***
## habitaciones               ***
## banios                     ***
## estrato4                   ***
## estrato5                   ***
## estrato6                   ***
## zona_nuevaCentro Historico    
## zona_nuevaNorte               
## zona_nuevaOccidente        ***
## zona_nuevaOriente          ***
## zona_nuevaSur              *  
## parqueadero_flagTRUE       ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2541 on 8230 degrees of freedom
## Multiple R-squared:  0.8567, Adjusted R-squared:  0.8564 
## F-statistic:  4099 on 12 and 8230 DF,  p-value: < 0.00000000000000022

Al estimar un modelo auxiliar para evaluar el efecto de la ausencia de parqueadero, se encuentra que los inmuebles con parqueadero son, en promedio, 6.5 % más costosos que aquellos que no disponen de este atributo y significativo , ceteris paribus. Este resultado refuerza la hipótesis de que los valores faltantes (NA) en la variable de parqueaderos podrían corresponder a ceros mal codificados. No obstante, dado que no es posible asumir que todos los inmuebles poseen parqueadero (lo que invalidaría una imputación estándar), ni tampoco que todos los NA representan ceros, la estrategia metodológicamente más prudente, en ausencia de información auxiliar, consiste en estimar el modelo utilizando únicamente las observaciones con información observada en parqueaderos.

Anexo 5. Correlaciones e interacciones entre variables.

library(paqueteMODELOS)
library(dplyr)
library(mice)
library(dplyr)
library(tidyr)
library(kableExtra)
library(ggplot2)
library(scales)
library(robustbase)
library(sf)
library(leaflet)
library("isotree")
library(car)
library(corrplot)
vivienda <- readRDS("vivienda_consolidada.rds")
norte_final <- readRDS("vivienda_norte_final.rds")
sur_final <- readRDS("vivienda_sur_final.rds")
vars_ordinales_cont <- vivienda %>%
  select(preciom, areaconst, estrato, banios, habitaciones, parqueaderos)
matriz_kendall <- cor(vars_ordinales_cont, method = "kendall", use = "complete.obs")
corrplot(matriz_kendall, 
         method = "color",          
         type = "upper",            
         addCoef.col = "black",     
         tl.col = "black",          
         tl.srt = 45,               
         col = COL2('RdBu', 10),   
         diag = FALSE,             
         mar = c(0,0,2,0),
         title = "Ilustración A7. Matriz de Asociación de Kendall: Viviendas")

La ilustración A7 presenta la matriz de asociación mediante el coeficiente de Kendall. Se observa que el área construida, el número de baños y los parqueaderos muestran una relación positiva moderada con el precio. El estrato también presenta una asociación de 0.56. Por otra parte, el número de habitaciones exhibe una asociación más débil, cercana a 0.32.

options(contrasts = c("contr.sum", "contr.poly"))
vivienda_anova <- vivienda %>%
  mutate(
    zona_nueva = as.factor(zona_nueva),
    estrato = as.factor(estrato),
    tipo = as.factor(tipo)
  ) %>%
    filter(!is.na(zona_nueva), !is.na(estrato), !is.na(tipo))

modelo_lm <- lm(log(preciom) ~ zona_nueva * estrato * tipo, data = vivienda_anova)

anova_tipo3 <- Anova(modelo_lm, type = "III")
print(anova_tipo3)
## Anova Table (Type III tests)
## 
## Response: log(preciom)
##                         Sum Sq   Df     F value                Pr(>F)    
## (Intercept)              93241    1 671047.3473 < 0.00000000000000022 ***
## zona_nueva                  12    5     17.8644 < 0.00000000000000022 ***
## estrato                    698    3   1674.6570 < 0.00000000000000022 ***
## tipo                       218    1   1571.0941 < 0.00000000000000022 ***
## zona_nueva:estrato          13   15      6.0501   0.00000000000086313 ***
## zona_nueva:tipo              2    5      3.5464              0.003324 ** 
## estrato:tipo                 9    3     21.8779   0.00000000000004167 ***
## zona_nueva:estrato:tipo     11   15      5.3215   0.00000000008622567 ***
## Residuals                 1139 8195                                      
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Con fines exploratorios, se realizó un ANOVA de tres vías (Type III) para evaluar el efecto conjunto de las variables cualitativas (Zona, estrato y t ipo de inmueble) sobre el precio del inmueble expresado en logaritmo. Los resultados evidencian que, ademas de los efectos principales, existen interacciones estadísticamente significativas entre las variables categoricas consideradas. Este hallazgo sugiere que el efecto de cada factor sobre el precio no es independiente, lo cual resulta relevante para la especificación de modelos predictivos más completos que incorporen tanto variables cualitativas como cuantitativas.

Anexo 6. Modelo 1 - Total de inmuebles registrados en Cali.

Para la construcción del modelo 1 se evaluaron diversas especificaciones alternativas. El modelo finalmente seleccionado incolpora log en el precio y en area costruida e interraciones entre las variables categoricas (estrato:tipo y zona_nueva:tipo). La selección del modelo se basó en el criterio de información bayesiano (BIC).

Este modelo tiene como propósito evaluar, en la base global de inmuebles, si las propuestas de crédito del cliente son realistas o, por el contrario, se encuentran por debajo del valor esperado para el tipo de inmueble.

options(contrasts = c("contr.treatment", "contr.poly"))
vivienda <- readRDS("vivienda_consolidada.rds")
norte_final <- readRDS("vivienda_norte_final.rds")
sur_final <- readRDS("vivienda_sur_final.rds")
vivienda$estrato <- factor(vivienda$estrato)
vivienda$tipo1   <- factor(vivienda$tipo)
vivienda$estrato <- as.factor(vivienda$estrato)
modelo_global_log <- lm(
  log(preciom) ~ log(areaconst) + estrato + banios + 
    habitaciones + parqueaderos + zona_nueva + tipo + 
    estrato:tipo + zona_nueva:tipo, 
  data = vivienda
)
summary(modelo_global_log)
## 
## Call:
## lm(formula = log(preciom) ~ log(areaconst) + estrato + banios + 
##     habitaciones + parqueaderos + zona_nueva + tipo + estrato:tipo + 
##     zona_nueva:tipo, data = vivienda)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1.43768 -0.15138 -0.00069  0.14695  1.00422 
## 
## Coefficients:
##                                       Estimate Std. Error t value
## (Intercept)                          2.6165253  0.0384915  67.977
## log(areaconst)                       0.5072903  0.0091823  55.247
## estrato4                             0.3327848  0.0163320  20.376
## estrato5                             0.5392265  0.0159665  33.772
## estrato6                             0.8653710  0.0179361  48.247
## banios                               0.0816945  0.0038622  21.153
## habitaciones                        -0.0349926  0.0033886 -10.327
## parqueaderos                         0.0755018  0.0036328  20.784
## zona_nuevaCentro Historico           0.0096023  0.0245648   0.391
## zona_nuevaNorte                      0.0426826  0.0159106   2.683
## zona_nuevaOccidente                  0.0631634  0.0157358   4.014
## zona_nuevaOriente                    0.0275696  0.0275290   1.001
## zona_nuevaSur                       -0.0004858  0.0148644  -0.033
## tipoCasa                             0.1826782  0.0249657   7.317
## estrato4:tipoCasa                   -0.1479023  0.0235677  -6.276
## estrato5:tipoCasa                   -0.2438703  0.0226626 -10.761
## estrato6:tipoCasa                   -0.2932966  0.0247114 -11.869
## zona_nuevaCentro Historico:tipoCasa -0.0165329  0.0368788  -0.448
## zona_nuevaNorte:tipoCasa            -0.1233524  0.0240558  -5.128
## zona_nuevaOccidente:tipoCasa        -0.0509869  0.0246089  -2.072
## zona_nuevaOriente:tipoCasa          -0.1110356  0.0401880  -2.763
## zona_nuevaSur:tipoCasa              -0.0234636  0.0221058  -1.061
##                                                 Pr(>|t|)    
## (Intercept)                         < 0.0000000000000002 ***
## log(areaconst)                      < 0.0000000000000002 ***
## estrato4                            < 0.0000000000000002 ***
## estrato5                            < 0.0000000000000002 ***
## estrato6                            < 0.0000000000000002 ***
## banios                              < 0.0000000000000002 ***
## habitaciones                        < 0.0000000000000002 ***
## parqueaderos                        < 0.0000000000000002 ***
## zona_nuevaCentro Historico                       0.69589    
## zona_nuevaNorte                                  0.00732 ** 
## zona_nuevaOccidente                    0.000060355639209 ***
## zona_nuevaOriente                                0.31663    
## zona_nuevaSur                                    0.97393    
## tipoCasa                               0.000000000000283 ***
## estrato4:tipoCasa                      0.000000000370078 ***
## estrato5:tipoCasa                   < 0.0000000000000002 ***
## estrato6:tipoCasa                   < 0.0000000000000002 ***
## zona_nuevaCentro Historico:tipoCasa              0.65395    
## zona_nuevaNorte:tipoCasa               0.000000301456701 ***
## zona_nuevaOccidente:tipoCasa                     0.03831 *  
## zona_nuevaOriente:tipoCasa                       0.00574 ** 
## zona_nuevaSur:tipoCasa                           0.28854    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.24 on 6666 degrees of freedom
##   (1555 observations deleted due to missingness)
## Multiple R-squared:  0.8546, Adjusted R-squared:  0.8542 
## F-statistic:  1866 on 21 and 6666 DF,  p-value: < 0.00000000000000022
BIC(modelo_global_log)
## [1] 71.61843

Interpretación de los coeficientes.

  • log(areaconst): Por cada aumento del 1% en el área construida, el precio del inmueble aumenta aproximadamente un 0.51%, ceteris paribus. Esto evidencia rendimientos marginales decrecientes del área sobre el precio.

  • Cada baño adicional se asocia con un aumento promedio de cerca 8.5% en el precio (ceteris paribus).

  • Cada parqueadero adicional se asocia con un aumento promedio del 7.5% en el precio, manteniendo constantes las demás variables.

  • Habitaciones: Manteniendo las demas variables constantes (Especificamente el area construida en este caso), añadir más habitaciones reduce el precio un 3.49%.

Estrato (base: estrato 3)

Para el tipo de inmueble base (apartamento):

Estrato 4: ≈ 39.5% más costoso.

Estrato 5: ≈ 71.4% más costoso.

Estrato 6: ≈ 137.6% más costoso.

Dado que el modelo incluye interacciones con el tipo de inmueble, este efecto no es homogéneo entre apartamentos y casas.

  • Tipo de inmueble (base: Apartamento):

Una Casa es en promedio 20.0% más costosa que un Apartamento, ceteris paribus.

  • Zona (base: Centro):

Centro Histórico: sin efecto significativo. Norte: +4.3% respecto al Centro Occidente: +6.5% respecto al Centro (significativo) Nota: La base no incluye barrios como siloe. Oriente: No significativo. Sur: No significativo.

estrato4:tipoCasa: El coeficiente es negativo y significativo, lo que indica que el bonuns de precios frente a los apartamentos se reduce en este estrato. Mientras que en el estrato base las casas son aproximadamente 20.03% más costosas, en el estrato 4 esta diferencia se reduce a cerca del 3.5%, ceteris paribus.

estrato5:tipoCasa: Coeficiente negativo y significativo. Indica que, en el estrato 5, las casas son en promedio 5.9% más económicas que los apartamentos comparables.

estrato6:tipoCasa: Coeficiente negativo y significativo. Sugiere que, en el estrato 6, las casas son aproximadamente 10.5% más económicas que los apartamentos equivalentes.

La misma lógica aplica para las interacciones entre zona y tipo de inmueble, las cuales indican que el diferencial de precio entre casas y apartamentos varía según la localización.

El modelo explica ≈ 85.46% de la variabilidad de los precios de los inmuebles en la ciudad de Cali, eliminando 1.555 observaciones con valores faltantes (NA) en parqueaderos.

Supuestos:

Linealidad

Para evaluar el supuesto de linealidad y justificar la inclusión del log en el area construida , se ajustó un Modelo Aditivo Generalizado (GAM). La Ilustración A8 presenta las funciones parciales de área y número de habitaciones, evidenciando una relación claramente no lineal con el logaritmo del precio de los inmuebles.

library(mgcv)
modelo_gam <- gam(log(preciom) ~ s(areaconst) + estrato + banios + 
                    s(habitaciones) + parqueaderos + zona_nueva + tipo + 
                    estrato:tipo, 
                  data = vivienda)

par(mfrow = c(1, 2), oma = c(0, 0, 3, 0)) 
plot(modelo_gam, select = 1, shade = TRUE, 
     shade.col = "lightblue", main = "Área",
     xlab = "Área Construida (m2)", ylab = "f(Área)")
plot(modelo_gam, select = 2, shade = TRUE, 
     shade.col = "mistyrose", main = "Habitaciones",
     xlab = "Número de Habitaciones", ylab = "f(Habitaciones)")
mtext("Ilustración A8: Efectos parciales de Área y Habitaciones", 
      side = 3, line = 1, outer = TRUE, cex = 1.1, font = 2)

summary(modelo_gam)
## 
## Family: gaussian 
## Link function: identity 
## 
## Formula:
## log(preciom) ~ s(areaconst) + estrato + banios + s(habitaciones) + 
##     parqueaderos + zona_nueva + tipo + estrato:tipo
## 
## Parametric coefficients:
##                             Estimate Std. Error t value             Pr(>|t|)
## (Intercept)                 5.066451   0.021296 237.906 < 0.0000000000000002
## estrato4                    0.323384   0.016239  19.914 < 0.0000000000000002
## estrato5                    0.514031   0.016618  30.931 < 0.0000000000000002
## estrato6                    0.796253   0.019123  41.639 < 0.0000000000000002
## banios                      0.080476   0.003861  20.844 < 0.0000000000000002
## parqueaderos                0.079508   0.003603  22.070 < 0.0000000000000002
## zona_nuevaCentro Historico -0.004455   0.018106  -0.246             0.805666
## zona_nuevaNorte            -0.005483   0.011722  -0.468             0.640005
## zona_nuevaOccidente         0.036243   0.011762   3.081             0.002068
## zona_nuevaOriente          -0.015808   0.019862  -0.796             0.426137
## zona_nuevaSur              -0.011765   0.010920  -1.077             0.281373
## tipoCasa                    0.081825   0.021953   3.727             0.000195
## estrato4:tipoCasa          -0.133559   0.022837  -5.848     0.00000000519929
## estrato5:tipoCasa          -0.214118   0.022590  -9.479 < 0.0000000000000002
## estrato6:tipoCasa          -0.185593   0.025343  -7.323     0.00000000000027
##                               
## (Intercept)                ***
## estrato4                   ***
## estrato5                   ***
## estrato6                   ***
## banios                     ***
## parqueaderos               ***
## zona_nuevaCentro Historico    
## zona_nuevaNorte               
## zona_nuevaOccidente        ** 
## zona_nuevaOriente             
## zona_nuevaSur                 
## tipoCasa                   ***
## estrato4:tipoCasa          ***
## estrato5:tipoCasa          ***
## estrato6:tipoCasa          ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##                   edf Ref.df      F             p-value    
## s(areaconst)    8.450  8.902 358.25 <0.0000000000000002 ***
## s(habitaciones) 6.934  7.952  18.29 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## R-sq.(adj) =  0.857   Deviance explained = 85.8%
## GCV = 0.0566  Scale est. = 0.056343  n = 6688

Multicolinealidad

gvif_values <- vif(modelo_global_log)
print(gvif_values)
##                      GVIF Df GVIF^(1/(2*Df))
## log(areaconst)   4.076902  1        2.019134
## estrato          8.900710  3        1.439585
## banios           3.265088  1        1.806955
## habitaciones     2.419882  1        1.555597
## parqueaderos     1.923468  1        1.386892
## zona_nueva      17.493100  5        1.331333
## tipo            16.858351  1        4.105892
## estrato:tipo    31.090911  3        1.773259
## zona_nueva:tipo 77.975080  5        1.545950

La multicolinealidad del modelo se evaluó mediante el GVIF (Generalized Variance Inflation Factor), adecuado para variables categóricas y modelos con interacciones. Ninguna variable mostró valores que indiquen problemas graves de colinealidad.

Normalidad de los errores

Dado que el modelo cuenta con un número elevado de observaciones (6.668), se utilizó la prueba de Jarque-Bera para evaluar la normalidad de los residuos, acompañada de un Q-Q plot. Los resultados fueron:

library(tseries)
jarque.bera.test(residuals(modelo_global_log))
## 
##  Jarque Bera Test
## 
## data:  residuals(modelo_global_log)
## X-squared = 370.4, df = 2, p-value < 0.00000000000000022
qqnorm(residuals(modelo_global_log), 
       main = "Ilustración A9: Q-Q plot de residuos")
qqline(residuals(modelo_global_log), col = "red")

Tanto la ilustración A.9 como el valor p del estadístico indican que los errores del modelo no se distribuyen normalmente.

Homocedasticidad

Se realizó un test de Breusch-Pagan para detectar heterocedasticidad. Los resultados muestran que la varianza de los errores no es constante, indicando que los residuos del modelo no cumplen el supuesto de homocedasticidad.

library(lmtest)
bptest(modelo_global_log)
## 
##  studentized Breusch-Pagan test
## 
## data:  modelo_global_log
## BP = 387.95, df = 21, p-value < 0.00000000000000022

Autocorrelación

Se realizó un test de Durbin-Watson para evaluar la autocorrelación de los residuos. Los resultados fueron:

library(lmtest)
dwtest(modelo_global_log)
## 
##  Durbin-Watson test
## 
## data:  modelo_global_log
## DW = 1.7368, p-value < 0.00000000000000022
## alternative hypothesis: true autocorrelation is greater than 0

Esto indica que los errores del modelo presentan autocorrelación positiva. Este comportamiento es esperado, dado que nuestra base de datos corresponde a inmuebles, y las ubicaciones cercanas tienden a influirse mutuamente, generando cierta relación entre las observaciones.

Presencia de atipicos

Finalmente, se presenta la Ilustración A10, que muestra los puntos influyentes mediante un gráfico de residuos studentizados versus apalancamiento. Este análisis se complementa con la presentación de un extracto (head) de los valores DFBETAS del modelo planteado.

par(mfrow = c(1, 1), mar = c(5, 5, 4, 2) + 0.1)
plot(modelo_global_log, which = 5, 
     main = "Ilustración A10: Análisis de Puntos Influyentes")

# Calcular DFBETAs
dfbetas_values <- dfbetas(modelo_global_log)

# máximo absoluto por observación
max_dfb <- apply(abs(dfbetas_values), 1, max)

# ordenar de mayor a menor
orden <- order(max_dfb, decreasing = TRUE)

# ver las observaciones más influyentes
head(dfbetas_values[orden, ])
##       (Intercept) log(areaconst)     estrato4     estrato5    estrato6
## 3118  0.411784950    -0.53691909  0.060983470  0.116348124  0.17895517
## 2843  0.390041403    -0.47575081  0.062744479  0.044434981  0.12759566
## 7451  0.353846338    -0.41646340  0.055608163  0.047771803  0.12101353
## 4357 -0.004205224     0.01303773 -0.002977588  0.006552142  0.02732087
## 7639 -0.084699266     0.13054898 -0.057714888 -0.025585596 -0.02270285
## 8152  0.130461715    -0.20678563  0.029593756  0.051467518  0.08149551
##           banios habitaciones parqueaderos zona_nuevaCentro Historico
## 3118  0.07279852   0.37262922   0.10356202               0.0147675402
## 2843  0.15964264   0.15042801   0.15774181               0.0097296732
## 7451  0.10548967   0.09993363   0.15759154               0.0079896171
## 4357  0.09496760   0.02106171  -0.31559964               0.0003060009
## 7639  0.05437609  -0.06001454  -0.30462943              -0.0045269937
## 8152 -0.01276223   0.28116446   0.02807868               0.0081908370
##      zona_nuevaNorte zona_nuevaOccidente zona_nuevaOriente zona_nuevaSur
## 3118    0.0118933406         0.027699216     -0.0047805550  -0.022924385
## 2843    0.0156310033         0.012624309     -0.0065697910  -0.075145326
## 7451    0.0123476105         0.012287661     -0.0063878114  -0.061674233
## 4357   -0.0001098773         0.004516732      0.0020335601  -0.003939588
## 7639   -0.0478865214        -0.006291313      0.0028798039   0.001297201
## 8152    0.0082117834         0.014618277     -0.0005564136  -0.007128989
##         tipoCasa estrato4:tipoCasa estrato5:tipoCasa estrato6:tipoCasa
## 3118 -0.12946914        0.12570134      0.1127887979        0.09949496
## 2843  0.09555613       -0.02694371      0.0008260897       -0.04603402
## 7451  0.10114730       -0.02296756     -0.0025410422       -0.04391322
## 4357 -0.05720671        0.08840640      0.1100473637        0.11985844
## 7639 -0.04171674        0.03425822      0.0223454554        0.04728622
## 8152  0.03411965       -0.08540688     -0.0894371039       -0.08643458
##      zona_nuevaCentro Historico:tipoCasa zona_nuevaNorte:tipoCasa
## 3118                         0.082787347              0.116303810
## 2843                         0.003797733             -0.007239883
## 7451                         0.001698098             -0.009559874
## 4357                        -0.019316688             -0.080177999
## 7639                        -0.001833498              0.042241089
## 8152                         0.030660429              0.035541426
##      zona_nuevaOccidente:tipoCasa zona_nuevaOriente:tipoCasa
## 3118                  0.096498565                0.096152448
## 2843                 -0.009584797                0.007425993
## 7451                 -0.010889332                0.005443285
## 4357                 -0.029300373               -0.008044952
## 7639                  0.004999952               -0.005690436
## 8152                  0.032719276                0.302393072
##      zona_nuevaSur:tipoCasa
## 3118             0.11165444
## 2843             0.02649240
## 7451             0.01501542
## 4357            -0.03381135
## 7639             0.00798422
## 8152             0.07173747

A partir del análisis de la Ilustración A10 (Residuos vs. Apalancamiento), se observa que, si bien existen algunas observaciones con apalancamiento (leverage) elevado (como los casos 8152,3118,2843); su número es reducido en relación con el tamaño muestral. Complementariamente, los estadísticos DFBETAS indican que la exclusión de estos puntos produciría cambios muy pequeños en los coeficientes, insuficientes para alterar las estimaciones globales. En consecuencia, no se consideró necesario eliminar observaciones del modelo y re entrenar el modelo.

Discusión y Supuestos

Dado que los mismos supuestos no se cumplen en los cuatro modelos planteados, se proponen las mismas soluciones para los cuatro modelos. El análisis de los supuestos solo se aplica a aquellos que no se cumplen (normalidad, linealidad, homocedasticidad, presencia de outliers y autocorrelación) y no se considera la multicolinealidad.

Análisis de Linealidad y Especificación del Modelo

A través de la evaluación con un Modelo Aditivo Generalizado (GAM), se determinó que el modelo original no satisfacía el supuesto de linealidad, especialmente en la variable areaconst (área construida en metros cuadrados). La relación entre los metros cuadrados y el precio no es proporcional, sino que presentaba una curvatura que el modelo lineal no logra capturar.

Solución Aplicada: Para resolver este problema, se aplicó una transformación logarítmica a las variables. Al usar logaritmos, el modelo ahora interpreta los cambios en términos porcentuales - Elasticidad.

Análisis de Normalidad en los residuos

  1. Normalidad de los Residuos Al evaluar el supuesto de normalidad mediante el test de Jarque-Bera (JB), los resultados ( =370.4,p<0.0001) indican que los residuos no siguen una distribución normal. Sin embargo, dada la robustez del modelo por el alto número de observaciones ( n=6,688), este incumplimiento puede obviarse en parte.

A pesar de esto, se identifican las siguientes posibles causas y posibles soluciones para abordar la falta de normalidad:

Transformaciones avanzadas: Una posible solución técnica es el uso de transformaciones de Box-Cox o Yeo-Johnson y con ello tratar de aproximar los residuos a una distribución normal de manera eficiente que con logaritmos.

Sesgo por variable omitida: La no normalidad suele ser un síntoma de variables no incluidas en el modelo, que el término de error está capturando. En este caso, factores como la antigüedad del inmueble o la autocorrelación espacial (vecindad) podrían estar generando sesgo en los residuos al no estar incorporados explícitamente en el modelo.

Alternativas de modelado: Una solución estructural seria utilizar Modelos Lineales Generalizados (GLM), que no requieren el supuesto de normalidad en los residuos.

Análisis Homocedasticidad

El test de Breusch-Pagan (BP = 387.95, p < 0.0001) rechaza la hipótesis nula de homocedasticidad, confirmando la presencia de heterocedasticidad en los residuos del modelo. Esto implica que la varianza del error no es constante a lo largo de todas las observaciones de la muestra.

Entre las posibles soluciones encontramos

Errores Estándar Robustos : Usar métodos como HC3 para que los errores estándar sean consistentes ante la presencia de heterocedasticidad.

El uso de Mínimos Cuadrados Ponderados (WLS): Asignandole un peso a cada observación en relación con su varianza.

Análisis de Autocorrelación

El test de Durbin-Watson (DW = 1.7368, p < 0.0001) indicando la presencia de autocorrelación positiva en los errores del modelo. En el contexto de este proyecto sobre el mercado inmobiliario de Cali, esto se traduce como autocorrelación espacial.

Es esperable en este tipo de modelos que los inmuebles cercanos compartan características similares que el modelo no logra capturar, como la seguridad, el acceso a servicios, la cercanía a centros culturales o de esparcimiento, etc.

Para evaluar esta autocorrelación espacial se utilizó el test de Moran, con los siguientes resultados:

library(spdep)
datos_completos <- vivienda[complete.cases(
  vivienda[, c("preciom","areaconst","estrato","banios","habitaciones","parqueaderos","zona_nueva","tipo")]
), ]
residuos <- residuals(modelo_global_log)
coords <- cbind(datos_completos$longitud, datos_completos$latitud)
coords[,1] <- jitter(coords[,1], factor = 1e-5)
coords[,2] <- jitter(coords[,2], factor = 1e-5)
vecinos <- knn2nb(knearneigh(coords, k = 5))
pesos <- nb2listw(vecinos, style = "W")
moran.test(residuos, pesos)
## 
##  Moran I test under randomisation
## 
## data:  residuos  
## weights: pesos    
## 
## Moran I statistic standard deviate = 17.774, p-value <
## 0.00000000000000022
## alternative hypothesis: greater
## sample estimates:
## Moran I statistic       Expectation          Variance 
##     0.12721204327    -0.00014954389     0.00005134664

Las soluciónes en este caso consisten en utilizar modelos espaciales, como SAR (Spatial Autoregressive Model) o SEM (Spatial Error Model), que permitan capturar las relaciones entre inmuebles cercanos.

Análisis de atipicos

Aunque el modelo detectó algunas observaciones atípicas, el examen mediante DBETA evidenció que estas no afectan significativamente los coeficientes debido al gran tamaño de la muestra de inmuebles.

Dado que muchas de estas observaciones son outliers representativos, es decir, reflejan casos reales del mercado inmobiliario, eliminarlas no resulta adecuado. En su lugar, se podrían emplear modelos robustos frente a outliers.

Anexo 7. Modelo 2. Sur - Apartamentos

sur_final <- readRDS("vivienda_sur_final.rds")
options(contrasts = c("contr.treatment", "contr.poly"))
sur_final$estrato <- factor(sur_final$estrato)
sur_final$tipo   <- factor(sur_final$tipo)
modelo_sur1 <- lm(log(preciom) ~  log(areaconst) + estrato + 
                   banios +  habitaciones + parqueaderos, 
                 data = sur_final)

summary(modelo_sur1)
## 
## Call:
## lm(formula = log(preciom) ~ log(areaconst) + estrato + banios + 
##     habitaciones + parqueaderos, data = sur_final)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1.59856 -0.11055  0.01385  0.12743  0.61659 
## 
## Coefficients:
##                 Estimate Std. Error t value             Pr(>|t|)    
## (Intercept)     2.280421   0.088520  25.762 < 0.0000000000000002 ***
## log(areaconst)  0.608316   0.022442  27.107 < 0.0000000000000002 ***
## estrato4        0.254487   0.035050   7.261    0.000000000000585 ***
## estrato5        0.371276   0.035250  10.533 < 0.0000000000000002 ***
## estrato6        0.632286   0.038203  16.550 < 0.0000000000000002 ***
## banios          0.072668   0.008537   8.512 < 0.0000000000000002 ***
## habitaciones   -0.036628   0.010034  -3.650              0.00027 ***
## parqueaderos    0.114180   0.010733  10.639 < 0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1934 on 1688 degrees of freedom
##   (295 observations deleted due to missingness)
## Multiple R-squared:  0.8453, Adjusted R-squared:  0.8447 
## F-statistic:  1318 on 7 and 1688 DF,  p-value: < 0.00000000000000022
BIC(modelo_sur1)
## [1] -701.5388

Interpretación de los coeficientes.

Área construida: log(areaconst): Por cada aumento del 1% en el área construida, el precio del inmueble aumenta aproximadamente un 0.60%, ceteris paribus. Esto evidencia rendimientos marginales decrecientes del área sobre el precio.

Estrato : (Base: Estrato 3)

Estrato 4: El precio de un apartamento en la zona sur aumenta en promedio un 28.9%.(ceteris paribus)

Estrato 5: El precio de un apartamento en la zona sur aumenta en promedio un 44.95%.(ceteris paribus)

Estrato 6: El precio de un apartamento en la zona sur aumenta en promedio un 88.1%.(ceteris paribus)

Parqueaderos: Cada parqueadero adicional incrementa el precio en promedio un 12%. Manteniendo constante las demás variables.

Baños: Cada baño adicional incrementa el precio en promedio un 7.5% al valor del inmueble. Manteniendo constantes las demás variables.

Habitaciones: Tiene un coeficiente negativo de -3.5%. Lo que indicaria que manteniendo constantes las demas variables los apartamentos del sur castigan tener más habitaciones, “efecto sardina”.

El modelo explica el 84.53% de la variabilidad de los precios de los apartamentos en el sur de cali, eliminando 295 observaciones con valores faltantes (NA) en parqueaderos.

Supuestos

Linealidad

Se procede de la misma forma con el modelo utilizando la base de apartamentos de la zona sur para evaluar la linealidad. Para ello, se emplea un modelo GAM con el fin de examinar las relaciones parciales. La Ilustración A11 muestra el efecto parcial del área construida y del número de habitaciones. Este gráfico sirve como guía para la inclusión del log en el área construida.Se observa la presencia de pocas observaciones con áreas grandes en el extremo derecho del gráfico de efecto parcial, lo que puede indicar observaciones problematicas.

# Aquí lo definiste como gam_sur
gam_sur <- mgcv::gam(log(preciom) ~ s(areaconst) + estrato + 
                       s(banios, k = 3) + s(habitaciones, k = 3) + parqueaderos, 
                     data = sur_final)
par(mfrow = c(1, 2), oma = c(0, 0, 3, 0)) 

plot(gam_sur, select = 1, shade = TRUE, 
     shade.col = "lightblue", main = "Area",
     xlab = "Area Construida (m2)", ylab = "f(Area)")
plot(gam_sur, select = 2, shade = TRUE, 
     shade.col = "mistyrose", main = "Habitaciones",
     xlab = "Número de Habitaciones", ylab = "f(Habitaciones)")
mtext("Ilustración A11: Efectos parciales de Área y Habitaciones", 
      side = 3, line = 1, outer = TRUE, cex = 1.1, font = 2)

summary(gam_sur)
## 
## Family: gaussian 
## Link function: identity 
## 
## Formula:
## log(preciom) ~ s(areaconst) + estrato + s(banios, k = 3) + s(habitaciones, 
##     k = 3) + parqueaderos
## 
## Parametric coefficients:
##              Estimate Std. Error t value             Pr(>|t|)    
## (Intercept)   5.19264    0.03679 141.140 < 0.0000000000000002 ***
## estrato4      0.21939    0.03398   6.456        0.00000000014 ***
## estrato5      0.31244    0.03478   8.982 < 0.0000000000000002 ***
## estrato6      0.57504    0.03697  15.552 < 0.0000000000000002 ***
## parqueaderos  0.10858    0.01048  10.359 < 0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##                   edf Ref.df      F             p-value    
## s(areaconst)    8.819  8.989 119.81 <0.0000000000000002 ***
## s(banios)       1.859  1.980  34.19 <0.0000000000000002 ***
## s(habitaciones) 1.000  1.000  33.96 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## R-sq.(adj) =  0.864   Deviance explained = 86.5%
## GCV = 0.033101  Scale est. = 0.032776  n = 1696

Multicolinealidad

vif_values_sur <- vif(modelo_sur1)
print(vif_values_sur)
##                    GVIF Df GVIF^(1/(2*Df))
## log(areaconst) 3.352951  1        1.831107
## estrato        2.009355  3        1.123335
## banios         2.941057  1        1.714951
## habitaciones   1.463710  1        1.209839
## parqueaderos   2.219667  1        1.489855

Para evaluar la multicolinealidad se empleó el factor de inflación de la varianza generalizado (GVIF), debido a la presencia de términos con múltiples grados de libertad derivados del uso de polinomios. Los resultados obtenidos no evidencian problemas de multicolinealidad en el modelo.

Homocedasticidad

Para evaluar la homocedasticidad de los residuos se aplicó la prueba de Breusch-Pagan. Los resultados se presentan a continuación e indican la presencia de heterocedasticidad en el modelo por lo que se rechaza la hipótesis nula de varianza constante.

bptest(modelo_sur1)
## 
##  studentized Breusch-Pagan test
## 
## data:  modelo_sur1
## BP = 272.58, df = 7, p-value < 0.00000000000000022

Normalidad en los errores

shapiro.test(residuals(modelo_sur1))
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(modelo_sur1)
## W = 0.9632, p-value < 0.00000000000000022

La normalidad de los errores se evaluó mediante la prueba de Shapiro-Wilk, dado que el número de observaciones del modelo es menor a 5.000. Los resultados que se presentan a continuación permiten rechazar la hipótesis nula de normalidad de los residuos.

shapiro.test(residuals(modelo_sur1))
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(modelo_sur1)
## W = 0.9632, p-value < 0.00000000000000022

Autocorrelación

La autocorrelación de los residuos se evaluó mediante la prueba de Durbin–Watson. Los resultados que se presentan a continuación permiten rechazar la hipótesis nula de ausencia de autocorrelación , evidenciando correlación positiva entre los errores. Este resultado es consistente con la naturaleza espacial implícita de los datos inmobiliarios.

dwtest(modelo_sur1)
## 
##  Durbin-Watson test
## 
## data:  modelo_sur1
## DW = 1.5697, p-value < 0.00000000000000022
## alternative hypothesis: true autocorrelation is greater than 0

Valores extremos

par(mfrow = c(1, 1), mar = c(5, 5, 4, 2) + 0.1)
plot(modelo_sur1, which = 5, 
     main = "Ilustración A12: Análisis de Puntos Influyentes")

dfbetas_values <- dfbetas(modelo_sur1)
max_dfb <- apply(abs(dfbetas_values), 1, max)
orden <- order(max_dfb, decreasing = TRUE)
head(dfbetas_values[orden, ],5
     )
##      (Intercept) log(areaconst)    estrato4   estrato5   estrato6       banios
## 745   1.80238729    -2.19774389  0.07184288  0.1576466  0.2967496  0.835279158
## 1678  1.72930002    -1.99670277  0.07233838  0.1749125  0.3060908  0.647883620
## 101  -0.04664826    -0.27827975  0.74583861  0.7666599  0.7270159  0.043512160
## 883   0.23158967     0.02846382 -0.55709316 -0.5504144 -0.5096137 -0.008410695
## 143   0.12758271     0.03923516 -0.50280463 -0.5076362 -0.4650176 -0.030529491
##      habitaciones parqueaderos
## 745     0.8428441  0.905086321
## 1678    0.4998522  0.926067363
## 101     0.0809652  0.146316804
## 883    -0.1274939  0.005790936
## 143     0.1267732 -0.043519770

Si bien el análisis de influencia mediante la distancia de Cook y los DFBetas permitió identificar observaciones potencialmente influyentes, se optó por conservar el modelo con la muestra completa. Esta decisión se fundamenta en el objetivo del estudio, orientado a la predicción y a la identificación de inmuebles potencialmente oportunidades de negocio, donde los valores atípicos pueden representar oportunidades reales de mercado.

Anexo 8. Modelo 3. Casas - Zona Norte.

Para la elección del modelo especifico correspondiente a la Zona norte – Casas, se siguió el mismo criterio metodológico aplicado en los modelos anteriores. Inicialmente se estimaron diversas modelos y se seleccionaron aquellos con mejor desempeño según el (BIC) y la significancia estadística de los coeficientes.

En este caso, si bien el modelo con menor BIC no incluía la variable habitaciones, se decidió mantenerla en la especificación final debido a requerimientos explícitos del cliente y a su relevancia operativa para la etapa de predicción.

El modelo elegido se presenta a continuación:

sur_final <- readRDS("vivienda_sur_final.rds")
norte_final$estrato <- factor(norte_final$estrato)
norte_final$tipo   <- factor(norte_final$tipo)
modelo_norte_r <- lm(log(preciom) ~ poly(areaconst,2) + estrato + 
                       + banios + parqueaderos+habitaciones,
                       data = norte_final)
summary(modelo_norte_r)
## 
## Call:
## lm(formula = log(preciom) ~ poly(areaconst, 2) + estrato + +banios + 
##     parqueaderos + habitaciones, data = norte_final)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.60762 -0.14350 -0.00991  0.14028  0.99396 
## 
## Coefficients:
##                       Estimate Std. Error t value             Pr(>|t|)    
## (Intercept)          5.4828653  0.0554543  98.872 < 0.0000000000000002 ***
## poly(areaconst, 2)1  5.6693851  0.3859490  14.689 < 0.0000000000000002 ***
## poly(areaconst, 2)2 -2.3429431  0.3150768  -7.436    0.000000000000702 ***
## estrato4             0.2822718  0.0419708   6.725    0.000000000064883 ***
## estrato5             0.3802345  0.0398742   9.536 < 0.0000000000000002 ***
## estrato6             0.6518463  0.0611711  10.656 < 0.0000000000000002 ***
## banios               0.0303153  0.0133699   2.267               0.0239 *  
## parqueaderos         0.0474763  0.0093590   5.073    0.000000615667876 ***
## habitaciones        -0.0006902  0.0095592  -0.072               0.9425    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2427 on 378 degrees of freedom
##   (185 observations deleted due to missingness)
## Multiple R-squared:  0.7548, Adjusted R-squared:  0.7496 
## F-statistic: 145.4 on 8 and 378 DF,  p-value: < 0.00000000000000022
BIC(modelo_norte_r)
## [1] 52.94886

Interpretación de los coeficientes.

  • Área construida: Los coeficientes de area construida significativos representan un efecto no lineal sobre el logaritmo del precio , se intuyen rendimientos decrecientes en el precio, es decir a medida que el area aumenta hasta cierto punto el precio no aumenta de la misma forma.

  • Estrato : (Base: Estrato 3)

Estrato 4: El precio de un apartamento en la zona sur aumenta en promedio cerca de un 32.6 %.(ceteris paribus)

Estrato 5: El precio de un apartamento en la zona sur aumenta en promedio un 46.25%.(ceteris paribus)

Estrato 6: El precio de un apartamento en la zona sur aumenta en promedio un 91.89%.(ceteris paribus)

  • Parqueaderos: Cada parqueadero adicional incrementa el precio en promedio un 4,8% Manteniendo constante las demás variables.

  • Baños: Cada baño adicional incrementa el precio en promedio un 3% al valor del inmueble. Manteniendo constantes las demás variables.

  • Habitaciones: El coeficiente no es significativo y su valor es muy pequeño, podemos suponer que todo el efecto es capturado por el area construida.

Supuestos

Linealidad

Se emplea un modelo GAM con el fin de examinar las relaciones parciales en la base de datos de casas en la zona norte. La Ilustración A13 muestra el efecto parcial del área construida sobre el precio en logaritmo.

gam_norte <- mgcv::gam(log(preciom) ~ s(areaconst) + estrato + 
                       s(banios, k = 3) + s(parqueaderos, k = 3) + 
                       s(habitaciones, k = 3),
                     data = norte_final)
par(mfrow = c(1, 1))
plot(gam_norte, select = 1, shade = TRUE, shade.col = "lightblue",
     main = "Ilustración A13. Efecto Área", 
     xlab = "Área Construida (m2)", ylab = "f(área)")

summary(gam_norte)
## 
## Family: gaussian 
## Link function: identity 
## 
## Formula:
## log(preciom) ~ s(areaconst) + estrato + s(banios, k = 3) + s(parqueaderos, 
##     k = 3) + s(habitaciones, k = 3)
## 
## Parametric coefficients:
##             Estimate Std. Error t value             Pr(>|t|)    
## (Intercept)  5.78427    0.03458 167.275 < 0.0000000000000002 ***
## estrato4     0.24844    0.04309   5.765   0.0000000171659222 ***
## estrato5     0.34777    0.04156   8.369   0.0000000000000012 ***
## estrato6     0.61831    0.06156  10.044 < 0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##                   edf Ref.df      F              p-value    
## s(areaconst)    8.258  8.846 26.624 < 0.0000000000000002 ***
## s(banios)       1.656  1.874  6.598              0.00869 ** 
## s(parqueaderos) 1.000  1.000 24.332           0.00000186 ***
## s(habitaciones) 1.356  1.577  0.413              0.72731    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## R-sq.(adj) =  0.758   Deviance explained = 76.8%
## GCV = 0.059438  Scale est. = 0.056939  n = 387

La ilustracion A13 muestra el efecto parcial del área construida sobre el logaritmo del precio. Se observa que la relación no es completamente lineal, aunque los intervalos de confianza son bastante amplios, en parte debido a la escasez de datos en valores altos de área construida. Con base en la gráfica de efecto parcial, se intentó especificar en el modelo lineal un temrino polinomico de tercer grado poly(areaconst, 3), sin embargo, este no resulto significativo por lo que se opto por mantener unicamente un termino cuadratico para dicha variable.

Multicolinealidad

Para evaluar la multicolinealidad se empleó el factor de inflación de la varianza generalizado (GVIF), debido a la presencia de términos con múltiples grados de libertad derivados del uso de polinomios . Los resultados obtenidos no evidencian problemas de multicolinealidad en el modelo.

vif_modelo_norte_r <- vif(modelo_norte_r)
print(vif_modelo_norte_r)
##                        GVIF Df GVIF^(1/(2*Df))
## poly(areaconst, 2) 1.863253  2        1.168336
## estrato            1.673526  3        1.089613
## banios             2.225686  1        1.491873
## parqueaderos       1.262914  1        1.123795
## habitaciones       1.711981  1        1.308427

Normalidad en los errores

La normalidad de los errores se evaluó mediante la prueba de Shapiro-Wilk, dado que el número de observaciones del modelo es menor a 5.000. Los resultados que se presentan a continuación permiten rechazar la hipótesis nula de normalidad de los residuos.

shapiro.test(residuals(modelo_norte_r))
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(modelo_norte_r)
## W = 0.98502, p-value = 0.0004918

Homocedasticidad

Para evaluar la homocedasticidad de los residuos se aplicó la prueba de Breusch-Pagan. Los resultados se presentan a continuación e indican la presencia de heterocedasticidad en el modelo por lo que se rechaza la hipótesis nula de varianza constante.

bptest(modelo_norte_r)
## 
##  studentized Breusch-Pagan test
## 
## data:  modelo_norte_r
## BP = 17.391, df = 8, p-value = 0.02628

Autocorrelación

La autocorrelación de los residuos se evaluó mediante la prueba de Durbin–Watson. Los resultados que se presentan a continuación permiten rechazar la hipótesis nula de ausencia de autocorrelación , evidenciando correlación positiva entre los errores. Este resultado es consistente con la naturaleza espacial implícita de los datos inmobiliarios.

dwtest(modelo_norte_r)
## 
##  Durbin-Watson test
## 
## data:  modelo_norte_r
## DW = 1.8062, p-value = 0.02268
## alternative hypothesis: true autocorrelation is greater than 0

Valores extremos

par(mfrow = c(1, 1), mar = c(5, 5, 4, 2) + 0.1)
plot(modelo_norte_r, which = 5, 
     main = "Ilustración A14: Análisis de Puntos Influyentes")

dfbetas_values <- dfbetas(modelo_norte_r)
max_dfb <- apply(abs(dfbetas_values), 1, max)
orden <- order(max_dfb, decreasing = TRUE)
head(dfbetas_values[orden, ],5
     )
##     (Intercept) poly(areaconst, 2)1 poly(areaconst, 2)2      estrato4
## 291  0.06374822          0.56471898           1.7757795  0.2164291960
## 488  0.01672890         -0.34643096          -0.7069744  0.0103074070
## 282 -0.16497360         -0.08148683           0.1144954  0.1757340534
## 348  0.42837867          0.23600911          -0.2635898 -0.4104875563
## 100  0.00160346         -0.36402145          -0.4834706 -0.0006337343
##        estrato5    estrato6      banios parqueaderos habitaciones
## 291  0.21414653  0.38233092  0.13977443   0.13090097  -0.38852882
## 488  0.01918510  0.07686588  0.07283743   0.11566969  -0.20344230
## 282  0.23111560  0.15632432  0.18581723  -0.53378440   0.06792831
## 348 -0.49231809 -0.35119484  0.04039407   0.01798521  -0.20168534
## 100 -0.02586115  0.13441512 -0.13100228  -0.03078988   0.12507049

Anexo 9. Modelo 4. Occidente - Apartamentos

Dada la dificultad de encontrar inmuebles que se ajusten a los requerimientos específicos del cliente, se plantea un cuarto modelo que se ejecuta sobre la base de datos filtrada de apartamentos ubicados en el occidente de la ciudad. Se prueban diferentes especificaciones, buscando aquellas que presenten un mejor ajuste, medido a través del R cuadrado ajustado y el BIC. El modelo elegido incluye una transformación logarítmica en preciom y un polinomio cúbico en areaconst.

vivienda <- readRDS("vivienda_consolidada.rds")
viviendas_occidente <- subset(vivienda,
                              zona_nueva == "Occidente" &
                                tipo == "Apartamento")
viviendas_occidente$estrato <- as.factor(viviendas_occidente$estrato)
modelo_occ <- lm(log(preciom) ~ poly(areaconst,3,raw=TRUE) + habitaciones + banios + parqueaderos + estrato , data = viviendas_occidente)
summary(modelo_occ)
## 
## Call:
## lm(formula = log(preciom) ~ poly(areaconst, 3, raw = TRUE) + 
##     habitaciones + banios + parqueaderos + estrato, data = viviendas_occidente)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.85772 -0.14942  0.01434  0.16020  0.71771 
## 
## Coefficients:
##                                        Estimate      Std. Error t value
## (Intercept)                      4.072179239896  0.056412019657  72.186
## poly(areaconst, 3, raw = TRUE)1  0.014615408224  0.001002760901  14.575
## poly(areaconst, 3, raw = TRUE)2 -0.000038819588  0.000004007516  -9.687
## poly(areaconst, 3, raw = TRUE)3  0.000000035478  0.000000004895   7.248
## habitaciones                    -0.097994559414  0.011972858866  -8.185
## banios                           0.091979724067  0.010927109091   8.418
## parqueaderos                     0.144132105302  0.013504539909  10.673
## estrato4                         0.197429280565  0.036470764821   5.413
## estrato5                         0.396798694072  0.036425793248  10.893
## estrato6                         0.594905700670  0.038754605271  15.351
##                                             Pr(>|t|)    
## (Intercept)                     < 0.0000000000000002 ***
## poly(areaconst, 3, raw = TRUE)1 < 0.0000000000000002 ***
## poly(areaconst, 3, raw = TRUE)2 < 0.0000000000000002 ***
## poly(areaconst, 3, raw = TRUE)3 0.000000000000783569 ***
## habitaciones                    0.000000000000000732 ***
## banios                          < 0.0000000000000002 ***
## parqueaderos                    < 0.0000000000000002 ***
## estrato4                        0.000000075555412218 ***
## estrato5                        < 0.0000000000000002 ***
## estrato6                        < 0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2373 on 1126 degrees of freedom
##   (126 observations deleted due to missingness)
## Multiple R-squared:  0.8807, Adjusted R-squared:  0.8798 
## F-statistic: 923.7 on 9 and 1126 DF,  p-value: < 0.00000000000000022
BIC(modelo_occ)
## [1] 22.96446

Interpretación de los coeficientes.

Área construida: El modelo indica que la relación entre el área y el precio no es una línea recta, sino una curva compleja . Todos los coeficientes son estadisticamente significativos. Inicialmente a medida que aumenta el area el precio aumenta, luego alcanza un punto donde disminuye este precio y finalmente vuelve a subir, quizas capturando el efecto de mansiones de lujo o inmuebles exclusivos.Ceteris paribus.

Habitaciones: Un aumento en un habitación reduce el preco en -9.33%, manteniendo las demas variables constantes (especificamente el area construida).

Baños : un baño adicional incremente al precio del inmueble en 9.63% manteniendo las demas variables constantes.

Parqueaderos: Cada parqueadero adicional incrementa el precio en 15.5% manteniendo las demas variables constantes.

estrato 4 (Base estrato 3): Pasar del estrato base al estrato 4 representa un aumento promedio en el precio del 21.8%. Ceteris paribus.

estrato5 (Base estrato 3): Pasar del estrato base al estrato 5 representa un aumento promedio en el precio del 48.7%. Ceteris paribus.

estrato6 (Base estrato 3): Pasar del estrato base al estrato 6 representa un aumento promedio en el precio del 81.2%. Ceteris paribus.

El modelo explica el 88.07% de la variabilidad de los precios de los apartamentos en el occidente de cali, eliminando 126 observaciones con valores faltantes (NA) en parqueaderos.

Linealidad

Se emplea un modelo GAM con el fin de examinar las relaciones parciales en la base de datos de apartamentos ubicados en el occidente de Cali. La Ilustración A15 muestra el efecto parcial del área construida sobre el logaritmo del precio. Esta ilustración sirve como guía para la inclusión de un polinomio de tercer grado en la variable área construida.

gam_occidente <- mgcv::gam(log(preciom) ~ s(areaconst) + estrato + 
                       s(banios, k = 3) + s(parqueaderos, k = 3) + 
                       s(habitaciones, k = 3),
                     data = viviendas_occidente)
par(mfrow = c(1, 1))
plot(gam_norte, select = 1, shade = TRUE, shade.col = "lightblue",
     main = "Ilustración A15. Efecto Área", 
     xlab = "Área Construida (m2)", ylab = "f(área)")

summary(gam_occidente)
## 
## Family: gaussian 
## Link function: identity 
## 
## Formula:
## log(preciom) ~ s(areaconst) + estrato + s(banios, k = 3) + s(parqueaderos, 
##     k = 3) + s(habitaciones, k = 3)
## 
## Parametric coefficients:
##             Estimate Std. Error t value             Pr(>|t|)    
## (Intercept)  5.68104    0.03537 160.616 < 0.0000000000000002 ***
## estrato4     0.18945    0.03655   5.183          0.000000259 ***
## estrato5     0.39136    0.03747  10.445 < 0.0000000000000002 ***
## estrato6     0.58236    0.03961  14.703 < 0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##                   edf Ref.df     F             p-value    
## s(areaconst)    7.349  8.329 76.20 <0.0000000000000002 ***
## s(banios)       1.000  1.000 71.24 <0.0000000000000002 ***
## s(parqueaderos) 1.885  1.986 57.57 <0.0000000000000002 ***
## s(habitaciones) 1.401  1.640 37.45 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## R-sq.(adj) =  0.884   Deviance explained = 88.6%
## GCV = 0.054902  Scale est. = 0.054146  n = 1136

Multicolinealidad

Para evaluar la multicolinealidad se empleó el factor de inflación de la varianza generalizado (GVIF), debido a la presencia de términos con múltiples grados de libertad derivados del uso de polinomios . Los resultados obtenidos no evidencian problemas de multicolinealidad en el modelo.

library(car)
vif_modelo_occ <- vif(modelo_occ)
print(vif_modelo_occ)
##                                    GVIF Df GVIF^(1/(2*Df))
## poly(areaconst, 3, raw = TRUE) 5.475190  3        1.327598
## habitaciones                   1.685651  1        1.298326
## banios                         3.343650  1        1.828565
## parqueaderos                   2.415136  1        1.554071
## estrato                        2.102412  3        1.131843

Normalidad en los errores

La normalidad de los errores se evaluó mediante la prueba de Shapiro-Wilk, dado que el número de observaciones del modelo es menor a 5.000. Los resultados que se presentan a continuación permiten rechazar la hipótesis nula de normalidad de los residuos.

shapiro.test(residuals(modelo_occ))
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(modelo_occ)
## W = 0.99489, p-value = 0.0006765

Homocedasticidad

Para evaluar la homocedasticidad de los residuos se aplicó la prueba de Breusch-Pagan. Los resultados se presentan a continuación e indican la presencia de heterocedasticidad en el modelo por lo que se rechaza la hipótesis nula de varianza constante.

bptest(modelo_occ)
## 
##  studentized Breusch-Pagan test
## 
## data:  modelo_occ
## BP = 105.65, df = 9, p-value < 0.00000000000000022

Autocorrelación

La autocorrelación de los residuos se evaluó mediante la prueba de Durbin–Watson. Los resultados que se presentan a continuación permiten rechazar la hipótesis nula de ausencia de autocorrelación , evidenciando correlación positiva entre los errores. Este resultado es consistente con la naturaleza espacial implícita de los datos inmobiliarios.

dwtest(modelo_occ)
## 
##  Durbin-Watson test
## 
## data:  modelo_occ
## DW = 1.6644, p-value = 0.000000005221
## alternative hypothesis: true autocorrelation is greater than 0

Valores extremos

rownames(viviendas_occidente) <- viviendas_occidente$id
modelo_occ <- lm(formula = log(preciom) ~ poly(areaconst, 3, raw = TRUE) + 
                   habitaciones + banios + parqueaderos + estrato, 
                 data = viviendas_occidente)
plot(modelo_occ, which = 5, main = "Ilustración A16: Análisis de Puntos Influyentes")

dfbetas_values <- dfbetas(modelo_occ)
max_dfb <- apply(abs(dfbetas_values), 1, max)
orden <- order(max_dfb, decreasing = TRUE)
top_5_ids <- rownames(dfbetas_values)[orden][1:5]
casas_influyentes <- viviendas_occidente[top_5_ids, ]
tabla_impacto <- dfbetas_values[top_5_ids, ]
print(tabla_impacto)
##      (Intercept) poly(areaconst, 3, raw = TRUE)1
## 7182  0.73658522                     -1.12931216
## 7969 -0.19414700                      0.33374514
## 6868 -0.06927709                      0.01143141
## 7456  0.03722931                      0.13069829
## 7923 -0.04836016                      0.12918757
##      poly(areaconst, 3, raw = TRUE)2 poly(areaconst, 3, raw = TRUE)3
## 7182                      1.38884275                     -1.63664188
## 7969                     -0.38423597                      0.44717116
## 6868                     -0.02854361                      0.04526114
## 7456                     -0.13329744                      0.12318373
## 7923                     -0.12379029                      0.12273224
##      habitaciones      banios parqueaderos     estrato4     estrato5
## 7182   0.34339364 -0.27270059  0.362825694  0.160217290  0.253101637
## 7969  -0.05260616 -0.15978215  0.005997387 -0.034112308 -0.056607507
## 6868  -0.04175531 -0.12055966 -0.062388118  0.252207879  0.289493689
## 7456  -0.09065645  0.08997761  0.032398514 -0.248541852 -0.295304703
## 7923   0.02288770 -0.02019182 -0.293162622 -0.009445123  0.005107434
##         estrato6
## 7182  0.34470220
## 7969 -0.08386902
## 6868  0.32452125
## 7456 -0.31303594
## 7923  0.02133007

Se realizó un análisis de influencia utilizando el indicador DFBETAS, con el objetivo de identificar observaciones que pudieran tener un impacto desproporcionado sobre los coeficientes estimados del modelo. Si bien se identificaron algunos casos con valores relativamente altos de DFBETAS(id = 7183;7669 y 8130), estos corresponden a un número reducido de observaciones dentro de una muestra amplia de más de 1110 registros por lo que se conservaron las observaciones, asi mismo estas observaciones pueden ser verdaderas oportunidades de negocio que vale la pena ser evaluadas.

Anexo 10. Poligonos de inmuebles

El Anexo 10 muestra la configuración de los polígonos utilizados para la reclasificación de los inmuebles en nuevas zonas. Se identificó que un número considerable de inmuebles estaba incorrectamente clasificado. Además, se definió una nueva zona denominada “Centro Histórico”, con el objetivo de diferenciarla del área central convencional.