<!DOCTYPE html>
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.
library(paqueteMODELOS)
library(knitr)
data("vivienda")
datos<-vivienda
Tabla_descriptiva<-data.frame(
Nombre_de_variable=c(names(datos)),
Descripcion = c("Identificador único de la vivienda",
"Zona de la ciudad",
"Piso en la que está ubicada la vivienda",
"Estrato",
"Precio en millones de pesos",
"Área construida en metros cuadrados",
"Número de parqueaderos",
"Número de baños",
"Número de habitaciones",
"Tipo de vivienda",
"BARRIO",
"Coordenada de longitud",
"Coordenada de latitud"),
Naturaleza = c("Categorica",
"Categorica",
"Numerica",
"Categorica",
"Numerica",
"Numerica",
"Numerica",
"Numerica",
"Numerica",
"Categorica",
"Categorica",
"ubicación",
"ubicación")
)
kable(Tabla_descriptiva, caption = "Tabla 1")
| Nombre_de_variable | Descripcion | Naturaleza |
|---|---|---|
| id | Identificador único de la vivienda | Categorica |
| zona | Zona de la ciudad | Categorica |
| piso | Piso en la que está ubicada la vivienda | Numerica |
| estrato | Estrato | Categorica |
| preciom | Precio en millones de pesos | Numerica |
| areaconst | Área construida en metros cuadrados | Numerica |
| parqueaderos | Número de parqueaderos | Numerica |
| banios | Número de baños | Numerica |
| habitaciones | Número de habitaciones | Numerica |
| tipo | Tipo de vivienda | Categorica |
| barrio | BARRIO | Categorica |
| longitud | Coordenada de longitud | ubicación |
| latitud | Coordenada de latitud | ubicación |
Antes de iniciar cualquier estudio sobre una base de datos, es fundamental realizar un análisis exploratorio que permita conocer su estructura, calidad y características. De esta manera, la información que se presente será precisa y confiable, evitando conclusiones erróneas que puedan afectar negativamente la toma de decisiones.
library(summarytools)
datos_filtrados <- datos[, !names(datos) %in% c("id", "latitud", "longitud")]
print(descr(datos_filtrados), method = "render")
| areaconst | banios | estrato | habitaciones | parqueaderos | preciom | |
|---|---|---|---|---|---|---|
| Mean | 174.93 | 3.11 | 4.63 | 3.61 | 1.84 | 433.89 |
| Std.Dev | 142.96 | 1.43 | 1.03 | 1.46 | 1.12 | 328.65 |
| Min | 30.00 | 0.00 | 3.00 | 0.00 | 1.00 | 58.00 |
| Q1 | 80.00 | 2.00 | 4.00 | 3.00 | 1.00 | 220.00 |
| Median | 123.00 | 3.00 | 5.00 | 3.00 | 2.00 | 330.00 |
| Q3 | 229.00 | 4.00 | 5.00 | 4.00 | 2.00 | 540.00 |
| Max | 1745.00 | 10.00 | 6.00 | 10.00 | 10.00 | 1999.00 |
| MAD | 84.51 | 1.48 | 1.48 | 1.48 | 1.48 | 207.56 |
| IQR | 149.00 | 2.00 | 1.00 | 1.00 | 1.00 | 320.00 |
| CV | 0.82 | 0.46 | 0.22 | 0.40 | 0.61 | 0.76 |
| Skewness | 2.69 | 0.93 | -0.18 | 1.63 | 2.33 | 1.85 |
| SE.Skewness | 0.03 | 0.03 | 0.03 | 0.03 | 0.03 | 0.03 |
| Kurtosis | 12.91 | 1.13 | -1.11 | 3.98 | 8.31 | 3.67 |
| N.Valid | 8319 | 8319 | 8319 | 8319 | 6717 | 8320 |
| N | 8322 | 8322 | 8322 | 8322 | 8322 | 8322 |
| Pct.Valid | 99.96 | 99.96 | 99.96 | 99.96 | 80.71 | 99.98 |
Generated by
summarytools
1.1.1 (R version
4.4.2)
2025-08-11
#Normalizar variables categoricas
normalizar_columnas_base <- function(df, columnas) {
for (col in columnas) {
df[[col]] <- tolower(trimws(df[[col]]))
}
return(df)
}
datos2 <- normalizar_columnas_base(datos,c("id","zona","estrato","tipo","barrio"))
duplicados <- datos2[duplicated(datos2), ]
sum(duplicated(datos2))
## [1] 1
datos2 <- unique(datos2)
Se identifica que solo una columna está repetida y se procede a eliminar
blancos <- is.na(datos2)
# Contar el número de celdas vacías (NA) por columna
columna_blancos <- colSums(blancos)
# Ver la cantidad de celdas vacías por columna
#print(columna_blancos)
tabla_blancos <- data.frame(
Variable = names(columna_blancos),
NA_n = as.integer(columna_blancos),
Porcentaje = round(columna_blancos / nrow(datos2) * 100, 2),
row.names = NULL
)
knitr::kable(tabla_blancos, caption = "Celdas NA por columna")
| Variable | NA_n | Porcentaje |
|---|---|---|
| id | 2 | 0.02 |
| zona | 2 | 0.02 |
| piso | 2637 | 31.69 |
| estrato | 2 | 0.02 |
| preciom | 1 | 0.01 |
| areaconst | 2 | 0.02 |
| parqueaderos | 1604 | 19.28 |
| banios | 2 | 0.02 |
| habitaciones | 2 | 0.02 |
| tipo | 2 | 0.02 |
| barrio | 2 | 0.02 |
| longitud | 2 | 0.02 |
| latitud | 2 | 0.02 |
Ya que se evidencia que la concentracion de datos faltantes se concentra en dos variable se realiza imputación mediante MODA
# Copia de trabajo
datos_imp <- datos2
# (opcional) columnas a excluir de la imputación
excluir <- c("id") # agrega "latitud","longitud", etc. si no quieres imputarlas
cols <- setdiff(names(datos_imp), excluir)
# Función de moda (sirve para numéricas, factor y character)
moda <- function(x) {
y <- x
if (is.character(y)) y[y == ""] <- NA # trata vacíos como NA (opcional)
y <- y[!is.na(y)]
if (!length(y)) return(NA)
ux <- unique(y)
ux[which.max(tabulate(match(y, ux)))]
}
# Imputación con moda por columna (simple y directa)
for (v in cols) {
m <- moda(datos_imp[[v]])
if (is.na(m)) next
if (is.factor(datos_imp[[v]])) {
# asegura que la moda exista como nivel
lv <- levels(datos_imp[[v]])
if (!(as.character(m) %in% lv)) levels(datos_imp[[v]]) <- c(lv, as.character(m))
datos_imp[[v]][is.na(datos_imp[[v]])] <- as.character(m)
} else {
datos_imp[[v]][is.na(datos_imp[[v]])] <- m
}
}
tabla_na <- data.frame(
Variable = names(datos_imp),
NA_n = colSums(is.na(datos_imp)),
Porcentaje = round(colMeans(is.na(datos_imp)) * 100, 2),
row.names = NULL
)
# Comprobar que ya no queden NA
knitr::kable(tabla_na, caption = "Faltantes por variable")
| Variable | NA_n | Porcentaje |
|---|---|---|
| id | 2 | 0.02 |
| zona | 0 | 0.00 |
| piso | 0 | 0.00 |
| estrato | 0 | 0.00 |
| preciom | 0 | 0.00 |
| areaconst | 0 | 0.00 |
| parqueaderos | 0 | 0.00 |
| banios | 0 | 0.00 |
| habitaciones | 0 | 0.00 |
| tipo | 0 | 0.00 |
| barrio | 0 | 0.00 |
| longitud | 0 | 0.00 |
| latitud | 0 | 0.00 |
head(datos_imp)
## # A tibble: 6 × 13
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1147 zona o… 02 3 250 70 1 3 6
## 2 1169 zona o… 02 3 320 120 1 2 3
## 3 1350 zona o… 02 3 350 220 2 2 4
## 4 5992 zona s… 02 4 400 280 3 5 3
## 5 1212 zona n… 01 5 260 90 1 2 3
## 6 1724 zona n… 01 5 240 87 1 3 3
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
# 1) Excluir columnas no numéricas
vars_excluir <- c("id","zona","tipo","barrio","latitud","longitud")
datos_num <- datos_imp[, setdiff(names(datos_imp), vars_excluir), drop = FALSE]
datos_num <- datos_num[, sapply(datos_num, is.numeric), drop = FALSE]
# 2) Transformación log estable (log1p = log(1+x))
datos_log <- as.data.frame(lapply(datos_num, log1p))
# 3) Outliers por IQR, devolviendo índices (mejor para localizar filas)
detectar_idx_iqr <- function(x){
x <- x[!is.na(x)]
if (length(x) < 2) return(integer(0))
Q1 <- quantile(x, .25); Q3 <- quantile(x, .75); I <- Q3 - Q1
which(x < (Q1 - 1.5*I) | x > (Q3 + 1.5*I))
}
idx_por_var <- lapply(datos_log, detectar_idx_iqr)
resumen <- data.frame(
Variable = names(idx_por_var),
Valores_Atipicos = sapply(idx_por_var, length),
row.names = NULL
)
#Conteo de válidos por variable (en datos_log)
n_valid <- sapply(datos_log, function(x) sum(!is.na(x)))
# Conteo de outliers (ya lo tienes en idx_por_var)
n_out <- sapply(idx_por_var, length)
# Porcentaje de outliers
pct_out <- ifelse(n_valid > 0, round(100 * n_out / n_valid, 2), NA_real_)
# Tabla final
tabla_out <- data.frame(
Variable = names(n_out),
N_validos = as.integer(n_valid[names(n_out)]),
N_outliers = as.integer(n_out),
Porcentaje = pct_out,
row.names = NULL
)
# Ordenar por mayor porcentaje (opcional)
tabla_out <- tabla_out[order(-tabla_out$Porcentaje), ]
# Mostrar (bonito en R Markdown)
knitr::kable(tabla_out, caption = "Porcentaje de valores atípicos por variable (IQR en log1p)")
| Variable | N_validos | N_outliers | Porcentaje | |
|---|---|---|---|---|
| 5 | habitaciones | 8321 | 888 | 10.67 |
| 3 | parqueaderos | 8321 | 183 | 2.20 |
| 4 | banios | 8321 | 54 | 0.65 |
| 2 | areaconst | 8321 | 12 | 0.14 |
| 1 | preciom | 8321 | 0 | 0.00 |
vars <- c("preciom","areaconst","parqueaderos","banios","habitaciones")
vars <- intersect(vars, names(datos_imp))
# Conversión segura a numérico
to_num <- function(x) if (is.numeric(x)) x else suppressWarnings(as.numeric(as.character(x)))
X <- setNames(lapply(datos_imp[vars], to_num), vars)
# Graficar
par(mfrow = c(2, 2), mar = c(4,4,2,1))
for (nm in names(X)) {
x <- X[[nm]]
x <- x[x > -1 & is.finite(x)] # log1p solo definido para x > -1
if (length(x)) {
boxplot(log1p(x), main = paste0("(", nm, ")"), ylab = "log(1+x)", outline = TRUE)
} else {
plot.new(); title(main = paste0(nm, " (sin datos válidos para log1p)"))
}
}
#par(mfrow = c(1,1))
Se decidió eliminar los valores atípicos de parqueaderos, baños, área construida y precio, dado que en conjunto representan menos del 3 % del total de observaciones.
# === Eliminar outliers (IQR en log1p) excepto la variable 'habitaciones' ===
# 1) Variables numéricas a evaluar (excluye id/zona/tipo/barrio/lat/long y 'habitaciones')
vars_excluir <- c("id","zona","tipo","barrio","latitud","longitud")
num_cols <- names(datos_imp)[sapply(datos_imp, is.numeric)]
vars_filtrar <- setdiff(intersect(num_cols, setdiff(names(datos_imp), vars_excluir)), "habitaciones")
# 2) Máscara de outliers en log1p alineada a filas
is_outlier_log1p <- function(x, k = 1.5){
m <- rep(FALSE, length(x))
ok <- is.finite(x) & (x > -1) # log1p definido para x > -1
if (sum(ok) < 2) return(m)
lx <- log1p(x[ok])
Q1 <- quantile(lx, .25); Q3 <- quantile(lx, .75); I <- Q3 - Q1
m_ok <- (lx < Q1 - k*I) | (lx > Q3 + k*I)
m[ok] <- m_ok
m
}
if (length(vars_filtrar) == 0) {
message("No hay variables para filtrar (aparte de 'habitaciones').")
datos_sin_out <- datos_imp
} else {
masks <- lapply(vars_filtrar, function(v) is_outlier_log1p(datos_imp[[v]]))
mask_any <- Reduce("|", masks) # outlier en cualquiera de las variables
cat("Filas a eliminar:", sum(mask_any), "de", nrow(datos_imp), "\n")
datos_sin_out <- datos_imp[!mask_any, ]
}
## Filas a eliminar: 242 de 8321
### 1) Preparar datos
excluir <- c("id", "longitud", "latitud", "preciom")
X <- datos_imp[, setdiff(names(datos_imp), excluir)]
X <- X[, sapply(X, is.numeric), drop=FALSE]
### 2) PCA con estandarización
pca <- prcomp(X, center = TRUE, scale. = TRUE)
### 3) Varianza explicada
cat("\n--- VARIANZA EXPLICADA ---\n")
##
## --- VARIANZA EXPLICADA ---
print(summary(pca)$importance[2:3, ]) # % por PC y acumulado
## PC1 PC2 PC3 PC4
## Proportion of Variance 0.63651 0.19332 0.09300 0.07717
## Cumulative Proportion 0.63651 0.82983 0.92283 1.00000
plot(pca, type="l", main="Scree plot")
El gráfico presentado corresponde al Scree plot del Análisis de Componentes Principales (PCA). En el eje horizontal (x) se representan las componentes principales (PC1, PC2, PC3 y PC4), mientras que en el eje vertical (y) se muestra la proporción de varianza explicada por cada una. Se observa que la primera componente (PC1) concentra la mayor parte de la variabilidad de los datos, seguida por la segunda (PC2), que aún aporta una cantidad relevante aunque considerablemente menor. A partir de la tercera (PC3) y cuarta (PC4) componentes, la contribución a la varianza total es muy reducida, lo que sugiere que estas últimas tienen un impacto limitado en la representación de la información original.
### 4) Biplot
biplot(pca, cex=0.7, main="Biplot PCA")
El Biplot del Análisis de Componentes Principales (PCA) combina en una sola visualización la información de las dos primeras componentes principales (PC1 y PC2), que en conjunto explican aproximadamente el 83 % de la variabilidad total de los datos. En el gráfico, los puntos negros representan las observaciones —en este caso, las propiedades del conjunto de datos— proyectadas en un espacio reducido a dos dimensiones. Las flechas rojas corresponden a las variables originales: su dirección indica hacia dónde aumenta el valor de la variable, mientras que su longitud refleja el grado de contribución a la componente. Se observa que las variables baños y área construida están más alineadas con PC1, lo que refuerza que esta dimensión está asociada principalmente al tamaño y equipamiento general de la vivienda. Por otro lado, parqueaderos y habitaciones muestran un mayor peso en PC2, lo que sugiere que esta componente distingue propiedades según su capacidad de parqueo y número de cuartos, independientemente del área o número de baños. Además, las observaciones que aparecen próximas entre sí representan inmuebles con características similares en las variables analizadas.
### 5) Cargas (qué variables pesan más en cada PC)
cat("\n--- TOP VARIABLES POR COMPONENTE ---\n")
##
## --- TOP VARIABLES POR COMPONENTE ---
for (i in 1:2) {
cat("\nPC", i, ":\n")
print(sort(abs(pca$rotation[,i]), decreasing = TRUE)[1:5])
}
##
## PC 1 :
## banios areaconst habitaciones parqueaderos <NA>
## 0.5531529 0.5371736 0.4565960 0.4438315 NA
##
## PC 2 :
## parqueaderos habitaciones banios areaconst <NA>
## 0.72210009 0.68718236 0.06144781 0.05075451 NA
### 6) Relación de PCs con precio y oferta
scores <- as.data.frame(pca$x) # coordenadas de observaciones en PCs
cat("\n--- CORRELACIONES CON PRECIO ---\n")
##
## --- CORRELACIONES CON PRECIO ---
print(cor(scores[,1:3], datos_imp$preciom, use="complete.obs"))
## [,1]
## PC1 0.7245570
## PC2 0.3344887
## PC3 -0.1130539
# ================================
# ANÁLISIS DE CONGLOMERADOS
# ================================
# 1) Preparar datos
excluir <- c("id")
#datos_imp$estrato <- as.numeric(datos_imp$estrato)
X <- datos_imp[, setdiff(names(datos_imp), excluir)]
X <- X[, sapply(X, is.numeric), drop = FALSE]
# Escalar variables
Xz <- scale(X)
# 2) Determinar número óptimo de clusters (método del codo)
wss <- numeric(10)
for (k in 1:10) {
wss[k] <- sum(kmeans(Xz, centers = k, nstart = 25)$tot.withinss)
}
plot(1:10, wss, type="b", pch=19, frame=FALSE,
xlab="Número de clusters",
ylab="Suma de cuadrados intra-cluster",
main="Método del codo para K óptimo")
En el gráfico, el eje X representa el número de clusters (k), mientras que el eje Y muestra la suma de cuadrados intra-cluster (WSS), una medida de la compacidad de los grupos. A medida que k aumenta, la WSS disminuye, ya que los grupos resultan más pequeños y homogéneos. El llamado “método del codo” consiste en identificar el punto a partir del cual esta reducción deja de ser significativa; en este caso, el cambio más evidente se observa en torno a k = 3 o k = 4, lo que sugiere que ese rango podría ser una elección adecuada para el número de clusters.
Se seleciona K=3
# >>> Elige el número de clusters (k) según el codo de la curva <<<
k <- 3 # ejemplo, puedes cambiarlo
# 3) Ejecutar K-means
set.seed(123)
km <- kmeans(Xz, centers = k, nstart = 25)
# 4) Resumen de cada cluster
resumen_clusters <- aggregate(X, by = list(Cluster = km$cluster), FUN = mean)
print(resumen_clusters)
## Cluster preciom areaconst parqueaderos banios habitaciones longitud
## 1 1 260.3589 123.9226 1.229112 2.404099 3.448240 -76.50786
## 2 2 848.5133 347.5094 2.773622 4.902067 4.875984 -76.53677
## 3 3 317.0746 117.0634 1.357957 2.588007 3.084587 -76.53383
## latitud
## 1 3.465921
## 2 3.411512
## 3 3.399554
# 5) Añadir cluster al dataset original
datos_imp$Cluster <- factor(km$cluster)
# 6) Visualización usando PCA
library(ggplot2)
pca_scores <- as.data.frame(prcomp(Xz)$x[,1:2])
pca_scores$Cluster <- factor(km$cluster)
ggplot(pca_scores, aes(PC1, PC2, color = Cluster)) +
geom_point(alpha = 0.6, size = 2) +
labs(title = "Clusters de propiedades (proyección PCA)") +
theme_minimal()
library(ggplot2)
library(dplyr)
# 1) Seleccionar solo numéricas para el clustering
excluir <- c("id", "longitud", "latitud", "estrato", "zona", "barrio")
X <- datos_imp[, setdiff(names(datos_imp), excluir)]
X <- X[, sapply(X, is.numeric), drop = FALSE]
# Escalar
Xz <- scale(X)
# 2) Elegir número de clusters (puedes ajustar k según método del codo)
set.seed(123)
k <- 3
km <- kmeans(Xz, centers = k, nstart = 25)
# 3) Añadir cluster al dataset original
datos_imp$Cluster <- factor(km$cluster)
# ================================
# 4) Distribución por estrato, zona y barrio
# ================================
# Tabla por estrato
tabla_estrato <- table(datos_imp$Cluster, datos_imp$estrato)
prop_estrato <- prop.table(tabla_estrato, margin = 1) * 100
# Tabla por zona
tabla_zona <- table(datos_imp$Cluster, datos_imp$zona)
prop_zona <- prop.table(tabla_zona, margin = 1) * 100
# Tabla por barrio
tabla_barrio <- table(datos_imp$Cluster, datos_imp$barrio)
prop_barrio <- prop.table(tabla_barrio, margin = 1) * 100
# Mostrar tablas
cat("\nDistribución por Estrato (%):\n")
##
## Distribución por Estrato (%):
print(round(prop_estrato, 1))
##
## 3 4 5 6
## 1 21.2 33.5 35.4 9.9
## 2 1.5 4.8 21.3 72.3
## 3 15.9 17.7 32.8 33.6
# Estrato por cluster
ggplot(datos_imp, aes(x = Cluster, fill = factor(estrato))) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent) +
labs(title = "Distribución de estratos dentro de cada cluster",
x = "Cluster", y = "% dentro del cluster", fill = "Estrato") +
theme_minimal()
El análisis de la distribución de estratos dentro de cada cluster revela diferencias claras en la composición socioeconómica de los grupos identificados. El Cluster 1 presenta una distribución relativamente equilibrada entre los estratos 3, 4, 5 y 6, aunque con mayor presencia en los estratos 4 y 5. El Cluster 2 está fuertemente concentrado en el estrato 6 (más del 70%), lo que sugiere que agrupa principalmente propiedades de alto nivel socioeconómico. Por su parte, el Cluster 3 tiene una presencia significativa en los estratos 5 y 6, pero mantiene una proporción considerable en los estratos 3 y 4, reflejando un perfil mixto. Estos patrones sugieren que los clusters no solo se diferencian por características físicas de las propiedades, sino también por su ubicación en segmentos socioeconómicos específicos del mercado.
Además, al contrastar con la variable de precio, se observa que los patrones por estrato guardan coherencia con el valor promedio de las propiedades en cada cluster. El Cluster 2, dominado por el estrato 6, presenta los precios más altos, lo que confirma su asociación con el segmento premium del mercado inmobiliario. El Cluster 1, con mayor presencia en estratos medios (4 y 5), registra precios intermedios, mientras que el Cluster 3, con una composición mixta y una proporción importante de estratos 3 y 4, tiende a tener precios más moderados. Esto indica que el análisis de clusters no solo refleja diferencias en ubicación socioeconómica, sino también en el rango de precios al que se dirigen las propiedades.
cat("\nDistribución por Zona (%):\n")
##
## Distribución por Zona (%):
print(round(prop_zona, 1))
##
## zona centro zona norte zona oeste zona oriente zona sur
## 1 1.3 26.8 10.0 3.8 58.0
## 2 0.3 11.4 28.2 0.7 59.4
## 3 2.2 20.0 18.0 6.3 53.6
# Zona por cluster
ggplot(datos_imp, aes(x = Cluster, fill = factor(zona))) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent) +
labs(title = "Distribución de zonas dentro de cada cluster",
x = "Cluster", y = "% dentro del cluster", fill = "Zona") +
theme_minimal()
En la distribución de zonas por cluster, se observa que la zona sur es predominante en los tres grupos, con más del 50% de representación en todos los casos. El Cluster 1 presenta una segunda concentración importante en la zona norte (26,8%), mientras que en el Cluster 2 y Cluster 3 destaca una mayor presencia de la zona oriente y zona oeste, respectivamente. La zona centro tiene participación mínima en todos los clusters. Estos resultados sugieren que, aunque la zona sur es el principal foco de la oferta inmobiliaria en los tres segmentos, cada cluster mantiene un patrón geográfico distintivo que podría influir en el precio y las características de las propiedades.
library(FactoMineR)
library(factoextra)
# Seleccionar las variables categóricas
cat_vars <- datos_imp[, c("zona","tipo")]
# Asegurar que sean factores
cat_vars[] <- lapply(cat_vars, factor)
# Ejecutar MCA
mca <- MCA(cat_vars, graph = FALSE)
# Gráfico biplot (categorías y observaciones)
fviz_mca_biplot(mca, repel = TRUE, ggtheme = theme_minimal(),
label = "var", # Solo etiquetas de variables (categorías)
invisible = "ind", # Ocultar individuos
title = "Análisis de Correspondencia Múltiple")
Eje Dim1 (25,8%): Diferencia principalmente entre las zonas oeste y apartamento (a la izquierda) frente a casa, zona oriente y zona centro (a la derecha). Esto sugiere que los apartamentos tienden a concentrarse más en la zona oeste, mientras que las casas se asocian con la zona oriente y centro.
Eje Dim2 (20%): Separa la zona norte (parte superior) del resto, lo que indica un patrón particular de oferta en esa zona.
Zona sur aparece cercana al centro, lo que indica que no tiene una asociación fuerte con un tipo de vivienda específico en comparación con otras zonas.
En conjunto, el gráfico muestra agrupamientos claros que permiten identificar patrones geográficos en el tipo de oferta:
Apartamentos → zona oeste
Casas → zona oriente y centro