Contexto y propósito del proyecto

La empresa inmobiliaria busca comprender de manera integral el mercado de vivienda urbana a partir de una base de datos con información detallada de propiedades residenciales. Este informe aplica técnicas de análisis multivariado descriptivo para identificar patrones, relaciones y segmentaciones relevantes que apoyen la toma de decisiones en compra, venta y valoración.

Preparación y validación de datos

Se cargó la base vivienda y se realizó una validación interna de estructura, tipos de variables, valores faltantes y duplicados. Estos procesos se ejecutan automáticamente en R y no se muestran en el informe, garantizando la calidad de la información analizada.

data("vivienda")

# Limpieza mínima de nombres (opcional pero útil)
vivienda <- vivienda %>% janitor::clean_names()

# OVERRIDE: quitar variables que  indicó que NO deben entrar a ACP/Clúster
drop_cols <- intersect(names(vivienda), c("id", "latitud", "longitud", "latitude", "longitude", "lat", "lon"))
if (length(drop_cols) > 0) {
  vivienda <- vivienda %>% dplyr::select(-all_of(drop_cols))
}


# Validaciones duras (no se imprimen, pero frenan si algo está mal)
stopifnot(is.data.frame(vivienda))
stopifnot(nrow(vivienda) > 0, ncol(vivienda) > 1)

# Convertir caracteres a factor
vivienda <- vivienda %>%
  mutate(across(where(is.character), as.factor))

# OVERRIDE metodológico: estrato como categórica ordinal
if ("estrato" %in% names(vivienda)) {
  vivienda <- vivienda %>% mutate(estrato = factor(estrato, ordered = TRUE))
}


# Identificar tipos
is_num <- sapply(vivienda, is.numeric)
is_cat <- sapply(vivienda, function(x) is.factor(x) || is.logical(x))

num_vars <- names(vivienda)[is_num]
cat_vars <- names(vivienda)[is_cat]

# Resumen rápido (ya con tipos correctos)
skimr::skim(vivienda)
Data summary
Name vivienda
Number of rows 8322
Number of columns 10
_______________________
Column type frequency:
factor 5
numeric 5
________________________
Group variables None

Variable type: factor

skim_variable n_missing complete_rate ordered n_unique top_counts
zona 3 1.00 FALSE 5 Zon: 4726, Zon: 1920, Zon: 1198, Zon: 351
piso 2638 0.68 FALSE 12 02: 1450, 03: 1097, 01: 860, 04: 607
estrato 3 1.00 TRUE 4 5: 2750, 4: 2129, 6: 1987, 3: 1453
tipo 3 1.00 FALSE 2 Apa: 5100, Cas: 3219
barrio 3 1.00 FALSE 436 val: 1008, ciu: 516, pan: 409, la : 366

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
preciom 2 1.00 433.89 328.65 58 220 330 540 1999 ▇▂▁▁▁
areaconst 3 1.00 174.93 142.96 30 80 123 229 1745 ▇▁▁▁▁
parqueaderos 1605 0.81 1.84 1.12 1 1 2 2 10 ▇▁▁▁▁
banios 3 1.00 3.11 1.43 0 2 3 4 10 ▇▇▃▁▁
habitaciones 3 1.00 3.61 1.46 0 3 3 4 10 ▂▇▂▁▁
Tratamiento de valores faltantes y estandarización

Antes de aplicar las técnicas multivariadas, se gestionaron los valores faltantes para evitar sesgos y pérdida de información. Para variables numéricas se utilizó imputación basada en PCA y para variables categóricas una imputación por moda. Posteriormente, las variables numéricas se centraron y escalaron para asegurar comparabilidad y estabilidad en ACP y Clúster.

# Separar

## 2.1 Manejo de faltantes

v_num <- vivienda %>% select(all_of(num_vars))
v_cat <- vivienda %>% select(all_of(cat_vars))

# Imputación numérica (missMDA) si hay NA
if (ncol(v_num) > 0 && anyNA(v_num)) {
  # Estimar número de dimensiones para imputar
  ncp_est <- tryCatch(missMDA::estim_ncpPCA(v_num, ncp.max = 10)$ncp, error = function(e) 2)
  v_num_imp <- missMDA::imputePCA(v_num, ncp = ncp_est)$completeObs
} else {
  v_num_imp <- v_num
}

# Imputación categórica por moda (simple)
mode_impute <- function(x){
  if (!anyNA(x)) return(x)
  ux <- x[!is.na(x)]
  if (length(ux) == 0) return(x)
  m <- names(sort(table(ux), decreasing = TRUE))[1]
  x[is.na(x)] <- m
  x
}

if (ncol(v_cat) > 0 && anyNA(v_cat)) {
  v_cat_imp <- v_cat %>% mutate(across(everything(), mode_impute))
} else {
  v_cat_imp <- v_cat
}

