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 |
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 sobrevivienda_limpia, no sobreviviendacruda.
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
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.
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).
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):
Variable no significativa:
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.
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:
log(preciom)), lo cual suele estabilizar este
tipo de distribución.(Recuerda: el enunciado no pide corregir, solo identificar y sugerir qué se podría hacer.)
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.
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).
(Se repiten los pasos 1 a 6 para la segunda solicitud.)
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"))
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).
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.
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:
preciom.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.
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).
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).
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.
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).