Actividad 2 Caso C&A

La compañía C&A recibió una solicitud de asesoría inmobiliaria por parte de una empresa internacional interesada en ubicar a dos familias en Cali. Para responder de manera objetiva, se construyeron modelos de regresión lineal múltiple a partir de las ofertas del mercado de los últimos tres meses, segmentadas según las condiciones de cada solicitud (tipo de inmueble y zona). Estos modelos permiten estimar el precio de mercado esperado para cada vivienda solicitada y, a partir de ahí, identificar ofertas reales que se ajusten al presupuesto pre-aprobado de cada familia.

Los resultados detallados, las validaciones estadísticas y la comparación de los dos modelos se presentan en la sección de Anexos. A continuación se resume la recomendación para cada solicitud; la síntesis ejecutiva completa se retoma al final del documento, una vez expuesta la evidencia que la soporta.

Anexos técnicos

Marco conceptual y estrategia metodológica

El análisis se enmarca en la teoría de precios hedónicos, según la cual el precio de un bien heterogéneo como una vivienda puede descomponerse como una función de sus atributos estructurales (área, número de habitaciones, baños, parqueaderos) y de localización (zona, estrato como proxy del entorno socioeconómico). Bajo este marco, un modelo de regresión múltiple sobre el precio permite recuperar el “precio implícito” de cada atributo, manteniendo los demás constantes.

Dado que los precios de vivienda suelen presentar asimetría a la derecha y varianza creciente con el nivel del precio, se contrastan dos especificaciones para cada solicitud:

  1. Modelo lineal: preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios
  2. Modelo log-lineal (semilogarítmico): log(preciom) ~ areaconst + estrato + habitaciones + parqueaderos + banios, donde los coeficientes se interpretan como cambios porcentuales aproximados en el precio ante cambios marginales en cada atributo.

