1 Informe ejecutivo

1.1 Contexto

María, gerente de C&A (Casas y Apartamentos), recibió una solicitud de una compañía internacional que busca ubicar a dos familias de empleados en Cali. Cada familia tiene requisitos de vivienda y un crédito hipotecario preaprobado distintos:

Característica Vivienda 1 Vivienda 2
Tipo Casa Apartamento
Área construida 200 m² 300 m²
Parqueaderos 1 3
Baños 2 3
Habitaciones 4 5
Estrato 4 o 5 5 o 6
Zona Norte Sur
Crédito preaprobado $350 millones $850 millones

Este informe usa un modelo estadístico de precios (regresión lineal múltiple), estimado de forma independiente para cada segmento de mercado relevante (casas en zona norte; apartamentos en zona sur) con datos de oferta inmobiliaria de los últimos tres meses, para responder dos preguntas concretas por cada familia: ¿cuánto vale en el mercado una vivienda con esas características? y ¿qué inmuebles reales disponibles hoy se ajustan a esas características y al presupuesto aprobado?

1.2 Recomendación — Vivienda 1 (Casa, Zona Norte, presupuesto $350 M)

Con base en 435 ofertas de casas en zona norte de los últimos tres meses, el modelo estima que una casa con las características solicitadas (200 m², 4 habitaciones, 2 baños, 1 parqueadero) tiene un valor de mercado de referencia de aproximadamente 393 millones de pesos (estrato 5; ver Anexo 2, sección “Vivienda 1 — Paso 5”, para el detalle por estrato e intervalos de predicción).

Esto está por encima del crédito preaprobado de $350 millones en aproximadamente 43 millones, lo que sugiere que una vivienda exactamente con esas características puede no ser alcanzable con el presupuesto actual. Con el modelo se identificaron 5 ofertas concretas, disponibles hoy y dentro del presupuesto, que más se aproximan a lo solicitado (ver mapa y tabla en el Anexo 2, sección Vivienda 1, Paso 6). Recomendamos a María presentar estas alternativas a la familia, manteniendo el estrato solicitado, ya que hay suficientes opciones dentro de presupuesto.

1.3 Recomendación — Vivienda 2 (Apartamento, Zona Sur, presupuesto $850 M)

Con base en 2381 ofertas de apartamentos en zona sur de los últimos tres meses, el modelo estima que un apartamento con las características solicitadas (300 m², 5 habitaciones, 3 baños, 3 parqueaderos) tiene un valor de mercado de referencia de aproximadamente 736 millones de pesos (estrato 6; ver Anexo 2, sección “Vivienda 2 — Paso 5”).

Esto está dentro del crédito preaprobado de $850 millones, por lo que una vivienda con esas características es alcanzable. Se identificaron 5 ofertas concretas dentro de presupuesto que se aproximan a lo solicitado (Anexo 2, sección Vivienda 2, Paso 6).

1.4 Advertencia sobre la calidad de los datos

Se detectaron 499 registros cuya ubicación geográfica está más cerca del centroide de otra zona que de la propia (ver Anexo 2, sección “Paso 1” de cada vivienda), lo que sugiere posibles errores de etiquetado de zona en la base de datos. Esto no invalida las conclusiones, pero es un punto a corregir en la calidad de los datos de origen.

1.5 Limitaciones del análisis

Los modelos se basan en precios de oferta (anuncios), no en precios de cierre de negociación, por lo que los valores reales de venta podrían ser distintos (típicamente algo menores, tras negociación). El tamaño de muestra de cada segmento (435 casas en zona norte, 2381 apartamentos en zona sur) acota la precisión de las estimaciones; el Anexo 2 detalla, para cada segmento, el ajuste del modelo (Paso 3) y las pruebas de validación de supuestos (Paso 4).


2 Anexo 1: Plan de trabajo (metodología)

