# =========================
# Paquetes
# =========================

library(readxl)
library(dplyr)
library(ggplot2)
library(FactoMineR)
library(factoextra)
library(missMDA)
library(cluster)
library(scales)
library(forcats)
library(tidyr)
library(knitr)
library(rmdwc) 
library(paqueteMODELOS)

1 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 holístico 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.

2 2. Resumen

Este documento presenta un análisis de datos del mercado inmobiliario con el objetivo de apoyar decisiones estratégicas en una empresa inmobiliaria. Se desarrollan cuatro líneas principales:

  1. Análisis de Componentes Principales (ACP/PCA): reducir dimensionalidad e interpretar la estructura de variación de variables numéricas, conectándolas con el comportamiento del precio.
  2. Análisis de Conglomerados: segmentar propiedades residenciales en grupos homogéneos y perfilar su comportamiento por zona y estrato.
  3. Análisis de Correspondencia (MCA): estudiar asociaciones entre variables categóricas (tipo, zona, barrio) para identificar patrones de oferta.
  4. Visualización: mapas y gráficos para comunicar hallazgos de forma clara.

3 3. Objetivos

3.1 3.1 Objetivo general

Proveer un análisis descriptivo y multivariado que permita entender la estructura del mercado inmobiliario y segmentar la oferta para orientar decisiones estratégicas.

3.2 3.2 Objetivos específicos

  • ACP/PCA: identificar combinaciones de variables que explican la mayor variación en las características del inmueble y evaluar su relación con el precio.
  • Conglomerados: agrupar inmuebles en segmentos homogéneos y describir perfiles (precio, área, estrato, dotación).
  • Correspondencia (MCA): analizar relaciones entre tipo–zona–barrio para reconocer patrones de oferta.
  • Visualización: construir gráficos y mapas que faciliten la comunicación a dirección.

4 4. Datos

A continuacion vamos a cargar nuestra base de datos anteriormente descargada como un archivo .xlsx, dado que tuvimos problemas al intentar extraerla del paqueteMertodos sugerido por la Universidad. Posterior a la carga de la bse de datos, se realiza un data frame para observar una parte de nuestros datos.

# =========================
# Cargamos nuestro data set
# =========================
vivienda <- read_excel("vivienda.xlsx")

# Validación mínima de columnas 
req <- c("id","zona","piso","estrato","preciom","areaconst","parqueaderos",
         "banios","habitaciones","tipo","barrio","longitud","latitud")
missing_cols <- setdiff(req, names(vivienda))
if(length(missing_cols) > 0){
  stop(paste("Faltan columnas en el Excel:", paste(missing_cols, collapse=", ")))
}

dim(vivienda)
## [1] 8322   13
head(vivienda, 10)
## # A tibble: 10 × 13
##       id zona   piso  estrato preciom areaconst parqueaderos banios habitaciones
##    <dbl> <chr>  <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
##  1  1147 Zona … <NA>        3     250        70            1      3            6
##  2  1169 Zona … <NA>        3     320       120            1      2            3
##  3  1350 Zona … <NA>        3     350       220            2      2            4
##  4  5992 Zona … 02          4     400       280            3      5            3
##  5  1212 Zona … 01          5     260        90            1      2            3
##  6  1724 Zona … 01          5     240        87            1      3            3
##  7  2326 Zona … 01          4     220        52            2      2            3
##  8  4386 Zona … 01          5     310       137            2      3            4
##  9  1209 Zona … 02          5     320       150            2      4            6
## 10  1592 Zona … 02          5     780       380            2      3            3
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>

5 5. Calidad de datos y preprocesamiento

5.1 5.1 Eliminación de filas no informativas

Se eliminan filas casi vacías y se conservan filas con preciom disponible.

v <- vivienda %>%
  filter(rowSums(is.na(.)) <= 5) %>%     # elimina filas casi vacías
  filter(!is.na(preciom))                # precio requerido para perfiles

dim(v)
## [1] 8319   13

5.2 5.2 Estandarización de tipos (evita errores posteriores)

En el preprocesamiento se realizó una estandarización de tipos con el fin de evitar errores en cálculos y modelos posteriores (por ejemplo, medianas, distancias, ACP/MCA y clustering). Para ello:

  • Las variables categóricas (zona, tipo, barrio) se convirtieron a tipo factor.Además, valores vacíos o faltantes se recodificaron como la categoría “Desconocido”, garantizando que las observaciones con información incompleta sigan siendo utilizables en análisis categóricos.
  • Las variables numéricas (id, piso, estrato, preciom, areaconst, parqueaderos, banios, habitaciones, longitud, latitud) se convirtieron a tipo numeric. Cualquier valor no numérico o infinito se trató como faltante (NA) para prevenir fallas en operaciones aritméticas y procedimientos multivariados.

Regla de Transformacion

