María es agente inmobiliaria y fundó su propia compañía C&A (Casas y Apartamentos) en Cali. En el contexto de una desaceleración temporal del sector, recibe una solicitud de asesoría para ubicar dos empleados con sus familias, cada uno con restricciones específicas de tipo de vivienda, zona, estrato y un crédito preaprobado.
La siguiente tabla sintetiza las condiciones exigidas por el cliente para cada vivienda. Estos parámetros definen el alcance del análisis y serán utilizados para filtrar las ofertas disponibles y estimar el precio esperado con base en modelos de regresión.
| Característica | Vivienda 1 | Vivienda 2 |
|---|---|---|
| Tipo | Casa | Apartamento |
| Área construida (m²) | 200 | 300 |
| Parqueaderos | 1 | 3 |
| Baños | 2 | 3 |
| Habitaciones | 4 | 5 |
| Estrato | 4 o 5 | 5 o 6 |
| Zona | Norte | Sur |
| Crédito preaprobado (COP) | 350 millones | 850 millones |
Los datos corresponden a ofertas inmobiliarias (últimos meses) e incluyen variables geográficas (latitud/longitud), características del inmueble y precio en millones (preciom).
Como parte del preprocesamiento inicial, se carga la base de datos y se ejecutan ajustes básicos de calidad y estructura. En particular, se estandarizan los nombres de las variables, se verifican tipos de datos (numéricos y categóricos) y se deja el dataset listo para el filtrado, visualización y modelamiento. Esta preparación reduce errores posteriores y facilita la trazabilidad del flujo analítico.
if (!exists("vivienda")) {
if (requireNamespace("paqueteMODELOS", quietly = TRUE)) {
library(paqueteMODELOS)
data("vivienda")
} else {
library(readxl)
vivienda <- readxl::read_excel("vivienda.xlsx")
}
}
vivienda <- vivienda %>%
clean_names() %>%
mutate(
zona = as.factor(zona),
tipo = as.factor(tipo),
barrio = as.factor(barrio),
piso = as.factor(piso),
estrato = as.numeric(estrato),
preciom = as.numeric(preciom),
areaconst = as.numeric(areaconst),
parqueaderos = as.numeric(parqueaderos),
banios = as.numeric(banios),
habitaciones = as.numeric(habitaciones),
longitud = as.numeric(longitud),
latitud = as.numeric(latitud)
)
glimpse(vivienda)
## Rows: 8,322
## Columns: 13
## $ id <dbl> 1147, 1169, 1350, 5992, 1212, 1724, 2326, 4386, 1209, 159…
## $ zona <fct> Zona Oriente, Zona Oriente, Zona Oriente, Zona Sur, Zona …
## $ piso <fct> NA, NA, NA, 02, 01, 01, 01, 01, 02, 02, 02, 02, 02, 02, 0…
## $ estrato <dbl> 3, 3, 3, 4, 5, 5, 4, 5, 5, 5, 6, 4, 5, 6, 4, 5, 5, 4, 5, …
## $ preciom <dbl> 250, 320, 350, 400, 260, 240, 220, 310, 320, 780, 750, 62…
## $ areaconst <dbl> 70, 120, 220, 280, 90, 87, 52, 137, 150, 380, 445, 355, 2…
## $ parqueaderos <dbl> 1, 1, 2, 3, 1, 1, 2, 2, 2, 2, NA, 3, 2, 2, 1, 4, 2, 2, 2,…
## $ banios <dbl> 3, 2, 2, 5, 2, 3, 2, 3, 4, 3, 7, 5, 6, 2, 4, 4, 4, 3, 2, …
## $ habitaciones <dbl> 6, 3, 4, 3, 3, 3, 3, 4, 6, 3, 6, 5, 6, 2, 5, 5, 4, 3, 3, …
## $ tipo <fct> Casa, Casa, Casa, Casa, Apartamento, Apartamento, Apartam…
## $ barrio <fct> 20 de julio, 20 de julio, 20 de julio, 3 de julio, acopi,…
## $ longitud <dbl> -76.51168, -76.51237, -76.51537, -76.54000, -76.51350, -7…
## $ latitud <dbl> 3.43382, 3.43369, 3.43566, 3.43500, 3.45891, 3.36971, 3.4…
La base vivienda se cargó exitosamente con 8.322
observaciones y 13 variables, combinando atributos numéricos y
categóricos, con presencia de algunos NA que deben tratarse en la
depuración
Para garantizar claridad y trazabilidad en el análisis, se construyó un diccionario de variables que resume el nombre de cada atributo y su descripción. Este insumo es clave para comprender el rol de las variables categóricas (zona, tipo, barrio) y numéricas (área, estrato, baños, habitaciones, parqueaderos) dentro de los modelos y visualizaciones.
dic <- tibble::tribble(
~variable, ~descripcion,
"zona", "Ubicación de la vivienda: Zona Centro, Zona Norte, etc.",
"piso", "Piso que ocupa la vivienda: 1, 2, ...",
"estrato", "Estrato socioeconómico: 3, 4, 5, 6",
"preciom", "Precio de la vivienda en millones de pesos",
"areaconst", "Área construida",
"parqueaderos", "Número de parqueaderos",
"banios", "Número de baños",
"habitaciones", "Número de habitaciones",
"tipo", "Tipo de vivienda: Casa, Apartamento",
"barrio", "Barrio de ubicación de la vivienda",
"longitud", "Coordenada geográfica (longitud)",
"latitud", "Coordenada geográfica (latitud)"
)
knitr::kable(dic, align = "l")
| variable | descripcion |
|---|---|
| zona | Ubicación de la vivienda: Zona Centro, Zona Norte, etc. |
| piso | Piso que ocupa la vivienda: 1, 2, … |
| estrato | Estrato socioeconómico: 3, 4, 5, 6 |
| preciom | Precio de la vivienda en millones de pesos |
| areaconst | Área construida |
| parqueaderos | Número de parqueaderos |
| banios | Número de baños |
| habitaciones | Número de habitaciones |
| tipo | Tipo de vivienda: Casa, Apartamento |
| barrio | Barrio de ubicación de la vivienda |
| longitud | Coordenada geográfica (longitud) |
| latitud | Coordenada geográfica (latitud) |
El punto 2 solicita explorar la relación entre precio y variables clave (área, estrato, baños, habitaciones y zona). Para cumplirlo de forma completa, primero se explora la base global (todas las zonas y tipos) y después se profundiza en cada caso (Casa Norte y Apartamento Sur).
En este análisis exploratorio se evalúa la relación entre el precio
(millones) y el área construida (\(m^2\)), dado que el metraje suele ser uno
de los principales determinantes del valor de una vivienda.
Adicionalmente, los puntos se colorean por zona para
identificar posibles diferencias espaciales en el mercado inmobiliario.
Con el fin de mejorar la legibilidad del gráfico, se excluyen registros
con zona faltante y se aplica un “zoom” mediante percentiles \((1\%–99\%)\), lo cual no elimina datos,
sino que ajusta la escala para evitar que valores extremos oculten el
patrón general.
Interpretación : En el mercado inmobiliario se espera una relación positiva entre área construida y precio, pero con dispersión creciente (propiedades grandes pueden ubicarse en barrios y estratos diferentes). La zona captura heterogeneidad espacial (acceso, servicios, valorización), por lo que puede desplazar el nivel de precios incluso a igual metraje.
Con el fin de comparar el comportamiento del mercado inmobiliario entre zonas, se presenta un diagrama de cajas (boxplot) del precio por zona. Este tipo de gráfico permite identificar diferencias en la mediana, la dispersión (rango intercuartílico) y la presencia de valores atípicos. En el contexto de la actividad, esta comparación es clave porque las solicitudes del cliente están condicionadas a zonas específicas (Norte y Sur), por lo que conocer el nivel y variabilidad de precios por zona aporta evidencia para interpretar los resultados del modelamiento y la selección de ofertas.
g2 <- ggplot(vivienda, aes(x = zona, y = preciom)) +
geom_boxplot() +
labs(title = "Distribución de precios por zona (global)", x = "Zona", y = "Precio (millones)") +
theme_minimal()
ggplotly(g2)
Interpretación:En el boxplot se observan diferencias claras de precio por zona: Zona Oeste tiene la mediana más alta y mayor variabilidad, mientras que Zona Oriente presenta los precios típicos más bajos. Además, en Norte y Sur aparecen muchos valores atípicos altos, indicando presencia de inmuebles “premium”.
En esta sección se aborda el Caso 1 (Vivienda 1), correspondiente a una casa en Zona Norte con un crédito preaprobado de 350 millones. El análisis se desarrolla en seis etapas:
##Paso 1: Filtro base1 + tablas + mapa
En este paso se construye la base1, filtrando únicamente las ofertas que cumplen con la solicitud de la Vivienda 1: Casa ubicada en Zona Norte. Posteriormente, se revisan los primeros registros y se generan tablas de verificación para confirmar que el filtrado se aplicó correctamente, y un mapa para visualizar la distribución geográfica de las ofertas seleccionadas.
base1 <- vivienda %>%
filter(tipo == "Casa", zona == "Zona Norte") %>%
drop_na(preciom, areaconst, estrato, parqueaderos, banios, habitaciones, latitud, longitud)
head(base1, 3)
Tablas de comprobación (validan que el filtro es correcto):
janitor::tabyl(base1, tipo)
janitor::tabyl(base1, zona)
Mapa de puntos:
library(leaflet)
leaflet(base1) %>%
addProviderTiles("CartoDB.Positron") %>%
addCircleMarkers(
lng = ~longitud, lat = ~latitud,
radius = 4, stroke = FALSE, fillOpacity = 0.6,
popup = ~paste0("<b>Barrio:</b> ", barrio,
"<br><b>Precio (M):</b> ", preciom,
"<br><b>Área:</b> ", areaconst,
"<br><b>Estrato:</b> ", estrato)
)
Discusión (por qué podrían verse puntos “fuera de
zona”): Aunque el filtro usa la etiqueta
zona == "Zona Norte", en datos reales pueden aparecer
coordenadas atípicas por errores de geocodificación, captura manual, o
porque “zona” es una categorización administrativa que no necesariamente
coincide con límites geográficos estrictos. También es común encontrar
outliers de latitud/longitud (por ejemplo, signos incorrectos o
coordenadas incompletas) que desplazan puntos fuera del clúster
esperado.
En esta etapa se realiza un análisis exploratorio focalizado sobre la base base1 (casas en Zona Norte), con el fin de identificar cómo se relaciona el precio con las variables más relevantes para la solicitud: área construida, estrato, número de baños y habitaciones (y en general las características internas del inmueble). Las visualizaciones interactivas permiten observar tendencias, dispersión y posibles valores atípicos, información que servirá como soporte para el modelo de regresión y la selección final de ofertas dentro del presupuesto.
p_area_1 <- ggplot(base1, aes(x = areaconst, y = preciom)) +
geom_point(alpha = 0.35) +
geom_smooth(method = "lm", se = TRUE) +
labs(title = "Base1: Precio vs Área construida", x = "Área construida", y = "Precio (millones)") +
theme_minimal()
ggplotly(p_area_1)
p_estrato_1 <- ggplot(base1, aes(x = as.factor(estrato), y = preciom)) +
geom_boxplot() +
labs(title = "Base1: Precio por estrato", x = "Estrato", y = "Precio (millones)") +
theme_minimal()
ggplotly(p_estrato_1)
p_banios_1 <- ggplot(base1, aes(x = banios, y = preciom)) +
geom_jitter(alpha = 0.35, width = 0.15, height = 0) +
geom_smooth(method = "lm", se = TRUE) +
labs(title = "Base1: Precio vs Baños", x = "Baños", y = "Precio (millones)") +
theme_minimal()
ggplotly(p_banios_1)
p_hab_1 <- ggplot(base1, aes(x = habitaciones, y = preciom)) +
geom_jitter(alpha = 0.35, width = 0.15, height = 0) +
geom_smooth(method = "lm", se = TRUE) +
labs(title = "Base1: Precio vs Habitaciones", x = "Habitaciones", y = "Precio (millones)") +
theme_minimal()
ggplotly(p_hab_1)
Interpretación :
Precio vs Área construida: Se observa una tendencia positiva clara: a mayor área construida, mayor precio. Sin embargo, la dispersión aumenta en áreas grandes, lo que sugiere heterocedasticidad y presencia de valores atípicos que podrían influir en el modelo.
Precio por estrato (boxplot): El precio aumenta sistemáticamente con el estrato: la mediana de estrato 6 es superior a la de estrato 3–4. Además, en estratos altos aparece mayor variabilidad y outliers de alto valor, lo cual refleja segmentos “premium” dentro de la Zona Norte.
Precio vs Baños: Existe una relación positiva entre número de baños y precio, con mayor dispersión a partir de 4–5 baños. Esto sugiere que baños puede aportar información, aunque también está asociado al tamaño/lujo del inmueble.
Precio vs Habitaciones: La relación con habitaciones es positiva pero más moderada: tener más habitaciones suele asociarse con mayor precio, aunque con alta variabilidad. Esto indica que el precio no depende solo del número de habitaciones, sino también de área, estrato y otras características.
Conclusión del EDA: En casas de Zona Norte, el precio está principalmente asociado a área y estrato, mientras que baños y habitaciones aportan señal adicional. La presencia de outliers y variabilidad creciente sugiere considerar diagnósticos del modelo y, si es necesario, transformaciones (por ejemplo log(preciom)) o métodos robustos.
En este paso se estima un modelo de regresión lineal múltiple con el objetivo de cuantificar el efecto de las principales características del inmueble sobre el precio (millones) dentro de la Zona Norte. En particular, se incluyen variables estructurales y socioeconómicas (área construida, estrato, habitaciones, parqueaderos y baños) para explicar la variación del precio y evaluar cuáles son estadísticamente significativas.
Modelo:
\[
\text{Precio}_i = \beta_0 + \beta_1(\text{Área}_i) +
\beta_2(\text{Estrato}_i) + \beta_3(\text{Habitaciones}_i)
+ \beta_4(\text{Parqueaderos}_i) + \beta_5(\text{Baños}_i) +
\varepsilon_i
\]
modelo1 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = base1)
summary(modelo1)
##
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos +
## banios, data = base1)
##
## Residuals:
## Min 1Q Median 3Q Max
## -784.29 -77.56 -16.03 47.67 978.61
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -238.17090 44.40551 -5.364 1.34e-07 ***
## areaconst 0.67673 0.05281 12.814 < 2e-16 ***
## estrato 80.63495 9.82632 8.206 2.70e-15 ***
## habitaciones 7.64511 5.65873 1.351 0.177
## parqueaderos 24.00598 5.86889 4.090 5.14e-05 ***
## banios 18.89938 7.48800 2.524 0.012 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 155.1 on 429 degrees of freedom
## Multiple R-squared: 0.6041, Adjusted R-squared: 0.5995
## F-statistic: 130.9 on 5 and 429 DF, p-value: < 2.2e-16
Conclusion
El modelo es globalmente significativo (F = 130.9; p < 2.2e-16) y explica aproximadamente el 60% de la variabilidad del precio (\(R^2 = 0.604\); \(R^2 ajustado = 0.600\)), lo que indica un ajuste razonable para un problema inmobiliario. En términos de efectos marginales y manteniendo constantes las demás variables: por cada 1 \(m^2\) adicional el precio aumenta en promedio \(0.677\) millones; por cada 1 nivel de estrato el precio aumenta en 80.635 millones; cada parqueadero adicional incrementa el precio en \(24.006\) millones y cada baño adicional en \(18.899\) millones.
En cuanto a significancia, área, estrato, parqueaderos y baños resultan estadísticamente significativos (\(p < 0.05\)), mientras que habitaciones no es significativa (\(p = 0.177\)), lo cual puede deberse a que su efecto se solapa con el área construida (colinealidad). El intercepto negativo no se interpreta de forma literal (corresponde a valores “cero” de los predictores, fuera del rango real), y se entiende principalmente como un término de ajuste del modelo.
A continuación se presenta la tabla de coeficientes del modelo con sus errores estándar, valores p e intervalos de confianza al 95%, ordenada por significancia estadística. Esto permite identificar qué variables aportan mayor evidencia para explicar el precio en casas de Zona Norte.
tab_coef <- tidy(modelo1, conf.int = TRUE) %>%
arrange(p.value) %>%
mutate(
estimate = round(estimate, 3),
std.error = round(std.error, 3),
statistic = round(statistic, 3),
p.value = signif(p.value, 3),
conf.low = round(conf.low, 3),
conf.high = round(conf.high, 3)
)
knitr::kable(tab_coef, caption = "Coeficientes del modelo (IC 95%)")
| term | estimate | std.error | statistic | p.value | conf.low | conf.high |
|---|---|---|---|---|---|---|
| areaconst | 0.677 | 0.053 | 12.814 | 0.00e+00 | 0.573 | 0.781 |
| estrato | 80.635 | 9.826 | 8.206 | 0.00e+00 | 61.321 | 99.949 |
| (Intercept) | -238.171 | 44.406 | -5.364 | 1.00e-07 | -325.450 | -150.891 |
| parqueaderos | 24.006 | 5.869 | 4.090 | 5.14e-05 | 12.471 | 35.541 |
| banios | 18.899 | 7.488 | 2.524 | 1.20e-02 | 4.182 | 33.617 |
| habitaciones | 7.645 | 5.659 | 1.351 | 1.77e-01 | -3.477 | 18.767 |
Los resultados muestran que área construida y estrato son los predictores más influyentes y significativos (\(p < 0.001\)). Además, parqueaderos y baños también aportan significativamente al precio (\(p < 0.05\)). En cambio, habitaciones no resulta significativa (\(p = 0.177\)), lo que sugiere que su efecto podría estar capturado por el área u otras variables del modelo.
glance(modelo1) %>% knitr::kable(digits = 4)
| r.squared | adj.r.squared | sigma | statistic | p.value | df | logLik | AIC | BIC | deviance | df.residual | nobs |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0.6041 | 0.5995 | 155.1149 | 130.919 | 0 | 5 | -2808.43 | 5630.86 | 5659.387 | 10322017 | 429 | 435 |
informe:
El modelo presenta un ajuste moderado–alto, con \(R^2=0.6041\) y \(R^2 ajustado=0.5995\) lo que indica que explica aproximadamente el 60% de la variabilidad del precio en casas de Zona Norte. El error estándar residual es \(\sigma\thickapprox 155.11\) millones, y la prueba global del modelo es significativa (estadístico \(F = 130.92;\; p < 0.001\)), evidenciando que el conjunto de predictores aporta capacidad explicativa.
En este paso se evalúan los supuestos del modelo de regresión lineal ajustado para base1. Para ello se revisan los gráficos diagnósticos estándar: residuos vs ajustados (linealidad y homocedasticidad), Q–Q plot (normalidad de los residuos), Scale–Location (constancia de varianza) y residuos vs leverage (observaciones influyentes). Esta validación permite determinar si el modelo cumple condiciones básicas para una inferencia confiable y, si no, proponer mejoras.
par(mfrow = c(2,2))
plot(modelo1)
par(mfrow = c(1,1))
Conclusion
Residuals vs Fitted: Los residuos se concentran alrededor de cero, pero se observa que la dispersión aumenta en valores ajustados altos, lo cual sugiere heterocedasticidad (varianza no constante) y posible presencia de outliers.
Normal Q–Q: El Q–Q plot muestra desviaciones en las colas respecto a la línea teórica, indicando que la normalidad de los residuos no se cumple perfectamente, especialmente por valores extremos.
Scale–Location: La tendencia creciente en la línea roja confirma que la variabilidad de los residuos aumenta con el valor predicho, reforzando la evidencia de heterocedasticidad.
Residuals vs Leverage Aparecen algunas observaciones con mayor leverage y cercanas a líneas de Cook, lo que sugiere posibles puntos influyentes que podrían afectar los coeficientes y el ajuste del modelo.
-Conclusión general En conjunto, los diagnósticos sugieren heterocedasticidad, desviaciones de normalidad en colas y algunas observaciones influyentes. Como alternativa de mejora, se recomienda evaluar log(preciom), usar errores robustos y revisar outliers/influencias
Pruebas útiles:
# Normalizamos -> (si n es grande, Shapiro tiende a rechazar)
set.seed(123)
res_muestra <- sample(residuals(modelo1), size = min(5000, length(residuals(modelo1))))
shapiro.test(res_muestra)
##
## Shapiro-Wilk normality test
##
## data: res_muestra
## W = 0.85246, p-value < 2.2e-16
# Heterocedasticidad
bptest(modelo1)
##
## studentized Breusch-Pagan test
##
## data: modelo1
## BP = 80.281, df = 5, p-value = 7.33e-16
# Colinealidad
vif(modelo1)
## areaconst estrato habitaciones parqueaderos banios
## 1.460998 1.307757 1.721015 1.226334 1.967421
-hapiro–Wilk: p-value < 2.2e-16 \(\Rightarrow\) se rechaza la normalidad de residuos (hay desviaciones, probablemente por colas/outliers).
-Breusch–Pagan: p-value = 7.33e-16 \(\Rightarrow\) se confirma heterocedasticidad (varianza no constante).
-VIF: valores ~ 1.23 a 1.97 \(\Rightarrow\) no hay evidencia de colinealidad fuerte (en general, VIF < 5 es aceptable).
Interpretación:
Las pruebas estadísticas corroboran lo observado en los gráficos: los residuos no cumplen completamente normalidad y existe heterocedasticidad, por lo que la inferencia del modelo podría verse afectada si se usan errores estándar “clásicos”. Sin embargo, los VIF cercanos a 1–2 indican que no hay problemas relevantes de colinealidad entre los predictores.
En esta etapa se estima el precio esperado de la Vivienda 1 con base en el modelo ajustado para casas en Zona Norte, considerando dos escenarios debido al rango de estrato solicitado (4 y 5). Para interpretar correctamente la incertidumbre, se reportan dos medidas: (i) el intervalo de confianza (95%), que corresponde al rango del precio promedio esperado para viviendas con estas características, y (ii) el intervalo de predicción (95%), que representa el rango plausible para el precio de una vivienda individual en el mercado. Esta comparación permite tomar decisiones con un criterio más realista frente al crédito disponible.
# Para estratos 4 y 5
sol1_e4 <- data.frame(areaconst = 200, estrato = 4, habitaciones = 4, parqueaderos = 1, banios = 2)
sol1_e5 <- data.frame(areaconst = 200, estrato = 5, habitaciones = 4, parqueaderos = 1, banios = 2)
# Intervalo de CONFIANZA
ic_e4 <- predict(modelo1, newdata = sol1_e4, interval = "confidence")
ic_e5 <- predict(modelo1, newdata = sol1_e5, interval = "confidence")
# Intervalo de PREDICCIÓN para vivienda individual
ip_e4 <- predict(modelo1, newdata = sol1_e4, interval = "prediction")
ip_e5 <- predict(modelo1, newdata = sol1_e5, interval = "prediction")
ic_e4; ip_e4
## fit lwr upr
## 1 312.101 287.1908 337.0111
## fit lwr upr
## 1 312.101 6.205196 617.9968
ic_e5; ip_e5
## fit lwr upr
## 1 392.7359 360.8802 424.5917
## fit lwr upr
## 1 392.7359 86.19637 699.2755
Conclusion
Con el modelo ajustado para casas en Zona Norte, se estimó el precio de la vivienda solicitada (200 \(m^2\), 4 habitaciones, 2 baños y 1 parqueadero) considerando dos escenarios de estrato. Para estrato 4, el precio esperado es 312.10 millones. El intervalo de confianza (95%) para el precio promedio esperado se ubica entre 287.19 y 337.01 millones, mientras que el intervalo de predicción (95%) para una vivienda individual es mucho más amplio: 6.21 a 617.99 millones. Para estrato 5, el precio esperado aumenta a 392.74 millones, con un intervalo de confianza (95%) entre 360.88 y 424.59 millones, y un intervalo de predicción (95%) entre 86.20 y 699.28 millones.
Se sugieren ofertas que:
1) Estén en Casa - Zona Norte (base1),
2) Tengan precio real <= 350,
3) Sean “parecidas” a la solicitud
(metraje/baños/habitaciones/parqueaderos/estrato).
Además, se calcula la predicción del modelo para priorizar ofertas consistentes con el mercado.
base1_eval <- base1 %>%
mutate(pred = as.numeric(predict(modelo1, newdata = base1))) %>%
filter(preciom <= 350) %>%
mutate(
score_similitud =
abs(areaconst - 200) +
20*abs(estrato - 4.5) +
15*abs(habitaciones - 4) +
15*abs(banios - 2) +
10*abs(parqueaderos - 1),
gap = pred - preciom # menor que 0 sugiere "barata" frente al modelo; y mayor que 0 sugiere "cara"
) %>%
arrange(score_similitud, desc(gap))
top5_350 <- base1_eval %>% slice(1:5)
top5_350 %>%
select(id, barrio, preciom, pred, gap, areaconst, estrato, habitaciones, banios, parqueaderos) %>%
knitr::kable(digits = 2)
| id | barrio | preciom | pred | gap | areaconst | estrato | habitaciones | banios | parqueaderos |
|---|---|---|---|---|---|---|---|---|---|
| 612 | calima | 270 | 228.76 | -41.24 | 196 | 3 | 4 | 2 | 1 |
| 1163 | la merced | 350 | 427.57 | 77.57 | 216 | 5 | 4 | 2 | 2 |
| 1887 | vipasa | 340 | 437.67 | 97.67 | 203 | 5 | 4 | 3 | 2 |
| 1270 | el bosque | 350 | 426.42 | 76.42 | 203 | 5 | 5 | 2 | 2 |
| 4210 | el bosque | 350 | 459.65 | 109.65 | 200 | 5 | 4 | 3 | 3 |
Se seleccionaron cinco ofertas en Zona Norte con precio ≤ 350 millones y alta similitud con el requerimiento. En general, las opciones en estrato 5 presentan características cercanas al objetivo (area de 200 \(m^2\) y 4 habitaciones$) y muestran gap positivo, lo que sugiere precios publicados relativamente favorables frente al valor estimado por el modelo; no obstante, la decisión final debe complementarse con una verificación física del inmueble y condiciones de negociación.
Para complementar la selección de ofertas, se presenta un mapa interactivo con la ubicación geográfica de las cinco viviendas recomendadas. Esta visualización permite verificar la distribución espacial de las alternativas dentro de la Zona Norte y facilita la toma de decisiones al considerar criterios prácticos como cercanía a vías principales, accesibilidad y concentración por sectores.
# Aseguramos que top5_350 tenga lat/long y crea un número para marcar 1..5
top5_350_map <- top5_350 %>%
mutate(rank = row_number())
# Límites para hacer zoom automático
lng_min <- min(top5_350_map$longitud, na.rm = TRUE)
lng_max <- max(top5_350_map$longitud, na.rm = TRUE)
lat_min <- min(top5_350_map$latitud, na.rm = TRUE)
lat_max <- max(top5_350_map$latitud, na.rm = TRUE)
# Iconos numerados (1..5)
icons_num <- awesomeIcons(
icon = "home",
library = "fa",
markerColor = "blue",
iconColor = "white"
)
leaflet(top5_350_map) %>%
addProviderTiles("CartoDB.Positron") %>%
fitBounds(lng1 = lng_min, lat1 = lat_min, lng2 = lng_max, lat2 = lat_max) %>%
addAwesomeMarkers(
lng = ~longitud, lat = ~latitud,
icon = icons_num,
label = ~paste0("Oferta #", rank, " | ID: ", id, " | $", preciom, "M"),
popup = ~paste0(
"<b>Oferta #</b> ", rank,
"<br><b>ID:</b> ", id,
"<br><b>Barrio:</b> ", barrio,
"<br><b>Precio real (M):</b> ", preciom,
"<br><b>Pred (M):</b> ", round(pred, 1),
"<br><b>Área (m²):</b> ", areaconst,
"<br><b>Estrato:</b> ", estrato,
"<br><b>Hab/Baños/Pq:</b> ", habitaciones, "/", banios, "/", parqueaderos
)
) %>%
addLabelOnlyMarkers(
lng = ~longitud, lat = ~latitud,
label = ~as.character(rank),
labelOptions = labelOptions(
noHide = TRUE, direction = "center",
textOnly = TRUE, style = list("font-weight" = "bold", "font-size" = "12px")
)
)
El mapa muestra que las cinco ofertas seleccionadas se concentran dentro de la Zona Norte y se ubican en sectores relativamente cercanos entre sí, lo que sugiere disponibilidad de alternativas en un mismo corredor urbano. Esto facilita la comparación y priorización de visitas, manteniendo el criterio de presupuesto y similitud con la vivienda solicitada.
En esta sección se desarrolla el Caso 2 (Vivienda 2), correspondiente a un apartamento en Zona Sur con un crédito preaprobado de 850 millones. Al igual que en el Caso 1, el análisis se realiza de manera estructurada:
filtrado de ofertas que cumplen el tipo y la zona,
análisis exploratorio de las variables más relevantes para el precio,
estimación de un modelo de regresión lineal múltiple,
validación de supuestos,
predicción del precio para las características solicitadas considerando estrato 5 y 6, y
selección de al menos cinco ofertas potenciales que se ajusten al presupuesto y al perfil requerido.
Se filtran los datos para obtener apartamentos en Zona Sur (base2), se verifica el resultado con tablas y se visualiza su ubicación en un mapa.
base2 <- vivienda %>%
filter(tipo == "Apartamento", zona == "Zona Sur") %>%
drop_na(preciom, areaconst, estrato, parqueaderos, banios, habitaciones, latitud, longitud)
head(base2, 3)
janitor::tabyl(base2, tipo)
janitor::tabyl(base2, zona)
Conclusion
Conclusión: Las tablas de verificación confirman que el filtrado se aplicó correctamente: base2 contiene 2.381 registros, todos correspondientes a Apartamentos (100%) y ubicados en Zona Sur (100%), sin observaciones de otros tipos de vivienda ni de otras zonas.
leaflet(base2) %>%
addProviderTiles("CartoDB.Positron") %>%
addCircleMarkers(
lng = ~longitud, lat = ~latitud,
radius = 4, stroke = FALSE, fillOpacity = 0.6,
popup = ~paste0("<b>Barrio:</b> ", barrio,
"<br><b>Precio (M):</b> ", preciom,
"<br><b>Área:</b> ", areaconst,
"<br><b>Estrato:</b> ", estrato)
)
El mapa muestra una alta concentración de apartamentos en Zona Sur y algunos puntos dispersos, lo que facilita identificar sectores con mayor oferta para priorizar la búsqueda.
En esta etapa se realiza un análisis exploratorio focalizado sobre base2 (apartamentos en Zona Sur) para identificar cómo se relaciona el precio con las variables más relevantes para la solicitud: área construida, estrato, número de baños y habitaciones. Las visualizaciones interactivas permiten observar tendencias generales, niveles de dispersión y posibles valores atípicos, información que sirve como base para el ajuste del modelo de regresión y la posterior selección de ofertas dentro del crédito de 850 millones.
p_area_2 <- ggplot(base2, aes(x = areaconst, y = preciom)) +
geom_point(alpha = 0.35) +
geom_smooth(method = "lm", se = TRUE) +
labs(title = "Base2: Precio vs Área construida", x = "Área construida", y = "Precio (millones)") +
theme_minimal()
ggplotly(p_area_2)
p_estrato_2 <- ggplot(base2, aes(x = as.factor(estrato), y = preciom)) +
geom_boxplot() +
labs(title = "Base2: Precio por estrato", x = "Estrato", y = "Precio (millones)") +
theme_minimal()
ggplotly(p_estrato_2)
p_banios_2 <- ggplot(base2, aes(x = banios, y = preciom)) +
geom_jitter(alpha = 0.35, width = 0.15, height = 0) +
geom_smooth(method = "lm", se = TRUE) +
labs(title = "Base2: Precio vs Baños", x = "Baños", y = "Precio (millones)") +
theme_minimal()
ggplotly(p_banios_2)
p_hab_2 <- ggplot(base2, aes(x = habitaciones, y = preciom)) +
geom_jitter(alpha = 0.35, width = 0.15, height = 0) +
geom_smooth(method = "lm", se = TRUE) +
labs(title = "Base2: Precio vs Habitaciones", x = "Habitaciones", y = "Precio (millones)") +
theme_minimal()
ggplotly(p_hab_2)
Conclusion
Precio vs Área construida: Se observa una relación positiva fuerte: a mayor área construida, mayor precio. La dispersión aumenta en áreas grandes y aparecen algunos valores atípicos, lo que sugiere variabilidad del mercado para apartamentos de mayor tamaño.
Precio por estrato (boxplot): El precio aumenta claramente con el estrato. En particular, estrato 6 presenta la mediana más alta y una mayor variabilidad, además de varios outliers de alto valor, lo que indica un segmento premium en Zona Sur.
Precio vs Baños Existe una tendencia positiva: más baños suele asociarse con mayor precio. No obstante, hay dispersión considerable en baños 2–4, lo que indica que el precio también depende de otras características (área, estrato y ubicación específica).
Precio vs Habitaciones La relación con habitaciones es positiva pero moderada. A medida que aumentan las habitaciones, el precio tiende a subir, aunque con alta variabilidad, especialmente en 3–4 habitaciones.
En apartamentos de Zona Sur, el precio está principalmente asociado a área y estrato, mientras que baños y habitaciones aportan señal adicional con mayor dispersión; por ello, el modelo debe validarse y complementarse con criterios de selección para escoger las mejores ofertas dentro de 850M.
En este paso se ajusta un modelo de regresión lineal múltiple para explicar el precio (millones) de los apartamentos en Zona Sur a partir de variables estructurales y socioeconómicas: área construida, estrato, habitaciones, parqueaderos y baños. El objetivo es cuantificar el efecto marginal de cada predictor, identificar cuáles variables son estadísticamente significativas y evaluar el nivel de ajuste del modelo para apoyar la predicción y selección de ofertas.
modelo2 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = base2)
summary(modelo2)
##
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos +
## banios, data = base2)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1092.02 -42.28 -1.33 40.58 926.56
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -261.62501 15.63220 -16.736 < 2e-16 ***
## areaconst 1.28505 0.05403 23.785 < 2e-16 ***
## estrato 60.89709 3.08408 19.746 < 2e-16 ***
## habitaciones -24.83693 3.89229 -6.381 2.11e-10 ***
## parqueaderos 72.91468 3.95797 18.422 < 2e-16 ***
## banios 50.69675 3.39637 14.927 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 98.02 on 2375 degrees of freedom
## Multiple R-squared: 0.7485, Adjusted R-squared: 0.748
## F-statistic: 1414 on 5 and 2375 DF, p-value: < 2.2e-16
En apartamentos de Zona Sur, el precio está fuertemente explicado por área, estrato, parqueaderos y baños, con un ajuste alto(\(R^2adj \approx 0748\)). El efecto negativo de habitaciones debe interpretarse con cautela, ya que puede estar influenciado por colinealidad con el área o por características no incluidas en el modelo.
A continuación se presentan los coeficientes estimados del modelo para apartamentos en Zona Sur, junto con sus errores estándar, valores p e intervalos de confianza al 95%, ordenados por significancia. Esta tabla permite identificar qué variables tienen mayor evidencia estadística y cuantificar su efecto marginal sobre el precio.
tidy(modelo2, conf.int = TRUE) %>%
arrange(p.value) %>%
knitr::kable(digits = 4)
| term | estimate | std.error | statistic | p.value | conf.low | conf.high |
|---|---|---|---|---|---|---|
| areaconst | 1.2850 | 0.0540 | 23.7853 | 0 | 1.1791 | 1.3910 |
| estrato | 60.8971 | 3.0841 | 19.7457 | 0 | 54.8493 | 66.9448 |
| parqueaderos | 72.9147 | 3.9580 | 18.4223 | 0 | 65.1533 | 80.6761 |
| (Intercept) | -261.6250 | 15.6322 | -16.7363 | 0 | -292.2792 | -230.9708 |
| banios | 50.6967 | 3.3964 | 14.9267 | 0 | 44.0366 | 57.3569 |
| habitaciones | -24.8369 | 3.8923 | -6.3811 | 0 | -32.4696 | -17.2043 |
Los resultados indican que área construida, estrato, parqueaderos y baños tienen efectos positivos y estadísticamente significativos sobre el precio (\(p \approx 0\)) con intervalos de confianza que no incluyen cero. En contraste, habitaciones presenta un efecto negativo y significativo, lo que sugiere que, controlando por área y estrato, un mayor número de habitaciones podría asociarse con distribuciones menos valoradas o con colinealidad respecto al metraje. El intercepto negativo no se interpreta de forma literal, ya que corresponde a un escenario fuera del rango real de las variables.
glance(modelo2) %>% knitr::kable(digits = 4)
| r.squared | adj.r.squared | sigma | statistic | p.value | df | logLik | AIC | BIC | deviance | df.residual | nobs |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0.7485 | 0.748 | 98.0194 | 1413.802 | 0 | 5 | -14292.77 | 28599.54 | 28639.96 | 22818541 | 2375 | 2381 |
El modelo presenta un ajuste alto, con \(R^2 = 0.7485\) y \(R_{adj}^2=0.748\),, lo que indica que explica cerca del 75% de la variabilidad del precio en base2. El error estándar residual es \(\sigma \approx 98.02\) millones y la prueba global es significativa (F ≈ 1413.80; p < 0.001), confirmando que el modelo es útil para explicar y predecir el precio en apartamentos de Zona Sur.
Conclusión del modelo: En apartamentos de Zona Sur,
área y estrato suelen capturar gran parte del valor, pero pueden entrar
factores no observados (amenities del conjunto, antigüedad,
administración, vista, etc.). Si el ajuste es limitado, se recomienda
incluir barrio y/o transformaciones, o comparar con métodos
no lineales.
En este paso se validan los principales supuestos del modelo de regresión ajustado para apartamentos en Zona Sur. Para ello se revisan los gráficos diagnósticos estándar: Residuals vs Fitted (linealidad y homocedasticidad), Q–Q plot (normalidad de residuos), Scale–Location (constancia de varianza) y Residuals vs Leverage (observaciones influyentes). Esta evaluación permite identificar posibles desviaciones que puedan afectar la inferencia y proponer mejoras.
par(mfrow = c(2,2))
plot(modelo2)
par(mfrow = c(1,1))
Conclusiones de las gráficas diagnósticas
Residuals vs Fitted: Los residuos se concentran alrededor de cero, pero la dispersión aumenta en valores ajustados altos y se observan algunos puntos extremos, lo cual sugiere heterocedasticidad y presencia de outliers.
Q–Q Residuals: El Q–Q plot muestra desviaciones claras en las colas, especialmente en la cola superior, indicando que la normalidad de los residuos no se cumple completamente debido a valores extremos.
Scale–Location: La línea roja presenta una tendencia creciente, lo que confirma que la varianza de los residuos no es constante; existe heterocedasticidad, especialmente para precios predichos altos.
Residuals vs Leverage: Se identifican algunas observaciones con leverage relativamente alto (por ejemplo la marcada como 2042), potencialmente influyentes. Estas observaciones pueden afectar el ajuste y conviene revisarlas o aplicar técnicas robustas.
Conclusión general: En conjunto, los diagnósticos sugieren heterocedasticidad, desviaciones de normalidad en colas y algunos puntos influyentes. Como alternativas, se recomienda evaluar log(preciom), usar errores robustos y revisar outliers/influencias antes de basar decisiones únicamente en el modelo.
set.seed(123)
res_muestra2 <- sample(residuals(modelo2), size = min(5000, length(residuals(modelo2))))
shapiro.test(res_muestra2)
##
## Shapiro-Wilk normality test
##
## data: res_muestra2
## W = 0.79118, p-value < 2.2e-16
bptest(modelo2)
##
## studentized Breusch-Pagan test
##
## data: modelo2
## BP = 754.81, df = 5, p-value < 2.2e-16
vif(modelo2)
## areaconst estrato habitaciones parqueaderos banios
## 2.066518 1.545162 1.429280 1.737878 2.529494
Las pruebas confirman lo observado en los diagnósticos: los residuos no cumplen completamente normalidad y existe heterocedasticidad, por lo que la inferencia con errores estándar clásicos puede verse afectada. Sin embargo, los VIF se mantienen en valores bajos, indicando que no hay problemas relevantes de colinealidad entre los predictores.
En esta etapa se estima el precio esperado para la Vivienda 2 (apartamento en Zona Sur) a partir del modelo ajustado sobre base2. Debido a que el cliente solicita un rango de estrato (5 o 6), se evalúan ambos escenarios y se reporta un intervalo de predicción (95%), el cual representa la incertidumbre asociada al precio de una vivienda individual con estas características en el mercado.
sol2_e5 <- data.frame(areaconst = 300, estrato = 5, habitaciones = 5, parqueaderos = 3, banios = 3)
sol2_e6 <- data.frame(areaconst = 300, estrato = 6, habitaciones = 5, parqueaderos = 3, banios = 3)
pred2_e5 <- predict(modelo2, newdata = sol2_e5, interval = "prediction")
pred2_e6 <- predict(modelo2, newdata = sol2_e6, interval = "prediction")
pred2_e5
## fit lwr upr
## 1 675.0247 481.455 868.5945
pred2_e6
## fit lwr upr
## 1 735.9218 542.3141 929.5296
Para un apartamento con 300 \(m^2\), 5 habitaciones, 3 baños y 3 parqueaderos, el modelo estima un precio esperado de 675.02 millones en estrato 5, con un intervalo de predicción (95%) entre 481.46 y 868.59 millones. Para estrato 6, el precio esperado aumenta a 735.92 millones, con un intervalo de predicción (95%) entre 542.31 y 929.53 millones.
En ambos casos, el aumento de estrato incrementa el precio esperado y los intervalos reflejan la variabilidad natural del mercado y factores no incluidos en el modelo.
Conclusion
Dado el crédito máximo de 850 millones, el escenario estrato 5 se ajusta cómodamente al presupuesto, mientras que en estrato 6 el valor esperado sigue dentro del crédito, aunque el intervalo de predicción supera 850M en su límite superior.
En este paso se seleccionan ofertas potenciales para la Vivienda 2 que cumplan el presupuesto del cliente (≤ 850 millones) y se aproximen al perfil solicitado (apartamento en Zona Sur con 300 \(m^2\), 3 parqueaderos, 3 baños y 5 habitaciones; estrato 5–6). Para priorizar las alternativas, se calcula (i) la predicción del modelo para cada oferta, (ii) un score de similitud (distancia ponderada respecto a la vivienda objetivo) y (iii) el gap entre el precio predicho y el precio publicado, con el fin de identificar opciones con buena relación costo–valor.
base2_eval <- base2 %>%
mutate(pred = as.numeric(predict(modelo2, newdata = base2))) %>%
filter(preciom <= 850) %>%
mutate(
score_similitud =
abs(areaconst - 300) +
20*abs(estrato - 5.5) +
15*abs(habitaciones - 5) +
15*abs(banios - 3) +
10*abs(parqueaderos - 3),
gap = pred - preciom
) %>%
arrange(score_similitud, desc(gap))
top5_850 <- base2_eval %>% slice(1:5)
top5_850 %>%
select(id, barrio, preciom, pred, gap, areaconst, estrato, habitaciones, banios, parqueaderos) %>%
knitr::kable(digits = 2)
| id | barrio | preciom | pred | gap | areaconst | estrato | habitaciones | banios | parqueaderos |
|---|---|---|---|---|---|---|---|---|---|
| 8113 | cuarto de legua | 410 | 671.93 | 261.93 | 295.55 | 5 | 4 | 4 | 2 |
| 6175 | capri | 350 | 661.31 | 311.31 | 270.00 | 5 | 4 | 3 | 3 |
| 7512 | seminario | 670 | 751.58 | 81.58 | 300.00 | 5 | 6 | 5 | 3 |
| 7658 | cuarto de legua | 520 | 703.34 | 183.34 | 320.00 | 5 | 4 | 4 | 2 |
| 7680 | pampa linda | 450 | 682.29 | 232.29 | 267.00 | 5 | 3 | 3 | 3 |
Se seleccionaron cinco ofertas en Zona Sur con precio menor o igual a 850 millones y características cercanas al perfil requerido. Todas las opciones resultaron en estrato 5 y presentan gap positivo (predicción mayor que el precio publicado), lo que sugiere que, según el modelo, están por debajo del valor esperado para su combinación de área y atributos. En particular, destacan inmuebles con áreas cercanas a 300 \(m^2\) y parqueaderos entre 2 y 3, lo que los hace viables para el proceso de visita y comparación dentro del crédito disponible.
top5_850_map <- top5_850 %>%
mutate(
rank = row_number(),
# Clasificación simple por gap
etiqueta_gap = ifelse(gap > 0, "Oportunidad (gap>0)", "Más cara vs modelo (gap≤0)")
)
# Zoom automático
lng_min <- min(top5_850_map$longitud, na.rm = TRUE)
lng_max <- max(top5_850_map$longitud, na.rm = TRUE)
lat_min <- min(top5_850_map$latitud, na.rm = TRUE)
lat_max <- max(top5_850_map$latitud, na.rm = TRUE)
# Iconos por categoría (cambia colores aquí)
icons_gap <- awesomeIcons(
icon = "building",
library = "fa",
markerColor = ifelse(top5_850_map$gap > 0, "green", "red"),
iconColor = "white"
)
leaflet(top5_850_map) %>%
addProviderTiles("CartoDB.Positron") %>%
fitBounds(lng1 = lng_min, lat1 = lat_min, lng2 = lng_max, lat2 = lat_max) %>%
addAwesomeMarkers(
lng = ~longitud, lat = ~latitud,
icon = icons_gap,
label = ~paste0("Oferta #", rank, " | ", etiqueta_gap, " | $", preciom, "M"),
popup = ~paste0(
"<b>Oferta #</b> ", rank,
"<br><b>Clasificación:</b> ", etiqueta_gap,
"<br><b>ID:</b> ", id,
"<br><b>Barrio:</b> ", barrio,
"<br><b>Precio real (M):</b> ", preciom,
"<br><b>Pred (M):</b> ", round(pred, 1),
"<br><b>Gap (Pred - Real):</b> ", round(gap, 1),
"<br><b>Área (m²):</b> ", areaconst,
"<br><b>Estrato:</b> ", estrato,
"<br><b>Hab/Baños/Pq:</b> ", habitaciones, "/", banios, "/", parqueaderos
)
) %>%
addLabelOnlyMarkers(
lng = ~longitud, lat = ~latitud,
label = ~as.character(rank),
labelOptions = labelOptions(
noHide = TRUE, direction = "center",
textOnly = TRUE,
style = list("font-weight" = "bold", "font-size" = "12px")
)
) %>%
addLegend(
position = "bottomright",
colors = c("green", "red"),
labels = c("Oportunidad (gap>0)", "Más cara vs modelo (gap≤0)"),
title = "Clasificación por gap",
opacity = 1
)
El mapa muestra la ubicación de las cinco ofertas recomendadas en Zona Sur, facilitando la comparación espacial y la priorización de visitas según cercanía y concentración de alternativas.
Determinantes del precio: En ambos casos, el área construida y el estrato son los factores con mayor capacidad explicativa del precio. Variables como parqueaderos y baños también aportan valor, aunque parte de su efecto puede solaparse con el tamaño del inmueble.
Importancia del contexto espacial:La segmentación por zona (y a mayor detalle por barrio) es clave, ya que captura diferencias del mercado que no siempre quedan reflejadas únicamente en variables físicas.
Incertidumbre: Los intervalos de predicción evidencian alta variabilidad del precio debido a factores no incluidos (acabados, antigüedad, estado del inmueble, administración del conjunto y negociación).
Vivienda 1 (Casa Norte, 350M):Se recomienda priorizar las ofertas del listado top5_350, ya que cumplen el presupuesto y presentan alta similitud con los criterios solicitados. Se sugiere iniciar visitas por aquellas con gap positivo, dado que el modelo indica una posible mejor relación costo–valor.
Vivienda 2 (Apartamento Sur, 850M): Se
recomienda enviar top5_850 como alternativas iniciales.
Dado el presupuesto, además de las variables cuantitativas, conviene
evaluar aspectos cualitativos como seguridad, accesibilidad,
administración del conjunto, servicios y entorno urbano.