0.1 Problema

Una empresa inmobiliaria líder en una gran ciudad está buscando comprender en profundidad el mercado de viviendas urbanas para tomar decisiones estratégicas más informadas. La empresa posee una base de datos extensa que contiene información detallada sobre diversas propiedades residenciales disponibles en el mercado. Se requiere realizar un análisis de estos datos para identificar patrones, relaciones y segmentaciones relevantes que permitan mejorar la toma de decisiones en cuanto a la compra, venta y valoración de propiedades.

0.2 Base de Datos a Utilizar

# install.packages("gitcreds")
# gitcreds::gitcreds_delete("https://github.com")
# devtools::install_github("centromagis/paqueteMODELOS", force = TRUE)

library(paqueteMODELOS)
library(dplyr)
library(ggplot2)
library(corrplot)
library(mice)
library(cluster)
library(FactoMineR)
library(factoextra)
library(gridExtra)

set.seed(1234)
data("vivienda")
str(vivienda)
## spc_tbl_ [8,322 × 13] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ id          : num [1:8322] 1147 1169 1350 5992 1212 ...
##  $ zona        : chr [1:8322] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
##  $ piso        : chr [1:8322] NA NA NA "02" ...
##  $ estrato     : num [1:8322] 3 3 3 4 5 5 4 5 5 5 ...
##  $ preciom     : num [1:8322] 250 320 350 400 260 240 220 310 320 780 ...
##  $ areaconst   : num [1:8322] 70 120 220 280 90 87 52 137 150 380 ...
##  $ parqueaderos: num [1:8322] 1 1 2 3 1 1 2 2 2 2 ...
##  $ banios      : num [1:8322] 3 2 2 5 2 3 2 3 4 3 ...
##  $ habitaciones: num [1:8322] 6 3 4 3 3 3 3 4 6 3 ...
##  $ tipo        : chr [1:8322] "Casa" "Casa" "Casa" "Casa" ...
##  $ barrio      : chr [1:8322] "20 de julio" "20 de julio" "20 de julio" "3 de julio" ...
##  $ longitud    : num [1:8322] -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num [1:8322] 3.43 3.43 3.44 3.44 3.46 ...
##  - attr(*, "spec")=List of 3
##   ..$ cols   :List of 13
##   .. ..$ id          : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ zona        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ piso        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ estrato     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ preciom     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ areaconst   : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ parqueaderos: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ banios      : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ habitaciones: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ tipo        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ barrio      : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ longitud    : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ latitud     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   ..$ default: list()
##   .. ..- attr(*, "class")= chr [1:2] "collector_guess" "collector"
##   ..$ delim  : chr ";"
##   ..- attr(*, "class")= chr "col_spec"
##  - attr(*, "problems")=<externalptr>

La base de datos vivienda contiene información de oferta de propiedades residenciales en la ciudad, con variables de ubicación (zona, barrio, piso), características físicas (área construida, número de baños, habitaciones, parqueaderos), variables socioeconómicas (estrato) y comerciales (precio en millones).

1 Retos

El reto principal consiste en realizar un análisis integral y multidimensional de la base de datos para obtener una comprensión del mercado inmobiliario urbano. Antes de abordar las técnicas multivariadas como Componentes Principales, Conglomerados y Correspondencia, se realiza un proceso de entendimiento y preparacion de los datos, de forma que los resultados de los modelos se apoyen sobre información confiable.

1.1 Entendimiento de los datos

Antes de modelar, es necesario conocer el conjunto de datos disponible. Para ello se tuvo en cuenta lo siguiente:

  • Cuántos registros y atributos hay, y de qué tipo son.
  • Medidas de centralidad y dispersión para los atributos numéricos.
  • La matriz de correlación de los atributos numéricos, para identificar redundancia.
  • La presencia de datos atípicos.
  • La presencia y magnitud de datos faltantes.
  • El balance/distribución de las variables categóricas.

En esta etapa no se modifican los datos, solo se documentan sus características.

1.1.1 Dimensión y tipos de datos

