Contexto

María fundó C&A hace unos años en Cali luego de trabajar como agente de bienes raíces en Cali y Bogotá. Hoy la compañía recibió una solicitud de una empresa internacional que necesita ubicar a dos empleados y sus familias en la ciudad. La solicitud trae condiciones puntuales para dos viviendas y un crédito preaprobado independiente para cada una:

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 o 5 5 o 6
Zona Norte Sur
Crédito preaprobado $350 millones $850 millones

Este informe resuelve la solicitud en dos partes: un informe ejecutivo con la recomendación para María, y dos anexos técnicos donde se documenta la metodología y los resultados de la modelación estadística que sustentan esa recomendación segun las indicaciones de la actividad. Toda la evidencia se construye sobre la base de ofertas de vivienda de los últimos tres meses en Cali, disponible en el paquete paqueteMODELOS.


Informe ejecutivo

Resumen de la solicitud

Para responder a la empresa se estimó, para cada vivienda, un modelo de regresión lineal múltiple sobre el segmento de mercado que corresponde exactamente al tipo de inmueble y la zona solicitados (casas en zona norte para la vivienda 1; apartamentos en zona sur para la vivienda 2). Con cada modelo se calculó el precio esperado de una vivienda con las características pedidas, y luego se buscaron ofertas reales del mercado que se ajustan a esas características y al presupuesto disponible.

Vivienda 1 — Casa, zona norte, crédito de $350 millones

Con base en 435 ofertas de casas en zona norte con información completa, el modelo explica el 60.4 % de la variación del precio (R² ajustado 59.9 %) a partir del área construida, el estrato, el número de habitaciones, parqueaderos y baños. Todas las variables tienen el signo esperado; el número de habitaciones es la única que no resulta estadísticamente significativa una vez se controla por área construida.

Para una casa de 200 m², 4 habitaciones, 1 parqueadero y 2 baños, el modelo predice:

  • Estrato 4: precio esperado de $312 millones (intervalo de confianza al 95 %: $287–$337 millones).
  • Estrato 5: precio esperado de $393 millones (intervalo de confianza al 95 %: $361–$425 millones).

Recomendación: el presupuesto de $350 millones es suficiente para una casa de estas características en estrato 4, con un margen razonable frente al precio esperado. En estrato 5 el precio esperado ya supera el crédito preaprobado, así que solo sería viable si aparece una oferta puntual por debajo del promedio de su segmento, o si la empresa está dispuesta a negociar un área ligeramente menor o financiación adicional. En el Anexo 2 se presentan 8 ofertas reales del mercado que cumplen el presupuesto y las condiciones solicitadas, concentradas en los barrios El Bosque, La Flora y La Merced.

Vivienda 2 — Apartamento, zona sur, crédito de $850 millones

Con base en 2,381 ofertas de apartamentos en zona sur, el modelo explica el 74.9 % de la variación del precio (R² ajustado 74.8 %), un ajuste notablemente mejor que el de la vivienda 1, y todas las variables son altamente significativas.

Para un apartamento de 300 m², 5 habitaciones, 3 parqueaderos y 3 baños, el modelo predice:

  • Estrato 5: precio esperado de $675 millones (intervalo de confianza al 95 %: $652–$698 millones).
  • Estrato 6: precio esperado de $736 millones (intervalo de confianza al 95 %: $713–$759 millones).

Recomendación: el crédito de $850 millones cubre holgadamente el precio esperado en ambos estratos, dejando entre $115 y $175 millones de margen. Este margen es importante porque un apartamento de 300 m² es un inmueble grande para la zona sur (por encima del percentil 90 del área construida en ese segmento), por lo que la oferta disponible con exactamente esas características es limitada; el margen de presupuesto permite a la empresa considerar alternativas ligeramente distintas en área o ubicación sin salirse del crédito aprobado. El Anexo 2 presenta 8 ofertas reales dentro de presupuesto, entre ellas inmuebles en Guadalupe, El Ingenio y Ciudadela Pasoancho.

Consideraciones de mercado y limitaciones del análisis

  • Las ventas de vivienda en Cali han disminuido durante este año debido a la coyuntura política y social. Sin embargo, las entidades de crédito hipotecario siguen ofreciendo diferentes alternativas de financiación. Esta situación puede resultar favorable para un comprador que ya cuenta con un crédito preaprobado, como es el caso de la empresa solicitante, ya que actualmente existe una mayor disponibilidad de inventario y los vendedores podrían tener una mayor disposición para negociar el precio.

  • El modelo presenta un mejor ajuste para los apartamentos ubicados en la zona sur, con un R² de 74.9 %, frente al modelo de casas en la zona norte, que alcanza un R² de 60.4 %. En términos prácticos, esto quiere decir que la estimación del precio para la vivienda 2 tiene un mayor nivel de confiabilidad que la estimación realizada para la vivienda 1. En el Anexo 2 se presenta con mayor detalle la razón de esta diferencia y se identifican las variables adicionales que podrían ayudar a mejorar el modelo utilizado para las casas.

  • Los dos modelos presentan heterocedasticidad, es decir, la variabilidad de los errores aumenta a medida que aumenta el precio de la vivienda. También se observan colas pesadas en los residuales, un comportamiento que es común en este tipo de modelos de precios de vivienda. Estas características no hacen que las estimaciones puntuales pierdan validez, pero sí deben tenerse en cuenta al interpretar los intervalos de predicción individuales, ya que estos son más amplios que los intervalos asociados a la media y pueden ser especialmente amplios en los inmuebles de mayor precio. El análisis detallado de estos resultados se encuentra en el Anexo 2.

  • Al revisar la ubicación geográfica de las ofertas, se encontró que algunos registros que están etiquetados dentro de una determinada zona se encuentran, de acuerdo con sus coordenadas, más cerca del centroide correspondiente a otra zona. Esta situación se analiza con mayor detalle en el Anexo 2 y tiene un mayor impacto en la vivienda 1, correspondiente a las casas de la zona norte, que en la vivienda 2.


