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.
# 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).
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.
Antes de modelar, es necesario conocer el conjunto de datos disponible. Para ello se tuvo en cuenta lo siguiente:
En esta etapa no se modifican los datos, solo se documentan sus características.
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")
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).
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.
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).
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:
id, zona, estrato,
areaconst, banios, habitaciones,
tipo, barrio, longitud,
latitud). Representan un 0.04% de los datos.parqueaderos: 1.605 valores faltantes
(19.3%), sin una relación clara con el tipo de vivienda.piso: 2.638 valores faltantes (31.7%),
el porcentaje más alto de toda la base.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.
De acuerdo con lo observado en la etapa anterior, se define la siguiente secuencia de actividades:
zona (se retira el prefijo
“Zona”) y convertir zona y tipo a factor.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”).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.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.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.
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
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.
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).
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.
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.
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.
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.
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")
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:
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.
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).
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.
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.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.