dim(vivienda)
## [1] 8322   13
sapply(vivienda, class)
##           id         zona         piso      estrato      preciom    areaconst 
##    "numeric"  "character"  "character"    "numeric"    "numeric"    "numeric" 
## parqueaderos       banios habitaciones         tipo       barrio     longitud 
##    "numeric"    "numeric"    "numeric"  "character"  "character"    "numeric" 
##      latitud 
##    "numeric"

La base cuenta con 8.322 registros y 13 variables. Las variables id, estrato, preciom, areaconst, parqueaderos, banios, habitaciones, longitud y latitud son numéricas; zona, piso, tipo y barrio son de tipo texto (categóricas/nominales, aunque piso y estrato tienen un orden implícito).

Para el análisis se definen dos grupos de variables de interés:

num_vars <- c("estrato", "preciom", "areaconst", "parqueaderos", "banios", "habitaciones")
cat_vars <- c("zona", "tipo", "barrio")

1.1.2 Medidas de centralidad y dispersión

summary(vivienda[, num_vars])
##     estrato         preciom         areaconst       parqueaderos   
##  Min.   :3.000   Min.   :  58.0   Min.   :  30.0   Min.   : 1.000  
##  1st Qu.:4.000   1st Qu.: 220.0   1st Qu.:  80.0   1st Qu.: 1.000  
##  Median :5.000   Median : 330.0   Median : 123.0   Median : 2.000  
##  Mean   :4.634   Mean   : 433.9   Mean   : 174.9   Mean   : 1.835  
##  3rd Qu.:5.000   3rd Qu.: 540.0   3rd Qu.: 229.0   3rd Qu.: 2.000  
##  Max.   :6.000   Max.   :1999.0   Max.   :1745.0   Max.   :10.000  
##  NA's   :3       NA's   :2        NA's   :3        NA's   :1605    
##      banios        habitaciones   
##  Min.   : 0.000   Min.   : 0.000  
##  1st Qu.: 2.000   1st Qu.: 3.000  
##  Median : 3.000   Median : 3.000  
##  Mean   : 3.111   Mean   : 3.605  
##  3rd Qu.: 4.000   3rd Qu.: 4.000  
##  Max.   :10.000   Max.   :10.000  
##  NA's   :3        NA's   :3
data.frame(desv_estandar = sapply(vivienda[, num_vars], sd, na.rm = TRUE))
##              desv_estandar
## estrato           1.029222
## preciom         328.647244
## areaconst       142.964126
## parqueaderos      1.124909
## banios            1.428210
## habitaciones      1.459537

El precio (preciom) y el área construida (areaconst) son las variables con mayor dispersión relativa, coherente con un mercado que va desde vivienda económica hasta vivienda de lujo. El estrato se mueve entre 3 y 6 (no incluye estratos 1 y 2 en esta oferta).

1.1.3 Matriz de correlación

M <- cor(vivienda[, num_vars], use = "pairwise.complete.obs")
round(M, 2)
##              estrato preciom areaconst parqueaderos banios habitaciones
## estrato         1.00    0.61      0.27         0.42   0.42        -0.07
## preciom         0.61    1.00      0.69         0.69   0.67         0.26
## areaconst       0.27    0.69      1.00         0.58   0.65         0.52
## parqueaderos    0.42    0.69      0.58         1.00   0.57         0.28
## banios          0.42    0.67      0.65         0.57   1.00         0.59
## habitaciones   -0.07    0.26      0.52         0.28   0.59         1.00
corrplot(M, method = "color", addCoef.col = "black", type = "upper", tl.col = "black")

El precio se correlaciona de forma moderada-alta con área construida, parqueaderos y baños (todas alrededor de 0.6-0.7), lo cual es consistente con el mercado: propiedades más grandes y con más comodidades tienden a ser más costosas. No se observan correlaciones extremas (>0.9) que sugieran eliminar alguna variable por redundancia, por lo que se conservan todas las variables numéricas para el análisis.

1.1.4 Datos atípicos

par(mfrow = c(2, 3))
for (v in num_vars) {
  boxplot(vivienda[[v]], main = v, col = "#8FD3D3")
}