Anexo 1 — Plan de trabajo segun las indicaciones dadas en los pasos requeridos para la obtención de los resultados

Para responder la solicitud de la empresa se siguió la misma secuencia de trabajo para cada una de las dos viviendas:

  1. Filtro y depuración. Se filtran las ofertas de la base vivienda por tipo de inmueble y zona (casas en zona norte para la vivienda 1; apartamentos en zona sur para la vivienda 2), y se eliminan los registros con valores faltantes en las variables que entran al modelo. Se verifica la consulta con las primeras observaciones, conteos por variable, y un mapa de los puntos filtrados sobre el conjunto completo de la ciudad para detectar posibles inconsistencias en la zona declarada.
  2. Análisis exploratorio. Se calculan correlaciones entre el precio y el área construida, el estrato, los baños, las habitaciones y la zona (esta última a nivel de todo el mercado, dado que dentro de cada base filtrada la zona ya es constante), acompañadas de gráficos interactivos en plotly.
  3. Modelo de regresión lineal múltiple. Se estima precio ~ área construida + estrato + habitaciones + parqueaderos + baños sobre cada base filtrada, y se interpretan los coeficientes, su significancia y el R².
  4. Validación de supuestos. Se revisan linealidad, normalidad de los residuales, homocedasticidad y multicolinealidad (VIF), y se compara contra una especificación alternativa (modelo log-lineal) para evaluar si el ajuste mejora.
  5. Predicción puntual. Se calcula el precio esperado para las características exactas solicitadas por la empresa, con su intervalo de confianza.
  6. Búsqueda de ofertas. Se filtran ofertas reales dentro del presupuesto aprobado y con características similares a las solicitadas, y se ubican en un mapa interactivo.

Los pasos 1 a 6 se aplican primero a la vivienda 1 y luego se replican, en la misma secuencia, para la vivienda 2, cambiando únicamente el filtro de tipo/zona y el presupuesto disponible.


Anexo 2 — Resultados de la modelación

Carga de datos

# Instalación del paquete que contiene la base de ofertas de vivienda
# (ejecutar solo la primera vez / si el paquete no está instalado)
if (!requireNamespace("paqueteMODELOS", quietly = TRUE)) {
  if (!requireNamespace("devtools", quietly = TRUE)) install.packages("devtools")
  devtools::install_github("centromagis/paqueteMODELOS", force = TRUE)
}
library(paqueteMODELOS)
data("vivienda")

vars_modelo <- c("preciom", "areaconst", "estrato", "banios", "habitaciones", "parqueaderos")

kable(head(vivienda, 3), caption = "Primeros registros de la base completa `vivienda`")
Primeros registros de la base completa vivienda
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
1147 Zona Oriente NA 3 250 70 1 3 6 Casa 20 de julio -76.51168 3.43382
1169 Zona Oriente NA 3 320 120 1 2 3 Casa 20 de julio -76.51237 3.43369
1350 Zona Oriente NA 3 350 220 2 2 4 Casa 20 de julio -76.51537 3.43566

La base completa tiene 8322 ofertas y 13 variables. Antes de cualquier filtro, se revisan los valores faltantes por columna:

kable(t(colSums(is.na(vivienda))), caption = "Valores faltantes por variable, base completa")
Valores faltantes por variable, base completa
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
3 3 2638 3 2 3 1605 3 3 3 3 3 3

Casi todas las variables tienen apenas 2 o 3 registros incompletos, probablemente errores puntuales de captura, salvo piso y parqueaderos, con muchos mas vacios que se revisan dentro de cada base filtrada.

Vivienda 1: Casa — Zona Norte (crédito $350 millones)

Filtro y depuración

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

