Introducción

María fundó C&A hace algunos años en Cali. Recientemente recibió una solicitud de una compañía internacional que necesita ubicar a dos empleados (con sus familias) en dos viviendas con características y presupuestos distintos. Este informe usa técnicas de modelación estadística (regresión lineal múltiple) para estimar el precio esperado de cada vivienda solicitada y recomendar ofertas concretas del mercado que se ajusten al crédito preaprobado de cada caso.

# Si es la primera vez que corres esto en tu máquina, descomenta estas líneas:
# install.packages("devtools")
# devtools::install_github("centromagis/paqueteMODELOS", force = TRUE)
# install.packages(c("dplyr","ggplot2","plotly","leaflet","car","lmtest","knitr","DT"))

library(paqueteMODELOS)
library(dplyr)
library(ggplot2)
library(plotly)
library(leaflet)
library(car)      # para VIF (multicolinealidad)
library(lmtest)   # para bptest (homocedasticidad) y dwtest (independencia)
library(knitr)
library(DT)

data("vivienda")
glimpse(vivienda)
## Rows: 8,322
## Columns: 13
## $ id           <dbl> 1147, 1169, 1350, 5992, 1212, 1724, 2326, 4386, 1209, 159…
## $ zona         <chr> "Zona Oriente", "Zona Oriente", "Zona Oriente", "Zona Sur…
## $ piso         <chr> NA, NA, NA, "02", "01", "01", "01", "01", "02", "02", "02…
## $ estrato      <dbl> 3, 3, 3, 4, 5, 5, 4, 5, 5, 5, 6, 4, 5, 6, 4, 5, 5, 4, 5, …
## $ preciom      <dbl> 250, 320, 350, 400, 260, 240, 220, 310, 320, 780, 750, 62…
## $ areaconst    <dbl> 70, 120, 220, 280, 90, 87, 52, 137, 150, 380, 445, 355, 2…
## $ parqueaderos <dbl> 1, 1, 2, 3, 1, 1, 2, 2, 2, 2, NA, 3, 2, 2, 1, 4, 2, 2, 2,…
## $ banios       <dbl> 3, 2, 2, 5, 2, 3, 2, 3, 4, 3, 7, 5, 6, 2, 4, 4, 4, 3, 2, …
## $ habitaciones <dbl> 6, 3, 4, 3, 3, 3, 3, 4, 6, 3, 6, 5, 6, 2, 5, 5, 4, 3, 3, …
## $ tipo         <chr> "Casa", "Casa", "Casa", "Casa", "Apartamento", "Apartamen…
## $ barrio       <chr> "20 de julio", "20 de julio", "20 de julio", "3 de julio"…
## $ longitud     <dbl> -76.51168, -76.51237, -76.51537, -76.54000, -76.51350, -7…
## $ latitud      <dbl> 3.43382, 3.43369, 3.43566, 3.43500, 3.45891, 3.36971, 3.4…
kable(head(vivienda, 5))
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
5992 Zona Sur 02 4 400 280 3 5 3 Casa 3 de julio -76.54000 3.43500
1212 Zona Norte 01 5 260 90 1 2 3 Apartamento acopi -76.51350 3.45891

Depuración inicial de la base de datos

Antes de proceder con los filtros por zona como en todo proyecto de analitca procedemos primeramente a hacer una revisión de la calidad de los datos buscando posibles valores faltantes, nulos, duplicados y valores inconsistentes en las diferentes categorias como por ejemplo precios negativos o en 0, coordenadas fuera de la ciudad de Cali etc.

# 1. Dimensión original y estructura de variables categóricas
dim(vivienda)
## [1] 8322   13
sapply(vivienda[c("zona","tipo","piso","estrato")], table, useNA = "ifany")
## $zona
## 
##  Zona Centro   Zona Norte   Zona Oeste Zona Oriente     Zona Sur         <NA> 
##          124         1920         1198          351         4726            3 
## 
## $tipo
## 
## Apartamento        Casa        <NA> 
##        5100        3219           3 
## 
## $piso
## 
##   01   02   03   04   05   06   07   08   09   10   11   12 <NA> 
##  860 1450 1097  607  567  245  204  211  146  130   84   83 2638 
## 
## $estrato
## 
##    3    4    5    6 <NA> 
## 1453 2129 2750 1987    3
# 2. Valores faltantes por columna
colSums(is.na(vivienda))
##           id         zona         piso      estrato      preciom    areaconst 
##            3            3         2638            3            2            3 
## parqueaderos       banios habitaciones         tipo       barrio     longitud 
##         1605            3            3            3            3            3 
##      latitud 
##            3
# 3. Registros duplicados exactos
sum(duplicated(vivienda))
## [1] 1
# 4. Valores imposibles o fuera de rango en variables numéricas
resumen_num <- summary(vivienda[c("preciom","areaconst","parqueaderos",
                                   "banios","habitaciones")])