Para responder la solicitud de las dos familias se sigue un plan de trabajo de seis pasos, aplicado de forma independiente a cada segmento de mercado relevante (casas en zona norte para la Vivienda 1; apartamentos en zona sur para la Vivienda 2, como réplica del mismo procedimiento):

  1. Filtro y depuración de la base. Se construye una subbase específica para cada solicitud (base1: casas de zona norte; base2: apartamentos de zona sur), y se verifica con tablas de frecuencia y una muestra de registros que el filtro se aplicó correctamente. Se añade un chequeo geográfico (distancia de cada inmueble al centroide de su zona vs. al de las demás) para detectar posibles inconsistencias de etiquetado en la variable zona.
  2. Análisis exploratorio de datos. Se examina, con gráficos interactivos (plotly), la relación entre el precio y sus determinantes (área construida, estrato, baños, habitaciones, zona) en el mercado general, para justificar la selección de variables del modelo y anticipar el signo esperado de sus coeficientes.
  3. Modelo de regresión lineal múltiple. Se elige este método porque la variable respuesta (precio) es continua y se dispone de varios predictores numéricos con relación aproximadamente lineal con el precio (según el paso 2); el modelo permite además interpretar directamente el efecto marginal de cada característica de la vivienda sobre su precio, que es justo lo que se necesita para asesorar a las familias. La zona y el tipo de vivienda no se incluyen como predictores porque, una vez filtrada la base a un segmento específico, ambas variables quedan constantes (sin variación) y por lo tanto no aportan información al modelo de ese segmento.
  4. Validación de supuestos. Se revisan los supuestos clásicos del modelo lineal (linealidad y homocedasticidad mediante el gráfico de residuos, normalidad mediante gráfico Q-Q y prueba de Shapiro-Wilk, independencia mediante Durbin-Watson, y multicolinealidad mediante los factores de inflación de varianza VIF), como respaldo de que las interpretaciones y predicciones del modelo son razonables.
  5. Estimación puntual. Se usa el modelo validado para predecir el precio esperado de una vivienda con las características exactas solicitadas por cada familia, junto con un intervalo de predicción del 95%.
  6. Búsqueda de ofertas y comparación. Se cruza la predicción del modelo con el crédito preaprobado de cada familia para identificar, dentro de las ofertas reales disponibles en la base, al menos cinco alternativas concretas que se ajusten al presupuesto y a las características solicitadas, y se comparan los dos modelos (uno por segmento) en términos de ajuste y cumplimiento de supuestos.

Este plan se aplica primero a la Vivienda 1 (Casa, zona norte) y luego se replica de forma idéntica para la Vivienda 2 (Apartamento, zona sur), como pide el enunciado.

3 Anexo 2: Resultados de la modelación, validación y comparación de modelos

Esta sección documenta, para cada solicitud, la ejecución completa del plan de trabajo del Anexo 1: filtrado y chequeo de la base de datos, análisis exploratorio, especificación y validación del modelo de regresión, predicción y búsqueda de ofertas.

3.1 Preparación de los datos

cat("Observaciones totales (con variables completas):", n_total, "\n")
## Observaciones totales (con variables completas): 6717
cat("Zonas disponibles:", paste(zonas_disponibles, collapse = ", "), "\n")
## Zonas disponibles: Zona Centro, Zona Norte, Zona Oeste, Zona Oriente, Zona Sur
cat("Tipos de vivienda disponibles:", paste(tipos_disponibles, collapse = ", "), "\n")
## Tipos de vivienda disponibles: Apartamento, Casa

3.2 Análisis exploratorio de datos (mercado general)

Antes de filtrar por segmento, se explora la relación entre el precio y sus principales determinantes en toda la base (todas las zonas y tipos), para tener contexto de mercado. Los gráficos son interactivos (pase el cursor sobre los puntos para ver el detalle de cada inmueble).

3.2.1 Matriz de correlación

vars_num <- c("preciom", "areaconst", "estrato", "banios", "habitaciones")
mat_cor <- cor(vivienda[, vars_num], use = "pairwise.complete.obs")

plot_ly(
  x = colnames(mat_cor), y = rownames(mat_cor), z = mat_cor,
  type = "heatmap", colors = colorRamp(c("#2166ac", "#f7f7f7", "#b2182b")),
  zmin = -1, zmax = 1
) %>%
  layout(title = "Correlación entre precio y sus determinantes")
cor_precio_area <- mat_cor["preciom", "areaconst"]
cor_precio_estrato <- mat_cor["preciom", "estrato"]
cor_precio_banios <- mat_cor["preciom", "banios"]
cor_precio_hab <- mat_cor["preciom", "habitaciones"]