kable(head(base1_bruta, 3), caption = "Primeros 3 registros de base1 (Casa, Zona Norte)")
Primeros 3 registros de base1 (Casa, 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
4057 Zona Norte 02 6 750 445 NA 7 6 Casa acopi -76.52950 3.38527

base1 reúne 722 ofertas de casas en zona norte. Antes de depurar, los faltantes en esta base son:

kable(t(colSums(is.na(base1_bruta))), caption = "Valores faltantes en base1 antes de depurar")
Valores faltantes en base1 antes de depurar
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
0 0 372 0 0 0 287 0 0 0 0 0 0

El vacío en piso es esperable: piso identifica el piso de un apartamento y no aplica a una casa, por lo que no se depura, adicional, no es una variable de relevancia dentro de la solicitud inicial. El vacío en parqueaderos (287 registros, 39.8 % de la base) sí afecta al modelo y se depura eliminando esos registros:

base1 <- base1_bruta %>% filter(if_all(all_of(vars_modelo), ~ !is.na(.)))
cat("Registros después de depurar (722-287):", nrow(base1))
## Registros después de depurar (722-287): 435

Verificación de la consulta. Como comprobación, todas las observaciones de base1 deben tener tipo == "Casa" y zona == "Zona Norte":

kable(table(base1$tipo, base1$zona), caption = "Comprobación cruzada tipo x zona en base1")
Comprobación cruzada tipo x zona en base1
Zona Norte
Casa 435

Mapa de los puntos. Se ubican geográficamente las 722 casas de zona norte sobre el mapa de coordenadas de toda la ciudad, para ver si en efecto se agrupan en el norte:

p_mapa1 <- plot_ly() %>%
  add_trace(data = vivienda %>% filter(!is.na(longitud)),
            x = ~longitud, y = ~latitud, type = "scattergl", mode = "markers",
            marker = list(size = 4, color = "lightgrey"),
            name = "Resto del mercado", hoverinfo = "skip") %>%
  add_trace(data = base1_bruta %>% filter(!is.na(longitud)),
            x = ~longitud, y = ~latitud, type = "scattergl", mode = "markers",
            marker = list(size = 6, color = "firebrick"),
            name = "Casas · Zona Norte",
            text = ~paste0("Barrio: ", barrio, "<br>Estrato: ", estrato, "<br>Precio: $", preciom, "M"),
            hoverinfo = "text") %>%
  layout(title = "Casas etiquetadas como Zona Norte sobre el conjunto de la ciudad",
         xaxis = list(title = "Longitud"), yaxis = list(title = "Latitud"))
p_mapa1

Discusión. Los puntos rojos se concentran en el sector norte, pero no todos: hay observaciones rojas dispersas hacia el sur y el centro del mapa. Para cuantificarlo, se calculó el centroide geográfico de cada zona declarada y se comparó, para cada casa de zona norte, cuál es la zona cuyo centroide le queda geográficamente más cerca:

centroides <- vivienda %>% filter(!is.na(longitud)) %>%
  group_by(zona) %>% summarise(lon_c = mean(longitud), lat_c = mean(latitud), .groups = "drop")

zona_mas_cercana <- function(lon, lat) {
  d <- sqrt((lon - centroides$lon_c)^2 + (lat - centroides$lat_c)^2)
  centroides$zona[which.min(d)]
}

base1_geo <- base1_bruta %>% filter(!is.na(longitud)) %>%
  rowwise() %>% mutate(zona_cercana = zona_mas_cercana(longitud, latitud)) %>% ungroup()

tab_geo1 <- table(declarada = base1_geo$zona, mas_cercana = base1_geo$zona_cercana)
kable(tab_geo1, caption = "Zona declarada vs. zona geográficamente más cercana (casas zona norte)")
Zona declarada vs. zona geográficamente más cercana (casas zona norte)
Zona Centro Zona Norte Zona Oeste Zona Oriente Zona Sur
Zona Norte 55 515 37 30 85
pct_mismatch1 <- round(mean(base1_geo$zona_cercana != "Zona Norte") * 100, 1)

Un 28.7% de las casas que están etiquetadas como “Zona Norte” se encuentran, según sus coordenadas, más cerca del centroide de otra zona, principalmente de la zona sur y la zona oeste. Sin embargo, esto no quiere decir necesariamente que la zona haya sido registrada de manera incorrecta. Las zonas comerciales que maneja una inmobiliaria no tienen por qué ser círculos definidos alrededor de un punto central, sino que corresponden a polígonos irregulares que pueden seguir vías, barrios o límites administrativos. Por esta razón, una vivienda ubicada cerca del límite de la Zona Norte puede estar geográficamente más cerca del centroide de una zona vecina y, aun así, pertenecer correctamente a la Zona Norte.

No obstante, el porcentaje encontrado es lo suficientemente alto como para considerar también otras posibles causas, como una imprecisión en la georreferenciación de las viviendas, especialmente si algunas coordenadas fueron capturadas manualmente, o la existencia de fronteras poco claras entre zonas en determinados barrios. Esto es importante para el análisis porque el precio de la vivienda depende en buena medida de la zona en la que se encuentra, como se evidenció en la sección exploratoria. Por lo tanto, una clasificación incorrecta de la zona podría afectar la comparación de precios y generar sesgos al seleccionar inmuebles comparables. Por esta razón, se recomienda que María valide la zona de los barrios que presentan una mayor distancia respecto a su centroide antes de utilizarlos como comparables directos.

Análisis exploratorio de datos

Antes de fijar la zona, conviene ver cómo se comporta el precio por zona y tipo en todo el mercado, para justificar por qué la zona importa como filtro:

resumen_mercado <- vivienda %>% filter(!is.na(preciom)) %>%
  group_by(zona, tipo) %>% summarise(mediana_precio = median(preciom), n = n(), .groups = "drop")

plot_ly(resumen_mercado, x = ~zona, y = ~mediana_precio, color = ~tipo, type = "bar",
        text = ~paste0("n = ", n), hoverinfo = "text+y") %>%
  layout(title = "Precio mediano por zona y tipo de vivienda (mercado completo)",
         xaxis = list(title = ""), yaxis = list(title = "Precio mediano (millones $)"),
         barmode = "group")

Las casas cuestan sistemáticamente más que los apartamentos en una misma zona, lo cual puede explicarse porque normalmente tienen una mayor área y más espacios o servicios propios. Además, el precio varía bastante según la zona: la zona norte tiene un precio mediano de casas notablemente menor que la zona oeste, mientras que la zona sur, donde se concentra la mayoría del mercado, presenta precios medianos altos tanto para casas como para apartamentos. Esto confirma que la zona es una variable relevante para determinar el precio y justifica analizar cada solicitud dentro de su propio segmento de mercado, en lugar de utilizar un único modelo para toda la ciudad.

Dentro de base1 (ya fija la zona), se examina la correlación del precio con las variables numéricas:

corr1 <- round(cor(base1[, vars_modelo]), 2)
plot_ly(x = colnames(corr1), y = colnames(corr1), z = corr1, type = "heatmap",
        colors = colorRamp(c("steelblue", "white", "firebrick")), zmin = -1, zmax = 1) %>%
  layout(title = "Matriz de correlación — Casas, Zona Norte")
plot_ly(base1, x = ~areaconst, y = ~preciom, color = ~factor(estrato),
        type = "scatter", mode = "markers",
        text = ~paste0("Barrio: ", barrio, "<br>Baños: ", banios, "<br>Habitaciones: ", habitaciones),
        hoverinfo = "text+x+y") %>%
  layout(title = "Precio vs. área construida por estrato — Casas, Zona Norte",
         xaxis = list(title = "Área construida (m²)"), yaxis = list(title = "Precio (millones $)"))

El área construida es la variable con mayor correlación con el precio (0.69), seguida del estrato (0.53) y el número de baños (0.51). El número de habitaciones tiene la correlación más baja con el precio (0.37) a pesar de estar moderadamente correlacionado con el área (0.42); Esto permite anticipar que, al controlar por el área construida en el modelo múltiple, el número de habitaciones podría perder parte de su capacidad explicativa, ya que dos casas con la misma área pero diferente número de habitaciones pueden simplemente distribuir el espacio de manera distinta, sin que esto genere un cambio importante en su valor comercial. En el gráfico de dispersión también se observa claramente el efecto del estrato: para una misma área construida, las casas de estrato 5 (y algunas de estrato 6, aunque son pocas en la base) se ubican sistemáticamente por encima de las de estrato 3 y 4.

Modelo de regresión lineal múltiple

m1 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = base1)
summary(m1)
## 
## 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

