library(paqueteMODELOS)
## Cargando paquete requerido: boot
## Cargando paquete requerido: broom
## Warning: package 'broom' was built under R version 4.5.3
## Cargando paquete requerido: GGally
## Warning: package 'GGally' was built under R version 4.5.3
## Cargando paquete requerido: ggplot2
## Warning: package 'ggplot2' was built under R version 4.5.3
## Cargando paquete requerido: gridExtra
## Warning: package 'gridExtra' was built under R version 4.5.3
## Cargando paquete requerido: knitr
## Cargando paquete requerido: summarytools
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.5.3
##
## 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(tidyr)
library(ggplot2)
library(plotly)
## Warning: package 'plotly' was built under R version 4.5.3
##
## Adjuntando el paquete: 'plotly'
## The following object is masked from 'package:ggplot2':
##
## last_plot
## The following object is masked from 'package:stats':
##
## filter
## The following object is masked from 'package:graphics':
##
## layout
library(leaflet)
## Warning: package 'leaflet' was built under R version 4.5.3
library(knitr)
library(factoextra)
## Warning: package 'factoextra' was built under R version 4.5.3
## Welcome to factoextra!
## Want to learn more? See two factoextra-related books at https://www.datanovia.com/library/principal-component-methods
data("vivienda")
str(vivienda)
## spc_tbl_ [8,322 × 13] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
## $ id : num [1:8322] 1147 1169 1350 5992 1212 ...
## $ zona : chr [1:8322] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
## $ piso : chr [1:8322] NA NA NA "02" ...
## $ estrato : num [1:8322] 3 3 3 4 5 5 4 5 5 5 ...
## $ preciom : num [1:8322] 250 320 350 400 260 240 220 310 320 780 ...
## $ areaconst : num [1:8322] 70 120 220 280 90 87 52 137 150 380 ...
## $ parqueaderos: num [1:8322] 1 1 2 3 1 1 2 2 2 2 ...
## $ banios : num [1:8322] 3 2 2 5 2 3 2 3 4 3 ...
## $ habitaciones: num [1:8322] 6 3 4 3 3 3 3 4 6 3 ...
## $ tipo : chr [1:8322] "Casa" "Casa" "Casa" "Casa" ...
## $ barrio : chr [1:8322] "20 de julio" "20 de julio" "20 de julio" "3 de julio" ...
## $ longitud : num [1:8322] -76.5 -76.5 -76.5 -76.5 -76.5 ...
## $ latitud : num [1:8322] 3.43 3.43 3.44 3.44 3.46 ...
## - attr(*, "spec")=List of 3
## ..$ cols :List of 13
## .. ..$ id : list()
## .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
## .. ..$ zona : list()
## .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
## .. ..$ piso : list()
## .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
## .. ..$ estrato : list()
## .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
## .. ..$ preciom : list()
## .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
## .. ..$ areaconst : list()
## .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
## .. ..$ parqueaderos: list()
## .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
## .. ..$ banios : list()
## .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
## .. ..$ habitaciones: list()
## .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
## .. ..$ tipo : list()
## .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
## .. ..$ barrio : list()
## .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
## .. ..$ longitud : list()
## .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
## .. ..$ latitud : list()
## .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
## ..$ default: list()
## .. ..- attr(*, "class")= chr [1:2] "collector_guess" "collector"
## ..$ delim : chr ";"
## ..- attr(*, "class")= chr "col_spec"
## - attr(*, "problems")=<externalptr>
Una vez cargada la información, se debe realizar una limpieza inicial, donde se eliminan los valores nulos, celdas vacías y demás elementos que puedan afectar el análisis del conjunto de datos.
# Ver datos faltantes por variable
cat("=== DATOS FALTANTES POR VARIABLE ===\n")
## === DATOS FALTANTES POR VARIABLE ===
colSums(is.na(vivienda))
## id zona piso estrato preciom areaconst
## 3 3 2638 3 2 3
## parqueaderos banios habitaciones tipo barrio longitud
## 1605 3 3 3 3 3
## latitud
## 3
# Porcentaje de datos faltantes
cat("\n=== PORCENTAJE DE DATOS FALTANTES ===\n")
##
## === PORCENTAJE DE DATOS FALTANTES ===
round(colSums(is.na(vivienda))/nrow(vivienda)*100, 2)
## id zona piso estrato preciom areaconst
## 0.04 0.04 31.70 0.04 0.02 0.04
## parqueaderos banios habitaciones tipo barrio longitud
## 19.29 0.04 0.04 0.04 0.04 0.04
## latitud
## 0.04
# Número de registros antes de limpiar
cat("\nRegistros originales:", nrow(vivienda))
##
## Registros originales: 8322
# Limpiar datos
vivienda_limpia <- vivienda %>%
dplyr::select(estrato, preciom, areaconst,
parqueaderos, banios, habitaciones) %>%
na.omit()
cat("\nRegistros después de limpiar:", nrow(vivienda_limpia))
##
## Registros después de limpiar: 6717
# Resumen estadístico
summary(vivienda_limpia)
## estrato preciom areaconst parqueaderos
## Min. :3.00 Min. : 58.0 Min. : 30.0 Min. : 1.000
## 1st Qu.:4.00 1st Qu.: 248.0 1st Qu.: 86.0 1st Qu.: 1.000
## Median :5.00 Median : 355.0 Median : 130.0 Median : 2.000
## Mean :4.83 Mean : 468.9 Mean : 181.1 Mean : 1.835
## 3rd Qu.:6.00 3rd Qu.: 580.0 3rd Qu.: 233.0 3rd Qu.: 2.000
## Max. :6.00 Max. :1999.0 Max. :1745.0 Max. :10.000
## banios habitaciones
## Min. : 0.000 Min. : 0.000
## 1st Qu.: 2.000 1st Qu.: 3.000
## Median : 3.000 Median : 3.000
## Mean : 3.255 Mean : 3.611
## 3rd Qu.: 4.000 3rd Qu.: 4.000
## Max. :10.000 Max. :10.000
El resumen estadístico de las 6 variables numéricas seleccionadas para el análisis muestra lo siguiente:
En este caso preciom y areaconst muestran asimetría positiva clara, consistente con el comportamiento típico de precios inmobiliarios donde unas pocas propiedades de lujo elevan el promedio. Esto sugiere evaluar más adelante una transformación logarítmica sobre estas variables si el modelo de regresión lo requiere.
vivienda_limpia %>%
pivot_longer(cols = everything(), names_to = "variable", values_to = "valor") %>%
ggplot(aes(x = variable, y = valor)) +
geom_boxplot(fill = "steelblue", alpha = 0.6) +
facet_wrap(~variable, scales = "free") +
theme_minimal() +
labs(title = "Boxplots de variables numéricas", x = "", y = "Valor")
Se observan valores atípicos superiores en areaconst, preciom y
parqueaderos, correspondientes a un segmento de propiedades grandes o de
lujo dentro de la oferta general. Estrato no presenta
outliers, lo cual es esperable por tratarse de una variable ordinal
acotada.
ado que estos valores atípicos parecen corresponder a propiedades legítimas de alto valor (no errores de captura), no se recomienda eliminarlos del análisis general; sin embargo, se debe considerar su influencia sobre los supuestos del modelo de regresión (sección de validaDción, punto 4).
Existen registros cuya zona declarada no corresponde con su ubicación geográfica real. Para evidenciarlo, se compara la distribución de la latitud según la zona declarada:
ggplot(vivienda, aes(x = zona, y = latitud, fill = zona)) +
geom_boxplot() +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
labs(title = "Distribución de latitud según zona declarada",
y = "Latitud", x = "Zona")
## Warning: Removed 3 rows containing non-finite outside the scale range
## (`stat_boxplot()`).
Se observa que Zona Norte presenta la mediana de latitud más alta 3.47, consistente con su ubicación geográfica esperada, mientras que Zona Sur presenta la mediana más baja 3.39. Sin embargo, Zona Centro, Zona Oeste y Zona Oriente se solapan considerablemente entre sí medianas entre 3.44 y 3.45, lo que dificulta diferenciarlas únicamente por latitud.
Adicionalmente, se observan varios puntos atípicos en la parte inferior de casi todas las zonas, incluyendo “Zona Norte”, que caen por debajo de 3.40 e incluso cerca de 3.35 es decir, registros etiquetados como zona norte pero con una latitud típica de zona sur. Esto confirma el problema de codificación mencionado. También aparece una categoría NA sin datos, que deberá revisarse.
leaflet(vivienda) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(~longitud, ~latitud,
color = ~colorFactor("Set1", zona)(zona),
radius = 3, opacity = 0.7,
popup = ~paste("Zona declarada:", zona))
## Warning in validateCoords(lng, lat, funcName): Data contains 3 rows with either
## missing or invalid lat/lon values and will be ignored
Se observa que los colores (zonas declaradas) están completamente mezclados en el espacio: en la parte superior (norte real de la ciudad) predominan puntos azules, pero también aparecen puntos naranja, verdes y morados en esa misma zona; en la parte inferior (sur real) predomina el naranja, pero igualmente se mezclan puntos azules, verdes y morados. No hay una separación geográfica limpia entre colores — cualquier zona declarada aparece dispersa por toda la extensión de la ciudad.
Gracias a esto descarta la posibilidad de depurar la zona norte
usando únicamente la etiqueta zona o un simple corte de
latitud, ya que el nivel de mezcla es alto en todas las categorías, no
solo en los bordes. Es necesario usar un criterio geográfico objetivo el
análisis de conglomerados (clustering) con latitud y
longitud — que agrupe las viviendas según su ubicación real,
independientemente de la etiqueta declarada, y así construir una
definición confiable de “zona norte” para filtrar
base1.
sum(is.na(vivienda$longitud))
## [1] 3
sum(is.na(vivienda$latitud))
## [1] 3
Se identificaron 3 registros con valores faltantes en
longitud y latitud. Dado que representan una
proporción mínima del total de la base, se excluyen únicamente para
efectos del análisis de conglomerados geográfico.
# Excluir registros con NA en coordenadas y estandarizar
geo <- vivienda %>%
filter(!is.na(longitud), !is.na(latitud))
geo_scaled <- geo %>%
select(longitud, latitud) %>%
scale()
Para determinar el número óptimo de conglomerados se utiliza el método del codo, que nos ayuda a evaluar la variabilidad interna (WSS) para distintos valores de k
set.seed(123)
fviz_nbclust(geo_scaled, kmeans, method = "wss", k.max = 10) +
labs(title = "Método del codo para número óptimo de clusters")
El método del codo muestra una reducción marcada de la varianza interna hasta k=4, punto a partir del cual agregar más conglomerados no mejora sustancialmente el ajuste. Por lo tanto, se seleccionan 4 conglomerados para el análisis, consistente con las principales zonas geográficas de la ciudad.
Ahora apliquemos el k-means con k=4:
set.seed(123)
kmeans_geo <- kmeans(geo_scaled, centers = 4, nstart = 25)
geo$cluster_geo <- factor(kmeans_geo$cluster)
fviz_cluster(kmeans_geo, data = geo_scaled,
geom = "point",
ellipse.type = "convex",
palette = c("#00AFBB", "#E7B800", "#FC4E07", "#7CAE00"),
ggtheme = theme_minimal()) +
labs(title = "Clusters geográficos de viviendas")
Ahora podemos comparar estos clusters contra la zona declarada, para identificar cuál corresponde a la zona norte real:
table(geo$zona, geo$cluster_geo)
##
## 1 2 3 4
## Zona Centro 2 98 16 8
## Zona Norte 160 1028 107 625
## Zona Oeste 17 125 1025 31
## Zona Oriente 52 83 17 199
## Zona Sur 3148 350 1014 214
Se observa que los registros declarados como “Zona Norte” se distribuyen principalmente entre los clusters 2 y 4 (1,028 y 625 registros respectivamente), mientras que “Zona Sur” se concentra fuertemente en el cluster 1 (3,148 registros). El cluster 3 agrupa mayoritariamente “Zona Oeste” y una cantidad importante de “Zona Sur”, lo que sugiere que corresponde a una zona occidental/suroccidental de la ciudad.
Llama la atención que la “Zona Norte” quedó dividida entre dos clusters distintos (2 y 4), lo que podría indicar que el norte real de la ciudad se subdivide geográficamente en dos grupos, o que uno de estos dos clusters corresponde en realidad a otra zona colindante mal codificada como norte.
Se debe confirmar entonces cuál cluster corresponde geográficamente al norte real, necesitamos ver la latitud promedio de cada cluster (a mayor latitud, más al norte):
geo %>%
group_by(cluster_geo) %>%
summarise(
lat_prom = mean(latitud),
lon_prom = mean(longitud),
n = n()
)
## # A tibble: 4 × 4
## cluster_geo lat_prom lon_prom n
## <fct> <dbl> <dbl> <int>
## 1 1 3.38 -76.5 3379
## 2 2 3.46 -76.5 1684
## 3 3 3.43 -76.5 2179
## 4 4 3.46 -76.5 1077
Cluster 1 (lat. prom. = 3.375): latitud más baja, corresponde entonces a la zona sur real de la ciudad — coincide con que este cluster concentraba la mayoría de los registros de “Zona Sur” en la tabla de contingencia.
Cluster 3 (lat. prom. = 3.428, lon. prom. = -76.547, la más occidental): corresponde a la zona oeste, coincidiendo con el predominio de “Zona Oeste” observado antes en el gráfico.
Clusters 2 y 4 (lat. prom. = 3.462 y 3.460 respectivamente, las más altas): corresponden a la zona norte real de la ciudad. La diferencia entre ambos está en la longitud (cluster 2 más occidental, cluster 4 más oriental), lo que sugiere que el norte se subdivide en noroccidente y nororiente, pero ambos son geográficamente norte.
La zona norte real se define como la unión de los clusters 2 y 4, pues ambos comparten la latitud más alta del datos.
Ahora, con esto se puede construir base1 con este criterio definitivo (tipo casa + clusters 2 y 4):
base1 <- geo %>%
filter(tipo == "Casa", cluster_geo %in% c(2, 4))
cat("Registros en base1:", nrow(base1), "\n")
## Registros en base1: 1199
head(base1, 3) %>% kable()
| id | zona | piso | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | barrio | longitud | latitud | cluster_geo |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1147 | Zona Oriente | NA | 3 | 250 | 70 | 1 | 3 | 6 | Casa | 20 de julio | -76.51168 | 3.43382 | 2 |
| 1169 | Zona Oriente | NA | 3 | 320 | 120 | 1 | 2 | 3 | Casa | 20 de julio | -76.51237 | 3.43369 | 2 |
| 1350 | Zona Oriente | NA | 3 | 350 | 220 | 2 | 2 | 4 | Casa | 20 de julio | -76.51537 | 3.43566 | 2 |
table(base1$zona)
##
## Zona Centro Zona Norte Zona Oeste Zona Oriente Zona Sur
## 83 613 27 238 238
conteo total y la tabla completa de zonas:
cat("Registros en base1:", nrow(base1), "\n")
## Registros en base1: 1199
table(base1$zona)
##
## Zona Centro Zona Norte Zona Oeste Zona Oriente Zona Sur
## 83 613 27 238 238
base1 quedó con 1,199 registros de tipo casa
ubicados geográficamente en el norte (clusters 2 y 4). Sin embargo, al
revisar la etiqueta zona original dentro de este filtro, se
observa una mezcla considerable:
Esto confirma que la variable zona original no es
confiable para segmentar el mercado y que el criterio geográfico
(clustering por latitud/longitud) es el método correcto para definir
base1, tal como se hizo. De aquí en adelante, el análisis
de la vivienda 1 se basa en este conjunto depurado de 1,199 casas del
norte real de la ciudad.
Para verificar visualmente la calidad del filtro geográfico aplicado,
se grafican los puntos de base1 en un mapa:
leaflet(base1) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(~longitud, ~latitud,
color = ~colorFactor("Set1", zona)(zona),
radius = 4, opacity = 0.8,
popup = ~paste("Zona declarada:", zona, "<br>Precio:", preciom, "M"))
Se puede observar que, a pesar de la mezcla de etiquetas en zona, los puntos de base1 se concentran en la parte superior (norte) de la ciudad, con muy pocos puntos dispersos hacia el sur. Esto valida que el criterio de depuración por clustering geográfico (latitud/longitud) funcionó para aislar la oferta de casas ubicadas realmente en el norte de Cali.
Se analiza la relación entre el precio de la vivienda y sus
principales características, utilizando la base depurada de casas en
zona norte (base1).
library(corrplot)
## Warning: package 'corrplot' was built under R version 4.5.3
## corrplot 0.95 loaded
vars_cor <- base1 %>%
select(preciom, areaconst, estrato, banios, habitaciones) %>%
na.omit()
matriz_cor <- cor(vars_cor)
round(matriz_cor, 2)
## preciom areaconst estrato banios habitaciones
## preciom 1.00 0.64 0.68 0.56 0.16
## areaconst 0.64 1.00 0.40 0.48 0.31
## estrato 0.68 0.40 1.00 0.43 -0.05
## banios 0.56 0.48 0.43 1.00 0.56
## habitaciones 0.16 0.31 -0.05 0.56 1.00
corrplot(matriz_cor, method = "color", type = "upper",
addCoef.col = "black", tl.col = "black", tl.srt = 45,
title = "Matriz de correlación - base1", mar = c(0,0,1,0))
El precio muestra correlaciones positivas moderadas a fuertes con la mayoría de las variables:
Estrato (0.68) y área construida (0.64) son las variables con mayor asociación con el precio, lo cual es lógico: viviendas más grandes y en estratos más altos tienden a costar más.
Baños (0.56) también muestra una correlación considerable, coherente con que más baños suele acompañar a viviendas de mayor tamaño y valor.
Habitaciones (0.16) presenta una correlación baja con el precio, lo que resulta menos intuitivo, pero puede explicarse porque el número de habitaciones varía poco dentro de este segmento (casas de estrato 4-6 en el norte), mientras que el área y el estrato capturan mejor las diferencias de valor.
Entre las variables predictoras, la correlación estrato y baños (0.43) y areaconst y baños (0.48) sugiere que hay algún tipo de multicolinealidad a tener en cuenta al construir el modelo de regresión. La correlación entre estrato y habitaciones es prácticamente nula (-0.05), indicando que son variables independientes de la vivienda.
areaconst y estrato aparecen como los predictores más relevantes del precio en este segmento, mientras que habitaciones parece no resultar significativa en el modelo de regresión múltiple.
Ahora, hacemos el mismo análisis pero con gráficos interactivos de plotly. El más importante es el precio vs. área construida, que está coloreado por estratos en el gráfico:
p1 <- ggplot(base1, aes(x = areaconst, y = preciom, color = factor(estrato),
text = paste("Barrio:", barrio, "<br>Precio:", preciom, "M<br>Área:", areaconst, "m²"))) +
geom_point(alpha = 0.6) +
theme_minimal() +
labs(title = "Precio vs. Área construida (por estrato)",
x = "Área construida (m²)", y = "Precio (millones)", color = "Estrato")
ggplotly(p1, tooltip = "text")
precio según estrato (boxplot) y según zona (dentro de base1, para ver si aún hay diferencias por la mezcla de etiquetas):
p2 <- ggplot(base1, aes(x = factor(estrato), y = preciom, fill = factor(estrato),
text = paste("Estrato:", estrato, "<br>Precio:", preciom, "M"))) +
geom_boxplot(alpha = 0.7) +
theme_minimal() +
labs(title = "Precio según estrato", x = "Estrato", y = "Precio (millones)") +
theme(legend.position = "none")
ggplotly(p2, tooltip = "text")
p3 <- ggplot(base1, aes(x = zona, y = preciom, fill = zona,
text = paste("Zona:", zona, "<br>Precio:", preciom, "M"))) +
geom_boxplot(alpha = 0.7) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
labs(title = "Precio según zona declarada (dentro de base1)", x = "Zona", y = "Precio (millones)")
ggplotly(p3, tooltip = "text")
-Precio según estrato: se observa una relación clara y creciente entre el estrato y el precio: la mediana pasa de aproximadamente 250M en estrato 3, a 400M en estrato 4, 500M en estrato 5, y salta a cerca de 900M en estrato 6. Con esto confirmamos que la correlación (0.68) observada anteriormente y coherente con el mercado inmobiliario, porque a mayor estrato socioeconómico, mayor valor de la propiedad. También se observan varios outliers superiores en los estratos 4 y 5, viviendas que se salen del rango típico de su estrato.
-Precio según zona declarada: dentro de base1 ya filtrada geográficamente al norte real, aún se observan diferencias notorias de precio entre las zonas declaradas: “Zona Oeste” muestra la mediana más alta 750M y mayor dispersión, mientras que “Zona Oriente” muestra la mediana más baja 150-200M. “Zona Norte”, “Zona Centro” y “Zona Sur”.
Se estima un modelo de regresión lineal múltiple para predecir el precio de la vivienda en función de sus características físicas: área construida, estrato, número de habitaciones, número de parqueaderos y número de baños.
modelo completo con todas las variables:
modelo_completo <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = base1)
summary(modelo_completo)
##
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos +
## banios, data = base1)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1013.46 -100.61 -14.08 63.48 926.50
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -331.18398 36.58800 -9.052 < 2e-16 ***
## areaconst 0.62293 0.04466 13.949 < 2e-16 ***
## estrato 106.72620 8.22157 12.981 < 2e-16 ***
## habitaciones -11.77561 4.71054 -2.500 0.0126 *
## parqueaderos 44.75137 5.66357 7.902 9.54e-15 ***
## banios 42.16572 6.74871 6.248 6.89e-10 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 187.7 on 768 degrees of freedom
## (425 observations deleted due to missingness)
## Multiple R-squared: 0.6526, Adjusted R-squared: 0.6504
## F-statistic: 288.6 on 5 and 768 DF, p-value: < 2.2e-16
Significancia: todas las variables resultaron
estadísticamente significativas (p < 0.05), incluyendo
habitaciones (p = 0.0126). No se requiere eliminar ninguna
del modelo por criterio de significancia. Se conserva entonces el modelo
completo como el modelo final para la Vivienda 1.
Los coeficientes de areaconst, estrato, parqueaderos y baños son positivos y lógicos, porque ante viviendas más grandes, en estratos más altos, con más parqueaderos y más baños tienden a costar más, lo cual es coherente con el comportamiento esperado del mercado.
Coeficiente de determinación (R²): el modelo explica el 65.26% de la variabilidad del precio, lo cual es un ajuste razonable para un modelo de solo 5 predictores en un mercado, donde factores no observados como la ubicación exacta dentro del barrio, acabados, antigüedad, vista, cercanía a vías principales y demás, también influyen en el precio y no están capturados aquí.
Se realiza el diagnóstico de los supuestos clásicos de la regresión lineal: linealidad, normalidad de los residuos, homocedasticidad y ausencia de multicolinealidad.
plot(modelo_completo, which = 1) # Residuals vs Fitted
-Residuals vs Fitted (linealidad y homocedasticidad):
El gráfico muestra que los residuos no se distribuyen de forma aleatoria y constante alrededor de cero, en realidad se observa un patrón en forma de embudo, donde la dispersión de los residuos es menor para valores ajustados bajos y aumenta considerablemente para valores ajustados altos (viviendas de mayor precio predicho). Esto es un indicio de heterocedasticidad.
Adicionalmente, la línea roja (suavizado) muestra una ligera curvatura en lugar de ser perfectamente horizontal, lo que sugiere que podría existir una relación no lineal entre los predictores y el precio, especialmente en el rango de valores altos.
plot(modelo_completo, which = 2)
- Q-Q Plot (normalidad de los residuos):
Los residuos estandarizados se ajustan razonablemente bien a la línea teórica de normalidad en la parte central de la distribución. Sin embargo, se observan desviaciones en ambas colas. En la cola izquierda residuos muy negativos y en la cola derecha residuos muy positivos, los puntos se alejan considerablemente de la línea diagonal. Esto indica que los residuos no siguen una distribución completamente normal, curtosis alta es decir, hay más valores extremos.
plot(modelo_completo, which = 3)
- Scale-Location (homocedasticidad):
Este gráfico confirma lo observado en el Residuals vs Fitted, la línea roja muestra una tendencia claramente creciente, en lugar de ser horizontal. Esto significa que la magnitud de los residuos (raíz cuadrada de los residuos estandarizados) aumenta a medida que aumenta el valor ajustado del precio, confirmando que hay heterocedasticidad. Lo que indica que el modelo predice con menor precisión (mayor dispersión de errores) las viviendas de precio alto que las de precio bajo, lo cual es un patrón común en modelos de precios inmobiliarios cuando no se transforma la variable respuesta.
Con esto se termina el diagnóstico visual. Ahora es necesario respaldar todo con pruebas formales como Breusch-Pagan (homocedasticidad), Shapiro-Wilk (normalidad) y VIF (multicolinealidad):
library(lmtest)
## Warning: package 'lmtest' was built under R version 4.5.3
## Cargando paquete requerido: zoo
## Warning: package 'zoo' was built under R version 4.5.3
##
## Adjuntando el paquete: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
library(car)
## Warning: package 'car' was built under R version 4.5.3
## Cargando paquete requerido: carData
## Warning: package 'carData' was built under R version 4.5.3
##
## Adjuntando el paquete: 'car'
## The following object is masked from 'package:dplyr':
##
## recode
## The following object is masked from 'package:boot':
##
## logit
# Homocedasticidad
bptest(modelo_completo)
##
## studentized Breusch-Pagan test
##
## data: modelo_completo
## BP = 153.46, df = 5, p-value < 2.2e-16
# Normalidad de residuos
shapiro.test(residuals(modelo_completo))
##
## Shapiro-Wilk normality test
##
## data: residuals(modelo_completo)
## W = 0.87621, p-value < 2.2e-16
# Multicolinealidad
vif(modelo_completo)
## areaconst estrato habitaciones parqueaderos banios
## 1.420439 1.666202 1.670454 1.292048 2.157528
- Pruebas formales de los supuestos:
Homocedasticidad (Breusch-Pagan): BP = 153.46, p-value < 2.2e-16. Como el p-value es menor a 0.05, se rechaza la hipótesis nula de homocedasticidad, confirmando formalmente lo observado en los gráficos de Residuals vs Fitted y Scale-Location, existe heterocedasticidad en los residuos del modelo.
Normalidad (Shapiro-Wilk): W = 0.876, p-value < 2.2e-16. Con un p-value menor a 0.05, se rechaza la hipótesis nula de normalidad, confirmando lo observado en el Q-Q Plot, los residuos no siguen una distribución normal.
Multicolinealidad (VIF): todos los valores están entre 1.29 y 2.16, muy por debajo del umbral de preocupación (VIF > 5), por lo que no hay evidencia de multicolinealidad entre los predictores.
En este caso el modelo cumple el supuesto de ausencia de multicolinealidad, pero no cumple los supuestos de homocedasticidad ni de normalidad de los residuos. Esto significa que, si bien los coeficientes estimados siguen siendo insesgados, los errores estándar y las pruebas de significancia (valores p) podrían no ser totalment confiables, y los intervalos de confianza de las predicciones podrían estar subestimados o sobreestimados, especialmente para viviendas de precio alto.
vivienda1 <- data.frame(
areaconst = 200,
estrato = 4,
habitaciones = 4,
parqueaderos = 1,
banios = 2
)
pred1 <- predict(modelo_completo, newdata = vivienda1, interval = "prediction", level = 0.95)
pred1
## fit lwr upr
## 1 302.288 -66.88957 671.4655
vivienda1_e5 <- vivienda1
vivienda1_e5$estrato <- 5
pred1_e5 <- predict(modelo_completo, newdata = vivienda1_e5, interval = "prediction", level = 0.95)
pred1_e5
## fit lwr upr
## 1 409.0142 39.20178 778.8265
Para una casa de 200 m² de área construida, 1 parqueadero, 2 baños y 4 habitaciones, el modelo predice:
El precio estimado aumenta al pasar de estrato 4 a estrato 5. El precio puntual para estrato 4 (302.3M) se ajusta dentro del crédito preaprobado de 350 millones, mientras que para estrato 5 (409.0M) lo supera. Dentro del presupuesto de 350 millones, es más viable encontrar una vivienda con las características solicitadas en estrato 4 que en estrato 5.
ofertas_v1 <- base1 %>%
filter(preciom <= 350,
estrato %in% c(4, 5)) %>%
arrange(desc(preciom))
cat("Número de ofertas potenciales encontradas:", nrow(ofertas_v1), "\n")
## Número de ofertas potenciales encontradas: 148
head(ofertas_v1, 10) %>%
select(barrio, zona, estrato, preciom, areaconst, banios, habitaciones, parqueaderos, latitud, longitud) %>%
kable()
| barrio | zona | estrato | preciom | areaconst | banios | habitaciones | parqueaderos | latitud | longitud |
|---|---|---|---|---|---|---|---|---|---|
| alfonso lópez | Zona Oriente | 4 | 350 | 126 | 3 | 3 | NA | 3.45853 | -76.48383 |
| chipichape | Zona Norte | 4 | 350 | 98 | 3 | 4 | 2 | 3.48154 | -76.52840 |
| colseguros | Zona Sur | 4 | 350 | 214 | 3 | 5 | 1 | 3.43086 | -76.53057 |
| colseguros | Zona Sur | 4 | 350 | 247 | 2 | 4 | 1 | 3.44987 | -76.53464 |
| el bosque | Zona Norte | 5 | 350 | 200 | 3 | 4 | 3 | 3.48503 | -76.53010 |
| el bosque | Zona Norte | 5 | 350 | 300 | 5 | 6 | 3 | 3.48577 | -76.53010 |
| el bosque | Zona Norte | 5 | 350 | 240 | 3 | 6 | 2 | 3.48635 | -76.53136 |
| el bosque | Zona Norte | 5 | 350 | 203 | 2 | 5 | 2 | 3.48531 | -76.51448 |
| la flora | Zona Norte | 5 | 350 | 264 | 3 | 4 | 2 | 3.46412 | -76.50330 |
| la flora | Zona Norte | 5 | 350 | 110 | 4 | 3 | 1 | 3.48157 | -76.52353 |
Se identificaron 148 ofertas dentro de
base1 que cumplen simultáneamente con el presupuesto de 350
millones y el estrato solicitado (4 o 5). Esto representa una
disponibilidad amplia (aproximadamente el 12% del total de
base1), lo que indica que la empresa tiene un número
considerable de opciones reales dentro de su presupuesto. Las diez
ofertas más cercanas al tope del presupuesto se presentan en la tabla
anterior.
Todas las ofertas mostradas alcanzan el límite máximo de 350 millones, lo que indica que el presupuesto asignado permite acceder al segmento superior de precios disponible dentro de estos criterios. Se destaca especialmente la oferta en El Bosque (estrato 5, 200 m², 3 baños, 4 habitaciones, 3 parqueaderos, $350M), que coincide casi exactamente con el área construida solicitada (200 m²) y el número de habitaciones (4), superando ligeramente lo pedido en baños y parqueaderos — una característica favorable más que una limitación.
Se observa también que la mayoría de las ofertas dentro del
presupuesto se concentran en los barrios El Bosque,
La Flora y Chipichape, mientras que
Colseguros aparece asociado a “Zona Sur” en la etiqueta
declarada, otro caso de la mezcla de codificación ya documentada (aunque
geográficamente pertenece al norte real, según el filtro de
base1).
Cabe mencionar que un registro (Alfonso López) presenta un valor
faltante (NA) en parqueaderos, por lo que no
se puede confirmar si cumple con el requisito mínimo de parqueadero
solicitado.
También, se encuentran 6 registros con el nombre de Valle del Lili, Ciudad 2000 y Colseguros barrios pertenecientes al sur de la ciudad, pero que en este conjunto de datos están localizados en el oriente, pero segun la longitud y latitud hacen parte de la zona norte. Por lo tanto se retiran antes de realizar el mapa:
# Excluir barrios adicionales mal ubicados dentro de ofertas_v1
ofertas_v1 <- ofertas_v1 %>%
filter(!barrio %in% c("el cedro", "valle del lili", "santa isabel", "colseguros andes", "ciudad 2000"))
cat("Registros en ofertas_v1 después de excluir barrios atípicos:", nrow(ofertas_v1), "\n")
## Registros en ofertas_v1 después de excluir barrios atípicos: 126
Ahora construyamos el mapa con estas ofertas potenciales, como pide el enunciado (al menos 5 ofertas en un mapa):
leaflet(ofertas_v1) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(~longitud, ~latitud,
radius = 6, color = "darkgreen", fillOpacity = 0.8,
popup = ~paste0("<b>", barrio, "</b><br>",
"Precio: ", preciom, "M<br>",
"Área: ", areaconst, " m²<br>",
"Estrato: ", estrato, "<br>",
"Baños: ", banios, " | Hab: ", habitaciones, " | Parq: ", parqueaderos))
Las 126 ofertas potenciales se concentran principalmente en la parte superior del mapa (extremo norte), con una densidad especialmente alta alrededor de Montebello, y una segunda concentración notable más al sur del norte (zona intermedia, cerca del centro de la ciudad). Esto confirma que la oferta de casas de estrato 4-5 dentro del presupuesto de 350 millones está distribuida en un corredor relativamente amplio dentro del norte real de la ciudad, dándole a la empresa múltiples opciones de ubicación dentro de esta zona, y no solo un sector puntual.
Con base en el modelo de regresión y la búsqueda directa sobre
base1, se recomienda a la empresa considerar principalmente
las ofertas de estrato 4 (más holgadas dentro del presupuesto según la
predicción del modelo) y priorizar los barrios con mayor concentración
de oferta identificados en el mapa, donde existe mayor probabilidad de
encontrar una vivienda que además de ajustarse al precio, cumpla con las
características físicas solicitadas (200 m², 1 parqueadero, 2 baños, 4
habitaciones).
Ahora replicamos exactamente el mismo flujo para la segunda solicitud: apartamento, zona Sur, estrato 5 o 6, 300 m², 3 parqueaderos, 3 baños, 5 habitaciones, crédito preaprobado de 850 millones.
Empecemos por identificar el cluster geográfico que corresponde a la zona sur real (ya lo vimos en la tabla de latitudes promedio):
base2 <- geo %>%
filter(tipo == "Apartamento", cluster_geo == 1)
cat("Registros en base2:", nrow(base2), "\n")
## Registros en base2: 2041
head(base2, 3) %>% kable()
| id | zona | piso | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | barrio | longitud | latitud | cluster_geo |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1724 | Zona Norte | 01 | 5 | 240 | 87 | 1 | 3 | 3 | Apartamento | acopi | -76.51700 | 3.36971 | 1 |
| 4386 | Zona Norte | 01 | 5 | 310 | 137 | 2 | 3 | 4 | Apartamento | acopi | -76.53105 | 3.38296 | 1 |
| 6857 | Zona Norte | 03 | 3 | 100 | 49 | NA | 1 | 2 | Apartamento | acopi | -76.54531 | 3.37775 | 1 |
table(base2$zona)
##
## Zona Norte Zona Oeste Zona Oriente Zona Sur
## 89 15 10 1927
Se aplicó el mismo criterio de depuración geográfica utilizado para
base1, filtrando ahora apartamentos ubicados en el cluster
correspondiente a la zona sur real (cluster 1, latitud promedio =
3.375).
base2 quedó constituida y se observa una consistencia mucho mayor que
en base1, están correctamente etiquetados como “Zona Sur”,
solo 114 registros (5.6%) corresponden a otras zonas mal codificadas (89
“Zona Norte”, 15 “Zona Oeste”, 10 “Zona Oriente”).
Ahora el mapa de verificación de base2
leaflet(base2) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(~longitud, ~latitud,
color = ~colorFactor("Set1", zona)(zona),
radius = 3, opacity = 0.7,
popup = ~paste("Zona declarada:", zona, "<br>Precio:", preciom, "M"))
El mapa confirma que la gran mayoría de los puntos de
base2 (color morado, “Zona Sur”) se concentran de manera
clara y consistente en la parte inferior (sur) de la ciudad,
extendiéndose desde el centro hasta el límite sur de la mancha urbana.
Solo un número reducido de puntos de otros colores (mal etiquetados)
aparece disperso hacia la parte superior del mapa, cerca de El Poblado
Campestre y Navarro.
Sigamos con el análisis exploratorio
vars_cor2 <- base2 %>%
select(preciom, areaconst, estrato, banios, habitaciones) %>%
na.omit()
matriz_cor2 <- cor(vars_cor2)
round(matriz_cor2, 2)
## preciom areaconst estrato banios habitaciones
## preciom 1.00 0.80 0.68 0.75 0.34
## areaconst 0.80 1.00 0.53 0.68 0.41
## estrato 0.68 0.53 1.00 0.62 0.25
## banios 0.75 0.68 0.62 1.00 0.49
## habitaciones 0.34 0.41 0.25 0.49 1.00
corrplot(matriz_cor2, method = "color", type = "upper",
addCoef.col = "black", tl.col = "black", tl.srt = 45,
title = "Matriz de correlación - base2", mar = c(0,0,1,0))
base1 (0.56).base1.base1 (0.16), aunque sigue siendo la variable
con menor asociación.Entre los predictores, se observa una correlación alta entre
areaconst y banios (0.68) y entre estrato y
banios (0.62), lo que sugiere revisar la multicolinealidad en
el modelo de regresión con mayor atención que en el caso de
base1.
Ahora el gráfico interactivo con plotly (precio vs. área, coloreado por estrato)
p1_v2 <- ggplot(base2, aes(x = areaconst, y = preciom, color = factor(estrato),
text = paste("Barrio:", barrio, "<br>Precio:", preciom, "M<br>Área:", areaconst, "m²"))) +
geom_point(alpha = 0.6) +
theme_minimal() +
labs(title = "Precio vs. Área construida (por estrato) - base2",
x = "Área construida (m²)", y = "Precio (millones)", color = "Estrato")
ggplotly(p1_v2, tooltip = "text")
El gráfico confirma la correlación fuerte (0.80) observada en la
matriz: se aprecia una relación positiva y bastante definida entre área
construida y precio, más clara y consistente que la observada en
base1. El estrato 6 se ubica en la parte superior del
gráfico, mayores precios para una misma área, mientras que los puntos
rosados estrato 3, se concentran en la parte inferior, con precios bajos
incluso para áreas moderadas.
Existen un par de valores atípicos en el eje horizontal — apartamentos de estrato 5 con áreas por encima de 500 m² e incluso cercanos a 900 m² — que muestran precios relativamente bajos (300-650M) para su tamaño, lo cual podría distorsionar el ajuste del modelo de regresión y merece atención en la validación de supuestos.
boxplots de precio según estrato y según zona
p2_v2 <- ggplot(base2, aes(x = factor(estrato), y = preciom, fill = factor(estrato),
text = paste("Estrato:", estrato, "<br>Precio:", preciom, "M"))) +
geom_boxplot(alpha = 0.7) +
theme_minimal() +
labs(title = "Precio según estrato - base2", x = "Estrato", y = "Precio (millones)") +
theme(legend.position = "none")
ggplotly(p2_v2, tooltip = "text")
Se confirma una relación clara y creciente entre estrato y precio en
el segmento de apartamentos del sur, en este caso, la mediana pasa de
aproximadamente 140M en estrato 3, a 200M en estrato 4, 280M en estrato
5, y sube considerablemente a cerca de 570M en estrato 6. El salto entre
estrato 5 y 6 es particularmente marcado, más pronunciado que el
observado en base1 para casas del norte.
Ahora bien, el estrato 6 presenta además la mayor dispersión y numerosos valores atípicos superiores varios apartamentos por encima de 1,500 millones, reflejando la heterogeneidad del segmento de lujo dentro de esta categoría. Los estratos 3 y 4 muestran distribuciones mucho más compactas y homogéneas.
Igual que en base1, el estrato es un diferenciador claro
y consistente del precio, reforzando su relevancia como predictor en el
modelo de regresión. El comportamiento es coherente con la lógica de
mercado.
boxplot de precio segpun la zona
p3_v2 <- ggplot(base2, aes(x = zona, y = preciom, fill = zona,
text = paste("Zona:", zona, "<br>Precio:", preciom, "M"))) +
geom_boxplot(alpha = 0.7) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
labs(title = "Precio según zona declarada (dentro de base2)", x = "Zona", y = "Precio (millones)")
ggplotly(p3_v2, tooltip = "text")
Dentro de base2, la mayoría de los registros
corresponden a “Zona Sur” (mediana ~250M, con abundantes outliers
superiores hasta 1,750M reflejando la heterogeneidad del segmento de
lujo), consistente con el alto grado de codificación correcta observado
antes (94.4%). “Zona Oeste”, aunque con muy pocos registros dentro de
este filtro, muestra la mediana más alta (~530M) y mayor dispersión.
“Zona Norte” y “Zona Oriente” (las etiquetas minoritarias mal
codificadas dentro de base2) presentan medianas más bajas y
menor dispersión.
Igual que en base1, persisten diferencias de precio
según la etiqueta zona declarada, aun cuando
geográficamente todos estos registros pertenecen al sur real de la
ciudad. Por tal motivo, se mantendrá el modelo con los criterios del
modelo construido para la Vivienda 1, zona tampoco se
incluirá como predictor en el modelo de regresión de la Vivienda 2.
Se estima un modelo de regresión lineal múltiple para predecir el precio de los apartamentos en base2:
modelo_completo2 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = base2)
summary(modelo_completo2)
##
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos +
## banios, data = base2)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1311.93 -41.66 -2.27 38.69 864.57
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -252.38732 19.23625 -13.120 < 2e-16 ***
## areaconst 1.54318 0.06648 23.211 < 2e-16 ***
## estrato 52.82076 3.79118 13.933 < 2e-16 ***
## habitaciones -25.57012 4.91112 -5.207 2.16e-07 ***
## parqueaderos 79.90876 5.00677 15.960 < 2e-16 ***
## banios 52.58781 4.10943 12.797 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 100.2 on 1696 degrees of freedom
## (339 observations deleted due to missingness)
## Multiple R-squared: 0.7835, Adjusted R-squared: 0.7829
## F-statistic: 1228 on 5 and 1696 DF, p-value: < 2.2e-16
Significancia: todas las variables resultaron
altamente significativas (p < 0.001 en todos los casos), incluso con
mayor fuerza estadística que en el modelo de base1. Al
igual que para la base 1 las cinco variables resultaron estadísticamente
significativas (p < 0.001), se conserva el modelo completo como
modelo final para la Vivienda 2, sin necesidad de eliminar
predictores.
Al igual que en la Vivienda 1, los coeficientes de
areaconst, estrato, parqueaderos
y banios son positivos y coherentes con la lógica del
mercado. Es interesante notar que el efecto marginal del área construida
es más del doble en base2 (1.54 vs. 0.62 millones por m²)
que en base1, lo cual es consistente con que los
apartamentos de zona sur tienden a estar en estratos más altos y en un
mercado de mayor valor por metro cuadrado. De forma similar,
parqueaderos tiene un efecto más fuerte (79.9 vs. 44.8
millones), razonable en edificios de apartamentos donde el parqueadero
es un atributo más escaso y valorado que en casas.
Coeficiente de determinación (R²): el modelo explica
el 78.35% de la variabilidad del precio (R² ajustado =
78.29%), un ajuste notablemente mejor que el obtenido
en base1 (65.26%). Esto confirma que el precio de los
apartamentos del sur está mejor explicado por sus características
físicas que el de las casas del norte, posiblemente porque el mercado de
apartamentos es más homogéneo, son edificios con especificaciones más
estandarizadas que el de casas,
validación de supuestos para este modelo
plot(modelo_completo2, which = 1)
Al igual que en el modelo de base1, se observa un patrón
de embudo la dispersión de los residuos es pequeña para valores
ajustados bajos y aumenta considerablemente para valores ajustados
altos. La línea roja (suavizado) se mantiene relativamente plana en la
parte central, pero se desvía ligeramente al alza en el extremo
derecho.
Q-Q Plot
plot(modelo_completo2, which = 2)
Los residuos se ajustan razonablemente bien a la línea teórica en la
parte central de la distribución, pero se observan desviaciones marcadas
en ambas colas, más pronunciadas que en el modelo de
base1.
Los residuos no siguen una distribución normal, con colas considerablemente más pesadas que en el modelo de la Vivienda 1. La observación 1708 en particular se comporta como un outlier, que debería revisarse manualmente porque podría tratarse de un error de digitación en el precio o en alguna de las variables predictoras, dado lo extremo de su desviación.
Scale-Location
plot(modelo_completo2, which = 3)
La línea roja (suavizado) muestra una tendencia
creciente, similar a la de base1, confirmando
heterocedasticidad. La dispersión es notablemente mayor para valores
ajustados por encima de 500-700 millones, mientras que se mantiene
compacta y estable en el rango de precios bajos.
La observación 1708 vuelve a destacarse como un punto claramente atípico, con la mayor magnitud de residuo estandarizado de todo el conjunto de datos.
pruebas formales (Breusch-Pagan, Shapiro-Wilk, VIF)
bptest(modelo_completo2)
##
## studentized Breusch-Pagan test
##
## data: modelo_completo2
## BP = 714.13, df = 5, p-value < 2.2e-16
shapiro.test(residuals(modelo_completo2))
##
## Shapiro-Wilk normality test
##
## data: residuals(modelo_completo2)
## W = 0.75012, p-value < 2.2e-16
vif(modelo_completo2)
## areaconst estrato habitaciones parqueaderos banios
## 2.230000 1.687558 1.386554 1.983032 2.645342
Homocedasticidad (Breusch-Pagan): BP = 714.13,
p-value < 2.2e-16. Se rechaza la hipótesis nula de homocedasticidad,
confirmando la heterocedasticidad observada gráficamente. El estadístico
BP es considerablemente mayor que el de base1 (153.46), lo
que indica que este problema es más severo en el modelo de la Vivienda
2.
Normalidad (Shapiro-Wilk): W = 0.750, p-value
< 2.2e-16. Se rechaza la hipótesis nula de normalidad. El valor de W
es notablemente más bajo que el de base1 (0.876),
reflejando una desviación de la normalidad más pronunciada, consistente
con el outlier extremo (observación 1708) identificado en los gráficos
de diagnóstico.
Multicolinealidad (VIF): todos los valores están
entre 1.39 y 2.65, por debajo del umbral de preocupación (VIF > 5).
banios (2.65) y areaconst (2.23) presentan los
valores más altos, mayores que en base1, pero aún dentro de
un rango aceptable. No hay evidencia de multicolinealidad
problemática.
Predicción del precio, usando las características solicitadas: apartamento, 300 m², 3 parqueaderos, 3 baños, 5 habitaciones, estrato 5 o 6.
vivienda2_e5 <- data.frame(
areaconst = 300,
estrato = 5,
habitaciones = 5,
parqueaderos = 3,
banios = 3
)
pred2_e5 <- predict(modelo_completo2, newdata = vivienda2_e5, interval = "prediction", level = 0.95)
pred2_e5
## fit lwr upr
## 1 744.3105 545.682 942.939
vivienda2_e6 <- vivienda2_e5
vivienda2_e6$estrato <- 6
pred2_e6 <- predict(modelo_completo2, newdata = vivienda2_e6, interval = "prediction", level = 0.95)
pred2_e6
## fit lwr upr
## 1 797.1313 598.4904 995.7722
Para la misma vivienda pero en estrato 5, el modelo predice un precio de 744.3 millones de pesos, con un intervalo de predicción del 95% entre 545.7 y 942.9 millones.
Para un apartamento de 300 m², 3 parqueaderos, 3 baños y 5 habitaciones, en estrato 6, el modelo predice un precio de 797.1 millones de pesos, con un intervalo de predicción del 95% entre 598.5 y 995.8 millones.
Interpretación: como es de esperarse (coeficiente
positivo de estrato, +52.82 millones por unidad), el precio
estimado para estrato 5 (744.3M) es menor que para estrato 6 (797.1M),
una diferencia de aproximadamente 52.8 millones, consistente con el
coeficiente del modelo.
Ambos precios puntuales estimados (744.3M para estrato 5 y 797.1M para estrato 6) se ajustan dentro del crédito preaprobado de 850 millones, dejando un margen de 105.7M y 52.9M respectivamente. Esto indica que, a diferencia de la Vivienda 1 (donde el estrato 5 era la opción más viable dentro de presupuesto), en la Vivienda 2 ambos estratos solicitados (5 o 6) son alcanzables con el crédito disponible, aunque el estrato 6 deja un margen más ajustado.
Punto 6
ofertas_v2 <- base2 %>%
filter(preciom <= 850,
estrato %in% c(5, 6)) %>%
arrange(desc(preciom))
cat("Número de ofertas potenciales encontradas:", nrow(ofertas_v2), "\n")
## Número de ofertas potenciales encontradas: 1117
head(ofertas_v2, 10) %>%
select(barrio, zona, estrato, preciom, areaconst, banios, habitaciones, parqueaderos, latitud, longitud) %>%
kable()
| barrio | zona | estrato | preciom | areaconst | banios | habitaciones | parqueaderos | latitud | longitud |
|---|---|---|---|---|---|---|---|---|---|
| ciudad jardín | Zona Norte | 6 | 850 | 191.0 | 4 | 3 | 2 | 3.37100 | -76.54100 |
| ciudad jardín | Zona Sur | 6 | 850 | 187.0 | 5 | 4 | 3 | 3.36287 | -76.53887 |
| ciudad jardín | Zona Sur | 6 | 850 | 168.0 | 5 | 4 | 3 | 3.35114 | -76.54369 |
| ciudad jardín | Zona Sur | 6 | 850 | 192.0 | 5 | 3 | 2 | 3.36540 | -76.52846 |
| ciudad jardín | Zona Sur | 6 | 850 | 191.8 | 5 | 4 | 2 | 3.35961 | -76.53798 |
| ciudad jardín | Zona Sur | 6 | 850 | 186.0 | 5 | 4 | 3 | 3.35621 | -76.54309 |
| el peñon | Zona Oeste | 6 | 850 | 325.0 | 3 | 2 | 2 | 3.37500 | -76.54200 |
| pance | Zona Sur | 6 | 850 | 187.0 | 4 | 3 | 3 | 3.34782 | -76.53530 |
| pance | Zona Sur | 6 | 850 | 352.0 | 3 | 3 | 4 | 3.34265 | -76.53729 |
| pance | Zona Sur | 6 | 850 | 187.0 | 4 | 3 | 3 | 3.35064 | -76.54484 |
Se identificaron 1,117 ofertas dentro de
base2 que cumplen simultáneamente con el presupuesto de 850
millones y el estrato solicitado (5 o 6). Esta cifra representa una
disponibilidad muy amplia (más del 54% de base2),
sustancialmente mayor a la encontrada para la Vivienda 1 (148 ofertas,
~12% de base1), lo que indica que el presupuesto de 850
millones ofrece un margen de negociación considerablemente más holgado
dentro del mercado del sur.
Interpretación: todas las ofertas mostradas alcanzan el límite máximo de 850 millones, concentrándose principalmente en el barrio Ciudad Jardín (7 de las 10 mejores ofertas) y también en Pance y El Peñón. Se destaca la oferta en Ciudad Jardín (estrato 6, 191 m², 4 baños, 3 habitaciones, 2 parqueaderos, $850M), que se ajusta razonablemente al área solicitada (300 m² pedidos vs. 191 m² ofrecidos) aunque con menor tamaño que lo requerido; y la oferta en Pance (352 m², 3 baños, 3 habitaciones, 4 parqueaderos), que sí supera el área mínima solicitada y cumple con creces el requisito de parqueaderos.
Se observa nuevamente el problema de codificación de zona: la primera oferta aparece etiquetada como “Zona Norte” pese a estar geográficamente en el sur real (confirmado por su latitud de 3.371, la más baja de la tabla), reforzando la importancia de haber usado el criterio de clustering geográfico en lugar de la etiqueta original.
leaflet(ofertas_v2) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(~longitud, ~latitud,
radius = 5, color = "darkblue", fillOpacity = 0.6,
popup = ~paste0("<b>", barrio, "</b><br>",
"Precio: ", preciom, "M<br>",
"Área: ", areaconst, " m²<br>",
"Estrato: ", estrato, "<br>",
"Baños: ", banios, " | Hab: ", habitaciones, " | Parq: ", parqueaderos))
El mapa muestra las 1,117 ofertas potenciales identificadas para la Vivienda 2. A diferencia del mapa de la Vivienda 1, aquí se observa una cobertura mucho más amplia y densa, extendiéndose por prácticamente toda la extensión del sur real de la ciudad, desde el sector cercano a Buitrera (parte inferior izquierda) hasta el límite con Ciudad Jardín y El Poblado Campestre (parte superior derecha).
Se identifican dos núcleos de mayor concentración: uno en la parte
superior-centro del mapa y otro más disperso hacia el sector inferior.
No se observan puntos claramente fuera de la zona sur real, a diferencia
de lo ocurrido con base1 no fue necesario aplicar exclusiones manuales
adicionales sobre ofertas_v2.
Tanto para el tipo de vivienda 1 como la vivienda 2, los modelos violaron los supuestos de homocedasticidad y normalidad de los residuos (confirmado con las pruebas de Breusch-Pagan y Shapiro-Wilk), siendo este problema más marcado en el modelo de la Vivienda 2. Esto implica que los intervalos de predicción deben interpretarse con cautela, especialmente en el segmento de precios altos. Sin embargo, entendiendo y conociendo las zonas de la ciudad, se puede confirmar que los modelos están filtrando la informamción correctamente.
Ahora bien, para la vivienda 1 (Casa, Zona Norte, crédito 350 millones) se considera:
El modelo de regresión explicó el 65.3% de la
variabilidad del precio, con estrato y
areaconst como los predictores más influyentes.
La predicción puntual para las características solicitadas (200 m², 1 parqueadero, 2 baños, 4 habitaciones) resultó en 302 millones (estrato 4) y 409 millones (estrato 5). Solo el estrato 4 se ajusta cómodamente al presupuesto de 350 millones.
La búsqueda directa sobre base1 confirmó esta
conclusión: se encontraron 148 ofertas reales dentro
del presupuesto, concentradas principalmente en el sector de Montebello
y alrededores.
Para la base uno se debería enfocar la búsqueda hacia viviendas de estrato 4, donde el presupuesto ofrece mayor margen; el estrato 5 es alcanzable únicamente en casos puntuales con áreas más ajustadas.
En la Vivienda 2 (Apartamento, Zona Sur, crédito 850 millones) se obtiene:
El modelo de regresión tuvo un mejor ajuste que el de la Vivienda 1, explicando el 78.4% de la variabilidad del precio, lo que sugiere un mercado de apartamentos más homogéneo y predecible que el de casas.
La predicción puntual para las características solicitadas (300 m², 3 parqueaderos, 3 baños, 5 habitaciones) resultó en 744 millones (estrato 5) y 797 millones (estrato 6), ambos dentro del presupuesto de 850 millones.
La búsqueda directa sobre base2 mostró una
disponibilidad mucho más amplia: 1,117 ofertas dentro
de presupuesto, concentradas en Ciudad Jardín y Pance, aunque ninguna de
las mejores opciones alcanza simultáneamente las 5 habitaciones y los
300 m² exactos solicitados.
Para la base 2 presupuesto es suficiente para ambos estratos permitidos (5 o 6); se sugiere priorizar Ciudad Jardín y Pance, y estar dispuesta a flexibilizar el número de habitaciones si se prioriza el área y los parqueaderos solicitados.
Para finalizar, con base en el análisis, ambas solicitudes son realistas dentro de los presupuestos, para la Vivienda 1 tiene mayor probabilidad de éxito enfocándose en estrato 4, mientras que la Vivienda 2 cuenta con amplia disponibilidad en ambos estratos solicitados. Se sugiere presentar a la empresa las ofertas identificadas en los mapas de las secciones 6 y 10 como punto incial de la negociación.