resumen_num
##     preciom         areaconst       parqueaderos        banios      
##  Min.   :  58.0   Min.   :  30.0   Min.   : 1.000   Min.   : 0.000  
##  1st Qu.: 220.0   1st Qu.:  80.0   1st Qu.: 1.000   1st Qu.: 2.000  
##  Median : 330.0   Median : 123.0   Median : 2.000   Median : 3.000  
##  Mean   : 433.9   Mean   : 174.9   Mean   : 1.835   Mean   : 3.111  
##  3rd Qu.: 540.0   3rd Qu.: 229.0   3rd Qu.: 2.000   3rd Qu.: 4.000  
##  Max.   :1999.0   Max.   :1745.0   Max.   :10.000   Max.   :10.000  
##  NAs    :2        NAs    :3        NAs    :1605     NAs    :3       
##   habitaciones   
##  Min.   : 0.000  
##  1st Qu.: 3.000  
##  Median : 3.000  
##  Mean   : 3.605  
##  3rd Qu.: 4.000  
##  Max.   :10.000  
##  NAs    :3
# Casos sospechosos: precio o área en cero o negativos
sospechosos <- vivienda %>%
  filter(preciom <= 0 | areaconst <= 0 | habitaciones <= 0 | banios <= 0)
nrow(sospechosos)
## [1] 76
kable(head(sospechosos, 5))
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
243 Zona Norte NA 3 190 435 NA 0 0 Casa acopi -76.49032 3.43856
2013 Zona Norte NA 3 270 330 NA 3 0 Casa acopi -76.51884 3.45032
2014 Zona Norte NA 3 270 330 NA 3 0 Casa acopi -76.51884 3.45032
2741 Zona Norte NA 4 485 320 NA 4 0 Casa acopi -76.52199 3.39466
3273 Zona Norte NA 3 400 324 NA 0 0 Casa acopi -76.52545 3.44268
# 5. Coordenadas fuera del rango aproximado de Cali
# (lat ~ 3.3 a 3.5 ; lon ~ -76.6 a -76.4; ajusta el rango si tu base usa otro formato)
coord_fuera <- vivienda %>%
  filter(latitud < 3.0 | latitud > 3.7 | longitud < -76.8 | longitud > -76.2)
nrow(coord_fuera)
## [1] 0
# 6. Construcción de la base depurada: se excluyen NA en variables clave,
#    duplicados y los valores imposibles identificados arriba
vivienda_limpia <- vivienda %>%
  distinct() %>%
  filter(!is.na(preciom), !is.na(areaconst), !is.na(estrato),
         !is.na(banios), !is.na(habitaciones), !is.na(zona), !is.na(tipo),
         preciom > 0, areaconst > 0, habitaciones > 0, banios > 0)

dim(vivienda_limpia)
## [1] 8243   13
comparacion_filas <- data.frame(
  base = c("vivienda (original)", "vivienda_limpia (depurada)"),
  n_filas = c(nrow(vivienda), nrow(vivienda_limpia)),
  filas_eliminadas = c(0, nrow(vivienda) - nrow(vivienda_limpia)),
  porcentaje_eliminado = c(0, round((nrow(vivienda) - nrow(vivienda_limpia)) / nrow(vivienda) * 100, 2))
)

kable(comparacion_filas)
base n_filas filas_eliminadas porcentaje_eliminado
vivienda (original) 8322 0 0.00
vivienda_limpia (depurada) 8243 79 0.95

Resultados: Como resultado de esta depuración se retiraron 3 registros con datos nulos, y 76 que probablemente son errores de digitación o inmuebles indicadores necesarios para el analisis final.

Tambien noté que la altura de piso ya estaban ordenados, puede pasar que en colombia los pisos usen una nomenclatura diferente (Ej. piso 08 vs piso 810). Respecto a datos fuera del rango de la ciudad de Cali no econtré ninguno