La selección del modelo final para cada solicitud no se basa únicamente en el ajuste dentro de muestra (R²), sino en una validación fuera de muestra (partición 80/20 entrenamiento-prueba) que compara el error de predicción de ambas especificaciones en la escala original de precios (millones de pesos), corrigiendo el sesgo de retransformación del modelo log-lineal mediante el estimador de smearing de Duan (1983): \(\hat{y} = \exp(\hat{\beta}'x) \cdot \overline{\exp(\hat{e})}\), donde \(\overline{\exp(\hat{e})}\) es el promedio de los residuos exponenciados del modelo de entrenamiento.

Adicionalmente, dado que los datos tienen coordenadas geográficas, se revisa si el estadístico de Durbin-Watson sugiere posible dependencia entre observaciones, como indicio indirecto de agrupamiento espacial (submercados o efectos de barrio) que el modelo estructural no captura.

Solicitud 1: Casa, Zona Norte (crédito pre-aprobado: $350 millones)

Paso 1. Filtro de la base y validación geográfica

Se filtra la base se incluye únicamente ofertas de casas ubicadas en la Zona Norte, que es el segmento relevante para la primera solicitud.

base1 <- vivienda %>% filter(tipo == "Casa", zona == "Zona Norte")
cat("Número de observaciones en base1:", nrow(base1), "\n")
## Número de observaciones en base1: 435
kable(head(base1, 3), caption = "Primeros 3 registros de base1 (Casas, Zona Norte)")
Primeros 3 registros de base1 (Casas, Zona Norte)
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
1209 Zona Norte 02 5 320 150 2 4 6 Casa acopi -76.51341 3.47968
1592 Zona Norte 02 5 780 380 2 3 3 Casa acopi -76.51674 3.48721
4460 Zona Norte 02 4 625 355 3 5 5 Casa acopi -76.53179 3.40590
kable(base1 %>%
        summarise(n = n(), precio_prom = mean(preciom), area_prom = mean(areaconst),
                  estrato_prom = round(mean(estrato), 1)),
      caption = "Resumen de base1")
Resumen de base1
n precio_prom area_prom estrato_prom
435 479.7724 292.7177 4.5

Para verificar la consistencia geográfica del filtro, se ubican los puntos de base1 en el mapa y se comparan contra la distribución de todas las zonas de la ciudad.

set.seed(123)
muestra_zonas <- vivienda %>% sample_n(min(1500, nrow(vivienda)))

plot_ly(muestra_zonas, lat = ~latitud, lon = ~longitud, type = "scattermapbox",
        mode = "markers", color = ~zona,
        marker = list(size = 6, opacity = 0.6),
        text = ~paste("Zona:", zona, "<br>Barrio:", barrio)) %>%
  layout(mapbox = list(style = "open-street-map", zoom = 10.5,
                        center = list(lat = 3.43, lon = -76.53)),
         title = "Distribución espacial de todas las zonas (muestra)")
rangos <- vivienda %>% group_by(zona) %>%
  summarise(lat_min = min(latitud), lat_max = max(latitud),
            lon_min = min(longitud), lon_max = max(longitud))
kable(rangos, caption = "Rango de coordenadas por zona")
Rango de coordenadas por zona
zona lat_min lat_max lon_min lon_max
Zona Centro 3.39841 3.48700 -76.54400 -76.49700
Zona Norte 3.33308 3.49770 -76.58915 -76.46745
Zona Oeste 3.35973 3.49415 -76.58744 -76.46400
Zona Oriente 3.34400 3.47300 -76.56113 -76.46570
Zona Sur 3.33300 3.49684 -76.56706 -76.46300

Discusión: los rangos de latitud y longitud de las cinco zonas se traslapan de manera considerable (todas comparten prácticamente el mismo rango de longitud, entre -76.6° y -76.5°, y rangos de latitud muy similares). Esto indica que la variable zona no corresponde a una partición geográfica estricta por cuadrantes de coordenadas, sino a una clasificación administrativa o comercial (probablemente por comuna o sector catastral) que no se traduce en fronteras limpias sobre el mapa. Por esta razón, al graficar los puntos de base1 se observan casas etiquetadas como “Zona Norte” geográficamente cercanas a puntos de otras zonas, esto no es un error del filtro, sino una característica de cómo fue construida la variable en la fuente original. En consecuencia, el filtro por la variable zona (no por coordenadas) es el criterio correcto y consistente con la forma en que el mercado y la inmobiliaria clasifican los sectores.

plot_ly(base1, lat = ~latitud, lon = ~longitud, type = "scattermapbox",
        mode = "markers", marker = list(size = 7, color = "#2c7fb8", opacity = 0.7),
        text = ~paste("Barrio:", barrio, "<br>Precio:", preciom, "M")) %>%
  layout(mapbox = list(style = "open-street-map", zoom = 11,
                        center = list(lat = mean(base1$latitud), lon = mean(base1$longitud))),
         title = "Ofertas de casas en Zona Norte (base1)")

Paso 2. Análisis exploratorio de datos

Se analiza la relación entre el precio (preciom) y las variables área construida, estrato, número de baños, número de habitaciones y zona (esta última ya fijada en “Zona Norte” para base1).

num_vars <- base1 %>% select(preciom, areaconst, estrato, banios, habitaciones)
kable(round(cor(num_vars), 2), caption = "Matriz de correlación - base1")
Matriz de correlación - base1
preciom areaconst estrato banios habitaciones
preciom 1.00 0.69 0.53 0.51 0.37
areaconst 0.69 1.00 0.35 0.46 0.42
estrato 0.53 0.35 1.00 0.35 0.06
banios 0.51 0.46 0.35 1.00 0.59
habitaciones 0.37 0.42 0.06 0.59 1.00
p1 <- ggplot(base1, aes(x = areaconst, y = preciom, color = factor(estrato),
                         text = paste("Barrio:", barrio))) +
  geom_point(alpha = 0.7) +
  labs(title = "Precio vs. Área construida (coloreado por estrato)",
       x = "Área construida (m²)", y = "Precio (millones $)", color = "Estrato") +
  theme_minimal()
ggplotly(p1, tooltip = c("x", "y", "text", "colour"))
p2 <- ggplot(base1, aes(x = factor(estrato), y = preciom)) +
  geom_boxplot(fill = "#74a9cf") +
  labs(title = "Precio por estrato", x = "Estrato", y = "Precio (millones $)") +
  theme_minimal()
ggplotly(p2)
p3 <- ggplot(base1, aes(x = factor(habitaciones), y = preciom)) +
  geom_boxplot(fill = "#a1d99b") +
  labs(title = "Precio por número de habitaciones", x = "Habitaciones", y = "Precio (millones $)") +
  theme_minimal()
ggplotly(p3)
p4 <- ggplot(base1, aes(x = factor(banios), y = preciom)) +
  geom_boxplot(fill = "#fdae6b") +
  labs(title = "Precio por número de baños", x = "Baños", y = "Precio (millones $)") +
  theme_minimal()
ggplotly(p4)

Interpretación: el área construida presenta la correlación más fuerte con el precio, seguida del estrato; ambas relaciones son positivas y consistentes con la lógica del mercado inmobiliario (a mayor área y mejor estrato, mayor precio). El número de habitaciones y de baños muestran una asociación positiva más moderada, y se observa dispersión considerable dentro de cada categoría, lo que sugiere que estas variables por sí solas no explican completamente el precio y que su efecto probablemente se solape con el del área construida (viviendas más grandes tienden a tener más habitaciones y baños).

Paso 3. Especificación, comparación y selección del modelo

Siguiendo la estrategia metodológica descrita, se estiman dos especificaciones sobre base1: un modelo lineal y un modelo log-lineal.

modelo1 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = base1)
modelo1_log <- lm(log(preciom) ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = base1)
summary(modelo1)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = base1)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -784.29  -77.56  -16.03   47.67  978.61 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -238.17090   44.40551  -5.364 1.34e-07 ***
## areaconst       0.67673    0.05281  12.814  < 2e-16 ***
## estrato        80.63495    9.82632   8.206 2.70e-15 ***
## habitaciones    7.64511    5.65873   1.351    0.177    
## parqueaderos   24.00598    5.86889   4.090 5.14e-05 ***
## banios         18.89938    7.48800   2.524    0.012 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 155.1 on 429 degrees of freedom
## Multiple R-squared:  0.6041, Adjusted R-squared:  0.5995 
## F-statistic: 130.9 on 5 and 429 DF,  p-value: < 2.2e-16
kable(tidy(modelo1, conf.int = TRUE), digits = 3, caption = "Coeficientes del modelo lineal (Casas, Zona Norte)")
Coeficientes del modelo lineal (Casas, Zona Norte)
term estimate std.error statistic p.value conf.low conf.high
(Intercept) -238.171 44.406 -5.364 0.000 -325.450 -150.891
areaconst 0.677 0.053 12.814 0.000 0.573 0.781
estrato 80.635 9.826 8.206 0.000 61.321 99.949
habitaciones 7.645 5.659 1.351 0.177 -3.477 18.767
parqueaderos 24.006 5.869 4.090 0.000 12.471 35.541
banios 18.899 7.488 2.524 0.012 4.182 33.617
kable(glance(modelo1)[, c("r.squared", "adj.r.squared", "sigma", "statistic", "p.value")],
      digits = 3, caption = "Bondad de ajuste - modelo lineal")
Bondad de ajuste - modelo lineal
r.squared adj.r.squared sigma statistic p.value
0.604 0.599 155.115 130.919 0