par(mfrow = c(1, 1))
data.frame(
  variable = num_vars,
  n_atipicos = sapply(num_vars, function(v) length(boxplot.stats(vivienda[[v]])$out))
)
##                  variable n_atipicos
## estrato           estrato          0
## preciom           preciom        552
## areaconst       areaconst        382
## parqueaderos parqueaderos        567
## banios             banios         72
## habitaciones habitaciones        888

Se identifican datos atípicos principalmente en preciom, areaconst y habitaciones. En un mercado inmobiliario real estos valores no necesariamente son errores: corresponden a propiedades de lujo o de gran tamaño, por lo que no se eliminan, ya que representan un segmento legítimo de la oferta (de hecho, se espera que el análisis de conglomerados los agrupe como un segmento diferenciado).

1.1.5 Datos faltantes

colSums(is.na(vivienda))
##           id         zona         piso      estrato      preciom    areaconst 
##            3            3         2638            3            2            3 
## parqueaderos       banios habitaciones         tipo       barrio     longitud 
##         1605            3            3            3            3            3 
##      latitud 
##            3
md.pattern(vivienda[, c("zona", "piso", "estrato", "preciom", "areaconst",
                         "parqueaderos", "banios", "habitaciones", "tipo", "barrio")],
           rotate.names = TRUE)

##      preciom zona estrato areaconst banios habitaciones tipo barrio
## 4808       1    1       1         1      1            1    1      1
## 1909       1    1       1         1      1            1    1      1
## 876        1    1       1         1      1            1    1      1
## 726        1    1       1         1      1            1    1      1
## 1          1    0       0         0      0            0    0      0
## 2          0    0       0         0      0            0    0      0
##            2    3       3         3      3            3    3      3
##      parqueaderos piso     
## 4808            1    1    0
## 1909            1    0    1
## 876             0    1    1
## 726             0    0    2
## 1               0    0    9
## 2               0    0   10
##              1605 2638 4266

Se observan tres situaciones distintas de datos faltantes:

  1. Registros prácticamente vacíos: 3 filas tienen valores faltantes simultáneos en casi todas las variables (id, zona, estrato, areaconst, banios, habitaciones, tipo, barrio, longitud, latitud). Representan un 0.04% de los datos.
  2. parqueaderos: 1.605 valores faltantes (19.3%), sin una relación clara con el tipo de vivienda.
  3. piso: 2.638 valores faltantes (31.7%), el porcentaje más alto de toda la base.

1.1.6 Distribución de variables categóricas

p_zona <- ggplot(vivienda, aes(x = zona)) + geom_bar(fill = "#0D3B66") +
  theme_minimal() + labs(title = "Oferta por zona", x = NULL, y = "Viviendas") +
  theme(axis.text.x = element_text(angle = 30, hjust = 1))
p_tipo <- ggplot(vivienda, aes(x = tipo)) + geom_bar(fill = "#F4D35E") +
  theme_minimal() + labs(title = "Oferta por tipo", x = NULL, y = "Viviendas")
p_estrato <- ggplot(vivienda, aes(x = factor(estrato))) + geom_bar(fill = "#EE6C4D") +
  theme_minimal() + labs(title = "Oferta por estrato", x = "Estrato", y = "Viviendas")
gridExtra::grid.arrange(p_zona, p_tipo, p_estrato, ncol = 3)

La oferta está fuertemente concentrada en la Zona Sur (más de la mitad de los registros), predominan los apartamentos sobre las casas, y el estrato 5 es el más frecuente. La base no incluye viviendas de estratos 1 y 2, por lo que las conclusiones aplican al segmento medio y alto del mercado.

1.2 Preparación de los datos

De acuerdo con lo observado en la etapa anterior, se define la siguiente secuencia de actividades:

  1. Eliminar los 3 registros prácticamente vacíos (pérdida de información despreciable, <0.1%).
  2. Estandarizar el texto de zona (se retira el prefijo “Zona”) y convertir zona y tipo a factor.
  3. Imputar parqueaderos con la mediana, dado que su ausencia no está asociada de forma clara al tipo de vivienda (no se puede asumir que sea homogéneamente “cero parqueaderos”).
  4. Conservar piso sin imputar: con 31.7% de datos faltantes, cualquier imputación introduciría un sesgo importante; además no es una variable requerida en los tres retos de esta actividad (PCA, conglomerados y correspondencia), por lo que se excluye del análisis cuantitativo y solo se documenta como limitación de la base.
  5. Retirar longitud y latitud: son coordenadas geográficas que, sin un componente explícito de análisis espacial (mapas), no aportan información interpretable por sí solas para PCA, conglomerados o correspondencia; la ubicación ya queda representada por la variable categórica zona.
  6. Conservar los datos atípicos identificados (no se eliminan ni se capan), ya que corresponden a un segmento real y relevante del mercado (vivienda de alto valor).