Interpretación de los coeficientes

  • Área construida: cada m² adicional aumenta el precio en $0.677 millones (p < 0.001). Es lógico: es el determinante físico más directo del tamaño y costo de construcción de una casa.
  • Estrato: subir un estrato aumenta el precio en $80.6 millones (p < 0.001), el segundo efecto más fuerte del modelo. Tiene sentido porque el estrato resume infraestructura, ubicación y valorización del sector, variables que el área por sí sola no captura.
  • Habitaciones: el coeficiente ($7.6 millones por habitación adicional) no es significativo (p = 0.177). Como se anticipó en el análisis exploratorio, una vez se controla por área construida, agregar habitaciones sin agregar área simplemente implica cuartos más pequeños, lo que el mercado no premia con un mayor precio.
  • Parqueaderos: cada parqueadero adicional suma $24 millones (p < 0.001). Es razonable: en zona norte el parqueadero es un atributo escaso y valorado, sobre todo en casas de estratos altos.
  • Baños: cada baño adicional suma $18.9 millones (p = 0.012). Es coherente: más baños suele asociarse a un mejor acabado y a una casa de mayor categoría, más allá del área.

Ajuste del modelo (R²). El modelo explica el 60.4% de la variación del precio de las casas en zona norte (R² ajustado 59.9%). Es un ajuste razonable pero deja una parte considerable del precio sin explicar. Esto es esperable en un mercado de casas, donde factores que no están en la base, el barrio específico, el estado de la construcción, los acabados interiores, si tiene o no zonas verdes o piscina, la antigüedad del inmueble, pesan bastante y aquí no se miden. Para mejorar el ajuste se podría: (i) incorporar el barrio como variable categórica (hay bastante variación de precio entre barrios de la misma zona), (ii) usar la ubicación exacta (longitud/latitud) en vez de la zona agregada, y (iii) recoger variables de calidad de acabados o antigüedad que hoy no están en la base.

