Este informe desarrolla un análisis multivariado del mercado de vivienda
urbana utilizando la base de datos vivienda del paquete paqueteMODELOS,
aplicando Análisis de Componentes Principales, Análisis de Conglomerados y
Análisis de Correspondencia.
Se cargan los paquetes necesarios para el análisis: paqueteMODELOS trae
el dataset vivienda; dplyr y ggplot2 para manipulación y
visualización; corrplot para graficar la matriz de correlación;
FactoMineR y factoextra para PCA, Correspondencia y su visualización;
cluster para Análisis de Conglomerados.
Se carga el dataset vivienda incluido en el paquete paqueteMODELOS.
Se identifica el número de registros, número de atributos y el tipo de cada uno.
## [1] 8322 13
El dataset tiene 8322 registros y 13 atributos.
La Tabla 1.1 caracteriza el
conjunto de datos según el tipo de cada variable. Las variables cuantitativas
corresponden a estrato, preciom, areaconst, parqueaderos, banios y
habitaciones, mientras que zona, piso, tipo y barrio son variables
categóricas. Adicionalmente, el dataset incluye las variables longitud y
latitud, que aunque son de naturaleza numérica, corresponden a coordenadas
geográficas y no a características intrínsecas de la vivienda; dado que el
alcance de este análisis no contempla un componente espacial, estas dos
variables se excluyen del conjunto de variables cuantitativas a utilizar en
el Análisis de Componentes Principales. Por otro lado, la tabla también
permite identificar una concentración importante de valores faltantes en las
variables piso y parqueaderos, muy por encima del resto de atributos.
tabla_tipos <- data.frame(
Variable = names(vivienda),
Tipo = sapply(vivienda, class),
Ejemplo = sapply(vivienda, function(x) as.character(head(x[!is.na(x)], 1))),
NA_count = sapply(vivienda, function(x) sum(is.na(x))),
row.names = NULL
)
knitr::kable(tabla_tipos,
caption = "Tipo de dato, ejemplo de valor y datos faltantes por variable",
col.names = c("Variable", "Tipo de dato", "Ejemplo", "Valores faltantes"))| Variable | Tipo de dato | Ejemplo | Valores faltantes |
|---|---|---|---|
| id | numeric | 1147 | 3 |
| zona | character | Zona Oriente | 3 |
| piso | character | 02 | 2638 |
| estrato | numeric | 3 | 3 |
| preciom | numeric | 250 | 2 |
| areaconst | numeric | 70 | 3 |
| parqueaderos | numeric | 1 | 1605 |
| banios | numeric | 3 | 3 |
| habitaciones | numeric | 6 | 3 |
| tipo | character | Casa | 3 |
| barrio | character | 20 de julio | 3 |
| longitud | numeric | -76.51168 | 3 |
| latitud | numeric | 3.43382 | 3 |
Se seleccionan las variables numéricas y se calculan medidas de centralidad (media, mediana) y dispersión (desviación estándar, rango).
La Tabla 1.2 presenta los estadísticos de
centralidad y dispersión de las variables numéricas del dataset. Se observa
una asimetría marcada hacia la derecha en preciom y areaconst, donde la
media supera considerablemente a la mediana (433.89 frente a 330.00 millones
de pesos, y 174.93 frente a 123.00 metros cuadrados, respectivamente), lo
que indica la presencia de un grupo reducido de propiedades de alto valor y
tamaño que desplaza el promedio por encima del comportamiento típico del
mercado. Esta asimetría se refuerza con la alta dispersión de preciom,
cuya desviación estándar (328.65) es comparable en magnitud a su propia
media, evidenciando un mercado heterogéneo en precios. Por su parte, la
variable estrato se concentra entre los valores 3 y 6, sin registros en
los estratos 1 y 2, lo que sugiere que la oferta capturada en este dataset
está sesgada hacia segmentos socioeconómicos medios y altos. Finalmente,
longitud y latitud presentan una dispersión prácticamente nula
(desviaciones estándar de 0.02 y 0.04), lo cual es consistente con que el
dataset corresponde exclusivamente a viviendas ubicadas dentro de la ciudad
de Cali
resumen_stats <- variables_numericas %>%
summarise(across(everything(),
list(Media = ~mean(.x, na.rm = TRUE),
Mediana = ~median(.x, na.rm = TRUE),
DesvEst = ~sd(.x, na.rm = TRUE),
Min = ~min(.x, na.rm = TRUE),
Max = ~max(.x, na.rm = TRUE),
NA_count = ~sum(is.na(.x))),
.names = "{.col}__{.fn}")) %>%
pivot_longer(everything(), names_to = c("Variable","Estadistico"),
names_sep = "__", values_to = "Valor") %>%
pivot_wider(names_from = Estadistico, values_from = Valor)
knitr::kable(resumen_stats, digits = 2,
caption = "Estadísticos descriptivos de las variables numéricas")| Variable | Media | Mediana | DesvEst | Min | Max | NA_count |
|---|---|---|---|---|---|---|
| estrato | 4.63 | 5.00 | 1.03 | 3.00 | 6.00 | 3 |
| preciom | 433.89 | 330.00 | 328.65 | 58.00 | 1999.00 | 2 |
| areaconst | 174.93 | 123.00 | 142.96 | 30.00 | 1745.00 | 3 |
| parqueaderos | 1.84 | 2.00 | 1.12 | 1.00 | 10.00 | 1605 |
| banios | 3.11 | 3.00 | 1.43 | 0.00 | 10.00 | 3 |
| habitaciones | 3.61 | 3.00 | 1.46 | 0.00 | 10.00 | 3 |
| longitud | -76.53 | -76.53 | 0.02 | -76.59 | -76.46 | 3 |
| latitud | 3.42 | 3.42 | 0.04 | 3.33 | 3.50 | 3 |
Se identificó que las variables banios y habitaciones registran un mínimo de 0, lo cual carece de sentido en el contexto de una vivienda habitable: toda propiedad residencial, ya sea casa o apartamento, cuenta por definición con al menos un baño y una
habitación. Esto sugiere que dichos valores corresponden a errores de
captura durante el proceso de web scraping, probablemente campos que el
anunciante dejó vacíos y que el sistema completó con 0 en lugar de un valor
faltante (NA). Se identificaron 45 registros con banios == 0 y 66 con
habitaciones == 0. Para descartar que este patrón estuviera asociado a un
tipo de propiedad específico (por ejemplo, locales u oficinas mal
clasificados como vivienda), se revisó la distribución de tipo para los
registros con banios == 0, encontrando que el problema está presente
tanto en apartamentos (14 casos) como en casas (31 casos).
Se calcula la matriz de correlación entre las variables numéricas para identificar posibles atributos redundantes.
matriz_correlacion <- cor(variables_numericas, use = "pairwise.complete.obs")
round(matriz_correlacion, 2)corrplot(matriz_correlacion, method = "color", addCoef.col = "black",
type = "upper", tl.col = "black", number.cex = 0.7)Figure 1.1: Matriz de correlación de las variables numéricas
La Figura 1.1 muestra las correlaciones entre
las variables numéricas del dataset. Centrando el análisis en las cinco
variables seleccionadas para el Análisis de Componentes Principales
(preciom, areaconst, parqueaderos, banios y habitaciones), se
observa un bloque de correlaciones moderadas a fuertes (entre 0.57 y 0.69)
entre preciom, areaconst, parqueaderos y banios, lo que indica que
estas variables tienden a moverse conjuntamente y comparten información
redundante sobre el tamaño y valor de la propiedad. En contraste,
habitaciones presenta correlaciones más débiles con preciom (0.26) y
parqueaderos (0.28), aunque mantiene una relación moderada con areaconst
(0.52) y banios (0.59), lo que sugiere un comportamiento parcialmente
distinto al resto del grupo. Ninguna correlación entre estas cinco variables
supera 0.7, por lo que no se identifica redundancia extrema que amerite
excluir alguna de ellas antes del análisis. Este patrón de correlaciones
moderadas es favorable para el Análisis de Componentes Principales, ya que
sugiere la existencia de estructura compartida susceptible de resumirse en
un número reducido de componentes.
Figure 1.2: Boxplot de las variables numéricas para detección de atípicos
La Figura 1.2 muestra la presencia de valores
atípicos principalmente en preciom y areaconst, con observaciones que
alcanzan hasta 2000 millones de pesos y 1750 metros cuadrados,
respectivamente. A diferencia de los valores inválidos identificados
previamente en banios y habitaciones, estos atípicos corresponden a
propiedades de alto valor y gran tamaño que son plausibles dentro del
mercado inmobiliario real, por lo que no se consideran errores de captura.
Dado que representan la cola alta legítima del mercado, se conservan en el
análisis para no distorsionar la variabilidad real capturada en los
siguientes análisis multivariados.
El conteo de valores faltantes por variable ya se identificó en la tabla de tipos de datos (Sección 1.3). Aquí se revisa una dimensión distinta: cuántos valores faltantes acumula cada registro (fila), lo que ayuda a decidir si conviene eliminar observaciones muy incompletas o si los NA están dispersos entre distintas propiedades.
na_por_fila <- rowSums(is.na(vivienda))
tabla_na_fila <- data.frame(
NA_por_registro = names(table(na_por_fila)),
Cantidad_registros = as.integer(table(na_por_fila))
)
knitr::kable(tabla_na_fila,
caption = "Distribución del número de registros según cantidad de valores faltantes por registro",
col.names = c("N° de valores faltantes en el registro", "Cantidad de registros"))| N° de valores faltantes en el registro | Cantidad de registros |
|---|---|
| 0 | 4808 |
| 1 | 2785 |
| 2 | 726 |
| 12 | 1 |
| 13 | 2 |
El máximo de valores faltantes en un mismo registro es de 13.
Se revisa la distribución de las variables categóricas tipo y zona
para detectar posible desbalance en la oferta.
knitr::kable(table(vivienda$tipo),
caption = "Distribución de la variable tipo de vivienda",
col.names = c("Tipo", "Frecuencia"))| Tipo | Frecuencia |
|---|---|
| Apartamento | 5100 |
| Casa | 3219 |
knitr::kable(table(vivienda$zona),
caption = "Distribución de la variable zona",
col.names = c("Zona", "Frecuencia"))| Zona | Frecuencia |
|---|---|
| Zona Centro | 124 |
| Zona Norte | 1920 |
| Zona Oeste | 1198 |
| Zona Oriente | 351 |
| Zona Sur | 4726 |
En la Sección 1.7 se caracterizó la distribución del número de valores
faltantes por registro, encontrando registros con un número extremo de
atributos vacíos. Un registro con 12 o más valores faltantes (de un total
de 13 columnas) no aporta información utilizable para el análisis
multivariado, ya que prácticamente todo el registro estaría compuesto por
datos imputados o supuestos. Por esta razón, antes de proceder con la
imputación de parqueaderos y la exclusión de valores inválidos en
banios y habitaciones, se eliminan del dataset las filas que
presentan 12 o más valores faltantes.
n_antes <- nrow(vivienda)
vivienda <- vivienda %>%
filter(rowSums(is.na(.)) < 12)
n_despues <- nrow(vivienda)
cat("Registros antes de eliminar filas con 12 o más valores faltantes:", n_antes, "\n")## Registros antes de eliminar filas con 12 o más valores faltantes: 8322
## Registros después de eliminar filas con 12 o más valores faltantes: 8319
## Registros eliminados: 3
A partir de este punto, el dataset no contiene registros con un nivel extremo de valores faltantes, quedando listo para las siguientes etapas de preparación de los datos.
La variable parqueaderos presenta 1605 valores faltantes, equivalentes a
cerca del 19% del total de registros del dataset, una proporción
considerablemente mayor que la de las demás variables numéricas relevantes
para el análisis de Componentes Principales posterior (preciom,
areaconst, banios y habitaciones, cada una con apenas 2-3 valores
faltantes). Dada esta magnitud, se optó por imputar los valores faltantes
en lugar de excluir las filas correspondientes, bajo el supuesto de que un
NA en esta variable representa información no reportada por el anunciante
y no necesariamente la ausencia de parqueadero en el inmueble. La
imputación se realiza utilizando la mediana de parqueaderos calculada
por grupos definidos por tipo de vivienda y estrato, con el fin de
capturar de mejor manera la heterogeneidad del mercado inmobiliario según
estas dos características.
na_antes <- sum(is.na(vivienda$parqueaderos))
vivienda <- vivienda %>%
group_by(tipo, estrato) %>%
mutate(parqueaderos = ifelse(is.na(parqueaderos),
median(parqueaderos, na.rm = TRUE),
parqueaderos)) %>%
ungroup()
na_despues <- sum(is.na(vivienda$parqueaderos))
if (na_despues > 0) {
vivienda$parqueaderos[is.na(vivienda$parqueaderos)] <- median(vivienda$parqueaderos, na.rm = TRUE)
}
cat("NA en parqueaderos antes de imputar:", na_antes, "\n")## NA en parqueaderos antes de imputar: 1602
## NA en parqueaderos después de imputar: 0
A partir de este punto, la variable parqueaderos queda completa (sin
valores faltantes) y lista para ser utilizada en el Análisis de
Componentes Principales.
Como se identificó en la Sección 1.3, las variables banios y
habitaciones registran un mínimo de 0, un valor sin sentido para una
vivienda habitable, ya que toda propiedad residencial cuenta por
definición con al menos un baño y una habitación. Se detectaron 45
registros con banios == 0 y 66 con habitaciones == 0, probablemente
asociados a errores de captura durante el proceso de web scraping. A
diferencia del caso de parqueaderos, aquí se optó por excluir estos
registros en lugar de imputarlos, ya que 0 no es un valor faltante sino
un dato inválido efectivamente registrado, y no existe un supuesto
razonable que permita reconstruir el valor real de baños o habitaciones
a partir de otras variables. Dado que estos registros representan una
fracción muy pequeña del total del dataset, su exclusión no compromete
de forma significativa el tamaño muestral ni la representatividad del
análisis.
n_antes <- nrow(vivienda)
vivienda <- vivienda %>%
filter(banios > 0, habitaciones > 0)
n_despues <- nrow(vivienda)
cat("Registros antes de excluir valores inválidos:", n_antes, "\n")## Registros antes de excluir valores inválidos: 8319
## Registros después de excluir valores inválidos: 8243
## Registros excluidos: 76
A partir de este punto, las variables banios y habitaciones quedan
libres de valores inválidos y listas para el análisis de componentes
principales.
vars_pca <- c("preciom", "areaconst", "parqueaderos", "banios", "habitaciones")
# NA por variable, después de toda la preparación
sapply(vivienda[, vars_pca], function(x) sum(is.na(x)))## preciom areaconst parqueaderos banios habitaciones
## 0 0 0 0 0
## [1] 0
Dado que las variables seleccionadas para el Análisis de Componentes
Principales (preciom, areaconst, parqueaderos, banios y
habitaciones) se miden en escalas y unidades muy distintas —desde
millones de pesos hasta conteos de baños o habitaciones—, es necesario
estandarizarlas (centrar y escalar a varianza unitaria) antes de aplicar
el PCA. De lo contrario, las variables con mayor magnitud absoluta, como
preciom y areaconst, dominarían artificialmente la varianza capturada
por las componentes, en desmedro de variables con rangos más pequeños
como banios o habitaciones. Por esta razón, el PCA se calcula sobre
las variables estandarizadas (scale.unit = TRUE).
Antes de interpretar los resultados del PCA es necesario determinar cuántas componentes conviene retener. La Tabla 3.1 y la Figura 3.1 presentan los valores propios y el porcentaje de varianza explicada por cada componente. La primera componente concentra el 65.05% de la varianza total (valor propio de 3.25), y junto con la segunda (17.76%, valor propio de 0.89) acumulan un 82.81% de la variabilidad original de las cinco variables estandarizadas. Bajo el criterio de Kaiser (retener componentes con valor propio superior a 1), solo la primera componente lo cumple de forma estricta; sin embargo, dado que la segunda componente tiene un valor propio cercano a 1 y que entre ambas se explica una proporción alta de la varianza total, se retienen las dos primeras componentes principales para las visualizaciones e interpretaciones siguientes.
tabla_eig <- round(as.data.frame(res.pca$eig), 2)
knitr::kable(tabla_eig,
caption = "Valores propios y varianza explicada por componente",
col.names = c("Valor propio", "% Varianza", "% Varianza acumulada"))| Valor propio | % Varianza | % Varianza acumulada | |
|---|---|---|---|
| comp 1 | 3.25 | 65.05 | 65.05 |
| comp 2 | 0.89 | 17.76 | 82.81 |
| comp 3 | 0.35 | 7.03 | 89.84 |
| comp 4 | 0.32 | 6.44 | 96.28 |
| comp 5 | 0.19 | 3.72 | 100.00 |
Figure 3.1: Varianza explicada por cada componente principal
Para interpretar el significado de las dos primeras componentes se examina
la relación entre estas y las variables originales, mostrada en la Figura
3.2, así como la contribución de cada variable,
presentada en la Figura 3.3. Las cinco variables
cargan de forma positiva y con magnitudes similares sobre la primera
componente (banios 0.88, areaconst 0.87, preciom 0.85, parqueaderos
0.77 y habitaciones 0.63), con contribuciones relativamente equilibradas
que oscilan entre 12.25% y 23.79%, por lo que la primera componente se
interpreta como un eje general de tamaño y valor de la vivienda: a
mayor puntuación en esta componente, mayor precio, área construida, número
de parqueaderos, baños y habitaciones de la propiedad. La segunda
componente, en cambio, está dominada por habitaciones (carga de 0.73 y
contribución de 59.86%), que se opone a parqueaderos (carga de -0.44,
contribución de 22.27%) y, en menor medida, a preciom (carga de -0.36,
contribución de 14.90%), mientras que areaconst y banios apenas
contribuyen a este eje. La segunda componente se interpreta entonces como
un contraste entre viviendas con más habitaciones en relación con sus
parqueaderos y precio, frente a viviendas con más parqueaderos y mayor
precio en relación con su número de habitaciones.
Figure 3.2: Círculo de correlaciones de las variables en el plano de las dos primeras componentes
Figure 3.3: Contribución de las variables a las dos primeras componentes principales
Se visualizan los individuos en el plano de las dos primeras componentes
principales, coloreados según la variable tipo, con el fin de explorar
si apartamentos y casas presentan patrones diferenciados en el espacio
definido por las componentes.
fviz_pca_ind(res.pca, habillage = vivienda$tipo,
geom = "point", addEllipses = TRUE,
palette = c("#FF7F00", "#034D94"))Figure 3.4: Individuos del PCA coloreados según tipo de vivienda
La Figura 3.4 muestra una separación clara entre apartamentos y casas a lo largo de la primera componente: las casas presentan, en promedio, una puntuación de 1.17 en la primera componente, frente a -0.73 en los apartamentos, lo que es consistente con la interpretación de esta componente como un eje de tamaño y valor, e indica que las casas tienden a ser propiedades de mayor tamaño y precio que los apartamentos. En la segunda componente también se observa una diferencia, aunque menos pronunciada (0.42 en casas frente a -0.26 en apartamentos), lo que sugiere que las casas tienden a tener relativamente más habitaciones en proporción a sus parqueaderos y precio que los apartamentos. A pesar de esta separación de medias, las elipses de ambos grupos se superponen de forma considerable, lo que indica que el tipo de vivienda no constituye una frontera nítida en el espacio de las dos primeras componentes principales.
Se repite la visualización de individuos coloreando ahora por la
variable zona, con el objetivo de identificar si existe algún tipo de
segmentación geográfica del mercado en el espacio de las componentes
principales.
Figure 3.5: Individuos del PCA coloreados según zona
A diferencia de lo observado para tipo, la Figura 3.5
muestra un solapamiento considerable entre las cinco zonas, sin una
segmentación geográfica clara del mercado en el espacio de las dos primeras
componentes. La Zona Oeste registra la puntuación promedio más alta en la
primera componente (0.64), mientras que la Zona Norte presenta la más baja
(-0.40), lo que sugiere una leve tendencia de la Zona Oeste hacia
propiedades de mayor tamaño y valor en comparación con la Zona Norte; las
zonas Centro, Oriente y Sur se ubican en valores intermedios cercanos a
cero. En la segunda componente, las zonas Centro y Oriente presentan las
puntuaciones promedio más altas (0.99 y 1.20, respectivamente), lo que
indica una relativa mayor proporción de habitaciones frente a parqueaderos
y precio en esas zonas, mientras que la Zona Oeste muestra el valor más
bajo (-0.57), consistente con viviendas con relativamente más parqueaderos
y mayor precio en proporción a sus habitaciones. En conjunto, estas
diferencias son moderadas y las elipses de las distintas zonas se
superponen ampliamente, por lo que la zona no resulta ser un factor
determinante en la estructura de componentes principales, a diferencia del
tipo de vivienda.
El Análisis de Conglomerados se realiza sobre las puntuaciones (scores) de las dos primeras componentes principales obtenidas en el análisis anterior, en lugar de sobre las cinco variables originales. Esto permite trabajar sobre un espacio de menor dimensión, no correlacionado y que ya resume el 82.81% de la variabilidad original del conjunto de datos, evitando además la redundancia que introduciría el uso directo de variables correlacionadas entre sí. Se emplea el algoritmo K-means, apropiado dado el tamaño del dataset (8243 observaciones) y el objetivo de obtener segmentos discretos y fácilmente interpretables para la toma de decisiones de negocio. Como K-means requiere definir de antemano el número de clusters (k), se utiliza el método del codo, que evalúa la suma de cuadrados dentro de los clusters (WSS) para distintos valores de k, seleccionando el punto en el que aumentar el número de grupos deja de reducir significativamente dicha dispersión.
scores_pca <- res.pca$ind$coord[, 1:2]
set.seed(123)
fviz_nbclust(scores_pca, kmeans, method = "wss") +
labs(subtitle = "Método del codo")El valor de k se determina observando el punto en el que la curva de WSS se aplana (el “codo” de la curva), y dicho valor se utilizará en la siguiente subsección para ajustar el modelo K-means definitivo.
A partir del método del codo aplicado en la sección anterior, se determinó que k = 4 es el número apropiado de clusters, ya que es el punto donde la curva de suma de cuadrados dentro de los clusters (WSS) pasa de una caída pronunciada a un comportamiento notablemente más plano. Se ajusta entonces el modelo K-means definitivo con cuatro centros, utilizando múltiples inicializaciones aleatorias (nstart = 25) para evitar quedar en una solución subóptima, dado que el resultado de K-means depende de la ubicación inicial de los centroides.
set.seed(123)
km_res <- kmeans(scores_pca, centers = 4, nstart = 25)
fviz_cluster(km_res, data = scores_pca,
palette = c("#034D94", "#FF7F00", "#00AFBB", "#E7B800"),
geom = "point", ellipse.type = "convex",
ggtheme = theme_minimal())Para caracterizar los cuatro segmentos identificados por el modelo K-means,
se calcula el perfil promedio de las variables originales (preciom,
areaconst, parqueaderos, banios y habitaciones) en cada cluster,
junto con el número de viviendas que agrupa cada uno.
vivienda_clusters <- vivienda %>%
mutate(cluster = factor(km_res$cluster))
perfil_clusters <- vivienda_clusters %>%
group_by(cluster) %>%
summarise(
n = n(),
across(all_of(vars_pca), mean)
)
knitr::kable(perfil_clusters, digits = 2,
caption = "Perfil promedio de las viviendas según cluster",
col.names = c("Cluster", "N° viviendas", "Precio (millones)",
"Área construida (m²)", "Parqueaderos",
"Baños", "Habitaciones"))| Cluster | N° viviendas | Precio (millones) | Área construida (m²) | Parqueaderos | Baños | Habitaciones |
|---|---|---|---|---|---|---|
| 1 | 742 | 471.89 | 318.39 | 1.57 | 4.75 | 6.87 |
| 2 | 2461 | 524.71 | 193.74 | 1.99 | 3.67 | 3.65 |
| 3 | 855 | 1145.43 | 416.97 | 3.88 | 5.18 | 4.39 |
| 4 | 4185 | 227.15 | 87.22 | 1.13 | 2.11 | 2.89 |
La Tabla 4.1 permite caracterizar los cuatro segmentos identificados por K-means en términos concretos del negocio inmobiliario. El Cluster 4 es el segmento más numeroso (4185 viviendas, el 50.8% del total) y corresponde a las propiedades más económicas y compactas del mercado, con un precio promedio de 227.15 millones de pesos, 87.22 m² construidos y los valores más bajos en parqueaderos, baños y habitaciones; representa la entrada del mercado de vivienda en Cali. El Cluster 1 agrupa 742 viviendas de tamaño considerable (318.39 m²) y un número notablemente alto de habitaciones (6.87 en promedio) y baños (4.75), pero con relativamente pocos parqueaderos (1.57) y el menor precio por metro cuadrado del grupo (≈1.48 millones de pesos/m²); este perfil sugiere viviendas familiares amplias, probablemente casas grandes orientadas a espacio habitable más que a comodidades como parqueaderos. El Cluster 2, el segundo más numeroso (2461 viviendas), presenta un tamaño moderado (193.74 m²) pero un precio promedio relativamente alto (524.71 millones), resultando en el precio por metro cuadrado más alto de los cuatro grupos (≈2.71 millones de pesos/m²), lo que sugiere propiedades de gama media-alta ubicadas en sectores valorizados. Finalmente, el Cluster 3 es el segmento premium del mercado: aunque el menos numeroso junto al Cluster 1 (855 viviendas), presenta los valores máximos en precio (1145.43 millones), área construida (416.97 m²), parqueaderos (3.88) y baños (5.18), consolidándose como el grupo de propiedades de mayor valor y mejor dotación de comodidades, aunque con un número de habitaciones (4.39) inferior al del Cluster 1, lo que refuerza la interpretación de la segunda componente principal como un contraste entre habitaciones y parqueaderos/precio.
Previo a la construcción del Análisis de Correspondencia, se
revisaron las tres variables categóricas de interés (tipo, zona,
barrio). Las variables tipo y zona no presentan valores faltantes ni problemas de calidad tras la depuración realizada en la sección 2, por lo que se incorporan directamente al análisis. La variable barrio, en cambio, presenta dos dificultades: en
primer lugar, se identificaron categorías duplicadas por inconsistencias de
capitalización; por ejemplo, “la flora” y “La Flora” registradas como
barrios distintos, las cuales se unificaron normalizando el texto a
minúsculas, reduciendo el número de categorías únicas de 433 a 404. En
segundo lugar, barrio presenta una distribución altamente fragmentada:
de los 404 barrios identificados, la mayoría concentra muy pocas
observaciones, mientras que un número reducido de barrios (encabezados por
valle del lili, ciudad jardín y pance) agrupa la mayor parte de la
oferta. Dado que un número tan alto de categorías haría inmanejable e
ilegible la representación gráfica del MCA, y que las categorías con muy
pocas observaciones aportarían escasa robustez estadística al análisis, se
optó por conservar como categorías individuales únicamente los barrios con
al menos 50 viviendas (37 barrios), agrupando el resto en una categoría
“Otros”. Esta categoría concentra 2732 viviendas (33.1% del total), lo cual
constituye en sí mismo un hallazgo relevante: evidencia una alta dispersión
geográfica de la oferta de vivienda en Cali, con la demanda repartida entre
un gran número de barrios pequeños, más allá de los pocos sectores de mayor
concentración. Se advierte que la categoría “Otros” agrupa barrios
heterogéneos entre sí, por lo que su interpretación en los resultados del
MCA debe entenderse como “el resto disperso del mercado” y no como un
sector geográfico homogéneo.
# 1. Cantidad de categorías únicas en cada variable categórica de interés
sapply(vivienda[, c("tipo", "zona", "barrio")], function(x) length(unique(x)))
# 2. Verificación de NA residuales en estas mismas variables
sapply(vivienda[, c("tipo", "zona", "barrio")], function(x) sum(is.na(x)))
# 3. Distribución de frecuencias de barrio, para evaluar si hay categorías
# con muy pocas observaciones
sort(table(vivienda$barrio), decreasing = TRUE)vivienda <- vivienda %>%
mutate(barrio = str_to_lower(barrio) %>% str_trim())
# Volvemos a revisar la cantidad de categorías después de normalizar
length(unique(vivienda$barrio))
sort(table(vivienda$barrio), decreasing = TRUE)[1:20]umbral_barrio <- 50
frecuencias_barrio <- table(vivienda$barrio)
barrios_frecuentes <- names(frecuencias_barrio[frecuencias_barrio >= umbral_barrio])
vivienda <- vivienda %>%
mutate(barrio_agrupado = if_else(barrio %in% barrios_frecuentes, barrio, "Otros"))
length(barrios_frecuentes)## [1] 37
##
## Otros valle del lili ciudad jardín
## 2732 1009 515
## pance la flora santa teresita
## 409 363 262
## el caney el ingenio la hacienda
## 207 202 165
## normandía los cristales acopi
## 153 151 148
## el limonar prados del norte el refugio
## 134 127 119
## aguacatal ciudad 2000 caney
## 109 96 87
## cristales urbanización la flora brisas de los
## 83 83 82
## zona sur nueva tequendama quintas de don
## 74 73 73
## versalles santa isabel parcelaciones pance
## 70 64 61
## el peñon el lido torres de comfandi
## 60 59 57
## capri san fernando juanamb√∫
## 56 54 52
## santa monica melendez villa del prado
## 52 51 51
## el bosque santa anita
## 50 50
Con las variables categóricas depuradas en la sección anterior (tipo,
zona y barrio_agrupado), se construye el conjunto de datos para el
Análisis de Correspondencia Múltiple (MCA) y se ajusta el modelo.
vivienda_mca <- vivienda %>%
select(tipo, zona, barrio_agrupado) %>%
mutate(across(everything(), as.factor))
res.mca <- MCA(vivienda_mca, graph = FALSE)
summary(res.mca)##
## Call:
## MCA(X = vivienda_mca, graph = FALSE)
##
##
## Eigenvalues
## Dim.1 Dim.2 Dim.3 Dim.4 Dim.5
## Variance 0.643 0.599 0.495 0.375 0.342
## % of var. 4.594 4.276 3.538 2.678 2.440
## Cumulative % of var. 4.594 8.870 12.409 15.087 17.527
##
## Individuals (the 10 first)
## Dim.1 ctr cos2 Dim.2 ctr cos2 Dim.3 ctr
## 1 | -0.620 0.007 0.043 | 0.337 0.002 0.013 | -2.138 0.112
## 2 | -0.620 0.007 0.043 | 0.337 0.002 0.013 | -2.138 0.112
## 3 | -0.620 0.007 0.043 | 0.337 0.002 0.013 | -2.138 0.112
## 4 | -0.528 0.005 0.192 | -0.115 0.000 0.009 | -0.638 0.010
## 5 | 0.090 0.000 0.000 | 1.605 0.052 0.132 | 0.742 0.013
## 6 | 0.090 0.000 0.000 | 1.605 0.052 0.132 | 0.742 0.013
## 7 | 0.090 0.000 0.000 | 1.605 0.052 0.132 | 0.742 0.013
## 8 | 0.090 0.000 0.000 | 1.605 0.052 0.132 | 0.742 0.013
## 9 | -0.309 0.002 0.005 | 1.651 0.055 0.137 | 0.233 0.001
## 10 | -0.309 0.002 0.005 | 1.651 0.055 0.137 | 0.233 0.001
## cos2
## 1 0.517 |
## 2 0.517 |
## 3 0.517 |
## 4 0.279 |
## 5 0.028 |
## 6 0.028 |
## 7 0.028 |
## 8 0.028 |
## 9 0.003 |
## 10 0.003 |
##
## Categories (the 10 first)
## Dim.1 ctr cos2 v.test Dim.2 ctr cos2
## Apartamento | 0.370 4.360 0.219 42.470 | -0.041 0.059 0.003
## Casa | -0.592 6.982 0.219 -42.470 | 0.066 0.094 0.003
## Zona Centro | -0.655 0.326 0.006 -7.254 | 0.457 0.170 0.003
## Zona Norte | -0.061 0.045 0.001 -3.039 | 1.677 35.853 0.835
## Zona Oeste | 2.204 36.300 0.818 82.127 | -0.355 1.014 0.021
## Zona Oriente | -0.686 1.019 0.021 -13.007 | 0.423 0.418 0.008
## Zona Sur | -0.465 6.399 0.287 -48.663 | -0.626 12.460 0.521
## acopi | -0.091 0.008 0.000 -1.113 | 2.090 4.365 0.080
## aguacatal | 2.639 4.772 0.093 27.731 | -0.488 0.175 0.003
## brisas de los | 0.054 0.002 0.000 0.492 | 2.091 2.421 0.044
## v.test Dim.3 ctr cos2 v.test
## Apartamento -4.765 | 0.414 7.094 0.274 47.540 |
## Casa 4.765 | -0.663 11.358 0.274 -47.540 |
## Zona Centro 5.060 | -2.706 7.230 0.109 -29.980 |
## Zona Norte 82.970 | 0.393 2.382 0.046 19.455 |
## Zona Oeste -13.241 | -0.535 2.771 0.048 -19.915 |
## Zona Oriente 8.034 | -2.907 23.796 0.369 -55.154 |
## Zona Sur -65.515 | 0.260 2.597 0.090 27.209 |
## acopi 25.652 | 0.761 0.699 0.011 9.336 |
## aguacatal -5.129 | -0.457 0.186 0.003 -4.801 |
## brisas de los 19.025 | 1.066 0.761 0.011 9.701 |
##
## Categorical variables (eta2)
## Dim.1 Dim.2 Dim.3
## tipo | 0.219 0.003 0.274 |
## zona | 0.851 0.896 0.576 |
## barrio_agrupado | 0.860 0.897 0.636 |
Se examina la relación entre las categorías de zona y barrio_agrupado
en el plano de las dos primeras dimensiones del MCA, con el fin de
verificar si la variable barrio_agrupado reproduce, a un nivel más
detallado, la segmentación geográfica capturada por zona. Para ello se
filtra el mapa de categorías general, excluyendo las categorías de tipo.
fviz_mca_var(res.mca, axes = c(1,2),
col.var = "contrib",
gradient.cols = c("#FF7F00", "#034D94"),
repel = TRUE,
max.overlaps = 30,
select.var = list(contrib = 15))## Warning in (function (mapping = NULL, data = NULL, stat = "identity", position
## = "identity", : Ignoring unknown parameters: `max.overlaps`
La Figura ?? evidencia una fuerte concordancia
geográfica entre zona y barrio_agrupado. Cabe señalar que el filtro
aplicado selecciona las 15 categorías con mayor contribución conjunta a las
Dimensiones 1 y 2, sin excluir explícitamente las categorías de tipo;
sin embargo, dado el bajo eta2 de esta variable en el plano (0.219 y 0.003
en las Dimensiones 1 y 2, respectivamente), Apartamento y Casa
aparecen con contribuciones marginales (entre 5% y 8%) y ubicadas cerca
del centro del plano, sin asociarse a ningún extremo geográfico. La
Zona Oeste se ubica de forma aislada en el extremo positivo de la
Dimensión 1 (2.204), siendo además la categoría de zona con mayor
contribución a esta dimensión (36.30%) y la mejor representada (cos2 =
0.818); en esa misma región del plano se agrupan barrios como
santa teresita, normandía, los cristales, aguacatal, cristales y
el peñón, todos con coordenadas en la Dimensión 1 cercanas a 2.6-2.7,
confirmando que corresponden efectivamente al sector occidental de la
ciudad. La Zona Norte, por su parte, domina la Dimensión 2
(contribución de 35.85%, cos2 = 0.835) y coincide en esa dimensión con
barrios como acopi, prados del norte, villa del prado, el bosque,
torres de comfandi y santa mónica (coordenadas en Dimensión 2 entre
2.0 y 2.2). La Zona Sur se posiciona en el cuadrante negativo de
ambas dimensiones (-0.465, -0.626) junto a barrios reconocibles del sur
de Cali como valle del lili, ciudad jardín, pance y el caney. En
contraste, la Zona Centro exhibe las contribuciones y valores de
cos2 más bajos entre las cinco zonas (0.33% y 0.17% en las Dimensiones 1
y 2, respectivamente), lo que indica que está pobremente representada en
el plano y no se asocia de forma clara con un grupo específico de
barrios en estas dos primeras dimensiones. La categoría Otros, como es
esperable dada su naturaleza heterogénea, se ubica cerca del origen del
plano (-0.214, 0.293), sin una asociación geográfica definida.
Se repite el ejercicio de proyección para la variable tipo, con el
objetivo de determinar si el tipo de vivienda (apartamento o casa) sigue
un patrón de segmentación similar al observado para zona y
barrio_agrupado en las dos primeras dimensiones del MCA.
cats_tipo <- c("Apartamento", "Casa")
fviz_mca_var(res.mca, axes = c(1,3),
select.var = list(name = cats_tipo),
repel = TRUE, col.var = "contrib",
gradient.cols = c("#034D94", "#FF7F00"))Figure 5.1: Mapa de categorías de tipo de vivienda en el plano de las dos primeras dimensiones del MCA
A diferencia de zona y barrio_agrupado, la Figura
5.1 muestra que las categorías Apartamento (0.370,
-0.041) y Casa (-0.592, 0.066) se ubican relativamente cerca del
origen del plano formado por las dos primeras dimensiones, con niveles
de cos2 modestos (0.219 en ambos casos para la Dimensión 1, y apenas
0.003 para la Dimensión 2). Esto es consistente con la tabla de eta2 por
variable categórica, en la que tipo explica solo el 21.9% y el 0.3% de
las Dimensiones 1 y 2, respectivamente, muy por debajo del 85.1%/89.6% y
86.0%/89.7% que explican zona y barrio_agrupado en esas mismas
dimensiones. Esto indica que las dos primeras dimensiones del MCA están
dominadas casi en su totalidad por la estructura geográfica del mercado
(zona y barrio), mientras que el tipo de vivienda opera como un patrón
de segmentación distinto y en buena medida independiente de la
ubicación: de hecho, tipo alcanza su mayor asociación con la
Dimensión 3 (eta2 = 0.274), no representada en este mapa, lo que
sugiere que la diferenciación entre apartamentos y casas se explica
mejor por un eje adicional del MCA que por la segmentación geográfica
capturada en el plano principal.
El análisis multivariado del mercado de vivienda urbana en Cali, realizado sobre 8243 registros depurados de la plataforma OLX, permite identificar tres hallazgos estructurales que caracterizan la dinámica de oferta en la ciudad:
1. El mercado se organiza principalmente por tamaño y valor de la propiedad, no por ubicación geográfica. El Análisis de Componentes Principales mostró que un único eje —que combina precio, área construida, parqueaderos y baños— concentra el 65% de la variabilidad del mercado, mientras que la zona de la ciudad explica una proporción marginal de esa misma variabilidad (las cinco zonas se solapan ampliamente en el plano de componentes). Este hallazgo se confirma de forma independiente en el Análisis de Correspondencia Múltiple, donde el tipo de vivienda resultó prácticamente no relacionado con la segmentación geográfica capturada por zona y barrio (eta2 de apenas 0.22 frente a valores superiores a 0.85 para zona y barrio). En conjunto, esto indica que el atributo más relevante para diferenciar el valor de una propiedad no es su ubicación, sino sus características físicas (tamaño, dotación de parqueaderos y baños).
2. Existe un segundo patrón relacionado con la proporción entre habitaciones y comodidades. Independientemente del tamaño general de la vivienda, se identificó un eje adicional que contrasta propiedades con mayor número de habitaciones frente a propiedades con mayor número de parqueaderos y precio relativo. Las casas tienden a inclinarse hacia el primer perfil (más habitaciones en proporción a su tamaño), mientras que las propiedades de mayor precio por metro cuadrado se inclinan hacia el segundo.
3. El mercado se segmenta naturalmente en cuatro perfiles de vivienda claramente diferenciables. El Análisis de Conglomerados identificó cuatro grupos con perfiles de negocio distintos: un segmento masivo de entrada (50.8% del mercado, viviendas compactas y económicas), un segmento familiar orientado a espacio habitable (9.0%, casas grandes con muchas habitaciones pero pocos parqueaderos), un segmento de gama media-alta con alta valorización por metro cuadrado (29.9%), y un segmento premium de propiedades de lujo (10.4%, con los valores máximos en precio, área, parqueaderos y baños).
4. La oferta está altamente fragmentada geográficamente. El 33.1% de las viviendas del dataset se distribuye en más de 360 barrios distintos, cada uno con menos de 50 propiedades registradas, evidenciando que ningún sector domina de forma individual el mercado, salvo un puñado de barrios líderes como Valle del Lili, Ciudad Jardín y Pance, y una asociación clara entre ciertos barrios y zonas específicas (por ejemplo, Zona Oeste con barrios como Santa Teresita y Normandía).
Para la definición de precios y valoración de propiedades: Se recomienda que los modelos internos de valoración de la empresa prioricen como variables principales el área construida, el número de parqueaderos y baños, y el tipo de vivienda, por encima de la zona geográfica como criterio diferenciador de precio. Esto no significa ignorar la ubicación, sino reconocer que, dentro de esta base de datos, propiedades de características físicas similares tienden a tener precios comparables independientemente de en cuál de las cinco zonas se ubiquen.
Para la segmentación de portafolio y estrategia comercial: Se recomienda alinear la oferta y las campañas comerciales de la empresa con los cuatro segmentos identificados: (i) desarrollar y promocionar activamente el segmento de entrada, dado que concentra más de la mitad del mercado y probablemente el mayor volumen de transacciones; (ii) dirigir una oferta diferenciada de vivienda familiar (más habitaciones, menos parqueaderos) hacia compradores con necesidades de espacio antes que de comodidades vehiculares; (iii) explotar el segmento premium con una estrategia de marketing especializada, dado que representa una porción menor del volumen pero previsiblemente el mayor margen por transacción.
Para la expansión geográfica y prospección de nuevos inventarios: Dada la alta fragmentación de la oferta en barrios pequeños, se recomienda que la empresa priorice la consolidación de inventario en los barrios de mayor concentración ya identificados (Valle del Lili, Ciudad Jardín, Pance, y los barrios asociados a la Zona Oeste), donde existe mayor liquidez de mercado y comparabilidad de precios, antes que dispersar esfuerzos comerciales de forma homogénea en la totalidad de los cientos de barrios de la ciudad.
Para futuras iteraciones del análisis: Se recomienda ampliar este análisis incorporando variables adicionales no disponibles en el presente dataset (año de construcción, tiempo en el mercado, estado de la propiedad) que podrían explicar la variabilidad residual no capturada por los componentes principales actuales (cerca del 17% de la varianza no explicada por las dos primeras componentes), así como actualizar periódicamente el análisis de conglomerados para monitorear si la segmentación del mercado se mantiene estable o evoluciona con el tiempo, dado el carácter dinámico y competitivo del sector inmobiliario.