El nuevo dataset limpio tiene solo tuvo un 1% de datos afectados lo cual para nuestro analisis no tiene un gran impacto, seguiremos trabajando con este dataset de vivienda_limpia

A partir de aquí, todos los filtros de zona/tipo (base1, base2) se hacen sobre vivienda_limpia, no sobre vivienda cruda.


PARTE A — Vivienda 1 (Casa, Zona Norte, crédito 350 millones)

1. Filtro de la base de datos (base1)

base1 <- vivienda_limpia %>%
  filter(grepl("norte", zona, ignore.case = TRUE),
         grepl("casa", tipo, ignore.case = TRUE))

# Primeros 3 registros
kable(head(base1, 3))
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
# Tabla de comprobación: conteo por zona y tipo dentro de base1
table(base1$zona, base1$tipo)
##             
##              Casa
##   Zona Norte  700
# Dimensiones de la base filtrada
dim(base1)
## [1] 700  13

Mapa de comprobación geográfica

leaflet(base1) %>%
  addTiles() %>%
  addCircleMarkers(lng = ~longitud, lat = ~latitud,
                    radius = 4, color = "darkgreen",
                    popup = ~paste0("Barrio: ", barrio,
                                     "<br>Precio: ", preciom, " millones",
                                     "<br>Área: ", areaconst, " m2"))

Discusión: Este mapa nos confirma de una manera visual que no hay outliers en otras zonas, ya con esto seguimos con el EDA.

2. Análisis exploratorio (EDA)

comenzamos el EDA realizando una matrix de correlacion solo con los datos numericos para determinar primeramente si existen coorelaciones fuertes entre estas variables, seguidamente un grafico scatter plot y box para graficar estas posibles correlaciones e identificarlas mas facilmente por registro.

# Matriz de correlación (solo variables numéricas relevantes)
num_vars <- base1 %>% select(preciom, areaconst, estrato, banios, habitaciones)
cor_matrix <- cor(num_vars, use = "complete.obs")
kable(round(cor_matrix, 2))
preciom areaconst estrato banios habitaciones
preciom 1.00 0.73 0.62 0.57 0.37
areaconst 0.73 1.00 0.47 0.51 0.45
estrato 0.62 0.47 1.00 0.43 0.10
banios 0.57 0.51 0.43 1.00 0.61
habitaciones 0.37 0.45 0.10 0.61 1.00
# Precio vs área construida
p1 <- plot_ly(base1, x = ~areaconst, y = ~preciom, type = "scatter",
              mode = "markers", color = ~factor(estrato),
              text = ~paste("Barrio:", barrio)) %>%
  layout(title = "Precio vs Área construida (color = estrato)",
         xaxis = list(title = "Área construida (m2)"),
         yaxis = list(title = "Precio (millones $)"))
p1
# Precio vs número de habitaciones
p2 <- plot_ly(base1, x = ~factor(habitaciones), y = ~preciom, type = "box") %>%
  layout(title = "Precio según número de habitaciones",
         xaxis = list(title = "Habitaciones"),
         yaxis = list(title = "Precio (millones $)"))
p2
# Precio vs número de baños
p3 <- plot_ly(base1, x = ~factor(banios), y = ~preciom, type = "box") %>%
  layout(title = "Precio según número de baños",
         xaxis = list(title = "Baños"),
         yaxis = list(title = "Precio (millones $)"))
p3

Interpretación:

El precio de las casas en zona norte correlaciona más fuertemente con el área construida (0.73) y el estrato (0.62), de forma moderada con el número de baños (0.57), y débilmente con el número de habitaciones (0.37). Esto tiene sentido económico: el tamaño de la vivienda y el estrato socioeconómico de la zona son los principales determinantes del precio, mientras que el número de habitaciones por sí solo dice poco si no se controla por el área total (una casa con muchas habitaciones pequeñas no necesariamente vale más que una con pocas habitaciones amplias).

El gráfico de dispersión precio vs. área muestra una relación positiva razonablemente lineal, con dispersión creciente a medida que aumenta el área (heterocedasticidad visual: a mayor tamaño, mayor variabilidad en el precio, probablemente por diferencias en acabados y ubicación específica dentro de la zona norte que el modelo no captura). Los boxplots de habitaciones y baños muestran medianas crecientes, pero con bastante solapamiento entre categorías y algunos valores atípicos hacia arriba (casas premium dentro de la misma categoría de habitaciones/baños).

3. Modelo de regresión lineal múltiple