core <- c("zona", "estrato", "preciom", "areaconst", "banios", "habitaciones",
          "tipo", "barrio", "longitud", "latitud")
viv <- vivienda[rowSums(is.na(vivienda[, core])) == 0, ]

viv$zona <- factor(gsub("^Zona ", "", trimws(viv$zona)))
viv$tipo <- factor(viv$tipo)

viv$parqueaderos[is.na(viv$parqueaderos)] <- median(viv$parqueaderos, na.rm = TRUE)

viv$longitud <- NULL
viv$latitud <- NULL

dim(viv)
## [1] 8319   11
colSums(is.na(viv))
##           id         zona         piso      estrato      preciom    areaconst 
##            0            0         2635            0            0            0 
## parqueaderos       banios habitaciones         tipo       barrio 
##            0            0            0            0            0

La base final de trabajo queda con 8319 registros, prácticamente sin pérdida de información frente a los 8.322 originales, y sin datos faltantes en las variables que se usarán en los tres analisis.

2 1. Análisis de Componentes Principales

Objetivo: reducir la dimensionalidad del conjunto de datos y visualizar la estructura de las variables cuantitativas en componentes principales, para identificar las características clave que explican la variación de precios y oferta del mercado.

Se utilizan únicamente las variables cuantitativas puras de la vivienda (precio, área construida, parqueaderos, baños y habitaciones). Se deja fuera estrato: aunque está codificado como número, es en realidad una categoría socioeconómica ordinal, por lo que se reserva más adelante como variable de apoyo para interpretar los componentes y los conglomerados, en vez de usarla como insumo activo del PCA.

pca_vars <- c("preciom", "areaconst", "parqueaderos", "banios", "habitaciones")
Xz <- scale(viv[, pca_vars])

res.pca <- prcomp(Xz)
summary(res.pca)
## Importance of components:
##                           PC1    PC2     PC3     PC4     PC5
## Standard deviation     1.7690 0.9399 0.66884 0.58862 0.43952
## Proportion of Variance 0.6259 0.1767 0.08947 0.06929 0.03863
## Cumulative Proportion  0.6259 0.8026 0.89207 0.96137 1.00000
res.pca$rotation
##                    PC1         PC2        PC3         PC4         PC5
## preciom      0.4743947  0.37561823  0.4283944 -0.11301216  0.66148860
## areaconst    0.4895828 -0.03382407  0.2492969  0.75082122 -0.36507963
## parqueaderos 0.4080975  0.49793007 -0.7590785 -0.03532650 -0.08985529
## banios       0.4897691 -0.18974131  0.2284402 -0.64791742 -0.50213802
## habitaciones 0.3589444 -0.75751618 -0.3548862  0.04950563  0.41101476

2.1 Elección del número de componentes

fviz_eig(res.pca, addlabels = TRUE)

El primer componente principal explica el 62.6% de la variabilidad de la base, y entre los dos primeros se alcanza el 80.3%. Esto indica que dos componentes son suficientes para resumir de forma adecuada la variación de la oferta inmobiliaria en cuanto a tamaño y precio.

2.2 Estructura de las variables

fviz_pca_var(res.pca,
             col.var = "contrib",
             gradient.cols = c("#FF7F00", "#034D94"),
             repel = TRUE)

contrib_pct <- round(get_pca_var(res.pca)$contrib, 2)
contrib_pct
##              Dim.1 Dim.2 Dim.3 Dim.4 Dim.5
## preciom      22.51 14.11 18.35  1.28 43.76
## areaconst    23.97  0.11  6.21 56.37 13.33
## parqueaderos 16.65 24.79 57.62  0.12  0.81
## banios       23.99  3.60  5.22 41.98 25.21
## habitaciones 12.88 57.38 12.59  0.25 16.89