El precio muestra una correlación de 0.68 con el área construida, 0.59 con el estrato, 0.67 con el número de baños y 0.27 con el número de habitaciones. Todas son positivas, en línea con lo esperado: inmuebles más grandes, de estratos más altos y con más baños/habitaciones tienden a costar más.

3.2.2 Precio vs. área construida, por zona

g1 <- ggplot(vivienda, aes(x = areaconst, y = preciom, color = zona,
                            text = paste0("Barrio: ", barrio, "<br>Tipo: ", tipo))) +
  geom_point(alpha = 0.5, size = 1.5) +
  labs(x = "Área construida (m²)", y = "Precio (millones COP)",
       title = "Precio vs. área construida, por zona", color = "Zona") +
  theme_minimal()

ggplotly(g1, tooltip = c("x", "y", "text", "colour"))

3.2.3 Precio por estrato

g2 <- ggplot(vivienda, aes(x = factor(estrato), y = preciom, fill = factor(estrato))) +
  geom_boxplot(show.legend = FALSE) +
  labs(x = "Estrato", y = "Precio (millones COP)", title = "Distribución del precio por estrato") +
  theme_minimal()

ggplotly(g2)

3.2.4 Precio por número de baños y de habitaciones

g3 <- ggplot(vivienda, aes(x = factor(banios), y = preciom, fill = factor(banios))) +
  geom_boxplot(show.legend = FALSE) +
  labs(x = "N° de baños", y = "Precio (millones COP)", title = "Precio por número de baños") +
  theme_minimal()

g4 <- ggplot(vivienda, aes(x = factor(habitaciones), y = preciom, fill = factor(habitaciones))) +
  geom_boxplot(show.legend = FALSE) +
  labs(x = "N° de habitaciones", y = "Precio (millones COP)", title = "Precio por número de habitaciones") +
  theme_minimal()

ggplotly(g3)
ggplotly(g4)

3.2.5 Precio por zona

g5 <- ggplot(vivienda, aes(x = fct_reorder(zona, preciom, median), y = preciom, fill = zona)) +
  geom_boxplot(show.legend = FALSE) +
  coord_flip() +
  labs(x = NULL, y = "Precio (millones COP)", title = "Precio por zona (ordenado por mediana)") +
  theme_minimal()

ggplotly(g5)

Interpretación general: el precio se asocia de forma clara y positiva con el área construida (la relación más fuerte), con el estrato socioeconómico y, en menor medida, con el número de baños y habitaciones (que están a su vez correlacionados entre sí y con el área, ya que inmuebles más grandes suelen tener más espacios). La variable zona marca diferencias claras de nivel de precios entre sectores de la ciudad, lo que justifica analizar cada zona por separado en el modelo predictivo, en lugar de mezclar zonas con niveles de precio estructuralmente distintos.


Las siguientes dos secciones (una por solicitud) siguen exactamente los pasos 1 a 6 del enunciado, ya filtrados al segmento tipo+zona relevante para cada familia.

3.3 Vivienda 1: Casas en Zona Norte

3.3.1 Paso 1 — Filtrado y chequeo de la base (base1)

head(res_v1$base, 3) %>%
  kableExtra::kbl(caption = "Primeros 3 registros de base1 (Casas, Zona Norte)") %>%
  kableExtra::kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
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.51 3.480
1592 Zona Norte 02 5 780 380 2 3 3 Casa acopi -76.52 3.487
4460 Zona Norte 02 4 625 355 3 5 5 Casa acopi -76.53 3.406
cat("Verificación del filtro -- valores únicos de tipo:", paste(names(res_v1$chequeo_tipo), collapse=", "), "\n")
## Verificación del filtro -- valores únicos de tipo: Casa
cat("Verificación del filtro -- valores únicos de zona:", paste(names(res_v1$chequeo_zona), collapse=", "), "\n")
## Verificación del filtro -- valores únicos de zona: Zona Norte
cat("Tamaño de la base filtrada (base1):", res_v1$n_base, "registros\n")
## Tamaño de la base filtrada (base1): 435 registros
res_v1$mapa

El chequeo geográfico (distancia de cada punto al centroide de su propia zona vs. al centroide más cercano de otra zona) identificó 94 de 435 inmuebles (en rojo en el mapa) cuya ubicación está objetivamente más cerca de otra zona que de zona norte – probablemente errores de captura en la etiqueta de zona, o inmuebles ubicados en el límite administrativo entre zonas.

