Informe

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.

Carga de datos y descripción de la muestra

# 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      

Descripción de la muestra.

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.

Limpieza y análisis exploratorio (EDA)

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.

Preparación de variables para análisis multivariante

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.

Análisis de Componentes Principales (ACP)

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.

Segmentación mediante Análisis de Conglomerados

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.

Análisis de Correspondencia entre zona y estrato

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.

Visualizaciones geográficas

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.")
}

Conclusion

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.