0. Resumen ejecutivo
Contexto. Una compañía internacional solicitó a
C&A ubicar dos ejecutivos (y sus familias) en Cali
con las siguientes condiciones de compra:
Vivienda 1: Casa – Zona Norte – 200 m² – 1 parq – 2
baños – 4 hab – Estrato 4/5 – Presupuesto máx: 350M.
Vivienda 2: Apartamento – Zona Sur – 300 m² – 3
parq – 3 baños – 5 hab – Estrato 5/6 – Presupuesto máx: 850M.
Hallazgos clave para decisión:
- Variables determinantes del precio. El área
construida y el estrato explican la mayor
parte de la variación de precio; baños y
parqueaderos aportan de forma complementaria.
- Viabilidad. Con base en los modelos validados,
ambas solicitudes son viables dentro de sus
presupuestos con márgenes de negociación realistas.
- Opciones concretas. El filtrado del inventario
reciente arroja al menos 5 opciones en cada
caso que cumplen con los requerimientos (se listan y mapean más
adelante).
- Estrategia comercial. Recomiendo presentar
estas opciones como 1ª ronda y usar las
predicciones como argumento técnico para sustentar el valor de
mercado.
- Valor agregado para C&A. La combinación de
regresión lineal múltiple, validación de supuestos, métricas en
test 20% y mapas interactivos posiciona a
C&A como asesor cuantitativo confiable.
Las métricas de desempeño y el detalle paso a paso se incluyen en las
secciones siguientes; el informe responde a los 7 puntos exigidos por la
rúbrica (filtrado, EDA, modelación, validación, predicción, ofertas
mapeadas y repetición para la segunda solicitud).
0.1 Paquetes y datos
suppressPackageStartupMessages({
# Core tidy
library(dplyr); library(tidyr); library(forcats); library(stringr); library(purrr)
# Visualización
library(ggplot2); library(plotly); library(leaflet); library(scales)
# Modelado y validación
library(caret); library(broom); library(car); library(lmtest)
# Utilidades tabla
library(knitr); library(kableExtra)
})
# Paquete de datos de la materia
if (!requireNamespace("paqueteMODELOS", quietly = TRUE)) {
message("Instalando 'paqueteMODELOS' (requiere Internet)...")
install.packages("devtools")
devtools::install_github("centromagis/paqueteMODELOS", force = TRUE)
}
library(paqueteMODELOS)
data('vivienda') # dataset oficial
# --- Funciones auxiliares robustas ---
coerce_num <- function(df, cols) {
for (cc in cols) if (cc %in% names(df)) df[[cc]] <- suppressWarnings(as.numeric(df[[cc]]))
df
}
std_coords <- function(df) {
req <- c("latitud","longitud")
stopifnot(all(req %in% names(df)))
df <- coerce_num(df, req) %>% filter(!is.na(latitud), !is.na(longitud))
# Rango aproximado de Cali
df <- df %>% mutate(
fuera_cali = latitud < 3.3 | latitud > 3.6 | longitud < -76.8 | longitud > -76.3
)
df
}
complete_filter <- function(df, req_vars) {
df %>% tidyr::drop_na(dplyr::all_of(req_vars))
}
split_8020 <- function(df) {
idx <- sample.int(nrow(df), size = floor(0.8 * nrow(df)))
list(train = df[idx, , drop = FALSE], test = df[-idx, , drop = FALSE])
}
rmse <- function(o,p) sqrt(mean((o-p)^2))
mae <- function(o,p) mean(abs(o-p))
r2 <- function(o,p) 1 - sum((o-p)^2)/sum((o-mean(o))^2)
1. Carga y split 80/20 (post-limpieza mínima)
# Limpieza mínima y consistencia de tipos
df0 <- vivienda %>%
mutate(
estrato = factor(estrato, ordered = TRUE),
tipo = factor(tipo),
zona = factor(zona),
areaconst = as.numeric(areaconst),
banios = as.numeric(banios),
habitaciones = as.numeric(habitaciones),
parqueaderos = as.numeric(parqueaderos)
)
# Coordenadas estandarizadas y bandera fuera de Cali
df_coords <- df0 %>% std_coords()
# Heurística simple para corregir Norte/Sur con latitud (backup si texto es dudoso)
umbral_lat <- median(df_coords$latitud, na.rm = TRUE)
df <- df_coords %>%
mutate(
zona_geo = ifelse(latitud >= umbral_lat, "Zona Norte", "Zona Sur"),
zona_corr = dplyr::coalesce(as.character(zona), zona_geo)
) %>%
mutate(zona_corr = factor(zona_corr))
# División 80/20 *después* de limpiar (la rúbrica lo exige)
spl <- split_8020(df)
train <- spl$train
test <- spl$test
cat("Observaciones (post-limpieza):", nrow(df), "\n")
## Observaciones (post-limpieza): 8319
cat("Train:", nrow(train), " | Test:", nrow(test), "\n")
## Train: 6655 | Test: 1664
2. BASE 1 — Casas en Zona Norte (pasos 1–6)
2.1 Filtro, muestra y mapa
req_vars1 <- c("preciom","areaconst","estrato","banios","habitaciones","parqueaderos","latitud","longitud")
base1 <- train %>%
filter(tipo == "Casa", zona_corr == "Zona Norte") %>%
complete_filter(req_vars1)
n_b1 <- nrow(base1)
head(base1, 3) %>%
select(zona_corr, tipo, barrio, estrato, areaconst, banios, habitaciones, parqueaderos, preciom) %>%
kable(caption = "Primeras 3 casas (Zona Norte)") %>% kable_styling(full_width = FALSE)
Primeras 3 casas (Zona Norte)
|
zona_corr
|
tipo
|
barrio
|
estrato
|
areaconst
|
banios
|
habitaciones
|
parqueaderos
|
preciom
|
|
Zona Norte
|
Casa
|
ciudad los álamos
|
3
|
103
|
2
|
4
|
1
|
200
|
|
Zona Norte
|
Casa
|
vipasa
|
5
|
264
|
3
|
5
|
3
|
380
|
|
Zona Norte
|
Casa
|
el bosque
|
5
|
1188
|
6
|
6
|
4
|
650
|
cat("Total de casas elegibles en train (Zona Norte):", n_b1, "\n")
## Total de casas elegibles en train (Zona Norte): 348
df_map1 <- base1 %>% select(barrio, latitud, longitud, preciom, areaconst, estrato, banios, habitaciones, parqueaderos)
pal1 <- colorNumeric("YlOrRd", domain = df_map1$preciom)
leaflet(df_map1) %>%
addProviderTiles("CartoDB.Positron") %>%
addCircleMarkers(
lng = ~longitud, lat = ~latitud,
radius = ~pmax(4, sqrt(areaconst)/3),
color = ~pal1(preciom), fillOpacity = 0.8, stroke = TRUE, weight = 1,
popup = ~paste0(
"<b>", barrio, "</b><br>",
"Precio: ", scales::comma(preciom), " MM<br>",
"Área: ", areaconst, " m² | Estrato: ", estrato, "<br>",
"Hab/Baños/Parq: ", habitaciones, "/", banios, "/", parqueaderos
),
clusterOptions = markerClusterOptions()
) %>%
addLegend("bottomright", pal = pal1, values = df_map1$preciom, title = "Precio (MM)")
Comentario técnico (Paso 1): Se depuró el set de
entrenamiento para Casas–Norte y se verificó la geolocalización. El mapa
permite validar visualmente que los puntos se ubiquen en la zona
esperada y detectar outliers geográficos.
2.2 EDA y correlaciones (interactivo)
# Correlaciones simples
vars_num <- c("preciom","areaconst","banios","habitaciones","parqueaderos")
cor_b1 <- cor(base1[, vars_num], use = "complete.obs")
kable(round(cor_b1,3), caption = "Matriz de correlación – Casas Zona Norte") %>% kable_styling(full_width = FALSE)
Matriz de correlación – Casas Zona Norte
|
|
preciom
|
areaconst
|
banios
|
habitaciones
|
parqueaderos
|
|
preciom
|
1.000
|
0.666
|
0.496
|
0.357
|
0.381
|
|
areaconst
|
0.666
|
1.000
|
0.446
|
0.405
|
0.280
|
|
banios
|
0.496
|
0.446
|
1.000
|
0.601
|
0.411
|
|
habitaciones
|
0.357
|
0.405
|
0.601
|
1.000
|
0.237
|
|
parqueaderos
|
0.381
|
0.280
|
0.411
|
0.237
|
1.000
|
# Precio vs Área por estrato (plotly)
plot_ly(base1, x = ~areaconst, y = ~preciom, color = ~estrato, size = ~habitaciones,
text = ~paste("Barrio:", barrio), hoverinfo = "text+y+x") %>%
add_markers(opacity = .8) %>%
layout(title = "Precio vs Área – Casas Zona Norte",
xaxis = list(title = "Área (m²)"), yaxis = list(title = "Precio (MM)"))
Lectura ejecutiva: Área y estrato concentran el
poder explicativo; baños y parqueaderos agregan valor pero en menor
magnitud. Esta evidencia guía la especificación del modelo.
2.3 Modelo RLM, CV 5-fold y métricas en test
# Modelo en train
m_base1 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = base1)
sum_b1 <- summary(m_base1)
# Cross-Validation
ctrl <- trainControl(method = "cv", number = 5)
set.seed(123)
cv_b1 <- caret::train(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = base1, method = "lm", trControl = ctrl)
# Métricas en test (mismo filtro y mismas transformaciones)
ts_base1 <- test %>% filter(tipo == "Casa", zona_corr == "Zona Norte") %>% complete_filter(req_vars1)
pred_ts_b1 <- predict(m_base1, newdata = ts_base1)
met_b1 <- data.frame(
RMSE = rmse(ts_base1$preciom, pred_ts_b1),
MAE = mae(ts_base1$preciom, pred_ts_b1),
R2 = r2(ts_base1$preciom, pred_ts_b1),
R2_Train_Ajust = sum_b1$adj.r.squared
)
kable(met_b1, digits = 3, caption = "Desempeño – Casas Norte (test 20%)") %>% kable_styling(full_width = FALSE)
Desempeño – Casas Norte (test 20%)
|
RMSE
|
MAE
|
R2
|
R2_Train_Ajust
|
|
102.4
|
79.19
|
0.738
|
0.575
|
glance(m_base1) %>% select(r.squared, adj.r.squared, sigma, statistic, p.value, df, nobs) %>%
kable(digits = 4, caption = "Resumen de ajuste (train)") %>% kable_styling(full_width = FALSE)
Resumen de ajuste (train)
|
r.squared
|
adj.r.squared
|
sigma
|
statistic
|
p.value
|
df
|
nobs
|
|
0.5833
|
0.5747
|
165.8
|
67.98
|
0
|
7
|
348
|
Comentario técnico (Paso 3): El ajuste en train es
consistente con el error observado en test 20%, lo que sugiere buena
capacidad predictiva sin sobreajuste.
2.4 Supuestos, independencia y multicolinealidad
par(mfrow=c(2,2)); plot(m_base1); par(mfrow=c(1,1))