Para una variable categorica C: \[ C'=\mathrm{factor}\left( \begin{cases} \text{``Desconocido''}, & \text{si } C \text{ es NA o } C=\text{``''} \\ C, & \text{en otro caso} \end{cases} \right) \] Para una variable numerica X: \[ X'= \begin{cases} \text{NA}, & \text{si } X \in \{+\infty,-\infty\}\ \text{o no es convertible a numérico} \\ \mathrm{as.numeric}(X), & \text{en otro caso} \end{cases} \]

# Categóricas a factor
v <- v %>%
  mutate(
    zona   = as.factor(ifelse(is.na(zona)  | zona=="",  "Desconocido", as.character(zona))),
    tipo   = as.factor(ifelse(is.na(tipo)  | tipo=="",  "Desconocido", as.character(tipo))),
    barrio = as.factor(ifelse(is.na(barrio)| barrio=="","Desconocido", as.character(barrio)))
  )

# Numéricas a numeric 
num_cols <- c("id","piso","estrato","preciom","areaconst","parqueaderos",
              "banios","habitaciones","longitud","latitud")
for(col in num_cols){
  v[[col]] <- suppressWarnings(as.numeric(v[[col]]))
  v[[col]][is.infinite(v[[col]])] <- NA
}

str(v)
## tibble [8,319 × 13] (S3: tbl_df/tbl/data.frame)
##  $ id          : num [1:8319] 1147 1169 1350 5992 1212 ...
##  $ zona        : Factor w/ 5 levels "Zona Centro",..: 4 4 4 5 2 2 2 2 2 2 ...
##  $ piso        : num [1:8319] NA NA NA 2 1 1 1 1 2 2 ...
##  $ estrato     : num [1:8319] 3 3 3 4 5 5 4 5 5 5 ...
##  $ preciom     : num [1:8319] 250 320 350 400 260 240 220 310 320 780 ...
##  $ areaconst   : num [1:8319] 70 120 220 280 90 87 52 137 150 380 ...
##  $ parqueaderos: num [1:8319] 1 1 2 3 1 1 2 2 2 2 ...
##  $ banios      : num [1:8319] 3 2 2 5 2 3 2 3 4 3 ...
##  $ habitaciones: num [1:8319] 6 3 4 3 3 3 3 4 6 3 ...
##  $ tipo        : Factor w/ 2 levels "Apartamento",..: 2 2 2 2 1 1 1 1 2 2 ...
##  $ barrio      : Factor w/ 436 levels "20 de julio",..: 1 1 1 2 3 3 3 3 3 3 ...
##  $ longitud    : num [1:8319] -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num [1:8319] 3.43 3.43 3.44 3.44 3.46 ...

5.3 5.3 Diagnóstico de valores faltantes

Calculamos el porcentaje de valores faltantes de la siguiente manera: \[ NA(X) = \dfrac{\sum_{i=1}^{n}1(x_i\; es \; NA)}{n}*100 \] Donde;

  • \(X = x_1,x_2,...,x_n\) es una variable con \(n\) observaciones.
  • 1 es una funcion indicadora que toma el valor de 1 si \(x_i\) es un valor faltante (NA) y 0 en caso contrario.

Posterior al calculo realizamos una tabla con los resultados, la cual se presenta acontinuacion.

na_prop <- sapply(v, function(x) mean(is.na(x)))
na_tbl <- data.frame(variable = names(na_prop), prop_na = as.numeric(na_prop)) %>%
  arrange(desc(prop_na))

kable(na_tbl, digits = 4, caption = "Proporción de valores faltantes por variable")
Proporción de valores faltantes por variable
variable prop_na
piso 0.3167
parqueaderos 0.1926
id 0.0000
zona 0.0000
estrato 0.0000
preciom 0.0000
areaconst 0.0000
banios 0.0000
habitaciones 0.0000
tipo 0.0000
barrio 0.0000
longitud 0.0000
latitud 0.0000
na_tbl2 <- na_tbl %>% filter(prop_na > 0)

ggplot(na_tbl2, aes(x = reorder(variable, prop_na), y = prop_na)) +
  geom_col() +
  coord_flip() +
  scale_y_continuous(labels = percent_format()) +
  labs(title="Valores faltantes por variable",
       x="Variable", y="% de faltantes")

5.4 5.4 Imputación

Con el fin de conservar la mayor cantidad posible de observaciones y garantizar la correcta ejecución de los métodos multivariados, se implementó una estrategia de imputación diferenciada según el tipo de análisis y la naturaleza de las variables.

  • Para el Análisis de Componentes Principales (ACP/PCA), que se basa exclusivamente en variables numéricas, se aplicó imputación por mediana en aquellas variables que presentaban valores faltantes.