La tabla anterior muestra la contribución porcentual (%) de cada variable a cada componente, calculada con get_pca_var(res.pca)$contrib (por construcción, cada columna suma 100%). Se observa que sobre CP1 las cinco variables aportan de forma relativamente pareja, mientras que sobre CP2 la contribución se concentra principalmente en habitaciones, seguida de parqueaderos y preciom.

Las cinco variables apuntan en un sentido similar sobre el primer componente (CP1), con contribuciones muy parejas (preciom, areaconst y banios con cargas ~0.47-0.49; parqueaderos 0.41; habitaciones 0.36). Esto confirma que CP1 puede interpretarse como un eje general de “tamaño/valor de la propiedad”: a mayor valor de CP1, propiedades más grandes, más costosas y con más comodidades.

El segundo componente (CP2) opone habitaciones (carga -0.76) a parqueaderos y preciom (cargas positivas), por lo que se interpreta como un eje que diferencia propiedades con muchas habitaciones pero relativamente más económicas/pequeñas (valores negativos de CP2) frente a propiedades de menos habitaciones pero más parqueaderos y valor (valores positivos de CP2).

2.3 Visualización de los individuos

fviz_pca_ind(res.pca, geom = "point", habillage = viv$tipo,
             alpha.ind = 0.15, pointsize = 0.8) +
  labs(title = "Individuos en el plano de Componentes Principales")

Se observa que las casas tienden a ubicarse hacia valores más altos de CP1 (mayor área/valor) que los apartamentos, aunque con una zona que se sobreponen considerable.

3 2. Análisis de Conglomerados

Objetivo: agrupar las propiedades en segmentos homogéneos, para entender las dinámicas de la oferta en las distintas zonas de la ciudad y en los distintos estratos socioeconómicos.

Se trabaja sobre las mismas variables estandarizadas usadas en el PCA (preciom, areaconst, parqueaderos, banios, habitaciones). Dado que la base tiene más de 8.000 registros, se utiliza k-means como método principal (escalable a todo el conjunto de datos), complementado con un dendrograma ilustrativo sobre una muestra pequeña para visualizar el mecanismo de agrupamiento jerárquico.

3.1 Elección del número de conglomerados

Para elegir el número de conglomerados se calcula de forma explícita el índice de Silhouette: para cada k entre 2 y 8 se ajusta un k-means (sobre una muestra de 600 registros, por costo computacional) y se calcula, con silhouette() del paquete cluster, el ancho de silhouette de cada observación respecto a su propio clúster frente al clúster vecino más cercano; el índice reportado para cada k es el promedio de esos anchos individuales.

set.seed(1234)
idx_sil <- sample(nrow(Xz), 600)
Xz_sil <- Xz[idx_sil, ]

sil_avg <- sapply(2:8, function(k) {
  km_k <- kmeans(Xz_sil, centers = k, nstart = 5)
  ss <- silhouette(km_k$cluster, dist(Xz_sil))
  mean(ss[, "sil_width"])
})

sil_tab <- data.frame(k = 2:8, silhouette_promedio = round(sil_avg, 4))
sil_tab
##   k silhouette_promedio
## 1 2              0.4987
## 2 3              0.4398
## 3 4              0.3905
## 4 5              0.3783
## 5 6              0.2565
## 6 7              0.2921
## 7 8              0.2914
k_optimo <- sil_tab$k[which.max(sil_tab$silhouette_promedio)]
ggplot(sil_tab, aes(x = k, y = silhouette_promedio)) +
  geom_line(color = "#034D94", linewidth = 1) +
  geom_point(size = 2, color = "#034D94") +
  geom_vline(xintercept = k_optimo, linetype = "dashed", color = "#EE6C4D") +
  theme_minimal() +
  labs(title = "Índice de Silhouette (muestra n=600)",
       x = "Número de conglomerados (k)", y = "Ancho de Silhouette promedio")