Validación de supuestos

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

par(mfrow = c(1,1))
  • Linealidad: el gráfico de residuales contra valores ajustados no muestra una curvatura sistemática marcada, aunque la dispersión de los residuales crece para precios altos.
  • Normalidad: el gráfico Q-Q muestra colas más pesadas que una normal, sobre todo a la derecha (precios altos sobreestimados por debajo y subestimados por arriba). La prueba formal lo confirma:
shapiro.test(residuals(m1))
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(m1)
## W = 0.85246, p-value < 2.2e-16

Con un p-valor prácticamente nulo se rechaza la normalidad de los residuales. En muestras grandes esta prueba es muy sensible a desviaciones pequeñas, así que el gráfico Q-Q es más informativo que el p-valor por sí solo: el problema real está concentrado en observaciones de precio alto, no en el grueso de los datos.

  • Homocedasticidad:
bptest(m1)
## 
##  studentized Breusch-Pagan test
## 
## data:  m1
## BP = 80.281, df = 5, p-value = 7.33e-16

El p-valor es prácticamente cero: se rechaza la homocedasticidad. Se confirma lo que ya se veía en el gráfico de residuales: el error del modelo crece con el precio, algo típico en variables de precio, que suelen tener una distribución sesgada a la derecha.

  • Multicolinealidad:
vif(m1)
##    areaconst      estrato habitaciones parqueaderos       banios 
##     1.460998     1.307757     1.721015     1.226334     1.967421

Todos los factores de inflación de varianza están por debajo de 2, muy lejos del umbral habitual de preocupación (5 o 10), así que la multicolinealidad no es un problema en este modelo.

Qué se podría hacer: dado que el problema principal es la heterocedasticidad, es decir, que la variabilidad de los errores aumenta a medida que aumenta el precio, y la presencia de colas pesadas, que indica que existen algunos errores extremos más alejados de lo habitual, una alternativa natural es modelar el logaritmo del precio en lugar del precio en niveles. Esta transformación suele ayudar a estabilizar la variabilidad y reducir el efecto de los valores extremos en este tipo de variables. Se prueba a continuación como modelo de comparación, sin reemplazar el modelo principal, que se mantiene en niveles porque las predicciones del informe ejecutivo, expresadas en millones de pesos, son más directas de comunicar a la empresa.

Modelo alternativo (comparación)

m1_log <- lm(log(preciom) ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = base1)
summary(m1_log)
## 
## Call:
## lm(formula = log(preciom) ~ areaconst + estrato + habitaciones + 
##     parqueaderos + banios, data = base1)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1.18034 -0.16258 -0.01715  0.14973  1.06925 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  4.415e+00  7.418e-02  59.516  < 2e-16 ***
## areaconst    1.137e-03  8.822e-05  12.889  < 2e-16 ***
## estrato      2.152e-01  1.641e-02  13.113  < 2e-16 ***
## habitaciones 1.646e-02  9.453e-03   1.742  0.08229 .  
## parqueaderos 4.704e-02  9.804e-03   4.798 2.22e-06 ***
## banios       4.607e-02  1.251e-02   3.683  0.00026 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2591 on 429 degrees of freedom
## Multiple R-squared:  0.6974, Adjusted R-squared:  0.6939 
## F-statistic: 197.7 on 5 and 429 DF,  p-value: < 2.2e-16
comparacion1 <- data.frame(
  Modelo = c("Lineal (preciom)", "Log-lineal (log(preciom))"),
  R2 = c(round(summary(m1)$r.squared,3), round(summary(m1_log)$r.squared,3)),
  R2_ajustado = c(round(summary(m1)$adj.r.squared,3), round(summary(m1_log)$adj.r.squared,3))
)
kable(comparacion1, caption = "Comparación de modelos — Vivienda 1")
Comparación de modelos — Vivienda 1
Modelo R2 R2_ajustado
Lineal (preciom) 0.604 0.599
Log-lineal (log(preciom)) 0.697 0.694

El modelo log-lineal sube el R² de 0.604 a 0.697, una mejora real en el ajuste (el resultado de los dos modelos no es directamente comparable porque cambia la escala de la variable respuesta). En el modelo log-lineal los coeficientes se leen como efectos porcentuales: por ejemplo, cada m² adicional aumenta el precio esperado en cerca de 0.11%, y cada estrato adicional en cerca de 21.5%. Esta especificación es una buena candidata para reemplazar el modelo en niveles en una siguiente iteración, aunque para efectos de este informe se conserva el modelo en niveles por su interpretación directa en millones de pesos frente a un crédito preaprobado también en pesos.

Predicción puntual

nueva1 <- data.frame(areaconst = 200, estrato = c(4,5), habitaciones = 4, parqueaderos = 1, banios = 2)
pred1 <- predict(m1, nueva1, interval = "confidence")
kable(cbind(nueva1, round(pred1,1)), caption = "Precio esperado — Vivienda 1 (200 m², 4 hab., 1 parq., 2 baños)")
Precio esperado — Vivienda 1 (200 m², 4 hab., 1 parq., 2 baños)
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