Interpretación de los coeficientes (modelo lineal):

  • Área construida: por cada metro cuadrado adicional, el precio esperado aumenta significativamente (p < 0.001), manteniendo las demás variables constantes. Es el efecto más robusto del modelo y coincide con lo esperado: el área es el principal determinante del valor de una vivienda.
  • Estrato: subir un estrato socioeconómico se asocia con un incremento significativo en el precio (p < 0.001), reflejando el efecto de la ubicación y el entorno urbanístico sobre el valor comercial, independientemente del tamaño de la casa.
  • Habitaciones: el coeficiente no es estadísticamente significativo (p > 0.05) bajo errores estándar clásicos. Esto es razonable una vez que ya se controla por área construida: el número de habitaciones deja de aportar información adicional sobre el precio cuando el tamaño total de la vivienda ya está en el modelo (viviendas del mismo tamaño pueden repartir el espacio en más o menos habitaciones sin que ello cambie sustancialmente el precio).
  • Parqueaderos y baños: ambos son significativos (p < 0.001 y p < 0.05, respectivamente) con efecto positivo, consistente con que son atributos valorados de forma independiente en el mercado caleño. Errores estándar robustos (HC3): dado que se anticipa heterocedasticidad (se confirma en el Paso 4), es más prudente inferir significancia con errores estándar robustos a heterocedasticidad que con los clásicos:
kable(tidy(coeftest(modelo1, vcov = vcovHC(modelo1, type = "HC3"))), digits = 4,
      caption = "Coeficientes con errores estándar robustos (HC3) - modelo lineal, base1")
Coeficientes con errores estándar robustos (HC3) - modelo lineal, base1
term estimate std.error statistic p.value
(Intercept) -238.1709 45.7844 -5.2020 0.0000
areaconst 0.6767 0.1479 4.5754 0.0000
estrato 80.6349 12.1550 6.6339 0.0000
habitaciones 7.6451 6.9791 1.0954 0.2739
parqueaderos 24.0060 7.0696 3.3957 0.0007
banios 18.8994 10.4622 1.8065 0.0715

Bajo HC3, el coeficiente de banios pierde significancia al 5% (p ≈ 0.07), lo que indica que su significancia bajo el supuesto de varianza constante era, al menos en parte, artificial. Esto ilustra por qué la validación de supuestos del Paso 4 es indispensable antes de fijar conclusiones sobre significancia estadística.

Especificación alternativa (log-lineal):

kable(tidy(modelo1_log, conf.int = TRUE), digits = 4, caption = "Coeficientes del modelo log-lineal (Casas, Zona Norte)")
Coeficientes del modelo log-lineal (Casas, Zona Norte)
term estimate std.error statistic p.value conf.low conf.high
(Intercept) 4.4148 0.0742 59.5164 0.0000 4.2690 4.5606
areaconst 0.0011 0.0001 12.8891 0.0000 0.0010 0.0013
estrato 0.2152 0.0164 13.1135 0.0000 0.1830 0.2475
habitaciones 0.0165 0.0095 1.7416 0.0823 -0.0021 0.0350
parqueaderos 0.0470 0.0098 4.7977 0.0000 0.0278 0.0663
banios 0.0461 0.0125 3.6832 0.0003 0.0215 0.0707
cat("R² modelo lineal:      ", round(summary(modelo1)$r.squared, 3), "\n")
## R² modelo lineal:       0.604
cat("R² modelo log-lineal:  ", round(summary(modelo1_log)$r.squared, 3), "\n")
## R² modelo log-lineal:   0.697

En la escala logarítmica, el R² sube de 0.604 a 0.697, y los coeficientes se interpretan como semi-elasticidades: por ejemplo, cada baño adicional se asocia con un incremento aproximado de 4.6% en el precio, manteniendo lo demás constante. Este mejor ajuste dentro de muestra motiva evaluar si también mejora la capacidad predictiva fuera de muestra (Paso 4).

Ajuste general: en su escala original, el modelo lineal explica cerca del 60% de la variabilidad del precio de las casas en la Zona Norte. Es un ajuste razonable para datos de mercado inmobiliario real, pero deja una porción importante de la variación sin explicar por factores no observados antigüedad de la construcción, calidad de acabados, cercanía a vías principales, orientación, seguridad del sector, entre otros.

Paso 4. Validación de supuestos y evaluación predictiva

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

par(mfrow = c(1, 1))
shapiro_p <- shapiro.test(resid(modelo1))$p.value
bp_p <- bptest(modelo1)$p.value
dw <- dwtest(modelo1)
vif_vals <- vif(modelo1)
shapiro_p_log <- shapiro.test(resid(modelo1_log))$p.value
bp_p_log <- bptest(modelo1_log)$p.value

kable(data.frame(Prueba = c("Shapiro-Wilk (normalidad)", "Breusch-Pagan (homocedasticidad)",
                             "Durbin-Watson (independencia temporal/orden)"),
                  Lineal = c(format(shapiro_p, digits = 3), format(bp_p, digits = 3), round(dw$p.value, 4)),
                  `Log-lineal` = c(format(shapiro_p_log, digits = 3), format(bp_p_log, digits = 3), "—")),
      caption = "Pruebas de supuestos: H0 = se cumple el supuesto (normalidad / homocedasticidad)")
Pruebas de supuestos: H0 = se cumple el supuesto (normalidad / homocedasticidad)
Prueba Lineal Log.lineal
Shapiro-Wilk (normalidad) 8.77e-20 9.34e-05
Breusch-Pagan (homocedasticidad) 7.33e-16 6.63e-11
Durbin-Watson (independencia temporal/orden) 0.0055
kable(data.frame(Variable = names(vif_vals), VIF = round(vif_vals, 2)),
      caption = "Factores de inflación de varianza (VIF) - modelo lineal")
