INTRODUCCIÓN
El mercado inmobiliario constituye uno de los sectores más relevantes para el desarrollo económico de las ciudades, ya que involucra decisiones de inversión de alto valor y responde a dinámicas sociales, financieras y urbanísticas. En este contexto, las agencias de bienes raíces desempeñan un papel fundamental al actuar como intermediarias entre la oferta y la demanda de viviendas, proporcionando información, análisis y asesoría especializada para la toma de decisiones de compra.
María, profesional con más de diez años de experiencia en el sector inmobiliario, ha consolidado su trayectoria tras trabajar inicialmente en agencias reconocidas en Cali y Bogotá. Motivada por su experiencia y conocimiento del mercado, decidió fundar su propia empresa inmobiliaria denominada C&A (Casas y Apartamentos) en la ciudad de Cali, donde actualmente lidera un equipo de ocho agentes especializados en la comercialización de bienes raíces. A pesar de que el mercado inmobiliario de la ciudad ha presentado una desaceleración en los últimos meses, las perspectivas a mediano plazo sugieren una recuperación del sector impulsada por el financiamiento disponible para proyectos de construcción residencial y comercial.
En este escenario, María ha recibido una solicitud de asesoría por parte de una compañía internacional interesada en adquirir dos viviendas en la ciudad para ubicar a dos de sus empleados junto con sus familias. Cada vivienda debe cumplir con características específicas relacionadas con el tipo de inmueble, área construida, número de parqueaderos, baños, habitaciones, estrato socioeconómico, ubicación dentro de la ciudad y un presupuesto máximo previamente aprobado mediante crédito hipotecario.
Con el fin de apoyar esta decisión, el presente documento desarrolla un análisis basado en técnicas de modelación estadística y análisis de datos, utilizando información reciente del mercado inmobiliario de Cali correspondiente a los últimos tres meses. A partir de variables como ubicación, estrato socioeconómico, área construida, número de parqueaderos, baños, habitaciones y tipo de vivienda, se construirán y evaluarán modelos que permitan estimar el valor de las propiedades y determinar cuáles opciones se ajustan mejor a las condiciones solicitadas por la empresa.
Carga de datos
# Cargar paquete
library(paqueteMODELOS)
library(lmtest)
# Asignar a un dataframe
df <- vivienda;df
## # A tibble: 8,322 × 13
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <dbl> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1147 Zona … <NA> 3 250 70 1 3 6
## 2 1169 Zona … <NA> 3 320 120 1 2 3
## 3 1350 Zona … <NA> 3 350 220 2 2 4
## 4 5992 Zona … 02 4 400 280 3 5 3
## 5 1212 Zona … 01 5 260 90 1 2 3
## 6 1724 Zona … 01 5 240 87 1 3 3
## 7 2326 Zona … 01 4 220 52 2 2 3
## 8 4386 Zona … 01 5 310 137 2 3 4
## 9 1209 Zona … 02 5 320 150 2 4 6
## 10 1592 Zona … 02 5 780 380 2 3 3
## # ℹ 8,312 more rows
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
# Ver resumen
summary(df)
## id zona piso estrato
## Min. : 1 Length:8322 Length:8322 Min. :3.000
## 1st Qu.:2080 Class :character Class :character 1st Qu.:4.000
## Median :4160 Mode :character Mode :character Median :5.000
## Mean :4160 Mean :4.634
## 3rd Qu.:6240 3rd Qu.:5.000
## Max. :8319 Max. :6.000
## NA's :3 NA's :3
## 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
## NA's :2 NA's :3 NA's :1605 NA's :3
## habitaciones tipo barrio longitud
## Min. : 0.000 Length:8322 Length:8322 Min. :-76.59
## 1st Qu.: 3.000 Class :character Class :character 1st Qu.:-76.54
## Median : 3.000 Mode :character Mode :character Median :-76.53
## Mean : 3.605 Mean :-76.53
## 3rd Qu.: 4.000 3rd Qu.:-76.52
## Max. :10.000 Max. :-76.46
## NA's :3 NA's :3
## latitud
## Min. :3.333
## 1st Qu.:3.381
## Median :3.416
## Mean :3.418
## 3rd Qu.:3.452
## Max. :3.498
## NA's :3
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.5.2
##
## Adjuntando el paquete: 'dplyr'
## The following object is masked from 'package:gridExtra':
##
## combine
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(knitr)
library(kableExtra)
## Warning: package 'kableExtra' was built under R version 4.5.2
##
## Adjuntando el paquete: 'kableExtra'
## The following object is masked from 'package:dplyr':
##
## group_rows
library(summarytools)
library(leaflet)
## Warning: package 'leaflet' was built under R version 4.5.2
tabla_zona <- df %>%
count(zona) %>%
arrange(desc(n))
kable(tabla_zona,
col.names = c("Zona", "Número de viviendas"),
caption = "Distribución de viviendas por zona en la base de datos") %>%
kable_styling(full_width = FALSE, bootstrap_options = c("striped","hover","condensed"))
| Zona | Número de viviendas |
|---|---|
| Zona Sur | 4726 |
| Zona Norte | 1920 |
| Zona Oeste | 1198 |
| Zona Oriente | 351 |
| Zona Centro | 124 |
| NA | 3 |
Filtrado inicial para la generación de dos dataframe con respecto a las zonas correspondientes: Norte y Sur
df_norte <- df[df$zona == "Zona Norte", ]
df_sur <- df[df$zona == "Zona Sur", ]
summary(df_norte)
## id zona piso estrato
## Min. : 31.0 Length:1923 Length:1923 Min. :3.000
## 1st Qu.: 832.8 Class :character Class :character 1st Qu.:3.000
## Median :2400.5 Mode :character Mode :character Median :4.000
## Mean :2558.0 Mean :4.282
## 3rd Qu.:3867.8 3rd Qu.:5.000
## Max. :8319.0 Max. :6.000
## NA's :3 NA's :3
## preciom areaconst parqueaderos banios
## Min. : 65.0 Min. : 30.0 Min. : 1.000 Min. : 0.000
## 1st Qu.: 160.0 1st Qu.: 70.0 1st Qu.: 1.000 1st Qu.: 2.000
## Median : 300.0 Median : 107.0 Median : 1.000 Median : 2.000
## Mean : 345.6 Mean : 161.1 Mean : 1.646 Mean : 2.778
## 3rd Qu.: 430.0 3rd Qu.: 215.2 3rd Qu.: 2.000 3rd Qu.: 3.000
## Max. :1940.0 Max. :1440.0 Max. :10.000 Max. :10.000
## NA's :3 NA's :3 NA's :636 NA's :3
## habitaciones tipo barrio longitud
## Min. : 0.000 Length:1923 Length:1923 Min. :-76.59
## 1st Qu.: 3.000 Class :character Class :character 1st Qu.:-76.53
## Median : 3.000 Mode :character Mode :character Median :-76.52
## Mean : 3.501 Mean :-76.52
## 3rd Qu.: 4.000 3rd Qu.:-76.50
## Max. :10.000 Max. :-76.47
## NA's :3 NA's :3
## latitud
## Min. :3.333
## 1st Qu.:3.457
## Median :3.472
## Mean :3.464
## 3rd Qu.:3.485
## Max. :3.498
## NA's :3
summary(df_sur)
## id zona piso estrato
## Min. : 1 Length:4729 Length:4729 Min. :3.000
## 1st Qu.:2574 Class :character Class :character 1st Qu.:4.000
## Median :4378 Mode :character Mode :character Median :5.000
## Mean :4361 Mean :4.717
## 3rd Qu.:6057 3rd Qu.:5.000
## Max. :8305 Max. :6.000
## NA's :3 NA's :3
## preciom areaconst parqueaderos banios
## Min. : 75.0 Min. : 40.0 Min. : 1.000 Min. : 0.000
## 1st Qu.: 222.0 1st Qu.: 78.0 1st Qu.: 1.000 1st Qu.: 2.000
## Median : 320.0 Median : 113.0 Median : 1.000 Median : 3.000
## Mean : 426.5 Mean : 173.3 Mean : 1.835 Mean : 3.179
## 3rd Qu.: 520.0 3rd Qu.: 220.0 3rd Qu.: 2.000 3rd Qu.: 4.000
## Max. :1900.0 Max. :1600.0 Max. :10.000 Max. :10.000
## NA's :3 NA's :3 NA's :624 NA's :3
## habitaciones tipo barrio longitud
## Min. : 0.000 Length:4729 Length:4729 Min. :-76.57
## 1st Qu.: 3.000 Class :character Class :character 1st Qu.:-76.54
## Median : 3.000 Mode :character Mode :character Median :-76.53
## Mean : 3.601 Mean :-76.53
## 3rd Qu.: 4.000 3rd Qu.:-76.52
## Max. :10.000 Max. :-76.46
## NA's :3 NA's :3
## latitud
## Min. :3.333
## 1st Qu.:3.370
## Median :3.385
## Mean :3.390
## 3rd Qu.:3.409
## Max. :3.497
## NA's :3
nrow(df_norte)
## [1] 1923
nrow(df_sur)
## [1] 4729
head(df_norte ,3)
## # A tibble: 3 × 13
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <dbl> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1212 Zona N… 01 5 260 90 1 2 3
## 2 1724 Zona N… 01 5 240 87 1 3 3
## 3 2326 Zona N… 01 4 220 52 2 2 3
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
tabla_resumen <- df_norte %>%
summarise(
Viviendas = n(),
Precio_promedio = mean(preciom, na.rm = TRUE),
Area_promedio = mean(areaconst, na.rm = TRUE),
Parqueaderos_prom = mean(parqueaderos, na.rm = TRUE),
Banios_prom = mean(banios, na.rm = TRUE),
Habitaciones_prom = mean(habitaciones, na.rm = TRUE)
)
kable(tabla_resumen,
caption = "Estadísticas descriptivas de viviendas en Zona Norte",
digits = 2) %>%
kable_styling(full_width = FALSE, bootstrap_options = c("striped","hover"))
| Viviendas | Precio_promedio | Area_promedio | Parqueaderos_prom | Banios_prom | Habitaciones_prom |
|---|---|---|---|---|---|
| 1923 | 345.61 | 161.12 | 1.65 | 2.78 | 3.5 |
A partir de las estadísticas descriptivas obtenidas para las viviendas ubicadas en la Zona Norte de Cali, se identifican algunas características relevantes del mercado inmobiliario en este sector de la ciudad.
En primer lugar, la base de datos analizada incluye 1.923 viviendas, lo que representa una muestra amplia y representativa del comportamiento del mercado en esta zona. El precio promedio de las viviendas se sitúa alrededor de 345,61 millones de pesos, lo cual sugiere que la zona norte se posiciona como un sector de valor medio–alto dentro del mercado inmobiliario de la ciudad.
En términos de características físicas, las viviendas presentan un área construida promedio de 161,12 m², lo que indica que predominan inmuebles de tamaño moderado a amplio, adecuados para familias. Asimismo, se observa que las propiedades cuentan en promedio con 1,65 parqueaderos, 2,78 baños y 3,5 habitaciones, configuraciones que son consistentes con viviendas familiares de nivel socioeconómico medio y medio–alto.
leaflet(df_norte) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
setView(
lng = mean(df_norte$longitud, na.rm = TRUE),
lat = mean(df_norte$latitud, na.rm = TRUE),
zoom = 12
) %>%
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
radius = 5,
color = "darkred",
fillOpacity = 0.7,
popup = ~paste0(
"<b>Barrio:</b> ", barrio,
"<br><b>Precio:</b> ", preciom, " millones",
"<br><b>Área:</b> ", areaconst, " m²",
"<br><b>Habitaciones:</b> ", habitaciones
)
)
## Warning in validateCoords(lng, lat, funcName): Data contains 3 rows with either
## missing or invalid lat/lon values and will be ignored
En general, el mapa confirma que la mayoría de los inmuebles se ubican dentro del área esperada para la zona norte de Cali, lo cual respalda la validez del filtrado realizado. No obstante, la presencia de algunos puntos fuera de esta zona sugiere la necesidad de revisar posibles inconsistencias en la georreferenciación o en la clasificación de la variable zona, especialmente si se pretende realizar análisis espaciales más detallados o modelos que dependan de la ubicación geográfica de los inmuebles.
library(plotly)
library(dplyr)
df_corr <- df_norte %>%
select(preciom, areaconst, estrato, banios, habitaciones, latitud, longitud)
# Calcular matriz de correlación
matriz_corr <- cor(df_corr, use = "complete.obs")
# Etiquetas en español (7 variables)
etiquetas <- c(
"Precio (M)",
"Área Const.",
"Estrato",
"Baños",
"Habitaciones",
"Latitud",
"Longitud"
)
# Texto de anotaciones
texto_corr <- matrix(
sprintf("%.2f", matriz_corr),
nrow = nrow(matriz_corr)
)
# Mapa de calor interactivo con Viridis
plot_ly(
x = etiquetas,
y = etiquetas,
z = matriz_corr,
type = "heatmap",
colorscale = "Viridis",
zmin = -1, zmax = 1,
text = texto_corr,
hovertemplate = "<b>%{y}</b> vs <b>%{x}</b><br>Correlación: <b>%{text}</b><extra></extra>"
) %>%
add_annotations(
x = rep(etiquetas, each = length(etiquetas)),
y = rep(etiquetas, times = length(etiquetas)),
text = as.vector(texto_corr),
showarrow = FALSE,
font = list(size = 11, color = "white")
) %>%
layout(
title = list(
text = "<b>Mapa de Calor de Correlaciones — Zona Norte</b>",
font = list(size = 16)
),
xaxis = list(title = "", tickangle = -35),
yaxis = list(title = "", autorange = "reversed"),
margin = list(l = 120, b = 100, t = 60),
paper_bgcolor = "#fafafa",
plot_bgcolor = "#fafafa"
)
Precio (M) muestra correlaciones positivas moderadas-altas con Área Construida (0.73) y Estrato (0.61), lo que indica que a mayor tamaño del inmueble y mayor estrato socioeconómico, el precio tiende a incrementarse considerablemente. Baños (0.66) también presenta una correlación positiva relevante, sugiriendo que la cantidad de baños es un buen indicador del valor del inmueble, posiblemente porque refleja el tamaño y lujo de la propiedad. Habitaciones (0.39) tiene una relación positiva más moderada con el precio. Por otro lado, Longitud (-0.49) presenta la correlación negativa más fuerte con el precio, lo que geográficamente sugiere que los inmuebles ubicados más al occidente de Cali tienden a ser más costosos. Latitud (-0.17) tiene una relación negativa débil, indicando que la ubicación norte-sur tiene poca influencia directa sobre el precio en esta zona. En conjunto, las variables más relevantes para explicar el precio en df_norte son el área construida, el estrato y el número de baños, mientras que la ubicación geográfica (especialmente la longitud) también aporta información valiosa sobre la distribución espacial de los precios.
# Modelo de regresión lineal múltiple
modelo <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = df_norte)
# Resumen del modelo
summary(modelo)
##
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos +
## banios, data = df_norte)
##
## Residuals:
## Min 1Q Median 3Q Max
## -721.33 -62.41 -5.97 40.70 941.76
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -265.61225 22.00179 -12.072 < 2e-16 ***
## areaconst 0.70321 0.03721 18.900 < 2e-16 ***
## estrato 84.49148 4.60020 18.367 < 2e-16 ***
## habitaciones -11.18464 3.78370 -2.956 0.00317 **
## parqueaderos 31.69619 4.35742 7.274 6.06e-13 ***
## banios 42.75011 4.69749 9.101 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 128.3 on 1281 degrees of freedom
## (636 observations deleted due to missingness)
## Multiple R-squared: 0.6868, Adjusted R-squared: 0.6856
## F-statistic: 561.8 on 5 and 1281 DF, p-value: < 2.2e-16
El modelo de regresión lineal muestra que las variables independientes explican aproximadamente el 68.7% de la variabilidad del precio de las viviendas (R² = 0.6868), indicando un buen ajuste para predicción. Entre los estimadores, el área construida y el estrato tienen un efecto positivo fuerte, aumentando el precio en 0.70 unidades por m² y 84.49 unidades por nivel de estrato, respectivamente. Los parqueaderos y los baños también contribuyen positivamente con incrementos de 31.70 y 42.75 unidades por cada uno, mientras que el coeficiente de habitaciones es negativo (-11.18), lo que podría reflejar correlaciones con otras variables o características específicas del mercado. Todos los coeficientes, excepto la variable de habitaciones con menor magnitud, son altamente significativos, confirmando la relevancia de estas variables para explicar el precio inmobiliario.
# Gráficos de diagnóstico
par(mfrow = c(2,2))
plot(modelo)
# Intervalos de confianza de los coeficientes
confint(modelo, level = 0.95)
## 2.5 % 97.5 %
## (Intercept) -308.7757584 -222.4487510
## areaconst 0.6302163 0.7762001
## estrato 75.4667322 93.5162222
## habitaciones -18.6075752 -3.7617142
## parqueaderos 23.1477177 40.2446559
## banios 33.5344956 51.9657163
# VIF para detectar multicolinealidad
library(car)
## Cargando paquete requerido: carData
##
## Adjuntando el paquete: 'car'
## The following object is masked from 'package:dplyr':
##
## recode
## The following object is masked from 'package:boot':
##
## logit
vif(modelo)
## areaconst estrato habitaciones parqueaderos banios
## 2.139878 1.314173 2.313930 1.523718 2.684230
Residuals vs Fitted: Se observa un patrón en forma de embudo (heterocedasticidad), donde la varianza de los residuos aumenta con los valores ajustados. Esto viola el supuesto de homocedasticidad del modelo lineal clásico. Q-Q Residuals: Los residuos se desvían de la línea diagonal teórica, especialmente en las colas, indicando que no siguen una distribución normal. Las observaciones 1419, 1425 y 1435 aparecen como valores atípicos influyentes. Scale-Location: La línea roja muestra una tendencia creciente en lugar de ser horizontal, lo que confirma la heterocedasticidad detectada anteriormente. La dispersión no es constante a lo largo de los valores ajustados. Residuals vs Leverage: Los puntos 1435, 469 y 1801 superan o se aproximan a la distancia de Cook, sugiriendo que son observaciones influyentes que pueden estar distorsionando los coeficientes estimados.
Si bien el modelo presenta un R² = 0.687 aceptable y coeficientes significativos, los diagnósticos revelan violaciones importantes a los supuestos de MCO: heterocedasticidad y no normalidad de residuos. Se recomienda considerar una transformación de la variable objetivo precio.
# Extraer residuos del modelo
residuos <- resid(lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = df_norte))
# Test de normalidad Shapiro-Wilk
shapiro.test(residuos)
##
## Shapiro-Wilk normality test
##
## data: residuos
## W = 0.83799, p-value < 2.2e-16
# Gráfico Q-Q
qqnorm(residuos)
qqline(residuos, col = "red")
El test de Shapiro-Wilk arroja un valor de W = 0.838 con p < 2.2e-16, lo que significa que rechazamos la hipótesis nula de normalidad. En otras palabras, los residuos no se distribuyen normalmente.
dwtest(lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = df_norte))
##
## Durbin-Watson test
##
## data: lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = df_norte)
## DW = 1.7967, p-value = 0.0001161
## alternative hypothesis: true autocorrelation is greater than 0
DW = 1.797, lo que está algo por debajo de 2.
p-value = 0.0001161, indicando que rechazamos la hipótesis nula de no autocorrelación positiva.
Esto sugiere que existe ligera autocorrelación positiva en los residuos, lo que puede afectar la eficiencia de los coeficientes y los errores estándar, especialmente en series temporales o datos espaciales.
precio = β₀ + β₁(areaconst) + β₂(estrato) + β₃(habitaciones) + β₄(parqueaderos) + β₅(baños)
# ============================================
# PREDICCIÓN DE PRECIO - MODELO LM ZONA NORTE
# ============================================
# 1. CREAR EL DATA FRAME CON LOS DATOS DE LA VIVIENDA
nueva_vivienda <- data.frame(
areaconst = 200,
parqueaderos = 1,
banios = 2,
habitaciones = 4,
estrato = 4 # valor medio del rango 4-5
)
# 2. REALIZAR LA PREDICCIÓN CON EL MODELO
prediccion <- predict(modelo,
newdata = nueva_vivienda,
interval = "prediction", # intervalo de predicción
level = 0.95) # 95% de confianza
# 3. MOSTRAR RESULTADOS
cat("===============================================\n")
## ===============================================
cat(" PREDICCIÓN DE PRECIO - ZONA NORTE \n")
## PREDICCIÓN DE PRECIO - ZONA NORTE
cat("===============================================\n")
## ===============================================
cat("Características de la vivienda:\n")
## Características de la vivienda:
cat(" - Tipo: Casa\n")
## - Tipo: Casa
cat(" - Área construida:", nueva_vivienda$areaconst, "m²\n")
## - Área construida: 200 m²
cat(" - Parqueaderos: ", nueva_vivienda$parqueaderos, "\n")
## - Parqueaderos: 1
cat(" - Baños: ", nueva_vivienda$banios, "\n")
## - Baños: 2
cat(" - Habitaciones: ", nueva_vivienda$habitaciones, "\n")
## - Habitaciones: 4
cat(" - Estrato: ", nueva_vivienda$estrato, "\n")
## - Estrato: 4
cat("-----------------------------------------------\n")
## -----------------------------------------------
cat("Precio estimado: ", round(prediccion[,"fit"], 2), "millones COP\n")
## Precio estimado: 285.45 millones COP
cat("Límite inferior: ", round(prediccion[,"lwr"], 2), "millones COP\n")
## Límite inferior: 33.51 millones COP
cat("Límite superior: ", round(prediccion[,"upr"], 2), "millones COP\n")
## Límite superior: 537.4 millones COP
cat("===============================================\n")
## ===============================================
# 4. PREDICCIÓN CON ESTRATO 5 (valor alto del rango)
nueva_vivienda_e5 <- nueva_vivienda
nueva_vivienda_e5$estrato <- 5
prediccion_e5 <- predict(modelo,
newdata = nueva_vivienda_e5,
interval = "prediction",
level = 0.95)
cat("\nComparación por estrato:\n")
##
## Comparación por estrato:
cat(" Estrato 4 -> Precio estimado:", round(prediccion[,"fit"], 2), "millones COP\n")
## Estrato 4 -> Precio estimado: 285.45 millones COP
cat(" Estrato 5 -> Precio estimado:", round(prediccion_e5[,"fit"], 2), "millones COP\n")
## Estrato 5 -> Precio estimado: 369.94 millones COP
# ============================================================
# ALGORITMO: TOP 5 OFERTAS POTENCIALES PARA EL CLIENTE
# Modelo: modelo | Base de datos: df_norte
# Presupuesto máximo: 350 millones COP
# Perfil: Casa, 200m², 4 hab, 2 baños, 1 parq, estrato 4-5
# ============================================================
library(dplyr)
library(leaflet)
# ------------------------------------------------------------
# PASO 1: DEFINIR PERFIL DEL CLIENTE Y PRESUPUESTO
# ------------------------------------------------------------
presupuesto_max <- 350
tolerancia_area <- 0.20 # ±20% del área solicitada (160-240m²)
area_objetivo <- 200
# ------------------------------------------------------------
# PASO 2: FILTRAR CANDIDATOS EN df_norte
# ------------------------------------------------------------
candidatos <- df_norte %>%
filter(
tipo == "Casa",
habitaciones == 4,
banios >= 2,
parqueaderos >= 1,
estrato %in% c(4, 5),
areaconst >= area_objetivo * (1 - tolerancia_area),
areaconst <= area_objetivo * (1 + tolerancia_area),
preciom <= presupuesto_max
) %>%
filter(!is.na(areaconst), !is.na(estrato),
!is.na(habitaciones), !is.na(parqueaderos), !is.na(banios))
cat("✔ Candidatos filtrados:", nrow(candidatos), "\n")
## ✔ Candidatos filtrados: 14
# ------------------------------------------------------------
# PASO 3: CALCULAR PRECIO ESTIMADO CON EL MODELO
# ------------------------------------------------------------
candidatos$precio_estimado <- predict(modelo, newdata = candidatos)
# ------------------------------------------------------------
# PASO 4: CALCULAR MÉTRICAS DE VALOR
# ------------------------------------------------------------
candidatos <- candidatos %>%
mutate(
# Diferencia entre precio real y estimado por el modelo
diferencia_precio = preciom - precio_estimado,
# % de diferencia (negativo = oferta por debajo del mercado = oportunidad)
pct_diferencia = (diferencia_precio / precio_estimado) * 100,
# Margen disponible del crédito
margen_credito = presupuesto_max - preciom,
# Score de valor: penaliza sobreprecios, premia oportunidades y margen
score_valor = -pct_diferencia + (margen_credito / presupuesto_max) * 50
)
# ------------------------------------------------------------
# PASO 5: SELECCIONAR TOP 5 POR SCORE DE VALOR
# ------------------------------------------------------------
top5 <- candidatos %>%
arrange(desc(score_valor)) %>%
slice_head(n = 5) %>%
select(id, barrio, estrato, areaconst, habitaciones, banios,
parqueaderos, preciom, precio_estimado,
diferencia_precio, pct_diferencia, margen_credito, score_valor,
latitud, longitud)
# ------------------------------------------------------------
# PASO 6: MOSTRAR RESULTADOS EN CONSOLA
# ------------------------------------------------------------
cat("\n===============================================================\n")
##
## ===============================================================
cat(" TOP 5 OFERTAS POTENCIALES - ZONA NORTE \n")
## TOP 5 OFERTAS POTENCIALES - ZONA NORTE
cat(" Presupuesto máximo: $350M COP \n")
## Presupuesto máximo: $350M COP
cat("===============================================================\n\n")
## ===============================================================
for (i in 1:nrow(top5)) {
oferta <- top5[i, ]
cat(sprintf("🏠 OFERTA #%d — ID: %d | Barrio: %s\n", i, oferta$id, oferta$barrio))
cat(sprintf(" Área: %dm² | Estrato: %d | Hab: %d | Baños: %d | Parq: %d\n",
oferta$areaconst, oferta$estrato, oferta$habitaciones,
oferta$banios, oferta$parqueaderos))
cat(sprintf(" Precio oferta: $%.1f M\n", oferta$preciom))
cat(sprintf(" Precio modelo: $%.1f M\n", oferta$precio_estimado))
cat(sprintf(" Diferencia: %+.1f M (%+.1f%%)\n",
oferta$diferencia_precio, oferta$pct_diferencia))
cat(sprintf(" Margen crédito: $%.1f M\n", oferta$margen_credito))
cat(sprintf(" Score de valor: %.2f\n", oferta$score_valor))
if (oferta$pct_diferencia < -5) {
cat(" PORTUNIDAD: precio por debajo del mercado\n")
} else if (oferta$pct_diferencia <= 5) {
cat(" PRECIO JUSTO: alineado con el mercado\n")
} else {
cat(" SOBREPRECIO: evaluar negociación\n")
}
cat("---------------------------------------------------------------\n")
}
## 🏠 OFERTA #1 — ID: 1644 | Barrio: vipasa
## Área: 171m² | Estrato: 4 | Hab: 4 | Baños: 4 | Parq: 3
## Precio oferta: $270.0 M
## Precio modelo: $414.0 M
## Diferencia: -144.0 M (-34.8%)
## Margen crédito: $80.0 M
## Score de valor: 46.20
## PORTUNIDAD: precio por debajo del mercado
## ---------------------------------------------------------------
## 🏠 OFERTA #2 — ID: 3053 | Barrio: la flora
## Área: 230m² | Estrato: 5 | Hab: 4 | Baños: 4 | Parq: 2
## Precio oferta: $320.0 M
## Precio modelo: $508.2 M
## Diferencia: -188.2 M (-37.0%)
## Margen crédito: $30.0 M
## Score de valor: 41.32
## PORTUNIDAD: precio por debajo del mercado
## ---------------------------------------------------------------
## 🏠 OFERTA #3 — ID: 1343 | Barrio: la flora
## Área: 200m² | Estrato: 5 | Hab: 4 | Baños: 4 | Parq: 2
## Precio oferta: $320.0 M
## Precio modelo: $487.1 M
## Diferencia: -167.1 M (-34.3%)
## Margen crédito: $30.0 M
## Score de valor: 38.60
## PORTUNIDAD: precio por debajo del mercado
## ---------------------------------------------------------------
## 🏠 OFERTA #4 — ID: 1641 | Barrio: la flora
## Área: 170m² | Estrato: 5 | Hab: 4 | Baños: 4 | Parq: 3
## Precio oferta: $343.0 M
## Precio modelo: $497.7 M
## Diferencia: -154.7 M (-31.1%)
## Margen crédito: $7.0 M
## Score de valor: 32.09
## PORTUNIDAD: precio por debajo del mercado
## ---------------------------------------------------------------
## 🏠 OFERTA #5 — ID: 1506 | Barrio: la flora
## Área: 180m² | Estrato: 5 | Hab: 4 | Baños: 4 | Parq: 2
## Precio oferta: $340.0 M
## Precio modelo: $473.1 M
## Diferencia: -133.1 M (-28.1%)
## Margen crédito: $10.0 M
## Score de valor: 29.56
## PORTUNIDAD: precio por debajo del mercado
## ---------------------------------------------------------------
# ------------------------------------------------------------
# PASO 7: TABLA RESUMEN
# ------------------------------------------------------------
resumen <- top5 %>%
select(id, barrio, estrato, areaconst, preciom,
precio_estimado, pct_diferencia, margen_credito, score_valor) %>%
rename(
"ID" = id,
"Barrio" = barrio,
"Estrato" = estrato,
"Área (m²)" = areaconst,
"Precio ($M)" = preciom,
"Modelo ($M)" = precio_estimado,
"Dif. (%)" = pct_diferencia,
"Margen ($M)" = margen_credito,
"Score" = score_valor
)
print(resumen)
## # A tibble: 5 × 9
## ID Barrio Estrato `Área (m²)` `Precio ($M)` `Modelo ($M)` `Dif. (%)`
## <dbl> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1644 vipasa 4 171 270 414. -34.8
## 2 3053 la flora 5 230 320 508. -37.0
## 3 1343 la flora 5 200 320 487. -34.3
## 4 1641 la flora 5 170 343 498. -31.1
## 5 1506 la flora 5 180 340 473. -28.1
## # ℹ 2 more variables: `Margen ($M)` <dbl>, Score <dbl>
# ------------------------------------------------------------
# PASO 8: MAPA LEAFLET CON TOP 5
# ------------------------------------------------------------
top5_geo <- top5 %>% filter(!is.na(latitud), !is.na(longitud))
leaflet(top5_geo) %>%
addTiles() %>%
addMarkers(
lng = ~longitud,
lat = ~latitud,
popup = ~paste0(
"<b>Oferta ID: ", id, "</b><br>",
"Barrio: ", barrio, "<br>",
"Área: ", areaconst, " m² | Estrato: ", estrato, "<br>",
"Precio: $", preciom, "M<br>",
"Modelo: $", round(precio_estimado, 1), "M<br>",
"Diferencia: ", round(pct_diferencia, 1), "%<br>",
"Margen crédito: $", margen_credito, "M"
),
label = ~paste0("$", preciom, "M | ", barrio)
) %>%
addLegend("bottomright",
colors = "blue",
labels = "Oferta potencial",
title = "Top 5 Ofertas ≤ $350M")
Conclusión — Top 5 Ofertas Potenciales Zona Norte
Hallazgo principal: todas son oportunidades de mercado El resultado más destacado del análisis es que las 5 propiedades están valoradas por debajo del precio estimado por el modelo, con diferencias que oscilan entre -28% y -37%. Esto indica que el mercado actual en la zona norte, específicamente en los barrios Vipasa y La Flora, está ofreciendo propiedades a precios significativamente inferiores a su valor estadístico estimado, lo que representa una ventana de oportunidad real para el comprador.
Análisis por oferta
Oferta #1 — Vipasa ($270M) es la recomendación principal. Presenta el mayor descuento absoluto respecto al modelo (-$144M), el mayor margen de crédito disponible ($80M) y el score más alto (46.20). Aunque su área es ligeramente menor (171m²), compensa con 4 baños y 3 parqueaderos, superando las especificaciones solicitadas. Es la opción con menor riesgo financiero.
Ofertas #2 y #3 — La Flora ($320M) son prácticamente equivalentes en precio pero la Oferta #2 tiene 30m² adicionales (230m²), lo que la hace más atractiva en términos de valor por metro cuadrado. Ambas son estrato 5, con 4 baños y 2 parqueaderos, superando el perfil solicitado. El margen de crédito restante es ajustado ($30M), lo que deja poco espacio para imprevistos.
Ofertas #4 y #5 — La Flora ($340M–$343M) están muy cerca del límite del presupuesto, con márgenes de apenas $7M y $10M respectivamente. Aunque siguen siendo oportunidades frente al modelo, el riesgo financiero es alto ante cualquier costo adicional como escrituración, adecuaciones o impuestos.
head(df_sur ,3)
## # A tibble: 3 × 13
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <dbl> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 5992 Zona S… 02 4 400 280 3 5 3
## 2 5098 Zona S… 05 4 290 96 1 2 3
## 3 698 Zona S… 02 3 78 40 1 1 2
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
tabla_resumen <- df_sur %>%
summarise(
Viviendas = n(),
Precio_promedio = mean(preciom, na.rm = TRUE),
Area_promedio = mean(areaconst, na.rm = TRUE),
Parqueaderos_prom = mean(parqueaderos, na.rm = TRUE),
Banios_prom = mean(banios, na.rm = TRUE),
Habitaciones_prom = mean(habitaciones, na.rm = TRUE)
)
kable(tabla_resumen,
caption = "Estadísticas descriptivas de viviendas en Zona Sur",
digits = 2) %>%
kable_styling(full_width = FALSE, bootstrap_options = c("striped","hover"))
| Viviendas | Precio_promedio | Area_promedio | Parqueaderos_prom | Banios_prom | Habitaciones_prom |
|---|---|---|---|---|---|
| 4729 | 426.52 | 173.31 | 1.83 | 3.18 | 3.6 |
La Zona Sur concentra 4.729 viviendas, representando el segmento más amplio del dataset analizado. Con un precio promedio de $426.52M COP, esta zona se posiciona como un mercado de valor medio-alto, aunque con características físicas que no necesariamente justifican ese precio frente a la Zona Norte.
leaflet(df_sur) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
setView(
lng = mean(df_norte$longitud, na.rm = TRUE),
lat = mean(df_norte$latitud, na.rm = TRUE),
zoom = 12
) %>%
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
radius = 5,
color = "darkred",
fillOpacity = 0.7,
popup = ~paste0(
"<b>Barrio:</b> ", barrio,
"<br><b>Precio:</b> ", preciom, " millones",
"<br><b>Área:</b> ", areaconst, " m²",
"<br><b>Habitaciones:</b> ", habitaciones
)
)
## Warning in validateCoords(lng, lat, funcName): Data contains 3 rows with either
## missing or invalid lat/lon values and will be ignored
El mapa muestra la distribución espacial de las 4.729 viviendas de la Zona Sur. A primera vista, la concentración principal es coherente con el sur de Cali, sin embargo se identifican anomalías geográficas importantes que requieren discusión.
Hallazgos por zona del mapa Concentración principal (masa central) — La gran nube roja central corresponde efectivamente al sur de Cali, abarcando comunas como Ciudad Jardín, El Ingenio, Valle del Lili y sectores aledaños. Esta distribución es geográficamente coherente con lo esperado para la Zona Sur. Puntos dispersos al norte del cluster — Se observan varios puntos aislados que se extienden hacia la parte superior del mapa, alejados de la masa principal. Estos puntos podrían corresponder a:
Errores de geocodificación en el dataset Registros con coordenadas mal asignadas al momento de la captura Viviendas clasificadas como “Zona Sur” administrativamente pero ubicadas físicamente fuera de ese perímetro
Puntos hacia el oriente (derecha del mapa) — El grupo de puntos que se desprende hacia la derecha sugiere viviendas en sectores limítrofes o periféricos que podrían pertenecer geográficamente a otra zona pero fueron etiquetadas como Zona Sur en la base de datos. Referencia a Pichindé (occidente) — La aparición del municipio de Pichindé en el margen izquierdo del mapa es una señal de alerta. Pichindé es una zona rural montañosa al occidente de Cali, completamente fuera del perímetro urbano sur. Ninguna vivienda urbana debería aparecer en esa dirección, lo que confirma la presencia de coordenadas incorrectas o atípicas en el dataset.
library(plotly)
library(dplyr)
df_corr <- df_sur %>%
select(preciom, areaconst, estrato, banios, habitaciones, latitud, longitud)
# Calcular matriz de correlación
matriz_corr <- cor(df_corr, use = "complete.obs")
# Etiquetas en español (7 variables)
etiquetas <- c(
"Precio (M)",
"Área Const.",
"Estrato",
"Baños",
"Habitaciones",
"Latitud",
"Longitud"
)
# Texto de anotaciones
texto_corr <- matrix(
sprintf("%.2f", matriz_corr),
nrow = nrow(matriz_corr)
)
# Mapa de calor interactivo con Viridis
plot_ly(
x = etiquetas,
y = etiquetas,
z = matriz_corr,
type = "heatmap",
colorscale = "Viridis",
zmin = -1, zmax = 1,
text = texto_corr,
hovertemplate = "<b>%{y}</b> vs <b>%{x}</b><br>Correlación: <b>%{text}</b><extra></extra>"
) %>%
add_annotations(
x = rep(etiquetas, each = length(etiquetas)),
y = rep(etiquetas, times = length(etiquetas)),
text = as.vector(texto_corr),
showarrow = FALSE,
font = list(size = 11, color = "white")
) %>%
layout(
title = list(
text = "<b>Mapa de Calor de Correlaciones — Zona Sur</b>",
font = list(size = 16)
),
xaxis = list(title = "", tickangle = -35),
yaxis = list(title = "", autorange = "reversed"),
margin = list(l = 120, b = 100, t = 60),
paper_bgcolor = "#fafafa",
plot_bgcolor = "#fafafa"
)
El área construida es el predictor más poderoso con r = 0.76, confirmando que el tamaño físico de la vivienda es el principal determinante del precio en la Zona Sur, incluso por encima del estrato socioeconómico. Esto contrasta con mercados donde la ubicación domina sobre el tamaño. Los baños superan al estrato como predictor (0.71 vs 0.60), lo que sugiere que en la Zona Sur el nivel de acabados y funcionalidad interna de la vivienda pesa más que la clasificación socioeconómica del sector al momento de fijar el precio. Habitaciones tiene correlación moderada y baja (0.39), consistente con lo observado en el modelo de Zona Norte donde el coeficiente de habitaciones era negativo. Esto refleja una multicolinealidad con baños (r = 0.65) y área construida (r = 0.56): viviendas con más habitaciones tienden a tener más baños y mayor área, por lo que su efecto independiente sobre el precio se diluye. Multicolinealidad entre predictores. Se identifican correlaciones relevantes entre variables independientes que deben considerarse en el modelo:
Baños ↔︎ Área construida: 0.69 — alta Baños ↔︎ Habitaciones: 0.65 — alta Área construida ↔︎ Habitaciones: 0.56 — moderada Estrato ↔︎ Baños: 0.45 — moderada
Estas correlaciones entre predictores pueden inflar los errores estándar de los coeficientes y dificultar la interpretación individual de cada variable, lo que explicaría el signo negativo de habitaciones observado en el modelo de Zona Norte. Las coordenadas geográficas tienen correlación débil y negativa (-0.25 y -0.18), lo que indica que la ubicación dentro de la Zona Sur no discrimina fuertemente el precio. Esto es coherente con los problemas de coordenadas atípicas identificados en el mapa anterior, que podrían estar atenuando artificialmente estas correlaciones.
# Modelo de regresión lineal múltiple
modelos <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = df_sur)
# Resumen del modelo
summary(modelos)
##
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos +
## banios, data = df_sur)
##
## Residuals:
## Min 1Q Median 3Q Max
## -826.25 -73.90 -12.28 51.37 997.38
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -364.7058 17.2411 -21.153 <2e-16 ***
## areaconst 0.8779 0.0249 35.252 <2e-16 ***
## estrato 90.8114 3.4971 25.968 <2e-16 ***
## habitaciones -23.2891 2.7545 -8.455 <2e-16 ***
## parqueaderos 73.3075 2.8449 25.768 <2e-16 ***
## banios 50.9761 2.9917 17.039 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 156 on 4099 degrees of freedom
## (624 observations deleted due to missingness)
## Multiple R-squared: 0.7723, Adjusted R-squared: 0.772
## F-statistic: 2780 on 5 and 4099 DF, p-value: < 2.2e-16
El modelo de regresión lineal para la Zona Sur
muestra un ajuste sólido, con un R² de 0.7723,
indicando que aproximadamente el 77% de la variabilidad en el precio de
las viviendas (preciom) se explica por las variables
independientes incluidas: área construida, estrato, habitaciones,
parqueaderos y baños. Entre los coeficientes, área construida
(0.878), estrato (90.81), parqueaderos
(73.31) y baños (50.98) tienen un efecto
positivo y altamente significativo sobre el precio, mientras que
habitaciones (-23.29) presenta un efecto negativo,
posiblemente asociado a correlaciones con otras características de la
vivienda o a tipologías específicas del mercado en esta zona. El
intercepto negativo (-364.71) funciona como referencia del precio base.
En conjunto, los resultados reflejan que la Zona Sur valora
principalmente el área, el estrato, los baños y los
parqueaderos, mientras que la cantidad de habitaciones no
incrementa necesariamente el valor del inmueble, y el modelo presenta un
ajuste robusto y estadísticamente significativo para la predicción del
precio de las viviendas en esta zona.
# Gráficos de diagnóstico
par(mfrow = c(2,2))
plot(modelos)
# Intervalos de confianza de los coeficientes
confint(modelos, level = 0.95)
## 2.5 % 97.5 %
## (Intercept) -398.5076936 -330.903959
## areaconst 0.8290774 0.926727
## estrato 83.9552601 97.667646
## habitaciones -28.6893717 -17.888793
## parqueaderos 67.7298798 78.885126
## banios 45.1106772 56.841496
# VIF para detectar multicolinealidad
library(car)
vif(modelos)
## areaconst estrato habitaciones parqueaderos banios
## 2.378045 1.522590 2.106874 1.988851 3.069858
# Extraer residuos del modelo
residuos <- resid(lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = df_sur))
# Test de normalidad Shapiro-Wilk
shapiro.test(residuos)
##
## Shapiro-Wilk normality test
##
## data: residuos
## W = 0.8332, p-value < 2.2e-16
# Gráfico Q-Q
qqnorm(residuos)
qqline(residuos, col = "red")
El resultado del Shapiro-Wilk para los residuos del modelo de la Zona
Sur muestra W = 0.8332 con p < 2.2e-16, lo que indica que los
residuos no se distribuyen normalmente. Esto significa que la hipótesis
nula de normalidad se rechaza y sugiere que los supuestos clásicos de
regresión lineal no se cumplen completamente en términos de
normalidad.
Implicaciones:
Los intervalos de confianza y los valores p podrían no ser totalmente confiables.
Se recomienda considerar transformaciones de la variable precio (por ejemplo, logaritmo,sqrt) o modelos robustos que no dependan de la normalidad estricta de los residuos.
A pesar de esto, el modelo sigue siendo útil para predicción, dado su alto R² de 0.7723, pero los análisis inferenciales deben interpretarse con precaución.
Conclusión — Normal Q-Q Plot Zona Sur Interpretación del gráfico Este Q-Q Plot amplificado confirma y detalla con mayor claridad la no normalidad severa de los residuos del modelo de Zona Sur identificada en los diagnósticos anteriores.
Análisis por segmentos de la distribución Cola izquierda (cuantiles teóricos < -2) Los puntos caen muy por debajo de la línea roja de referencia, con residuos que alcanzan valores de hasta -700M. Esto indica que el modelo sobreestima sistemáticamente los precios de las viviendas más baratas, generando errores negativos extremos que no serían esperables bajo normalidad. Zona central (-1 a +1) Es la única región donde los puntos se aproximan razonablemente a la línea roja, sugiriendo que el modelo funciona aceptablemente solo para viviendas de precio medio, que constituyen la mayor parte del dataset. Cola derecha (cuantiles teóricos > +2) Los puntos se despegan fuertemente hacia arriba, con residuos que superan los +700M y llegan cerca de +1000M. Esto significa que el modelo subestima significativamente los precios de las viviendas más costosas, un comportamiento típico cuando existen propiedades de lujo que escapan al patrón lineal general.
dwtest(lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = df_sur))
##
## Durbin-Watson test
##
## data: lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = df_sur)
## DW = 1.7056, p-value < 2.2e-16
## alternative hypothesis: true autocorrelation is greater than 0
El test de Durbin-Watson para el modelo de la Zona Sur arrojó DW = 1.706 con p < 2.2e-16, lo que indica la presencia de autocorrelación positiva significativa en los residuos. Esto sugiere que los errores no son completamente independientes, lo que puede afectar la precisión de los errores estándar y, por lo tanto, la confiabilidad de los intervalos de confianza y los valores p. Aunque el modelo presenta un R² alto (0.7723) y coeficientes significativos, esta autocorrelación sugiere que podrían existir patrones espaciales o características no incluidas en el modelo que influyen en los precios de las viviendas, por lo que sería recomendable considerar modelos robustos, correcciones por autocorrelación o incluir variables adicionales que capturen estos efectos.
# ============================================
# PREDICCIÓN DE PRECIO - MODELO LM ZONA SUR
# ============================================
# 1. CREAR EL DATA FRAME CON LOS DATOS DE LA VIVIENDA
nueva_vivienda <- data.frame(
areaconst = 300,
parqueaderos = 3,
banios = 3,
habitaciones = 5,
estrato = 5 # valor medio del rango 5-6
)
# 2. REALIZAR LA PREDICCIÓN CON EL MODELO
prediccion <- predict(modelos,
newdata = nueva_vivienda,
interval = "prediction",
level = 0.95)
# 3. MOSTRAR RESULTADOS
cat("===============================================\n")
## ===============================================
cat(" PREDICCIÓN DE PRECIO - ZONA SUR \n")
## PREDICCIÓN DE PRECIO - ZONA SUR
cat("===============================================\n")
## ===============================================
cat("Características de la vivienda:\n")
## Características de la vivienda:
cat(" - Tipo: Apartamento\n")
## - Tipo: Apartamento
cat(" - Área construida:", nueva_vivienda$areaconst, "m2\n")
## - Área construida: 300 m2
cat(" - Parqueaderos: ", nueva_vivienda$parqueaderos, "\n")
## - Parqueaderos: 3
cat(" - Baños: ", nueva_vivienda$banios, "\n")
## - Baños: 3
cat(" - Habitaciones: ", nueva_vivienda$habitaciones, "\n")
## - Habitaciones: 5
cat(" - Estrato: ", nueva_vivienda$estrato, "\n")
## - Estrato: 5
cat("-----------------------------------------------\n")
## -----------------------------------------------
cat("Precio estimado: ", round(prediccion[,"fit"], 2), "millones COP\n")
## Precio estimado: 609.13 millones COP
cat("Límite inferior: ", round(prediccion[,"lwr"], 2), "millones COP\n")
## Límite inferior: 303.05 millones COP
cat("Límite superior: ", round(prediccion[,"upr"], 2), "millones COP\n")
## Límite superior: 915.2 millones COP
cat("===============================================\n")
## ===============================================
# 4. COMPARACIÓN ESTRATO 5 vs ESTRATO 6
nueva_vivienda_e6 <- nueva_vivienda
nueva_vivienda_e6$estrato <- 6
prediccion_e6 <- predict(modelos,
newdata = nueva_vivienda_e6,
interval = "prediction",
level = 0.95)
cat("\nComparación por estrato:\n")
##
## Comparación por estrato:
cat(" Estrato 5 -> Precio estimado:", round(prediccion[,"fit"], 2), "millones COP\n")
## Estrato 5 -> Precio estimado: 609.13 millones COP
cat(" Estrato 6 -> Precio estimado:", round(prediccion_e6[,"fit"], 2), "millones COP\n")
## Estrato 6 -> Precio estimado: 699.94 millones COP
# ============================================================
# ALGORITMO: TOP 5 OFERTAS POTENCIALES PARA EL CLIENTE
# Modelo: modelo_sur | Base de datos: df_sur
# Presupuesto máximo: 850 millones COP
# Perfil: Apartamento, 300m², 5 hab, 3 baños, 3 parq, estrato 5-6
# ============================================================
library(dplyr)
library(leaflet)
# ------------------------------------------------------------
# PASO 1: DEFINIR PERFIL DEL CLIENTE Y PRESUPUESTO
# ------------------------------------------------------------
presupuesto_max <- 850
tolerancia_area <- 0.20 # ±20% del área solicitada (240-360m²)
area_objetivo <- 300
# ------------------------------------------------------------
# PASO 2: FILTRAR CANDIDATOS EN df_sur
# ------------------------------------------------------------
candidatos <- df_sur %>%
filter(
tipo == "Apartamento",
habitaciones == 5,
banios >= 3,
parqueaderos >= 3,
estrato %in% c(5, 6),
areaconst >= area_objetivo * (1 - tolerancia_area),
areaconst <= area_objetivo * (1 + tolerancia_area),
preciom <= presupuesto_max
) %>%
filter(!is.na(areaconst), !is.na(estrato),
!is.na(habitaciones), !is.na(parqueaderos), !is.na(banios))
cat("✔ Candidatos filtrados:", nrow(candidatos), "\n")
## ✔ Candidatos filtrados: 1
# ------------------------------------------------------------
# PASO 3: CALCULAR PRECIO ESTIMADO CON EL MODELO
# ------------------------------------------------------------
candidatos$precio_estimado <- predict(modelos, newdata = candidatos)
# ------------------------------------------------------------
# PASO 4: CALCULAR MÉTRICAS DE VALOR
# ------------------------------------------------------------
candidatos <- candidatos %>%
mutate(
# Diferencia entre precio real y estimado por el modelo
diferencia_precio = preciom - precio_estimado,
# % de diferencia (negativo = oferta por debajo del mercado = oportunidad)
pct_diferencia = (diferencia_precio / precio_estimado) * 100,
# Margen disponible del crédito
margen_credito = presupuesto_max - preciom,
# Score de valor: penaliza sobreprecios, premia oportunidades y margen
score_valor = -pct_diferencia + (margen_credito / presupuesto_max) * 50
)
# ------------------------------------------------------------
# PASO 5: SELECCIONAR TOP 5 POR SCORE DE VALOR
# ------------------------------------------------------------
top5 <- candidatos %>%
arrange(desc(score_valor)) %>%
slice_head(n = 5) %>%
select(id, barrio, estrato, areaconst, habitaciones, banios,
parqueaderos, preciom, precio_estimado,
diferencia_precio, pct_diferencia, margen_credito, score_valor,
latitud, longitud)
# ------------------------------------------------------------
# PASO 6: MOSTRAR RESULTADOS EN CONSOLA
# ------------------------------------------------------------
cat("\n===============================================================\n")
##
## ===============================================================
cat(" TOP 5 OFERTAS POTENCIALES - ZONA SUR \n")
## TOP 5 OFERTAS POTENCIALES - ZONA SUR
cat(" Presupuesto máximo: $850M COP \n")
## Presupuesto máximo: $850M COP
cat("===============================================================\n\n")
## ===============================================================
for (i in 1:nrow(top5)) {
oferta <- top5[i, ]
cat(sprintf("🏠 OFERTA #%d — ID: %d | Barrio: %s\n", i, oferta$id, oferta$barrio))
cat(sprintf(" Área: %dm² | Estrato: %d | Hab: %d | Baños: %d | Parq: %d\n",
oferta$areaconst, oferta$estrato, oferta$habitaciones,
oferta$banios, oferta$parqueaderos))
cat(sprintf(" Precio oferta: $%.1f M\n", oferta$preciom))
cat(sprintf(" Precio modelo: $%.1f M\n", oferta$precio_estimado))
cat(sprintf(" Diferencia: %+.1f M (%+.1f%%)\n",
oferta$diferencia_precio, oferta$pct_diferencia))
cat(sprintf(" Margen crédito: $%.1f M\n", oferta$margen_credito))
cat(sprintf(" Score de valor: %.2f\n", oferta$score_valor))
if (oferta$pct_diferencia < -5) {
cat(" OPORTUNIDAD: precio por debajo del mercado\n")
} else if (oferta$pct_diferencia <= 5) {
cat(" PRECIO JUSTO: alineado con el mercado\n")
} else {
cat(" SOBREPRECIO: evaluar negociación\n")
}
cat("---------------------------------------------------------------\n")
}
## 🏠 OFERTA #1 — ID: 8036 | Barrio: seminario
## Área: 256m² | Estrato: 5 | Hab: 5 | Baños: 5 | Parq: 3
## Precio oferta: $530.0 M
## Precio modelo: $672.5 M
## Diferencia: -142.5 M (-21.2%)
## Margen crédito: $320.0 M
## Score de valor: 40.01
## OPORTUNIDAD: precio por debajo del mercado
## ---------------------------------------------------------------
# ------------------------------------------------------------
# PASO 7: TABLA RESUMEN
# ------------------------------------------------------------
resumen <- top5 %>%
select(id, barrio, estrato, areaconst, preciom,
precio_estimado, pct_diferencia, margen_credito, score_valor) %>%
rename(
"ID" = id,
"Barrio" = barrio,
"Estrato" = estrato,
"Área (m²)" = areaconst,
"Precio ($M)" = preciom,
"Modelo ($M)" = precio_estimado,
"Dif. (%)" = pct_diferencia,
"Margen ($M)" = margen_credito,
"Score" = score_valor
)
print(resumen)
## # A tibble: 1 × 9
## ID Barrio Estrato `Área (m²)` `Precio ($M)` `Modelo ($M)` `Dif. (%)`
## <dbl> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 8036 seminario 5 256 530 672. -21.2
## # ℹ 2 more variables: `Margen ($M)` <dbl>, Score <dbl>
# ------------------------------------------------------------
# PASO 8: MAPA LEAFLET CON TOP 5
# ------------------------------------------------------------
top5_geo <- top5 %>% filter(!is.na(latitud), !is.na(longitud))
leaflet(top5_geo) %>%
addTiles() %>%
addMarkers(
lng = ~longitud,
lat = ~latitud,
popup = ~paste0(
"<b>Oferta ID: ", id, "</b><br>",
"Barrio: ", barrio, "<br>",
"Área: ", areaconst, " m² | Estrato: ", estrato, "<br>",
"Precio: $", preciom, "M<br>",
"Modelo: $", round(precio_estimado, 1), "M<br>",
"Diferencia: ", round(pct_diferencia, 1), "%<br>",
"Margen crédito: $", margen_credito, "M"
),
label = ~paste0("$", preciom, "M | ", barrio)
) %>%
addLegend("bottomright",
colors = "blue",
labels = "Oferta potencial",
title = "Top 5 Ofertas ≤ $850M")
Conclusión — Resultado de Búsqueda Zona Sur: Una Sola Oferta
El algoritmo identificó únicamente 1 propiedad que cumple simultáneamente todos los criterios del perfil solicitado (apartamento, ~300m², 5 habitaciones, ≥3 baños, ≥3 parqueaderos, estrato 5-6, precio ≤ $850M) en la Zona Sur. Esto es un resultado relevante en sí mismo, pues refleja la escasa oferta disponible para este perfil específico en el mercado.
Análisis de la única oferta — ID 8036 | Barrio Seminario La propiedad presenta características sólidas: 256m², estrato 5, 5 habitaciones, 5 baños y 3 parqueaderos. Su precio de oferta es $530M, mientras que el modelo lo valora en $672.5M, generando una brecha de -$142.5M (-21.2%), lo que la clasifica como una oportunidad de mercado clara. El margen de crédito disponible es amplio ($320M), lo que otorga considerable seguridad financiera al comprador. Sin embargo, hay aspectos que merecen discusión: El área es inferior a la solicitada. El cliente buscaba 300m² y la oferta tiene 256m², una diferencia de 44m² (-14.7%) que está dentro de la tolerancia del ±20% aplicada en el filtro pero que debe ser considerada en la negociación. 5 baños supera el perfil solicitado. El cliente requería 3 baños y la propiedad ofrece 5, lo que agrega valor real no reflejado en el precio de oferta y puede justificar el interés por la propiedad.