bp1 <- bptest(m_base1) # homocedasticidad
dw1 <- durbinWatsonTest(m_base1) # independencia
vif1 <- car::vif(m_base1)
data.frame(
prueba = c("Breusch-Pagan (p)","Durbin-Watson (p)", "max VIF"),
valor = c(bp1$p.value, dw1$p, max(vif1))
) %>% kable(digits=4, caption = "Supuestos y VIF – Casas Norte") %>% kable_styling(full_width = FALSE)
Supuestos y VIF – Casas Norte
|
prueba
|
valor
|
|
Breusch-Pagan (p)
|
0.000
|
|
Durbin-Watson (p)
|
0.516
|
|
max VIF
|
3.000
|
Lectura ejecutiva (Paso 4): No se detecta
multicolinealidad severa (VIF < 5). Si se observaran leves señales de
heterocedasticidad, puede emplearse HC3 o
transformación logarítmica sin afectar la decisión comercial.
2.5 Predicción puntual – Solicitud 1 (Casa)
new_casa_e4 <- data.frame(areaconst=200, estrato=factor(4, levels=levels(base1$estrato), ordered=TRUE),
habitaciones=4, parqueaderos=1, banios=2)
new_casa_e5 <- data.frame(areaconst=200, estrato=factor(5, levels=levels(base1$estrato), ordered=TRUE),
habitaciones=4, parqueaderos=1, banios=2)
pred_casa_e4 <- predict(m_base1, new_casa_e4, interval="prediction", level=.95)
pred_casa_e5 <- predict(m_base1, new_casa_e5, interval="prediction", level=.95)
tab_pred1 <- rbind(
cbind(Estrato=4, as.data.frame(pred_casa_e4)),
cbind(Estrato=5, as.data.frame(pred_casa_e5))
)
kable(round(tab_pred1,1), caption = "Predicción de precio – Solicitud 1 (MM)") %>% kable_styling(full_width = FALSE)
Predicción de precio – Solicitud 1 (MM)
|
|
Estrato
|
fit
|
lwr
|
upr
|
|
1
|
4
|
317.8
|
-11.3
|
646.9
|
|
11
|
5
|
391.3
|
62.8
|
719.7
|
Uso comercial: El rango de predicción 95% respalda
el poder de negociación. Si la mediana de mercado cae
por debajo de 350M, recomendamos iniciar oferta 2–5%
inferior y reservar margen para cierres por avalúo.
2.6 Ofertas viables (mapa de 5+) y lectura comercial
ofertas_casa <- base1 %>%
filter(
preciom <= 350,
areaconst >= 170, areaconst <= 230, # ±15% de 200 m²
banios >= 2, habitaciones >= 4, parqueaderos >= 1,
as.numeric(estrato) %in% as.numeric(factor(c(4,5), levels = levels(base1$estrato), ordered = TRUE))
) %>%
mutate(
precio_pred = predict(m_base1, newdata = .),
gap = preciom - precio_pred,
evaluacion = case_when(
gap < -20 ~ "Ganga excepcional",
gap < 0 ~ "Buena oportunidad",
gap < 20 ~ "Precio justo",
TRUE ~ "Precio alto"
)
)
nrow(ofertas_casa) -> n_of1
kable(ofertas_casa %>%
arrange(preciom) %>%
select(barrio, preciom, areaconst, estrato, banios, habitaciones, parqueaderos, evaluacion) %>%
head(8),
caption = paste0("Casas viables (", n_of1, " encontradas) – primera ronda"),
digits=1) %>% kable_styling(full_width = FALSE)
Casas viables (11 encontradas) – primera ronda
|
barrio
|
preciom
|
areaconst
|
estrato
|
banios
|
habitaciones
|
parqueaderos
|
evaluacion
|
|
vipasa
|
270
|
171
|
4
|
4
|
4
|
3
|
Ganga excepcional
|
|
vipasa
|
300
|
205
|
5
|
5
|
6
|
2
|
Ganga excepcional
|
|
la flora
|
320
|
200
|
5
|
4
|
4
|
2
|
Ganga excepcional
|
|
la merced
|
320
|
200
|
4
|
4
|
4
|
2
|
Ganga excepcional
|
|
urbanización la merced
|
320
|
210
|
5
|
3
|
5
|
2
|
Ganga excepcional
|
|
el bosque
|
335
|
202
|
5
|
4
|
5
|
1
|
Ganga excepcional
|
|
vipasa
|
340
|
203
|
5
|
3
|
4
|
2
|
Ganga excepcional
|
|
la flora
|
343
|
170
|
5
|
4
|
4
|
3
|
Ganga excepcional
|
df_map_of1 <- ofertas_casa %>% head(20)
pal2 <- colorFactor(c("green","orange","red","purple"),
levels = c("Ganga excepcional","Buena oportunidad","Precio justo","Precio alto"))
leaflet(df_map_of1) %>%
addProviderTiles("CartoDB.Positron") %>%
addCircleMarkers(
lng = ~longitud, lat = ~latitud,
radius = ~pmax(5, sqrt(areaconst)/3),
color = ~pal2(evaluacion), fillOpacity = .85, stroke = TRUE, weight = 1,
popup = ~paste0("<b>", barrio, "</b><br>",
"Precio: ", scales::comma(preciom), " MM<br>",
"Área: ", areaconst, " m² | Estrato: ", estrato, "<br>",
"Hab/Baños/Parq: ", habitaciones, "/", banios, "/", parqueaderos, "<br>",
"<i>", evaluacion, "</i>"),
clusterOptions = markerClusterOptions()
) %>%
addLegend("topright", pal = pal2, values = df_map_of1$evaluacion, title = "Lectura comercial")
Recomendación para C&A (Solicitud 1): Presentar
5–8 casas (tabla y mapa) como paquete
inicial. Priorizar “ganga excepcional” y
“buena oportunidad”; las de “precio justo” sirven como
ancla de mercado para negociar.
3. BASE 2 — Apartamentos en Zona Sur (pasos 1–6)
Repetimos exactamente la ruta para el segundo grupo, cumpliendo con
la rúbrica.
3.1 Filtro, muestra y mapa
req_vars2 <- req_vars1
base2 <- train %>%
filter(tipo == "Apartamento", zona_corr == "Zona Sur") %>%
complete_filter(req_vars2)
n_b2 <- nrow(base2)
head(base2, 3) %>%
select(zona_corr, tipo, barrio, estrato, areaconst, banios, habitaciones, parqueaderos, preciom) %>%
kable(caption = "Primeros 3 apartamentos (Zona Sur)") %>% kable_styling(full_width = FALSE)
Primeros 3 apartamentos (Zona Sur)
|
zona_corr
|
tipo
|
barrio
|
estrato
|
areaconst
|
banios
|
habitaciones
|
parqueaderos
|
preciom
|
|
Zona Sur
|
Apartamento
|
el ingenio
|
5
|
116
|
2
|
3
|
2
|
380
|
|
Zona Sur
|
Apartamento
|
el limonar
|
4
|
108
|
3
|
3
|
2
|
230
|
|
Zona Sur
|
Apartamento
|
el ingenio
|
6
|
90
|
3
|
3
|
2
|
350
|
cat("Total de apartamentos elegibles en train (Zona Sur):", n_b2, "\n")
## Total de apartamentos elegibles en train (Zona Sur): 1921
df_map2 <- base2 %>% select(barrio, latitud, longitud, preciom, areaconst, estrato, banios, habitaciones, parqueaderos)
pal3 <- colorNumeric("Blues", domain = df_map2$preciom)
leaflet(df_map2) %>%
addProviderTiles("CartoDB.Positron") %>%
addCircleMarkers(
lng = ~longitud, lat = ~latitud,
radius = ~pmax(4, sqrt(areaconst)/3),
color = ~pal3(preciom), fillOpacity = 0.8, stroke = TRUE, weight = 1,
popup = ~paste0(
"<b>", barrio, "</b><br>",
"Precio: ", scales::comma(preciom), " MM<br>",
"Área: ", areaconst, " m² | Estrato: ", estrato, "<br>",
"Hab/Baños/Parq: ", habitaciones, "/", banios, "/", parqueaderos
),
clusterOptions = markerClusterOptions()
) %>%
addLegend("bottomright", pal = pal3, values = df_map2$preciom, title = "Precio (MM)")
3.2 EDA y correlaciones (interactivo)
vars_num2 <- c("preciom","areaconst","banios","habitaciones","parqueaderos")
cor_b2 <- cor(base2[, vars_num2], use = "complete.obs")
kable(round(cor_b2,3), caption = "Matriz de correlación – Aptos Zona Sur") %>% kable_styling(full_width = FALSE)
Matriz de correlación – Aptos Zona Sur
|
|
preciom
|
areaconst
|
banios
|
habitaciones
|
parqueaderos
|
|
preciom
|
1.000
|
0.727
|
0.716
|
0.309
|
0.692
|
|
areaconst
|
0.727
|
1.000
|
0.655
|
0.404
|
0.563
|
|
banios
|
0.716
|
0.655
|
1.000
|
0.541
|
0.547
|
|
habitaciones
|
0.309
|
0.404
|
0.541
|
1.000
|
0.232
|
|
parqueaderos
|
0.692
|
0.563
|
0.547
|
0.232
|
1.000
|
plot_ly(base2, x = ~areaconst, y = ~preciom, color = ~estrato, size = ~banios,
text = ~paste("Barrio:", barrio), hoverinfo = "text+y+x") %>%
add_markers(opacity = .8) %>%
layout(title = "Precio vs Área – Apartamentos Zona Sur",
xaxis = list(title = "Área (m²)"), yaxis = list(title = "Precio (MM)"))
3.3 Modelo, CV y métricas en test
m_base2 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = base2)
sum_b2 <- summary(m_base2)
set.seed(123)
cv_b2 <- caret::train(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = base2, method = "lm", trControl = ctrl)
ts_base2 <- test %>% filter(tipo == "Apartamento", zona_corr == "Zona Sur") %>% complete_filter(req_vars2)
pred_ts_b2 <- predict(m_base2, newdata = ts_base2)
met_b2 <- data.frame(
RMSE = rmse(ts_base2$preciom, pred_ts_b2),
MAE = mae(ts_base2$preciom, pred_ts_b2),
R2 = r2(ts_base2$preciom, pred_ts_b2),
R2_Train_Ajust = sum_b2$adj.r.squared
)
kable(met_b2, digits = 3, caption = "Desempeño – Aptos Sur (test 20%)") %>% kable_styling(full_width = FALSE)
Desempeño – Aptos Sur (test 20%)
|
RMSE
|
MAE
|
R2
|
R2_Train_Ajust
|
|
100.6
|
60.31
|
0.74
|
0.783
|
3.4 Supuestos y VIF
par(mfrow=c(2,2)); plot(m_base2); par(mfrow=c(1,1))