El ancho de silhouette promedio más alto se obtiene en k = 2 (0.4987), seguido de cerca por k=3 (0.4398). Esto es un resultado esperable en un mercado inmobiliario real: como ya se observó en el entendimiento de los datos (propiedades de lujo/gran tamaño conviviendo con vivienda económica), la oferta forma más un continuo de tamaño/precio que grupos naturalmente muy separados, por lo que el índice de Silhouette premia soluciones con pocos clústeres muy amplios.

Sin embargo, un resultado de solo 2 grupos tiene poco valor práctico para la empresa, ya que no permite diferenciar suficientemente la estrategia comercial por segmento. Se opta entonces por k=4: su ancho de Silhouette (0.3905) sigue siendo razonable y claramente superior al de soluciones con k≥6 (que caen por debajo de 0.30), por lo que constituye un buen equilibrio entre cohesión estadística y granularidad útil para la segmentación comercial.

3.1.1 Confirmación con dendrograma jerárquico

Como evidencia adicional a la elección de k=4, se construye un dendrograma jerárquico (distancia euclidiana, método de Ward) sobre una muestra pequeña de viviendas (n=30), que permite visualizar directamente el mecanismo de fusión de las observaciones y contrastarlo con el número de grupos elegido:

set.seed(1234)
samp <- viv[sample(nrow(viv), 30), pca_vars]
samp_z <- scale(samp)
rownames(samp_z) <- paste0("V", 1:nrow(samp_z))

d <- dist(samp_z, method = "euclidean")
hc <- hclust(d, method = "ward.D2")
plot(hc, main = "Dendrograma (muestra de 30 viviendas)", xlab = "", sub = "")
rect.hclust(hc, k = 4, border = "#034D94")

alturas <- sort(hc$height, decreasing = TRUE)[1:6]
alturas_tab <- data.frame(k_al_cortar = 2:7, altura_de_fusion = round(alturas, 2))
alturas_tab$salto_respecto_al_siguiente_corte <- c(round(-diff(alturas_tab$altura_de_fusion), 2), NA)
alturas_tab
##   k_al_cortar altura_de_fusion salto_respecto_al_siguiente_corte
## 1           2            11.11                              3.86
## 2           3             7.25                              1.61
## 3           4             5.64                              0.83
## 4           5             4.81                              0.92
## 5           6             3.89                              0.77
## 6           7             3.12                                NA

La tabla anterior muestra, para cada número de grupos k al cortar el árbol, la altura a la que ocurrió la fusión que redujo el número de clústeres a k-1, junto con el salto respecto al siguiente corte. Al cortar en k=4 (línea punteada en el gráfico), se obtienen 4 ramas claramente diferenciadas y de tamaño razonable, sin que se genere un grupo trivial de una sola observación (algo que sí ocurre si se sigue subdividiendo hacia valores de k más altos). Esto complementa la evidencia del índice de Silhouette: aunque estadísticamente la cohesión más alta se logra con 2-3 grupos muy amplios, la estructura jerárquica muestra que 4 grupos siguen siendo una partición natural y estable de los datos, no una subdivisión forzada, lo que refuerza la elección de k=4 como número de conglomerados.

3.2 Segmentación con k-means (k=4)

set.seed(1234)
km <- kmeans(Xz, centers = 4, nstart = 25)
table(km$cluster)
## 
##    1    2    3    4 
##  650  810 4490 2369
fviz_cluster(km, data = Xz, geom = "point", ellipse.type = "convex",
             alpha = 0.3, pointsize = 0.8) +
  labs(title = "Conglomerados sobre el plano de Componentes Principales")

3.3 Perfil de los conglomerados

viv$cluster <- factor(km$cluster)

perfil <- viv %>%
  group_by(cluster) %>%
  summarise(
    n = n(),
    estrato_prom = round(mean(estrato), 2),
    precio_prom_M = round(mean(preciom), 0),
    area_prom_m2 = round(mean(areaconst), 0),
    parqueaderos_prom = round(mean(parqueaderos), 2),
    banios_prom = round(mean(banios), 2),
    habitaciones_prom = round(mean(habitaciones), 2)
  )