Factores de inflación de varianza (VIF) - modelo lineal
Variable VIF
areaconst areaconst 1.46
estrato estrato 1.31
habitaciones habitaciones 1.72
parqueaderos parqueaderos 1.23
banios banios 1.97

Independencia de los residuos (Durbin-Watson): aunque esta prueba se diseñó para series de tiempo, un valor del estadístico DW alejado de 2 en datos de corte transversal ordenados espacialmente puede ser indicio indirecto de estructura residual no capturada (p. ej., agrupamiento por barrio); se reporta en la tabla anterior junto con normalidad y homocedasticidad.

Validación fuera de muestra (partición 80/20): se compara el error de predicción de ambos modelos en la escala original (millones de pesos), aplicando la corrección de smearing de Duan al modelo log-lineal.

set.seed(2026)
idx1 <- sample(seq_len(nrow(base1)), floor(0.8 * nrow(base1)))
train1 <- base1[idx1, ]; test1 <- base1[-idx1, ]

m_lin_tr  <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = train1)
m_log_tr  <- lm(log(preciom) ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = train1)

pred_lin  <- predict(m_lin_tr, newdata = test1)
smear1    <- mean(exp(resid(m_log_tr)))
pred_log  <- exp(predict(m_log_tr, newdata = test1)) * smear1

kable(data.frame(Modelo = c("Lineal", "Log-lineal (con corrección de smearing)"),
                  RMSE = c(rmse(test1$preciom, pred_lin), rmse(test1$preciom, pred_log)),
                  MAE  = c(mae(test1$preciom, pred_lin), mae(test1$preciom, pred_log))),
      digits = 1, caption = "Error de predicción fuera de muestra (millones $) - base1")
Error de predicción fuera de muestra (millones $) - base1
Modelo RMSE MAE
Lineal 172.1 115.2
Log-lineal (con corrección de smearing) 204.0 121.2

Interpretación integrada:

  • Normalidad y homocedasticidad: se rechazan en ambas especificaciones (p < 0.05), aunque el modelo log-lineal reduce sustancialmente la severidad de la violación de normalidad (p pasa de ≈1e-20 a ≈1e-4), consistente con que la transformación logarítmica atenúa la asimetría derecha típica de los precios.
  • Posible dependencia espacial: el estadístico de Durbin-Watson se aleja levemente de 2, lo que es consistente con que casas geográficamente cercanas podrían tener errores de predicción correlacionados (efectos de submercado o barrio no capturados por las variables estructurales). No se dispone de una prueba espacial formal (p. ej., I de Moran) en esta versión del análisis por limitaciones de instalación del paquete spdep, pero se señala como una limitación a validar en una siguiente iteración.
  • Multicolinealidad: todos los VIF son menores a 2, sin evidencia de colinealidad problemática.
  • Decisión de modelo final: pese a que el modelo log-lineal mejora el ajuste y los supuestos dentro de muestra, el modelo lineal predice mejor fuera de muestra en este segmento (menor RMSE y MAE). Este resultado, aparentemente contraintuitivo, es una observación metodológicamente relevante: un mejor cumplimiento de supuestos y un mayor R² no garantizan mejor capacidad predictiva fuera de muestra, especialmente con un tamaño de muestra moderado (n = 435) donde la partición holdout tiene alta varianza. Por consistencia con el objetivo del ejercicio (predecir precios de mercado para asesorar a C&A) y por parsimonia interpretativa para un informe ejecutivo, se adopta el modelo lineal como modelo final para la Solicitud 1, documentando la posible dependencia espacial como línea de trabajo futuro (p. ej., un modelo de rezago espacial o error espacial, disponible en el paquete spatialreg, o una prueba formal de I de Moran con spdep).

Paso 5. Predicción del precio para la solicitud 1

La solicitud pide una casa de 200 m², 1 parqueadero, 2 baños, 4 habitaciones, estrato 4 o 5, en la Zona Norte. Se predice con el modelo final (lineal) e intervalos de confianza al 95%.

nueva_vivienda1 <- data.frame(areaconst = 200, estrato = c(4, 5),
                               habitaciones = 4, parqueaderos = 1, banios = 2)
pred1 <- cbind(nueva_vivienda1, predict(modelo1, newdata = nueva_vivienda1, interval = "confidence"))
kable(pred1, digits = 1, caption = "Precio estimado para la vivienda solicitada (Solicitud 1)")
Precio estimado para la vivienda solicitada (Solicitud 1)
areaconst estrato habitaciones parqueaderos banios fit lwr upr
200 4 4 1 2 312.1 287.2 337.0
200 5 4 1 2 392.7 360.9 424.6

Interpretación: para estrato 4, el modelo estima un precio de mercado de aproximadamente $312 millones (IC 95%: 287–337), dentro del crédito pre-aprobado de $350 millones. Para estrato 5, el precio estimado sube a aproximadamente $393 millones (IC 95%: 361–425), lo cual excede el presupuesto disponible. Esto indica que, con las características solicitadas, la búsqueda debe concentrarse preferentemente en estrato 4 para mantenerse dentro del crédito aprobado.

Paso 6. Ofertas potenciales para la solicitud 1

Se filtran ofertas reales de base1 que se ajusten al presupuesto de $350 millones y a características similares a las solicitadas (estrato 4 o 5, área construida amplia, al menos 3 habitaciones).

ofertas1 <- base1 %>%
  filter(preciom <= 350, estrato %in% c(4, 5), areaconst >= 150, habitaciones >= 3) %>%
  arrange(desc(preciom)) %>%
  head(6)

