El mercado inmobiliario urbano es uno de los sectores económicos más dinámicos y complejos, influenciado por una gran cantidad de variables que interactúan simultáneamente. El análisis de una sola variable a la vez resulta insuficiente para capturar la riqueza de información contenida en los datos; por ello, se recurre al análisis multivariado, que permite estudiar múltiples variables de forma conjunta.
El presente informe aplica tres técnicas de análisis multivariado sobre una base de datos de propiedades residenciales urbanas, con el fin de identificar patrones, segmentos y relaciones que permitan a la empresa inmobiliaria tomar decisiones estratégicas más informadas:
## Dimensiones de la base de datos: 8322 13
## Número de propiedades: 8322
## Número de variables: 13
## 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")=
## .. cols(
## .. id = col_double(),
## .. zona = col_character(),
## .. piso = col_character(),
## .. estrato = col_double(),
## .. preciom = col_double(),
## .. areaconst = col_double(),
## .. parqueaderos = col_double(),
## .. banios = col_double(),
## .. habitaciones = col_double(),
## .. tipo = col_character(),
## .. barrio = col_character(),
## .. longitud = col_double(),
## .. latitud = col_double()
## .. )
## - attr(*, "problems")=<externalptr>
La base de datos contiene información de 8322 propiedades con 13 variables, incluyendo características físicas de las viviendas (área, baños, habitaciones, parqueaderos), información socioeconómica (estrato) y geográfica (zona, barrio).
vivienda %>%
select(preciom, areaconst, parqueaderos, banios, habitaciones, estrato) %>%
summary() %>%
kable(caption = "Estadísticas descriptivas de las variables cuantitativas")| preciom | areaconst | parqueaderos | banios | habitaciones | estrato | |
|---|---|---|---|---|---|---|
| Min. : 58.0 | Min. : 30.0 | Min. : 1.000 | Min. : 0.000 | Min. : 0.000 | Min. :3.000 | |
| 1st Qu.: 220.0 | 1st Qu.: 80.0 | 1st Qu.: 1.000 | 1st Qu.: 2.000 | 1st Qu.: 3.000 | 1st Qu.:4.000 | |
| Median : 330.0 | Median : 123.0 | Median : 2.000 | Median : 3.000 | Median : 3.000 | Median :5.000 | |
| Mean : 433.9 | Mean : 174.9 | Mean : 1.835 | Mean : 3.111 | Mean : 3.605 | Mean :4.634 | |
| 3rd Qu.: 540.0 | 3rd Qu.: 229.0 | 3rd Qu.: 2.000 | 3rd Qu.: 4.000 | 3rd Qu.: 4.000 | 3rd Qu.:5.000 | |
| Max. :1999.0 | Max. :1745.0 | Max. :10.000 | Max. :10.000 | Max. :10.000 | Max. :6.000 | |
| NA’s :2 | NA’s :3 | NA’s :1605 | NA’s :3 | NA’s :3 | NA’s :3 |
## preciom id zona estrato areaconst banios habitaciones tipo barrio longitud
## 4808 1 1 1 1 1 1 1 1 1 1
## 1909 1 1 1 1 1 1 1 1 1 1
## 876 1 1 1 1 1 1 1 1 1 1
## 726 1 1 1 1 1 1 1 1 1 1
## 1 1 0 0 0 0 0 0 0 0 0
## 2 0 0 0 0 0 0 0 0 0 0
## 2 3 3 3 3 3 3 3 3 3
## latitud parqueaderos piso
## 4808 1 1 1 0
## 1909 1 1 0 1
## 876 1 0 1 1
## 726 1 0 0 2
## 1 0 0 0 12
## 2 0 0 0 13
## 3 1605 2638 4275
Se seleccionan las variables cuantitativas relevantes para los análisis de ACP y Cluster, eliminando los registros con datos faltantes:
# Variables cuantitativas seleccionadas
vars_num <- c("preciom", "areaconst", "parqueaderos", "banios", "habitaciones", "estrato")
# Dataset para análisis cuantitativo (sin NA)
vivienda_completa <- vivienda %>%
select(all_of(vars_num), zona, tipo) %>%
na.omit()
vivienda_num <- vivienda_completa %>% select(all_of(vars_num))
cat("Registros completos disponibles:", nrow(vivienda_num), "\n")## Registros completos disponibles: 6717
## Porcentaje de datos conservados: 80.7 %
El ACP permite transformar las 6 variables cuantitativas originales en un conjunto menor de componentes principales no correlacionados entre sí, que capturan la mayor varianza posible del conjunto de datos original. Esto facilita la visualización y comprensión de la estructura del mercado inmobiliario.
Dado que las variables tienen escalas muy diferentes (precio en millones, área en m², estrato de 1 a 6), es indispensable estandarizarlas antes de aplicar el ACP:
# Estandarización
vivienda_Z <- scale(vivienda_num)
# Cálculo de componentes principales
res.pca <- prcomp(vivienda_Z)
# Resumen de varianza explicada
summary(res.pca)## Importance of components:
## PC1 PC2 PC3 PC4 PC5 PC6
## Standard deviation 1.8665 1.1059 0.70675 0.59961 0.49435 0.43560
## Proportion of Variance 0.5806 0.2039 0.08325 0.05992 0.04073 0.03162
## Cumulative Proportion 0.5806 0.7845 0.86772 0.92765 0.96838 1.00000
fviz_eig(res.pca,
addlabels = TRUE,
ylim = c(0, 60),
barfill = "#034D94",
barcolor = "#034D94",
linecolor = "#FF7F00") +
ggtitle("Varianza explicada por cada componente principal") +
ylab("Porcentaje de varianza explicada (%)") +
xlab("Componente Principal") +
theme_minimal()var_exp <- data.frame(
Componente = paste0("CP", 1:6),
Varianza_individual = round(summary(res.pca)$importance[2,]*100, 2),
Varianza_acumulada = round(summary(res.pca)$importance[3,]*100, 2)
)
kable(var_exp, caption = "Varianza explicada por componente principal",
col.names = c("Componente", "Varianza individual (%)", "Varianza acumulada (%)"))| Componente | Varianza individual (%) | Varianza acumulada (%) | |
|---|---|---|---|
| PC1 | CP1 | 58.06 | 58.06 |
| PC2 | CP2 | 20.39 | 78.45 |
| PC3 | CP3 | 8.33 | 86.77 |
| PC4 | CP4 | 5.99 | 92.76 |
| PC5 | CP5 | 4.07 | 96.84 |
| PC6 | CP6 | 3.16 | 100.00 |
Interpretación: Los primeros dos componentes explican aproximadamente el 78.4% de la varianza total de los datos, lo que permite representar la mayor parte de la información en un plano bidimensional.
fviz_pca_var(res.pca,
col.var = "contrib",
gradient.cols = c("#FFC300", "#FF7F00", "#034D94"),
repel = TRUE,
title = "Contribución de variables a los componentes principales") +
theme_minimal()cargas <- as.data.frame(round(res.pca$rotation[, 1:3], 4))
cargas$Variable <- rownames(cargas)
cargas <- cargas %>% select(Variable, PC1, PC2, PC3)
kable(cargas, caption = "Cargas de las variables en los tres primeros componentes")| Variable | PC1 | PC2 | PC3 | |
|---|---|---|---|---|
| preciom | preciom | 0.4705 | 0.2395 | 0.1081 |
| areaconst | areaconst | 0.4503 | -0.2044 | 0.2595 |
| parqueaderos | parqueaderos | 0.4278 | 0.1513 | 0.6162 |
| banios | banios | 0.4653 | -0.1599 | -0.3696 |
| habitaciones | habitaciones | 0.2986 | -0.6676 | -0.3313 |
| estrato | estrato | 0.2954 | 0.6377 | -0.5431 |
Interpretación CP1: El primer componente captura principalmente la variación en las variables preciom (0.47), banios (0.47), areaconst (0.45) y parqueaderos (0.43), todas con cargas positivas y de magnitud similar. Representa una dimensión de “tamaño y capacidad global de la vivienda”: valores altos en CP1 corresponden a propiedades amplias, costosas, con muchos baños y parqueaderos.
Interpretación CP2: El segundo componente refleja principalmente la oposición entre estrato (+0.64) y habitaciones (−0.67). Representa una dimensión de “nivel socioeconómico frente a número de habitaciones”: valores positivos en CP2 corresponden a propiedades de estrato alto con pocas habitaciones (apartamentos de lujo), mientras que valores negativos corresponden a propiedades con más habitaciones pero de estrato más bajo.
fviz_pca_ind(res.pca,
col.ind = as.factor(vivienda_completa$estrato),
palette = c("#2ecc71","#3498db","#9b59b6","#e74c3c","#f39c12","#1abc9c"),
addEllipses = TRUE,
ellipse.type = "confidence",
legend.title = "Estrato",
repel = FALSE,
title = "Propiedades en el plano de componentes principales (por estrato)",
label = "none") +
theme_minimal()El análisis de conglomerados permite segmentar las propiedades en grupos que sean homogéneos internamente pero heterogéneos entre sí. Esto posibilita identificar segmentos diferenciados del mercado inmobiliario con características propias, facilitando estrategias focalizadas.
set.seed(1234)
fviz_nbclust(vivienda_Z, kmeans,
method = "wss",
k.max = 8) +
ggtitle("Método del codo: número óptimo de clusters") +
xlab("Número de clusters (k)") +
ylab("Suma de cuadrados intra-cluster (WSS)") +
theme_minimal()set.seed(1234)
fviz_nbclust(vivienda_Z, kmeans,
method = "silhouette",
k.max = 8) +
ggtitle("Índice de Silhouette: número óptimo de clusters") +
xlab("Número de clusters (k)") +
ylab("Silhouette promedio") +
theme_minimal()Decisión: El índice de Silhouette sugiere k = 2 como valor máximo (~0.42), mientras que el método del codo muestra una inflexión visible entre k = 2 y k = 3. Se elige k = 3 clusters para obtener una segmentación más granular del mercado, que permita identificar tres perfiles comerciales claramente diferenciados (popular, medio-alto y premium).
set.seed(1234)
k_optimo <- 3 # <-- Ajusta este valor según tus gráficos anteriores
km_result <- kmeans(vivienda_Z, centers = k_optimo, nstart = 25)
cat("Número de propiedades por cluster:\n")## Número de propiedades por cluster:
##
## 1 2 3
## 887 2332 3498
fviz_cluster(km_result,
data = vivienda_Z,
geom = "point",
ellipse.type = "norm",
palette = c("#034D94", "#FF7F00", "#2ecc71"),
ggtheme = theme_minimal(),
main = "Segmentación del mercado inmobiliario",
alpha = 0.5)vivienda_clusters <- vivienda_completa
vivienda_clusters$cluster <- as.factor(km_result$cluster)
vivienda_clusters %>%
group_by(cluster) %>%
summarise(
n_propiedades = n(),
precio_medio_MM = round(mean(preciom), 1),
area_media_m2 = round(mean(areaconst), 1),
parqueaderos_prom = round(mean(parqueaderos), 2),
banios_prom = round(mean(banios), 2),
habitaciones_prom = round(mean(habitaciones), 2),
estrato_prom = round(mean(estrato), 2)
) %>%
kable(caption = "Perfil promedio de cada cluster",
col.names = c("Cluster","N","Precio ($M)","Área (m²)",
"Parqueaderos","Baños","Habitaciones","Estrato"))| Cluster | N | Precio ($M) | Área (m²) | Parqueaderos | Baños | Habitaciones | Estrato |
|---|---|---|---|---|---|---|---|
| 1 | 887 | 1117.0 | 419.6 | 3.82 | 5.23 | 4.59 | 5.68 |
| 2 | 2332 | 533.8 | 215.6 | 1.98 | 3.98 | 4.24 | 5.15 |
| 3 | 3498 | 261.3 | 97.7 | 1.23 | 2.27 | 2.94 | 4.40 |
vivienda_clusters %>%
count(cluster, tipo) %>%
group_by(cluster) %>%
mutate(pct = round(n/sum(n)*100, 1)) %>%
ggplot(aes(x = cluster, y = pct, fill = tipo)) +
geom_col(position = "stack") +
geom_text(aes(label = paste0(pct, "%")),
position = position_stack(vjust = 0.5), color = "white", size = 3.5) +
scale_fill_manual(values = c("#034D94", "#FF7F00")) +
labs(title = "Distribución del tipo de vivienda por cluster",
x = "Cluster", y = "Porcentaje (%)", fill = "Tipo") +
theme_minimal()vivienda_clusters %>%
count(cluster, zona) %>%
group_by(cluster) %>%
mutate(pct = round(n/sum(n)*100, 1)) %>%
ggplot(aes(x = cluster, y = pct, fill = zona)) +
geom_col(position = "stack") +
labs(title = "Distribución de zona por cluster",
x = "Cluster", y = "Porcentaje (%)", fill = "Zona") +
theme_minimal()Interpretación de los clusters:
Cluster 1 — Segmento Premium (n = 887, ~13% de la oferta): Precio promedio de $1.117 millones y área de 419 m², estrato promedio de 5.7. Predominan las casas (72%). Son las propiedades de mayor tamaño, valor y nivel socioeconómico del mercado.
Cluster 2 — Segmento Medio-Alto (n = 2.332, ~34% de la oferta): Precio promedio de $533 millones y área de 215 m², estrato promedio de 5.2. Distribución casi equilibrada entre casas (52%) y apartamentos (48%). Representa el segmento intermedio, orientado principalmente a estratos 5.
Cluster 3 — Segmento Popular (n = 3.498, ~52% de la oferta): Precio promedio de $261 millones y área de 97 m², estrato promedio de 4.4. Predominan los apartamentos (82%). Es el segmento más numeroso y corresponde a la oferta de vivienda más accesible del mercado.
El análisis de correspondencia permite visualizar asociaciones entre variables categóricas en un plano bidimensional, mostrando qué categorías de una variable tienden a aparecer junto a qué categorías de otra variable.
tabla_tz <- table(vivienda$tipo, vivienda$zona)
kable(tabla_tz, caption = "Frecuencias: Tipo de vivienda × Zona")| Zona Centro | Zona Norte | Zona Oeste | Zona Oriente | Zona Sur | |
|---|---|---|---|---|---|
| Apartamento | 24 | 1198 | 1029 | 62 | 2787 |
| Casa | 100 | 722 | 169 | 289 | 1939 |
##
## Pearson's Chi-squared test
##
## data: tabla_tz
## X-squared = 690.93, df = 4, p-value < 2.2e-16
Resultado: El p-value de 3.207561e-148 < 0.05 indica que se rechaza la hipótesis de independencia: el tipo de vivienda y la zona de ubicación sí están relacionados.
res_ac1 <- CA(tabla_tz, graph = FALSE)
# Varianza explicada
vals_prop1 <- as.data.frame(round(res_ac1$eig, 3))
kable(vals_prop1, caption = "Varianza explicada - Tipo vs Zona")| eigenvalue | percentage of variance | cumulative percentage of variance | |
|---|---|---|---|
| dim 1 | 0.083 | 100 | 100 |
# tipo tiene 2 categorías → CA genera 1 sola dimensión
# Función robusta: maneja coord como vector (1D) o matriz (2D+)
extract_ca_coord <- function(coord_obj, grupo) {
if (is.null(dim(coord_obj))) {
# Vector 1D
data.frame(nombre = names(coord_obj),
coord1 = as.numeric(coord_obj),
grupo = grupo, stringsAsFactors = FALSE)
} else {
# Matriz 2D
data.frame(nombre = rownames(coord_obj),
coord1 = as.numeric(coord_obj[, 1]),
grupo = grupo, stringsAsFactors = FALSE)
}
}
df_filas <- extract_ca_coord(res_ac1$row$coord, "Tipo de vivienda")
df_cols_d <- extract_ca_coord(res_ac1$col$coord, "Zona")
df_mapa1 <- rbind(df_filas, df_cols_d)
ggplot(df_mapa1, aes(x = coord1, y = grupo, label = nombre, color = grupo)) +
geom_point(size = 4) +
geom_text(vjust = -1.2, size = 3.5, fontface = "bold") +
geom_hline(yintercept = 1.5, linetype = "dashed", color = "gray60") +
scale_color_manual(values = c("Tipo de vivienda" = "#034D94", "Zona" = "#FF7F00")) +
labs(title = "Mapa de correspondencias: Tipo de vivienda vs Zona",
subtitle = "Dimensión 1 explica el 100% de la inercia total",
x = "Dimensión 1", y = "", color = "") +
theme_minimal() +
theme(legend.position = "bottom",
axis.text.y = element_blank(),
axis.ticks.y = element_blank())Interpretación: Al tener solo 2 categorías en la variable Tipo, el AC genera 1 única dimensión que explica el 100% de la inercia total.
Del mapa se observa que: - Los Apartamentos se asocian principalmente con la Zona Oeste, ambos ubicados en el extremo negativo de la Dimensión 1. - Las Casas se asocian principalmente con las Zonas Centro y Oriente, compartiendo el extremo positivo de la Dimensión 1. Las Zonas Norte y Sur presentan posiciones intermedias en el mapa.
vivienda$estrato_f <- as.factor(vivienda$estrato)
tabla_ze <- table(vivienda$zona, vivienda$estrato_f)
colnames(tabla_ze) <- paste("Estrato", colnames(tabla_ze))
kable(tabla_ze, caption = "Frecuencias: Zona × Estrato")| Estrato 3 | Estrato 4 | Estrato 5 | Estrato 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 |
##
## Pearson's Chi-squared test
##
## data: tabla_ze
## X-squared = 3830.4, df = 12, p-value < 2.2e-16
Resultado: El p-value 0e+00 < 0.05 confirma que zona y estrato están significativamente relacionados.
res_ac2 <- CA(tabla_ze, graph = FALSE)
vals_prop2 <- as.data.frame(round(res_ac2$eig, 3))
kable(vals_prop2, caption = "Varianza explicada - Zona vs Estrato")| eigenvalue | percentage of variance | cumulative percentage of variance | |
|---|---|---|---|
| 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_ac2,
repel = TRUE,
col.row = "#034D94",
col.col = "#e74c3c",
title = "Mapa de correspondencias: Zona vs Estrato") +
theme_minimal()fviz_screeplot(res_ac2, addlabels = TRUE, ylim = c(0, 90)) +
ggtitle("Varianza explicada por dimensión - Zona vs Estrato") +
ylab("Porcentaje de varianza (%)") +
xlab("Dimensión") +
theme_minimal()Interpretación: Las dos primeras dimensiones explican el 97.6% de la varianza total.
Del mapa se observa que: - El Estrato 6 se asocia fuertemente con la Zona Oeste, confirmando que esta zona concentra la oferta de mayor nivel socioeconómico. - El Estrato 3 (el más bajo presente en la muestra) se asocia con las Zonas Oriente y Centro, sectores de menor nivel socioeconómico. - Los Estratos 4 y 5 se concentran en las Zonas Norte y Sur, correspondientes al segmento medio y medio-alto de la ciudad.
El ACP permitió reducir las 6 variables cuantitativas originales a un número menor de componentes, logrando que los dos primeros expliquen aproximadamente el 78.4% de la varianza total. Este nivel de representación es adecuado para un análisis exploratorio del mercado inmobiliario.
Los principales hallazgos son:
Se identificaron 3 segmentos diferenciados del mercado inmobiliario:
Cluster 1 — Segmento Premium: Propiedades con precio promedio de $1.117 millones y área de 419 m² (estrato ~5.7). Representan el 13% de la oferta y corresponden a casas grandes de alto valor.
Cluster 2 — Segmento Medio-Alto: Precio promedio de $533 millones y área de 215 m² (estrato ~5.2). Constituyen el 34% de la oferta con una mezcla equilibrada de casas y apartamentos.
Cluster 3 — Segmento Popular: Precio promedio de $261 millones y área de 97 m² (estrato ~4.4). Son el segmento más numeroso (52% de la oferta), con predominio de apartamentos, y representan la vivienda más accesible del mercado.
Los dos análisis de correspondencia revelaron asociaciones significativas (p < 0.05 en ambos casos):
Tipo de vivienda y Zona: Los Apartamentos se asocian con la Zona Oeste, mientras que las Casas se vinculan con las Zonas Centro y Oriente. Esta diferenciación refleja los patrones urbanísticos propios de cada sector de la ciudad.
Zona y Estrato: La Zona Oeste concentra el Estrato 6, mientras que las Zonas Oriente y Centro se asocian con el Estrato 3 (el más bajo de la muestra). Las Zonas Norte y Sur corresponden a los estratos medios (4 y 5), confirmando una geografía socioeconómica claramente segmentada.
Con base en los hallazgos anteriores, se recomienda a la empresa inmobiliaria:
Segmentación del portafolio: Diseñar estrategias comerciales diferenciadas para cada uno de los 3 segmentos identificados, adaptando mensajes, canales y precios a cada perfil de cliente.
Focalización geográfica: Concentrar las inversiones en propiedades de estrato alto en la Zona Oeste, donde la demanda premium es más intensa según el análisis de correspondencia.
Diversificación de oferta: En la Zona Oeste, donde predominan apartamentos, explorar oportunidades de casas para capturar demanda insatisfecha de ese tipo de propiedad.
Monitoreo de segmentos: Establecer indicadores de seguimiento específicos por cluster para detectar cambios en los patrones del mercado de manera oportuna.
| Paquete | Versión | Función |
|---|---|---|
paqueteMODELOS |
- | Datos del curso |
tidyverse |
- | Manipulación y visualización de datos |
factoextra |
- | Visualización de ACP y Cluster |
FactoMineR |
- | ACP y Análisis de Correspondencia |
cluster |
- | Análisis de conglomerados |
mice |
- | Detección de datos faltantes |
Los datos provienen de la base vivienda
del paquete paqueteMODELOS, que contiene información de
propiedades residenciales disponibles en una ciudad colombiana, obtenida
mediante técnicas de web scraping desde la plataforma OLX.