3.3.2 Paso 3 — Modelo de regresión lineal múltiple

res_v1$modelo_tidy %>%
  kableExtra::kbl(digits = 3, caption = "Coeficientes del modelo -- Vivienda 1 (Casas, Zona Norte)") %>%
  kableExtra::kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Coeficientes del modelo – Vivienda 1 (Casas, Zona Norte)
term estimate std.error statistic p.value
(Intercept) -238.171 44.406 -5.364 0.000
areaconst 0.677 0.053 12.814 0.000
estrato 80.635 9.826 8.206 0.000
habitaciones 7.645 5.659 1.351 0.177
parqueaderos 24.006 5.869 4.090 0.000
banios 18.899 7.488 2.524 0.012
res_v1$modelo_glance %>% select(r.squared, adj.r.squared, sigma, statistic, p.value, df, nobs) %>%
  kableExtra::kbl(digits = 3, caption = "Bondad de ajuste -- Vivienda 1") %>%
  kableExtra::kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Bondad de ajuste – Vivienda 1
r.squared adj.r.squared sigma statistic p.value df nobs
0.604 0.599 155.1 130.9 0 5 435
r2_v1 <- res_v1$modelo_glance$r.squared

Interpretación de los coeficientes. Para cada variable con un p-valor inferior a 0.05, el coeficiente indica el cambio esperado en el precio (en millones de pesos) ante un aumento de una unidad en esa variable, manteniendo las demás constantes. Se espera, y sería lo lógico económicamente, que los coeficientes de área construida, estrato, baños, habitaciones y parqueaderos sean todos positivos (más espacio, mejor estrato o más comodidades deberían encarecer el inmueble); un coeficiente negativo y significativo en alguna de estas variables sería contraintuitivo y ameritaría revisión de los datos (posible multicolinealidad, o que esa variable esté capturando un efecto de otra correlacionada con ella).

Interpretación de R². El modelo explica 60.4% de la variabilidad del precio de las casas en zona norte. Este es un ajuste moderado: el modelo captura una parte importante de la variación de precios, pero queda una porción relevante sin explicar, probablemente asociada a atributos no observados (acabados, antigüedad, vista, estado de conservación, cercanía a vías o equipamientos). Para mejorar el ajuste se podría: (i) incorporar variables adicionales no disponibles en esta base (antigüedad, acabados, cercanía a vías principales o centros comerciales); (ii) usar variables de barrio como efecto fijo, dado que dentro de una misma zona el barrio puede aportar información de precio adicional; (iii) probar transformaciones (p. ej. log(preciom)) si la relación no es perfectamente lineal; o (iv) usar modelos no lineales (árboles, bosques aleatorios) si la relación entre las variables y el precio no es aditiva.

3.3.3 Paso 4 — Validación de supuestos

par(mfrow = c(1, 2))
plot(res_v1$modelo, which = 1)
plot(res_v1$modelo, which = 2)

par(mfrow = c(1, 1))
if (!is.null(res_v1$shapiro)) cat("Shapiro-Wilk (normalidad de residuos): W =", round(res_v1$shapiro$statistic,3),
                                    ", p-valor =", signif(res_v1$shapiro$p.value,3), "\n")
## Shapiro-Wilk (normalidad de residuos): W = 0.852 , p-valor = 0.0000000000000000000877
if (!is.null(res_v1$bp)) cat("Breusch-Pagan (homocedasticidad): BP =", round(res_v1$bp$statistic,3),
                               ", p-valor =", signif(res_v1$bp$p.value,3), "\n")
## Breusch-Pagan (homocedasticidad): BP = 80.28 , p-valor = 0.000000000000000733
if (!is.null(res_v1$dw)) cat("Durbin-Watson (independencia): DW =", round(res_v1$dw$statistic,3),
                               ", p-valor =", signif(res_v1$dw$p.value,3), "\n")
## Durbin-Watson (independencia): DW = 1.762 , p-valor = 0.00547
if (!is.null(res_v1$vif)) { cat("Factores de inflación de varianza (VIF):\n"); print(round(res_v1$vif,2)) }
## Factores de inflación de varianza (VIF):
##    areaconst      estrato habitaciones parqueaderos       banios 
##         1.46         1.31         1.72         1.23         1.97