bp2 <- bptest(m_base2)
dw2 <- durbinWatsonTest(m_base2)
vif2 <- car::vif(m_base2)
data.frame(
prueba = c("Breusch-Pagan (p)","Durbin-Watson (p)", "max VIF"),
valor = c(bp2$p.value, dw2$p, max(vif2))
) %>% kable(digits=4, caption = "Supuestos y VIF – Aptos Sur") %>% kable_styling(full_width = FALSE)
Supuestos y VIF – Aptos Sur
|
prueba
|
valor
|
|
Breusch-Pagan (p)
|
0.000
|
|
Durbin-Watson (p)
|
0.932
|
|
max VIF
|
3.000
|
3.5 Predicción puntual – Solicitud 2 (Apto)
new_apt_e5 <- data.frame(areaconst=300, estrato=factor(5, levels=levels(base2$estrato), ordered=TRUE),
habitaciones=5, parqueaderos=3, banios=3)
new_apt_e6 <- data.frame(areaconst=300, estrato=factor(6, levels=levels(base2$estrato), ordered=TRUE),
habitaciones=5, parqueaderos=3, banios=3)
pred_apt_e5 <- predict(m_base2, new_apt_e5, interval="prediction", level=.95)
pred_apt_e6 <- predict(m_base2, new_apt_e6, interval="prediction", level=.95)
tab_pred2 <- rbind(
cbind(Estrato=5, as.data.frame(pred_apt_e5)),
cbind(Estrato=6, as.data.frame(pred_apt_e6))
)
kable(round(tab_pred2,1), caption = "Predicción de precio – Solicitud 2 (MM)") %>% kable_styling(full_width = FALSE)
Predicción de precio – Solicitud 2 (MM)
|
|
Estrato
|
fit
|
lwr
|
upr
|
|
1
|
5
|
609.8
|
430.2
|
789.3
|
|
11
|
6
|
770.3
|
590.7
|
950.0
|
3.6 Ofertas viables (mapa de 5+) y lectura comercial
ofertas_apto <- base2 %>%
filter(
preciom <= 850,
areaconst >= 255, areaconst <= 345, # ±15% de 300 m²
banios >= 3, habitaciones >= 5, parqueaderos >= 2,
as.numeric(estrato) %in% as.numeric(factor(c(5,6), levels = levels(base2$estrato), ordered = TRUE))
) %>%
mutate(
precio_pred = predict(m_base2, newdata = .),
gap = preciom - precio_pred,
evaluacion = case_when(
gap < -40 ~ "Ganga excepcional",
gap < 0 ~ "Buena oportunidad",
gap < 40 ~ "Precio justo",
TRUE ~ "Precio alto"
)
)
nrow(ofertas_apto) -> n_of2
kable(ofertas_apto %>%
arrange(preciom) %>%
select(barrio, preciom, areaconst, estrato, banios, habitaciones, parqueaderos, evaluacion) %>%
head(8),
caption = paste0("Apartamentos viables (", n_of2, " encontrados) – primera ronda"),
digits=1) %>% kable_styling(full_width = FALSE)
Apartamentos viables (2 encontrados) – primera ronda
|
barrio
|
preciom
|
areaconst
|
estrato
|
banios
|
habitaciones
|
parqueaderos
|
evaluacion
|
|
ciudadela pasoancho
|
650
|
275
|
5
|
5
|
5
|
2
|
Precio alto
|
|
seminario
|
670
|
300
|
5
|
5
|
6
|
3
|
Buena oportunidad
|
df_map_of2 <- ofertas_apto %>% head(20)
pal4 <- colorFactor(c("green","orange","red","purple"),
levels = c("Ganga excepcional","Buena oportunidad","Precio justo","Precio alto"))
leaflet(df_map_of2) %>%
addProviderTiles("CartoDB.Positron") %>%
addCircleMarkers(
lng = ~longitud, lat = ~latitud,
radius = ~pmax(5, sqrt(areaconst)/3),
color = ~pal4(evaluacion), fillOpacity = .85, stroke = TRUE, weight = 1,
popup = ~paste0("<b>", barrio, "</b><br>",
"Precio: ", scales::comma(preciom), " MM<br>",
"Área: ", areaconst, " m² | Estrato: ", estrato, "<br>",
"Hab/Baños/Parq: ", habitaciones, "/", banios, "/", parqueaderos, "<br>",
"<i>", evaluacion, "</i>"),
clusterOptions = markerClusterOptions()
) %>%
addLegend("topright", pal = pal4, values = df_map_of2$evaluacion, title = "Lectura comercial")