# Dataset listo para modelado
v_ready <- bind_cols(v_num_imp, v_cat_imp)
Análisis de Componentes Principales (ACP)

El ACP se emplea para reducir la dimensionalidad del conjunto de variables numéricas y sintetizar la información en componentes que explican la mayor proporción de variabilidad. En este informe, el scree plot permite decidir cuántos componentes retener; el gráfico de variables muestra cuáles aportan más a cada componente; y el biplot facilita interpretar simultáneamente variables y viviendas, revelando estructuras y tendencias del mercado.

# 3) ACP (Análisis de Componentes Principales)

## 3.1 Preparar matriz numérica (centrar/escalar)

# Si no hay suficientes numéricas, detenemos con un error claro
if (ncol(v_num_imp) < 2) stop("ACP requiere al menos 2 variables numéricas. Revisa tu base.")

X <- scale(v_num_imp)  # centrar y escalar
res_pca <- FactoMineR::PCA(as.data.frame(X), graph = FALSE)
factoextra::fviz_eig(res_pca, addlabels = TRUE)

Lectura del Scree plot

El scree plot muestra que el primer componente principal explica el 65.8% de la variabilidad total del mercado de vivienda, mientras que el segundo componente aporta un 16.4%. En conjunto, las dos primeras componentes concentran aproximadamente el 82.2% de la información del conjunto de datos.

A partir del tercer componente, el aporte adicional de variabilidad disminuye de forma progresiva (7.2%, 6.7% y 3.8% respectivamente), evidenciando claramente el punto de inflexión característico del criterio del codo.

En consecuencia, el análisis posterior se enfoca principalmente en las primeras dos componentes, ya que sintetizan la mayor parte de la estructura del mercado inmobiliario y permiten una reducción significativa de la dimensionalidad sin pérdida relevante de información.

factoextra::fviz_pca_var(res_pca, col.var = "contrib", repel = TRUE)

¿Qué variables están “moviendo” el mercado?

El mapa de variables del Análisis de Componentes Principales muestra que el primer componente principal (Dim1 = 65.8%) está dominado por variables directamente asociadas al tamaño y valor económico de las viviendas. En particular, precio, área construida, número de baños, habitaciones y parqueaderos presentan flechas largas y orientadas en la misma dirección, lo que evidencia una fuerte relación positiva entre estas características.

El segundo componente (Dim2 = 16.4%) introduce una diferenciación secundaria principalmente asociada con el número de habitaciones, lo que sugiere variabilidad adicional relacionada con la distribución interna de los inmuebles, más que con su valor económico.

En conjunto, el ACP evidencia que las diferencias más relevantes del mercado inmobiliario urbano están explicadas fundamentalmente por características estructurales y económicas de las viviendas, confirmando que el tamaño y el nivel de equipamiento son los principales factores que segmentan la oferta analizada.

# 4) Conglomerados 

## 4.1 Clustering sobre coordenadas del ACP 

# Usamos coordenadas de individuos en el espacio PCA
pca_scores <- as.data.frame(res_pca$ind$coord)

# Elegir K con silhouette (rápido) entre 2 y 10
sil_by_k <- function(df, k){
  km <- kmeans(df, centers = k, nstart = 25)
  ss <- silhouette(km$cluster, dist(df))
  mean(ss[, 3])
}

k_grid <- 2:min(10, nrow(pca_scores)-1)
sil_vals <- sapply(k_grid, function(k) sil_by_k(pca_scores, k))
best_k <- k_grid[which.max(sil_vals)]

# K-means final
set.seed(123)
km <- kmeans(pca_scores, centers = best_k, nstart = 50)

v_ready$cluster <- factor(km$cluster)
Segmentación con análisis de conglomerados (Clúster)

Para segmentar el mercado en grupos homogéneos, se aplicó k-means sobre las coordenadas obtenidas del ACP. El número de grupos se seleccionó mediante el criterio de silhouette, priorizando una separación clara entre segmentos. Los clústeres resultantes representan perfiles de vivienda con características similares, útiles para diferenciar estrategias de precios, inversión y comercialización por segmento.

plot(k_grid, sil_vals, type = "b", xlab = "K", ylab = "Silhouette promedio")
abline(v = best_k, lty = 2)

Selección del número de clústeres

El índice silhouette evalúa qué tan compactos y bien separados se encuentran los grupos formados. En el análisis realizado, el valor máximo del silhouette promedio se obtiene para K = 2 (≈ 0.48), lo que indica que esta partición presenta la mejor separación relativa entre los segmentos de vivienda.

A partir de valores mayores de K, el índice silhouette disminuye de manera progresiva (ubicándose aproximadamente entre 0.40 y 0.28), lo que sugiere que aumentar el número de clústeres no aporta una segmentación sustancialmente más clara y tiende a introducir solapamientos entre los grupos.