Interpretación. El gráfico de residuos vs. valores ajustados permite evaluar linealidad y homocedasticidad (un patrón embudo indicaría varianza no constante); el gráfico Q-Q compara los residuos contra una distribución normal (desviaciones fuertes en las colas indican no normalidad). El test de Shapiro-Wilk formaliza la evaluación de normalidad (aquí se rechaza la normalidad de los residuos (p < 0.05), lo que sugiere que los intervalos de predicción deben interpretarse con cautela), el de Breusch-Pagan la homocedasticidad (aquí se rechaza la homocedasticidad (p < 0.05): la varianza del error no es constante, probablemente crece con el precio/tamaño del inmueble), y el VIF la multicolinealidad entre predictores (valores por encima de 5-10 señalan colinealidad problemática). El test de Durbin-Watson se reporta por completitud, pero su supuesto de independencia asume un orden natural de las observaciones (p. ej. temporal), que no aplica de forma directa a datos de corte transversal como estos; para datos inmobiliarios georreferenciados, un diagnóstico más pertinente sería la autocorrelación espacial de los residuos (p. ej. el índice de Moran), ya que inmuebles cercanos entre sí suelen compartir factores de valorización no observados (cercanía a vías, parques, seguridad del sector). No se realizan correcciones, tal como pide el enunciado; de presentarse violaciones, se sugeriría: para heterocedasticidad, usar errores estándar robustos (sandwich/lmtest) o transformar la variable respuesta (log(preciom)); para no normalidad, la misma transformación logarítmica suele ayudar; para multicolinealidad, retirar o combinar variables redundantes (p. ej. habitaciones y baños suelen estar correlacionadas entre sí).

3.3.4 Paso 5 — Predicción para la Vivienda 1

res_v1$pred_tabla %>%
  kableExtra::kbl(digits = 1, col.names = c("Estrato", "Precio estimado (M COP)", "Límite inferior (95%)", "Límite superior (95%)"),
                   caption = "Predicción de precio -- Casa de 200 m², 4 hab., 2 baños, 1 parqueadero") %>%
  kableExtra::kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Predicción de precio – Casa de 200 m², 4 hab., 2 baños, 1 parqueadero
Estrato Precio estimado (M COP) Límite inferior (95%) Límite superior (95%)
4 312.1 6.2 618.0
5 392.7 86.2 699.3

3.3.5 Paso 6 — Ofertas potenciales dentro de presupuesto ($350 M)

if (res_v1$n_candidatas == 0) {
  cat("No se encontró ninguna casa en zona norte, de ningún estrato, con precio listado <= $350M en los últimos tres meses.")
} else if (res_v1$estrato_relajado) {
  cat("Nota: al filtrar por estrato 4-5 y presupuesto <= $350M había menos de 5 opciones, por lo que se amplió la búsqueda a todos los estratos disponibles en casas de zona norte dentro de presupuesto.")
} else {
  cat("Búsqueda restringida a estrato 4-5 y presupuesto <= $350M.")
}
## Búsqueda restringida a estrato 4-5 y presupuesto <= $350M.
if (nrow(res_v1$ofertas) > 0) {
  res_v1$ofertas %>%
    select(barrio, estrato, preciom, areaconst, habitaciones, banios, parqueaderos, precio_predicho, valor) %>%
    kableExtra::kbl(digits = 1,
                     col.names = c("Barrio", "Estrato", "Precio (M)", "Área (m²)", "Hab.", "Baños",
                                   "Parq.", "Precio predicho (M)", "Valor (predicho - listado)"),
                     caption = "Ofertas recomendadas para la Vivienda 1") %>%
    kableExtra::kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
} else {
  cat("(No hay ofertas que mostrar dentro de este presupuesto.)")
}
Ofertas recomendadas para la Vivienda 1
Barrio Estrato Precio (M) Área (m²) Hab. Baños Parq. Precio predicho (M) Valor (predicho - listado)
alamos 4 275 120 4 2 1 258.0 -17.0
vipasa 5 350 346 4 2 1 491.5 141.5
la merced 5 350 216 4 2 2 427.6 77.6
zona norte 4 265 162 4 3 1 305.3 40.3
la flora 5 320 160 4 3 1 384.6 64.6
res_v1$mapa_ofertas