Para la casa solicitada, el precio esperado es de $312 millones en estrato 4 y $393 millones en estrato 5 (intervalos de confianza al 95% para el precio medio del segmento). Frente al crédito preaprobado de $350 millones, estrato 4 queda cómodamente dentro de presupuesto y estrato 5 lo supera.

Ofertas potenciales

ofertas1 <- base1 %>%
  filter(preciom <= 350, estrato %in% c(4,5), areaconst >= 150, habitaciones >= 3, banios >= 2) %>%
  arrange(desc(preciom)) %>%
  select(barrio, estrato, preciom, areaconst, parqueaderos, banios, habitaciones, longitud, latitud)

datatable(ofertas1 %>% select(-longitud, -latitud) %>% head(8),
          caption = "Ofertas potenciales — Casa, Zona Norte, presupuesto $350M",
          options = list(dom = 't'), rownames = FALSE)
top1 <- head(ofertas1, 8)
plot_ly(top1, x = ~longitud, y = ~latitud, type = "scatter", mode = "markers",
        marker = list(size = ~areaconst/15, color = ~preciom, colorscale = "Reds", showscale = TRUE),
        text = ~paste0(barrio, "<br>$", preciom, "M · ", areaconst, " m²<br>Estrato ", estrato,
                        " · ", habitaciones, " hab · ", banios, " baños"),
        hoverinfo = "text") %>%
  layout(title = "Ofertas potenciales para la Vivienda 1 (tamaño = área, color = precio)",
         xaxis = list(title = "Longitud"), yaxis = list(title = "Latitud"))

Se encontraron 53 ofertas que cumplen simultáneamente el presupuesto, el estrato solicitado y áreas y baños razonablemente cercanos a lo pedido. Las 8 más relevantes están concentradas en El Bosque, La Flora y La Merced, todos sectores dentro de zona norte, con precios entre $335 y $350 millones y áreas de 160 a 300 m². Estas opciones dejan poco margen de negociación frente al presupuesto, por lo que se recomienda a María priorizar las de menor precio dentro del grupo como primer punto de partida para la negociación.


Vivienda 2: Apartamento — Zona Sur (crédito $850 millones)

Se repite la misma secuencia de seis pasos, ahora para apartamentos en zona sur.

Filtro y depuración

base2_bruta <- vivienda %>% filter(tipo == "Apartamento", zona == "Zona Sur")
kable(head(base2_bruta, 3), caption = "Primeros 3 registros de base2 (Apartamento, Zona Sur)")
Primeros 3 registros de base2 (Apartamento, 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(t(colSums(is.na(base2_bruta))), caption = "Valores faltantes en base2 antes de depurar")
Valores faltantes en base2 antes de depurar
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
0 0 622 0 0 0 406 0 0 0 0 0 0

base2 reúne 2787 ofertas. Igual que antes, piso no se depura, y sí se depuran los 406 registros sin parqueaderos:

base2 <- base2_bruta %>% filter(if_all(all_of(vars_modelo), ~ !is.na(.)))
cat("Registros después de depurar:", nrow(base2))
## Registros después de depurar: 2381
kable(table(base2$tipo, base2$zona), caption = "Comprobación cruzada tipo x zona en base2")
Comprobación cruzada tipo x zona en base2
Zona Sur
Apartamento 2381

Mapa de los puntos:

plot_ly() %>%
  add_trace(data = vivienda %>% filter(!is.na(longitud)),
            x = ~longitud, y = ~latitud, type = "scattergl", mode = "markers",
            marker = list(size = 4, color = "lightgrey"),
            name = "Resto del mercado", hoverinfo = "skip") %>%
  add_trace(data = base2_bruta %>% filter(!is.na(longitud)),
            x = ~longitud, y = ~latitud, type = "scattergl", mode = "markers",
            marker = list(size = 6, color = "darkgreen"),
            name = "Apartamentos · Zona Sur",
            text = ~paste0("Barrio: ", barrio, "<br>Estrato: ", estrato, "<br>Precio: $", preciom, "M"),
            hoverinfo = "text") %>%
  layout(title = "Apartamentos etiquetados como Zona Sur sobre el conjunto de la ciudad",
         xaxis = list(title = "Longitud"), yaxis = list(title = "Latitud"))
base2_geo <- base2_bruta %>% filter(!is.na(longitud)) %>%
  rowwise() %>% mutate(zona_cercana = zona_mas_cercana(longitud, latitud)) %>% ungroup()

tab_geo2 <- table(declarada = base2_geo$zona, mas_cercana = base2_geo$zona_cercana)
kable(tab_geo2, caption = "Zona declarada vs. zona geográficamente más cercana (apartamentos zona sur)")
Zona declarada vs. zona geográficamente más cercana (apartamentos zona sur)
Zona Centro Zona Norte Zona Oeste Zona Oriente Zona Sur
Zona Sur 188 75 135 95 2294
pct_mismatch2 <- round(mean(base2_geo$zona_cercana != "Zona Sur") * 100, 1)

Aquí el desajuste es menor: 17.7% de los apartamentos etiquetados como zona sur quedan geográficamente más cerca de otro centroide, frente al 28.7% de la vivienda 1. Zona sur es, además, la zona con más inventario del mercado (2787 apartamentos), lo que puede explicar un polígono más consolidado y con fronteras más claras que zona norte. Aun así, se recomienda el mismo cuidado: no descartar del todo estos puntos, pero sí tratarlos con precaución si aparecen entre las comparables finales.

Análisis exploratorio de datos

corr2 <- round(cor(base2[, vars_modelo]), 2)
plot_ly(x = colnames(corr2), y = colnames(corr2), z = corr2, type = "heatmap",
        colors = colorRamp(c("steelblue", "white", "firebrick")), zmin = -1, zmax = 1) %>%
  layout(title = "Matriz de correlación — Apartamentos, Zona Sur")
plot_ly(base2, x = ~areaconst, y = ~preciom, color = ~factor(estrato),
        type = "scatter", mode = "markers",
        text = ~paste0("Barrio: ", barrio, "<br>Baños: ", banios, "<br>Habitaciones: ", habitaciones),
        hoverinfo = "text+x+y") %>%
  layout(title = "Precio vs. área construida por estrato — Apartamentos, Zona Sur",
         xaxis = list(title = "Área construida (m²)"), yaxis = list(title = "Precio (millones $)"))

En apartamentos las correlaciones con el precio son más altas que en casas: área construida (0.74), baños (0.71), parqueaderos (0.69) y estrato (0.65) están todas por encima de 0.6. El número de habitaciones vuelve a ser la más débil (0.3); en apartamentos esto es todavía más marcado que en casas, porque en un mismo edificio el área del apartamento está más estandarizada por el diseño arquitectónico, y añadir una habitación casi siempre implica subdividir el espacio existente en vez de ampliarlo.

Modelo de regresión lineal múltiple

m2 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = base2)
summary(m2)
## 
## 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

