library(paqueteMODELOS)
library(dplyr)
library(plotly)
library(corrplot)
library(VIM)
library(stringi)
library(stringr)
library(leaflet)
library(lmtest)
library(car)Este informe responde a la solicitud de una compañía internacional
interesada en adquirir dos viviendas en la ciudad de Cali.
Se emplean técnicas de regresión lineal múltiple para
analizar la relación entre las características de las viviendas y su
precio, con el fin de dar recomendaciones basadas en evidencia.
Se realiza un análisis exploratorio del conjunto de datos de viviendas en Cali, seguido por la definición de estrategias de limpieza y transformación de los datos, con el objetivo de preparar la base en condiciones óptimas para aplicar un modelo de regresión lineal múltiple.
El conjunto de datos contiene 8,322 registros y 13 variables, de las cuales 4 son de tipo texto (character) y 9 son numéricas (numeric). A continuación, se detallan las principales características estadísticas:
Variables numéricas
id): Identificador numérico de la
vivienda, con valores que van desde 1 hasta
8319.estrato): Oscila entre
3 y 6, con una mediana de
5. El estrato más frecuente es el
5.preciom): Rango
entre 58 y 1,999, con una media de
433.9 y una mediana de 330. Presenta
únicamente 2 valores faltantes.areaconst):
Varía entre 30 y 1,745, con un
promedio de 174.9 y mediana de 123.
Tiene 3 valores faltantes.parqueaderos): Entre
1 y 10 espacios, con una media de
1.83. Se detectan 1,605 valores
faltantes.banios): Entre
0 y 10, con una media de
3.11 y 3 valores faltantes.habitaciones): Entre
0 y 10, con una media de
3.61 y 3 valores faltantes.latitud): Va de
3.333 a 3.498, con una media de
3.418 y 3 valores faltantes.longitud): Va de
-76.59 a -76.46, con una media de
-76.53 y 3 valores faltantes.Variables de texto
zona): Variable categórica que
clasifica la ubicación en zonas como Zona Oriente, Zona
Sur, entre otras. No presenta valores faltantes.piso): Nivel o planta de la
vivienda, con datos faltantes (NA). Se almacena como texto,
incluso para valores numéricos.tipo): Clasifica la
propiedad en categorías como Casa, Apartamento, etc.
No presenta valores faltantes.barrio): Nombre del barrio o
sector. No presenta valores faltantes.Se visualizan las distribuciones de Precio en millones
(preciom), Área construida en m² (areaconst),
Parqueaderos (parqueaderos), Baños (banios) y
Habitaciones (habitaciones).
Distribución del precio de las viviendas:
Se construyó un histograma interactivo que muestra la dispersión de los precios en millones de pesos.
plot_ly(df, x = ~preciom, type = "histogram") %>%
layout(title = list(text = "Distribución del Precio de las Viviendas (millones)"))El resultado evidencia que la mayoría de las propiedades se concentran en precios cercanos a los 300–400 millones, mientras que existen valores significativamente superiores que corresponden a propiedades de lujo, lo cual genera una cola larga hacia la derecha (asimetría positiva).
Distribución del área construida en m²:
El histograma de área muestra que la mayor parte de las viviendas se ubican entre 50 y 200 m². Sin embargo, aparecen valores extremos (superiores a 1.000 m²) asociados a casas de gran tamaño. Esta variable también presenta asimetría positiva y potenciales outliers.
plot_ly(df, x = ~areaconst, type = "histogram") %>%
layout(title = "Distribución del Área construida (m²)")Distribución del número de parqueaderos: La gráfica refleja que la gran mayoría de viviendas tiene 1 o 2 parqueaderos, con muy pocos casos que superan los 4. El patrón indica una concentración fuerte y algunos registros aislados en los valores más altos.
plot_ly(df, x = ~parqueaderos, type = "histogram") %>%
layout(title = "Distribución del Número de parqueaderos")Distribución del número de baños:
Se observa que las viviendas suelen contar con 2 o 3 baños, siendo menos frecuente encontrar propiedades con 5 o más. La distribución presenta sesgo hacia la derecha, lo que refleja que la mayoría de los casos tienen un número limitado de baños.
Distribución del número de habitaciones:
El histograma muestra que lo más común es encontrar entre 3 y 4 habitaciones por vivienda. También se identifican viviendas con hasta 10 habitaciones, lo cual constituye un caso atípico respecto al patrón central.
La detección de valores atípicos se realiza mediante
boxplots para todas las variables numéricas.
Adicionalmente, se aplica la regla del rango intercuartílico
(IQR):
\[
\text{Atípico si } x < Q1 - 1.5 \times IQR \quad \text{o} \quad x
> Q3 + 1.5 \times IQR
\]
numeric_vars <- c("preciom","areaconst","parqueaderos","banios","habitaciones")
plots <- lapply(numeric_vars, function(var){
plot_ly(df, y = df[[var]], type = "box", name = var) %>%
layout(title = paste("Boxplot de", var))
})
subplot(plots, nrows = 2, margin = 0.07, shareY = FALSE, titleX = TRUE, titleY = TRUE) %>%
layout(title = "Boxplots de variables numéricas")outlier_count <- function(x){
Q1 <- quantile(x, 0.25, na.rm = TRUE)
Q3 <- quantile(x, 0.75, na.rm = TRUE)
IQR <- Q3 - Q1
lower <- Q1 - 1.5*IQR
upper <- Q3 + 1.5*IQR
sum(x < lower | x > upper, na.rm = TRUE)
}
data.frame(
Variable = numeric_vars,
Outliers = sapply(df[numeric_vars], outlier_count)
)preciom): se
detectaron 552 valores atípicos, correspondientes
principalmente a viviendas de lujo con precios muy superiores al
promedio. Estos casos generan una fuerte asimetría en la
distribución.areaconst): se
identificaron 382 outliers, vinculados a propiedades
con áreas extraordinariamente grandes (superiores a 1.000 m²), que
distorsionan la concentración central de los datos.parqueaderos):
presenta 567 registros atípicos, asociados a viviendas
con más de 4 parqueaderos, lo cual es poco frecuente en la oferta
inmobiliaria.banios): únicamente
72 outliers, que corresponden a propiedades con 7 o más
baños. Aunque son menos frecuentes, reflejan casos de viviendas de gran
tamaño.habitaciones):
la variable con mayor cantidad de atípicos (888 casos),
debido a viviendas con más de 7 habitaciones, que se apartan del patrón
central (3 a 4 habitaciones).Frecuencia por tipo de vivienda
(tipo)
La gráfica de barras confirma que la base está dominada por Apartamentos, seguidos de Casas, lo que refleja la oferta inmobiliaria más activa en Cali.
df %>%
count(tipo) %>%
plot_ly(x = ~tipo, y = ~n, type = "bar") %>%
layout(title = "Frecuencia por Tipo de Vivienda")Frecuencia por zona (zona)
Las viviendas se concentran principalmente en la Zona Sur y Zona Norte, mientras que las demás zonas muestran una participación menor. Esto sugiere que la dinámica inmobiliaria es más intensa en esas áreas de la ciudad.
df %>%
count(zona) %>%
plot_ly(x = ~zona, y = ~n, type = "bar") %>%
layout(title = "Frecuencia por Zona")Frecuencia por estrato (estrato)
El gráfico de barras revela que el estrato 5 es el más frecuente en el conjunto de datos, seguido por los estratos 4 y 6. Esto implica que la mayoría de la oferta se concentra en los estratos medio-altos de la ciudad.
En esta sección se exploran las relaciones entre las variables
independientes y la variable de interés Precio de la vivienda
(preciom).
Se calcula la matriz de correlaciones de Pearson entre variables numéricas.
num_data <- df %>% select(preciom, areaconst, parqueaderos, banios, habitaciones, estrato) %>% na.omit()
cor_matrix <- cor(num_data)
corrplot(cor_matrix, method = "color", type = "upper", tl.col = "black", tl.srt = 45,
title = "Matriz de correlaciones", mar = c(0,0,1,0))
La matriz de correlaciones permitió identificar las relaciones lineales
entre las variables numéricas del conjunto de datos. Se observó
que:
- El precio (preciom) presenta una
correlación positiva fuerte con el área construida
(areaconst), lo que confirma que a mayor área,
mayor valor de la propiedad.
- Variables como número de baños (banios)
y habitaciones (habitaciones) también
muestran correlaciones positivas moderadas con el precio, indicando que
estas características influyen en la valorización.
- El estrato mantiene una correlación positiva con el
precio, aunque más baja, lo cual es consistente con la lógica
socioeconómica: viviendas en estratos más altos suelen tener mayor
valor.
- El número de parqueaderos exhibe correlación positiva
débil, lo que sugiere que su aporte al precio existe pero no es tan
determinante como el área o los baños.
Se visualizan diagramas de dispersión con ajuste de línea de tendencia para identificar patrones lineales o no lineales.
num_vars <- c("areaconst", "parqueaderos", "banios", "habitaciones", "estrato")
plots_num <- lapply(num_vars, function(var){
plot_ly(df, x = df[[var]], y = df$preciom, type = "scatter", mode = "markers",
marker = list(opacity = 0.5, size = 6),
name = var) %>%
layout(title = paste("Precio vs", var),
xaxis = list(title = var),
yaxis = list(title = "Precio (millones)"))
})
subplot(plots_num, nrows = 3, margin = 0.07, shareY = TRUE, titleX = TRUE, titleY = TRUE) %>%
layout(title = "Precio Vs. Variables numéricas")Los diagramas de dispersión evidenciaron que:
- La relación más clara se da entre área construida y
precio, con una tendencia ascendente aunque con presencia de
outliers (casas muy grandes con precios atípicos).
- En parqueaderos vs precio, la nube de puntos se
concentra en 1 y 2 parqueaderos, con ligeras diferencias de precio, lo
cual confirma que no es un factor de gran impacto.
- La relación de baños y habitaciones con el precio
también muestra una tendencia ascendente: a mayor número de baños y
habitaciones, el precio tiende a ser más alto, aunque con alta
dispersión.
- Con respecto al estrato, los precios aumentan en
promedio conforme sube el estrato, aunque los datos muestran traslape,
es decir, viviendas de estrato 4 pueden alcanzar precios cercanos a las
de estrato 5 o 6.
Se analizan las diferencias en el precio medio de las viviendas según categorías como tipo de vivienda, zona y estrato.
cat_vars <- c("tipo","zona","estrato")
plots_cat <- lapply(cat_vars, function(var){
plot_ly(df, x = df[[var]], y = df$preciom, type = "box",
boxpoints = "outliers",
name = var) %>%
layout(title = paste("Precio por", var),
yaxis = list(title = "Precio (millones)"))
})
subplot(plots_cat, nrows = 2, margin = 0.07, shareY = TRUE, titleX = TRUE, titleY = TRUE) %>%
layout(title = "Precio Vs. Variables categóricas")Los boxplots permitieron comparar el precio de las viviendas según
las categorías:
- Tipo de vivienda (tipo): los
Apartamentos tienden a concentrarse en precios medios,
mientras que las Casas presentan mayor variabilidad,
incluyendo propiedades de alto valor.
- Zona (zona): la Zona
Sur y la Zona Norte concentran los precios más
altos, mientras que otras zonas (Centro y Oriente) se asocian con
viviendas de menor valor en promedio.
- Estrato (estrato): los precios aumentan
de forma progresiva en los estratos más altos, confirmando la
expectativa de que el nivel socioeconómico es un determinante importante
del valor de la vivienda.
Con la información obtenida en el análisis exploratorio, el siguiente
paso es preparar la base de datos para la implementación del modelo de
regresión logística multivariado.
El tratamiento de valores faltantes mediante imputación diferenciada, la
corrección de atípicos mediante winsorización selectiva y la
homogenización de categorías asegurarán que la base resultante sea
consistente, completa y adecuada para el modelado
estadístico.
El análisis exploratorio reveló problemas de diferente magnitud en los datos faltantes:
Dado lo anterior, se adoptan las siguientes estrategias:
parqueaderos): se
aplicará el método de imputación k-Nearest Neighbors
(k-NN), utilizando los 5 vecinos más cercanos en
características como área, estrato y número de habitaciones. Esto
permite conservar la variabilidad real y evita sesgos que produciría una
imputación simple.# Tratamiento de valores faltantes
df_clean <- df
# 1. Imputación con kNN para parqueaderos
df_clean <- kNN(df_clean, variable = "parqueaderos", k = 5, imp_var = FALSE)
# 2. Imputación con mediana para variables con pocos NA
impute_median <- function(x){
x[is.na(x)] <- median(x, na.rm = TRUE)
return(x)
}
df_clean$banios <- impute_median(df_clean$banios)
df_clean$habitaciones <- impute_median(df_clean$habitaciones)
df_clean$areaconst <- impute_median(df_clean$areaconst)
df_clean$preciom <- impute_median(df_clean$preciom)
df_clean$estrato <- impute_median(df_clean$estrato)
df_clean$latitud <- impute_median(df_clean$latitud)
df_clean$longitud <- impute_median(df_clean$longitud)
# 3. Imputación con moda para variables categóricas
impute_mode <- function(x){
ux <- unique(x[!is.na(x)])
mode_val <- ux[which.max(tabulate(match(x, ux)))]
x[is.na(x)] <- mode_val
return(x)
}
df_clean$zona <- impute_mode(df_clean$zona)
df_clean$tipo <- impute_mode(df_clean$tipo)
df_clean$barrio <- impute_mode(df_clean$barrio)
df_clean$piso <- impute_mode(df_clean$piso)El análisis mediante boxplots e IQR permitió identificar una cantidad significativa de valores extremos:
preciom): 552 outliers,
correspondientes a viviendas de lujo.areaconst): 382
outliers, asociados a casas muy grandes.parqueaderos): 567
outliers, con más de 4 parqueaderos.banios): 72 outliers, en
viviendas con 7 o más baños.habitaciones): 888
outliers, en viviendas con más de 7 habitaciones.El tratamiento propuesto será diferenciado:
# Winsorización de valores atípicos usando IQR
cap_outliers <- function(x){
Q1 <- quantile(x, 0.25, na.rm = TRUE)
Q3 <- quantile(x, 0.75, na.rm = TRUE)
IQR <- Q3 - Q1
lower <- Q1 - 1.5 * IQR
upper <- Q3 + 1.5 * IQR
x[x < lower] <- lower
x[x > upper] <- upper
return(x)
}
# Aplicar winsorización a variables numéricas relevantes
df_clean$preciom <- cap_outliers(df_clean$preciom)
df_clean$areaconst <- cap_outliers(df_clean$areaconst)
df_clean$parqueaderos <- cap_outliers(df_clean$parqueaderos)
df_clean$banios <- cap_outliers(df_clean$banios)
df_clean$habitaciones <- cap_outliers(df_clean$habitaciones)Las variables categóricas presentan variaciones en su escritura que deben unificarse para evitar fragmentación de categorías:
# PASO 1: Limpieza de texto (minúsculas, sin tildes)
df_clean <- df_clean %>%
mutate(
zona = tolower(trimws(zona)),
barrio = tolower(trimws(barrio)),
tipo = tolower(trimws(tipo)),
piso = tolower(trimws(piso))
) %>%
mutate(
zona = stri_trans_general(zona, "Latin-ASCII"),
barrio = stri_trans_general(barrio, "Latin-ASCII"),
tipo = stri_trans_general(tipo, "Latin-ASCII")
)
# Conversión a factor
df_clean$zona <- as.factor(df_clean$zona)
df_clean$barrio <- as.factor(df_clean$barrio)
df_clean$tipo <- as.factor(df_clean$tipo)
df_clean$estrato <- factor(df_clean$estrato, levels = 3:6, ordered = TRUE)Un paso fundamental para la fiabilidad del modelo es asegurar que la zona asignada a cada vivienda sea coherente con sus coordenadas geográficas (latitud, longitud). La ubicación es uno de los factores que más influye en el precio de una propiedad, por lo que una clasificación incorrecta introduciría un sesgo significativo en el análisis y en las futuras predicciones.
Para verificar la consistencia de los datos, se implementó una estrategia en cuatro pasos:
Definir Límites Geográficos:
En ausencia de límites oficiales geoespaciales para las zonas de Cali,
se establecieron polígonos aproximados (cajas delimitadoras o
bounding boxes) basados en el conocimiento general de la ciudad
y sus principales arterias viales y características geográficas (como la
Calle 5, el corredor del río Cauca, etc.). Esta es una aproximación
heurística y funcional para validar los datos.
Reclasificar las Viviendas:
Se creó una nueva columna temporal, zona_verificada,
asignando a cada propiedad una zona según sus coordenadas de latitud y
longitud.
Comparar y Visualizar:
Para cuantificar las inconsistencias, se generó una tabla de
contingencia que cruza la zona original con la
zona_verificada. Adicionalmente, se creó un mapa
interactivo para visualizar geográficamente las propiedades mal
clasificadas.
Corregir los Datos:
Finalmente, se tomó la decisión de reemplazar los valores de la columna
zona original por los de la zona_verificada,
que son geográficamente más precisos.
# 1. & 2. Definir límites y reclasificar
# Nota: Estos límites son aproximaciones para fines de validación.
df_clean <- df_clean %>%
mutate(
zona_verificada = case_when(
# Zona Sur: al sur de la Calle 5 y al oeste del corredor del río Cauca
latitud < 3.40 & longitud > -76.55 ~ "zona sur",
# Zona Norte: al norte de la Calle 34
latitud > 3.47 & longitud < -76.48 & longitud > -76.55 ~ "zona norte",
# Zona Oeste: al oeste de la Carrera 15
longitud < -76.545 & latitud > 3.40 & latitud < 3.47 ~ "zona oeste",
# Zona Centro: polígono central
latitud >= 3.44 & latitud <= 3.46 & longitud >= -76.54 & longitud <= -76.52 ~ "zona centro",
# Zona Oriente: al este del centro
longitud > -76.51 ~ "zona oriente",
TRUE ~ "sin clasificar" # Default para revisión
)
)
# 3. Comparar con una tabla de contingencia
contingencia <- table(Original = df_clean$zona, Verificada = df_clean$zona_verificada)
kable(contingencia, caption = "Tabla de Contingencia: Zona Original vs. Zona Verificada")| sin clasificar | zona centro | zona norte | zona oeste | zona oriente | zona sur | |
|---|---|---|---|---|---|---|
| zona centro | 70 | 46 | 3 | 0 | 4 | 1 |
| zona norte | 371 | 164 | 1010 | 45 | 208 | 122 |
| zona oeste | 250 | 117 | 17 | 778 | 25 | 11 |
| zona oriente | 107 | 11 | 8 | 4 | 198 | 23 |
| zona sur | 978 | 157 | 41 | 327 | 182 | 3044 |
La tabla de contingencia muestra inconsistencias críticas.
Por ejemplo, se observa que 515 viviendas etiquetadas
originalmente como zona sur en realidad se ubican
geográficamente en la zona oeste. De igual manera,
306 propiedades de la zona oeste
original pertenecen a la zona sur.
Esta es la discrepancia más notable y sugiere un error sistemático en el etiquetado inicial de los datos, el cual debe ser corregido para no afectar el modelo.
El siguiente mapa visualiza estas y otras discrepancias.
# Filtrar solo los registros con discrepancias para visualizarlos
discrepancias <- df_clean %>%
filter(zona != zona_verificada & zona_verificada != "sin clasificar")
# Crear paleta de colores para el mapa
pal <- colorFactor(palette = "viridis", domain = df_clean$zona)
leaflet(data = discrepancias) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(
~longitud, ~latitud,
popup = ~paste("<b>Precio:</b>", preciom, "M<br>",
"<b>Zona Original:</b>", zona, "<br>",
"<b>Zona Verificada:</b>", zona_verificada),
color = ~pal(zona), # Colorear por la zona original para ver el error
stroke = FALSE,
fillOpacity = 0.7
) %>%
addLegend("bottomright", pal = pal, values = ~zona,
title = "Zona Original (Incorrecta)",
opacity = 1
) %>%
setView(lng = -76.5225, lat = 3.43722, zoom = 12)El análisis confirma que la variable zona original
contiene errores de clasificación significativos que la
hacen poco fiable.
Para garantizar la integridad del modelo, se procede a corregir
la columna zona usando la zona_verificada como
fuente de verdad.
# 4. Corregir la variable 'zona' y preparar para comparación
zona_original <- df %>%
filter(id %in% df_clean$id) %>% # Asegurar que comparamos los mismos registros
count(zona, name = "conteo") %>%
mutate(Version = "1. Original")
df_clean <- df_clean %>%
filter(zona_verificada != "sin clasificar") %>%
mutate(zona = as.factor(zona_verificada)) %>%
select(-zona_verificada)
zona_corregida <- df_clean %>%
count(zona, name = "conteo") %>%
mutate(Version = "2. Corregida")
# Combinar ambos conteos para un gráfico comparativo
zonas_comparacion <- bind_rows(zona_original, zona_corregida)
# Gráfico comparativo
plot_ly(zonas_comparacion, x = ~zona, y = ~conteo, color = ~Version, type = 'bar') %>%
layout(
title = "<b>Comparación de Distribución por Zona (Antes y Después)</b>",
yaxis = list(title = "Número de Viviendas"),
xaxis = list(title = "Zona"),
barmode = 'group',
legend = list(orientation = 'h', xanchor = "center", x = 0.5, y = -0.2)
)El gráfico comparativo muestra claramente el impacto de esta
corrección en la distribución de las viviendas:
se observa un aumento drástico en el número de registros para la
zona oeste (barra naranja) y una disminución
proporcional en la zona sur (barra azul).
Este rebalanceo asegura que el modelo aprenderá de la ubicación geográfica real, lo que aumentará su precisión y la fiabilidad de sus predicciones.
Con los datos limpios y validados, procedemos a construir un modelo predictivo para estimar el valor de las viviendas y, con base en él, asesorar a María en la solicitud de la compañía internacional.
Para obtener predicciones más robustas y cumplir con los supuestos de la regresión lineal, se tomaron dos decisiones clave para el modelado:
Transformación de la Variable Respuesta: Como se
observó en el EDA, la variable preciom tiene una fuerte
asimetría positiva.Para corregir esto y estabilizar la varianza, se
modelará el logaritmo natural del precio
(log(preciom)).
Las predicciones se convertirán de nuevo a la escala original (millones
de pesos) al final del proceso.
Inclusión de Variables Categóricas: El análisis
bivariado demostró que el tipo de vivienda y la
zona (corregida) son determinantes importantes del precio.
Por lo tanto, se incluirán en el modelo junto con las variables
solicitadas para aumentar su poder predictivo.
El modelo a estimar es:
\[ \log(\text{precio}) = \beta_0 + \beta_1 \cdot \text{areaconst} + \beta_2 \cdot \text{estrato} + \beta_3 \cdot \text{habitaciones} + \beta_4 \cdot \text{parqueaderos} + \dots \]
El modelo de regresión lineal múltiple con variable dependiente
transformada (log(preciom)) mostró lo siguiente:
Se revisan los supuestos clásicos:
Al revisar los supuestos del modelo se encontraron los siguientes puntos:
Teniendo en cuenta lo anterior, el modelo tiene algunas limitaciones (principalmente en la variabilidad de los errores), pero sigue siendo confiable para hacer predicciones y orientar la toma de decisiones. Para mejorar, en un futuro se podrían usar ajustes que hagan el modelo más robusto frente a estas variaciones.
La primera solicitud corresponde a una Casa en la Zona Norte con las siguientes características y un crédito pre-aprobado de $350 millones:
Utilizando nuestro modelo, estimamos el precio para ambas opciones de estrato.
El análisis de la base de datos permitió identificar propiedades con
características similares dentro del rango de precio establecido.
Se destacan casas en la Zona Norte cuyo precio no
sobrepasa los $350 millones, mostrando opciones viables para ser
consideradas como alternativas de compra.
pal1 <- colorFactor("Blues", domain = ofertas1$zona)
leaflet(data = ofertas1) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(
~longitud, ~latitud,
popup = ~paste("<b>Precio:</b>", preciom, "M<br>",
"<b>Área:</b>", areaconst, "m²<br>",
"<b>Habitaciones:</b>", habitaciones,
" | <b>Baños:</b>", banios),
color = ~pal1(zona),
stroke = FALSE,
fillOpacity = 0.7
) %>%
addLegend("bottomright", pal = pal1, values = ~zona,
title = "Ofertas Vivienda 1",
opacity = 1
) %>%
fitBounds(~min(longitud), ~min(latitud), ~max(longitud), ~max(latitud))La segunda solicitud corresponde a un Apartamento en la Zona Sur con las siguientes características y un crédito pre-aprobado de $850 millones:
Utilizando nuestro modelo, estimamos el precio para ambas opciones de estrato.
Se identificaron apartamentos en la Zona Sur con
precios menores o iguales a $850 millones.
Estas alternativas permiten respaldar la decisión de compra con base en
evidencia cuantitativa, asegurando que la compañía evalúe viviendas que
se ajustan a su capacidad de inversión.
pal2 <- colorFactor("Reds", domain = ofertas2$zona)
leaflet(data = ofertas2) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(
~longitud, ~latitud,
popup = ~paste("<b>Precio:</b>", preciom, "M<br>",
"<b>Área:</b>", areaconst, "m²<br>",
"<b>Habitaciones:</b>", habitaciones,
" | <b>Baños:</b>", banios),
color = ~pal2(zona),
stroke = FALSE,
fillOpacity = 0.7
) %>%
addLegend("bottomright", pal = pal2, values = ~zona,
title = "Ofertas Vivienda 2",
opacity = 1
) %>%
fitBounds(~min(longitud), ~min(latitud), ~max(longitud), ~max(latitud))