1 Resumen ejecutivo

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.

2 1. Planteamiento del problema

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.

2.1 1.1 Objetivo general

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.

2.2 1.2 Objetivos específicos

  1. Evaluar la estructura, calidad y relaciones bivariadas de las variables de la base.
  2. Identificar componentes principales que sinteticen las características cuantitativas de las viviendas.
  3. Construir segmentos homogéneos de propiedades mediante análisis de conglomerados.
  4. Evaluar la asociación entre zona, tipo de vivienda, estrato y barrio mediante análisis de correspondencia.
  5. Traducir los hallazgos estadísticos en conclusiones y recomendaciones orientadas a la gestión inmobiliaria.

3 2. Datos y estrategia metodológica

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:

  1. auditoría de calidad y análisis exploratorio;
  2. análisis bivariado de variables cuantitativas y categóricas;
  3. reducción de dimensionalidad mediante ACP;
  4. segmentación mediante K-means;
  5. análisis de correspondencia simple y múltiple, complementado con visualización geográfica.

3.1 2.1 Clasificación de variables

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"'
)
Tabla 1. Clasificación estadística de las variables
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.

4 3. Calidad y preparación de los datos

4.1 3.1 Valores faltantes

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"'
  )
Tabla 2. Valores faltantes por variable
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.

4.2 3.2 Duplicidad del identificador

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"'
  )
Tabla 3. Verificación de integridad del identificador
Indicador Valor
Registros totales 8322
ID no faltantes 8319
ID duplicados entre valores no faltantes 0

5 4. Análisis exploratorio y bivariado

5.1 4.1 Estadísticos descriptivos

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"'
  )
Tabla 4. Estadísticos descriptivos de las variables utilizadas
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.

5.2 4.2 Distribución del precio

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.

5.3 4.3 Relación entre área construida y precio

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.

5.4 4.4 Matriz de correlaciones

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"'
  )
Tabla 5. Matriz de correlación de Spearman
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

6 5. Análisis de Componentes Principales

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.

6.1 5.1 Adecuación para reducción dimensional

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"'
  )
Tabla 6. Adecuación de la matriz para ACP
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.

6.2 5.2 Varianza explicada

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"'
  )
Tabla 7. Varianza explicada por los componentes principales
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.

6.3 5.3 Cargas y contribuciones

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"'
  )
Tabla 8. Cargas de las variables en los tres primeros componentes
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"'
  )
Tabla 9. Contribución porcentual a PC1 y PC2
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.

6.4 5.4 Círculo de correlaciones

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.

6.5 5.5 Mapa de individuos

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.

7 6. Análisis de Conglomerados

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.

7.1 6.1 Selección del número de conglomerados

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.

7.2 6.2 Perfil de los segmentos

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"'
  )
Tabla 10. Perfil promedio de los tres segmentos
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.

7.2.1 Segmento económico / compacto

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.

7.2.2 Segmento intermedio

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.

7.2.3 Segmento premium

Concentra propiedades de mayor tamaño, precio, número de baños y parqueaderos, además de mayor estrato promedio. Este grupo requiere una estrategia de posicionamiento basada en exclusividad, confort, amplitud y localización.

7.3 6.3 Visualización de los segmentos en el plano PCA

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)

8 7. Análisis de Correspondencia

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.

8.1 7.1 Zona y tipo de vivienda

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"'
  )
Tabla 11. Frecuencias observadas de zona por tipo de vivienda
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"'
  )
Tabla 12. Distribución porcentual del tipo de vivienda dentro de cada zona
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"'
  )
Tabla 13. Asociación entre zona y tipo de vivienda
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.

8.2 7.2 Zona y estrato

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"'
  )
Tabla 14. Distribución porcentual de estratos dentro de cada zona
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"'
  )
Tabla 15. Asociación entre zona y estrato
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.

8.3 7.3 Mapa de correspondencia: zona y estrato

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.

8.4 7.4 Síntesis territorial

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"'
  )
Tabla 16. Perfil dominante de la oferta por zona
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.

9 8. Barrio, zona y tipo: correspondencia múltiple

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.

10 9. Visualización espacial de los segmentos

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.

10.1 9.1 Distribución espacial

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)

10.2 9.2 Mapa interactivo

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"
  )

11 10. Discusión integrada

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.

12 11. Conclusiones

  1. 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.

  2. 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.

  3. La segunda dimensión captura configuración habitacional y posición socioeconómica. Las mayores contribuciones a PC2 corresponden a habitaciones y estrato.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

13 12. Recomendaciones para la toma de decisiones

  1. 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.

  2. 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.

  3. 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.

  4. Diseñar estrategias comerciales por zona, puesto que la composición por tipología y estrato no es uniforme en la ciudad.

  5. 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.

  6. 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.

14 13. Limitaciones

  • La base representa ofertas publicadas, no precios efectivos de cierre de transacciones.
  • No se dispone de variables como antigüedad, estado de conservación, administración, amenidades, accesibilidad o calidad del entorno.
  • El estrato es una variable ordinal y se incorpora al ACP y clustering como puntuación numérica creciente; esta decisión simplifica su escala de medición.
  • El análisis principal utiliza casos completos en parqueaderos; si el patrón de ausencia no fuera aleatorio podría generarse sesgo de selección.
  • Los conglomerados son descriptivos y no deben interpretarse como relaciones causales.
  • El agrupamiento de barrios poco frecuentes en “Otros barrios” mejora la legibilidad del MCA, pero reduce detalle en categorías de baja frecuencia.

15 14. Validación técnica y reproducibilidad

Durante el render se ejecutan controles automáticos que verifican:

  • existencia de las 13 variables requeridas;
  • disponibilidad de registros válidos;
  • ausencia de NA en la matriz utilizada por ACP y clustering;
  • suma de varianza explicada del ACP igual a 100 %;
  • correspondencia entre número de observaciones y asignaciones de K-means;
  • ausencia de conglomerados vacíos;
  • dimensiones válidas de las tablas de contingencia.

Por tanto, si este documento finaliza el proceso de render sin errores, las validaciones estructurales principales del análisis también han sido superadas.

16 15. Referencias

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.

17 Anexo A. Coordenadas del análisis de correspondencia zona–tipo

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"'
)
Tabla A1. Coordenadas de las zonas en la correspondencia zona–tipo
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"'
)
Tabla A2. Coordenadas del tipo de vivienda en la correspondencia zona–tipo
Tipo V1
Apartamento 0.2290
Casa -0.3627

18 Anexo B. Información de la sesión

sessionInfo()
## 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