modelo1 <- lm(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 
## -765.19  -77.49  -15.62   46.41  979.13 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -231.5727    45.1306  -5.131 4.39e-07 ***
## areaconst       0.6646     0.0533  12.470  < 2e-16 ***
## estrato        78.3545     9.9538   7.872 2.94e-14 ***
## habitaciones    6.0679     5.8640   1.035  0.30136    
## parqueaderos   24.2743     5.9052   4.111 4.74e-05 ***
## banios         22.4145     7.8308   2.862  0.00441 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 155 on 425 degrees of freedom
##   (269 observations deleted due to missingness)
## Multiple R-squared:  0.6022, Adjusted R-squared:  0.5975 
## F-statistic: 128.7 on 5 and 425 DF,  p-value: < 2.2e-16

Interpretación de coeficientes:

El modelo obtiene un R² ajustado de 0.5975: explica cerca del 60% de la variabilidad del precio de las casas en zona norte. Es un ajuste moderado para un modelo de precios de vivienda con solo 5 variables — razonable, pero deja un 40% de la variabilidad sin explicar, probablemente asociado a factores no incluidos como el barrio específico, la antigüedad de la construcción, el estado/acabados del inmueble o la cercanía a vías principales.

Variables significativas (p < 0.05):

  • areaconst (+0.66 millones por m², p<0.001): por cada metro cuadrado adicional, el precio esperado sube ~0.66 millones de pesos, manteniendo las demás variables constantes. Signo lógico y esperado.
  • estrato (+78.35 millones, p<0.001): cada estrato adicional suma en promedio ~78 millones al precio — es el efecto individual más grande del modelo, coherente con que en Colombia el estrato es un fuerte indicador de ubicación, seguridad y calidad de la zona, no solo del inmueble en sí.
  • parqueaderos (+24.27 millones, p<0.001) y banios (+22.41 millones, p=0.0044): ambos significativos con signo positivo lógico — más parqueaderos y más baños son atributos que el mercado valora.

Variable no significativa:

  • habitaciones (+6.07 millones, p=0.30): controlando por área, el número de habitaciones no tiene un efecto estadísticamente significativo sobre el precio. Es razonable: a igual área total, más habitaciones implica cuartos más pequeños, lo cual compensa cualquier efecto positivo de tener “más cuartos”.

Para mejorar el ajuste se podría incorporar el barrio (como variable categórica), antigüedad de la construcción, y variables de acabados o amenidades (ascensor, zona social, seguridad), que suelen explicar buena parte de la varianza residual en modelos hedónicos de precios de vivienda.

4. Validación de supuestos

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

par(mfrow = c(1,1))

# Normalidad de los residuos
shapiro.test(residuals(modelo1))
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(modelo1)
## W = 0.84982, p-value < 2.2e-16
# Homocedasticidad (varianza constante de residuos)
bptest(modelo1)
## 
##  studentized Breusch-Pagan test
## 
## data:  modelo1
## BP = 78.183, df = 5, p-value = 2.013e-15
# Independencia de los residuos
dwtest(modelo1)
## 
##  Durbin-Watson test
## 
## data:  modelo1
## DW = 1.7442, p-value = 0.003318
## alternative hypothesis: true autocorrelation is greater than 0
# Multicolinealidad
vif(modelo1)
##    areaconst      estrato habitaciones parqueaderos       banios 
##     1.473677     1.330709     1.743692     1.236088     2.064333

Interpretación:

  • Normalidad (Shapiro-Wilk): W=0.850, p<0.001 → se rechaza la normalidad de los residuos. Es un resultado esperado en precios de vivienda, que suelen tener una cola derecha larga (pocas casas muy caras). Sugerencia: transformar la variable respuesta con logaritmo (log(preciom)), lo cual suele estabilizar este tipo de distribución.
  • Homocedasticidad (Breusch-Pagan): BP=78.18, p<0.001 → se rechaza la homocedasticidad, hay varianza no constante de los residuos (probablemente crece con el precio/área, como se veía en el scatter del EDA). Sugerencia: usar errores estándar robustos (heterocedasticidad-consistentes) o aplicar la misma transformación logarítmica.
  • Independencia (Durbin-Watson): DW=1.744, p=0.003 → estadísticamente se detecta algo de autocorrelación positiva. Sin embargo, este test está pensado originalmente para series de tiempo (orden temporal); en datos de corte transversal como este, un valor cercano a 2 (1.74) indica que la desviación es leve en términos prácticos, aunque significativa estadísticamente por el tamaño de muestra.
  • Multicolinealidad (VIF): todos los valores están entre 1.24 y 2.06, muy por debajo del umbral de preocupación (5-10). No hay problema de multicolinealidad entre las variables predictoras — cada una aporta información relativamente independiente al modelo.

(Recuerda: el enunciado no pide corregir, solo identificar y sugerir qué se podría hacer.)

5. Predicción para la Vivienda 1 solicitada

nueva_vivienda1 <- data.frame(
  areaconst = 200,
  estrato = 4,        #
  habitaciones = 4,
  parqueaderos = 1,
  banios = 2
)

pred1 <- predict(modelo1, newdata = nueva_vivienda1, interval = "prediction")
pred1
##        fit      lwr      upr
## 1 308.1488 2.384356 613.9132

Interpretación: el modelo predice un precio esperado de $308.1 millones para la vivienda solicitada (área 200 m², estrato 4, 4 habitaciones, 1 parqueadero, 2 baños), con un intervalo de predicción al 95% de $2.4 a $613.9 millones. El valor puntual está dentro del crédito preaprobado de $350 millones, con un margen de ~42 millones. Sin embargo, el intervalo de predicción es extremadamente amplio — refleja el error estándar residual alto del modelo (155 millones) frente a precios medianos de ~330 millones —, por lo que la confianza en el precio exacto de una vivienda individual es baja, aunque la estimación central sea alentadora. Si se prueba con estrato 5 en lugar de 4, el precio esperado sube (dado el coeficiente positivo de estrato, +78.35 millones por nivel), acercándose más al límite del presupuesto.

6. Ofertas potenciales dentro del presupuesto (350 millones)

credito1 <- 350

ofertas1 <- base1 %>%
  filter(preciom <= credito1,
         habitaciones >= 4,
         banios >= 2,
         parqueaderos >= 1,
         estrato %in% c(4, 5)) %>%
  arrange(desc(preciom)) %>%   # las más cercanas al tope del presupuesto primero
  head(5)

kable(ofertas1)
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
3779 Zona Norte 03 4 350 98 2 3 4 Casa chipichape -76.52840 3.48154
4210 Zona Norte 01 5 350 200 3 3 4 Casa el bosque -76.53010 3.48503
4209 Zona Norte 02 5 350 300 3 5 6 Casa el bosque -76.53010 3.48577
4422 Zona Norte 02 5 350 240 2 3 6 Casa el bosque -76.53136 3.48635
1270 Zona Norte NA 5 350 203 2 2 5 Casa el bosque -76.51448 3.48531
leaflet(ofertas1) %>%
  addTiles() %>%
  addCircleMarkers(lng = ~longitud, lat = ~latitud,
                    radius = 6, color = "blue",
                    popup = ~paste0("Barrio: ", barrio,
                                     "<br>Precio: ", preciom, " millones",
                                     "<br>Área: ", areaconst, " m2",
                                     "<br>Habitaciones: ", habitaciones))

Discusión: se identificaron 5 ofertas dentro del presupuesto, pero un dato importante es que las 5 están priceadas exactamente en $350 millones, es decir, en el tope exacto del crédito preaprobado — no hay margen de negociación en ninguna de ellas. La oferta en “el bosque” con 300 m² de área y 6 habitaciones (id 4209) ofrece más área y espacio por el mismo precio que las demás, siendo la más atractiva en términos de valor por metro cuadrado. La recomendación para María es presentar estas 5 opciones al cliente, pero advirtiendo explícitamente que agotan el 100% del crédito disponible; si el cliente quiere margen para gastos de cierre o negociación, convendría ampliar la búsqueda relajando ligeramente algún criterio (por ejemplo, aceptando 1 habitación menos).


PARTE B — Vivienda 2 (Apartamento, Zona Sur, crédito 850 millones)

(Se repiten los pasos 1 a 6 para la segunda solicitud.)

1. Filtro de la base de datos (base2)

base2 <- vivienda_limpia %>%
  filter(grepl("sur", zona, ignore.case = TRUE),
         grepl("apartamento", tipo, ignore.case = TRUE))

kable(head(base2, 3))
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
table(base2$zona, base2$tipo)
##           
##            Apartamento
##   Zona Sur        2777
dim(base2)
## [1] 2777   13
leaflet(base2) %>%
  addTiles() %>%
  addCircleMarkers(lng = ~longitud, lat = ~latitud,
                    radius = 4, color = "darkorange",
                    popup = ~paste0("Barrio: ", barrio,
                                     "<br>Precio: ", preciom, " millones",
                                     "<br>Área: ", areaconst, " m2"))

2. Análisis exploratorio (EDA) con plotly

num_vars2 <- base2 %>% select(preciom, areaconst, estrato, banios, habitaciones)
cor_matrix2 <- cor(num_vars2, use = "complete.obs")
kable(round(cor_matrix2, 2))
preciom areaconst estrato banios habitaciones
preciom 1.00 0.76 0.67 0.73 0.34
areaconst 0.76 1.00 0.48 0.69 0.45
estrato 0.67 0.48 1.00 0.57 0.21
banios 0.73 0.69 0.57 1.00 0.52
habitaciones 0.34 0.45 0.21 0.52 1.00
p4 <- plot_ly(base2, x = ~areaconst, y = ~preciom, type = "scatter",
              mode = "markers", color = ~factor(estrato)) %>%
  layout(title = "Precio vs Área construida (color = estrato) - Zona Sur",
         xaxis = list(title = "Área construida (m2)"),
         yaxis = list(title = "Precio (millones $)"))
p4
p5 <- plot_ly(base2, x = ~factor(habitaciones), y = ~preciom, type = "box") %>%
  layout(title = "Precio según número de habitaciones - Zona Sur")
p5
p6 <- plot_ly(base2, x = ~factor(banios), y = ~preciom, type = "box") %>%
  layout(title = "Precio según número de baños - Zona Sur")
p6

Interpretación: en zona sur las correlaciones con el precio son en general más fuertes que en zona norte: área (0.76), baños (0.73) y estrato (0.67), mientras que habitaciones vuelve a ser la más débil (0.34). Esto sugiere que el mercado de apartamentos en zona sur es más homogéneo o estandarizado — los atributos físicos explican mejor el precio que en el caso de las casas de zona norte. El scatter de precio vs. área muestra una relación positiva más ajustada (menos dispersión relativa) que en zona norte, consistente con la mejor bondad de ajuste que se observa en el modelo de regresión (ver punto 3).

3. Modelo de regresión lineal múltiple

modelo2 <- lm(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 
## -1095.63   -42.49    -1.25    40.97   922.58 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -255.85169   15.95956 -16.031  < 2e-16 ***
## areaconst       1.29109    0.05415  23.842  < 2e-16 ***
## estrato        60.27082    3.09689  19.462  < 2e-16 ***
## habitaciones  -26.58471    4.03631  -6.586 5.53e-11 ***
## parqueaderos   75.72992    4.19324  18.060  < 2e-16 ***
## banios         49.94971    3.46080  14.433  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 98.02 on 2369 degrees of freedom
##   (402 observations deleted due to missingness)
## Multiple R-squared:  0.7477, Adjusted R-squared:  0.7471 
## F-statistic:  1404 on 5 and 2369 DF,  p-value: < 2.2e-16

Interpretación de coeficientes:

Interpretación de coeficientes:

El modelo logra explicar el 75% de las diferencias de precio entre apartamentos de zona sur — un resultado bastante bueno, y mejor que el modelo de casas en zona norte (60%).

Estas son las variables que más mueven el precio:

Área construida: cada metro cuadrado extra suma 1.29 millones. Entre más grande el apartamento, más caro — como se esperaba. Estrato: cada estrato adicional suma aproximadamente 60 millones. Confirma que la ubicación/estrato pesa mucho en el precio. Parqueaderos: cada parqueadero extra suma 76 millones. Es el factor que más sube el precio de todos — en apartamentos, tener parqueadero parece ser muy valorado, probablemente porque no todos los edificios lo ofrecen aunque siendo sincero esto es mas para inmuebles premium que necesitan mas de un parqueadero.+

Baños: cada baño extra suma 50 millones.

Y el resultado más curioso:

Habitaciones: cada habitación extra resta aproximadamente 27 millones al precio (sube el número de cuartos, pero baja el precio). esto tiene sentido si el apartamento tiene el mismo tamaño, meter más habitaciones significa que cada una queda más pequeña. La gente prefiere pocos cuartos amplios que muchos cuartos chiquitos, así que el mercado paga menos por eso.

4. Validación de supuestos

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

par(mfrow = c(1,1))

shapiro.test(residuals(modelo2))
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(modelo2)
## W = 0.79121, p-value < 2.2e-16
bptest(modelo2)
## 
##  studentized Breusch-Pagan test
## 
## data:  modelo2
## BP = 764.22, df = 5, p-value < 2.2e-16
dwtest(modelo2)
## 
##  Durbin-Watson test
## 
## data:  modelo2
## DW = 1.5379, p-value < 2.2e-16
## alternative hypothesis: true autocorrelation is greater than 0
vif(modelo2)
##    areaconst      estrato habitaciones parqueaderos       banios 
##     2.064997     1.554021     1.444125     1.813306     2.605630

Interpretación:

  • Normalidad (Shapiro-Wilk): W=0.791, p<0.001 → se rechaza la normalidad, incluso con mayor evidencia que en el modelo de zona norte. Misma sugerencia: transformación logarítmica de preciom.
  • Homocedasticidad (Breusch-Pagan): BP=764.22, p<0.001 → heterocedasticidad fuerte, considerablemente más marcada que en el modelo 1 (BP=78.18). Esto es consistente con el mayor rango de precios observado en zona sur (hasta apartamentos muy costosos que generan mayor varianza en los residuos).
  • Independencia (Durbin-Watson): DW=1.538, p<0.001 → se detecta autocorrelación; igual que en el modelo 1, esta lectura debe tomarse con cautela ya que el test es más apropiado para series de tiempo que para datos de corte transversal.
  • Multicolinealidad (VIF): todos los valores entre 1.44 y 2.61, por debajo del umbral de preocupación. No hay problema de multicolinealidad.

5. Predicción para la Vivienda 2 solicitada

nueva_vivienda2 <- data.frame(
  areaconst = 300,
  estrato = 5,       # el enunciado pide "5 o 6"; probar también estrato = 6
  habitaciones = 5,
  parqueaderos = 3,
  banios = 3
)

pred2 <- predict(modelo2, newdata = nueva_vivienda2, interval = "prediction")
pred2
##        fit      lwr     upr
## 1 676.9457 483.3134 870.578

Interpretación: el modelo predice un precio esperado de $676.9 millones para la vivienda solicitada (área 300 m², estrato 5, 5 habitaciones, 3 parqueaderos, 3 baños), con un intervalo de predicción al 95% de $483.3 a $870.6 millones. El valor puntual queda cómodamente dentro del crédito preaprobado de $850 millones, con un margen de ~173 millones. El límite superior del intervalo (870.6M) supera ligeramente el presupuesto, pero la mayor parte del rango de predicción es asequible — una señal más favorable que en el caso de la vivienda 1. Vale la pena probar también con estrato 6 (el enunciado permite “5 o 6”), lo que elevaría el precio esperado en ~60 millones adicionales por el coeficiente de estrato.

6. Ofertas potenciales dentro del presupuesto (850 millones)

credito2 <- 850

ofertas2 <- base2 %>%
  filter(preciom <= credito2,
         habitaciones >= 5,
         banios >= 3,
         parqueaderos >= 3,
         estrato %in% c(5, 6)) %>%
  arrange(desc(preciom)) %>%
  head(5)

kable(ofertas2)
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
7182 Zona Sur NA 5 730 573 3 8 5 Apartamento guadalupe -76.54800 3.40800
7512 Zona Sur NA 5 670 300 3 5 6 Apartamento seminario -76.55000 3.40900
8036 Zona Sur NA 5 530 256 3 5 5 Apartamento seminario -76.55408 3.40748
leaflet(ofertas2) %>%
  addTiles() %>%
  addCircleMarkers(lng = ~longitud, lat = ~latitud,
                    radius = 6, color = "red",
                    popup = ~paste0("Barrio: ", barrio,
                                     "<br>Precio: ", preciom, " millones",
                                     "<br>Área: ", areaconst, " m2",
                                     "<br>Habitaciones: ", habitaciones))

Discusión: a diferencia de la vivienda 1, aquí el filtro estricto (≥5 habitaciones, ≥3 baños, ≥3 parqueaderos, estrato 5-6, precio ≤850M) solo arrojó 3 ofertas reales, no 5 — el segmento de apartamentos grandes y de alta gama en zona sur es escaso en la base de datos. Las tres opciones encontradas (guadalupe, seminario x2) están todas dentro de presupuesto con margen considerable (entre $530M y $730M, muy por debajo del tope de $850M). La recomendación para María es presentar estas 3 opciones al cliente — destacando que tienen margen de negociación, a diferencia del caso de la vivienda 1 — y advertir que si el cliente desea más alternativas para comparar, sería necesario relajar algún criterio (por ejemplo, aceptar 2 parqueaderos en vez de 3).


Conclusiones y recomendaciones (Informe Ejecutivo)

el modelo (R² ajustado = 0.60) predice un precio esperado de $308.1 millones para la vivienda solicitada, dentro del crédito preaprobado de $350 millones, aunque con un margen ajustado y una incertidumbre considerable a nivel individual (intervalo de predicción de $2.4M a $613.9M). Se identificaron 5 ofertas reales en el mercado que cumplen los requisitos, pero las 5 están priceadas exactamente en el tope del presupuesto — no dejan margen de negociación. Se recomienda presentar estas 5 opciones al cliente, siendo transparente en que absorben la totalidad del crédito disponible.

el modelo (R² ajustado = 0.75, mejor ajustado que el de zona norte) predice un precio esperado de $676.9 millones, cómodamente dentro del crédito preaprobado de $850 millones, con mayor confianza relativa que en el caso anterior. Sin embargo, solo existen 3 ofertas reales en el mercado que cumplen simultáneamente todos los requisitos solicitados — un segmento de oferta limitado. Se recomienda presentar esas 3 opciones, que además cuentan con margen de negociación frente al presupuesto, y anticipar al cliente que ampliar las alternativas implicaría flexibilizar alguna característica (parqueaderos u habitaciones).

  • ambos modelos muestran multicolinealidad nula (VIF bajos) pero incumplen los supuestos de normalidad y homocedasticidad de los residuos — un patrón común en precios de vivienda con distribución asimétrica. Esto no invalida las predicciones puntuales, pero sí implica que los intervalos de confianza/predicción deben interpretarse con cautela. Como mejora futura se recomienda: (1) transformar preciom con logaritmo para estabilizar varianza y normalidad, y (2) revisar la codificación de parqueaderos, ya que un número importante de registros (que posiblemente corresponden a inmuebles sin parqueadero) están como NA en vez de 0, lo que hace que lm() los descarte automáticamente y reduzca el tamaño de muestra efectivo usado en ambos modelos.

Vivienda 1 (Casa, Zona Norte): el modelo estima que esta casa debería costar alrededor de $308 millones, lo cual sí alcanza con el crédito de $350 millones. Eso sí, la predicción no es muy precisa a nivel individual (podría variar bastante, entre $2M y $614M según el modelo), así que es un estimado más “en promedio” que exacto. Encontramos 5 casas reales en el mercado que cumplen lo pedido, pero las 5 cuestan justo los 350 millones — es decir, se gastaría todo el crédito, sin nada de margen para negociar el precio.

Vivienda 2 (Apartamento, Zona Sur): el modelo estima un precio de $677 millones, muy por debajo del crédito de $850 millones — hay bastante más holgura que en el caso anterior, y esta predicción es más confiable que la de la casa. El problema aquí no es el precio sino la disponibilidad: solo hay 3 apartamentos reales en el mercado que cumplan con todo lo pedido. La buena noticia es que esos 3 sí tienen margen para negociar el precio. Si el cliente quiere más opciones para elegir, tocaría ceder en algo (por ejemplo, aceptar un parqueadero menos).

Qué tan confiables son los modelos: ambos modelos son válidos (no tienen problemas de variables repetitivas entre sí), pero no cumplen del todo los supuestos estadísticos ideales — algo normal cuando se trabaja con precios de vivienda, porque unas pocas propiedades muy caras “desbalancean” los datos. Esto no significa que las predicciones estén mal, pero sí que hay que tomarlas con cierta cautela, sobre todo los rangos (no tanto el número central). Dos cosas que mejorarían el modelo a futuro: usar el precio en escala logarítmica (ayuda a que los datos se comporten mejor estadísticamente), y revisar cómo se registró el dato de parqueaderos, porque muchos inmuebles que probablemente no tienen parqueadero quedaron marcados como “dato faltante” en vez de “cero” — eso hizo que el modelo descartara automáticamente una parte importante de los datos.

Anexos

Los anexos (estimaciones completas, validaciones y comparación de modelos) están incluidos como código y salidas a lo largo de este mismo documento (Partes A y B).