En consecuencia, se seleccionaron dos clústeres como la solución más parsimoniosa e interpretable, permitiendo identificar perfiles diferenciados del mercado inmobiliario sin introducir complejidad innecesaria.

factoextra::fviz_cluster(list(data = pca_scores, cluster = km$cluster), geom = "point")

Interpretación visual de la segmentación

El gráfico muestra la distribución de las viviendas en el espacio reducido obtenido mediante el Análisis de Componentes Principales (ACP), diferenciando los dos clústeres identificados. Se observa una separación moderada entre los grupos, con cierta superposición en la zona central del gráfico, lo cual es consistente con el valor del índice silhouette obtenido (≈ 0.48).

El clúster 2 presenta una mayor dispersión espacial, lo que sugiere una mayor heterogeneidad en las características de las viviendas que lo conforman, mientras que el clúster 1 se concentra en un rango más acotado de valores, indicando un perfil más homogéneo.

Aunque la segmentación no es completamente separada, los resultados permiten identificar perfiles diferenciados del mercado inmobiliario, que posteriormente se interpretan mediante el análisis comparativo de variables clave como precio, área construida y estrato.

library(knitr)
library(kableExtra)

if (ncol(v_num_imp) > 0) {

  v_out <- bind_cols(v_num_imp, cluster = v_ready$cluster) %>%
    group_by(cluster) %>%
    summarise(
      across(
        where(is.numeric),
        list(
          media = ~round(mean(.x, na.rm = TRUE), 2),
          sd    = ~round(sd(.x, na.rm = TRUE), 2)
        ),
        .names = "{.col}_{.fn}"
      ),
      .groups = "drop"
    )

  v_out_show <- v_out[, 1:min(10, ncol(v_out))]

  knitr::kable(
    v_out_show,
    format = "html",
    caption = "Perfil de clúster (resumen por grupo)"
  ) %>%
    kableExtra::kable_styling(
      bootstrap_options = c("striped", "hover", "condensed", "responsive"),
      full_width = TRUE
    )
}
Perfil de clúster (resumen por grupo)
cluster preciom_media preciom_sd areaconst_media areaconst_sd parqueaderos_media parqueaderos_sd banios_media banios_sd habitaciones_media
1 290.09 143.12 112.10 59.27 1.35 0.49 2.45 0.86 3.09
2 794.62 381.45 332.53 167.74 2.85 1.34 4.76 1.23 4.89
Interpretación del perfil de los clústeres

La tabla de perfil de clústeres resume las características promedio de las viviendas agrupadas, permitiendo una comparación directa entre los segmentos identificados.

  • Clúster 1 agrupa viviendas de menor valor relativo, con un precio promedio cercano a 290 y áreas construidas más reducidas (≈ 112 m²). Este segmento representa una oferta más accesible y de mayor heterogeneidad interna.
  • Clúster 2 concentra viviendas de mayor valor, con precios promedio cercanos a 795, áreas construidas significativamente mayores (≈ 333 m²) y mayor número de parqueaderos. Este clúster corresponde a un segmento premium del mercado inmobiliario.

Estas diferencias evidencian que la segmentación lograda separa claramente el mercado en perfiles con distintas capacidades económicas y tamaños, lo cual es clave para definir estrategias diferenciadas de inversión, precios y comercialización.

# 5) Correspondencias (categóricas)

## 5.1 Selección automática: CA si son 2 variables, MCA si son 3+ variables categóricas

# Mantener solo categóricas "útiles" (más de 1 nivel)
cat_ok <- v_cat_imp %>%
  select(where(is.factor)) %>%
  select(where(~ nlevels(.) > 1))

if (ncol(cat_ok) < 2) stop("Correspondencias requiere al menos 2 variables categóricas con más de 1 nivel.")
Análisis de correspondencia (MCA)

Para comprender relaciones entre variables categóricas (por ejemplo tipo de vivienda, zona, barrio o estrato), se aplicó Análisis de Correspondencia Múltiple (MCA). Los mapas factoriales permiten identificar categorías asociadas: categorías cercanas tienden a coexistir en el mercado, lo que aporta evidencia sobre patrones territoriales o de tipología dentro de la oferta inmobiliaria.

lump_rare <- function(x, min_n = 20, other = "Otros") {
  x <- as.factor(x)
  tb <- table(x)
  rare <- names(tb[tb < min_n])
  levels(x)[levels(x) %in% rare] <- other
  droplevels(x)
}

cat_ok2 <- cat_ok %>%
  mutate(
    across(
      where(is.factor),
      function(col) {
        if (nlevels(col) > 30) {
          lump_rare(col, min_n = 20)
        } else {
          col
        }
      }
    )
  )
