# Paquetes
library(tidyverse)
library(janitor)
library(skimr)
library(plotly)
library(leaflet)
library(htmlwidgets)
library(broom)
library(lmtest)
library(car)
library(paqueteMODELOS)
data("vivienda")
df0 <- vivienda %>% as_tibble() %>% clean_names()
# Estructura
glimpse(df0)## 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…
base1 <- df %>% filter(tipo == "Casa", str_detect(zona, regex("Norte", ignore_case = TRUE)))
# Tablas de verificación
tabla_tipo <- base1 %>% count(tipo)
tabla_zona <- base1 %>% count(zona)
tabla_estrato <- base1 %>% count(estrato)
list(tabla_tipo = tabla_tipo, tabla_zona = tabla_zona, tabla_estrato = tabla_estrato)## $tabla_tipo
## # A tibble: 1 × 2
## tipo n
## <fct> <int>
## 1 Casa 722
##
## $tabla_zona
## # A tibble: 1 × 2
## zona n
## <fct> <int>
## 1 Zona Norte 722
##
## $tabla_estrato
## # A tibble: 4 × 2
## estrato n
## <dbl> <int>
## 1 3 235
## 2 4 161
## 3 5 271
## 4 6 55
vars_corr <- base1 %>%
select(preciom, areaconst, estrato, banios, habitaciones) %>%
drop_na()
# Dispersión interactiva Precio vs Área
p_area <- plot_ly(vars_corr, x = ~areaconst, y = ~preciom, type = "scatter", mode = "markers",
hoverinfo = "text",
text = ~paste("Área:", areaconst, "<br>Precio:", round(preciom,1),"M")) %>%
layout(title = "Precio vs Área construida (Casas - Norte)",
xaxis = list(title = "Área construida (m²)"),
yaxis = list(title = "Precio (M COP)"))
p_area# Box interactivo Precio por estrato (plotly)
p_estrato <- vars_corr %>%
mutate(estrato = as.factor(estrato)) %>%
plot_ly(x = ~estrato, y = ~preciom, type = "box") %>%
layout(title = "Precio por estrato (Casas - Norte)",
xaxis = list(title = "Estrato"),
yaxis = list(title = "Precio (M COP)"))
p_estrato## preciom areaconst estrato banios habitaciones
## preciom 1.0000000 0.7313480 0.6123503 0.5233357 0.3227096
## areaconst 0.7313480 1.0000000 0.4573818 0.4628152 0.3753323
## estrato 0.6123503 0.4573818 1.0000000 0.4083039 0.1073141
## banios 0.5233357 0.4628152 0.4083039 1.0000000 0.5755314
## habitaciones 0.3227096 0.3753323 0.1073141 0.5755314 1.0000000
m_base1 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = base1)
summary(m_base1)##
## 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
## (287 observations deleted due to missingness)
## Multiple R-squared: 0.6041, Adjusted R-squared: 0.5995
## F-statistic: 130.9 on 5 and 429 DF, p-value: < 2.2e-16
## # A tibble: 1 × 12
## r.squared adj.r.squared sigma statistic p.value df logLik AIC BIC
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0.604 0.599 155. 131. 5.42e-84 5 -2808. 5631. 5659.
## # ℹ 3 more variables: deviance <dbl>, df.residual <int>, nobs <int>
## # A tibble: 6 × 7
## term estimate std.error statistic p.value conf.low conf.high
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 (Intercept) -238. 44.4 -5.36 1.34e- 7 -325. -151.
## 2 areaconst 0.677 0.0528 12.8 4.70e-32 0.573 0.781
## 3 estrato 80.6 9.83 8.21 2.70e-15 61.3 99.9
## 4 habitaciones 7.65 5.66 1.35 1.77e- 1 -3.48 18.8
## 5 parqueaderos 24.0 5.87 4.09 5.14e- 5 12.5 35.5
## 6 banios 18.9 7.49 2.52 1.20e- 2 4.18 33.6
par(mfrow = c(1,1))
# Heterocedasticidad
bp1 <- bptest(m_base1)
# Autocorrelación de residuos (poco esperable en cortes transversales, se reporta por completitud)
dw1 <- durbinWatsonTest(m_base1)
# Multicolinealidad
vifs1 <- car::vif(m_base1)
list(
breusch_pagan = bp1,
durbin_watson = dw1,
vif = vifs1
)## $breusch_pagan
##
## studentized Breusch-Pagan test
##
## data: m_base1
## BP = 80.281, df = 5, p-value = 7.33e-16
##
##
## $durbin_watson
## lag Autocorrelation D-W Statistic p-value
## 1 0.1173407 1.761505 0.02
## Alternative hypothesis: rho != 0
##
## $vif
## areaconst estrato habitaciones parqueaderos banios
## 1.460998 1.307757 1.721015 1.226334 1.967421
Características solicitadas: área = 200 m², parqueaderos = 1, baños = 2, habitaciones = 4, estrato = 4 o 5, zona Norte.
nuevo_v1 <- tibble(
areaconst = 200,
estrato = c(4,5),
habitaciones = 4,
parqueaderos = 1,
banios = 2
)
pred_v1 <- predict(m_base1, newdata = nuevo_v1, interval = "prediction")
cbind(nuevo_v1, as_tibble(pred_v1))## # A tibble: 2 × 8
## areaconst estrato habitaciones parqueaderos banios fit lwr upr
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 200 4 4 1 2 312. 6.21 618.
## 2 200 5 4 1 2 393. 86.2 699.
# Filtro por criterios base
cand_v1 <- base1 %>%
filter(preciom <= 350,
areaconst >= 140, areaconst <= 260, # tolerancia ±30% alrededor de 200
banios >= 2, habitaciones >= 3) %>%
mutate(
score = (abs(areaconst - 200)/200) +
(abs(habitaciones - 4)/4) +
(abs(parqueaderos - 1)/max(1,parqueaderos)) +
(abs(banios - 2)/max(1,banios)) +
(pmin(abs(estrato - 4), abs(estrato - 5)) / max(1,estrato))
) %>%
arrange(score)
head(select(cand_v1, barrio, zona, estrato, areaconst, habitaciones, parqueaderos, banios, preciom, longitud, latitud), 10)## # A tibble: 10 × 10
## barrio zona estrato areaconst habitaciones parqueaderos banios preciom
## <fct> <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 acopi Zona… 5 150 6 2 4 320
## 2 acopi Zona… 3 160 3 NA 2 230
## 3 acopi Zona… 3 162 5 NA 3 295
## 4 acopi Zona… 4 190 3 NA 2 275
## 5 acopi Zona… 4 180 3 NA 3 280
## 6 acopi Zona… 4 240 4 NA 4 330
## 7 alameda del… Zona… 3 148 4 2 4 280
## 8 alamos Zona… 3 150 4 1 2 220
## 9 barranquilla Zona… 3 240 4 1 2 250
## 10 barranquilla Zona… 3 150 4 NA 4 243
## # ℹ 2 more variables: longitud <dbl>, latitud <dbl>
top5_v1 <- cand_v1 %>% slice_head(n = 5)
if(nrow(top5_v1) > 0 && all(c("longitud","latitud") %in% names(top5_v1))){
m_v1 <- leaflet(top5_v1) %>% addTiles() %>%
addCircleMarkers(~longitud, ~latitud,
popup = ~paste0(
"Barrio: ", barrio,
"<br>Estrato: ", estrato,
"<br>Área: ", areaconst, " m²",
"<br>Habitaciones: ", habitaciones,
"<br>Baños: ", banios,
"<br>Parq: ", parqueaderos,
"<br><b>Precio:</b> ", round(preciom,1), " M"
),
radius = 6, opacity = 0.95)
m_v1
} else {
"No hay coordenadas o no se encontraron 5 ofertas candidatas bajo los criterios."
}base2 <- df %>% filter(tipo == "Apartamento", str_detect(zona, regex("Sur", ignore_case = TRUE)))
# Primeros 3 registros
head(base2, 3)## # A tibble: 3 × 13
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <dbl> <fct> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 5098 Zona S… 05 4 290 96 1 2 3
## 2 698 Zona S… 02 3 78 40 1 1 2
## 3 8199 Zona S… <NA> 6 875 194 2 5 3
## # ℹ 4 more variables: tipo <fct>, barrio <fct>, longitud <dbl>, latitud <dbl>
# Tablas de verificación
tabla2_tipo <- base2 %>% count(tipo)
tabla2_zona <- base2 %>% count(zona)
tabla2_estrato <- base2 %>% count(estrato)
list(tabla_tipo = tabla2_tipo, tabla_zona = tabla2_zona, tabla_estrato = tabla2_estrato)## $tabla_tipo
## # A tibble: 1 × 2
## tipo n
## <fct> <int>
## 1 Apartamento 2787
##
## $tabla_zona
## # A tibble: 1 × 2
## zona n
## <fct> <int>
## 1 Zona Sur 2787
##
## $tabla_estrato
## # A tibble: 4 × 2
## estrato n
## <dbl> <int>
## 1 3 201
## 2 4 1091
## 3 5 1033
## 4 6 462
vars2 <- base2 %>%
select(preciom, areaconst, estrato, banios, habitaciones) %>%
drop_na()
p_area2 <- plot_ly(vars2, x = ~areaconst, y = ~preciom, type = "scatter", mode = "markers",
hoverinfo = "text",
text = ~paste("Área:", areaconst, "<br>Precio:", round(preciom,1),"M")) %>%
layout(title = "Precio vs Área (Aptos - Sur)",
xaxis = list(title = "Área construida (m²)"),
yaxis = list(title = "Precio (M COP)"))
p_area2m_base2 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = base2)
summary(m_base2)##
## 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
## (406 observations deleted due to missingness)
## Multiple R-squared: 0.7485, Adjusted R-squared: 0.748
## F-statistic: 1414 on 5 and 2375 DF, p-value: < 2.2e-16
## # A tibble: 1 × 12
## r.squared adj.r.squared sigma statistic p.value df logLik AIC BIC
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0.749 0.748 98.0 1414. 0 5 -14293. 28600. 28640.
## # ℹ 3 more variables: deviance <dbl>, df.residual <int>, nobs <int>
## # A tibble: 6 × 7
## term estimate std.error statistic p.value conf.low conf.high
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 (Intercept) -262. 15.6 -16.7 1.60e- 59 -292. -231.
## 2 areaconst 1.29 0.0540 23.8 2.39e-112 1.18 1.39
## 3 estrato 60.9 3.08 19.7 1.76e- 80 54.8 66.9
## 4 habitaciones -24.8 3.89 -6.38 2.11e- 10 -32.5 -17.2
## 5 parqueaderos 72.9 3.96 18.4 6.04e- 71 65.2 80.7
## 6 banios 50.7 3.40 14.9 3.16e- 48 44.0 57.4
par(mfrow = c(1,1))
bp2 <- bptest(m_base2)
dw2 <- durbinWatsonTest(m_base2)
vifs2 <- car::vif(m_base2)
list(
breusch_pagan = bp2,
durbin_watson = dw2,
vif = vifs2
)## $breusch_pagan
##
## studentized Breusch-Pagan test
##
## data: m_base2
## BP = 754.81, df = 5, p-value < 2.2e-16
##
##
## $durbin_watson
## lag Autocorrelation D-W Statistic p-value
## 1 0.2314604 1.533316 0
## Alternative hypothesis: rho != 0
##
## $vif
## areaconst estrato habitaciones parqueaderos banios
## 2.066518 1.545162 1.429280 1.737878 2.529494
Características: área = 300 m², parqueaderos = 3, baños = 3, habitaciones = 5, estrato = 5 o 6, zona Sur.
nuevo_v2 <- tibble(
areaconst = 300,
estrato = c(5,6),
habitaciones = 5,
parqueaderos = 3,
banios = 3
)
pred_v2 <- predict(m_base2, newdata = nuevo_v2, interval = "prediction")
cbind(nuevo_v2, as_tibble(pred_v2))## # A tibble: 2 × 8
## areaconst estrato habitaciones parqueaderos banios fit lwr upr
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 300 5 5 3 3 675. 481. 869.
## 2 300 6 5 3 3 736. 542. 930.
cand_v2 <- base2 %>%
filter(preciom <= 850,
areaconst >= 210, areaconst <= 390, # tolerancia ±30% alrededor de 300
banios >= 3, habitaciones >= 4) %>%
mutate(
score = (abs(areaconst - 300)/300) +
(abs(habitaciones - 5)/5) +
(abs(parqueaderos - 3)/max(1,parqueaderos)) +
(abs(banios - 3)/max(1,banios)) +
(pmin(abs(estrato - 5), abs(estrato - 6)) / max(1,estrato))
) %>%
arrange(score)
head(select(cand_v2, barrio, zona, estrato, areaconst, habitaciones, parqueaderos, banios, preciom, longitud, latitud), 10)## # A tibble: 10 × 10
## barrio zona estrato areaconst habitaciones parqueaderos banios preciom
## <fct> <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 capri Zona… 5 270 4 3 3 350
## 2 ciudadela p… Zona… 5 275 5 2 5 650
## 3 ciudadela p… Zona… 5 249 4 2 4 650
## 4 colseguros Zona… 4 300 6 NA 5 390
## 5 colseguros Zona… 4 217 5 2 5 390
## 6 cuarto de l… Zona… 5 217. 5 1 4 321
## 7 cuarto de l… Zona… 5 220 5 2 5 420
## 8 cuarto de l… Zona… 5 296. 4 2 4 410
## 9 cuarto de l… Zona… 5 288 4 1 5 490
## 10 cuarto de l… Zona… 5 320 4 2 4 520
## # ℹ 2 more variables: longitud <dbl>, latitud <dbl>
top5_v2 <- cand_v2 %>% slice_head(n = 5)
if(nrow(top5_v2) > 0 && all(c("longitud","latitud") %in% names(top5_v2))){
m_v2 <- leaflet(top5_v2) %>% addTiles() %>%
addCircleMarkers(~longitud, ~latitud,
popup = ~paste0(
"Barrio: ", barrio,
"<br>Estrato: ", estrato,
"<br>Área: ", areaconst, " m²",
"<br>Habitaciones: ", habitaciones,
"<br>Baños: ", banios,
"<br>Parq: ", parqueaderos,
"<br><b>Precio:</b> ", round(preciom,1), " M"
),
radius = 6, opacity = 0.95)
m_v2
} else {
"No hay coordenadas o no se encontraron 5 ofertas candidatas bajo los criterios."
}```