Iván Darío García Ramos
Maestría en Ciencia de Datos
Pontificia Universidad Javeriana Cali
Modelos Estadísticos para la Toma de Decisiones
Se analizó una base de 8.322 ofertas inmobiliarias
con información de precio, área construida, dotación, estrato, tipología
y localización. El diagnóstico inicial mostró que la principal
limitación de calidad corresponde a la variable piso,
mientras que el análisis multivariado principal conservó 6.717
observaciones completas en las seis variables
seleccionadas.
El Análisis de Componentes Principales (ACP) mostró que los dos primeros componentes concentran 78,4 % de la variabilidad total. El primer eje representa principalmente una dimensión de escala económica, tamaño y dotación física, mientras que el segundo diferencia la configuración habitacional y el nivel socioeconómico. El análisis de conglomerados permitió identificar tres segmentos de oferta —económico/compacto, intermedio y premium— con perfiles claramente distintos.
En las variables categóricas se encontró asociación estadísticamente significativa entre zona y tipo de vivienda (\(p\) < 0,001) y entre zona y estrato (\(p\) < 0,001). En consecuencia, la oferta inmobiliaria no se distribuye de manera homogénea: su estructura cambia tanto por características físicas como por localización y perfil socioeconómico.
Una empresa inmobiliaria líder en una gran ciudad requiere comprender en profundidad el mercado de viviendas urbanas para fortalecer la toma de decisiones relacionadas con compra, venta y valoración de propiedades. La base de datos disponible contiene información de oferta sobre características físicas, económicas, socioeconómicas y geográficas de los inmuebles.
El reto consiste en identificar las dimensiones que explican la mayor variabilidad del mercado, construir segmentos homogéneos de propiedades y reconocer asociaciones entre las categorías territoriales y la tipología de vivienda. Para ello se utilizan técnicas de análisis exploratorio, Análisis de Componentes Principales, análisis de conglomerados y análisis de correspondencia.
Caracterizar la oferta inmobiliaria urbana mediante técnicas estadísticas multivariadas que permitan reducir dimensionalidad, identificar segmentos homogéneos y analizar asociaciones entre variables categóricas de localización y tipología de vivienda para apoyar decisiones estratégicas.
La fuente corresponde a datos de oferta inmobiliaria obtenidos
originalmente mediante web scraping y contenidos en la base
vivienda del material del curso. La base contiene
8322 registros y 13 variables.
El análisis se desarrolló en cinco etapas:
diccionario <- tibble(
Variable = c(
"id", "zona", "piso", "estrato", "preciom", "areaconst",
"parqueaderos", "banios", "habitaciones", "tipo",
"barrio", "longitud", "latitud"
),
Naturaleza = c(
"Identificador",
"Cualitativa nominal",
"Cualitativa ordinal",
"Cualitativa ordinal",
"Cuantitativa continua",
"Cuantitativa continua",
"Cuantitativa discreta",
"Cuantitativa discreta",
"Cuantitativa discreta",
"Cualitativa nominal",
"Cualitativa nominal",
"Coordenada geográfica",
"Coordenada geográfica"
),
Uso = c(
"Control de registros",
"Correspondencia y análisis territorial",
"Descriptivo",
"ACP, clustering y correspondencia",
"ACP y clustering",
"ACP y clustering",
"ACP y clustering",
"ACP y clustering",
"ACP y clustering",
"Correspondencia",
"Correspondencia múltiple",
"Mapa",
"Mapa"
)
)
knitr::kable(
diccionario,
caption = "Tabla 1. Clasificación estadística de las variables",
format = "html",
table.attr = 'class="table table-striped table-hover"'
)| Variable | Naturaleza | Uso |
|---|---|---|
| id | Identificador | Control de registros |
| zona | Cualitativa nominal | Correspondencia y análisis territorial |
| piso | Cualitativa ordinal | Descriptivo |
| estrato | Cualitativa ordinal | ACP, clustering y correspondencia |
| preciom | Cuantitativa continua | ACP y clustering |
| areaconst | Cuantitativa continua | ACP y clustering |
| parqueaderos | Cuantitativa discreta | ACP y clustering |
| banios | Cuantitativa discreta | ACP y clustering |
| habitaciones | Cuantitativa discreta | ACP y clustering |
| tipo | Cualitativa nominal | Correspondencia |
| barrio | Cualitativa nominal | Correspondencia múltiple |
| longitud | Coordenada geográfica | Mapa |
| latitud | Coordenada geográfica | Mapa |
El identificador y las coordenadas no se incorporan al ACP ni al clustering porque no representan atributos comparables de la vivienda. El estrato se trata como una escala ordinal codificada de manera creciente; esta decisión se mantiene explícita como supuesto metodológico.
tabla_na %>%
transmute(
Variable,
`Valores faltantes` = Valores_faltantes,
`Porcentaje (%)` = round(Porcentaje, 2)
) %>%
knitr::kable(
caption = "Tabla 2. Valores faltantes por variable",
format = "html",
table.attr = 'class="table table-striped table-hover"'
)| Variable | Valores faltantes | Porcentaje (%) |
|---|---|---|
| piso | 2638 | 31.70 |
| parqueaderos | 1605 | 19.29 |
| id | 3 | 0.04 |
| zona | 3 | 0.04 |
| estrato | 3 | 0.04 |
| areaconst | 3 | 0.04 |
| banios | 3 | 0.04 |
| habitaciones | 3 | 0.04 |
| tipo | 3 | 0.04 |
| barrio | 3 | 0.04 |
| longitud | 3 | 0.04 |
| latitud | 3 | 0.04 |
| barrio_limpio | 3 | 0.04 |
| preciom | 2 | 0.02 |
ggplot(
tabla_na,
aes(x = reorder(Variable, Porcentaje), y = Porcentaje)
) +
geom_col() +
geom_text(
aes(label = paste0(round(Porcentaje, 1), "%")),
hjust = -0.1,
size = 3.2
) +
coord_flip() +
expand_limits(y = max(tabla_na$Porcentaje) * 1.12) +
labs(
title = "Figura 1. Porcentaje de datos faltantes por variable",
x = NULL,
y = "Porcentaje de valores faltantes"
) +
theme_minimal(base_size = 12)La variable con mayor ausencia es piso, con
31,7 % de datos faltantes. Por esta razón no se incluyó
piso en el ACP ni en el clustering principal. Imputar de
manera automática una variable con una proporción alta de ausencia, sin
conocer el mecanismo que la genera, podría introducir estructura
artificial.
parqueaderos se conserva debido a su importancia
económica para la valoración de los inmuebles. Para el modelo principal
se empleó análisis de casos completos sobre las seis variables
seleccionadas, quedando 6.717 observaciones.
duplicados_id <- sum(
duplicated(vivienda_limpia$id) & !is.na(vivienda_limpia$id)
)
tibble(
Indicador = c(
"Registros totales",
"ID no faltantes",
"ID duplicados entre valores no faltantes"
),
Valor = c(
nrow(vivienda_limpia),
sum(!is.na(vivienda_limpia$id)),
duplicados_id
)
) %>%
knitr::kable(
caption = "Tabla 3. Verificación de integridad del identificador",
format = "html",
table.attr = 'class="table table-striped table-hover"'
)| Indicador | Valor |
|---|---|
| Registros totales | 8322 |
| ID no faltantes | 8319 |
| ID duplicados entre valores no faltantes | 0 |
descriptivos %>%
mutate(
across(
c(Media, `Desv. estándar`, Mediana, Mínimo, Máximo),
~ round(.x, 2)
)
) %>%
knitr::kable(
caption = "Tabla 4. Estadísticos descriptivos de las variables utilizadas",
format = "html",
table.attr = 'class="table table-striped table-hover"'
)| Variable | N | Media | Desv. estándar | Mediana | Mínimo | Máximo |
|---|---|---|---|---|---|---|
| preciom | 6717 | 468.88 | 335.04 | 355 | 58 | 1999 |
| areaconst | 6717 | 181.14 | 144.10 | 130 | 30 | 1745 |
| parqueaderos | 6717 | 1.84 | 1.12 | 2 | 1 | 10 |
| banios | 6717 | 3.26 | 1.38 | 3 | 0 | 10 |
| habitaciones | 6717 | 3.61 | 1.36 | 3 | 0 | 10 |
| estrato | 6717 | 4.83 | 0.95 | 5 | 3 | 6 |
El precio y el área presentan una dispersión considerable, coherente con la coexistencia de inmuebles de distinta escala económica. Esta heterogeneidad justifica estudiar simultáneamente precio, superficie, dotación y estrato en lugar de analizar cada atributo de manera aislada.
ggplot(datos_modelo, aes(x = preciom)) +
geom_histogram(bins = 35, boundary = 0) +
labs(
title = "Figura 2. Distribución del precio de oferta",
x = "Precio (millones)",
y = "Frecuencia"
) +
theme_minimal(base_size = 12)La distribución presenta asimetría positiva: la mayor parte de la oferta se concentra en niveles bajos y medios de precio, mientras una fracción menor corresponde a inmuebles de valores considerablemente superiores. Esta estructura anticipa la posible existencia de segmentos inmobiliarios diferenciados.
ggplot(datos_modelo, aes(x = areaconst, y = preciom)) +
geom_point(alpha = 0.18) +
geom_smooth(method = "lm", se = TRUE) +
labs(
title = "Figura 3. Relación entre área construida y precio",
x = "Área construida (m²)",
y = "Precio (millones)"
) +
theme_minimal(base_size = 12)La relación es positiva: las viviendas de mayor superficie tienden a presentar un mayor precio. No obstante, la dispersión alrededor de la tendencia confirma que el área no explica por sí sola la heterogeneidad del valor de oferta.
ggplot(
cor_long,
aes(x = Variable_1, y = Variable_2, fill = Correlacion)
) +
geom_tile() +
geom_text(
aes(label = sprintf("%.2f", Correlacion)),
size = 3.1
) +
scale_fill_gradient2(
low = "#B2182B",
mid = "white",
high = "#2166AC",
midpoint = 0,
limits = c(-1, 1)
) +
labs(
title = "Figura 4. Matriz de correlación de Pearson",
x = NULL,
y = NULL,
fill = "r"
) +
theme_minimal(base_size = 11) +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
panel.grid = element_blank()
)Como contraste de robustez se calculó también Spearman. La comparación entre ambas matrices permite verificar que las conclusiones no dependan exclusivamente de la linealidad.
round(cor_spearman, 2) %>%
knitr::kable(
caption = "Tabla 5. Matriz de correlación de Spearman",
format = "html",
table.attr = 'class="table table-striped table-hover"'
)| preciom | areaconst | parqueaderos | banios | habitaciones | estrato | |
|---|---|---|---|---|---|---|
| preciom | 1.00 | 0.82 | 0.74 | 0.76 | 0.43 | 0.69 |
| areaconst | 0.82 | 1.00 | 0.64 | 0.78 | 0.66 | 0.39 |
| parqueaderos | 0.74 | 0.64 | 1.00 | 0.62 | 0.34 | 0.54 |
| banios | 0.76 | 0.78 | 0.62 | 1.00 | 0.63 | 0.44 |
| habitaciones | 0.43 | 0.66 | 0.34 | 0.63 | 1.00 | 0.03 |
| estrato | 0.69 | 0.39 | 0.54 | 0.44 | 0.03 | 1.00 |
El ACP permite representar un conjunto de variables correlacionadas mediante nuevas combinaciones lineales ortogonales que concentran progresivamente la variabilidad total (Mathai, Provost, & Haubold, 2022a). Dado que precio, área y variables de conteo están expresadas en escalas diferentes, el análisis se realizó sobre variables estandarizadas.
tibble(
Indicador = c(
"KMO global",
"Bartlett: chi-cuadrado",
"Bartlett: grados de libertad",
"Bartlett: p-valor"
),
Resultado = c(
fmt_num(kmo_obj$MSA, 3),
fmt_num(as.numeric(bartlett_obj$chisq), 2),
as.character(bartlett_obj$df),
fmt_p(bartlett_obj$p.value)
)
) %>%
knitr::kable(
caption = "Tabla 6. Adecuación de la matriz para ACP",
format = "html",
table.attr = 'class="table table-striped table-hover"'
)| Indicador | Resultado |
|---|---|
| KMO global | 0,779 |
| Bartlett: chi-cuadrado | 22413,34 |
| Bartlett: grados de libertad | 15 |
| Bartlett: p-valor | < 0,001 |
El KMO global fue 0,779. Adicionalmente, la prueba de Bartlett fue significativa (\(\chi^2\) = 22413,34, \(p\) < 0,001), por lo que se rechaza la hipótesis de una matriz de correlaciones identidad. En conjunto, existe estructura de correlación aprovechable para reducción dimensional.
tabla_varianza %>%
mutate(
`Varianza explicada (%)` = round(`Varianza explicada (%)`, 2),
`Varianza acumulada (%)` = round(`Varianza acumulada (%)`, 2)
) %>%
knitr::kable(
caption = "Tabla 7. Varianza explicada por los componentes principales",
format = "html",
table.attr = 'class="table table-striped table-hover"'
)| Componente | Varianza explicada (%) | Varianza acumulada (%) |
|---|---|---|
| PC1 | 58.06 | 58.06 |
| PC2 | 20.38 | 78.45 |
| PC3 | 8.32 | 86.77 |
| PC4 | 5.99 | 92.76 |
| PC5 | 4.07 | 96.84 |
| PC6 | 3.16 | 100.00 |
ggplot(tabla_varianza, aes(x = Componente, y = `Varianza explicada (%)`)) +
geom_col() +
geom_line(aes(group = 1)) +
geom_point(size = 2) +
geom_text(
aes(label = paste0(round(`Varianza explicada (%)`, 1), "%")),
vjust = -0.5,
size = 3.2
) +
labs(
title = "Figura 5. Gráfico de sedimentación del ACP",
x = "Componente principal",
y = "Varianza explicada (%)"
) +
theme_minimal(base_size = 12)PC1 explica 58,1 % y PC2 20,4 %; conjuntamente concentran 78,4 % de la variabilidad. Por tanto, la proyección bidimensional conserva una proporción sustancial de la información original y resulta adecuada para una interpretación ejecutiva del mercado.
tabla_cargas %>%
select(Variable, PC1, PC2, PC3) %>%
mutate(across(where(is.numeric), ~ round(.x, 3))) %>%
knitr::kable(
caption = "Tabla 8. Cargas de las variables en los tres primeros componentes",
format = "html",
table.attr = 'class="table table-striped table-hover"'
)| Variable | PC1 | PC2 | PC3 |
|---|---|---|---|
| preciom | 0.470 | 0.239 | 0.108 |
| areaconst | 0.450 | -0.204 | 0.259 |
| parqueaderos | 0.428 | 0.151 | 0.616 |
| banios | 0.465 | -0.160 | -0.370 |
| habitaciones | 0.299 | -0.668 | -0.331 |
| estrato | 0.295 | 0.638 | -0.543 |
tabla_contribuciones %>%
select(Variable, PC1, PC2) %>%
mutate(across(where(is.numeric), ~ round(.x, 2))) %>%
knitr::kable(
caption = "Tabla 9. Contribución porcentual a PC1 y PC2",
format = "html",
table.attr = 'class="table table-striped table-hover"'
)| Variable | PC1 | PC2 |
|---|---|---|
| preciom | 22.14 | 5.73 |
| areaconst | 20.27 | 4.18 |
| parqueaderos | 18.30 | 2.29 |
| banios | 21.65 | 2.56 |
| habitaciones | 8.92 | 44.57 |
| estrato | 8.73 | 40.67 |
Las mayores contribuciones a PC1 corresponden a preciom, banios, areaconst, parqueaderos. Por ello PC1 se interpreta como una dimensión de escala económica, tamaño y dotación física.
En PC2 dominan habitaciones y estrato, por lo que el segundo eje representa principalmente una dimensión de configuración habitacional y posición socioeconómica.
ggplot(cor_pca, aes(x = PC1, y = PC2, label = Variable)) +
geom_hline(yintercept = 0, linetype = 2, linewidth = 0.4) +
geom_vline(xintercept = 0, linetype = 2, linewidth = 0.4) +
annotate(
"path",
x = cos(seq(0, 2 * pi, length.out = 200)),
y = sin(seq(0, 2 * pi, length.out = 200))
) +
geom_segment(
aes(x = 0, y = 0, xend = PC1, yend = PC2),
arrow = arrow(length = grid::unit(0.18, "cm"))
) +
geom_text(nudge_y = 0.04, size = 3.4) +
coord_fixed(xlim = c(-1.1, 1.1), ylim = c(-1.1, 1.1)) +
labs(
title = "Figura 6. Círculo de correlaciones",
x = paste0("PC1 (", round(var_exp[1], 1), "%)"),
y = paste0("PC2 (", round(var_exp[2], 1), "%)")
) +
theme_minimal(base_size = 12)La proximidad angular entre vectores refleja relaciones positivas, mientras que direcciones opuestas reflejan asociaciones negativas. La longitud de cada vector indica qué tan bien queda representada la variable en el plano PC1–PC2.
set.seed(9044951)
idx_pca_plot <- sample(
seq_len(nrow(scores_pca)),
size = min(1800, nrow(scores_pca))
)
ggplot(scores_pca[idx_pca_plot, ], aes(x = PC1, y = PC2)) +
geom_point(alpha = 0.25) +
labs(
title = "Figura 7. Propiedades proyectadas en PC1 y PC2",
x = paste0("PC1 (", round(var_exp[1], 1), "%)"),
y = paste0("PC2 (", round(var_exp[2], 1), "%)")
) +
theme_minimal(base_size = 12)La dispersión de individuos en el espacio reducido evidencia la coexistencia de propiedades con perfiles sustancialmente diferentes, lo cual justifica avanzar hacia un procedimiento formal de segmentación.
El análisis de conglomerados agrupa observaciones multivariadas buscando mayor homogeneidad dentro de cada grupo y diferenciación entre grupos (Mathai, Provost, & Haubold, 2022b). Se empleó K-means sobre las variables estandarizadas para evitar que el precio o el área dominaran la distancia euclidiana.
ggplot(tabla_wss, aes(x = k, y = WSS)) +
geom_line() +
geom_point(size = 2) +
scale_x_continuous(breaks = 1:8) +
labs(
title = "Figura 8. Método del codo",
x = "Número de conglomerados (k)",
y = "Suma de cuadrados intra-clúster"
) +
theme_minimal(base_size = 12)ggplot(tabla_silueta_k, aes(x = k, y = `Silueta promedio`)) +
geom_line() +
geom_point(size = 2) +
scale_x_continuous(breaks = 2:8) +
labs(
title = "Figura 9. Silueta promedio por número de conglomerados",
x = "Número de conglomerados (k)",
y = "Silueta promedio"
) +
theme_minimal(base_size = 12)La decisión final utiliza conjuntamente el cambio de pendiente del WSS, la silueta y la interpretabilidad económica. Se adoptó k = 3 porque produce una partición parsimoniosa y operacionalmente útil, diferenciando niveles bajo, medio y alto de escala inmobiliaria. La silueta promedio de la solución final, evaluada sobre una muestra reproducible de 2000 observaciones, fue 0,304.
perfil_cluster %>%
mutate(
across(
c(
`Precio (millones)`, `Área (m²)`, Parqueaderos,
Baños, Habitaciones, Estrato
),
~ round(.x, 2)
)
) %>%
knitr::kable(
caption = "Tabla 10. Perfil promedio de los tres segmentos",
format = "html",
table.attr = 'class="table table-striped table-hover"'
)| Segmento | N | Precio (millones) | Área (m²) | Parqueaderos | Baños | Habitaciones | Estrato |
|---|---|---|---|---|---|---|---|
| Económico / compacto | 3498 | 261.26 | 97.70 | 1.23 | 2.27 | 2.94 | 4.40 |
| Intermedio | 2332 | 533.78 | 215.57 | 1.98 | 3.98 | 4.24 | 5.15 |
| Premium | 887 | 1117.04 | 419.65 | 3.82 | 5.23 | 4.59 | 5.68 |
La solución explica 49,7 % de la variación total en el espacio estandarizado mediante diferencias entre conglomerados.
Corresponde al grupo con menor precio y superficie promedio. Su propuesta de valor está asociada con accesibilidad económica y eficiencia espacial; por su configuración, es razonable orientar estrategias comerciales hacia compradores con mayor sensibilidad al precio.
Presenta valores centrales de precio, área y dotación. Funciona como segmento de transición entre el mercado masivo y la oferta premium, con características adecuadas para hogares que demandan mayor espacio sin ingresar al extremo superior de precios.
set.seed(9044951)
idx_cluster_plot <- sample(
seq_len(nrow(scores_cluster)),
size = min(2200, nrow(scores_cluster))
)
ggplot(
scores_cluster[idx_cluster_plot, ],
aes(x = PC1, y = PC2, color = Segmento)
) +
geom_point(alpha = 0.38) +
stat_ellipse(level = 0.90, linewidth = 0.7) +
labs(
title = "Figura 10. Segmentos inmobiliarios en el espacio PCA",
x = paste0("PC1 (", round(var_exp[1], 1), "%)"),
y = paste0("PC2 (", round(var_exp[2], 1), "%)"),
color = "Segmento"
) +
theme_minimal(base_size = 12)El análisis de correspondencia permite examinar la dependencia entre variables categóricas mediante distancias entre perfiles y una representación de baja dimensión (Mathai, Provost, & Haubold, 2022b). Para evitar interpretar significancia estadística únicamente por el tamaño muestral, cada prueba chi-cuadrado se complementó con V de Cramér.
as.data.frame.matrix(tabla_zt) %>%
rownames_to_column("Zona") %>%
knitr::kable(
caption = "Tabla 11. Frecuencias observadas de zona por tipo de vivienda",
format = "html",
table.attr = 'class="table table-striped table-hover"'
)| Zona | Apartamento | Casa |
|---|---|---|
| Zona Centro | 24 | 100 |
| Zona Norte | 1198 | 722 |
| Zona Oeste | 1029 | 169 |
| Zona Oriente | 62 | 289 |
| Zona Sur | 2787 | 1939 |
round(prop_zt, 1) %>%
as.data.frame.matrix() %>%
rownames_to_column("Zona") %>%
knitr::kable(
caption = "Tabla 12. Distribución porcentual del tipo de vivienda dentro de cada zona",
format = "html",
table.attr = 'class="table table-striped table-hover"'
)| Zona | Apartamento | Casa |
|---|---|---|
| Zona Centro | 19.4 | 80.6 |
| Zona Norte | 62.4 | 37.6 |
| Zona Oeste | 85.9 | 14.1 |
| Zona Oriente | 17.7 | 82.3 |
| Zona Sur | 59.0 | 41.0 |
tibble(
Indicador = c(
"Chi-cuadrado",
"Grados de libertad",
"p-valor",
"V de Cramér"
),
Resultado = c(
fmt_num(as.numeric(chi_zt$statistic), 2),
as.character(chi_zt$parameter),
fmt_p(chi_zt$p.value),
fmt_num(v_zt, 3)
)
) %>%
knitr::kable(
caption = "Tabla 13. Asociación entre zona y tipo de vivienda",
format = "html",
table.attr = 'class="table table-striped table-hover"'
)| Indicador | Resultado |
|---|---|
| Chi-cuadrado | 690,93 |
| Grados de libertad | 4 |
| p-valor | < 0,001 |
| V de Cramér | 0,288 |
Se rechaza la hipótesis de independencia entre zona y tipo de vivienda (\(p\) < 0,001). La V de Cramér de 0,288 muestra que la asociación, además de estadísticamente detectable, tiene una magnitud no despreciable.
as.data.frame(prop_zt) %>%
rename(Zona = Var1, Tipo = Var2, Porcentaje = Freq) %>%
ggplot(aes(x = Zona, y = Porcentaje, fill = Tipo)) +
geom_col() +
scale_y_continuous(labels = function(x) paste0(x, "%")) +
labs(
title = "Figura 11. Composición del tipo de vivienda por zona",
x = NULL,
y = "Porcentaje dentro de la zona",
fill = "Tipo"
) +
theme_minimal(base_size = 12) +
theme(axis.text.x = element_text(angle = 20, hjust = 1))Debido a que tipo contiene dos categorías, el análisis
de correspondencia simple tiene una sola dimensión no trivial. Por ello,
la visualización porcentual es más informativa que forzar un mapa
bidimensional. Las coordenadas de correspondencia se presentan como
anexo técnico.
round(prop_ze, 1) %>%
as.data.frame.matrix() %>%
rownames_to_column("Zona") %>%
knitr::kable(
caption = "Tabla 14. Distribución porcentual de estratos dentro de cada zona",
format = "html",
table.attr = 'class="table table-striped table-hover"'
)| Zona | 3 | 4 | 5 | 6 |
|---|---|---|---|---|
| Zona Centro | 84.7 | 11.3 | 3.2 | 0.8 |
| Zona Norte | 29.8 | 21.2 | 40.1 | 9.0 |
| Zona Oeste | 4.5 | 7.0 | 24.2 | 64.3 |
| Zona Oriente | 96.9 | 2.3 | 0.6 | 0.3 |
| Zona Sur | 8.1 | 34.2 | 35.7 | 22.1 |
tibble(
Indicador = c(
"Chi-cuadrado",
"Grados de libertad",
"p-valor",
"V de Cramér"
),
Resultado = c(
fmt_num(as.numeric(chi_ze$statistic), 2),
as.character(chi_ze$parameter),
fmt_p(chi_ze$p.value),
fmt_num(v_ze, 3)
)
) %>%
knitr::kable(
caption = "Tabla 15. Asociación entre zona y estrato",
format = "html",
table.attr = 'class="table table-striped table-hover"'
)| Indicador | Resultado |
|---|---|
| Chi-cuadrado | 3830,44 |
| Grados de libertad | 12 |
| p-valor | < 0,001 |
| V de Cramér | 0,392 |
La relación entre zona y estrato es significativa (\(p\) < 0,001) y presenta una V de Cramér de 0,392. Esto demuestra que la distribución socioeconómica de la oferta cambia sistemáticamente entre zonas.
ggplot(
ca_ze_plot,
aes(
x = `Dim 1`,
y = `Dim 2`,
label = Categoria,
color = Tipo
)
) +
geom_hline(yintercept = 0, linetype = 2, linewidth = 0.35) +
geom_vline(xintercept = 0, linetype = 2, linewidth = 0.35) +
geom_point(size = 3) +
geom_text(nudge_y = 0.04, check_overlap = TRUE) +
labs(
title = "Figura 12. Mapa de correspondencia entre zona y estrato",
x = paste0(
"Dimensión 1 (",
round(ca_ze$eig[1, 2], 1),
"%)"
),
y = paste0(
"Dimensión 2 (",
round(ca_ze$eig[2, 2], 1),
"%)"
),
color = "Categoría"
) +
theme_minimal(base_size = 12)Los dos primeros ejes explican 97,6 % de la inercia, por lo que el plano bidimensional representa adecuadamente la asociación. La proximidad entre una zona y un estrato indica perfiles relativamente similares respecto de la distribución esperada bajo independencia.
resumen_territorial %>%
mutate(
`Participación tipo (%)` = round(`Participación tipo (%)`, 1),
`Participación estrato (%)` = round(`Participación estrato (%)`, 1)
) %>%
knitr::kable(
caption = "Tabla 16. Perfil dominante de la oferta por zona",
format = "html",
table.attr = 'class="table table-striped table-hover"'
)| Zona | Tipo dominante | Participación tipo (%) | Estrato dominante | Participación estrato (%) |
|---|---|---|---|---|
| Zona Centro | Casa | 80.6 | 3 | 84.7 |
| Zona Norte | Apartamento | 62.4 | 5 | 40.1 |
| Zona Oeste | Apartamento | 85.9 | 6 | 64.3 |
| Zona Oriente | Casa | 82.3 | 3 | 96.9 |
| Zona Sur | Apartamento | 59.0 | 5 | 35.7 |
La tabla anterior resume, para cada zona, la tipología y el estrato con mayor participación. Esta lectura facilita trasladar el análisis de correspondencia a decisiones de segmentación comercial y definición de comparables.
La variable barrio contiene numerosas categorías. Para
conservar interpretabilidad se mantuvieron los 20 barrios de mayor
frecuencia y las demás observaciones se agruparon en “Otros barrios”; no
se eliminó ningún registro por esta decisión.
El Análisis de Correspondencia Múltiple amplía el análisis categórico al estudiar simultáneamente zona, tipo y barrio, proporcionando una representación conjunta de asociaciones entre modalidades (Lombardo, van de Velden, & Beh, 2023).
mca_plot <- mca_coord %>%
filter(
Variable != "Barrio" |
Etiqueta == "Otros barrios" |
Etiqueta %in% barrios_top[1:12]
)
ggplot(
mca_plot,
aes(
x = `Dim 1`,
y = `Dim 2`,
label = Etiqueta,
color = Variable
)
) +
geom_hline(yintercept = 0, linetype = 2, linewidth = 0.35) +
geom_vline(xintercept = 0, linetype = 2, linewidth = 0.35) +
geom_point(size = 2.7) +
geom_text(nudge_y = 0.035, check_overlap = TRUE, size = 3) +
labs(
title = "Figura 13. Correspondencia múltiple: zona, tipo y barrio",
x = "Dimensión 1",
y = "Dimensión 2",
color = "Variable"
) +
theme_minimal(base_size = 12)El MCA se presenta como análisis complementario y no reemplaza las tablas de contingencia ni las pruebas de independencia. Su valor consiste en detectar modalidades que tienden a ocupar posiciones próximas en un mismo espacio geométrico.
Las coordenadas geográficas se utilizan únicamente para comunicación y diagnóstico espacial; no forman parte del algoritmo de clustering. De esta manera se evita construir segmentos que sean, por definición, una consecuencia directa de la ubicación.
ggplot(
base_mapa,
aes(x = longitud, y = latitud, color = Segmento)
) +
geom_point(alpha = 0.25, size = 1) +
coord_equal() +
labs(
title = "Figura 14. Distribución espacial de los segmentos inmobiliarios",
x = "Longitud",
y = "Latitud",
color = "Segmento"
) +
theme_minimal(base_size = 12)Para mantener un HTML de tamaño razonable en RPubs, el mapa interactivo utiliza una muestra reproducible de hasta 2.500 propiedades. El clustering y todas las estadísticas anteriores se estiman con la totalidad de los casos completos.
pal_seg <- leaflet::colorFactor(
palette = "Set1",
domain = base_mapa_leaflet$Segmento
)
leaflet(base_mapa_leaflet) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
radius = 3,
stroke = FALSE,
fillOpacity = 0.60,
color = ~pal_seg(Segmento),
popup = ~paste0(
"<b>Segmento:</b> ", Segmento,
"<br><b>Zona:</b> ", zona,
"<br><b>Tipo:</b> ", tipo,
"<br><b>Precio:</b> ", preciom, " millones",
"<br><b>Área:</b> ", areaconst, " m²"
)
) %>%
addLegend(
position = "bottomright",
pal = pal_seg,
values = ~Segmento,
title = "Segmento"
)Los resultados evidencian que la oferta inmobiliaria tiene una estructura multidimensional. El precio no puede analizarse aisladamente: su comportamiento está vinculado con área construida, parqueaderos, baños, habitaciones y estrato. El ACP resume esta relación mediante dos dimensiones principales que concentran 78,4 % de la variabilidad, permitiendo una interpretación más parsimoniosa del mercado.
La segmentación mediante K-means transforma esta estructura continua en tres perfiles operativos. El grupo económico/compacto, el intermedio y el premium no difieren únicamente en precio; se separan simultáneamente por escala física y dotación. Esto significa que una estrategia comercial homogénea perdería información relevante y podría inducir comparaciones de valor entre inmuebles estructuralmente distintos.
La dimensión territorial también es determinante. Zona y tipo de vivienda presentan asociación, y la relación entre zona y estrato es aún más estructurada. Por tanto, la localización funciona como un marcador de composición del mercado y debe considerarse al diseñar campañas, seleccionar comparables y construir modelos futuros de valoración.
Finalmente, la correspondencia múltiple incorpora barrio sin reducir el análisis a una tabla excesivamente fragmentada. Su lectura confirma que los patrones categóricos de la oferta se organizan en combinaciones de localización, tipología y contexto barrial, reforzando la necesidad de segmentación territorial.
La oferta inmobiliaria puede resumirse eficientemente en dos dimensiones principales. PC1 y PC2 concentran 78,4 % de la variabilidad de las seis variables utilizadas, lo que permite reducir dimensionalidad sin perder la mayor parte de la información.
La primera dimensión representa escala económica y dotación física. Las variables con mayor contribución a PC1 son preciom, banios, areaconst, parqueaderos, por lo que este eje diferencia fundamentalmente propiedades por tamaño, valor y nivel de equipamiento.
La segunda dimensión captura configuración habitacional y posición socioeconómica. Las mayores contribuciones a PC2 corresponden a habitaciones y estrato.
El mercado no constituye un conjunto homogéneo. La solución de tres conglomerados genera perfiles económicamente interpretables —económico/compacto, intermedio y premium— y explica 49,7 % de la variación total mediante diferencias entre grupos.
La tipología de vivienda depende de la zona. La prueba de independencia entre zona y tipo fue significativa (\(p\) < 0,001), con V de Cramér = 0,288.
La segmentación socioeconómica presenta un fuerte componente territorial. Zona y estrato no son independientes (\(p\) < 0,001), con V de Cramér = 0,392. Los dos primeros ejes del análisis de correspondencia explican 97,6 % de la inercia.
La calidad de datos condiciona el alcance del
análisis. La elevada ausencia observada en piso
impide incorporarla responsablemente al modelo principal sin un estudio
específico del mecanismo de faltantes.
Construir políticas de valoración diferenciadas por segmento. Los comparables utilizados para estimar precios deberían pertenecer al mismo perfil inmobiliario o incorporar explícitamente el segmento como covariable.
Orientar el segmento premium hacia atributos de diferenciación, especialmente área, dotación, parqueaderos, confort y localización, reduciendo la importancia de estrategias basadas exclusivamente en descuentos.
En el segmento económico/compacto, priorizar accesibilidad y eficiencia espacial, acompañadas de alternativas de financiación y mensajes comerciales centrados en costo total de adquisición.
Diseñar estrategias comerciales por zona, puesto que la composición por tipología y estrato no es uniforme en la ciudad.
Fortalecer la captura y normalización de datos,
especialmente piso, parqueaderos y nombres de barrio. Una
mejor calidad de estas variables aumentará la confiabilidad de futuros
modelos predictivos.
Extender el estudio hacia un modelo de valoración, incorporando variables adicionales como antigüedad, estado del inmueble, administración, amenidades, accesibilidad y distancia a puntos de interés.
parqueaderos; si el patrón de ausencia no fuera aleatorio
podría generarse sesgo de selección.Durante el render se ejecutan controles automáticos que verifican:
Por tanto, si este documento finaliza el proceso de render sin errores, las validaciones estructurales principales del análisis también han sido superadas.
Lombardo, R., van de Velden, M., & Beh, E. J. (2023). Three-Way Correspondence Analysis in R. The R Journal, 15(2), 237–262. https://doi.org/10.32614/RJ-2023-049
Mathai, A. M., Provost, S. B., & Haubold, H. J. (2022a). Chapter 9: Principal Component Analysis. En Multivariate Statistical Analysis in the Real and Complex Domains (pp. 597–639). Springer. https://doi.org/10.1007/978-3-030-95864-0_9
Mathai, A. M., Provost, S. B., & Haubold, H. J. (2022b). Chapter 15: Cluster Analysis and Correspondence Analysis. En Multivariate Statistical Analysis in the Real and Complex Domains (pp. 845–886). Springer. https://doi.org/10.1007/978-3-030-95864-0_15
Wickham, H., Çetinkaya-Rundel, M., & Grolemund, G. (2023). R for Data Science (2nd ed.). O’Reilly Media.
coords_zt_filas <- as.data.frame(ca_zt$row$coord) %>%
rownames_to_column("Zona")
coords_zt_cols <- as.data.frame(ca_zt$col$coord) %>%
rownames_to_column("Tipo")
knitr::kable(
coords_zt_filas,
digits = 4,
caption = "Tabla A1. Coordenadas de las zonas en la correspondencia zona–tipo",
format = "html",
table.attr = 'class="table table-striped table-hover"'
)| Zona | ca_zt\(row\)coord |
|---|---|
| Zona Centro | -0.8613 |
| Zona Norte | 0.0224 |
| Zona Oeste | 0.5048 |
| Zona Oriente | -0.8960 |
| Zona Sur | -0.0479 |
knitr::kable(
coords_zt_cols,
digits = 4,
caption = "Tabla A2. Coordenadas del tipo de vivienda en la correspondencia zona–tipo",
format = "html",
table.attr = 'class="table table-striped table-hover"'
)| Tipo | V1 |
|---|---|
| Apartamento | 0.2290 |
| Casa | -0.3627 |
## R version 4.5.2 (2025-10-31 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26200)
##
## Matrix products: default
## LAPACK version 3.12.1
##
## locale:
## [1] LC_COLLATE=Spanish_Colombia.utf8 LC_CTYPE=Spanish_Colombia.utf8
## [3] LC_MONETARY=Spanish_Colombia.utf8 LC_NUMERIC=C
## [5] LC_TIME=Spanish_Colombia.utf8
##
## time zone: America/Bogota
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] leaflet_2.2.3 scales_1.4.0 cluster_2.1.8.3 FactoMineR_2.16
## [5] psych_2.6.1 stringi_1.8.7 stringr_1.6.0 ggplot2_4.0.2
## [9] tibble_3.3.1 tidyr_1.3.2 dplyr_1.2.0
##
## loaded via a namespace (and not attached):
## [1] gtable_0.3.6 xfun_0.56 bslib_0.10.0
## [4] htmlwidgets_1.6.4 ggrepel_0.9.8 lattice_0.22-7
## [7] leaflet.providers_3.0.0 vctrs_0.7.1 tools_4.5.2
## [10] crosstalk_1.2.2 generics_0.1.4 parallel_4.5.2
## [13] pkgconfig_2.0.3 Matrix_1.7-4 RColorBrewer_1.1-3
## [16] S7_0.2.1 scatterplot3d_0.3-45 lifecycle_1.0.5
## [19] compiler_4.5.2 farver_2.1.2 mnormt_2.1.2
## [22] leaps_3.2 htmltools_0.5.9 sass_0.4.10
## [25] yaml_2.3.12 pillar_1.11.1 jquerylib_0.1.4
## [28] MASS_7.3-65 flashClust_1.1-4 DT_0.34.0
## [31] cachem_1.1.0 nlme_3.1-168 tidyselect_1.2.1
## [34] digest_0.6.39 mvtnorm_1.3-3 purrr_1.2.1
## [37] showtextdb_3.0 splines_4.5.2 labeling_0.4.3
## [40] fastmap_1.2.0 grid_4.5.2 cli_3.6.5
## [43] magrittr_2.0.4 withr_3.0.2 showtext_0.9-8
## [46] estimability_2.0.0 rmarkdown_2.30 emmeans_2.0.4
## [49] sysfonts_0.8.9 otel_0.2.0 ggtext_0.2.0
## [52] evaluate_1.0.5 knitr_1.51 irlba_2.3.7
## [55] mgcv_1.9-3 rlang_1.1.7 gridtext_0.1.6
## [58] Rcpp_1.1.1 xtable_1.8-4 glue_1.8.0
## [61] xml2_1.5.2 rstudioapi_0.18.0 jsonlite_2.0.0
## [64] R6_2.6.1 multcompView_0.1-12