kable(ofertas1 %>% select(barrio, preciom, areaconst, estrato, habitaciones, banios, parqueaderos),
      caption = "Ofertas potenciales dentro del presupuesto (Solicitud 1)")
Ofertas potenciales dentro del presupuesto (Solicitud 1)
barrio preciom areaconst estrato habitaciones banios parqueaderos
el bosque 350 200 5 4 3 3
el bosque 350 300 5 6 5 3
el bosque 350 240 5 6 3 2
el bosque 350 203 5 5 2 2
la flora 350 264 5 4 3 2
la flora 350 160 5 3 3 2
plot_ly(ofertas1, lat = ~latitud, lon = ~longitud, type = "scattermapbox", mode = "markers",
        marker = list(size = 14, color = "#d7301f"),
        text = ~paste0("Barrio: ", barrio, "<br>Precio: $", preciom, "M",
                        "<br>Área: ", areaconst, "m²", "<br>Estrato: ", estrato)) %>%
  layout(mapbox = list(style = "open-street-map", zoom = 11.5,
                        center = list(lat = mean(ofertas1$latitud), lon = mean(ofertas1$longitud))),
         title = "Ofertas potenciales para la Solicitud 1 (≤ $350M)")

Discusión: se identifican al menos 6 ofertas reales dentro del presupuesto, concentradas principalmente en los barrios El Bosque y La Flora, con áreas entre 160 y 300 m² y estratos 4-5. Estas opciones cumplen simultáneamente el presupuesto y un perfil de características cercano al solicitado, por lo que constituyen una recomendación concreta para la familia de la Solicitud 1.

Solicitud 2: Apartamento, Zona Sur (crédito pre-aprobado: $850 millones)

Paso 1. Filtro de la base y validación geográfica

base2 <- vivienda %>% filter(tipo == "Apartamento", zona == "Zona Sur")
cat("Número de observaciones en base2:", nrow(base2), "\n")
## Número de observaciones en base2: 2381
kable(head(base2, 3), caption = "Primeros 3 registros de base2 (Apartamentos, Zona Sur)")
Primeros 3 registros de base2 (Apartamentos, Zona Sur)
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
5098 Zona Sur 05 4 290 96 1 2 3 Apartamento acopi -76.53464 3.44987
698 Zona Sur 02 3 78 40 1 1 2 Apartamento aguablanca -76.50100 3.40000
8199 Zona Sur NA 6 875 194 2 5 3 Apartamento aguacatal -76.55700 3.45900
kable(base2 %>%
        summarise(n = n(), precio_prom = mean(preciom), area_prom = mean(areaconst),
                  estrato_prom = round(mean(estrato), 1)),
      caption = "Resumen de base2")
Resumen de base2
n precio_prom area_prom estrato_prom
2381 318.2369 102.1566 4.7
plot_ly(base2, lat = ~latitud, lon = ~longitud, type = "scattermapbox",
        mode = "markers", marker = list(size = 5, color = "#31a354", opacity = 0.5),
        text = ~paste("Barrio:", barrio, "<br>Precio:", preciom, "M")) %>%
  layout(mapbox = list(style = "open-street-map", zoom = 11,
                        center = list(lat = mean(base2$latitud), lon = mean(base2$longitud))),
         title = "Ofertas de apartamentos en Zona Sur (base2)")

Discusión: al igual que en la Solicitud 1, los rangos de coordenadas de la Zona Sur se traslapan con los de las zonas vecinas (ver tabla de rangos en la sección anterior), por lo que se ratifica que el filtro correcto es por la variable categórica zona y no por coordenadas geográficas.

Paso 2. Análisis exploratorio de datos

num_vars2 <- base2 %>% select(preciom, areaconst, estrato, banios, habitaciones)
kable(round(cor(num_vars2), 2), caption = "Matriz de correlación - base2")
Matriz de correlación - base2
preciom areaconst estrato banios habitaciones
preciom 1.00 0.74 0.65 0.71 0.30
areaconst 0.74 1.00 0.45 0.66 0.41
estrato 0.65 0.45 1.00 0.53 0.18
banios 0.71 0.66 0.53 1.00 0.52
habitaciones 0.30 0.41 0.18 0.52 1.00
p5 <- ggplot(base2, aes(x = areaconst, y = preciom, color = factor(estrato),
                         text = paste("Barrio:", barrio))) +
  geom_point(alpha = 0.5) +
  labs(title = "Precio vs. Área construida (coloreado por estrato)",
       x = "Área construida (m²)", y = "Precio (millones $)", color = "Estrato") +
  theme_minimal()
ggplotly(p5, tooltip = c("x", "y", "text", "colour"))
p6 <- ggplot(base2, aes(x = factor(estrato), y = preciom)) +
  geom_boxplot(fill = "#74a9cf") +
  labs(title = "Precio por estrato", x = "Estrato", y = "Precio (millones $)") +
  theme_minimal()
ggplotly(p6)
p7 <- ggplot(base2, aes(x = factor(habitaciones), y = preciom)) +
  geom_boxplot(fill = "#a1d99b") +
  labs(title = "Precio por número de habitaciones", x = "Habitaciones", y = "Precio (millones $)") +
  theme_minimal()
ggplotly(p7)
p8 <- ggplot(base2, aes(x = factor(banios), y = preciom)) +
  geom_boxplot(fill = "#fdae6b") +
  labs(title = "Precio por número de baños", x = "Baños", y = "Precio (millones $)") +
  theme_minimal()
ggplotly(p8)

Interpretación: en apartamentos de la Zona Sur, el área construida y el estrato vuelven a ser las variables más asociadas al precio. A diferencia de las casas, el número de baños muestra una relación positiva muy marcada, coherente con que en el segmento de apartamentos de mayor valor los baños adicionales suelen asociarse a acabados de lujo (walk-in closets, baños en suite, etc.).