Discusión. Estas ofertas se seleccionaron dentro del presupuesto de $350 millones, priorizando primero la cercanía a las características solicitadas (área, habitaciones, baños, parqueaderos) y, en caso de empate, el mejor “valor” (precio predicho por el modelo por encima del precio realmente listado, es decir, inmuebles que el modelo considera baratos relativo a sus atributos). Recomendamos a María visitar en persona las opciones con mayor “valor” positivo, ya que son las que el modelo señala como potencialmente subvaloradas frente al mercado.

3.4 Vivienda 2: Apartamentos en Zona Sur

3.4.1 Paso 1 — Filtrado y chequeo de la base (base2)

head(res_v2$base, 3) %>%
  kableExtra::kbl(caption = "Primeros 3 registros de base2 (Apartamentos, Zona Sur)") %>%
  kableExtra::kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
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.53 3.450
698 Zona Sur 02 3 78 40 1 1 2 Apartamento aguablanca -76.50 3.400
8199 Zona Sur NA 6 875 194 2 5 3 Apartamento aguacatal -76.56 3.459
cat("Verificación del filtro -- valores únicos de tipo:", paste(names(res_v2$chequeo_tipo), collapse=", "), "\n")
## Verificación del filtro -- valores únicos de tipo: Apartamento
cat("Verificación del filtro -- valores únicos de zona:", paste(names(res_v2$chequeo_zona), collapse=", "), "\n")
## Verificación del filtro -- valores únicos de zona: Zona Sur
cat("Tamaño de la base filtrada (base2):", res_v2$n_base, "registros\n")
## Tamaño de la base filtrada (base2): 2381 registros
res_v2$mapa

El chequeo geográfico identificó 405 de 2381 inmuebles (en rojo en el mapa) cuya ubicación está más cerca de otra zona que de zona sur.

3.4.2 Paso 3 — Modelo de regresión lineal múltiple

res_v2$modelo_tidy %>%
  kableExtra::kbl(digits = 3, caption = "Coeficientes del modelo -- Vivienda 2 (Apartamentos, Zona Sur)") %>%
  kableExtra::kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Coeficientes del modelo – Vivienda 2 (Apartamentos, Zona Sur)
term estimate std.error statistic p.value
(Intercept) -261.625 15.632 -16.736 0
areaconst 1.285 0.054 23.785 0
estrato 60.897 3.084 19.746 0
habitaciones -24.837 3.892 -6.381 0
parqueaderos 72.915 3.958 18.422 0
banios 50.697 3.396 14.927 0
res_v2$modelo_glance %>% select(r.squared, adj.r.squared, sigma, statistic, p.value, df, nobs) %>%
  kableExtra::kbl(digits = 3, caption = "Bondad de ajuste -- Vivienda 2") %>%
  kableExtra::kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Bondad de ajuste – Vivienda 2
r.squared adj.r.squared sigma statistic p.value df nobs
0.749 0.748 98.02 1414 0 5 2381
r2_v2 <- res_v2$modelo_glance$r.squared

Interpretación de R². El modelo explica 74.9% de la variabilidad del precio de los apartamentos en zona sur. Ajuste alto para este tipo de datos. Las mismas estrategias de mejora sugeridas para la Vivienda 1 aplican aquí (variables adicionales, efecto fijo de barrio o de piso, transformación logarítmica, o modelos no lineales).

3.4.3 Paso 4 — Validación de supuestos

par(mfrow = c(1, 2))
plot(res_v2$modelo, which = 1)
plot(res_v2$modelo, which = 2)

par(mfrow = c(1, 1))
if (!is.null(res_v2$shapiro)) cat("Shapiro-Wilk (normalidad de residuos): W =", round(res_v2$shapiro$statistic,3),
                                    ", p-valor =", signif(res_v2$shapiro$p.value,3), "\n")
## Shapiro-Wilk (normalidad de residuos): W = 0.791 , p-valor = 0.00000000000000000000000000000000000000000000000484
if (!is.null(res_v2$bp)) cat("Breusch-Pagan (homocedasticidad): BP =", round(res_v2$bp$statistic,3),
                               ", p-valor =", signif(res_v2$bp$p.value,3), "\n")
## Breusch-Pagan (homocedasticidad): BP = 754.8 , p-valor = 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000691
if (!is.null(res_v2$dw)) cat("Durbin-Watson (independencia): DW =", round(res_v2$dw$statistic,3),
                               ", p-valor =", signif(res_v2$dw$p.value,3), "\n")