perfil
## # A tibble: 4 × 8
##   cluster     n estrato_prom precio_prom_M area_prom_m2 parqueaderos_prom
##   <fct>   <int>        <dbl>         <dbl>        <dbl>             <dbl>
## 1 1         650         3.98           469          322              1.86
## 2 2         810         5.7           1160          426              3.93
## 3 3        4490         4.27           239           91              1.42
## 4 4        2369         5.14           545          208              2.01
## # ℹ 2 more variables: banios_prom <dbl>, habitaciones_prom <dbl>
table(Cluster = viv$cluster, Zona = viv$zona)
##        Zona
## Cluster Centro Norte Oeste Oriente  Sur
##       1     35   142    36     124  313
##       2      2    82   224       4  498
##       3     62  1224   377     184 2643
##       4     25   472   561      39 1272
table(Cluster = viv$cluster, Tipo = viv$tipo)
##        Tipo
## Cluster Apartamento Casa
##       1           9  641
##       2         226  584
##       3        3725  765
##       4        1140 1229

A partir del perfil promedio y del cruce con zona, tipo y estrato, los cuatro conglomerados podrian interpretarse de la siguiente manera:

  • Clúster 1 – Casas que se pueden considerar grandes de estrato medio (650 propiedades, 7.8%): estrato más bajo del grupo (3.98), pero con el mayor número de habitaciones (7.17) y un área considerable (322 m²) a un precio medio ($469M); son casi todo casas (641 de 650). Sugiere viviendas grandes, posiblemente multifamiliares o subdivididas, en estratos medios.
  • Clúster 2 – Vivienda premium (810 propiedades, 9.7%): estrato promedio más alto (5.7), el precio ($1.160M) y el área (426 m²) más altos de todos los grupos, con más baños y parqueaderos. Se concentra en la Zona Sur y, en menor medida, Zona Oeste.
  • Clúster 3 – Vivienda económica/ en su mayoria apartamentos (4.490 propiedades, 54%** de la oferta)**: el grupo más grande, con el precio ($239M) y área (91 m²) más bajos, y el menor número de baños y habitaciones. Es mayoritariamente apartamentos (3.725 de 4.490), concentrado en Zona Norte y Zona Sur. Representa el segmento masivo del mercado.
  • Clúster 4 – Vivienda estrato medio-alto balanceada aptos/casas (2.369 propiedades, 28.5%): estrato 5.14, precio y área intermedios ($545M, 208 m²), con una mezcla equilibrada de apartamentos y casas, presente sobre todo en Zona Sur y Zona Oeste.

Esta segmentación permite a la empresa diferenciar su estrategia comercial: el clúster 3 (masivo/económico) requiere volumen y eficiencia operativa, mientras que el clúster 1 (premium) requiere un servicio más especializado y de mayor margen por unidad. Esta lógica de agrupamiento es consistente con la que ya se anticipó en el dendrograma jerárquico de la sección anterior: las propiedades se fusionan primero por similitud de tamaño/precio, y los 4 segmentos resultantes de k-means sobre la totalidad de los datos son coherentes con las 4 ramas identificadas allí sobre la muestra pequeña.

4 3. Análisis de Correspondencia

Objetivo: examinar la relación entre las variables categóricas tipo de vivienda y zona, para identificar patrones de comportamiento de la oferta en el mercado inmobiliario.

Se deja fuera del análisis la variable barrio: aunque describe la ubicación de la propiedad, es en la práctica un identificador nominal de 437 categorías (un nombre propio de lugar), sin una relación analítica propia más allá de estar geográficamente contenido dentro de una zona. Cruzarla con zona y tipo no revela una asociación conceptualmente distinta a la que ya captura zona (una variable con solo 5 categorías, pensada precisamente para resumir la ubicación), y sí introduce ruido dimensional y una tabla de contingencia dispersa y difícil de interpretar. Por eso el análisis de correspondencia se concentra en la relación zona vs. tipo, que es la que aporta valor comercial directo (dónde predomina cada tipo de vivienda).

4.1 Zona vs. Tipo de vivienda

tab_zt <- table(viv$zona, viv$tipo)
tab_zt
##          
##           Apartamento Casa
##   Centro           24  100
##   Norte          1198  722
##   Oeste          1029  169
##   Oriente          62  289
##   Sur            2787 1939
chisq.test(tab_zt)
## 
##  Pearson's Chi-squared test
## 
## data:  tab_zt
## X-squared = 690.93, df = 4, p-value < 2.2e-16