# Ajusta estos nombres a los reales de tu base
vars_mca <- intersect(
  names(cat_ok2),
  c("tipo_vivienda", "zona", "barrio", "estrato")
)

# Si por nombre no coinciden, toma las primeras 4 categóricas
if (length(vars_mca) < 2) {
  vars_mca <- names(cat_ok2)[1:min(4, ncol(cat_ok2))]
}

cat_mca <- cat_ok2 %>% select(all_of(vars_mca))

res_mca <- FactoMineR::MCA(cat_mca, ncp = 5, graph = FALSE)
# MCA con todas las categóricas disponibles
#res_mca <- FactoMineR::MCA(cat_ok, graph = FALSE)
factoextra::fviz_mca_var(res_mca, repel = TRUE)

Categorías de Zona resaltadas

En el mapa factorial del Análisis de Correspondencias Múltiples (MCA), las categorías asociadas a la variable Zona se resaltan visualmente frente al resto de categorías del análisis.

Este resaltado permite identificar de forma más clara la posición relativa de las zonas de la ciudad dentro del espacio factorial y su proximidad con otras categorías, como barrios o niveles socioeconómicos.

La cercanía entre puntos sugiere asociaciones entre categorías, mientras que posiciones opuestas indican patrones diferenciados dentro de la estructura del mercado inmobiliario.

# Colores por categoria: Zonas en azul, el resto en rojo
mca_vars <- factoextra::get_mca_var(res_mca)
labs <- rownames(mca_vars$coord)

col_vec <- ifelse(
  grepl("^zona=|^zona\\b|\\bzona\\b", labs, ignore.case = TRUE),
  "blue",
  "red"
)

factoextra::fviz_mca_var(
  res_mca,
  repel = TRUE,
  select.var = list(contrib = 25),
  col.var = col_vec
)

# 6) Visualizaciones extra (opcional)

## 6.1 Precio por clúster (si existe una variable “precio/price”)

# Detectar automáticamente columna de precio
price_col <- names(v_ready)[grepl("precio|price|valor", names(v_ready), ignore.case = TRUE)][1]
if (!is.na(price_col) && price_col %in% names(v_ready) && is.numeric(v_ready[[price_col]])) {
  ggplot(v_ready, aes(x = cluster, y = .data[[price_col]])) +
    geom_boxplot() +
    labs(x = "Clúster", y = price_col, title = "Distribución de precio por clúster")
}

Interpretación de la distribución de precios por clúster

El diagrama de cajas evidencia diferencias claras en la distribución de precios entre los clústeres identificados, confirmando la existencia de segmentos diferenciados dentro del mercado inmobiliario.

  • Clúster 1 presenta precios más bajos y concentrados, con una mediana alrededor de los 250 y una dispersión relativamente menor. Aunque se observan algunos valores atípicos, el patrón general corresponde a una oferta más homogénea de viviendas de menor valor.
  • Clúster 2 muestra precios notablemente más altos, con una mediana claramente superior y una mayor variabilidad, evidenciada tanto por el rango intercuartílico como por la presencia de valores extremos elevados. Esto sugiere un segmento de mayor valor y mayor heterogeneidad.

Estas diferencias en nivel y dispersión de precios respaldan la validez de la segmentación realizada y refuerzan la interpretación de dos perfiles de mercado con comportamientos económicos diferenciados.

Conclusiones clave y recomendaciones estratégicas

El análisis multivariado permitió convertir una base de datos extensa en conocimiento accionable para la toma de decisiones. La reducción de dimensión (ACP), la segmentación (Clúster) y las asociaciones cualitativas (MCA) ofrecen una visión integral del mercado y soportan decisiones más informadas.

  • Implementar estrategias diferenciadas por clúster (precio, marketing, captación y tipo de inventario).
  • Priorizar zonas y tipologías asociadas con mayor valor o mayor demanda según los mapas factoriales.
  • Monitorear periódicamente los indicadores sintéticos del ACP para detectar cambios del mercado.
  • Actualizar la segmentación de manera regular para mantener ventajas competitivas en un entorno dinámico.
Referencias Bibliográficas
  • Jolliffe, I. T. (2002). Principal Component Analysis. Springer.
  • Kaufman, L., & Rousseeuw, P. J. (2005). Finding Groups in Data: An Introduction to Cluster Analysis. Wiley.
  • Greenacre, M. (2017). Correspondence Analysis in Practice. CRC Press.
  • Husson, F., Lê, S., & Pagès, J. (2017). Exploratory Multivariate Analysis by Example Using R. CRC Press.
  • Kassambara, A. (2017). Practical Guide to Principal Component Methods in R. STHDA.
  • R Core Team (2024). R: A Language and Environment for Statistical Computing. R Foundation for Statistical Computing.