Paso 3. Especificación, comparación y selección del modelo

modelo2 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = base2)
modelo2_log <- lm(log(preciom) ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = base2)
summary(modelo2)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = base2)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1092.02   -42.28    -1.33    40.58   926.56 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -261.62501   15.63220 -16.736  < 2e-16 ***
## areaconst       1.28505    0.05403  23.785  < 2e-16 ***
## estrato        60.89709    3.08408  19.746  < 2e-16 ***
## habitaciones  -24.83693    3.89229  -6.381 2.11e-10 ***
## parqueaderos   72.91468    3.95797  18.422  < 2e-16 ***
## banios         50.69675    3.39637  14.927  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 98.02 on 2375 degrees of freedom
## Multiple R-squared:  0.7485, Adjusted R-squared:  0.748 
## F-statistic:  1414 on 5 and 2375 DF,  p-value: < 2.2e-16
kable(tidy(modelo2, conf.int = TRUE), digits = 3, caption = "Coeficientes del modelo lineal (Apartamentos, Zona Sur)")
Coeficientes del modelo lineal (Apartamentos, Zona Sur)
term estimate std.error statistic p.value conf.low conf.high
(Intercept) -261.625 15.632 -16.736 0 -292.279 -230.971
areaconst 1.285 0.054 23.785 0 1.179 1.391
estrato 60.897 3.084 19.746 0 54.849 66.945
habitaciones -24.837 3.892 -6.381 0 -32.470 -17.204
parqueaderos 72.915 3.958 18.422 0 65.153 80.676
banios 50.697 3.396 14.927 0 44.037 57.357
kable(glance(modelo2)[, c("r.squared", "adj.r.squared", "sigma", "statistic", "p.value")],
      digits = 3, caption = "Bondad de ajuste - modelo lineal")
Bondad de ajuste - modelo lineal
r.squared adj.r.squared sigma statistic p.value
0.749 0.748 98.019 1413.802 0

Interpretación de los coeficientes (modelo lineal):

  • Área construida, estrato, parqueaderos y baños: los cuatro coeficientes son positivos y altamente significativos (p < 0.001), en línea con la lógica del mercado: apartamentos más grandes, en mejor estrato, con más parqueaderos y más baños tienen mayor precio.
  • Habitaciones: el coeficiente es negativo y significativo (p < 0.001). A primera vista puede parecer contraintuitivo, pero es un resultado lógico una vez controlado por área: para un apartamento del mismo tamaño total, tener más habitaciones implica que cada una es más pequeña, lo cual suele asociarse a un producto de menor categoría (apartamentos “compactos” con muchas alcobas pequeñas) frente a apartamentos de lujo con pocas habitaciones amplias.
kable(tidy(coeftest(modelo2, vcov = vcovHC(modelo2, type = "HC3"))), digits = 4,
      caption = "Coeficientes con errores estándar robustos (HC3) - modelo lineal, base2")
Coeficientes con errores estándar robustos (HC3) - modelo lineal, base2
term estimate std.error statistic p.value
(Intercept) -261.6250 23.3820 -11.1891 0.0000
areaconst 1.2850 0.3987 3.2230 0.0013
estrato 60.8971 3.9981 15.2314 0.0000
habitaciones -24.8369 6.9558 -3.5707 0.0004
parqueaderos 72.9147 15.1745 4.8051 0.0000
banios 50.6967 9.1456 5.5433 0.0000

A diferencia de base1, bajo errores robustos todas las variables se mantienen altamente significativas en base2, lo que sugiere que las conclusiones sobre significancia son más estables en este segmento, probablemente por el mayor tamaño muestral (n = 2381 vs. n = 435).

kable(tidy(modelo2_log, conf.int = TRUE), digits = 4, caption = "Coeficientes del modelo log-lineal (Apartamentos, Zona Sur)")
Coeficientes del modelo log-lineal (Apartamentos, Zona Sur)
term estimate std.error statistic p.value conf.low conf.high
(Intercept) 3.7841 0.0359 105.4917 0.000 3.7138 3.8544
areaconst 0.0025 0.0001 20.0743 0.000 0.0022 0.0027
estrato 0.2352 0.0071 33.2398 0.000 0.2214 0.2491
habitaciones -0.0187 0.0089 -2.0983 0.036 -0.0363 -0.0012
parqueaderos 0.1460 0.0091 16.0703 0.000 0.1281 0.1638
banios 0.1256 0.0078 16.1122 0.000 0.1103 0.1409
cat("R² modelo lineal:      ", round(summary(modelo2)$r.squared, 3), "\n")
## R² modelo lineal:       0.749
cat("R² modelo log-lineal:  ", round(summary(modelo2_log)$r.squared, 3), "\n")
## R² modelo log-lineal:   0.788

Ajuste general: el modelo lineal alcanza un R² ajustado de aproximadamente 0.75, notablemente superior al de las casas de la Zona Norte, lo que sugiere que en apartamentos las características estructurales explican mejor el precio (posiblemente porque los edificios son más homogéneos que las casas, donde factores idiosincrásicos del lote pesan más). El modelo log-lineal mejora aún más el ajuste dentro de muestra; se evalúa en el Paso 4 si esta mejora se traduce en mejor capacidad predictiva.

Paso 4. Validación de supuestos y evaluación predictiva

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

par(mfrow = c(1, 1))
shapiro_p2 <- shapiro.test(resid(modelo2))$p.value
bp_p2 <- bptest(modelo2)$p.value
dw2 <- dwtest(modelo2)
vif_vals2 <- vif(modelo2)
shapiro_p2_log <- shapiro.test(resid(modelo2_log))$p.value
bp_p2_log <- bptest(modelo2_log)$p.value