## Durbin-Watson (independencia): DW = 1.533 , p-valor = 0.00000000000000000000000000000173
if (!is.null(res_v2$vif)) { cat("Factores de inflación de varianza (VIF):\n"); print(round(res_v2$vif,2)) }
## Factores de inflación de varianza (VIF):
##    areaconst      estrato habitaciones parqueaderos       banios 
##         2.07         1.55         1.43         1.74         2.53

Igual que en la Vivienda 1, no se corrigen los posibles problemas detectados (por instrucción del enunciado), pero las mismas alternativas de remedio (errores robustos, transformación logarítmica, revisión de variables redundantes) aplican aquí.

3.4.4 Paso 5 — Predicción para la Vivienda 2

res_v2$pred_tabla %>%
  kableExtra::kbl(digits = 1, col.names = c("Estrato", "Precio estimado (M COP)", "Límite inferior (95%)", "Límite superior (95%)"),
                   caption = "Predicción de precio -- Apartamento de 300 m², 5 hab., 3 baños, 3 parqueaderos") %>%
  kableExtra::kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Predicción de precio – Apartamento de 300 m², 5 hab., 3 baños, 3 parqueaderos
Estrato Precio estimado (M COP) Límite inferior (95%) Límite superior (95%)
5 675.0 481.5 868.6
6 735.9 542.3 929.5

3.4.5 Paso 6 — Ofertas potenciales dentro de presupuesto ($850 M)

if (res_v2$n_candidatas == 0) {
  cat("No se encontró ningún apartamento en zona sur, de ningún estrato, con precio listado <= $850M en los últimos tres meses.")
} else if (res_v2$estrato_relajado) {
  cat("Nota: al filtrar por estrato 5-6 y presupuesto <= $850M había menos de 5 opciones, por lo que se amplió la búsqueda a todos los estratos disponibles en apartamentos de zona sur dentro de presupuesto.")
} else {
  cat("Búsqueda restringida a estrato 5-6 y presupuesto <= $850M.")
}
## Búsqueda restringida a estrato 5-6 y presupuesto <= $850M.
if (nrow(res_v2$ofertas) > 0) {
  res_v2$ofertas %>%
    select(barrio, estrato, preciom, areaconst, habitaciones, banios, parqueaderos, precio_predicho, valor) %>%
    kableExtra::kbl(digits = 1,
                     col.names = c("Barrio", "Estrato", "Precio (M)", "Área (m²)", "Hab.", "Baños",
                                   "Parq.", "Precio predicho (M)", "Valor (predicho - listado)"),
                     caption = "Ofertas recomendadas para la Vivienda 2") %>%
    kableExtra::kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
} else {
  cat("(No hay ofertas que mostrar dentro de este presupuesto.)")
}
Ofertas recomendadas para la Vivienda 2
Barrio Estrato Precio (M) Área (m²) Hab. Baños Parq. Precio predicho (M) Valor (predicho - listado)
capri 5 350 270 4 3 3 661.3 311.3
ciudad jardín 6 550 130 4 3 3 542.3 -7.7
pampa linda 5 450 267 3 3 3 682.3 232.3
capri 5 350 260 3 3 3 673.3 323.3
San Fernando 5 350 258 5 4 2 598.8 248.8
res_v2$mapa_ofertas

Discusión. Estas ofertas se seleccionaron dentro del presupuesto de $850 millones, priorizando la cercanía a las características solicitadas y, en caso de empate, el mejor “valor” frente al precio predicho por el modelo.

3.5 Comparación de los dos modelos

comparacion <- tibble(
  Segmento = c(res_v1$etiqueta, res_v2$etiqueta),
  `N observaciones` = c(res_v1$n_base, res_v2$n_base),
  `R2` = c(res_v1$modelo_glance$r.squared, res_v2$modelo_glance$r.squared),
  `R2 ajustado` = c(res_v1$modelo_glance$adj.r.squared, res_v2$modelo_glance$adj.r.squared),
  `Error estándar residual` = c(res_v1$modelo_glance$sigma, res_v2$modelo_glance$sigma),
  `Normalidad residuos (p Shapiro)` = c(
    if (!is.null(res_v1$shapiro)) signif(res_v1$shapiro$p.value, 3) else NA,
    if (!is.null(res_v2$shapiro)) signif(res_v2$shapiro$p.value, 3) else NA
  ),
  `Homocedasticidad (p Breusch-Pagan)` = c(
    if (!is.null(res_v1$bp)) signif(res_v1$bp$p.value, 3) else NA,
    if (!is.null(res_v2$bp)) signif(res_v2$bp$p.value, 3) else NA
  )
)