\[ x_i'= \begin{cases} \mathrm{Mediana}(X_{\text{obs}}), & \text{si } x_i \text{ es NA} \\ x_i, & \text{en otro caso} \end{cases} \] donde \(X_{obs}\) es el conjunto de valores observafdos no faltantes de la misma variable \(X\).

  • Para el Análisis de conglomerados (clustering), donde se integran variables mixtas (numéricas y categóricas), se utilizó un método de imputación más robusto basado en la estructura multivariada del conjunto de datos mediante missMDA::imputeFAMD(). \[ \hat{X} \approx U{\scriptstyle\sum} V^T \]

Adicionalmente, se crearon indicadores de faltantes para variables clave como piso y parqueaderos (por ejemplo, piso_miss y parq_miss). Estos indicadores toman valor 1 cuando el dato está ausente y 0 cuando está presente.

# Indicadores de faltantes
v <- v %>%
  mutate(
    piso_miss = as.integer(is.na(piso)),
    parq_miss = as.integer(is.na(parqueaderos))
  )

med_safe <- function(x){
  x <- suppressWarnings(as.numeric(x))
  if(all(is.na(x))) return(NA_real_)
  median(x, na.rm = TRUE)
}

# Imputación simple para análisis generales
v$piso[is.na(v$piso)] <- med_safe(v$piso)
v$parqueaderos[is.na(v$parqueaderos)] <- med_safe(v$parqueaderos)

6 6. Análisis de Componentes Principales (ACP / PCA)

6.1 6.1 Matriz numérica para ACP

Se seleccionaron las variables numéricas para el ACP y se imputaron los valores faltantes con la mediana de cada variable, garantizando una matriz completa y robusta frente a outliers

X_num <- v %>%
  select(estrato, latitud, longitud, areaconst, banios, habitaciones, parqueaderos, piso) %>%
  mutate(across(everything(), ~ifelse(is.na(.), med_safe(.), .)))

summary(X_num)
##     estrato         latitud         longitud        areaconst     
##  Min.   :3.000   Min.   :3.333   Min.   :-76.59   Min.   :  30.0  
##  1st Qu.:4.000   1st Qu.:3.381   1st Qu.:-76.54   1st Qu.:  80.0  
##  Median :5.000   Median :3.416   Median :-76.53   Median : 123.0  
##  Mean   :4.634   Mean   :3.418   Mean   :-76.53   Mean   : 174.9  
##  3rd Qu.:5.000   3rd Qu.:3.452   3rd Qu.:-76.52   3rd Qu.: 229.0  
##  Max.   :6.000   Max.   :3.498   Max.   :-76.46   Max.   :1745.0  
##      banios        habitaciones     parqueaderos         piso       
##  Min.   : 0.000   Min.   : 0.000   Min.   : 1.000   Min.   : 1.000  
##  1st Qu.: 2.000   1st Qu.: 3.000   1st Qu.: 1.000   1st Qu.: 2.000  
##  Median : 3.000   Median : 3.000   Median : 2.000   Median : 3.000  
##  Mean   : 3.111   Mean   : 3.605   Mean   : 1.867   Mean   : 3.527  
##  3rd Qu.: 4.000   3rd Qu.: 4.000   3rd Qu.: 2.000   3rd Qu.: 4.000  
##  Max.   :10.000   Max.   :10.000   Max.   :10.000   Max.   :12.000

6.2 6.2 Ajuste del ACP

Con la matriz numérica preprocesada, se ajustó un Análisis de Componentes Principales (ACP). El análisis se realizó con centrado y escalado de las variables, de modo que todas las variables contribuyan de forma comparable independientemente de su unidad o magnitud. Posteriormente, se calculó la proporción de varianza explicada por cada componente principal y su acumulado, con el fin de determinar cuántos componentes capturan la mayor parte de la variabilidad del conjunto de datos.

El procedimiento realizado para obtener la tabla fue le siguiente: \[ VarExp(PC_k)=\dfrac{\lambda_k}{\sum_{j=1}^{p}\lambda_j} \] donde:

  • \(\lambda_k\) es el autovalor asociadp al componente \(PC_k\).
  • \(P\) es el numero de variables.

Y el acumulado corresponde a: \[ VarAcum(m)=\sum_{k=1}^{n}VarExp(PC_k) \]

pca <- prcomp(X_num, center = TRUE, scale. = TRUE)

eig <- (pca$sdev^2) / sum(pca$sdev^2)
eig_df <- data.frame(
  PC = paste0("PC", seq_along(eig)),
  var_exp = eig,
  var_acum = cumsum(eig)
)

kable(head(eig_df, 10), digits=4, caption="Varianza explicada (primeros 10 componentes)")
Varianza explicada (primeros 10 componentes)
PC var_exp var_acum
PC1 0.3543 0.3543
PC2 0.1930 0.5473
PC3 0.1258 0.6731
PC4 0.0979 0.7710
PC5 0.0905 0.8615
PC6 0.0640 0.9255
PC7 0.0454 0.9709
PC8 0.0291 1.0000

En la Tabla se presenta la proporción de varianza explicada por los primeros componentes principales. Se observa que PC1 explica el 35.43% de la variabilidad total y PC2 explica el 19.30%, por lo que los dos primeros componentes acumulan 54.73%. Esto sugiere que una representación en dos dimensiones captura una parte sustancial de la información del conjunto de variables, aunque aún queda variabilidad relevante en componentes posteriores

6.2.1 Scree plot

El gráfico de sedimentación muestra la proporción de varianza explicada por cada componente principal. Se evidencia una caída pronunciada en la varianza explicada desde el primer componente (35.4%) al segundo (19.3%) y al tercero (12.6%). A partir del cuarto componente, los aportes individuales son menores (alrededor de 10% o menos), lo que sugiere un punto de ‘codo’ en los primeros componentes.

fviz_eig(pca, addlabels = TRUE) +
  labs(title = "ACP: Varianza explicada por componente")

6.3 6.3 Variables que más influyen

En el círculo de correlaciones del ACP se observa que las variables de tamaño y dotación (areaconst, habitaciones, baños y parqueaderos) apuntan en una dirección similar, indicando asociación positiva entre ellas. Estrato presenta una orientación distinta, sugiriendo que la segunda dimensión captura un eje relacionado con el segmento socioeconómico. Por su parte, latitud y longitud se agrupan en otra dirección, evidenciando que la ubicación también aporta variabilidad relevante en los componentes principales.

fviz_pca_var(pca, repel = TRUE) +
  labs(title = "ACP: Variables en el espacio de componentes")

6.4 6.4 Propiedades en PC1–PC2

En el plano PC1–PC2, las propiedades se visualizan en dos dimensiones que resumen la mayor parte de la variabilidad. La coloración por cuartiles de precio permite observar un gradiente: los inmuebles del cuartil más alto tienden a concentrarse en zonas específicas del espacio factorial, lo que sugiere que las combinaciones de características capturadas por los primeros componentes están asociadas con el nivel de precio.

scores <- as.data.frame(pca$x[,1:2])

q <- quantile(v$preciom, probs = seq(0,1,0.25), na.rm = TRUE)
scores$precio_q <- cut(v$preciom, breaks = q, include.lowest = TRUE)

ggplot(scores, aes(PC1, PC2, color = precio_q)) +
  geom_point(alpha = 0.6, size = 1.2) +
  labs(title="ACP: Propiedades en PC1-PC2 (cuartiles de precio)",
       color="Precio (cuartiles)")

6.5 6.5 Relación PCs vs precio (log)

Con el fin de evaluar qué dimensiones latentes del ACP se asocian con el precio, se calculó la correlación de Pearson entre los puntajes de los componentes principales (\(PC1–PC5\)) y \(log(1+precio)\).Los resultados muestran que PC1 presenta una correlación alta y positiva con el log-precio (\(r=0.82\)),indicando que la variación del precio está fuertemente alineada con el primer eje de variabilidad del conjunto de variables (principalmente características físicas y de dotación del inmueble). En contraste, PC2 y PC3 muestran asociaciones débiles (\(r=0.2\) y \(r=0.16\)), mientras que PC4 tiene una correlación leve y negativa (\(r=-0.11\)) y y PC5 es prácticamente nulo (\(r=0.007\)).

cor_pc_precio <- cor(pca$x[,1:5], log1p(v$preciom))
kable(round(cor_pc_precio, 4), caption="Correlación PCs vs log1p(preciom) (PC1–PC5)")
Correlación PCs vs log1p(preciom) (PC1–PC5)
PC1 0.8258
PC2 0.2055
PC3 0.1670
PC4 -0.1116
PC5 0.0078

Se calculo \(r_k = corr(PC_k, log(1+precio))\) usando la correlacion se Pearson, \[ r_k = corr(PC_k,y)=\dfrac{\sum_{i=1}^{n}(z_{ik}-\bar{z}_k)(y_i-\bar{y})} {\sqrt{\sum_{i=1}^{n}(z_{ik}-\bar{z}_k)^2}{\sqrt{\sum_{i=1}^{n}(y_{i}-\bar{y})^2}}} \] donde:

  • \(n\) es el numero de propiedades,
  • \(\bar{z}_k\) es el promedio de los puntajes de \(PC_k\), -\(\bar{y}\) es el promedio de \(log(1 + precio)\).

7 7. Análisis de Conglomerados (Segmentación)

7.1 7.1 Datos mixtos para clustering

Se construyó la matriz de variables mixtas para la segmentación (categóricas y numéricas), incluyendo indicadores de faltantes para capturar posible información asociada a la ausencia de datos. Posteriormente, se aseguraron los tipos de dato y, únicamente si existían NA, se imputaron mediante imputeFAMD() para obtener una matriz completa antes del análisis factorial y el clustering

# 7.1 Datos mixtos para clustering
X_mix <- v %>%
  select(zona, tipo, estrato, latitud, longitud, areaconst, banios, habitaciones,
         parqueaderos, piso, piso_miss, parq_miss)

# Asegurar tipos correctos (importante)
X_mix$zona <- as.factor(X_mix$zona)
X_mix$tipo <- as.factor(X_mix$tipo)

num_cols <- c("estrato","latitud","longitud","areaconst","banios","habitaciones",
              "parqueaderos","piso","piso_miss","parq_miss")
for(col in num_cols){
  X_mix[[col]] <- suppressWarnings(as.numeric(X_mix[[col]]))
  X_mix[[col]][is.infinite(X_mix[[col]])] <- NA
}

# Imputar SOLO si hay faltantes
if (sum(is.na(X_mix)) > 0) {
  imp <- missMDA::imputeFAMD(X_mix, ncp = 5)
  X_complete <- imp$completeObs
  cat("Se imputaron NA. NA totales luego:", sum(is.na(X_complete)), "\n")
} else {
  X_complete <- X_mix
  cat("No hay NA en X_mix. Se omite imputación.\n")
}
## No hay NA en X_mix. Se omite imputación.

Al verificar la matriz \(M_{mix}\) se encontro que no presentaba valores faltantes, \[ \sum 1(x_{ij} es NA)=0. \] Por lo tanto la imputacion se omitio y se trabajo directamente con la matrz original, evitando transformaciones innecesarias.

7.2 7.2 FAMD y espacio de clustering

Dado que el conjunto incluye variables mixtas (categóricas y numéricas), se aplicó FAMD para proyectar las observaciones a un espacio factorial de dimensión reducida. Se conservaron 10 dimensiones\(ncp=10\) y se obtuvieron las coordenadas de cada propiedad en dicho espacio\(Z\). El resultado \(8319*10\) ndica que 8319 propiedades quedaron representadas por 10 componentes factoriales, los cuales se utilizan como entrada para el algoritmo de clustering.

res_famd <- FAMD(X_complete, ncp = 10, graph = FALSE)
Z <- res_famd$ind$coord
dim(Z)
## [1] 8319   10

7.3 7.3 Selección de K (Silhouette en muestra)

Para definir el número de conglomerados \(K\), se evaluó el índice Silhouette promedio para \(K=2,...,10\).Con el fin de reducir el costo computacional, el cálculo se realizó sobre una muestra aleatoria de 2000 observaciones. El \(K\) óptimo se seleccionó como el que maximiza el Silhouette promedio, lo cual indica mejor separación y cohesión de los grupos.

Para ello se utiliza la Ecuacion de Sihouette; \[ s(i)=\dfrac{b(i)-a(i)}{\max\{a(i),b{i}\}} \] donde:

  • \(a(i)\) es la disstancia promedio de \(i\) a los puntos de su mismo cluister,
  • \(b(i)\) es la distancia minima promedio de \(i\) a puntos de cluster mas cercano diferente.

Asi, podemos aplicar, \[ \bar{s}(K) = \dfrac{1}{n}\sum_{i=1}^{n}s(i) \]

avg_sil_kmeans_sample <- function(data, k, sample_size = 2000, nstart=30, iter.max=300){
  set.seed(123)
  n <- nrow(data)
  idx <- sample(seq_len(n), size = min(sample_size, n))
  data_s <- data[idx, , drop = FALSE]

  km <- kmeans(data, centers = k, nstart = nstart, iter.max = iter.max)
  cl_s <- km$cluster[idx]
  sil <- silhouette(cl_s, dist(data_s))
  mean(sil[, 3])
}

k_grid <- 2:10
sil_vals <- sapply(k_grid, function(k) avg_sil_kmeans_sample(Z, k))
sil_df <- data.frame(k = k_grid, silhouette = sil_vals)

kable(sil_df, digits=4, caption="Silhouette promedio (muestra) por K")
Silhouette promedio (muestra) por K
k silhouette
2 0.2049
3 0.1954
4 0.2217
5 0.2540
6 0.2725
7 0.2706
8 0.2544
9 0.2384
10 0.2354
ggplot(sil_df, aes(k, silhouette)) +
  geom_line() + geom_point() +
  labs(title="Selección de K (Silhouette promedio en muestra)", x="K", y="Silhouette")

k_best <- sil_df$k[which.max(sil_df$silhouette)]
k_best
## [1] 6

De acuerdo con el criterio de Silhouette promedio, el valor que maximiza \(\bar{s}(K)\) fue \(K=6\); por tanto, se adopto esta particion como la segmentacion final.

7.4 7.4 K-means final y visualización

Con el valor optimo \(K\) eleccionado, se ajustó el algoritmo K-means sobre las coordenadas \(Z\) obtenidas por FAMD. Se usaron múltiples inicializaciones (nstart = 50) para reducir la sensibilidad a centroides iniciales y mejorar la estabilidad de la solución. Finalmente, se asignó a cada propiedad su etiqueta de clúster y se examinó la distribución de tamaños por grupo.

km <- kmeans(Z, centers = k_best, nstart = 50, iter.max = 500)
v$cluster <- factor(km$cluster)

table(v$cluster)
## 
##    1    2    3    4    5    6 
##  124  351 1126 1747 1799 3172

La partición final generó 6 conglomerados con tamaños desiguales, lo cual es esperable en mercados heterogéneos: algunos clústeres representan segmentos dominantes de oferta, mientras otros capturan nichos específicos (menor frecuencia). Esta distribución se utiliza posteriormente para perfilar e interpretar cada segmento

Z_2d <- Z[,1:2, drop = FALSE]
fviz_cluster(
  list(data = Z_2d, cluster = km$cluster),
  geom = "point", ellipse = TRUE,
  main = "Conglomerados en espacio FAMD (Dim 1 vs Dim 2)"
)

En la proyección Dim 1 vs Dim 2 del espacio FAMD se observa la separación relativa entre conglomerados. Las elipses resumen la dispersión intra-clúster y permiten identificar qué grupos presentan mayor solapamiento (segmentos similares) y cuáles están más claramente diferenciados (segmentos más especializados).

7.5 7.5 Perfil de clústeres

Para interpretar los segmentos obtenidos, se construyó un perfil numérico por clúster calculando estadísticos robustos: tamaño del grupo (n), mediana del precio y de las variables físicas (área construida, baños, habitaciones, parqueaderos, piso y estrato), y los cuartiles \(P25\) y \(P75\) del precio.La mediana y los cuartiles permiten describir cada segmento reduciendo la influencia de valores extremos, facilitando la comparación entre clústeres y la identificación de grupos de mayor y menor valor en el mercado.

v <- v %>% mutate(across(c(preciom, areaconst, banios, habitaciones, parqueaderos, piso, estrato), as.numeric))

perfil_clusters <- v %>%
  group_by(cluster) %>%
  summarise(
    n = n(),
    precio_med = median(preciom, na.rm=TRUE),
    precio_p25 = quantile(preciom, 0.25, na.rm=TRUE),
    precio_p75 = quantile(preciom, 0.75, na.rm=TRUE),
    areaconst_med = median(areaconst, na.rm=TRUE),
    banios_med = median(banios, na.rm=TRUE),
    hab_med = median(habitaciones, na.rm=TRUE),
    parq_med = median(parqueaderos, na.rm=TRUE),
    piso_med = median(piso, na.rm=TRUE),
    estrato_med = median(estrato, na.rm=TRUE),
    .groups="drop"
  ) %>% arrange(desc(n))

kable(perfil_clusters, digits=2, caption="Perfil numérico por clúster")
Perfil numérico por clúster
cluster n precio_med precio_p25 precio_p75 areaconst_med banios_med hab_med parq_med piso_med estrato_med
6 3172 255 185.00 340.00 90 2.0 3 1 3 5
5 1799 630 450.00 900.00 300 5.0 5 2 3 5
4 1747 257 155.00 375.00 96 2.0 3 2 3 4
3 1126 570 393.00 890.00 158 3.0 3 2 3 6
2 351 210 145.00 290.00 160 2.0 4 2 3 3
1 124 297 188.75 361.25 160 2.5 4 2 3 3

Clústeres con mayor precio_med y mayor areaconst_med representan segmentos ‘premium’, mientras que clústeres con menores medianas corresponden a oferta de entrada o económica

ggplot(v, aes(cluster, preciom)) +
  geom_boxplot(outlier.alpha = 0.2) +
  labs(title="Distribución de precio (preciom) por clúster", x="Clúster", y="Precio")

El diagrama de cajas evidencia diferencias claras de precio entre segmentos. Los clústeres 3 y 4 concentran las medianas más altas y mayor dispersión, sugiriendo oferta de mayor valor y heterogeneidad interna. En contraste, los clústeres 1, 2, 5 y 6 presentan medianas inferiores y rangos intercuartílicos más estrechos, asociados a segmentos más económicos. Los puntos fuera de los bigotes corresponden a valores atípicos, típicos de propiedades ‘premium’ o casos particulares del mercado.

Para caracterizar cada segmento, se estimó la composición interna de la oferta por zona y por estrato. Para cada clúster se calculó el porcentaje \(pct=n/\sum n\),de modo que los resultados se interpreten como ‘distribución dentro del clúster’ y no como frecuencias absolutas.

oferta_cluster_zona <- v %>%
  count(cluster, zona) %>%
  group_by(cluster) %>%
  mutate(pct = n/sum(n)) %>%
  ungroup()

oferta_cluster_estrato <- v %>%
  count(cluster, estrato) %>%
  group_by(cluster) %>%
  mutate(pct = n/sum(n)) %>%
  ungroup()

ggplot(oferta_cluster_zona, aes(x = cluster, y = pct, fill = zona)) +
  geom_col() +
  scale_y_continuous(labels = percent_format()) +
  labs(title="Composición de oferta por clúster y zona",
       x="Clúster", y="% dentro del clúster", fill="Zona")

ggplot(oferta_cluster_estrato, aes(x = cluster, y = pct, fill = factor(estrato))) +
  geom_col() +
  scale_y_continuous(labels = percent_format()) +
  labs(title="Composición de oferta por clúster y estrato",
       x="Clúster", y="% dentro del clúster", fill="Estrato")

Coimposicion por zona

Se observa una alta concentración geográfica por clúster: el clúster 1 se ubica principalmente en Zona Sur, el clúster 2 en Zona Centro, el clúster 3 en Zona Oeste, el clúster 6 en Zona Oriente. Los clústeres 4 y 5 muestran una mezcla, aunque con predominio de Zona Sur (clúster 4) y Zona Norte (clúster 5). Esto sugiere que los segmentos capturan patrones espaciales claros de oferta.

Composicion por estrato

Los clústeres también reflejan diferenciación socioeconómica: el clúster 2 está dominado por estrato 3 y el clúster 6 también se concentra en estrato 3; el clúster 3 presenta predominio de estrato 6; el clúster 4 combina principalmente estratos 5–6; y el clúster 5 concentra mayormente estratos 4–5. En conjunto, la segmentación evidencia que el mercado se estructura simultáneamente por localización y estrato.

ggplot(v, aes(longitud, latitud, color = cluster)) +
  geom_point(alpha = 0.6, size = 1.2) +
  labs(title="Distribución espacial de la oferta por clúster",
       x="Longitud", y="Latitud")

Los clústeres presentan agrupamiento espacial claro, con solapamientos puntuales que reflejan zonas de mezcla de segmentos.


8 8. Análisis de Correspondencia (MCA: tipo–zona–barrio)

Para explorar asociaciones entre variables categóricas (tipo, zona y barrio), se aplicó Análisis de Correspondencia Múltiple (MCA). Con el fin de mejorar la interpretabilidad y evitar alta dispersión por categorías poco frecuentes, se agruparon los barrios conservando únicamente los 30 más frecuentes y recodificando el resto en la categoría ‘Otros’.

cat_df <- v %>% select(tipo, zona, barrio) %>%
  mutate(across(everything(), as.factor))

# Mantener top 30 barrios para interpretabilidad
top_barrios <- names(sort(table(cat_df$barrio), decreasing = TRUE))[1:30]
cat_df <- cat_df %>%
  mutate(barrio = fct_other(barrio, keep = top_barrios, other_level = "Otros"))

8.1 8.1 Chi-cuadrado

Para visualizar la estructura conjunta entre tipo, zona y barrio, se utiliza MCA (Análisis de Correspondencia Múltiple), que descompone la tabla de contingencia de categorías en dimensiones latentes maximizando la inercia (variabilidad) explicada. Sea \(N[n_{ij}]\) la tabla de frecuencias y \(n=\sum_{i,j}n_{ij}\). Definimos la matriz de proporciones \(P=N/n\), los margenes \(r=P1\) y \(c=1^TP\) El MCA se basa en la descomposición SVD de la matriz estandarizada: \[ S=D_r^{-1/2}*(P-rc^T)*D_c^{-1/2} \] donde \(D_r=diag(r)\) y \(D_c=diag(c)\).

Las coordenadas factoriales (posiciones en el plano) se obtienen a partir de los autovalores/autovectores de esta descomposición, y permiten representar simultáneamente categorías y observaciones.

chi_tipo_zona   <- chisq.test(table(cat_df$tipo, cat_df$zona))
chi_tipo_barrio <- chisq.test(table(cat_df$tipo, cat_df$barrio))
chi_zona_barrio <- chisq.test(table(cat_df$zona, cat_df$barrio))

chi_tipo_zona
## 
##  Pearson's Chi-squared test
## 
## data:  table(cat_df$tipo, cat_df$zona)
## X-squared = 690.93, df = 4, p-value < 2.2e-16
chi_tipo_barrio
## 
##  Pearson's Chi-squared test
## 
## data:  table(cat_df$tipo, cat_df$barrio)
## X-squared = 1069.8, df = 30, p-value < 2.2e-16
chi_zona_barrio
## 
##  Pearson's Chi-squared test
## 
## data:  table(cat_df$zona, cat_df$barrio)
## X-squared = 11160, df = 120, p-value < 2.2e-16

El biplot del MCA muestra asociaciones entre categorías: categorías cercanas tienden a co-ocurrir en la oferta, mientras que categorías ubicadas en direcciones opuestas reflejan perfiles diferenciados. En particular, se observan combinaciones tipo–zona–barrio que se agrupan, indicando que la oferta inmobiliaria no se distribuye aleatoriamente: existe una estructura espacial y tipológica (zonas y barrios se asocian con ciertos tipos de vivienda), lo cual es consistente con los resultados del test.

8.2 8.2 MCA

Una vez estimado el MCA, se analiza la inercia (análogo a varianza explicada) para decidir cuántas dimensiones son suficientes para interpretar la estructura entre categorías (tipo–zona–barrio).

Loos autovalores \(\lambda_k\) obtenidos de la descomposición anterior representan la inercia de cada dimensión. La proporción de inercia explicada por la dimensión \(k\) se calcula como: \[ Inercia\; explicada(k)=\dfrac{\lambda_k}{\sum_h\lambda_h} \] y la inercia acumulada hasta \(K\) dimensiones como: \[ Incercia\; acumulada(K)=\sum_{k=1}^{K}\dfrac{\lambda_k}{\sum_h\lambda_h}. \] La siguiente gráfica resume estas proporciones para las primeras dimensiones.

mca <- MCA(cat_df, graph = FALSE)

fviz_eig(mca, addlabels = TRUE) +
  labs(title="MCA: Varianza explicada por dimensiones")

La gráfica muestra que las primeras dimensiones explican proporciones relativamente pequeñas de inercia, lo cual es habitual en MCA cuando existen muchas categorías (especialmente al incluir barrio). Aun así, las primeras 2–3 dimensiones concentran la mayor parte de la estructura interpretable y se utilizan para el biplot y la identificación de asociaciones entre categorías. En consecuencia, la interpretación se enfocará en Dim1–Dim2 (y opcionalmente Dim3) para describir los principales patrones tipo–zona–barrio.

fviz_mca_var(
  mca,
  repel = TRUE,
  select.var = list(contrib = 25)  # muestra solo las 25 categorías más influyentes
) + labs(title = "MCA: Asociación entre categorías (Top 25 por contribución)")

El mapa factorial evidencia patrones espaciales claros: Zona Oeste se asocia con barrios como cristales, normandía, aguacatal y el peñón (agrupados en el cuadrante derecho), mientras que Zona Norte se relaciona con acopi, la flora, prados del norte y torres de comfandi (parte superior). Zona Sur aparece vinculada con ciudad jardín y valle del lili (cuadrante inferior izquierdo). En contraste, las categorías Casa y Apartamento se ubican cerca del origen, sugiriendo que su asociación con una zona específica es más débil frente a la señal espacial que aportan los barrios y zonas.

fviz_contrib(mca, choice = "var", axes = 1, top = 15) +
  labs(title="MCA: Top contribuciones Dimensión 1")

fviz_contrib(mca, choice = "var", axes = 2, top = 15) +
  labs(title="MCA: Top contribuciones Dimensión 2")

Los gráficos muestran que la estructura de Dimensión 1 y Dimensión 2 está dominada por un conjunto reducido de categorías. Por tanto, la interpretación del plano MCA debe centrarse en esas categorías por encima de la línea roja, ya que son las que separan los perfiles de oferta. En términos prácticos, Dim1 y Dim2 están capturando los contrastes principales del mercado: categorías con alta contribución representan combinaciones tipo–zona–barrio más distintivas, mientras que las categorías con baja contribución tienen un papel secundario y tienden a ubicarse cerca del origen en el biplot.

9 9. Conclusiones

9.1 9.1 Conclusiones del ACP

  • La varianza acumulada de los 2 primeros componentes es: 54.7%.
  • La interpretación se apoya en el gráfico de variables del ACP (loadings).
    En general, un componente suele resumir tamaño/dotación (área, baños, habitaciones, parqueaderos) y otro componente resume ubicación/segmento (latitud/longitud, estrato).
  • La correlación PCs vs log1p(preciom) permite identificar qué dimensiones del ACP están más asociadas al precio.

9.2 9.2 Conclusiones del clustering

  • La selección por Silhouette recomienda K = 6.
  • Los perfiles por clúster evidencian segmentos diferenciados en precio, dotación y estrato, lo cual es útil para diseñar estrategias comerciales y de inversión.
  • El mapa por clúster muestra micro-mercados y concentración espacial de segmentos.

9.3 9.3 Conclusiones del MCA

  • Las pruebas Chi-cuadrado sugieren asociación entre tipo–zona–barrio.
  • El biplot de MCA permite reconocer combinaciones frecuentes en la oferta (por ejemplo, ciertos tipos de vivienda más asociados a ciertas zonas/barrrios).