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.
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)
| 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 | ▂▇▂▁▁ |
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)
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)
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)
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)
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)
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")
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
)
}
| 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 |
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.
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.")
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)
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")
}
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.
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.
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.