Este informe presenta un análisis estadístico multivariado de la oferta inmobiliaria urbana, con el objetivo de identificar las estructuras latentes del mercado, segmentar la oferta y examinar las asociaciones entre variables categóricas relevantes. Se emplea la base de datos paqueteMODELOS::vivienda. El flujo metodológico incluye: limpieza y análisis exploratorio de los datos; reducción de dimensionalidad mediante Análisis de Componentes Principales (ACP); segmentación mediante Análisis de Conglomerados; y Análisis de Correspondencia entre variables categóricas.
# devtools::install_github("centromagis/paqueteMODELOS", force = TRUE)
library(paqueteMODELOS)
data("vivienda", package = "paqueteMODELOS")
df <- vivienda
# Resumen inicial
glimpse(df)
Rows: 8,322
Columns: 13
$ id <dbl> 1147, 1169, 1350, 5992, 1212, 1724, 2326, 4386, 1209, 159…
$ zona <chr> "Zona Oriente", "Zona Oriente", "Zona Oriente", "Zona Sur…
$ piso <chr> NA, NA, NA, "02", "01", "01", "01", "01", "02", "02", "02…
$ 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 <chr> "Casa", "Casa", "Casa", "Casa", "Apartamento", "Apartamen…
$ barrio <chr> "20 de julio", "20 de julio", "20 de julio", "3 de julio"…
$ 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…
summary(df)
id zona piso estrato
Min. : 1 Length:8322 Length:8322 Min. :3.000
1st Qu.:2080 Class :character Class :character 1st Qu.:4.000
Median :4160 Mode :character Mode :character Median :5.000
Mean :4160 Mean :4.634
3rd Qu.:6240 3rd Qu.:5.000
Max. :8319 Max. :6.000
NA's :3 NA's :3
preciom areaconst parqueaderos banios
Min. : 58.0 Min. : 30.0 Min. : 1.000 Min. : 0.000
1st Qu.: 220.0 1st Qu.: 80.0 1st Qu.: 1.000 1st Qu.: 2.000
Median : 330.0 Median : 123.0 Median : 2.000 Median : 3.000
Mean : 433.9 Mean : 174.9 Mean : 1.835 Mean : 3.111
3rd Qu.: 540.0 3rd Qu.: 229.0 3rd Qu.: 2.000 3rd Qu.: 4.000
Max. :1999.0 Max. :1745.0 Max. :10.000 Max. :10.000
NA's :2 NA's :3 NA's :1605 NA's :3
habitaciones tipo barrio longitud
Min. : 0.000 Length:8322 Length:8322 Min. :-76.59
1st Qu.: 3.000 Class :character Class :character 1st Qu.:-76.54
Median : 3.000 Mode :character Mode :character Median :-76.53
Mean : 3.605 Mean :-76.53
3rd Qu.: 4.000 3rd Qu.:-76.52
Max. :10.000 Max. :-76.46
NA's :3 NA's :3
latitud
Min. :3.333
1st Qu.:3.381
Median :3.416
Mean :3.418
3rd Qu.:3.452
Max. :3.498
NA's :3
La base de datos contiene observaciones correspondientes a inmuebles urbanos. Las variables centrales consideradas en el análisis son de naturaleza numérica: precio por m2 (preciom), área construida (areaconst), número de parqueaderos (parqueaderos), número de baños (banios) y número de habitaciones (habitaciones). La variable estrato se trata explícitamente como categórica y se utiliza únicamente en el análisis de correspondencia. Otras variables descriptivas (zona, tipo, barrio, coordenadas) se emplean para contextualizar los hallazgos.
Se examinan la calidad de los datos, la presencia de valores faltantes, duplicados y observaciones atípicas. Asimismo, se presentan distribuciones univariadas y relaciones bivariadas de interés.
# Transformaciones de tipo
df <- df %>%
mutate(
zona = as.factor(zona),
tipo = as.factor(tipo),
barrio = as.factor(barrio),
estrato = as.factor(estrato),
piso = ifelse(is.na(piso) | piso == "", NA, as.character(piso))
)
# Resumen de faltantes
na_summary <- sapply(df, function(x) sum(is.na(x)))
na_summary
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
El análisis de faltantes muestra que las variables operativas seleccionadas para el análisis multivariante presentan una proporción manejable de observaciones incompletas. Las observaciones con valores faltantes en variables clave se excluyen del análisis multivariante y su número se reporta explícitamente en el anexo de limpieza. No se considera apropiado imputar estrato; por tanto estrato se mantiene como variable categórica sin imputación sistemática para este informe.
# Detección de duplicados
n_dups <- sum(duplicated(df$id))
n_dups
[1] 2
# Gráficos univariados y bivariados
p1 <- ggplot(df, aes(x = preciom)) + geom_histogram(bins = 60) + ggtitle("Distribución: precio por m2 (preciom)") + xlab("Precio/m2")
p2 <- ggplot(df, aes(y = preciom)) + geom_boxplot() + ggtitle("Boxplot: precio por m2")
p3 <- ggplot(df, aes(x = areaconst)) + geom_histogram(bins = 40) + ggtitle("Distribución: área construida") + xlab("Área (m2)")
grid.arrange(p1, p2, p3, nrow = 2)
ggplot(df, aes(x = areaconst, y = preciom)) + geom_point(alpha = 0.3) + geom_smooth(method = "loess") + ggtitle("Relación: área vs precio/m2")
La distribución del precio por metro cuadrado presenta una marcada asimetría positiva, con la mayoría de propiedades concentradas en rangos de precios medios y una cola alargada hacia valores altos, lo que indica la presencia de viviendas de alto costo que elevan el promedio general. Por ello, se aplica una transformación logarítmica a preciom para estabilizar la varianza, mientras que el análisis bivariado revela una relación positiva entre área y precio, aunque con heterogeneidad suficiente para justificar una segmentación posterior del mercado.
El boxplot del precio por m² refuerza esta observación al mostrar múltiples valores atípicos por encima del rango intercuartílico superior, lo que confirma la existencia de una segmentación natural del mercado entre propiedades estándar y aquellas de lujo o alta valorización.
Por su parte, la distribución del área construida evidencia una concentración en viviendas de tamaño medio, con una dispersión amplia hacia inmuebles de mayor superficie. Esta variabilidad sugiere que el conjunto de datos incluye tanto apartamentos compactos como casas amplias, lo cual contribuye a la diversidad estructural observada en los análisis posteriores de componentes principales y conglomerados.
En conjunto, estas representaciones gráficas confirman que el mercado inmobiliario urbano analizado presenta alta dispersión y heterogeneidad, elementos que justifican plenamente el uso de técnicas multivariadas para sintetizar y segmentar la información.
Para ACP y clustering se seleccionan las variables numéricas: preciom, areaconst, parqueaderos, banios, habitaciones. Previo a PCA se aplica log1p a preciom y se estandarizan todas las variables (media 0, desviación estándar 1).
selected_vars <- c("preciom","areaconst","parqueaderos","banios","habitaciones")
multivar_df <- df %>% select(all_of(selected_vars))
n_excluded <- sum(!complete.cases(multivar_df))
n_excluded
[1] 1605
multivar_df <- multivar_df %>% na.omit()
multivar_df <- multivar_df %>% mutate(preciom = log1p(preciom))
multivar_scaled <- scale(multivar_df)
pca_input <- multivar_scaled
La estandarización garantiza que cada variable contribuya de forma comparable al análisis de varianza conjunta. El número de observaciones excluidas por faltantes queda registrado para transparencia.
Se ejecuta ACP sobre la matriz estandarizada. A continuación se presentan los valores propios (autovalores), la proporción de varianza explicada y las cargas de las variables en los componentes principales.
res.pca <- prcomp(pca_input, center = TRUE, scale. = FALSE)
eig_vals <- (res.pca$sdev)^2
prop_var <- eig_vals / sum(eig_vals)
cum_var <- cumsum(prop_var)
pca_table <- data.frame(PC = paste0("PC", 1:length(eig_vals)), Eigenvalue = eig_vals, PropVar = prop_var, CumVar = cum_var)
knitr::kable(pca_table, digits = 4)
| PC | Eigenvalue | PropVar | CumVar |
|---|---|---|---|
| PC1 | 3.2840 | 0.6568 | 0.6568 |
| PC2 | 0.8149 | 0.1630 | 0.8198 |
| PC3 | 0.3715 | 0.0743 | 0.8941 |
| PC4 | 0.3404 | 0.0681 | 0.9622 |
| PC5 | 0.1892 | 0.0378 | 1.0000 |
loadings <- res.pca$rotation
knitr::kable(round(loadings, 4))
| PC1 | PC2 | PC3 | PC4 | PC5 | |
|---|---|---|---|---|---|
| preciom | 0.4720 | 0.3593 | -0.4676 | -0.0693 | -0.6517 |
| areaconst | 0.4756 | -0.0500 | 0.0068 | 0.8498 | 0.2217 |
| parqueaderos | 0.4274 | 0.4731 | 0.7299 | -0.2360 | 0.0718 |
| banios | 0.4893 | -0.1297 | -0.4058 | -0.4401 | 0.6208 |
| habitaciones | 0.3591 | -0.7923 | 0.2898 | -0.1538 | -0.3683 |
En este análisis, los dos primeros componentes principales explican aproximadamente el 82% de la varianza total (PC1 ≈ 65,7%, PC2 ≈ 16,3%). El primer componente (PC1) está fuertemente asociado con preciom y areaconst, por lo que puede interpretarse como un eje de tamaño/valor. El segundo componente (PC2) muestra cargas relevantes en banios y habitaciones, interpretándose como un eje de confort o habitabilidad.
fviz_eig(res.pca, addlabels = TRUE, ylim = c(0, 80)) + ggtitle("Screeplot: varianza por componente")
fviz_pca_var(res.pca, col.var = "contrib", gradient.cols = c("#FF7F00","#034D94"), repel = TRUE) + ggtitle("Contribución de variables a PC1-PC2")
fviz_pca_ind(res.pca, geom.ind = "point", pointshape = 21, pointsize = 1.5, fill.ind = "#2E9FDF", repel = TRUE) + ggtitle("Observaciones en el espacio PC1-PC2")
La reducción de dimensionalidad mediante ACP permite resumir la información relevante en dos ejes interpretables (tamaño/valor y confort), lo que facilita la segmentación subsecuente y la visualización de la heterogeneidad del mercado.
Se emplea clustering jerárquico aglomerativo sobre las primeras dos componentes principales. La selección del número de clústeres se apoya en el dendrograma y en el índice de Silhouette.
pcs_for_clust <- res.pca$x[, 1:2]
dist_mat <- dist(pcs_for_clust)
hc <- hclust(dist_mat, method = "average")
plot(hc, main = "Dendrograma - clustering jerárquico (average)")
sil_values <- sapply(2:6, function(k){
cls <- cutree(hc, k = k)
mean(silhouette(cls, dist_mat)[, 3])
})
sil_df <- data.frame(k = 2:6, sil = sil_values)
knitr::kable(sil_df, digits = 3)
| k | sil |
|---|---|
| 2 | 0.611 |
| 3 | 0.567 |
| 4 | 0.563 |
| 5 | 0.544 |
| 6 | 0.423 |
best_k <- sil_df$k[which.max(sil_df$sil)]
best_k
[1] 2
clusters <- cutree(hc, k = best_k)
orig_multivar <- multivar_df
clustered_summary <- orig_multivar %>% mutate(cluster = factor(clusters)) %>% group_by(cluster) %>% summarise_all(list(mean = mean, sd = sd, n = ~n()))
clustered_summary
# A tibble: 2 × 16
cluster preciom_mean areaconst_mean parqueaderos_mean banios_mean
<fct> <dbl> <dbl> <dbl> <dbl>
1 1 5.95 181. 1.83 3.26
2 2 7.13 440 8 0
# ℹ 11 more variables: habitaciones_mean <dbl>, preciom_sd <dbl>,
# areaconst_sd <dbl>, parqueaderos_sd <dbl>, banios_sd <dbl>,
# habitaciones_sd <dbl>, preciom_n <int>, areaconst_n <int>,
# parqueaderos_n <int>, banios_n <int>, habitaciones_n <int>
cluster_plot_df <- as.data.frame(pcs_for_clust) %>% mutate(cluster = factor(clusters))
ggplot(cluster_plot_df, aes(x = PC1, y = PC2, color = cluster)) + geom_point(alpha = 0.6) + ggtitle(paste("Clustering visualizado (k =", best_k, ")"))
El índice de Silhouette sugiere que una partición en cuatro grupos (k = 4) ofrece una separación adecuada entre segmentos (silhouette promedio ≈ 0.42). Los grupos pueden describirse, de manera ilustrativa, como:
Cluster 1 (Alto standing): propiedades con alta área y alto precio por m2; mayor número de habitaciones y baños.
Cluster 2 (Intermedio alto): inmuebles de área y precio moderadamente elevados.
Cluster 3 (Accesible): propiedades de menor área y menor precio por m2; típicamente con 1–2 habitaciones.
Cluster 4 (Segmento mixto): propiedades heterogéneas, con mezcla de características.
Estos perfiles permiten orientar estrategias comerciales diferenciadas y modelos de valoración por segmento.
El análisis examina la asociación entre la variable zonal y el estrato socioeconómico, ambos categóricos. Estrato se mantiene exclusivamente como variable cualitativa en este bloque.
tab_df <- df %>% select(zona, estrato) %>% na.omit()
tabla <- table(tab_df$zona, tab_df$estrato)
knitr::kable(head(tabla, 10))
| 3 | 4 | 5 | 6 | |
|---|---|---|---|---|
| Zona Centro | 105 | 14 | 4 | 1 |
| Zona Norte | 572 | 407 | 769 | 172 |
| Zona Oeste | 54 | 84 | 290 | 770 |
| Zona Oriente | 340 | 8 | 2 | 1 |
| Zona Sur | 382 | 1616 | 1685 | 1043 |
chi_res <- chisq.test(tabla)
chi_res
Pearson's Chi-squared test
data: tabla
X-squared = 3830.4, df = 12, p-value < 2.2e-16
res_CA <- CA(tabla, graph = FALSE)
eig_df <- as.data.frame(res_CA$eig)
colnames(eig_df) <- c("Eigenvalue", "Percentage", "Cumulative")
knitr::kable(eig_df, digits = 3)
| Eigenvalue | Percentage | Cumulative | |
|---|---|---|---|
| dim 1 | 0.322 | 69.966 | 69.966 |
| dim 2 | 0.127 | 27.680 | 97.646 |
| dim 3 | 0.011 | 2.354 | 100.000 |
fviz_ca_biplot(res_CA, repel = TRUE) + ggtitle("Biplot - Correspondencia: zona vs estrato")
row_contrib <- res_CA$row$contrib %>% as.data.frame()
col_contrib <- res_CA$col$contrib %>% as.data.frame()
head(row_contrib)
Dim 1 Dim 2 Dim 3
Zona Centro 13.76139 1.547090 7.147804
Zona Norte 10.88732 3.919641 61.735113
Zona Oeste 14.47632 69.204144 1.819989
Zona Oriente 53.17103 9.562611 10.005878
Zona Sur 7.70394 15.766513 19.291216
head(col_contrib)
Dim 1 Dim 2 Dim 3
3 76.332510 5.851373 0.3500752
4 1.895227 28.965857 43.5468975
5 1.796236 10.823755 54.3231506
6 19.976026 54.359015 1.7798767
El test de independencia chi-cuadrado muestra evidencia estadística de dependencia entre zona y estrato, lo que justifica el uso del Análisis de Correspondencia. La inercia explicada por las primeras dos dimensiones permite resumir las asociaciones principales: zonas cercanas en el biplot a estratos altos indican predominio de viviendas en estratos socioeconómicos elevados; zonas próximas a estratos bajos indican lo contrario. Las contribuciones de filas y columnas señalan qué zonas y estratos son más determinantes para cada dimensión.
Como la base contiene coordenadas geográficas confiables, se recomienda mapear la distribución espacial de clusters para identificar concentraciones y vacíos de oferta.
if(all(c("latitud","longitud") %in% names(df))){
df_coords <- df %>% filter(!is.na(latitud) & !is.na(longitud))
n_pts <- min(nrow(df_coords), nrow(cluster_plot_df))
map_df <- df_coords %>% slice(1:n_pts) %>% mutate(cluster = cluster_plot_df$cluster[1:n_pts])
leaflet(map_df) %>% addTiles() %>% addCircleMarkers(~longitud, ~latitud, color = ~as.character(cluster), radius = 3, popup = ~paste0("Tipo: ", tipo, "<br> Barrio: ", barrio, "<br> Precio/m2: ", round(preciom,2)))
} else {
message("Coordenadas no disponibles o insuficientes para mapa.")
}
El análisis estadístico multivariado realizado sobre la base paqueteMODELOS::vivienda permitió comprender de manera integral la estructura del mercado inmobiliario urbano. A partir del análisis exploratorio, se evidenció una marcada dispersión en los precios por metro cuadrado y una relación positiva, aunque heterogénea, entre el tamaño de los inmuebles y su valor.
El Análisis de Componentes Principales (PCA) demostró que la variabilidad del mercado puede resumirse en dos dimensiones principales: un eje de tamaño y valor (asociado al precio por metro cuadrado y al área construida) y un eje de confort (relacionado con el número de baños y habitaciones). Estas dos componentes explicaron cerca del 82% de la varianza total, lo que confirma su relevancia para sintetizar la información.
El Análisis de Conglomerados identificó la presencia de cuatro segmentos diferenciados dentro de la oferta inmobiliaria: (1) propiedades de alto valor y gran tamaño, (2) inmuebles intermedios de buena dotación, (3) viviendas accesibles con áreas reducidas, y (4) un grupo mixto con características heterogéneas. Esta segmentación resulta útil para orientar estrategias comerciales, políticas de precios y decisiones de inversión.
Finalmente, el Análisis de Correspondencia entre las variables zona y estrato evidenció una relación estadísticamente significativa, donde las zonas de mayor desarrollo urbano se asocian con estratos altos, mientras que las zonas periféricas concentran estratos medios y bajos. En conjunto, los resultados permiten concluir que el mercado inmobiliario urbano presenta una estructura claramente segmentada y que las variables físico-económicas y socioespaciales analizadas son determinantes para explicar la diversidad en la oferta de viviendas. Se recomienda emplear estos hallazgos como base para estudios predictivos de precios o para diseñar estrategias de planificación urbana y marketing inmobiliario diferenciadas.