#install.packages("devtools")
#devtools::install_github("centromagis/paqueteMODELOS", force = TRUE)
library(paqueteMODELOS)
paquetes <- c("devtools", "tidyverse", "FactoMineR", "factoextra",
"cluster", "corrplot", "ggrepel", "scales", "gridExtra",
"knitr", "VIM")
nuevos <- paquetes[!(paquetes %in% installed.packages()[, "Package"])]
if (length(nuevos) > 0) install.packages(nuevos, dependencies = TRUE)
library(tidyverse)
library(FactoMineR)
library(factoextra)
library(cluster)
library(corrplot)
library(ggrepel)
library(scales)
library(gridExtra)
library(patchwork)
library(VIM)
library(stringr)
library(stringi)
library(knitr)
library(kableExtra)
library(naniar)
library(dplyr)
set.seed(123)
La base de datos vivienda contiene información de oferta
de propiedades en la ciudad de Cali, 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 objetivo de este análisis es aplicar técnicas de estadística multivariada como Análisis de Componentes Principales, Análisis de Conglomerados y Análisis de Correspondencias, para identificar los factores que más explican la variación de precios, segmentar la oferta en grupos homogéneos y entender cómo se relacionan el tipo de vivienda, la zona y el barrio, de forma que la dirección de la empresa cuente con evidencia visual y cuantitativa para la toma de decisiones.
| Variable | Tipo | Descripción |
|---|---|---|
id |
Cuantitativa discreta (ID) | Identificador único de cada vivienda/registro en la base |
zona |
Cualitativa nominal | Zona geográfica de la ciudad donde se ubica la vivienda |
barrio |
Cualitativa nominal | Barrio específico dentro de la zona |
tipo |
Cualitativa nominal | Tipo de inmueble (casa / apartamento) |
estrato |
Cualitativa ordinal | Estrato socioeconómico de la vivienda |
piso |
Cualitativa ordinal | Piso en el que se ubica la unidad |
preciom |
Cuantitativa continua | Precio de venta de la vivienda, en millones de pesos colombianos (COP) |
areaconst |
Cuantitativa continua | Área construida de la vivienda, en metros cuadrados (m²) |
habitaciones |
Cuantitativa discreta | Número de habitaciones/alcobas |
banios |
Cuantitativa discreta | Número de baños |
parqueaderos |
Cuantitativa discreta | Número de parqueaderos asignados a la vivienda |
latitud |
Cuantitativa continua | Coordenada geográfica de latitud de la vivienda |
longitud |
Cuantitativa continua | Coordenada geográfica de longitud de la vivienda |
data("vivienda")
summary(vivienda)
## id zona piso estrato
## Min. : 1 Length:8322 Length:8322 Min. :3.000
## 1st Qu.:2080 Class :character Class :character 1st Qu.:4.000
## Median :4160 Mode :character Mode :character Median :5.000
## Mean :4160 Mean :4.634
## 3rd Qu.:6240 3rd Qu.:5.000
## Max. :8319 Max. :6.000
## NA's :3 NA's :3
## preciom areaconst parqueaderos banios
## Min. : 58.0 Min. : 30.0 Min. : 1.000 Min. : 0.000
## 1st Qu.: 220.0 1st Qu.: 80.0 1st Qu.: 1.000 1st Qu.: 2.000
## Median : 330.0 Median : 123.0 Median : 2.000 Median : 3.000
## Mean : 433.9 Mean : 174.9 Mean : 1.835 Mean : 3.111
## 3rd Qu.: 540.0 3rd Qu.: 229.0 3rd Qu.: 2.000 3rd Qu.: 4.000
## Max. :1999.0 Max. :1745.0 Max. :10.000 Max. :10.000
## NA's :2 NA's :3 NA's :1605 NA's :3
## habitaciones tipo barrio longitud
## Min. : 0.000 Length:8322 Length:8322 Min. :-76.59
## 1st Qu.: 3.000 Class :character Class :character 1st Qu.:-76.54
## Median : 3.000 Mode :character Mode :character Median :-76.53
## Mean : 3.605 Mean :-76.53
## 3rd Qu.: 4.000 3rd Qu.:-76.52
## Max. :10.000 Max. :-76.46
## NA's :3 NA's :3
## latitud
## Min. :3.333
## 1st Qu.:3.381
## Median :3.416
## Mean :3.418
## 3rd Qu.:3.452
## Max. :3.498
## NA's :3
#dim(vivienda)
glimpse(vivienda)
## Rows: 8,322
## Columns: 13
## $ id <dbl> 1147, 1169, 1350, 5992, 1212, 1724, 2326, 4386, 1209, 159…
## $ zona <chr> "Zona Oriente", "Zona Oriente", "Zona Oriente", "Zona Sur…
## $ piso <chr> NA, NA, NA, "02", "01", "01", "01", "01", "02", "02", "02…
## $ estrato <dbl> 3, 3, 3, 4, 5, 5, 4, 5, 5, 5, 6, 4, 5, 6, 4, 5, 5, 4, 5, …
## $ preciom <dbl> 250, 320, 350, 400, 260, 240, 220, 310, 320, 780, 750, 62…
## $ areaconst <dbl> 70, 120, 220, 280, 90, 87, 52, 137, 150, 380, 445, 355, 2…
## $ parqueaderos <dbl> 1, 1, 2, 3, 1, 1, 2, 2, 2, 2, NA, 3, 2, 2, 1, 4, 2, 2, 2,…
## $ banios <dbl> 3, 2, 2, 5, 2, 3, 2, 3, 4, 3, 7, 5, 6, 2, 4, 4, 4, 3, 2, …
## $ habitaciones <dbl> 6, 3, 4, 3, 3, 3, 3, 4, 6, 3, 6, 5, 6, 2, 5, 5, 4, 3, 3, …
## $ tipo <chr> "Casa", "Casa", "Casa", "Casa", "Apartamento", "Apartamen…
## $ barrio <chr> "20 de julio", "20 de julio", "20 de julio", "3 de julio"…
## $ longitud <dbl> -76.51168, -76.51237, -76.51537, -76.54000, -76.51350, -7…
## $ latitud <dbl> 3.43382, 3.43369, 3.43566, 3.43500, 3.45891, 3.36971, 3.4…
La base de datos está compuesta por 8.322 registros y 13 variables divididas entre cualitativas y cuantitativas, por lo que se debe tener cuidado a la hora de hacer uso de la técnica que se va a ajustar teniendo en cuenta qué tipos de datos admite cada una.
# =============================================================================
# BOXPLOTS DE VARIABLES CUANTITATIVAS
# =============================================================================
vars_cuanti <- vivienda %>%
select(preciom, areaconst, parqueaderos, banios, habitaciones, latitud, longitud) %>%
pivot_longer(cols = everything(), names_to = "variable", values_to = "valor")
ggplot(vars_cuanti, aes(x = variable, y = valor, fill = variable)) +
geom_boxplot(alpha = 0.7, outlier.alpha = 0.3, outlier.size = 0.8) +
facet_wrap(~ variable, scales = "free", ncol = 3) + # "free" = cada panel con su propio rango
labs(title = "Distribución de las variables cuantitativas",
x = NULL, y = NULL) +
theme_minimal() +
theme(legend.position = "none",
strip.text = element_text(face = "bold"),
axis.text.x = element_blank(),
axis.ticks.x = element_blank())
En general, se puede observar que todas las variables cuantitativas presentan una asimetría positiva (cola a la derecha) con outliers marcados por encima del bigote superior(a excepción de la variable longitud que tiene algunos por debajo). Esto sugiere la presencia de un segmento reducido de propiedades de alto valor (posiblemente estratos altos o zonas exclusivas) que se aleja considerablemente del resto del mercado. En contraste, las coordenadas geográficas (latitud, longitud) muestran una distribución más simétrica y sin atípicos relevantes, y su rango (3.33°–3.50° N, -76.59° a -76.46° O) es consistente con la extensión geográfica del municipio de Santiago de Cali.
Especificamente para estos casos de las variables de preciom y areaconst de manera individual los valores extremos son posibles, pero se verifica si son coherentes con las demás variables, es decir, si los valores atípicos realmente corresponden a viviendas ubicadas en una mejor zona y con un área construida amplia.
Para revisar lo anterior se crea la variable precio_m2 (precio de venta / área construida) porque el precio absoluto de una vivienda confunde efecto de tamaño con efecto de valorización por ubicación/estrato. Esta variable derivada permite comparar propiedades de tamaños distintos en una misma escala y es el indicador estándar utilizado en el sector inmobiliario para el análisis de valoración.
vivienda <- vivienda %>%
mutate(precio_m2 = preciom / areaconst)
#sum(is.na(vivienda$precio_m2))
#sum(is.na(vivienda$preciom) | is.na(vivienda$areaconst))
summary(vivienda$precio_m2)
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## 0.1461 1.9167 2.6400 2.7222 3.3795 9.4681 3
# Revisar los casos más extremos de precio y área
vivienda %>%
arrange(desc(preciom)) %>%
select(id, zona, tipo, estrato, areaconst, preciom, precio_m2) %>%
head(15)
## # A tibble: 15 × 7
## id zona tipo estrato areaconst preciom precio_m2
## <dbl> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
## 1 6100 Zona Oeste Casa 5 800 1999 2.50
## 2 7510 Zona Oeste Casa 6 400 1950 4.88
## 3 4609 Zona Oeste Apartamento 6 450 1950 4.33
## 4 7872 Zona Oeste Casa 6 400 1950 4.88
## 5 4564 Zona Norte Casa 5 734 1940 2.64
## 6 5861 Zona Sur Casa 6 320 1900 5.94
## 7 2758 Zona Sur Casa 6 450 1900 4.22
## 8 3202 Zona Sur Casa 6 335 1900 5.67
## 9 3652 Zona Sur Casa 6 850 1900 2.24
## 10 4576 Zona Sur Casa 6 450 1900 4.22
## 11 4731 Zona Sur Casa 6 700 1900 2.71
## 12 6817 Zona Sur Casa 6 470 1900 4.04
## 13 4308 Zona Sur Casa 6 1092 1900 1.74
## 14 6772 Zona Sur Casa 6 335 1900 5.67
## 15 8020 Zona Oeste Apartamento 6 261 1900 7.28
Teniendo en cuenta los 15 casos de viviendas con precio más altos, estos cuentan con un precio_m2 que va de 1.74 a 7.28, donde la mayoría están cercanos o por encima de la mediana general. Además, todos son de estratos 5 o 6, en zonas como Oeste, Norte o Sur, que tienden a ser de mayor valor. Es decir, que este es un patrón consistente para los precios altos.
vivienda %>%
arrange(desc(areaconst)) %>%
select(id, zona, tipo, estrato, areaconst, preciom, precio_m2) %>%
head(15)
## # A tibble: 15 × 7
## id zona tipo estrato areaconst preciom precio_m2
## <dbl> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
## 1 3324 Zona Oriente Casa 3 1745 255 0.146
## 2 5396 Zona Sur Casa 6 1600 1600 1
## 3 5684 Zona Sur Casa 6 1586 1800 1.13
## 4 315 Zona Sur Casa 6 1500 1650 1.1
## 5 5467 Zona Sur Casa 6 1500 1500 1
## 6 534 Zona Norte Casa 3 1440 370 0.257
## 7 1017 Zona Oriente Casa 3 1365 200 0.147
## 8 5016 Zona Sur Casa 6 1250 1500 1.2
## 9 37 Zona Sur Casa 5 1200 1450 1.21
## 10 5474 Zona Oeste Casa 5 1200 1200 1
## 11 4349 Zona Norte Casa 5 1188 650 0.547
## 12 4130 Zona Sur Casa 6 1100 1500 1.36
## 13 4308 Zona Sur Casa 6 1092 1900 1.74
## 14 6433 Zona Sur Casa 6 1090 1800 1.65
## 15 5978 Zona Sur Casa 6 1050 1600 1.52
Por otro lado, al revisar los casos tomando los de mayor área construida, se observa que hay tres casos pertenecientes al estrato 3. Cuando se compara el valor mínimo de las descriptivas generales de la variable precio_m2 se observa que toma un valor de 0.146 que corresponde exactamente al registro id= 3324, el que cuenta con mayor área construida, mostrando un patrón de ser una casa grande, pero a un precio por metro cuadrado muy bajo.
Por lo tanto, se verifica si esto es un patrón consistente en estrato 3 o son apenas tres registros aislados que rompen el patrón y habría que realizar alguna modificación.
# Buscar TODOS los casos con precio_m2 anormalmente bajo
vivienda %>%
filter(precio_m2 < quantile(precio_m2, 0.01, na.rm = TRUE)) %>% # percentil 1 más bajo
select(id, zona, tipo, estrato, areaconst, preciom, precio_m2) %>%
arrange(precio_m2)
## # A tibble: 84 × 7
## id zona tipo estrato areaconst preciom precio_m2
## <dbl> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
## 1 3324 Zona Oriente Casa 3 1745 255 0.146
## 2 1017 Zona Oriente Casa 3 1365 200 0.147
## 3 921 Zona Oriente Casa 3 870 190 0.218
## 4 534 Zona Norte Casa 3 1440 370 0.257
## 5 6472 Zona Sur Apartamento 5 605 170 0.281
## 6 6121 Zona Sur Apartamento 5 932 299 0.321
## 7 2732 Zona Sur Casa 4 465 175 0.376
## 8 2344 Zona Oriente Casa 3 600 250 0.417
## 9 1106 Zona Oriente Casa 3 350 150 0.429
## 10 243 Zona Norte Casa 3 435 190 0.437
## # ℹ 74 more rows
# ¿Se concentran en estrato 3? ¿En zonas específicas?
vivienda %>%
filter(precio_m2 < 1) %>%
count(zona, estrato, sort = TRUE)
## # A tibble: 15 × 3
## zona estrato n
## <chr> <dbl> <int>
## 1 Zona Oriente 3 82
## 2 Zona Sur 3 27
## 3 Zona Norte 3 21
## 4 Zona Sur 4 15
## 5 Zona Norte 5 13
## 6 Zona Sur 5 7
## 7 Zona Sur 6 6
## 8 Zona Norte 4 5
## 9 Zona Centro 3 3
## 10 Zona Centro 4 2
## 11 Zona Oeste 3 2
## 12 Zona Oeste 4 2
## 13 Zona Oeste 5 1
## 14 Zona Oriente 4 1
## 15 Zona Oriente 5 1
# Comparar precio_m2 promedio por estrato (¿estrato 3 es sistemáticamente bajo,
# o son solo estos 2-3 casos aislados?)
vivienda %>%
group_by(estrato) %>%
summarise(precio_m2_mediana = median(precio_m2, na.rm = TRUE),
precio_m2_min = min(precio_m2, na.rm = TRUE),
n = n())
## # A tibble: 5 × 4
## estrato precio_m2_mediana precio_m2_min n
## <dbl> <dbl> <dbl> <int>
## 1 3 1.71 0.146 1453
## 2 4 2.5 0.376 2129
## 3 5 2.82 0.281 2750
## 4 6 3.66 0.85 1987
## 5 NA NA Inf 3
Con lo anterior se puede confirmar que los tres casos no son errores puntuales, sino un patrón real y sistemático de estrato 3.
En particular, si fueran errores se esperaría tener solo 2 o 3 casos aislados que rompen el patrón en el estrato 3, pero se observa que la mayoría de los casos donde precio_m2 \(< 1\) están en estrato 3 e incluyen zonas como Oriente, Sur, Norte y algunos en el Centro. Además, se observa que la mediana de precio_m2 aumenta de forma consistente dependiendo el estrato, es decir que es coherente que el que tenga menor valor corresponda al estrato 3.
Se identificó entonces que los valores más bajos de precio_m2 se concentran de forma sistemática en zona Oriente y estrato 3, siendo consistente con zonas históricamente populares de la ciudad y es común encontrar lotes o casas construidas sobre terrenos grandes, pero con un valor de mercado bajo por metro cuadrado.
Antes de proceder al análisis de las variables cualitativas, se decide revisar particularmente la variable Barrio ya que hay algunos casos de barrios duplicados como “caney”/“el caney” y “cristales”/“los cristales”, por lo que se hace necesario estandarizarlos. (ver lista de barrios en el Anexo C: Listado de barrios originales)
Para estandarizar los datos se quitan los caracteres especiales como tildes, se dejan todos los nombres en minúscula y sin espacios, y se dejan solo los nombres correspondientes para no generar duplicados. (ver Anexo D: Tabla de equivalencias de estandarización de barrios)
vivienda <- vivienda %>%
mutate(
# Paso 0: corregir problemas de codificación conocidos ANTES de cualquier otra limpieza
barrio_fix = barrio %>%
iconv(from = "UTF-8", to = "UTF-8", sub = "") %>% # descarta bytes corruptos ilegibles
str_to_lower() %>%
str_trim() %>%
str_squish(),
# Paso 1: clave de comparación -> sin tildes, sin espacios, sin artículos
# (esta clave NUNCA se muestra, solo sirve para agrupar duplicados)
barrio_clave = barrio_fix %>%
stri_trans_general("Latin-ASCII") %>% # quita tildes: "río" -> "rio", "pérez" -> "perez"
str_remove("^urbanizaci[oó]n\\s+") %>%
str_remove("^(el|la|los|las)\\s+") %>%
str_remove_all("\\s+") # quita TODOS los espacios: "agua blanca" -> "aguablanca"
)
# Paso 2: dentro de cada clave, elegir como nombre oficial la variante más frecuente
mapa_barrios <- vivienda %>%
count(barrio_clave, barrio_fix, sort = TRUE) %>%
group_by(barrio_clave) %>%
slice_max(n, n = 1, with_ties = FALSE) %>% # la variante más común de cada grupo
ungroup() %>%
select(barrio_clave, barrio_std = barrio_fix)
# Paso 3: unir de vuelta para tener el nombre estandarizado definitivo
vivienda <- vivienda %>%
left_join(mapa_barrios, by = "barrio_clave")
vivienda %>%
filter(barrio_clave %in% c("aguablanca", "alamedadelrio", "alferezreal")) %>%
distinct(barrio, barrio_std, barrio_clave)
## # A tibble: 5 × 3
## barrio barrio_std barrio_clave
## <chr> <chr> <chr>
## 1 agua blanca aguablanca aguablanca
## 2 aguablanca aguablanca aguablanca
## 3 alameda del río alameda del río alamedadelrio
## 4 alameda del rio alameda del río alamedadelrio
## 5 alferez real alferez real alferezreal
# Verifica la codificación actual del vector
Encoding(vivienda$barrio) %>% table()
## .
## unknown UTF-8
## 7055 1267
# Si aparece "unknown" o "latin1" en vez de "UTF-8", fuerza la conversión
vivienda$barrio <- iconv(vivienda$barrio, from = "latin1", to = "UTF-8")
n_distinct(vivienda$barrio_std)
## [1] 357
Tras la revisión a la base de datos que contenía originalmente 437 valores distintos en la variable barrio, se evidenciaron inconsistencias de escritura, diferencias en el uso de tildes, espacios y problemas de codificación de caracteres especiales, que generaban duplicidad de categorías correspondientes al mismo barrio. Tras estandarizar los nombres, el número de barrios distintos se redujo a 357. Aun así, la variable mantiene alta fragmentación: para el Análisis de Correspondencias Múltiples se agruparon los barrios menos frecuentes en la categoría “Otros”, conservando únicamente los 15 barrios con mayor número de viviendas.
# =============================================================================
# DIAGRAMAS DE BARRAS DE VARIABLES CUALITATIVAS
# =============================================================================
# Se agrupan los barrios poco frecuentes en "Otros" porque son
#demasiadas categorías con pocos casos que distorsionan el analisis y las visualizaciones
top_barrios <- vivienda %>%
count(barrio_std, sort = TRUE) %>%
slice_head(n = 15) %>%
pull(barrio_std)
vivienda<- vivienda %>%
mutate(barrio_agr = fct_other(barrio_std, keep = as.character(top_barrios),
other_level = "Otros"))
# Función que arma un diagrama de barras con conteo + porcentaje sobre cada barra
graf_barras <- function(datos, var, titulo) {
datos %>%
count(.data[[var]]) %>%
mutate(pct = n / sum(n) * 100,
etiqueta = paste0(n, "\n(", round(pct, 1), "%)")) %>%
ggplot(aes(x = reorder(.data[[var]], -n), y = n)) +
geom_col(fill = "steelblue", alpha = 0.85) +
geom_text(aes(label = etiqueta), vjust = -0.15, size = 3) +
labs(title = titulo, x = NULL, y = "Frecuencia") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 30, hjust = 1, size= 9),
plot.margin = margin(t = 10, r = 5, b = 5, l = 5)) +
scale_y_continuous(expand = expansion(mult = c(0, 0.28))) # espacio para la etiqueta
}
g_zona <- graf_barras(vivienda, "zona", "Zona")
g_tipo <- graf_barras(vivienda, "tipo", "Tipo de vivienda")
g_estrato <- graf_barras(vivienda, "estrato", "Estrato")
g_barrio <- graf_barras(vivienda, "barrio_agr", "Barrio (agrupado, top 15 + Otros)")
# Panel: fila superior con zona/tipo/estrato (3 columnas), fila inferior
# con barrio a todo el ancho y más alto (para que quepan las 16 categorías)
(g_zona | g_tipo | g_estrato) / g_barrio +
plot_layout(heights = c(1, 1.4)) + # la fila de barrio ocupa más espacio vertical
plot_annotation(title = "Distribución de las variables cualitativas")
Se puede observar que hay una fuerte dominancia en la zona Sur (56.8%), que junto con la zona Norte acumulan alrededor del 80% de la base. En cuanto al tipo de vivienda hay una proporción razonablemente equilibrada entre casas y apartamentos.
También se evidencia que no hay presencia de estratos 1 ni 2 y que la mayoría hacen parte del estrato 5, sin embargo, se puede considerar una distribución relativamente pareja entre los estratos 4, 5, 6, no hay un estrato que domine en gran manera.
A nivel de Barrio, la oferta está fragmentada, aunque existen 357 barrios distintos (tras estandarizar), uno solo (Valle de Lili) concentra el 12.1% de las viviendas y el 47.2% restante se distribuye en cientos de barrios con presencia marginal, lo que llevó a agruparlos en la categoría ‘Otros’ para análisis posteriores.
vivienda %>%
filter(str_detect(barrio, "lferez|lfV")) %>%
distinct(barrio, barrio_fix, barrio_clave, barrio_std)
## # A tibble: 2 × 4
## barrio barrio_fix barrio_clave barrio_std
## <chr> <chr> <chr> <chr>
## 1 alferez real alferez real alferezreal alferez real
## 2 hacienda alferez real hacienda alferez real haciendaalferezreal hacienda alfe…
vivienda %>%
filter(!is.na(zona), !is.na(tipo)) %>%
ggplot(aes(x = zona, fill = tipo)) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent) +
labs(title = "Proporción de tipo de vivienda por zona",
x = NULL, y = "Proporción", fill = "Tipo") +
theme_minimal()
El gráfico permite observar que varía la presencia de casas o apartamentos dependiendo la zona donde se encuentre. En la zona Oriente y Centro predominan las casas con un 83% y 81% respectivamente. Por otro lado, en la zona Oeste predominan los apartamentos (85%), mientras que en las zonas Norte y Sur están mucho más equilibradas con una mezcla cercana a 60/40 a favor de apartamentos.
vivienda %>%
filter(!is.na(zona), !is.na(estrato)) %>%
ggplot(aes(x = zona, fill = as.factor(estrato))) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent) +
labs(title = "Proporción de estrato de vivienda por zona",
x = NULL, y = "Proporción", fill = "Estrato") +
theme_minimal()
Analizando los estratos que se encuentran en cada zona, se ve un patrón marcado mostrando que la variable zona no solo identifica un aspecto geográfico sino que también está muy correlacionada a nivel socioeconómico. La zona Oeste tiene mayor proporción del estrato 6, siendo la zona de mayor poder adquisitivo, sin apenas presencia del estrato 3. En cuanto a la zona Centro y Oriente son casi exclusivamente del estrato 3. Por su parte la zona Sur tiene una mezcla entre los estratos 4, 5, 6 con una baja proporción del estrato 3 y la zona Norte es la más heterogénea de todas con una mezcla de proporciones entre los cuatro estratos.
vivienda %>%
filter(!is.na(zona), !is.na(piso)) %>%
ggplot(aes(x = zona, fill = piso)) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent) +
labs(title = "Proporción de piso por zona",
x = NULL, y = "Proporción", fill = "Tipo") +
theme_minimal()
Este gráfico es coherente con lo que se había analizado anteriormente del tipo de vivienda, donde en las zonas de centro y oriente predominan las casas, por lo que coincide el hecho de que estas solo tengan un piso o incluso 3 y rara vez superan los 3 niveles, que se observan casos más que todo en la zona Centro que es donde pueden presentarse edificios empresariales de hasta 11 pisos. Las zonas Norte, Sur y Oeste son las únicas que llegan hasta el piso 12 con una distribución mucho más repartida entre todos los niveles, lo cual es coherente porque son las zonas donde hay mayor presencia de edificios.
# Precio por zona
vivienda %>%
filter(!is.na(zona), !is.na(preciom)) %>%
ggplot(aes(x = reorder(zona, preciom, median), y = preciom, fill = zona)) +
geom_boxplot(alpha = 0.8, outlier.alpha = 0.2) +
scale_y_continuous(labels = scales::comma) +
labs(title = "Precio de vivienda por zona", x = NULL, y = "Precio (millones COP)") +
theme_minimal() + theme(legend.position = "none")
El precio mediano crece de forma escalonada según la zona, en el mismo orden que ya se había identificado por estrato. Zona Oriente (~220 millones) y Zona Centro (~310 millones) muestran las medianas más bajas y cajas más compactas, seguidas de Zona Norte y Zona Sur (~320-340 millones, con numerosos atípicos superiores hasta cerca de 2,000 millones), y finalmente Zona Oeste, que se despega claramente del resto con una mediana de ~580 millones y una caja mucho más ancha (desde ~400 hasta ~900 millones), sin apenas atípicos por encima del bigote. En este último punto se puede señalar que mientras las zonas Norte y Sur tienen colas de atípicos muy largas y densas (muchas viviendas puntuales muy caras rompiendo con el resto del patrón de su zona), en Oeste el precio alto no es atípico sino que es común al segmento completo, lo que confirma que es una zona homogéneamente superior.
# Precio_m2 por estrato
vivienda%>%
filter(!is.na(estrato), !is.na(precio_m2)) %>%
ggplot(aes(x = reorder(estrato, precio_m2, median), y = precio_m2, fill = estrato)) +
geom_boxplot(alpha = 0.8, outlier.alpha = 0.2) +
labs(title = "Precio por m² según estrato", x = "Estrato", y = "Precio/m² (millones COP)") +
theme_minimal() + theme(legend.position = "none")
Esta gráfica confirma la existencia de un patrón de valorización creciente y consistente. La mediana sube de forma monótona de 1.71 millones/\(m^2\) en estrato 3 a 3.66 millones/\(m^2\) en estrato 6, con las cajas también desplazándose hacia arriba sin solaparse casi nada entre estratos consecutivos, es decir, la separación no es solo en el promedio, sino en la distribución completa. El estrato 3 es también el único que muestra atípicos hacia abajo (el de menor \(m^2\) que se identificó anteriormente), consistente con el segmento de casas grandes de bajo valor por metro.
# Área construida por tipo de vivienda
vivienda %>%
filter(!is.na(areaconst), !is.na(tipo)) %>%
ggplot(aes(x = reorder(tipo, areaconst, median), y = areaconst, fill = tipo)) +
geom_boxplot(alpha = 0.8, outlier.alpha = 0.2) +
labs(title = "Área construida según tipo de vivienda", x = NULL, y = "Área (m²)") +
theme_minimal() + theme(legend.position = "none")
Las casas muestran en general ser más grandes que los apartamentos. La mediana de casa (~230 \(m^2\)) casi triplica la de apartamento (~80 \(m^2\)) y la caja completa de casa está desplazada muy por encima de la de apartamento, con apenas superposición entre ambas distribuciones. Los apartamentos muestran una caja comprimida cerca de cero con una cola densa de atípicos hacia arriba (edificios de gran formato o dúplex atípicos), mientras que las casas tienen una dispersión natural mucho mayor incluso dentro del rango “normal” (bigote superior cercano a 700 \(m^2\)), reflejo de que el terreno de una casa varía mucho más que la planta de un apartamento estandarizado.
vivienda %>%
filter(!is.na(longitud), !is.na(latitud), !is.na(zona)) %>%
ggplot(aes(x = longitud, y = latitud, color = zona)) +
geom_point(alpha = 0.4, size = 0.8) +
coord_fixed() +
labs(title = "Distribución geográfica de las viviendas por zona",
x = "Longitud", y = "Latitud", color = "Zona") +
theme_minimal()
Se observa que los valores de latitud y longitud corresponden correctamente con las zonas específicas de la ciudad, formando segmentos bien delimitados. La zona centro por su reducido tamaño muestral, se observa como un núcleo geográfico pequeño rodeado por las demás zonas. Se identifica una franja de transición en el sector central de la ciudad donde las zonas Norte, Oeste, Oriente y Centro se entremezclan, reflejando probablemente los límites administrativos del área metropolitana más densa.
mat_cor<- vivienda %>%
select(preciom, areaconst, precio_m2, parqueaderos, banios, habitaciones) %>%
cor(use = "pairwise.complete.obs")
corrplot(mat_cor, method = "color", type = "upper", addCoef.col = "black",
tl.col = "black", tl.srt = 45, number.cex = 0.7,
title = "Correlación entre variables numéricas", mar = c(0, 0, 2, 0))
La matriz de correlación confirma la existencia de un grupo de variables asociadas al tamaño/magnitud de la vivienda (preciom, areaconst, parqueaderos, banios), con correlaciones entre 0.57 y 0.69, que se esperaría que posteriormente definan el primer componente del ACP. Se destaca que banios (r=0.67) explica el precio considerablemente mejor que habitaciones (r=0.26), sugiriendo que el número de baños es un mejor proxy de la categoría de la vivienda. La variable precio_m2, por su parte, muestra correlaciones bajas o negativas con las demás variables (r=-0.27 con área, r=-0.33 con habitaciones), confirmando que captura una dimensión de valorización independiente del tamaño, consistente con economías de escala en el precio por metro cuadrado, y validando su inclusión como variable diferenciada en el análisis multivariado.
faltantes <- colSums(is.na(vivienda)) %>% sort(decreasing = TRUE)
faltantes
## piso parqueaderos id zona estrato areaconst
## 2638 1605 3 3 3 3
## banios habitaciones tipo barrio longitud latitud
## 3 3 3 3 3 3
## precio_m2 barrio_fix barrio_clave barrio_std barrio_agr preciom
## 3 3 3 3 3 2
ggplot(data.frame(variable = names(faltantes), n_na = faltantes),
aes(x = reorder(variable, n_na), y = n_na)) +
geom_col(fill = "steelblue") +
coord_flip() +
labs(title = "Datos faltantes por variable",
x = NULL, y = "N° de valores faltantes") +
theme_minimal() -> g_na
g_na
En general, las variables no tienen gran cantidad de datos faltantes a excepción de parqueaderos y pisos que cuentan con un 19% y 31% de datos faltantes respectivamente.
Como la mayoría de variables tienen 3 datos faltantes, se sospecha que pueden coincidir los 3 registros con información faltante.
# Identificar y excluir específicamente las filas completamente vacías
filas_vacias <- which(is.na(vivienda$zona) & is.na(vivienda$estrato) &
is.na(vivienda$barrio) & is.na(vivienda$tipo))
filas_vacias
## [1] 8320 8321 8322
vivienda <- vivienda %>%
filter(!row_number() %in% filas_vacias)
dim(vivienda) # 8319 filas en vez de 8322
## [1] 8319 18
Se identificó que los 2-3 valores faltantes presentes en la mayoría de las variables correspondían, en realidad, a las mismas tres observaciones (filas 8320-8322), las cuales se encontraban completamente vacías a excepción de un valor de precio en un caso. Dado que estas filas no contienen información sustantiva sobre las características de la vivienda, no corresponden a un caso de datos faltantes susceptible de imputación, sino a registros sin contenido analítico, por lo que se excluyeron del análisis (8,322 → 8,319 observaciones).
Con los datos restantes, se comprueba qué tipos de datos faltantes se tienen en las variables restantes y de esta forma hacer el tratamiento correspondiente.
colSums(is.na(vivienda))
## id zona piso estrato preciom areaconst
## 0 0 2635 0 0 0
## parqueaderos banios habitaciones tipo barrio longitud
## 1602 0 0 0 0 0
## latitud precio_m2 barrio_fix barrio_clave barrio_std barrio_agr
## 0 0 0 0 0 0
\[ H_0: \text{Los datos faltantes son MCAR} \]
\[ H_1: \text{Los datos faltantes no son MCAR} \]
#se comprueba solo usando las variables originales
vivienda %>%
select(id, zona, piso, estrato, preciom, areaconst, parqueaderos,
banios, habitaciones, tipo, barrio, longitud, latitud) %>%
mcar_test() # H0: los datos SON MCAR. p < 0.05 -> se rechaza MCAR
## # A tibble: 1 × 4
## statistic df p.value missing.patterns
## <dbl> <dbl> <dbl> <int>
## 1 2211. 35 0 4
Tras excluir las tres observaciones sin contenido analítico y restringir el test a las variables originales de la base, el resultado del test de Little arroja un valor p muy cercano a cero, por lo que se rechaza la hipótesis nula, indica que hay evidencia estadística fuerte de que el patrón de valores faltantes está sistemáticamente relacionado con otras variables del conjunto de datos.
El siguiente paso será revisar para cada variable con valores faltantes, ¿con cuáles variables se asocia esta ausencia de dato?, para lo que se usa la prueba chi-cuadrado para verificar si existe una asociación real entre dos variables categóricas o si el patrón que se observa podría deberse simplemente al azar.
\[ H_0: \text{Las dos variables son independientes} \]
\[ H_1: \text{Las dos variables no son independientes} \]
# =============================================================================
# PISO
# =============================================================================
vivienda <- vivienda %>%
mutate(falta_piso = is.na(piso))
# ¿Se asocia con tipo?
chisq.test(table(vivienda$tipo, vivienda$falta_piso))
##
## Pearson's Chi-squared test with Yates' continuity correction
##
## data: table(vivienda$tipo, vivienda$falta_piso)
## X-squared = 128.1, df = 1, p-value < 2.2e-16
# ¿Se asocia con zona?
chisq.test(table(vivienda$zona, vivienda$falta_piso))
##
## Pearson's Chi-squared test
##
## data: table(vivienda$zona, vivienda$falta_piso)
## X-squared = 148.66, df = 4, p-value < 2.2e-16
# ¿Se asocia con variables numéricas? (t-test)
t.test(areaconst ~ falta_piso, data = vivienda)
##
## Welch Two Sample t-test
##
## data: areaconst by falta_piso
## t = -6.3496, df = 4604, p-value = 2.368e-10
## alternative hypothesis: true difference in means between group FALSE and group TRUE is not equal to 0
## 95 percent confidence interval:
## -29.22303 -15.43460
## sample estimates:
## mean in group FALSE mean in group TRUE
## 167.8624 190.1912
t.test(preciom ~ falta_piso, data = vivienda)
##
## Welch Two Sample t-test
##
## data: preciom by falta_piso
## t = -4.065, df = 4802.6, p-value = 4.88e-05
## alternative hypothesis: true difference in means between group FALSE and group TRUE is not equal to 0
## 95 percent confidence interval:
## -47.91755 -16.73646
## sample estimates:
## mean in group FALSE mean in group TRUE
## 423.665 455.992
Las cuatro pruebas para verificar la asociación de variable con la ausencia de datos en Piso rechazan independencia, es decir, que los datos no faltan al azar, sino que dependen de otras variables observables (tipo, zona, área, precio), lo cual es coherente con un tipo de datos faltante MAR donde el mecanismo de faltante se explica completamente por variables disponibles en la base, sin necesidad de invocar el valor mismo de piso que falta. Por lo tanto, se propone hacer la imputación de los datos con KNN usando tipo, zona, areaconst, estrato como predictores, ya que es la elección más adecuada.
# =============================================================================
# PARQUEADEROS
# =============================================================================
vivienda <- vivienda %>%
mutate(falta_parqueadero = is.na(parqueaderos))
chisq.test(table(vivienda$estrato, vivienda$falta_parqueadero))
##
## Pearson's Chi-squared test
##
## data: table(vivienda$estrato, vivienda$falta_parqueadero)
## X-squared = 1518.7, df = 3, p-value < 2.2e-16
chisq.test(table(vivienda$zona, vivienda$falta_parqueadero))
##
## Pearson's Chi-squared test
##
## data: table(vivienda$zona, vivienda$falta_parqueadero)
## X-squared = 770.91, df = 4, p-value < 2.2e-16
t.test(areaconst ~ falta_parqueadero, data = vivienda)
##
## Welch Two Sample t-test
##
## data: areaconst by falta_parqueadero
## t = 8.4626, df = 2543.4, p-value < 2.2e-16
## alternative hypothesis: true difference in means between group FALSE and group TRUE is not equal to 0
## 95 percent confidence interval:
## 24.74156 39.66546
## sample estimates:
## mean in group FALSE mean in group TRUE
## 181.1364 148.9329
t.test(preciom ~ falta_parqueadero, data = vivienda)
##
## Welch Two Sample t-test
##
## data: preciom by falta_parqueadero
## t = 24.172, df = 3099.6, p-value < 2.2e-16
## alternative hypothesis: true difference in means between group FALSE and group TRUE is not equal to 0
## 95 percent confidence interval:
## 166.8941 196.3602
## sample estimates:
## mean in group FALSE mean in group TRUE
## 468.8806 287.2534
En el caso de la variable Parqueaderos ocurre lo mismo y muestra una gran diferencia entre el precio promedio de las viviendas con dato (468.9 M) y sin dato (287.3 M).
Sin embargo, un punto a tener en cuenta es que con estas pruebas solo se puede comprobar si el dato es MAR, no distingue si es MNAR, pero se puede observar que el valor mínimo de parqueaderos para los datos que se tienen es 1, lo que podría sugerir que el formulario de origen probablemente solo registra el dato cuando la vivienda sí tiene parqueadero. Combinado con que las viviendas sin dato son sistemática y marcadamente más pequeñas y baratas, la interpretación más razonable es que el “NA” representa un valor real (0), no una casilla que se olvidó de diligenciar y por eso la imputación correcta será la regla de dominio (NA → 0), no un método estadístico.
Con los resultados obtenidos anteriormente, se procede entonces a realizar el tratamiento de los datos faltantes.
La variable Piso que cuenta con un 31,7% de datos faltantes mostró que se presentaba un mecanismo de datos faltantes al azar condicional (MAR), por lo tanto se opta por imputar mediante el algoritmo k-vecinos más cercanos (KNN, K=5), utilizando las variables tipo, zona, areaconst, estrato como predictoras.
Por otro lado, para la variable Parqueaderos se consideró que el tipo de dato faltante que presentaba era de tipo MNAR puesto que el valor mínimo observado en la variable es 1 (nunca 0) y las viviendas sin dato registrado presentan en general un promedio de precio menor (287.3 vs. 468.9 millones COP) y menor área (148.9 vs. 181.1 m²) que aquellas con dato disponible. Esto sugiere que la ausencia de registro corresponde a viviendas que efectivamente no cuentan con parqueadero, más que a un vacío de captura. Por tanto, se imputarán los valores faltantes como 0, en lugar de aplicar un método de imputación estadística genérico.
# 1. parqueaderos (MNAR): regla de dominio, NA -> 0
vivienda_clean <- vivienda %>%
mutate(parqueaderos = ifelse(is.na(parqueaderos), 0, parqueaderos))
# 2. piso (MAR): KNN usando tipo, zona, areaconst, estrato como predictores
# (ya no hay riesgo de que estos predictores tengan NA)
vivienda_clean <- kNN(vivienda_clean,
variable = "piso",
dist_var = c("tipo", "zona", "areaconst", "estrato"),
k = 5,
imp_var = FALSE)
# 3. Conversión final a factor
vivienda_clean <- vivienda_clean %>%
mutate(estrato = factor(estrato, ordered = TRUE),
piso = factor(piso, ordered = TRUE),
zona = as.factor(zona),
tipo = as.factor(tipo),
barrio_std = as.factor(barrio_std),
barrio_agr = as.factor(barrio_agr))
#str(vivienda_clean)
#eliminar variables que no se necesitaran en análisis posterior
vivienda_clean <- vivienda_clean %>%
select(-barrio, -barrio_fix, -barrio_clave, -falta_piso, -falta_parqueadero)
# Verificación: ya no debería quedar ningún NA
colSums(is.na(vivienda_clean))
## id zona piso estrato preciom areaconst
## 0 0 0 0 0 0
## parqueaderos banios habitaciones tipo longitud latitud
## 0 0 0 0 0 0
## precio_m2 barrio_std barrio_agr
## 0 0 0
Una vez depurada y preparada la base de datos, se procede a aplicar un conjunto de técnicas de estadística multivariada orientadas a explorar la estructura conjunta de las variables que caracterizan la oferta de vivienda en la ciudad de Cali. A diferencia del análisis univariado y bivariado presentado en las secciones anteriores, estas técnicas permiten estudiar simultáneamente las relaciones entre múltiples variables, con el fin de identificar patrones, agrupaciones y asociaciones que no son evidentes al examinar las variables de forma aislada.
Se usarán técnicas como el Análisis de componentes principales (ACP), Análisis de conglomerados y Análisis de correspondencias, las cuales en conjunto permiten construir una visión integral y complementaria del mercado de vivienda analizado, combinando la reducción de dimensionalidad, la segmentación de propiedades y el análisis de asociaciones categóricas, como insumo para la toma de decisiones estratégicas de la empresa inmobiliaria.
Se realiza el análisis de componentes principales (ACP) ya que permite sintetizar la información de múltiples variables cuantitativas correlacionadas entre sí en un número reducido de componentes no correlacionados, facilitando tanto la interpretación de los factores que explican la variación del mercado como la visualización conjunta de las características físicas, el precio y la ubicación de las viviendas.
En este caso, para el análisis se toman únicamente las variables numéricas preciom, areaconst, parqueaderos, banios, habitaciones, precio_m2 y se incluyeron las variables latitud y longitud como variables cuantitativas suplementarias, con el fin de examinar el patrón geográfico de los componentes sin que la ubicación influyera en la construcción de las componentes. Por otro lado, las variables zona, tipo, estrato y barrio_agr se incorporaron como variables cualitativas suplementarias, dado que el ACP clásico opera únicamente sobre variables cuantitativas; su proyección sobre el plano factorial permite interpretar la relación entre estas categorías y la estructura de componentes obtenida a partir de las variables activas de precio y tamaño.
vivienda_pca <- vivienda_clean %>%
select(preciom, areaconst, precio_m2, parqueaderos, banios, habitaciones,
latitud, longitud, zona, tipo, estrato, barrio_agr) %>%
as.data.frame()
rownames(vivienda_pca) <- vivienda_clean$id
pos_quanti_sup <- which(names(vivienda_pca) %in% c("latitud", "longitud"))
pos_quali_sup <- which(names(vivienda_pca) %in% c("zona", "tipo", "estrato", "barrio_agr"))
res.pca <- PCA(vivienda_pca,
quanti.sup = pos_quanti_sup,
quali.sup = pos_quali_sup,
scale.unit = TRUE, # estandariza: las variables tienen escalas muy distintas
ncp = 5,
graph = FALSE)
El gráfico de “screeplot” muestra la varianza explicada por cada componente principal. Los primeros componentes suelen explicar la mayor parte de la varianza, por lo que se utiliza para identificar la varianza explicada por cada uno y seleccionar la cantidad de componentes a retener y así reducir la dimensionalidad del dataset.
eig_tab <- as.data.frame(res.pca$eig)
print(round(eig_tab, 2))
## eigenvalue percentage of variance cumulative percentage of variance
## comp 1 3.13 52.16 52.16
## comp 2 1.53 25.49 77.65
## comp 3 0.58 9.70 87.35
## comp 4 0.43 7.17 94.53
## comp 5 0.25 4.17 98.70
g_scree <- fviz_eig(res.pca, addlabels = TRUE, barfill = "steelblue",
barcolor = "steelblue",
main = "Inercia explicada por componente (Scree plot)")
g_scree
El scree plot muestra que el primer componente explica el 52.2% de la varianza total y el segundo un 25.5% adicional, acumulando entre ambos un 77.7% de la inercia total con solo dos dimensiones. La marcada disminución en la varianza explicada a partir del tercer componente (9.7%, 7.2% y 4.2% para las dimensiones 3, 4 y 5, respectivamente) evidencia un punto de quiebre claro, consistente con el criterio de Kaiser (umbral de referencia: 100/6 ≈ 16.7% por componente dado el número de variables activas). Esto respalda la decisión de concentrar la interpretación del análisis en los dos primeros componentes principales, los cuales resumen adecuadamente la estructura de variación del mercado de vivienda analizado.
round(res.pca$var$contrib, 2) # % de contribución de cada variable a cada eje
## Dim.1 Dim.2 Dim.3 Dim.4 Dim.5
## preciom 23.21 9.98 0.32 16.68 4.85
## areaconst 23.15 4.73 15.03 18.76 2.88
## precio_m2 0.05 55.69 20.50 0.53 3.91
## parqueaderos 16.85 8.07 17.82 56.77 0.42
## banios 24.58 0.17 12.13 0.01 62.93
## habitaciones 12.15 21.36 34.20 7.26 25.00
g_contrib1 <- fviz_contrib(res.pca, choice = "var", axes = 1, top = 10,
fill = "darkorange") +
labs(title = "Contribución variables al eje 1 (Dim 1)")
g_contrib2 <- fviz_contrib(res.pca, choice = "var", axes = 2, top = 10,
fill = "darkorange") +
labs(title = "Contribución variables al eje 2 (Dim 2)")
g_contrib_panel <- arrangeGrob(g_contrib1, g_contrib2, ncol = 2)
grid::grid.draw(g_contrib_panel)
El eje 1 se define principalmente por las variables asociadas al tamaño y las comodidades de la vivienda (banios: 24.6%, preciom: 23.2%, areaconst: 23.2%, parqueaderos: 16.9%), pudiendo interpretarse como un componente general de ‘magnitud/valor de la vivienda’. En contraste, el eje 2 está dominado por precio_m2 (55.7%) y habitaciones (21.4%), configurando un componente de ‘valorización relativa por metro cuadrado’, prácticamente independiente del primero (precio_m2 contribuye solo 0.05% al eje 1 y banios apenas 0.2% al eje 2). Esta separación confirma que el precio absoluto y el precio por metro cuadrado capturan dimensiones distintas y complementarias del mercado inmobiliario analizado.
round(res.pca$var$cos2, 2)
## Dim.1 Dim.2 Dim.3 Dim.4 Dim.5
## preciom 0.73 0.15 0.00 0.07 0.01
## areaconst 0.72 0.07 0.09 0.08 0.01
## precio_m2 0.00 0.85 0.12 0.00 0.01
## parqueaderos 0.53 0.12 0.10 0.24 0.00
## banios 0.77 0.00 0.07 0.00 0.16
## habitaciones 0.38 0.33 0.20 0.03 0.06
corrplot(res.pca$var$cos2, is.corr = FALSE, method = "color",
addCoef.col = "black", tl.col = "black", number.cex = 0.7,
title = "Cos2 de las variables por componente", mar = c(0, 0, 2, 0))
g_var <- fviz_pca_var(res.pca, col.var = "cos2",
gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
repel = TRUE,
title = "Círculo de correlaciones - ACP (color = cos2)")
g_var
El \(Cos^2\) mide qué tan bien queda representada cada variable en el plano factorial, cuanto más cerca de 1, más fielmente se puede ver representada esa variable en el gráfico; por el contrario, entre más bajo sea, su representación se verá más distorsionada en las dos dimensiones específicas, aunque la variable pueda ser importante en otro eje.
El análisis de \(cos^2\) confirma que, calculando la calidad total de representación para cada variable en este primer plano factorial, se obtiene que preciom (0.88), precio_m2 (0.85), areaconst (0.79) y banios (0.77) presentan una calidad de representación alta en el plano formado por los dos primeros componentes, siendo fielmente interpretables en el círculo de correlaciones. En contraste, parqueaderos (0.65) y habitaciones (0.71) muestran una representación más moderada: parqueaderos concentra parte relevante de su varianza en la cuarta dimensión (\(cos^2\)=0.24), mientras que habitaciones se distribuye de forma casi equitativa entre los dos primeros ejes (0.38 y 0.33 respectivamente), lo que se refleja en la orientación diagonal de su vector en el círculo de correlaciones. Esto sugiere que, si bien estas dos variables contribuyen a la definición de los ejes, su posición en el plano (1,2) muestra que no capturan la totalidad de su variabilidad en estas dos dimensiones.
# Coordenadas, contribución y cos2 de cada vivienda en los primeros ejes
head(res.pca$ind$coord)
## Dim.1 Dim.2 Dim.3 Dim.4 Dim.5
## 1147 -0.2304799 -0.29672641 1.7675086 0.63871410 0.7643510
## 1169 -1.0425630 0.04908300 -0.2047370 -0.09770840 0.2330312
## 1350 -0.1157530 -0.90423114 -0.8700345 0.42525745 0.5701116
## 5992 1.2888789 -0.59986181 -1.1178995 0.62903824 -1.3116019
## 1212 -1.2267759 0.19029529 -0.0200995 0.05276753 0.1978058
## 1724 -0.9217953 0.05724045 0.1808391 0.10056381 -0.3984639
head(res.pca$ind$contrib)
## Dim.1 Dim.2 Dim.3 Dim.4 Dim.5
## 1147 2.040471e-04 6.919256e-04 6.449655e-02 1.139170e-02 0.028076750
## 1169 4.175119e-03 1.893255e-05 8.653785e-04 2.665873e-04 0.002609692
## 1350 5.146703e-05 6.425485e-03 1.562739e-02 5.049862e-03 0.015619997
## 5992 6.380996e-03 2.827805e-03 2.579996e-02 1.104917e-02 0.082673284
## 1212 5.780891e-03 2.845791e-04 8.340342e-06 7.775156e-05 0.001880352
## 1724 3.263873e-03 2.574856e-05 6.751468e-04 2.823964e-04 0.007630254
head(res.pca$ind$cos2)
## Dim.1 Dim.2 Dim.3 Dim.4 Dim.5
## 1147 0.012307089 0.020398664 0.7237896909 0.094515344 0.13535522
## 1169 0.907120538 0.002010586 0.0349826838 0.007967540 0.04531986
## 1350 0.006350068 0.387500687 0.3587455174 0.085707224 0.15403996
## 5992 0.308047826 0.066726197 0.2317391869 0.073374988 0.31900539
## 1212 0.949798053 0.022853718 0.0002549595 0.001757253 0.02469329
## 1724 0.804678842 0.003102842 0.0309698044 0.009577167 0.15035972
El análisis de individuos evidencia que las viviendas con mayor contribución a los dos primeros componentes corresponden a los casos atípicos identificados previamente en el análisis exploratorio. Las propiedades de mayor precio y área (ids 4564, 3652) dominan la contribución al eje 1, mientras que los casos extremos de precio por metro cuadrado, tanto el mínimo (id 534, estrato 3) como el máximo observado (id 8020), dominan la contribución al eje 2, validando la coherencia entre los hallazgos exploratorios iniciales y la estructura factorial obtenida.
# Viviendas que más contribuyen a la formación del eje 1 y del eje 2
# (posibles atípicos / propiedades "premium" que tensionan la estructura)
top_contrib_dim1 <- res.pca$ind$contrib[order(-res.pca$ind$contrib[, 1]), ][1:10, 1]
top_contrib_dim2 <- res.pca$ind$contrib[order(-res.pca$ind$contrib[, 2]), ][1:10, 2]
top_contrib_dim1
## 5684 5396 3652 5004 6766 8298 673 5978
## 0.3975061 0.2805041 0.2765774 0.2740874 0.2656938 0.2553888 0.2511394 0.2485029
## 5902 4564
## 0.2366920 0.2345307
top_contrib_dim2
## 534 7366 4112 5094 8020 921 684 4508
## 0.2686535 0.2565874 0.2295120 0.2158579 0.1913831 0.1770036 0.1659491 0.1609315
## 5151 5474
## 0.1561168 0.1561038
g_ind_cos2 <- fviz_pca_ind(res.pca, geom = "point", col.ind = "cos2",
gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
alpha.ind = 0.6,
title = "Individuos - ACP (color = calidad de representación, cos2)")
g_ind_cos2
El gráfico de individuos coloreado según la calidad de representación muestra un conjunto de puntos azules concentrados cerca del origen, rodeado de una periferia de puntos naranjas, siendo los puntos azules viviendas promedio que no se destacan particularmente ni en tamaño, precio, ni en valorización, así que su varianza está repartida entre más ejes. Por otro lado, los puntos naranjas de la periferia, son las viviendas atípicas que están muy bien representadas por el plano (1,2), por lo que su comportamiento extremo queda capturado casi por completo con estas dos dimensiones.
g_ind_zona <- fviz_pca_ind(res.pca, geom = "point", habillage = "zona",
addEllipses = TRUE, ellipse.level = 0.95,
alpha.ind = 0.5,
title = "Individuos - ACP coloreados por zona (elipses 95%)")
g_ind_zona
Las elipses de zona se superponen bastante entre sí, especialmente cerca del origen debido a que zona se incluyó como una variable suplementaria. Sin embargo, la elipse de la zona Oeste es la que más resalta y está más desplazada hacia la derecha y hacia arriba respecto a las demás, es decir, hacia valores más altos tanto en Dim.1 (tamaño/precio) como en Dim.2 (precio_m2), lo cual es consistente con las conclusiones que se tenían para esta zona considerándola de un nivel superior. Las zonas Centro, Norte y Oriente quedan más concentradas cerca del origen, reflejo de que son zonas con oferta más homogénea (menos viviendas “extremas” en tamaño o valorización).
g_biplot <- fviz_pca_biplot(res.pca, geom.ind = "point",
habillage = "tipo",
addEllipses = TRUE, alpha.ind = 0.4,
col.var = "black", repel = TRUE,
title = "Biplot ACP: individuos (por tipo) + variables activas") +
theme_minimal()
g_biplot
La interpretación conjunta de los dos primeros componentes permite caracterizar el mercado de vivienda en dos dimensiones complementarias: el componente 1 refleja el tamaño y la capacidad física de la vivienda (precio, área, baños, habitaciones y parqueaderos), diferenciando parcialmente entre casas, las cuales se encuentran en la nube desplazadas hacia valores más altos, y apartamentos, concentrados hacia valores más bajos. El componente 2, dominado por precio_m2, captura la valorización relativa por metro cuadrado, independiente del tamaño.
plot(res.pca, choix = "ind", invisible = "ind", autoLab = "yes",
title = "Categorías suplementarias (zona, tipo, estrato, barrio) en el plano (1,2)")
## Ignoring unknown labels:
## • colour : "none"
La proyección de las categorías suplementarias sobre este plano revela cuatro perfiles de mercado: un segmento premium de alta magnitud y alta valorización (estrato 6, barrios como Pance y Ciudad Jardín); un segmento de apartamentos compactos pero de alta valorización por metro (Normandía, Santa Teresita); viviendas de mayor tamaño con valorización moderada (tipo Casa); y un segmento de menor valor en ambas dimensiones, concentrado en estratos 3-4 y las zonas Oriente, Centro y Norte.
Con el objetivo de crear segmentos que agrupen las viviendas de tal forma que se puedan crear focos de diferentes clientes para la inmobiliaria, se procede a realizar una metodología de segmentación con el fin de que se tengan grupos muy homogéneos dentro de ellos (minimizar varianza intra-clase) y heterogéneos entre ellos (maximizar varianza inter-clase).
Se trabaja sobre las mismas variables estandarizadas usadas en el PCA (preciom,areaconst, precio_m2, parqueaderos, banios, habitaciones). Dado que la base de datos tiene más de 8.000 registros, se utiliza el método de k-means como método principal, ya que es menos costoso computacionalmente, además, cada grupo queda representado directamente por un ‘centroide’ (el perfil promedio del grupo), lo que facilita la creación de perfiles comerciales.
Para elegir el número de cluster, se usa el Método del codo el cual consiste en ejecutar k-means para distintos valores de k (en este caso 2-8) y calcular, para cada uno, la suma de cuadrados intra-cluster (WSS, la misma varianza intra-clase), por lo tanto, mientras más clusters se usan, cada vivienda queda más cerca de su propio centroide, así que la WSS siempre disminuye a medida que k crece, pero no de forma uniforme, puesto que a partir de cierto punto los clusters adicionales solo separan grupos que ya eran bastante homogéneos entre sí.
Al graficar la WSS contra el número de clusters, esta transición se ve como un “codo”: una caída pronunciada seguida de una curva que se aplana. El número de clusters que corresponde a ese punto de quiebre se toma como el óptimo, porque representa el mejor equilibrio entre simplicidad (pocos grupos) y capacidad explicativa (baja variabilidad dentro de cada grupo), por lo que agregar más clusters después de ese punto complica la interpretación sin aportar una mejora proporcional en la calidad de la segmentación.
vars_cluster <- vivienda_clean %>%
select(preciom, areaconst, precio_m2, parqueaderos, banios, habitaciones) %>%
as.data.frame()
rownames(vars_cluster) <- vivienda_clean$id
vars_cluster_z <- scale(vars_cluster) # estandarización obligatoria: las
# variables tienen escalas muy distintas
set.seed(1234)
wss <- sapply(2:8, function(k) {
km_k <- kmeans(vars_cluster_z, centers = k, nstart = 25)
km_k$tot.withinss
})
codo_tab <- data.frame(k = 2:8, wss = round(wss, 1))
print(codo_tab)
## k wss
## 1 2 32574.1
## 2 3 26324.5
## 3 4 22058.8
## 4 5 19895.8
## 5 6 17925.2
## 6 7 16732.1
## 7 8 15633.2
x <- codo_tab$k; y <- codo_tab$wss
x1 <- x[1]; y1 <- y[1]; x2 <- x[length(x)]; y2 <- y[length(y)]
dist_perp <- abs((y2 - y1) * x - (x2 - x1) * y + x2 * y1 - y2 * x1) /
sqrt((y2 - y1)^2 + (x2 - x1)^2)
k_optimo <- x[which.max(dist_perp)]
cat("k óptimo según el método del codo:", k_optimo, "\n")
## k óptimo según el método del codo: 4
g_codo <- ggplot(codo_tab, aes(x = k, y = wss)) +
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 = "Método del codo - variables originales estandarizadas",
x = "Número de conglomerados (k)", y = "Suma de cuadrados intra-cluster (WSS)")+
theme(
plot.title = element_text(size = 23, face = "bold"),
axis.title = element_text(size = 16),
axis.text = element_text(size = 18)
)
g_codo
El método del codo muestra una disminución pronunciada de la suma de cuadrados intra-cluster (WSS) hasta k=4, seguida de una reducción marginal y aproximadamente constante para valores superiores de k, evidenciando un punto de quiebre claro en k=4.
Adicionalmente, para complementar la evaluación del método del codo se usa el dendrograma jerárquico para verificar la selección del número óptimo de conglomerados (\(k\)).
El dendrograma expone la estructura jerárquica natural y continua subyacente en los datos sin imponer una partición rígida a priori. De esta manera, confirmar el resultado numérico del Método del codo mediante la distancia de fusión y la prominencia de las ramas en el árbol jerárquico permite validar que el número de grupos seleccionado no responda únicamente a un óptimo local del algoritmo no jerárquico, sino que refleje divisiones estructuralmente significativas y coherentes en el mercado analizado.
set.seed(1234)
idx_dendro <- sample(seq_len(nrow(vars_cluster_z)), size = min(30, nrow(vars_cluster_z)))
muestra_dendro <- vars_cluster_z[idx_dendro, ]
rownames(muestra_dendro) <- paste0("V", seq_len(nrow(muestra_dendro)))
dist_dendro <- dist(muestra_dendro, method = "euclidean")
hc_muestra <- hclust(dist_dendro, method = "ward.D2")
plot(hc_muestra, main = "Dendrograma (muestra de 30 viviendas)", xlab = "", sub = "",
cex.main = 2,
cex.axis = 1.5,
cex.lab = 1.5,
cex = 0.9)
rect.hclust(hc_muestra, k = 4, border = "#034D94")
# Tabla de alturas de fusión: cuantifica el "salto" entre cortes sucesivos,
# como respaldo numérico al corte que se observa visualmente en el dendrograma
alturas <- sort(hc_muestra$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 <- c(round(-diff(alturas_tab$altura_de_fusion), 2), NA)
alturas_tab
## k_al_cortar altura_de_fusion salto_respecto_al_siguiente
## 1 2 9.18 2.27
## 2 3 6.91 2.13
## 3 4 4.78 0.55
## 4 5 4.23 0.77
## 5 6 3.46 0.12
## 6 7 3.34 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.
La tabla de alturas de fusión del clustering jerárquico, confirma el número de clusters sugerido previamente. Los saltos más pronunciados se observan entre las particiones de 2 a 3 y de 3 a 4 grupos (2.27 y 2.13, respectivamente), mientras que las divisiones posteriores presentan saltos considerablemente menores. La coincidencia entre el \(k=4\) sugerido por el método del codo (sobre k-means y la base completa) y el \(k=4\) sugerido por el jerárquico (sobre una muestra independiente) constituye evidencia adicional a favor de este número de segmentos.
set.seed(1234)
res.kmeans <- kmeans(vars_cluster_z, centers = 4, nstart = 25)
table(res.kmeans$cluster)
##
## 1 2 3 4
## 661 4433 1558 1667
vivienda_clean$cluster <- factor(res.kmeans$cluster)
g_clusters <- fviz_cluster(res.kmeans, data = vars_cluster_z,
geom = "point", ellipse.type = "convex",
palette = "jco", alpha = 0.5,
main = "Conglomerados (k-means sobre variables originales,\nproyección 2D vía PCA solo para visualización)") +
theme(
plot.title = element_text(size = 25, face = "bold"),
axis.title = element_text(size = 20),
axis.text = element_text(size = 15),
legend.title = element_text(size = 15),
legend.text = element_text(size = 15)
)
g_clusters
La segmentación en cuatro conglomerados mediante k-means sobre las variables originales produjo grupos de tamaño equilibrado, en contraste con la partición desigual observada en el dendrograma sobre la muestra reducida. La proyección sobre el plano principal del ACP evidencia una correspondencia clara entre los clusters obtenidos y los cuatro perfiles de mercado identificados previamente en la interpretación de componentes. Viviendas de gran tamaño y precio absoluto (cluster 1), apartamentos compactos de alta valorización por metro cuadrado (cluster 4), el segmento de menor valor en ambas dimensiones (cluster 2), y viviendas de tamaño medio con valorización relativa más baja (cluster 3). Se observa, sin embargo, un solapamiento considerable entre los clusters en la región central del plano, reflejo de que las diferencias entre segmentos del mercado inmobiliario analizado son de naturaleza gradual más que categorías completamente discretas.
perfil_clusters <- vivienda_clean %>%
group_by(cluster) %>%
summarise(
n_viviendas = n(),
precio_prom_M = round(mean(preciom, na.rm = TRUE), 0),
precio_m2_prom = round(mean(precio_m2, na.rm = TRUE), 2),
area_prom_m2 = round(mean(areaconst, na.rm = TRUE), 0),
parqueaderos_p = round(mean(parqueaderos, na.rm = TRUE), 2),
banios_prom = round(mean(banios, na.rm = TRUE), 2),
habitaciones_p = round(mean(habitaciones, na.rm = TRUE), 2),
estrato_moda = names(sort(table(estrato), decreasing = TRUE))[1],
zona_moda = names(sort(table(zona), decreasing = TRUE))[1],
tipo_moda = names(sort(table(tipo), decreasing = TRUE))[1]
)
kable(perfil_clusters,
caption = "Perfil de los conglomerados",
col.names = c("Cluster", "N° viviendas", "Precio prom. (M COP)",
"Precio/m² prom.", "Área prom. (m²)", "Parqueaderos prom.",
"Baños prom.", "Habitaciones prom.", "Estrato moda",
"Zona moda", "Tipo moda"),
align = "c") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE, font_size = 13) %>%
scroll_box(width = "100%", height = "auto")
| Cluster | N° viviendas | Precio prom. (M COP) | Precio/m² prom. | Área prom. (m²) | Parqueaderos prom. | Baños prom. | Habitaciones prom. | Estrato moda | Zona moda | Tipo moda |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 661 | 1138 | 2.69 | 471 | 4.03 | 5.48 | 4.79 | 6 | Zona Sur | Casa |
| 2 | 4433 | 236 | 2.60 | 95 | 0.89 | 2.16 | 2.89 | 4 | Zona Sur | Apartamento |
| 3 | 1558 | 447 | 1.67 | 285 | 1.42 | 4.14 | 5.46 | 5 | Zona Sur | Casa |
| 4 | 1667 | 670 | 4.04 | 168 | 2.10 | 3.75 | 3.30 | 6 | Zona Sur | Apartamento |
# Tablas cruzadas: composición categórica de cada cluster
# =============================================================================
# Tabla 2: Cluster x Zona
# =============================================================================
tab_zona <- as.data.frame.matrix(table(Cluster = vivienda_clean$cluster,
Zona = vivienda_clean$zona))
tab_zona <- cbind(Cluster = rownames(tab_zona), tab_zona)
kable(tab_zona, caption = "Distribución de zona por cluster", row.names = FALSE,
align = "c") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE, font_size = 13)
| Cluster | Zona Centro | Zona Norte | Zona Oeste | Zona Oriente | Zona Sur |
|---|---|---|---|---|---|
| 1 | 2 | 71 | 113 | 1 | 474 |
| 2 | 59 | 1224 | 301 | 177 | 2672 |
| 3 | 59 | 386 | 111 | 172 | 830 |
| 4 | 4 | 239 | 673 | 1 | 750 |
# =============================================================================
# Tabla 3: Cluster x Tipo
# =============================================================================
tab_tipo <- as.data.frame.matrix(table(Cluster = vivienda_clean$cluster,
Tipo = vivienda_clean$tipo))
tab_tipo <- cbind(Cluster = rownames(tab_tipo), tab_tipo)
kable(tab_tipo, caption = "Distribución de tipo de vivienda por cluster", row.names = FALSE,
align = "c") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE, font_size = 13)
| Cluster | Apartamento | Casa |
|---|---|---|
| 1 | 98 | 563 |
| 2 | 3553 | 880 |
| 3 | 144 | 1414 |
| 4 | 1305 | 362 |
# =============================================================================
# Tabla 4: Cluster x Estrato
# =============================================================================
tab_estrato <- as.data.frame.matrix(table(Cluster = vivienda_clean$cluster,
Estrato = vivienda_clean$estrato))
tab_estrato <- cbind(Cluster = rownames(tab_estrato), tab_estrato)
kable(tab_estrato, caption = "Distribución de estrato por cluster", row.names = FALSE,
align = "c") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE, font_size = 13)
| Cluster | 3 | 4 | 5 | 6 |
|---|---|---|---|---|
| 1 | 9 | 33 | 151 | 468 |
| 2 | 1014 | 1629 | 1547 | 243 |
| 3 | 419 | 407 | 584 | 148 |
| 4 | 11 | 60 | 468 | 1128 |
#--------------------------------------------------#
g_perfil_precio <- ggplot(vivienda_clean, aes(x = cluster, y = preciom, fill = cluster)) +
geom_boxplot(alpha = 0.7, outlier.alpha = 0.2) +
scale_y_continuous(labels = comma) +
labs(title = "Distribución del precio por segmento (cluster)",
x = "Segmento", y = "Precio (millones COP)") +
theme_minimal() +
theme(legend.position = "none",
plot.title = element_text(size = 25, face = "bold"),
axis.title = element_text(size = 20),
axis.text = element_text(size = 15),
)
g_perfil_precio
g_perfil_area <- ggplot(vivienda_clean, aes(x = cluster, y = areaconst, fill = cluster)) +
geom_boxplot(alpha = 0.7, outlier.alpha = 0.2) +
labs(title = "Distribución del área construida por segmento",
x = "Segmento", y = "Área construida (m2)") +
theme_minimal() +
theme(legend.position = "none",
plot.title = element_text(size = 25, face = "bold"),
axis.title = element_text(size = 20),
axis.text = element_text(size = 15)
)
g_perfil_area
A partir del perfil promedio y del cruce con zona, tipo y estrato, los cuatro conglomerados podrian interpretarse de la siguiente manera:
Cluster 1: Es el segmento más pequeño (661 viviendas, 8%) pero el de mayor precio absoluto (1,138 millones) y mayor área (471 \(m^2\)), con más baños (5.48) y más parqueaderos (4.03) que cualquier otro cluster. Sin embargo, su precio_m2 (2.69) es prácticamente igual al del mercado masivo (cluster 2, 2.60) y muy por debajo del cluster 4 (4.04). Esto confirma directamente el patrón de economías de escala que se identificó anteriormente, donde se encontraban casas grandes, con costo alto en total, pero no necesariamente “caras por metro cuadrado”. Está fuertemente concentrado en Zona Sur (72% de sus casos) y en estrato 6 (71%).
Cluster 2: Con 4,433 viviendas (más de la mitad de la base), este es el segmento dominante conformado en su mayoría por apartamentos (80%), área pequeña (95 \(m^2\) promedio), el precio más bajo (236 millones), y muy pocos parqueaderos (0.89 en promedio — la mayoría con 0 o 1). Se concentra en estratos 4-5 y está geográficamente repartido de forma similar al mercado general (Sur y Norte). Son en esencia las viviendas más características y que pertenecen al mercado masivo de la ciudad.
Cluster 3: Tiene el precio_m2 más bajo de los cuatro segmentos (1.67), predominan las casas (91%), área intermedia (285 \(m^2\)), y su distribución de estrato está mucho más repartida hacia abajo que los demás (estratos 3-5 con peso similar, en vez de concentrarse en 6). Además, geográficamente, Zona Oriente está sobrerrepresentada casi 3 veces en este cluster (11% vs. 4.2% de participación general), por lo que coincide exactamente con el patrón de “casas grandes en zonas populares con bajo valor por metro” que se identificó como hallazgo particular del mercado, desde el inicio del informe.
Cluster 4: Con precio_m2 de 4.04 (el más alto, muy por encima de los demás), es el segmento que mejor representa “caro por metro cuadrado” más que “grande en total”. Consta de un área moderada (168 \(m^2\)), predominantemente apartamentos (78%), estrato 6 dominante (68%). Además, el 40% de este cluster está en Zona Oeste (673 de 1,667), muy por encima de la participación de esa zona en el mercado general (14.4%), lo cual indica una sobrerrepresentación de casi 3 veces. Esto es la confirmación cuantitativa, vía clustering, de lo que ya se había idnetificado en el biplot del ACP, donde se caracterizaba a la Zona Oeste como el motor geográfico del segmento de alta valorización por metro cuadrado.
g_mapa_clusters <- ggplot(vivienda_clean, aes(x = longitud, y = latitud, color = cluster)) +
geom_point(alpha = 0.5, size = 1.2) +
coord_fixed() +
labs(title = "Distribución geográfica de los segmentos de vivienda",
x = "Longitud", y = "Latitud", color = "Segmento") +
theme_minimal() +
theme( plot.title = element_text(size = 25, face = "bold"),
axis.title = element_text(size = 20),
axis.text = element_text(size = 15),
legend.title = element_text(size = 20),
legend.text = element_text(size = 15))
g_mapa_clusters
La proyección geográfica de los segmentos confirma espacialmente los hallazgos de las tablas cruzadas. El cluster 4 (alta valorización por metro cuadrado) forma una concentración claramente delimitada en el sector correspondiente a Zona Oeste, mientras que el cluster 1 (casas de gran tamaño y precio absoluto) muestra mayor presencia en el extremo sur de la ciudad. El cluster 2, al concentrar más de la mitad de la oferta, se distribuye de manera generalizada por toda el área urbana analizada. Se observa también un entremezclado considerable entre los cuatro segmentos en la mayor parte del territorio, lo que indica que la segmentación obtenida responde primordialmente a características de tamaño y valorización de la vivienda, más que a una división estrictamente geográfica del mercado.
Como método final, se usa el análisis de correspondencias para representar posibles asociaciones entre variables categóricas, es decir, la asociación entre sus categorías, con el fin de establecer si existe, patrones o estructuras en los datos.
Se construye entonces la tabla cruzada con las variables invlucradas en el análisis:
tabla_tipo_zona <- table(vivienda_clean$tipo, vivienda_clean$zona)
tabla_tipo_zona
##
## Zona Centro Zona Norte Zona Oeste Zona Oriente Zona Sur
## Apartamento 24 1198 1029 62 2787
## Casa 100 722 169 289 1939
De antemano, en la tabla cruzada se puede observar que existe cierta asociación en la presencia de viviendas de tipo casa o apartamento con respecto a la zona que se analice. Sin embargo, para comprobar este resultado formalmente se realiza la prueba chi-cuadrado para verificar si existe asociacón significativa entre las variables de tipo y zona.
\[ H_0: \text{Las dos variables son independientes} \]
\[ H_1: \text{Las dos variables no son independientes} \]
chisq.test(tabla_tipo_zona) # ¿existe asociación significativa entre tipo y zona?
##
## Pearson's Chi-squared test
##
## data: tabla_tipo_zona
## X-squared = 690.93, df = 4, p-value < 2.2e-16
El resultado de la prueba chi-cuadrado arroja un valor-p muy cercano a cero, por lo que se rechaza la hipótesis nula, indicando que las variables tipo y zona no son independientes y que por tanto existen relaciones entre sus categorías.
res.ca <- CA(tabla_tipo_zona, graph = FALSE)
res.ca$eig # inercia explicada por cada dimensión del AC
## eigenvalue percentage of variance cumulative percentage of variance
## dim 1 0.08305442 100 100
Dado que la variable tipo de vivienda cuenta con únicamente dos categorías (apartamento y casa), el Análisis de Correspondencias entre tipo y zona produce, por construcción, una única dimensión (rango = mín(2-1, 5-1) = 1), la cual explica el 100% de la inercia total. Por tanto, la relación entre ambas variables se representa mediante una proyección unidimensional en lugar del biplot bidimensional habitual, se decide realizar un Análisis de Correspondencias Múltiples (ACM), incluyendo variables suplementarias para poder graficar el mapa bidimensional
# Con 3 variables categóricas se usa Análisis de Correspondencias Múltiples
# (generalización del AC simple), incluyendo estrato como suplementaria.
vivienda_mca <- vivienda_clean %>%
select(tipo, zona, barrio_agr, estrato) %>%
as.data.frame()
res.mca <- MCA(vivienda_mca, quali.sup = 4, graph = FALSE)
res.mca$eig
## eigenvalue percentage of variance cumulative percentage of variance
## dim 1 0.6256141 9.384212 9.384212
## dim 2 0.5604926 8.407389 17.791601
## dim 3 0.4665594 6.998391 24.789992
## dim 4 0.3568515 5.352773 30.142765
## dim 5 0.3354326 5.031488 35.174253
g_mca_scree <- fviz_eig(res.mca, addlabels = TRUE, labelsize = 12,
main = "Inercia explicada - ACM (tipo, zona, barrio)") +
theme(
plot.title = element_text(size = 25, face = "bold"),
axis.title = element_text(size = 20),
axis.text = element_text(size = 20)
)
g_mca_scree
La inercia explicada en el ACM por eje es baja acumulando solo el 17.8% entre los dos primeros ejes, lo cual parece muy poco comparado con el porcentaje acumulado que se lograba en el ACP con la misma cantidad de componentes, sin embargo, esto es una característica matemática esperada del ACM, no una señal de mala calidad de análisis.
El ACM trabaja sobre la tabla de Burt (todas las categorías cruzadas contra todas), lo que infla artificialmente el denominador de la inercia total y hace que los porcentajes por eje se vean sistemáticamente más bajos que en un ACP, incluso cuando la estructura subyacente es fuerte.
g_mca <- fviz_mca_var(res.mca, repel = TRUE, col.var = "cos2",
gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
title = "ACM: categorías de tipo, zona, barrio y estrato (supl.)")+
theme(
plot.title = element_text(size = 25, face = "bold"),
axis.title = element_text(size = 20),
axis.text = element_text(size = 20),
legend.title = element_text(size = 20),
legend.text = element_text(size = 15)
)
g_mca
El gráfico muestra en la esquina derecha agrupados la zona Oeste, barrio Aguacatal, Los Cristales, Santa Teresita y Normandía, que son prácticamente los mismos barrios que dominaban el cluster 4 (alta valorización por \(m^2\), sobrerrepresentado en Zona Oeste) que se identificó en el clustering.
Por otro lado, en la esquina inferior izquierda se concentran Zona Centro, Zona Oriente, Zona Sur y varios barrios asociados (ciudad jardín, el refugio, el caney, pance, el ingenio) — el lado opuesto del mapa a Zona Oeste, coherente con el patrón de estrato bajo/valorización baja que se identificó como el cluster 3 del k-means.
# Contribución de cada categoría a las dos primeras dimensiones del ACM
round(res.mca$var$contrib, 2)
## Dim 1 Dim 2 Dim 3 Dim 4 Dim 5
## Apartamento 4.39 0.14 8.01 3.27 0.00
## Casa 6.95 0.22 12.68 5.18 0.00
## Zona Centro 0.36 0.34 7.45 2.72 36.97
## Zona Norte 0.33 33.76 4.18 0.20 0.00
## Zona Oeste 37.25 0.24 2.83 0.72 0.00
## Zona Oriente 1.09 0.89 22.47 5.84 12.88
## Zona Sur 5.39 14.51 1.63 0.05 0.00
## acopi 0.06 5.94 1.64 6.08 1.42
## aguacatal 5.45 0.08 0.24 0.68 0.00
## ciudad jardín 1.61 3.12 0.00 25.69 0.87
## el caney 0.43 1.97 0.67 0.50 0.06
## el ingenio 0.28 1.36 0.54 0.14 0.03
## el limonar 0.39 0.82 0.00 5.80 0.21
## el refugio 0.14 0.81 0.36 0.03 0.01
## la flora 0.01 16.61 9.63 0.66 6.39
## la hacienda 0.20 1.12 0.55 0.00 0.01
## los cristales 11.72 0.15 0.62 2.17 0.01
## normandía 8.47 0.13 0.12 0.01 0.00
## pance 0.96 2.55 0.10 8.58 0.77
## prados del norte 0.00 4.54 2.69 0.00 40.06
## santa teresita 13.14 0.20 0.21 0.13 0.00
## valle del lili 0.38 7.22 10.76 25.84 0.25
## Otros 0.98 3.26 12.61 5.73 0.01
La Zona Oeste domina el eje 1 con 37.25% de contribución más que todas las demás categorías juntas. Además, se destaca que los barrios asociados (santa teresita 13.14%, los cristales 11.72%, normandía 8.47%, aguacatal 5.45%) suman, junto con Zona Oeste, más del 75% de la definición de este eje, por lo que la Dim 1 no es un eje genérico, sino que representa el contraste de la Zona Oeste con el resto de la ciudad.
Por su parte, la Zona Norte (33.76%) domina el eje 2, junto con el barrio la flora (16.61%) y prados del norte (4.54%) del mismo lado, contrastando con Zona Sur (14.51%) y sus barrios (valle del lili 7.22%) del lado opuesto.
La oferta de vivienda analizada refleja un mercado fuertemente segmentado por zona geográfica y esta a su vez actúa como un indicador casi directo del nivel socioeconómico. La Zona Oeste concentra las viviendas de mayor estrato, mayor precio y mayor valorización por metro cuadrado, mientras que Zona Oriente y Zona Centro se caracterizan por estrato 3, predominio de casas y menor valorización relativa, lo cual es una asociación confirmada de forma independiente por el análisis bivariado, el ACP, el clustering y el ACM.
El precio de la vivienda no puede explicarse por una sola dimensión. El Análisis de Componentes Principales identificó dos componentes claramente diferenciados: uno de magnitud/tamaño (precio absoluto, área, baños, parqueaderos), que explica el 52.2% de la varianza, y otro de valorización relativa por metro cuadrado (precio_m2), con 25.5% adicional. Esta separación evidencia que “grande y caro” y “caro por metro cuadrado” son fenómenos distintos del mercado, lo cual confirma la existencia de economías de escala: las viviendas más grandes no son necesariamente las más eficientes en precio por metro.
La segmentación en cuatro conglomerados, validada de forma convergente mediante el método del codo y el clustering jerárquico, identifica perfiles de mercado con implicaciones comerciales claras. Muestra un segmento masivo de apartamentos de menor tamaño y precio (53% de la oferta), un segmento reducido de casas de gran tamaño y alto precio absoluto pero valorización moderada por metro, otro segmento premium de alta valorización por metro cuadrado fuertemente concentrado en Zona Oeste y un segmento de casas de menor valorización relativa, sobrerrepresentado en Zona Oriente y estratos medios-bajos.
El Análisis de Correspondencias Múltiples confirmó, con una técnica estadística independiente, la existencia de un ecosistema de mercado premium claramente diferenciado, anclado en Zona Oeste y barrios específicos (Santa Teresita, Los Cristales, Normandía, Aguacatal), replicando el mismo patrón detectado en el clustering, lo que evidencia que los hallazgos del estudio son robustos frente a distintas metodologías, no artefactos de una técnica en particular.
Con base en los hallazgos anteriores, se recomienda a la empresa inmobiliaria dirigir sus estrategias de valoración y comercialización de forma diferenciada por segmento de mercado, en lugar de aplicar criterios homogéneos a toda su cartera de propiedades. Para el segmento premium concentrado en Zona Oeste (cluster 4, alta valorización por metro cuadrado), la estrategia comercial debería enfatizar el precio por metro cuadrado como indicador de valor, dado que es la variable que mejor diferencia a este segmento del resto del mercado. Para el segmento de casas grandes en zonas de estrato medio-bajo (cluster 3, sobrerrepresentado en Zona Oriente), conviene evitar valoraciones basadas únicamente en el precio total, ya que estas propiedades muestran sistemáticamente un menor valor por metro cuadrado, y una estrategia de precio ajustada a esta realidad puede mejorar la rotación de este inventario.
Asimismo, dado que zona y estrato están altamente asociados en los datos analizados, se recomienda que futuros levantamientos de información recopilen ambas variables de forma independiente y verificada, para poder distinguir con mayor precisión el efecto de la ubicación del efecto puramente socioeconómico sobre el precio. Finalmente, se sugiere extender este análisis periódicamente conforme se actualice la base de datos, con el fin de monitorear si la estructura de segmentos identificada, especialmente la del cluster 1 (casas de gran tamaño y alto precio absoluto) y el cluster 2 (mercado masivo de apartamentos, más de la mitad de la oferta), se mantiene estable en el tiempo o si emergen nuevos patrones de oferta que ameriten ajustes en la estrategia comercial de la empresa.
Además de las variables originales de la base vivienda,
se construyeron las siguientes variables durante el proceso de
análisis:
| Variable | Tipo | Descripción |
|---|---|---|
precio_m2 |
Cuantitativa continua (derivada) | Precio por metro cuadrado (preciom / areaconst), en
millones COP/m² |
barrio_std |
Cualitativa nominal (derivada) | Nombre del barrio estandarizado (sin tildes, espacios extra ni duplicados de escritura) |
barrio_agr |
Cualitativa nominal (derivada) | Barrio agrupado en top 15 + categoría “Otros”, usado en el Análisis de Correspondencias Múltiples |
cluster |
Cualitativa nominal (derivada) | Segmento asignado a cada vivienda por el algoritmo k-means (1 a 4) |
kable(round(res.pca$var$contrib, 2),
caption = "Contribución de las variables activas a cada componente (%)") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE, font_size = 13) %>%
scroll_box(width = "100%", height = "auto")
| Dim.1 | Dim.2 | Dim.3 | Dim.4 | Dim.5 | |
|---|---|---|---|---|---|
| preciom | 23.21 | 9.98 | 0.32 | 16.68 | 4.85 |
| areaconst | 23.15 | 4.73 | 15.03 | 18.76 | 2.88 |
| precio_m2 | 0.05 | 55.69 | 20.50 | 0.53 | 3.91 |
| parqueaderos | 16.85 | 8.07 | 17.82 | 56.77 | 0.42 |
| banios | 24.58 | 0.17 | 12.13 | 0.01 | 62.93 |
| habitaciones | 12.15 | 21.36 | 34.20 | 7.26 | 25.00 |
kable(round(res.pca$var$cos2, 2),
caption = "Cos² de las variables activas por componente") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE, font_size = 13) %>%
scroll_box(width = "100%", height = "auto")
| Dim.1 | Dim.2 | Dim.3 | Dim.4 | Dim.5 | |
|---|---|---|---|---|---|
| preciom | 0.73 | 0.15 | 0.00 | 0.07 | 0.01 |
| areaconst | 0.72 | 0.07 | 0.09 | 0.08 | 0.01 |
| precio_m2 | 0.00 | 0.85 | 0.12 | 0.00 | 0.01 |
| parqueaderos | 0.53 | 0.12 | 0.10 | 0.24 | 0.00 |
| banios | 0.77 | 0.00 | 0.07 | 0.00 | 0.16 |
| habitaciones | 0.38 | 0.33 | 0.20 | 0.03 | 0.06 |
kable(round(res.pca$eig, 2),
caption = "Eigenvalues y varianza explicada - ACP (5 dimensiones)") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE, font_size = 13) %>%
scroll_box(width = "100%", height = "auto")
| eigenvalue | percentage of variance | cumulative percentage of variance | |
|---|---|---|---|
| comp 1 | 3.13 | 52.16 | 52.16 |
| comp 2 | 1.53 | 25.49 | 77.65 |
| comp 3 | 0.58 | 9.70 | 87.35 |
| comp 4 | 0.43 | 7.17 | 94.53 |
| comp 5 | 0.25 | 4.17 | 98.70 |
Adicionalmente, se presenta el listado de las viviendas con mayor contribución a la formación de los dos primeros componentes:
top20_dim1 <- res.pca$ind$contrib[order(-res.pca$ind$contrib[, 1]), ][1:20, 1:2]
top20_dim2 <- res.pca$ind$contrib[order(-res.pca$ind$contrib[, 2]), ][1:20, 1:2]
kable(round(top20_dim1, 4), caption = "Top 20 viviendas - mayor contribución al eje 1") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE, font_size = 13) %>%
scroll_box(width = "100%", height = "300px")
| Dim.1 | Dim.2 | |
|---|---|---|
| 5684 | 0.3975 | 0.0016 |
| 5396 | 0.2805 | 0.0585 |
| 3652 | 0.2766 | 0.0035 |
| 5004 | 0.2741 | 0.0015 |
| 6766 | 0.2657 | 0.0749 |
| 8298 | 0.2554 | 0.0009 |
| 673 | 0.2511 | 0.0012 |
| 5978 | 0.2485 | 0.0060 |
| 5902 | 0.2367 | 0.0139 |
| 4564 | 0.2345 | 0.0128 |
| 4308 | 0.2285 | 0.0012 |
| 4559 | 0.2269 | 0.0029 |
| 5467 | 0.2253 | 0.0120 |
| 5710 | 0.2240 | 0.0166 |
| 315 | 0.2190 | 0.0123 |
| 4238 | 0.2179 | 0.0026 |
| 5664 | 0.2143 | 0.0002 |
| 4340 | 0.2138 | 0.0201 |
| 5862 | 0.2133 | 0.0790 |
| 6433 | 0.2048 | 0.0000 |
kable(round(top20_dim2, 4), caption = "Top 20 viviendas - mayor contribución al eje 2") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE, font_size = 13) %>%
scroll_box(width = "100%", height = "300px")
| Dim.1 | Dim.2 | |
|---|---|---|
| 534 | 0.1287 | 0.2687 |
| 7366 | 0.0284 | 0.2566 |
| 4112 | 0.0172 | 0.2295 |
| 5094 | 0.0374 | 0.2159 |
| 8020 | 0.0394 | 0.1914 |
| 921 | 0.0231 | 0.1770 |
| 684 | 0.0248 | 0.1659 |
| 4508 | 0.0918 | 0.1609 |
| 5151 | 0.0243 | 0.1561 |
| 5474 | 0.1662 | 0.1561 |
| 7505 | 0.1205 | 0.1546 |
| 450 | 0.0264 | 0.1539 |
| 8249 | 0.0431 | 0.1530 |
| 5154 | 0.1364 | 0.1503 |
| 6928 | 0.0490 | 0.1424 |
| 4407 | 0.0103 | 0.1409 |
| 1017 | 0.0236 | 0.1396 |
| 8022 | 0.0492 | 0.1394 |
| 7871 | 0.0214 | 0.1392 |
| 7303 | 0.0074 | 0.1371 |
table(vivienda$barrio)
##
## 20 de julio 3 de julio
## 3 1
## acopi agua blanca
## 158 1
## aguablanca aguacatal
## 2 109
## alameda alameda del rÃo
## 16 2
## alameda del rio alamos
## 1 14
## alborada alcazares
## 1 2
## alfâ\u0088\u009a©rez real alferez real
## 5 2
## alfonso lópez alfonso lópez i
## 21 1
## alfonso lopez alto jordán
## 1 1
## altos de guadalupe altos de menga
## 4 3
## altos de santa antonio nariño
## 1 2
## aranjuez arboleda
## 15 5
## arboleda campestre candelaria arboledas
## 1 38
## atanasio girardot autopista sur
## 9 1
## bajo aguacatal barranquilla
## 1 6
## barrio 7de agosto barrio el recuerdo
## 1 1
## barrio eucarÃstico barrio obrero
## 1 1
## barrio tranquilo y base aâ\u0088\u009a©rea
## 1 2
## belalcazar Belalcazar
## 3 1
## belisario caicedo bella suiza
## 2 18
## bella suiza alta bellavista
## 4 43
## benjamÃn herrera berlin
## 8 1
## bloques del limonar bochalema
## 1 33
## bolivariano bosques de alboleda
## 1 1
## bosques del limonar boyacá
## 21 1
## bretaña brisas de guadalupe
## 16 1
## brisas de los Brisas De Los
## 81 1
## brisas del guabito brisas del limonar
## 1 1
## Bueno Madrid buenos aires
## 1 7
## cañasgordas cañaveralejo
## 7 12
## cañaverales cañaverales los samanes
## 21 1
## caldas Cali
## 1 37
## cali bella cali canto
## 1 1
## calibella calicanto
## 1 8
## calicanto viii calima
## 1 6
## calimio norte calipso
## 5 11
## cambulos camino real
## 3 35
## Camino Real campestre
## 1 1
## caney caney especial
## 88 5
## capri cascajal
## 56 1
## cataya real ceibas
## 1 1
## centelsa centenario
## 1 15
## Centenario centro
## 1 4
## cerro cristales cerros de guadalupe
## 22 1
## champagnat chapinero
## 14 7
## chiminangos Chiminangos
## 17 1
## chiminangos 1 etapa chiminangos 2 etapa
## 1 2
## chipichape ciudad 2000
## 30 95
## Ciudad 2000 ciudad antejardin
## 1 1
## ciudad bochalema ciudad córdoba
## 48 15
## ciudad córdoba reservado ciudad capri
## 1 13
## ciudad cordoba ciudad country
## 20 1
## ciudad del campo ciudad jardÃn
## 1 516
## Ciudad JardÃn ciudad jardin
## 2 22
## ciudad jardin pance ciudad los álamos
## 1 25
## ciudad los alamos ciudad melâ\u0088\u009a©ndez
## 1 1
## ciudad melendez ciudad modelo
## 1 7
## ciudad pacifica Ciudad Pacifica
## 2 1
## ciudad real ciudad talanga
## 3 1
## ciudad universitaria ciudadela comfandi
## 1 17
## ciudadela del rÃo ciudadela melendez
## 1 1
## ciudadela paso ancho ciudadela pasoancho
## 1 21
## colinas de menga colinas del bosque
## 3 1
## colinas del sur colon
## 8 1
## colseguros colseguros andes
## 44 4
## Colseguros Andes comfenalco
## 1 1
## compartir conjunto gibraltar
## 1 1
## cristóbal colón cristales
## 2 83
## cristobal colón cuarto de legua
## 14 44
## departamental ed benjamin herrera
## 29 1
## el bosque El Bosque
## 49 1
## el caney El Caney
## 208 1
## el castillo el cedro
## 6 8
## el diamante el dorado
## 2 6
## el gran limonar el guabal
## 8 19
## el guabito el ingenio
## 1 202
## El Ingenio el ingenio 3
## 1 1
## el ingenio i el ingenio ii
## 19 21
## el ingenio iii el jardÃn
## 20 15
## el jordán el lido
## 1 59
## el limonar el nacional
## 135 1
## el paraÃso el peñon
## 3 60
## el prado el refugio
## 2 120
## el rodeo el sena
## 1 1
## el trâ\u0088\u009a©bol el troncal
## 5 19
## el vallado eucarÃstico
## 1 2
## evaristo garcÃa farrallones de pance
## 2 1
## fenalco kennedy fepicol
## 1 1
## flora flora industrial
## 1 16
## floralia fonaviemcali
## 6 1
## francisco eladio ramirez fuentes de la
## 1 1
## gaitan gran limonar
## 1 24
## granada guadalupe
## 15 21
## guadalupe alto guaduales
## 1 2
## guayaquil hacienda alferez real
## 16 1
## ingenio ingenio i
## 1 1
## ingenio ii jamundi
## 1 4
## jamundi alfaguara jorge eliecer gaitán
## 1 1
## jorge isaacs jose manuel marroquÃn
## 1 1
## juanambâ\u0088\u009aâ\u0088« juanambu
## 53 2
## junÃn junin
## 6 18
## la alborada la alianza
## 5 5
## la arboleda la base
## 18 15
## la buitrera la campiña
## 3 13
## la cascada la ceibas
## 7 1
## la esmeralda la flora
## 1 366
## La Flora la floresta
## 2 18
## la fortaleza la gran colombia
## 4 1
## la hacienda La Hacienda
## 164 2
## la independencia la libertad
## 12 2
## la luisa la merced
## 1 26
## la morada la nueva base
## 1 8
## la playa la portada al
## 1 1
## la primavera la reforma
## 1 1
## la rivera la rivera i
## 11 2
## la rivera ii la riverita
## 2 1
## la riviera la selva
## 1 11
## la villa del laflora
## 1 1
## lares de comfenalco las acacias
## 1 12
## las amâ\u0088\u009a©ricas las camelias
## 3 1
## las ceibas las delicias
## 23 5
## las granjas las quintas de
## 10 1
## las vegas las vegas de
## 1 1
## libertadores los alamos
## 3 1
## los alcázares los alcazares
## 5 17
## los andes los cámbulos
## 21 6
## los cambulos los cristales
## 25 154
## los cristales club los farallones
## 1 4
## los guaduales Los Guaduales
## 25 1
## los guayacanes los jockeys
## 3 1
## los libertadores los parques barranquilla
## 4 6
## los robles lourdes
## 1 2
## mamellan manzanares
## 1 5
## mariano ramos marroquÃn iii
## 1 1
## mayapan las vegas melâ\u0088\u009a©ndez
## 46 23
## melendez menga
## 52 23
## metropolitano del norte miradol del aguacatal
## 21 1
## miraflores Miraflores
## 25 1
## morichal de comfandi multicentro
## 3 27
## municipal nápoles
## 3 29
## napoles normandÃa
## 2 154
## normandÃa west point normandia
## 1 5
## norte norte la flora
## 9 1
## nueva base nueva floresta
## 1 15
## nueva tequendama oasis de comfandi
## 73 6
## oasis de pasoancho occidente
## 1 11
## pacara pacará
## 19 4
## palmas del ingenio pampa linda
## 1 26
## pampalinda panamericano
## 12 9
## pance Pance
## 409 3
## parcelaciones pance parque residencial el
## 61 1
## paseo de los paso del comercio
## 2 6
## pasoancho poblado campestre
## 6 2
## ponce popular
## 1 6
## portada de comfandi portales de comfandi
## 2 1
## porvenir prados de oriente
## 3 6
## prados del limonar Prados Del Limonar
## 20 1
## prados del norte Prados Del Norte
## 126 1
## prados del sur primavera
## 2 2
## primero de mayo primitivo crespo
## 37 3
## puente del comercio puente palma
## 6 1
## quintas de don Quintas De Don
## 72 1
## quintas de salomia rafael uribe uribe
## 4 1
## refugio repâ\u0088\u009aâ\u0088«blica de israel
## 2 1
## rincón de salomia rincon de la
## 1 1
## riveras del valle rozo la torre
## 1 1
## saavedra galindo salomia
## 4 40
## samanes samanes de guadalupe
## 1 1
## sameco san antonio
## 1 24
## san bosco san carlos
## 8 4
## san cayetano san fernando
## 9 54
## San Fernando san fernando nuevo
## 1 10
## san fernando viejo san joaquÃn
## 18 16
## san joaquin san juan bosco
## 4 7
## san judas san judas tadeo
## 1 2
## san luÃs san luis
## 1 2
## san nicolás san nicolas
## 1 1
## san pedro san vicente
## 3 48
## santa santa anita
## 1 48
## Santa Anita santa anita sur
## 2 1
## santa bárbara santa elena
## 3 10
## santa fe santa helena de
## 8 1
## santa isabel Santa Isabel
## 63 1
## santa mónica santa mónica alta
## 3 1
## santa mónica popular santa mónica residencial
## 7 39
## santa monica Santa Monica
## 51 1
## santa monica norte santa monica popular
## 2 2
## santa monica residencial santa rita
## 5 45
## santa rosa santa teresita
## 1 262
## Santa Teresita Santafe
## 1 1
## santander santo domingo
## 1 5
## Santo Domingo sector aguacatal
## 1 1
## sector cañaveralejo guadalupe seminario
## 2 32
## sierras de normandÃa siete de agosto
## 1 8
## simón bolivar tejares cristales
## 1 4
## tejares de san templete
## 14 4
## tequendama tequendema
## 44 1
## terrón colorado torres de comfandi
## 1 57
## unión de vivienda unicentro cali
## 3 1
## urbanización barranquilla urbanización boyacá
## 4 1
## urbanización colseguros urbanización la flora
## 3 83
## urbanización la merced urbanización la nueva
## 4 4
## urbanización las cascadas urbanización nueva granada
## 1 3
## urbanización pacara urbanización rÃo lili
## 1 5
## urbanización san joaquin urbanización tequendama
## 4 7
## urbanizacion el saman urbanizacion gratamira
## 1 1
## urbanizacion lili valle de lili
## 2 1
## valle del lili Valle Del Lili
## 1008 1
## valle grande versalles
## 1 71
## villa colombia villa de veracruz
## 6 6
## villa del lago villa del parque
## 10 1
## villa del prado Villa Del Prado
## 51 1
## villa del sol villa del sur
## 25 5
## villas de veracruz Villas De Veracruz
## 8 1
## vipasa zona centro
## 32 1
## zona norte zona norte los
## 32 1
## zona oeste zona oriente
## 26 18
## zona residencial zona sur
## 1 74
tabla_equivalencias <- vivienda %>%
distinct(barrio, barrio_clave, barrio_std) %>%
arrange(barrio_clave) %>%
group_by(barrio_clave) %>%
filter(n() > 1)
kable(tabla_equivalencias, caption = "Equivalencia entre nombre original y nombre estandarizado de barrio") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE, font_size = 12) %>%
scroll_box(width = "100%", height = "400px")
| barrio | barrio_clave | barrio_std |
|---|---|---|
| agua blanca | aguablanca | aguablanca |
| aguablanca | aguablanca | aguablanca |
| alameda del rÃo | alamedadelrio | alameda del río |
| alameda del rio | alamedadelrio | alameda del río |
| alamos | alamos | alamos |
| los alamos | alamos | alamos |
| alborada | alborada | la alborada |
| la alborada | alborada | la alborada |
| alcazares | alcazares | los alcazares |
| los alcázares | alcazares | los alcazares |
| los alcazares | alcazares | los alcazares |
| alfonso lópez | alfonsolopez | alfonso lópez |
| alfonso lopez | alfonsolopez | alfonso lópez |
| arboleda | arboleda | la arboleda |
| la arboleda | arboleda | la arboleda |
| barranquilla | barranquilla | barranquilla |
| urbanización barranquilla | barranquilla | barranquilla |
| belalcazar | belalcazar | belalcazar |
| Belalcazar | belalcazar | belalcazar |
| el bosque | bosque | el bosque |
| El Bosque | bosque | el bosque |
| boyacá | boyaca | boyacá |
| urbanización boyacá | boyaca | boyacá |
| brisas de los | brisasdelos | brisas de los |
| Brisas De Los | brisasdelos | brisas de los |
| cali bella | calibella | cali bella |
| calibella | calibella | cali bella |
| cali canto | calicanto | calicanto |
| calicanto | calicanto | calicanto |
| cambulos | cambulos | los cambulos |
| los cámbulos | cambulos | los cambulos |
| los cambulos | cambulos | los cambulos |
| camino real | caminoreal | camino real |
| Camino Real | caminoreal | camino real |
| caney | caney | el caney |
| el caney | caney | el caney |
| El Caney | caney | el caney |
| ceibas | ceibas | las ceibas |
| la ceibas | ceibas | las ceibas |
| las ceibas | ceibas | las ceibas |
| centenario | centenario | centenario |
| Centenario | centenario | centenario |
| chiminangos | chiminangos | chiminangos |
| Chiminangos | chiminangos | chiminangos |
| ciudad 2000 | ciudad2000 | ciudad 2000 |
| Ciudad 2000 | ciudad2000 | ciudad 2000 |
| ciudad córdoba | ciudadcordoba | ciudad cordoba |
| ciudad cordoba | ciudadcordoba | ciudad cordoba |
| ciudadela paso ancho | ciudadelapasoancho | ciudadela pasoancho |
| ciudadela pasoancho | ciudadelapasoancho | ciudadela pasoancho |
| ciudad jardÃn | ciudadjardin | ciudad jardín |
| Ciudad JardÃn | ciudadjardin | ciudad jardín |
| ciudad jardin | ciudadjardin | ciudad jardín |
| ciudad los álamos | ciudadlosalamos | ciudad los álamos |
| ciudad los alamos | ciudadlosalamos | ciudad los álamos |
| ciudad pacifica | ciudadpacifica | ciudad pacifica |
| Ciudad Pacifica | ciudadpacifica | ciudad pacifica |
| colseguros | colseguros | colseguros |
| urbanización colseguros | colseguros | colseguros |
| colseguros andes | colsegurosandes | colseguros andes |
| Colseguros Andes | colsegurosandes | colseguros andes |
| cristales | cristales | los cristales |
| los cristales | cristales | los cristales |
| cristóbal colón | cristobalcolon | cristobal colón |
| cristobal colón | cristobalcolon | cristobal colón |
| flora | flora | la flora |
| la flora | flora | la flora |
| La Flora | flora | la flora |
| urbanización la flora | flora | la flora |
| el gran limonar | granlimonar | gran limonar |
| gran limonar | granlimonar | gran limonar |
| guaduales | guaduales | los guaduales |
| los guaduales | guaduales | los guaduales |
| Los Guaduales | guaduales | los guaduales |
| la hacienda | hacienda | la hacienda |
| La Hacienda | hacienda | la hacienda |
| el ingenio | ingenio | el ingenio |
| El Ingenio | ingenio | el ingenio |
| ingenio | ingenio | el ingenio |
| el ingenio i | ingenioi | el ingenio i |
| ingenio i | ingenioi | el ingenio i |
| el ingenio ii | ingenioii | el ingenio ii |
| ingenio ii | ingenioii | el ingenio ii |
| junÃn | junin | junin |
| junin | junin | junin |
| libertadores | libertadores | los libertadores |
| los libertadores | libertadores | los libertadores |
| la merced | merced | la merced |
| urbanización la merced | merced | la merced |
| miraflores | miraflores | miraflores |
| Miraflores | miraflores | miraflores |
| nápoles | napoles | nápoles |
| napoles | napoles | nápoles |
| normandÃa | normandia | normandía |
| normandia | normandia | normandía |
| la nueva base | nuevabase | la nueva base |
| nueva base | nuevabase | la nueva base |
| pacará | pacara | pacara |
| pacara | pacara | pacara |
| urbanización pacara | pacara | pacara |
| pampa linda | pampalinda | pampa linda |
| pampalinda | pampalinda | pampa linda |
| pance | pance | pance |
| Pance | pance | pance |
| prados del limonar | pradosdellimonar | prados del limonar |
| Prados Del Limonar | pradosdellimonar | prados del limonar |
| prados del norte | pradosdelnorte | prados del norte |
| Prados Del Norte | pradosdelnorte | prados del norte |
| la primavera | primavera | primavera |
| primavera | primavera | primavera |
| quintas de don | quintasdedon | quintas de don |
| Quintas De Don | quintasdedon | quintas de don |
| el refugio | refugio | el refugio |
| refugio | refugio | el refugio |
| san fernando | sanfernando | san fernando |
| San Fernando | sanfernando | san fernando |
| san joaquÃn | sanjoaquin | san joaquín |
| san joaquin | sanjoaquin | san joaquín |
| urbanización san joaquin | sanjoaquin | san joaquín |
| san luÃs | sanluis | san luis |
| san luis | sanluis | san luis |
| san nicolás | sannicolas | san nicolas |
| san nicolas | sannicolas | san nicolas |
| santa anita | santaanita | santa anita |
| Santa Anita | santaanita | santa anita |
| santa fe | santafe | santa fe |
| Santafe | santafe | santa fe |
| santa isabel | santaisabel | santa isabel |
| Santa Isabel | santaisabel | santa isabel |
| santa mónica | santamonica | santa monica |
| santa monica | santamonica | santa monica |
| Santa Monica | santamonica | santa monica |
| santa mónica popular | santamonicapopular | santa mónica popular |
| santa monica popular | santamonicapopular | santa mónica popular |
| santa mónica residencial | santamonicaresidencial | santa mónica residencial |
| santa monica residencial | santamonicaresidencial | santa mónica residencial |
| santa teresita | santateresita | santa teresita |
| Santa Teresita | santateresita | santa teresita |
| santo domingo | santodomingo | santo domingo |
| Santo Domingo | santodomingo | santo domingo |
| tequendama | tequendama | tequendama |
| urbanización tequendama | tequendama | tequendama |
| valle del lili | valledellili | valle del lili |
| Valle Del Lili | valledellili | valle del lili |
| villa del prado | villadelprado | villa del prado |
| Villa Del Prado | villadelprado | villa del prado |
| Villas De Veracruz | villasdeveracruz | villas de veracruz |
| villas de veracruz | villasdeveracruz | villas de veracruz |
kable(round(res.mca$var$contrib, 2),
caption = "Contribución de cada categoría a las 5 dimensiones del ACM") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE, font_size = 13) %>%
scroll_box(width = "100%", height = "400px")
| Dim 1 | Dim 2 | Dim 3 | Dim 4 | Dim 5 | |
|---|---|---|---|---|---|
| Apartamento | 4.39 | 0.14 | 8.01 | 3.27 | 0.00 |
| Casa | 6.95 | 0.22 | 12.68 | 5.18 | 0.00 |
| Zona Centro | 0.36 | 0.34 | 7.45 | 2.72 | 36.97 |
| Zona Norte | 0.33 | 33.76 | 4.18 | 0.20 | 0.00 |
| Zona Oeste | 37.25 | 0.24 | 2.83 | 0.72 | 0.00 |
| Zona Oriente | 1.09 | 0.89 | 22.47 | 5.84 | 12.88 |
| Zona Sur | 5.39 | 14.51 | 1.63 | 0.05 | 0.00 |
| acopi | 0.06 | 5.94 | 1.64 | 6.08 | 1.42 |
| aguacatal | 5.45 | 0.08 | 0.24 | 0.68 | 0.00 |
| ciudad jardín | 1.61 | 3.12 | 0.00 | 25.69 | 0.87 |
| el caney | 0.43 | 1.97 | 0.67 | 0.50 | 0.06 |
| el ingenio | 0.28 | 1.36 | 0.54 | 0.14 | 0.03 |
| el limonar | 0.39 | 0.82 | 0.00 | 5.80 | 0.21 |
| el refugio | 0.14 | 0.81 | 0.36 | 0.03 | 0.01 |
| la flora | 0.01 | 16.61 | 9.63 | 0.66 | 6.39 |
| la hacienda | 0.20 | 1.12 | 0.55 | 0.00 | 0.01 |
| los cristales | 11.72 | 0.15 | 0.62 | 2.17 | 0.01 |
| normandía | 8.47 | 0.13 | 0.12 | 0.01 | 0.00 |
| pance | 0.96 | 2.55 | 0.10 | 8.58 | 0.77 |
| prados del norte | 0.00 | 4.54 | 2.69 | 0.00 | 40.06 |
| santa teresita | 13.14 | 0.20 | 0.21 | 0.13 | 0.00 |
| valle del lili | 0.38 | 7.22 | 10.76 | 25.84 | 0.25 |
| Otros | 0.98 | 3.26 | 12.61 | 5.73 | 0.01 |
kable(round(res.mca$eig, 2),
caption = "Eigenvalues y varianza explicada - ACM (5 dimensiones)") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE, font_size = 13) %>%
scroll_box(width = "100%", height = "auto")
| eigenvalue | percentage of variance | cumulative percentage of variance | |
|---|---|---|---|
| dim 1 | 0.63 | 9.38 | 9.38 |
| dim 2 | 0.56 | 8.41 | 17.79 |
| dim 3 | 0.47 | 7.00 | 24.79 |
| dim 4 | 0.36 | 5.35 | 30.14 |
| dim 5 | 0.34 | 5.03 | 35.17 |
prop_explicada <- res.kmeans$betweenss / res.kmeans$totss
cat("Suma de cuadrados total (totss):", round(res.kmeans$totss, 1), "\n")
## Suma de cuadrados total (totss): 49908
cat("Suma de cuadrados intra-cluster (tot.withinss):", round(res.kmeans$tot.withinss, 1), "\n")
## Suma de cuadrados intra-cluster (tot.withinss): 22058.8
cat("Suma de cuadrados inter-cluster (betweenss):", round(res.kmeans$betweenss, 1), "\n")
## Suma de cuadrados inter-cluster (betweenss): 27849.2
cat("Proporción de varianza explicada por la segmentación:", round(prop_explicada * 100, 1), "%\n")
## Proporción de varianza explicada por la segmentación: 55.8 %