comparacion %>%
  kableExtra::kbl(digits = 3, caption = "Comparación de los modelos de los dos segmentos") %>%
  kableExtra::kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Comparación de los modelos de los dos segmentos
Segmento N observaciones R2 R2 ajustado Error estándar residual Normalidad residuos (p Shapiro) Homocedasticidad (p Breusch-Pagan)
Vivienda 1 (Casa, Zona Norte) 435 0.604 0.599 155.12 0 0
Vivienda 2 (Apartamento, Zona Sur) 2381 0.749 0.748 98.02 0 0

Los dos modelos se estiman de forma independiente porque corresponden a segmentos de mercado distintos (tipo de vivienda y zona), cada uno con su propia dinámica de precios; no son directamente “el mismo modelo aplicado dos veces” sino dos ajustes específicos a su segmento, lo cual es preferible a forzar un único modelo global cuando, como se vio en el análisis exploratorio, el precio varía sustancialmente entre zonas y tipos de vivienda.

4 Referencias

Kutner, M. H., Nachtsheim, C. J., Neter, J., & Li, W. (2005). Applied Linear Statistical Models (5th ed.). McGraw-Hill/Irwin.

Wooldridge, J. M. (2019). Introductory Econometrics: A Modern Approach (7th ed.). Cengage Learning.

Fox, J., & Weisberg, S. (2019). An R Companion to Applied Regression (3rd ed.). Sage Publications. (Paquete car.)

5 Anexo: información de sesión (reproducibilidad)

sessionInfo()
## R version 4.5.1 (2025-06-13)
## Platform: aarch64-apple-darwin20
## Running under: macOS Tahoe 26.5.2
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRblas.0.dylib 
## LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## time zone: America/Bogota
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] remotes_2.5.0      RColorBrewer_1.1-3 scales_1.4.0       kableExtra_1.4.0  
##  [5] lmtest_0.9-40      zoo_1.8-15         car_3.1-5          carData_3.0-6     
##  [9] broom_1.0.13       leaflet_2.2.3      plotly_4.12.1      lubridate_1.9.5   
## [13] forcats_1.0.1      stringr_1.6.0      dplyr_1.2.1        purrr_1.2.2       
## [17] readr_2.2.0        tidyr_1.3.2        tibble_3.3.1       ggplot2_4.0.3     
## [21] tidyverse_2.0.0   
## 
## loaded via a namespace (and not attached):
##  [1] gtable_0.3.6            xfun_0.60               bslib_0.9.0            
##  [4] htmlwidgets_1.6.4       lattice_0.22-7          tzdb_0.5.0             
##  [7] leaflet.providers_3.0.0 vctrs_0.7.3             tools_4.5.1            
## [10] crosstalk_1.2.2         generics_0.1.4          pkgconfig_2.0.3        
## [13] data.table_1.18.2.1     S7_0.2.2                lifecycle_1.0.5        
## [16] compiler_4.5.1          farver_2.1.2            textshaping_1.0.4      
## [19] htmltools_0.5.9         sass_0.4.10             yaml_2.3.12            
## [22] Formula_1.2-5           pillar_1.11.1           jquerylib_0.1.4        
## [25] cachem_1.1.0            abind_1.4-8             tidyselect_1.2.1       
## [28] digest_0.6.39           stringi_1.8.9           labeling_0.4.3         
## [31] fastmap_1.2.0           grid_4.5.1              cli_3.6.6              
## [34] magrittr_2.0.5          withr_3.0.3             backports_1.5.1        
## [37] timechange_0.4.0        rmarkdown_2.30          httr_1.4.8             
## [40] otel_0.2.0              hms_1.1.4               evaluate_1.0.5         
## [43] knitr_1.51              viridisLite_0.4.3       rlang_1.3.0            
## [46] glue_1.8.1              xml2_1.5.1              svglite_2.2.2          
## [49] rstudioapi_0.19.0       jsonlite_2.0.0          R6_2.6.1               
## [52] systemfonts_1.3.1