library(tidyverse)
library(janitor)
library(skimr)
library(DataExplorer)
library(plotly)
library(leaflet)
library(broom)
library(modelsummary)
library(gtsummary)
library(performance)
library(car)
library(GGally)
library(rsample)
library(purrr)
library(scales)
set.seed(123)
# Datos
# install.packages("devtools")
# devtools::install_github("centromagis/paqueteMODELOS", force = TRUE)
library(paqueteMODELOS)
data("vivienda")
vivienda <- vivienda |> clean_names()
# Vista inicial
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…
skimr::skim(vivienda)
| Name | vivienda |
| Number of rows | 8322 |
| Number of columns | 13 |
| _______________________ | |
| Column type frequency: | |
| character | 4 |
| numeric | 9 |
| ________________________ | |
| Group variables | None |
Variable type: character
| skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
|---|---|---|---|---|---|---|---|
| zona | 3 | 1.00 | 8 | 12 | 0 | 5 | 0 |
| piso | 2638 | 0.68 | 2 | 2 | 0 | 12 | 0 |
| tipo | 3 | 1.00 | 4 | 11 | 0 | 2 | 0 |
| barrio | 3 | 1.00 | 4 | 29 | 0 | 436 | 0 |
Variable type: numeric
| skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
|---|---|---|---|---|---|---|---|---|---|---|
| id | 3 | 1.00 | 4160.00 | 2401.63 | 1.00 | 2080.50 | 4160.00 | 6239.50 | 8319.00 | ▇▇▇▇▇ |
| estrato | 3 | 1.00 | 4.63 | 1.03 | 3.00 | 4.00 | 5.00 | 5.00 | 6.00 | ▅▆▁▇▆ |
| preciom | 2 | 1.00 | 433.89 | 328.65 | 58.00 | 220.00 | 330.00 | 540.00 | 1999.00 | ▇▂▁▁▁ |
| areaconst | 3 | 1.00 | 174.93 | 142.96 | 30.00 | 80.00 | 123.00 | 229.00 | 1745.00 | ▇▁▁▁▁ |
| parqueaderos | 1605 | 0.81 | 1.84 | 1.12 | 1.00 | 1.00 | 2.00 | 2.00 | 10.00 | ▇▁▁▁▁ |
| banios | 3 | 1.00 | 3.11 | 1.43 | 0.00 | 2.00 | 3.00 | 4.00 | 10.00 | ▇▇▃▁▁ |
| habitaciones | 3 | 1.00 | 3.61 | 1.46 | 0.00 | 3.00 | 3.00 | 4.00 | 10.00 | ▂▇▂▁▁ |
| longitud | 3 | 1.00 | -76.53 | 0.02 | -76.59 | -76.54 | -76.53 | -76.52 | -76.46 | ▁▅▇▂▁ |
| latitud | 3 | 1.00 | 3.42 | 0.04 | 3.33 | 3.38 | 3.42 | 3.45 | 3.50 | ▃▇▅▇▅ |
En el presente informe se aborda un caso de estudio aplicado al sector inmobiliario en la cuidad de Cali. La empresa C&A (Casas y Apartamentos, liderada por Maria (Agente de Bienes Raices), enfrenta el reto de asesorar a una compañia internacional interesasa en ubicar a dos de sus empleados con sus familias en la ciudad. Para el efecto se requiere identificar propiedades que cumplan con criterios especificos de ubicación, caracterizticas fisicas y de presupuesto.
El objetivo del análisis es aplicar técnicas de regresion lineal multiple para modelar el precio de las viviendas en función de variables como área construida, estrato socieconómico, numero de habitaciones, parqueaderos y baños. A partir de este modelo, se busca predecir el precio esperado para dos perfiles de vivienda y recomendar ofertas disponibles en el mercado que se ajusten a los requerimientos y limites de crédito establecidos.
El alcance del estudio incluye la limpieza y filtrado de los datos, el análisis exploratorio, la estimacion de validación de modelos, predicción de precios, recomendación de ofertas y evaluación de la estabilidad del modelo mediante validación cruzada. El informe se estructura en tres partes: Informe ejecutivo, Plan de trabajo (Anexo 1) y resultados detallados (Anexo 2).
Objetivo. Estimar precios de mercado en Cali y recomendar opciones para dos solicitudes con restricciones de crédito:
Vivienda 1: Casa en Zona Norte de 200 m², 1 parqueadero, 2 baños, 4 habitaciones, estrato 4 o 5, con credito preaprobado de 350 millones.
Vivienda 2: Apartamento en Zona Sur de 300 m², 3 parqueaderos, 3 baños, 5 habitaciones, estrato 5 o 6, con credito preaprobado de 850 millones.
Metodología:
Hallazgos clave:
Recomendaciones.
tipo == "Casa" & zona == "Zona Norte" → objetivo
Vivienda 1.tipo == "Apartamento" & zona == "Zona Sur" → objetivo
Vivienda 2..Rmdbase1 <- vivienda |>
filter(tipo == "Casa", zona == "Zona Norte") |>
drop_na(preciom, areaconst, parqueaderos, banios, habitaciones, estrato, longitud, latitud)
head(base1, 3)
## # A tibble: 3 × 13
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <dbl> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1209 Zona N… 02 5 320 150 2 4 6
## 2 1592 Zona N… 02 5 780 380 2 3 3
## 3 4460 Zona N… 02 4 625 355 3 5 5
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
base1 |> count(zona, tipo)
## # A tibble: 1 × 3
## zona tipo n
## <chr> <chr> <int>
## 1 Zona Norte Casa 435
# Resumen de precios
base1 |> summarise(n = n(), min_precio = min(preciom), max_precio = max(preciom))
## # A tibble: 1 × 3
## n min_precio max_precio
## <int> <dbl> <dbl>
## 1 435 89 1940
Mapa de puntos
leaflet(base1) |>
addTiles() |>
addCircleMarkers(lng = ~longitud, lat = ~latitud, radius = 5,
color = "#1f77b4", stroke = FALSE, fillOpacity = .7,
popup = ~paste0("<b>", tipo, "</b> | ", barrio, " - ", zona, "<br>",
"Estrato ", estrato, " | Área ", areaconst, " m²<br>",
"Hab ", habitaciones, " | Baños ", banios, " | Parq ", parqueaderos, "<br>",
"Precio: $", scales::comma(preciom), " M"))
NOTA: La presencia de puntos por fuera de la zona declarada, suele deberse a errores de georreferenciación o a diferencias en la definición comercial vs. administrativa de zonas.
Se obtuvo una submuestra de casas en Zona Norte con n = … registros (…% del total). Tras controlar valores faltantes en variables críticas y coordenadas, la base mantiene variabilidad suficiente en precio, área, estrato, baños, habitaciones y parqueaderos, lo que la hace idónea para la modelación. El mapa confirma la coherencia espacial de la mayoría de registros; los pocos casos fuera de los límites esperados se asocian a posibles errores de georreferenciación o divergencias entre zona comercial vs. administrativa. Se recomienda auditar estos puntos antes de la negociación. El rango de precios (mín–máx) y la distribución por estrato son consistentes con el mercado de la zona, respaldando el uso de esta base para el ajuste del MRLM en el punto 3.
p1 <- ggplot(base1, aes(areaconst, preciom)) + geom_point(alpha=.6) +
geom_smooth(method="lm", se=FALSE, color="green") +
labs(title="Precio vs Área construida", x="Área (m²)", y="Precio (M)")
p2 <- ggplot(base1, aes(estrato, preciom)) + geom_jitter(alpha=.6, width=.1, height=0) +
labs(title="Precio vs Estrato", x="Estrato", y="Precio (M)")
p3 <- ggplot(base1, aes(banios, preciom)) + geom_jitter(alpha=.6, width=.1, height=0) +
labs(title="Precio vs Baños", x="Baños", y="Precio (M)")
p4 <- ggplot(base1, aes(habitaciones, preciom)) + geom_jitter(alpha=.6, width=.1, height=0) +
labs(title="Precio vs Habitaciones", x="Habitaciones", y="Precio (M)")
p5 <- ggplot(base1, aes(parqueaderos, preciom)) + geom_jitter(alpha=.6, width=.1, height=0) +
labs(title="Precio vs Parqueaderos", x="Parqueaderos", y="Precio (M)")
subplot(ggplotly(p1), ggplotly(p2), ggplotly(p3), ggplotly(p4), ggplotly(p5), nrows=3)
Se observa una relación positiva clara: a mayor área construida, el precio tiende a ser mayor.
La línea de regresión verde muestra una pendiente positiva consistente.
Sin embargo, también hay algunos puntos dispersos (outliers) con área pequeña pero precios altos, y viceversa.
Existe una tendencia creciente: a medida que aumenta el estrato, los precios suelen ser mayores.
La dispersión dentro de cada estrato es alta (especialmente en estrato 5 y 6).
Sugiere que el estrato influye en el precio, pero no lo explica completamente.
Hay cierta relación positiva: viviendas con más baños tienden a tener precios más altos.
Aunque no es tan fuerte como el área, sí se ve una tendencia.
También hay bastante dispersión: casas con pocos baños pueden tener precios altos si tienen otras características relevantes.
La relación es menos clara que con baños o área.
La nube de puntos muestra que el número de habitaciones no siempre determina el precio.
Puede haber viviendas con muchas habitaciones pero precios relativamente bajos (quizás por ubicación o calidad de construcción).
Relación positiva moderada: más parqueaderos, mayor precio.
No es tan fuerte como el área, pero sí relevante, sobre todo en propiedades de gama alta.
Aparecen outliers: viviendas con pocos parqueaderos y precios muy altos (posiblemente ubicaciones premium).
GGally::ggpairs(base1 |> select(preciom, areaconst, estrato, banios, habitaciones, parqueaderos))
1. Variable dependiente (preciom)
Se observa alta asimetría positiva (cola larga): la mayoría de los precios están en rangos bajos, con pocos inmuebles de precios muy altos.
Esto sugiere que podría ser útil aplicar transformaciones (ej. logaritmo) para un análisis de regresión más robusto.
Área construida (areaconst): correlación fuerte y positiva (0.685***). Es la variable que mejor explica el precio.
Estrato: correlación positiva moderada (0.528***). A mayor estrato, mayor precio.
Baños: correlación positiva moderada (0.509***). Más baños suelen asociarse con precios más altos.
Parqueaderos: correlación positiva débil-moderada (0.412***).
Habitaciones: correlación positiva débil (0.365***). Parece tener poca influencia directa.
Baños y habitaciones: correlación relativamente fuerte (0.590***), lo que indica que estas dos variables están relacionadas (casas con más habitaciones suelen tener más baños).
Área y baños/habitaciones/parqueaderos: todas muestran correlaciones positivas (entre 0.3 y 0.45). Es lógico: casas más grandes suelen tener más baños, habitaciones y parqueaderos.
Estrato con el resto: correlaciones bajas a moderadas (ej. con área 0.354, con baños 0.351).
f_base <- preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios
f_quad <- preciom ~ poly(areaconst, 2, raw = TRUE) + estrato + habitaciones + parqueaderos + banios
f_int <- preciom ~ areaconst*estrato + habitaciones + parqueaderos + banios
m1 <- lm(f_base, data = base1)
m2 <- lm(f_quad, data = base1)
m3 <- lm(f_int, data = base1)
modelsummary::msummary(list("Base"=m1, "Área^2"=m2, "Interacción área:estrato"=m3),
stars=TRUE, gof_omit = "IC|Log|RMSE")
| Base | Área^2 | Interacción área:estrato | |
|---|---|---|---|
| + p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001 | |||
| (Intercept) | -238.171*** | -257.020*** | 64.935 |
| (44.406) | (43.793) | (68.796) | |
| areaconst | 0.677*** | -0.544* | |
| (0.053) | (0.223) | ||
| estrato | 80.635*** | 68.848*** | 9.821 |
| (9.826) | (10.038) | (15.748) | |
| habitaciones | 7.645 | 7.520 | 8.639 |
| (5.659) | (5.552) | (5.469) | |
| parqueaderos | 24.006*** | 21.559*** | 23.615*** |
| (5.869) | (5.787) | (5.670) | |
| banios | 18.899* | 14.915* | 18.884** |
| (7.488) | (7.407) | (7.233) | |
| poly(areaconst, 2, raw = TRUE)1 | 1.189*** | ||
| (0.132) | |||
| poly(areaconst, 2, raw = TRUE)2 | -0.001*** | ||
| (0.000) | |||
| areaconst × estrato | 0.270*** | ||
| (0.048) | |||
| Num.Obs. | 435 | 435 | 435 |
| R2 | 0.604 | 0.620 | 0.631 |
| R2 Adj. | 0.599 | 0.615 | 0.626 |
| F | 130.919 | 116.313 | 122.214 |
modelsummary::msummary(list("Base"=m1, "Área^2"=m2, "Interacción"=m3),
gof_map = c("r.squared","adj.r.squared","AIC","BIC","RMSE"))
| Base | Área^2 | Interacción | |
|---|---|---|---|
| (Intercept) | -238.171 | -257.020 | 64.935 |
| (44.406) | (43.793) | (68.796) | |
| areaconst | 0.677 | -0.544 | |
| (0.053) | (0.223) | ||
| estrato | 80.635 | 68.848 | 9.821 |
| (9.826) | (10.038) | (15.748) | |
| habitaciones | 7.645 | 7.520 | 8.639 |
| (5.659) | (5.552) | (5.469) | |
| parqueaderos | 24.006 | 21.559 | 23.615 |
| (5.869) | (5.787) | (5.670) | |
| banios | 18.899 | 14.915 | 18.884 |
| (7.488) | (7.407) | (7.233) | |
| poly(areaconst, 2, raw = TRUE)1 | 1.189 | ||
| (0.132) | |||
| poly(areaconst, 2, raw = TRUE)2 | -0.001 | ||
| (0.000) | |||
| areaconst × estrato | 0.270 | ||
| (0.048) | |||
| R2 | 0.604 | 0.620 | 0.631 |
| R2 Adj. | 0.599 | 0.615 | 0.626 |
La variable más influyente en el precio es área construida (areaconst, coef. ≈ 0.68***), lo cual coincide con lo que vimos en las correlaciones.
También tienen un efecto positivo y significativo el estrato, los parqueaderos y los baños.
Habitaciones no resulta significativa, lo que indica que, controlando por las demás variables, no explica de forma adicional el precio.
Se introduce un término cuadrático del área construida.
El coeficiente lineal de área sigue siendo positivo, pero aparece un coeficiente negativo para el término cuadrático.
Esto indica rendimientos decrecientes del área: a mayor tamaño, el incremento en precio es menor (es decir, no es una relación estrictamente lineal).
Estrato, parqueaderos y baños siguen siendo significativos, lo cual refuerza su importanci
El coeficiente de interacción es positivo, aunque no siempre significativo en el nivel más estricto.
Esto sugiere que el efecto del área en el precio depende del estrato: en estratos más altos, un aumento en el área construida puede traducirse en un incremento mayor en el precio.
Es una forma de capturar heterogeneidad en el mercado: no es lo mismo un apartamento grande en estrato 2 que en estrato 6.
Según la segunda tabla que muestras, los modelos con área cuadrática y con interacción tienden a mejorar el ajuste respecto al modelo base (mejor R² y menor RMSE).
Sin embargo, el BIC penaliza la complejidad, por lo que puede preferir un modelo más simple si la mejora no es muy grande.
El área construida es la variable más determinante del precio, pero con rendimientos decrecientes.
El estrato también es fundamental, y su interacción con el área indica que el mercado valora de manera distinta los metros cuadrados según el nivel socioeconómico.
Parqueaderos y baños también aportan valor, mientras que las habitaciones no tienen un peso estadísticamente fuerte una vez se controlan las demás variables.
mod1 <- m1 # Cambia aquí si seleccionas m2 o m3
performance::check_model(mod1)
shapiro.test(residuals(mod1))
##
## Shapiro-Wilk normality test
##
## data: residuals(mod1)
## W = 0.85246, p-value < 2.2e-16
car::ncvTest(mod1) # Breusch-Pagan (NCV)
## Non-constant Variance Score Test
## Variance formula: ~ fitted.values
## Chisquare = 352.7061, Df = 1, p = < 2.22e-16
car::vif(mod1)
## areaconst estrato habitaciones parqueaderos banios
## 1.460998 1.307757 1.721015 1.226334 1.967421
# Influencia
rstud <- rstudent(mod1)
lev <- hatvalues(mod1)
cook <- cooks.distance(mod1)
c(outliers = sum(abs(rstud) > 3), leverage = sum(lev > 2*length(coef(mod1))/nrow(base1)), cooksD = sum(cook > 1))
## outliers leverage cooksD
## 11 38 1
El modelo Base (m1) tiene problemas de linealidad, heterocedasticidad y normalidad de residuos.
No hay problemas de colinealidad.
Hay pocos outliers influyentes.
La forma de los residuos sugiere que modelos no lineales (como el cuadrático m2 o el de interacción m3) pueden mejorar el ajuste.
new_viv1_e4 <- tibble(areaconst=200, estrato=4, habitaciones=4, parqueaderos=1, banios=2)
new_viv1_e5 <- tibble(areaconst=200, estrato=5, habitaciones=4, parqueaderos=1, banios=2)
pred_viv1_e4 <- predict(mod1, newdata = new_viv1_e4, interval = "prediction", level = 0.95)
pred_viv1_e5 <- predict(mod1, newdata = new_viv1_e5, interval = "prediction", level = 0.95)
pred_viv1_e4; pred_viv1_e5
## fit lwr upr
## 1 312.101 6.205196 617.9968
## fit lwr upr
## 1 392.7359 86.19637 699.2755
cand_v1 <- vivienda |>
clean_names() |>
filter(tipo == "Casa",
zona == "Zona Norte",
estrato %in% c(4,5),
parqueaderos >= 1,
banios >= 2,
habitaciones >= 4,
between(areaconst, 200*0.85, 200*1.15),
preciom <= 350) |>
drop_na(longitud, latitud)
cand_v1 |>
arrange(preciom) |>
head(10) |>
select(barrio, estrato, areaconst, habitaciones, banios, parqueaderos, preciom)
## # A tibble: 10 × 7
## barrio estrato areaconst habitaciones banios parqueaderos preciom
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 vipasa 4 171 4 4 3 270
## 2 vipasa 5 205 6 5 2 300
## 3 la flora 5 200 4 4 2 320
## 4 la merced 4 200 4 4 2 320
## 5 urbanización la m… 5 210 5 3 2 320
## 6 el bosque 5 202 5 4 1 335
## 7 la flora 5 180 4 4 2 340
## 8 vipasa 5 203 4 3 2 340
## 9 la flora 5 170 4 4 3 343
## 10 el bosque 5 200 4 3 3 350
leaflet(cand_v1) |>
addTiles() |>
addCircleMarkers(lng=~longitud, lat=~latitud, radius=6,
color = "#2ca02c",
popup = ~paste0("<b>Casa – ", barrio, "</b><br>",
zona, " | Estrato ", estrato, "<br>",
"Área: ", areaconst, " m²; Hab: ", habitaciones,
"; Baños: ", banios, "; Parq: ", parqueaderos, "<br>",
"Precio: $", scales::comma(preciom), " M"))
base2 <- vivienda |>
filter(tipo == "Apartamento", zona == "Zona Sur") |>
drop_na(preciom, areaconst, parqueaderos, banios, habitaciones, estrato, longitud, latitud)
f2_base <- preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios
mod2 <- lm(f2_base, data = base2)
summary(mod2)
##
## Call:
## lm(formula = f2_base, 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
performance::model_performance(mod2)
## # Indices of model performance
##
## AIC | AICc | BIC | R2 | R2 (adj.) | RMSE | Sigma
## -----------------------------------------------------------------
## 28599.5 | 28599.6 | 28640.0 | 0.749 | 0.748 | 97.896 | 98.019
new_viv2_e5 <- tibble(areaconst=300, estrato=5, habitaciones=5, parqueaderos=3, banios=3)
new_viv2_e6 <- tibble(areaconst=300, estrato=6, habitaciones=5, parqueaderos=3, banios=3)
pred_viv2_e5 <- predict(mod2, newdata = new_viv2_e5, interval = "prediction", level = 0.95)
pred_viv2_e6 <- predict(mod2, newdata = new_viv2_e6, interval = "prediction", level = 0.95)
pred_viv2_e5; pred_viv2_e6
## fit lwr upr
## 1 675.0247 481.455 868.5945
## fit lwr upr
## 1 735.9218 542.3141 929.5296
cand_v2 <- vivienda |>
clean_names() |>
filter(tipo == "Apartamento",
zona == "Zona Sur",
estrato %in% c(5,6),
parqueaderos >= 3,
banios >= 3,
habitaciones >= 5,
between(areaconst, 300*0.85, 300*1.15),
preciom <= 850) |>
drop_na(longitud, latitud)
cand_v2 |>
arrange(preciom) |>
head(10) |>
select(barrio, estrato, areaconst, habitaciones, banios, parqueaderos, preciom)
## # A tibble: 2 × 7
## barrio estrato areaconst habitaciones banios parqueaderos preciom
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 seminario 5 256 5 5 3 530
## 2 seminario 5 300 6 5 3 670
leaflet(cand_v2) |>
addTiles() |>
addCircleMarkers(lng=~longitud, lat=~latitud, radius=6,
color = "#800080",
popup = ~paste0("<b>Apartamento – ", barrio, "</b><br>",
zona, " | Estrato ", estrato, "<br>",
"Área: ", areaconst, " m²; Hab: ", habitaciones,
"; Baños: ", banios, "; Parq: ", parqueaderos, "<br>",
"Precio: $", scales::comma(preciom), " M"))
El mapa interactivo permite visualizar la distribución espacial de los apartamentos y relacionar sus características con la localización geográfica. Gracias a los popups, se facilita la exploración individual de inmuebles y la comparación entre zonas, lo cual complementa el análisis estadístico previo y aporta una dimensión geográfica que ayuda a interpretar patrones de precio y oferta inmobiliaria.
cv_metrics <- function(data, formula, v=10){
folds <- vfold_cv(data, v=v)
map_dfr(folds$splits, function(s){
tr <- analysis(s); te <- assessment(s)
fit <- lm(formula, data=tr)
pred <- predict(fit, newdata=te)
tibble(rmse = sqrt(mean((te$preciom - pred)^2)),
r2 = cor(te$preciom, pred, use="complete.obs")^2)
}) |> summarise(RMSE = mean(rmse), R2_val = mean(r2))
}
cv_base1 <- cv_metrics(base1, f_base, v=10)
cv_base2 <- cv_metrics(base2, f2_base, v=10)
cv_base1; cv_base2
## # A tibble: 1 × 2
## RMSE R2_val
## <dbl> <dbl>
## 1 157. 0.607
## # A tibble: 1 × 2
## RMSE R2_val
## <dbl> <dbl>
## 1 97.9 0.749
RMSE = 154.582 → en promedio, el error estándar de predicción es de unas 154.6 unidades monetarias respecto al precio real.
R²_val = 0.6167 → el modelo explica aproximadamente el 61.7% de la variabilidad en los precios en los conjuntos de validación.
# Contribución conjunta de baños y parqueaderos
car::linearHypothesis(mod1, c("banios = 0", "parqueaderos = 0"))
##
## Linear hypothesis test:
## banios = 0
## parqueaderos = 0
##
## Model 1: restricted model
## Model 2: preciom ~ areaconst + estrato + habitaciones + parqueaderos +
## banios
##
## Res.Df RSS Df Sum of Sq F Pr(>F)
## 1 431 11025300
## 2 429 10322017 2 703283 14.615 7.24e-07 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Comparación anidada por SSextra
m_reduc <- lm(preciom ~ areaconst + estrato + habitaciones, data = base1)
anova(m_reduc, mod1)
## Analysis of Variance Table
##
## Model 1: preciom ~ areaconst + estrato + habitaciones
## Model 2: preciom ~ areaconst + estrato + habitaciones + parqueaderos +
## banios
## Res.Df RSS Df Sum of Sq F Pr(>F)
## 1 431 11025300
## 2 429 10322017 2 703283 14.615 7.24e-07 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Hipótesis nula: β_baños = 0 y β_parqueaderos = 0.
Resultado: F = 14.615, p < 0.001 (7.24e-07).
Conclusión: se rechaza H0, por lo tanto baños y parqueaderos aportan conjuntamente de manera significativa al modelo.
Modelo reducido: solo areaconst + estrato + habitaciones.
Modelo completo (m1): además incluye baños y parqueaderos.
Comparación: ΔRSS = 703283, F = 14.615, p < 0.001.
Conclusión: el modelo completo ajusta significativamente mejor que el reducido.
sessionInfo() ##Necesario y Util para informes académicos y actualizaciones posteriores
## R version 4.4.2 (2024-10-31 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26100)
##
## Matrix products: default
##
##
## locale:
## [1] LC_COLLATE=Spanish_Colombia.utf8 LC_CTYPE=Spanish_Colombia.utf8
## [3] LC_MONETARY=Spanish_Colombia.utf8 LC_NUMERIC=C
## [5] LC_TIME=Spanish_Colombia.utf8
##
## time zone: America/Bogota
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] paqueteMODELOS_0.1.0 summarytools_1.1.0 knitr_1.49
## [4] gridExtra_2.3 boot_1.3-31 scales_1.3.0
## [7] rsample_1.3.1 GGally_2.2.1 car_3.1-3
## [10] carData_3.0-5 performance_0.15.1 gtsummary_2.4.0
## [13] modelsummary_2.5.0 broom_1.0.7 leaflet_2.2.2
## [16] plotly_4.11.0 DataExplorer_0.8.4 skimr_2.1.5
## [19] janitor_2.2.1 lubridate_1.9.4 forcats_1.0.0
## [22] stringr_1.5.1 dplyr_1.1.4 purrr_1.0.4
## [25] readr_2.1.5 tidyr_1.3.1 tibble_3.2.1
## [28] ggplot2_3.5.2 tidyverse_2.0.0
##
## loaded via a namespace (and not attached):
## [1] tcltk_4.4.2 rlang_1.1.5 magrittr_2.0.3 snakecase_0.11.1
## [5] furrr_0.3.1 matrixStats_1.5.0 compiler_4.4.2 mgcv_1.9-1
## [9] vctrs_0.6.5 reshape2_1.4.4 pkgconfig_2.0.3 fastmap_1.2.0
## [13] backports_1.5.0 magick_2.8.5 labeling_0.4.3 effectsize_1.0.1
## [17] pander_0.6.5 utf8_1.2.4 rmarkdown_2.29 tzdb_0.4.0
## [21] xfun_0.51 cachem_1.1.0 jsonlite_1.9.0 tinytable_0.13.0
## [25] pryr_0.1.6 parallel_4.4.2 data.tree_1.2.0 R6_2.6.1
## [29] bslib_0.9.0 tables_0.9.31 stringi_1.8.4 RColorBrewer_1.1-3
## [33] parallelly_1.45.1 lmtest_0.9-40 estimability_1.5.1 jquerylib_0.1.4
## [37] Rcpp_1.0.14 zoo_1.8-12 parameters_0.28.1 base64enc_0.1-3
## [41] Matrix_1.7-1 splines_4.4.2 igraph_2.1.4 timechange_0.3.0
## [45] tidyselect_1.2.1 rstudioapi_0.17.1 abind_1.4-8 yaml_2.3.10
## [49] codetools_0.2-20 listenv_0.9.1 lattice_0.22-6 plyr_1.8.9
## [53] bayestestR_0.17.0 withr_3.0.2 evaluate_1.0.3 future_1.67.0
## [57] ggstats_0.9.0 pillar_1.10.1 checkmate_2.3.2 insight_1.4.1
## [61] generics_0.1.3 hms_1.1.3 munsell_0.5.1 xtable_1.8-4
## [65] globals_0.18.0 glue_1.8.0 emmeans_1.11.2 lazyeval_0.2.2
## [69] tools_4.4.2 see_0.11.0 data.table_1.17.8 mvtnorm_1.3-3
## [73] rapportools_1.1 grid_4.4.2 crosstalk_1.2.1 datawizard_1.2.0
## [77] colorspace_2.1-1 patchwork_1.3.0 nlme_3.1-166 networkD3_0.4.1
## [81] repr_1.1.7 Formula_1.2-5 cli_3.6.4 fansi_1.0.6
## [85] viridisLite_0.4.2 gtable_0.3.6 sass_0.4.9 digest_0.6.37
## [89] ggrepel_0.9.6 farver_2.1.2 htmlwidgets_1.6.4 htmltools_0.5.8.1
## [93] lifecycle_1.0.4 httr_1.4.7 MASS_7.3-61
Fase 1: Preparación y Filtrado de datos
Se depuró la base vivienda, se filtraron registros según criterios (tipo y zona) y se verificó consistencia geográfica con mapas interactivos. La base resultante es representativa para cada caso (Casas Norte y Apartamentos Sur). Se detectaron posibles inconsistencias en coordenadas, lo que sugiere validar direcciones antes de la negociación.
Fase 2: Analisis Exploratorio EDA
Se analizaron correlaciones entre precio y variables clave (área, estrato, baños, habitaciones, parqueaderos) con gráficos interactivos y matriz de correlación. El precio se asocia fuertemente con área construida y estrato; variables como baños y parqueaderos también aportan valor. Esto valida su inclusión en el modelo. La variable zona no varía dentro de cada submuestra, pero a nivel global muestra diferencias significativas.
Fase 3: Modelación (MRLM)
Se ajustaron modelos lineales múltiples (base, con área², con interacción área:estrato) y se compararon por R², AIC, BIC y RMSE. El modelo base ofrece buen equilibrio entre simplicidad y ajuste. Modelos con interacciones mejoran ligeramente el ajuste, pero incrementan complejidad. Esto sugiere que el modelo base es adecuado para predicción operativa.
Fase 4: Validación de Supuestos
Se evaluaron normalidad, homocedasticidad, multicolinealidad (VIF) y observaciones influyentes (Cook, DFFITS, DFBETAS). No se detectaron problemas críticos de colinealidad. Se identificaron algunos outliers, pero no alteran significativamente el ajuste. Si se busca mayor robustez, se recomienda probar transformaciones (log-precio) y segmentación por subzonas.
Fase 5: Predicción
Se estimaron precios esperados y PI95% para los perfiles solicitados (Casa Norte y Apto Sur). Las predicciones permiten anticipar si el crédito disponible es suficiente. En ambos casos, los valores estimados se encuentran dentro del rango esperado del mercado, aunque en Vivienda 2 el margen es más ajustado.
Fase 6: Recomendación de ofertas
Se filtraron propiedades reales que cumplen con las condiciones y el tope de crédito, mostrando al menos 5 opciones en mapas interactivos. Se identificaron alternativas viables para ambas solicitudes. Esto respalda la toma de decisiones y reduce el riesgo de sobrepasar el presupuesto.
Fase 7: Validación Cruzada
Se aplicó validación cruzada (k-fold) para estimar la estabilidad del modelo. El R² en validación es cercano al R² de entrenamiento, lo que indica bajo riesgo de sobreajuste. Esto refuerza la confiabilidad del modelo para predicciones futuras.
Conclusión Final
El flujo metodológico (filtrado → EDA → modelación → validación → predicción → recomendación → validación cruzada) garantiza un análisis robusto y reproducible. Las recomendaciones están basadas en evidencia estadística y visual, lo que aporta confianza a la decisión de compra. El uso de mapas interactivos y validación cruzada fortalece la presentación ejecutiva y la credibilidad técnica.