La prueba Chi-cuadrado rechaza la hipótesis de independencia (p-value < 2.2e-16): la zona y el tipo de vivienda sí están relacionados.

res.ca <- CA(tab_zt, graph = FALSE)
res.ca$eig
##       eigenvalue percentage of variance cumulative percentage of variance
## dim 1 0.08305442                    100                               100

El cálculo formal del análisis de correspondencias confirma lo esperado: como tipo solo tiene dos categorías, la tabla zona x tipo tiene rango min(5-1, 2-1) = 1, y la primera (única) dimensión concentra el 100% de la inercia. En un caso así, un mapa de dos ejes no aporta información adicional, por lo que el resultado se interpreta mejor con las proporciones directamente:

prop_zt <- round(prop.table(tab_zt, margin = 1) * 100, 1)
prop_zt
##          
##           Apartamento Casa
##   Centro         19.4 80.6
##   Norte          62.4 37.6
##   Oeste          85.9 14.1
##   Oriente        17.7 82.3
##   Sur            59.0 41.0
df_zt <- as.data.frame(prop_zt)
names(df_zt) <- c("zona", "tipo", "porcentaje")
ggplot(df_zt, aes(x = zona, y = porcentaje, fill = tipo)) +
  geom_col(position = "stack") +
  scale_fill_manual(values = c("#0D3B66", "#F4D35E")) +
  theme_minimal() +
  labs(title = "Composición de la oferta por zona", y = "% dentro de la zona", x = NULL)

La Zona Oeste es predominantemente de apartamentos (85.9%), mientras que Centro y Oriente son predominantemente de casas (80.6% y 82.3% respectivamente). Norte y Sur presentan una mezcla más equilibrada, con leve mayoría de apartamentos.

5 4. Conclusiones y Recomendaciones

  • Componentes Principales: la oferta inmobiliaria puede resumirse en dos ejes: un eje de tamaño/valor (CP1, 62.6% de la varianza) que combina precio, área, baños y parqueaderos, y un eje secundario (CP2, 17.7%) que contrasta número de habitaciones frente a valor/parqueaderos. Entre ambos se explica el 80.3% de la variabilidad de la oferta.
  • Conglomerados: existen 4 segmentos claros de mercado: vivienda premium (9.7%), casas grandes de estrato medio (7.8%), vivienda económica/masiva (54%, principalmente apartamentos) y vivienda de estrato medio-alto balanceada (28.5%). Cada segmento tiene una localización y composición (casa/apartamento) diferenciada.
  • Correspondencia: existe una relación significativa entre zona y tipo de vivienda (Chi-cuadrado, p-value < 2.2e-16), que además es unidimensional (el análisis de correspondencia confirma que una sola dimensión concentra el 100% de la inercia). La Zona Oeste se asocia predominantemente con apartamentos (85.9%), Centro y Oriente con casas (80.6% y 82.3%), mientras que Norte y Sur presentan una oferta más equilibrada. Se excluyó barrio del análisis por ser un identificador nominal de 437 categorías sin relación analítica propia, ya contenido geográficamente en zona.

5.1 Recomendaciones para la Inmobiliaria

  1. Segmentar la fuerza comercial y el portafolio de servicios según los 4 clústeres identificados: atención especializada y mayor margen para el segmento premium (clúster 1), y foco en volumen/eficiencia para el segmento económico (clúster 3), que concentra más de la mitad de la oferta.
  2. Adaptar la oferta por zona según el tipo dominante: reforzar la oferta de apartamentos en Zona Oeste y de casas en Centro/Oriente, en línea con el patrón de mercado ya consolidado en esos sectores.
  3. Usar el eje CP1 (tamaño/valor) como un indicador rápido de posicionamiento de una propiedad dentro del mercado al momento de fijar precios de venta, complementado por el estrato como variable de validación.
  4. Mejorar la calidad de captura de datos, en particular de piso (31.7% faltante) y parqueaderos (19.3% faltante), ya que limitan el detalle de análisis futuros, especialmente para el segmento de apartamentos donde el piso es una variable comercialmente relevante.