Interpretación de los coeficientes:

  • Área construida: $1.285 millones por m² adicional (p < 0.001), casi el doble del efecto por m² que en casas de zona norte. Es razonable: en zona sur, con más demanda y precios base más altos, cada metro cuadrado adicional de apartamento se paga más caro.
  • Estrato: $60.9 millones por estrato adicional (p < 0.001), efecto algo menor que en casas, consistente con que en apartamentos el edificio y sus amenidades ya capturan buena parte del efecto de categoría que en una casa recae más directamente sobre el estrato del sector.
  • Habitaciones: -24.8 millones por habitación adicional, y esta vez el coeficiente es significativo pero negativo (p < 0.001). A primera vista parece ilógico, pero es coherente con lo visto en la correlación: para un área construida fija, más habitaciones significa cuartos más pequeños y una distribución menos eficiente, lo que el mercado de apartamentos de zona sur penaliza en vez de premiar. Es el mismo fenómeno que en casas, pero aquí sí alcanza a ser estadísticamente significativo por el mayor tamaño de esta base.
  • Parqueaderos: $72.9 millones por parqueadero adicional (p < 0.001), el segundo efecto más fuerte del modelo. En zona sur, con más edificios de apartamentos, el parqueadero es un bien escaso y muy valorado.
  • Baños: $50.7 millones por baño adicional (p < 0.001), efecto casi tan fuerte como el de parqueaderos, y coherente con acabados de mayor categoría.

Ajuste del modelo. El R² es 74.9% (ajustado 74.8%), muy superior al de la vivienda 1. Con una base casi 6 veces más grande y variables de mayor peso relativo, el modelo de apartamentos en zona sur captura mucho mejor el precio. Aun así, queda cerca de una cuarta parte de la variación sin explicar, atribuible a las mismas variables no observadas (barrio específico, antigüedad, acabados, vista, amenidades del edificio) que limitan al modelo de la vivienda 1.

Validación de supuestos

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

par(mfrow = c(1,1))
shapiro.test(residuals(m2))
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(m2)
## W = 0.79118, p-value < 2.2e-16
bptest(m2)
## 
##  studentized Breusch-Pagan test
## 
## data:  m2
## BP = 754.81, df = 5, p-value < 2.2e-16
vif(m2)
##    areaconst      estrato habitaciones parqueaderos       banios 
##     2.066518     1.545162     1.429280     1.737878     2.529494

El patrón es similar al de la vivienda 1, aunque más marcado debido al mayor tamaño de la muestra: se rechaza la normalidad y la homocedasticidad, con p-valores prácticamente nulos en ambas pruebas. Los residuales presentan colas pesadas y una varianza que aumenta en los precios más altos, algo que también se puede observar en los gráficos Q-Q y de residuales vs. ajustados. La multicolinealidad tampoco representa un problema en este modelo, ya que todos los VIF se encuentran por debajo de 3. Al trabajar con una muestra grande, estas pruebas son muy sensibles, por lo que incluso pequeñas desviaciones de los supuestos ideales pueden resultar significativas. Para el negocio, lo más importante es que, al igual que en la vivienda 1, el error de predicción aumenta con el precio del inmueble, por lo que las predicciones son más confiables para apartamentos de precio medio que para los más costosos del segmento.