kable(data.frame(Prueba = c("Shapiro-Wilk (normalidad)", "Breusch-Pagan (homocedasticidad)",
                             "Durbin-Watson (independencia temporal/orden)"),
                  Lineal = c(format(shapiro_p2, digits = 3), format(bp_p2, digits = 3), round(dw2$p.value, 4)),
                  `Log-lineal` = c(format(shapiro_p2_log, digits = 3), format(bp_p2_log, digits = 3), "—")),
      caption = "Pruebas de supuestos - H0 = se cumple el supuesto")
Pruebas de supuestos - H0 = se cumple el supuesto
Prueba Lineal Log.lineal
Shapiro-Wilk (normalidad) 4.84e-48 4.23e-21
Breusch-Pagan (homocedasticidad) 6.91e-161 3.91e-147
Durbin-Watson (independencia temporal/orden) 0
kable(data.frame(Variable = names(vif_vals2), VIF = round(vif_vals2, 2)),
      caption = "Factores de inflación de varianza (VIF) - modelo lineal")
Factores de inflación de varianza (VIF) - modelo lineal
Variable VIF
areaconst areaconst 2.07
estrato estrato 1.55
habitaciones habitaciones 1.43
parqueaderos parqueaderos 1.74
banios banios 2.53
set.seed(2026)
idx2 <- sample(seq_len(nrow(base2)), floor(0.8 * nrow(base2)))
train2 <- base2[idx2, ]; test2 <- base2[-idx2, ]

m2_lin_tr <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = train2)
m2_log_tr <- lm(log(preciom) ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = train2)

pred2_lin <- predict(m2_lin_tr, newdata = test2)
smear2    <- mean(exp(resid(m2_log_tr)))
pred2_log <- exp(predict(m2_log_tr, newdata = test2)) * smear2

kable(data.frame(Modelo = c("Lineal", "Log-lineal (con corrección de smearing)"),
                  RMSE = c(rmse(test2$preciom, pred2_lin), rmse(test2$preciom, pred2_log)),
                  MAE  = c(mae(test2$preciom, pred2_lin), mae(test2$preciom, pred2_log))),
      digits = 1, caption = "Error de predicción fuera de muestra (millones $) - base2")
Error de predicción fuera de muestra (millones $) - base2
Modelo RMSE MAE
Lineal 93.1 59.9
Log-lineal (con corrección de smearing) 84.9 54.1

Interpretación integrada:

  • Normalidad y homocedasticidad: se rechazan en ambas especificaciones (p ≈ 0), con las mismas causas típicas de datos de precios (asimetría derecha, dispersión creciente con el nivel de precio).
  • Posible dependencia espacial: al comparar los estadísticos de Durbin-Watson de base1 y base2 (tablas anteriores), la desviación respecto a 2 es más marcada en apartamentos, lo que sugiere una estructura residual más fuerte, coherente con la idea de submercados dentro de la Zona Sur (p. ej., cercanía a Pance vs. sectores más consolidados) que las variables estructurales no capturan del todo. Al igual que en base1, no se dispone de una prueba espacial formal (I de Moran) en esta versión por limitaciones de instalación de spdep, pero es la limitación más relevante a validar en una siguiente iteración, junto con modelos espaciales (SAR/SEM, disponibles en spatialreg).
  • Multicolinealidad: VIF < 3 en todas las variables; sin problemas de colinealidad.
  • Decisión de modelo final: en este segmento, el modelo log-lineal mejora tanto el ajuste dentro de muestra como la predicción fuera de muestra (menor RMSE y MAE que el lineal), por lo que se adopta el modelo log-lineal (con corrección de smearing) como modelo final para la Solicitud 2. Este resultado contrasta con el de base1 y refuerza la importancia de evaluar cada segmento de forma independiente en lugar de asumir que una misma especificación es óptima para todo el mercado.

Paso 5. Predicción del precio para la solicitud 2

La solicitud pide un apartamento de 300 m², 3 parqueaderos, 3 baños, 5 habitaciones, estrato 5 o 6, en la Zona Sur. Se predice con el modelo final (log-lineal, corregido por smearing).

nueva_vivienda2 <- data.frame(areaconst = 300, estrato = c(5, 6),
                               habitaciones = 5, parqueaderos = 3, banios = 3)
pred2_log_ic <- predict(modelo2_log, newdata = nueva_vivienda2, interval = "confidence")
smear_final2 <- mean(exp(resid(modelo2_log)))
pred2 <- cbind(nueva_vivienda2,
               fit = exp(pred2_log_ic[, "fit"]) * smear_final2,
               lwr = exp(pred2_log_ic[, "lwr"]) * smear_final2,
               upr = exp(pred2_log_ic[, "upr"]) * smear_final2)
kable(pred2, digits = 1, caption = "Precio estimado para la vivienda solicitada (Solicitud 2, modelo log-lineal + smearing)")
Precio estimado para la vivienda solicitada (Solicitud 2, modelo log-lineal + smearing)
areaconst estrato habitaciones parqueaderos banios fit lwr upr
300 5 5 3 3 634.2 601.7 668.3
300 6 5 3 3 802.3 760.7 846.2

Interpretación: para estrato 5, el modelo estima un precio de mercado de aproximadamente $634 millones (IC 95% aproximado: 602–668), y para estrato 6, aproximadamente $802 millones (IC 95% aproximado: 761–846). El intervalo se obtiene exponenciando el intervalo en escala logarítmica y corrigiendo por el factor de smearing; es una aproximación razonable pero, al no ser una transformación lineal, no conserva exactamente el nivel de confianza nominal del 95%. Ambos escenarios están dentro del crédito pre-aprobado de $850 millones, con un margen considerable, lo que da flexibilidad para buscar opciones con mejores acabados o ubicación dentro de la Zona Sur.