Modelo alternativo (comparación)

m2_log <- lm(log(preciom) ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = base2)

comparacion2 <- data.frame(
  Modelo = c("Lineal (preciom)", "Log-lineal (log(preciom))"),
  R2 = c(round(summary(m2)$r.squared,3), round(summary(m2_log)$r.squared,3)),
  R2_ajustado = c(round(summary(m2)$adj.r.squared,3), round(summary(m2_log)$adj.r.squared,3))
)
kable(comparacion2, caption = "Comparación de modelos — Vivienda 2")
Comparación de modelos — Vivienda 2
Modelo R2 R2_ajustado
Lineal (preciom) 0.749 0.748
Log-lineal (log(preciom)) 0.788 0.787

Igual que en la vivienda 1, el modelo log-lineal mejora el ajuste (R² de 0.749 a 0.788), reforzando la recomendación de explorar esta especificación en una siguiente iteración del modelo, sin que cambie la conclusión de negocio de esta sección.

Predicción puntual

nueva2 <- data.frame(areaconst = 300, estrato = c(5,6), habitaciones = 5, parqueaderos = 3, banios = 3)
pred2 <- predict(m2, nueva2, interval = "confidence")
kable(cbind(nueva2, round(pred2,1)), caption = "Precio esperado — Vivienda 2 (300 m², 5 hab., 3 parq., 3 baños)")
Precio esperado — Vivienda 2 (300 m², 5 hab., 3 parq., 3 baños)
areaconst estrato habitaciones parqueaderos banios fit lwr upr
300 5 5 3 3 675.0 652.1 697.9
300 6 5 3 3 735.9 712.7 759.1

Para el apartamento solicitado, el precio esperado es de $675 millones en estrato 5 y $736 millones en estrato 6. Frente al crédito preaprobado de $850 millones, ambos escenarios quedan dentro de presupuesto, con un margen de $115 a $175 millones.

Ofertas potenciales

ofertas2 <- base2 %>%
  filter(preciom <= 850, estrato %in% c(5,6), areaconst >= 200, habitaciones >= 4,
         banios >= 3, parqueaderos >= 2) %>%
  arrange(desc(preciom)) %>%
  select(barrio, estrato, preciom, areaconst, parqueaderos, banios, habitaciones, longitud, latitud)

datatable(ofertas2 %>% select(-longitud, -latitud) %>% head(8),
          caption = "Ofertas potenciales — Apartamento, Zona Sur, presupuesto $850M",
          options = list(dom = 't'), rownames = FALSE)
top2 <- head(ofertas2, 8)
plot_ly(top2, x = ~longitud, y = ~latitud, type = "scatter", mode = "markers",
        marker = list(size = ~areaconst/25, color = ~preciom, colorscale = "Greens", showscale = TRUE),
        text = ~paste0(barrio, "<br>$", preciom, "M · ", areaconst, " m²<br>Estrato ", estrato,
                        " · ", habitaciones, " hab · ", banios, " baños"),
        hoverinfo = "text") %>%
  layout(title = "Ofertas potenciales para la Vivienda 2 (tamaño = área, color = precio)",
         xaxis = list(title = "Longitud"), yaxis = list(title = "Latitud"))

Se encontraron 23 ofertas que cumplen presupuesto, estrato y áreas/baños/parqueaderos cercanos a lo solicitado. A diferencia de la vivienda 1, aquí el presupuesto sí deja margen: los precios de las 8 ofertas destacadas van desde $650 hasta $750 millones, todos por debajo del tope de $850 millones, en barrios como Guadalupe, El Ingenio, Pance y Ciudadela Pasoancho. Se recomienda a María priorizar las de mayor área dentro de este grupo, ya que el presupuesto lo permite y el área construida es la variable que más explica el precio en este segmento.


Conclusiones generales

  1. Con el presupuesto y las características solicitadas, la vivienda 1 (casa, zona norte) es viable en estrato 4 con margen ajustado, y solo viable en estrato 5 con una oferta puntual por debajo del precio esperado del segmento o una renegociación del crédito.
  2. La vivienda 2 (apartamento, zona sur) es viable en ambos estratos solicitados, con margen amplio frente al crédito preaprobado.
  3. El modelo de apartamentos en zona sur es más confiable (R² de 74.9%) que el de casas en zona norte (R² de 60.4%); esto debe comunicarse a la empresa como parte de la asesoría, especialmente si se usa la predicción de la vivienda 1 como referencia de negociación.
  4. En ambos casos, la variable que menos ayuda a explicar el precio es el número de habitaciones, y en apartamentos incluso resulta negativa: más habitaciones sin más área no es un atributo que el mercado premie. Este hallazgo es útil para orientar a los compradores hacia áreas amplias con una distribución eficiente, más que hacia la cantidad de cuartos.
  5. Antes de cerrar cualquier negociación, se recomienda validar en campo la zona real de las ofertas cuya ubicación geográfica no coincide con su zona declarada, sobre todo en el segmento de casas de zona norte.