Paso 6. Ofertas potenciales para la solicitud 2

ofertas2 <- base2 %>%
  filter(preciom <= 850, estrato %in% c(5, 6), areaconst >= 200, habitaciones >= 4) %>%
  arrange(desc(preciom)) %>%
  head(6)

kable(ofertas2 %>% select(barrio, preciom, areaconst, estrato, habitaciones, banios, parqueaderos),
      caption = "Ofertas potenciales dentro del presupuesto (Solicitud 2)")
Ofertas potenciales dentro del presupuesto (Solicitud 2)
barrio preciom areaconst estrato habitaciones banios parqueaderos
pance 750 200.00 6 4 5 3
guadalupe 730 573.00 5 5 8 3
el ingenio 700 250.00 6 5 4 2
el ingenio 690 486.00 5 4 4 2
seminario 670 300.00 5 6 5 3
quintas de don 660 224.41 6 4 5 2
plot_ly(ofertas2, lat = ~latitud, lon = ~longitud, type = "scattermapbox", mode = "markers",
        marker = list(size = 14, color = "#d7301f"),
        text = ~paste0("Barrio: ", barrio, "<br>Precio: $", preciom, "M",
                        "<br>Área: ", areaconst, "m²", "<br>Estrato: ", estrato)) %>%
  layout(mapbox = list(style = "open-street-map", zoom = 11.5,
                        center = list(lat = mean(ofertas2$latitud), lon = mean(ofertas2$longitud))),
         title = "Ofertas potenciales para la Solicitud 2 (≤ $850M)")

Discusión: se identifican ofertas en barrios como Pance, Guadalupe, El Ingenio, Seminario y Ciudadela Pasoancho, todas por debajo del presupuesto de $850 millones y con áreas entre 200 y 570 m². Al haber más margen presupuestal que en la Solicitud 1, esta familia tiene mayor variedad de opciones, incluyendo alternativas con áreas considerablemente superiores a los 300 m² solicitados.

Comparación de los dos modelos

comparacion <- data.frame(
  Solicitud = c("1: Casas, Zona Norte", "2: Apartamentos, Zona Sur"),
  n = c(nrow(base1), nrow(base2)),
  Modelo_final = c("Lineal", "Log-lineal + smearing"),
  R2_ajustado_ins = c(round(summary(modelo1)$adj.r.squared, 3), round(summary(modelo2_log)$adj.r.squared, 3)),
  RMSE_holdout = c(round(rmse(test1$preciom, pred_lin), 1), round(rmse(test2$preciom, pred2_log), 1)),
  Durbin_Watson = c(round(dw$statistic, 2), round(dw2$statistic, 2))
)
kable(comparacion, caption = "Comparación de los modelos finales seleccionados por solicitud")
Comparación de los modelos finales seleccionados por solicitud
Solicitud n Modelo_final R2_ajustado_ins RMSE_holdout Durbin_Watson
1: Casas, Zona Norte 435 Lineal 0.599 172.1 1.76
2: Apartamentos, Zona Sur 2381 Log-lineal + smearing 0.787 84.9 1.53

Las dos solicitudes requirieron especificaciones distintas: para casas en Zona Norte el modelo lineal generalizó mejor fuera de muestra a pesar de un R² menor, mientras que para apartamentos en Zona Sur la transformación logarítmica mejoró tanto el ajuste como la predicción. En ambos casos el estadístico de Durbin-Watson se aleja de 2, de forma más marcada en apartamentos, lo que sugiere posible dependencia espacial residual la principal limitación metodológica compartida y el punto de partida natural para una extensión futura del análisis con modelos de econometría espacial (idealmente confirmando esta hipótesis con una prueba formal como la I de Moran una vez se pueda instalar spdep).

Síntesis ejecutiva y recomendación final

Solicitud 1 (Casa, Zona Norte, crédito $350M): con el modelo lineal (seleccionado por su mejor desempeño predictivo fuera de muestra), el precio de mercado estimado es de ~$312M en estrato 4 (dentro de presupuesto) y ~$393M en estrato 5 (fuera de presupuesto). Se recomienda enfocar la búsqueda en estrato 4, donde se identificaron ofertas reales concretas (p. ej., en El Bosque y La Flora) dentro del crédito aprobado.

Solicitud 2 (Apartamento, Zona Sur, crédito $850M): con el modelo log-lineal corregido por smearing (seleccionado por mejor ajuste y desempeño predictivo), el precio de mercado estimado es de ~$634M (estrato 5) a ~$802M (estrato 6). El escenario de estrato 5 deja un margen amplio frente al presupuesto; el de estrato 6 sigue siendo viable, pero con un margen más ajustado (~$48M) que el que sugería una estimación lineal simple. Se identificaron ofertas reales dentro de presupuesto en barrios como Pance, El Ingenio y Ciudadela Pasoancho.

Limitaciones y trabajo futuro: ambos modelos dejan una fracción relevante de la variabilidad del precio sin explicar y muestran autocorrelación espacial en los residuos, lo que indica que el submercado geográfico (barrio, cercanía a vías o centros comerciales) importa más allá de lo que capturan las variables estructurales disponibles. Se recomienda a C&A, para una siguiente iteración del ejercicio, (i) incorporar variables adicionales como antigüedad, calidad de acabados o distancia a puntos de interés, y (ii) explorar modelos de regresión espacial (rezago o error espacial) que incorporen explícitamente la dependencia entre viviendas vecinas.