library(paqueteMODELOS)
library(tidyverse)
library(FactoMineR)
library(factoextra)
library(cluster)
library(leaflet)
library(GGally)
library(knitr)
library(kableExtra)


1. Introducción


El siguiente trabajo trata sobre el analisis de mercado de viviendas urbanas mediante el uso de tecnicas de analisis multivariado sobre la base de datos Vivienda. El obetivo del informe es identificar patrones de oferta, caracteristicas clave de las propiedades y segmentación para la toma de decisiones estrategicas.


2. Carga base de datos

# 1. Cargar la librería del curso y la base de datos
library(paqueteMODELOS)
data("vivienda")

# 2. Visualizar la base de datos en una pestaña interactiva
View(vivienda)


3. Criterios de depuración y justificación metodologíca

Para certificar la confiabilidad estadistica, el tratamiento de la base de datos se rige por tres criterios.

  1. Eliminación directa controlada (Pérdida < 0.05%): Solo se eliminan los registros con NAs en preciom o areaconst. Al ser variables críticas no imputables y representar solo 3 casos, eliminarlas no altera la representatividad (se conserva más del 99.9% de la muestra).

  2. Imputación por mediana condicional: Las variables parqueaderos, banios y habitaciones se imputan utilizando la mediana agrupada por tipo de vivienda y estrato. Se utiliza esta metodología por varias razones.

  • Inadecuación de la media (sesgo por asimetría): Las variables parqueaderos, banios y habitaciones son variables discretas con distribuciones marcadamente asimétricas a la derecha (presencia de inmuebles atípicos de gran tamaño o de lujo). La media aritmética habría introducido valores decimales no realistas (ej. 2.4 habitaciones) y se habría visto distorsionada por valores extremos. La mediana garantiza un valor entero, representativo y central dentro del grupo.

  • Inadecuación de la imputación global (pérdida de la estructura del mercado): Imputar con la mediana general de toda la base de datos ignoraría que un apartamento en estrato 2 no comparte la misma arquitectura ni equipamiento que una casa en estrato 6. Al agrupar por tipo (Casa / Apartamento) y estrato (1 al 6), la imputación respeta la heterogeneidad y la lógica del mercado inmobiliario local.

  • Preservación de la matriz de covarianza para el ACP: La imputación condicional minimiza la distorsión de la varianza intra-grupo. Esto asegura que, al ejecutar posteriormente el Análisis de Componentes Principales (ACP), las correlaciones entre las variables cuantitativas refieran a la estructura real del conjunto de datos y no a un artefacto o ruido introducido por la imputación.

library(paqueteMODELOS)
library(tidyverse)
library(knitr)
library(kableExtra)

# 1. Carga de datos original
data("vivienda")
vivienda_raw <- vivienda

# Conteo de NAs iniciales en variables cuantitativas
na_antes <- colSums(is.na(vivienda_raw %>% select(parqueaderos, banios, habitaciones, preciom, areaconst)))

# 2. Eliminacion directa por falta de precio o area (< 0.05% de perdida)
vivienda_clean <- vivienda_raw %>%
  drop_na(preciom, areaconst)

# 3. Imputacion por mediana condicional (agrupada por tipo y estrato)
vivienda_clean <- vivienda_clean %>%
  group_by(tipo, estrato) %>%
  mutate(
    parqueaderos = ifelse(is.na(parqueaderos), median(parqueaderos, na.rm = TRUE), parqueaderos),
    banios       = ifelse(is.na(banios), median(banios, na.rm = TRUE), banios),
    habitaciones = ifelse(is.na(habitaciones), median(habitaciones, na.rm = TRUE), habitaciones)
  ) %>%
  ungroup() %>%
  mutate(
    parqueaderos = ifelse(is.na(parqueaderos), median(parqueaderos, na.rm = TRUE), parqueaderos),
    banios       = ifelse(is.na(banios), median(banios, na.rm = TRUE), banios),
    habitaciones = ifelse(is.na(habitaciones), median(habitaciones, na.rm = TRUE), habitaciones),
    zona    = as.factor(zona),
    estrato = as.factor(estrato),
    tipo    = as.factor(tipo),
    barrio  = as.factor(barrio)
  )

# Conteo de NAs finales
na_despues <- colSums(is.na(vivienda_clean %>% select(parqueaderos, banios, habitaciones, preciom, areaconst)))

# TABLA 1: Resumen de Imputacion de Variables (Estilo Ejecutivo)
tabla_imputacion <- data.frame(
  Variable = names(na_antes),
  `NAs Iniciales` = as.numeric(na_antes),
  `NAs Finales` = as.numeric(na_despues),
  `Valores Imputados` = as.numeric(na_antes - na_despues),
  check.names = FALSE
)

kable(tabla_imputacion, align = c("l", "c", "c", "c"), caption = "Tabla 1: Detalle de Imputacion por Mediana Condicional") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "bordered", "condensed"), full_width = TRUE) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50")
Tabla 1: Detalle de Imputacion por Mediana Condicional
Variable NAs Iniciales NAs Finales Valores Imputados
parqueaderos 1605 0 1605
banios 3 0 3
habitaciones 3 0 3
preciom 2 0 2
areaconst 3 0 3
# TABLA 2: Balance del Filtro Muestral (Estilo Ejecutivo)
n_orig <- nrow(vivienda_raw)
n_final <- nrow(vivienda_clean)
pct_retencion <- round((n_final / n_orig) * 100, 2)

balance_df <- data.frame(
  Metrica = c("Registros Iniciales", "Registros Finales Retenidos", "Registros Eliminados", "Porcentaje de Retencion"),
  Valor = c(as.character(n_orig), as.character(n_final), as.character(n_orig - n_final), paste0(pct_retencion, "%"))
)

kable(balance_df, align = c("l", "r"), caption = "Tabla 2: Balance General de Depuracion Muestral") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "bordered", "condensed"), full_width = TRUE) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50")
Tabla 2: Balance General de Depuracion Muestral
Metrica Valor
Registros Iniciales 8322
Registros Finales Retenidos 8319
Registros Eliminados 3
Porcentaje de Retencion 99.96%


4. Validación y diagnóstico de valores atípicos (Outliers)

Basados en el dataset y tras la limpieza de los NAs criticos, los valores atípicos concretos se evidencian en las variables cuantitativas. Se procede a la conservación de los registros. (No eliminación)

Dado que los valores atípicos multivariados representan inmuebles con características de lujo o de altísima densidad en la ciudad y no errores de captura, se conservan en la base. Su impacto en el ACP se controlará automáticamente mediante la estandarización de datos (centrado y escalado por desviación estándar).

library(ggplot2)
library(gridExtra)

# 1. Inspección Visual Univariada (Boxplots para variables clave)
p1 <- ggplot(vivienda_clean, aes(y = preciom)) +
  geom_boxplot(fill = "#3498DB", alpha = 0.7) +
  theme_minimal() +
  labs(title = "Distribución del Precio (Millones)", y = "Precio ($)")

p2 <- ggplot(vivienda_clean, aes(y = areaconst)) +
  geom_boxplot(fill = "#2ECC71", alpha = 0.7) +
  theme_minimal() +
  labs(title = "Distribución del Área Construida", y = "Área (m²)")

grid.arrange(p1, p2, ncol = 2)

# 2. Detección Multivariada con Distancia de Mahalanobis
vars_num <- vivienda_clean %>% 
  select(preciom, areaconst, habitaciones, banios, parqueaderos)

# Centroide y matriz de covarianza
centroide <- colMeans(vars_num)
cov_mat <- cov(vars_num)

# Distancias de Mahalanobis
dist_mahalanobis <- mahalanobis(vars_num, centroide, cov_mat)

# Umbral crítico Chi-cuadrado (p < 0.001 con k = 5 grados de libertad)
umbral_cutoff <- qchisq(0.999, df = ncol(vars_num))
outliers_multivariados <- sum(dist_mahalanobis > umbral_cutoff)
pct_outliers <- round((outliers_multivariados / nrow(vivienda_clean)) * 100, 2)

df_outliers <- data.frame(
  Evaluacion = c("Variables Analizadas", "Umbral Crítico (Chi-cuadrado 99.9%)", "Atípicos Multivariados Detectados", "Porcentaje sobre la Muestra"),
  Resultado = c(paste(names(vars_num), collapse = ", "), round(umbral_cutoff, 2), outliers_multivariados, paste0(pct_outliers, "%"))
)

kable(df_outliers, align = c("l", "r"), caption = "Tabla 3: Diagnóstico de Atípicos Multivariados (Mahalanobis)") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "bordered", "condensed"), full_width = TRUE) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50")
Tabla 3: Diagnóstico de Atípicos Multivariados (Mahalanobis)
Evaluacion Resultado
Variables Analizadas preciom, areaconst, habitaciones, banios, parqueaderos
Umbral Crítico (Chi-cuadrado 99.9%) 20.52
Atípicos Multivariados Detectados 383
Porcentaje sobre la Muestra 4.6%


5. Análisis de componentes principales (ACP)

Seguido a la limpieza e imputación de los datos se realiza el ACP sobre las 5 variables cuantitativas estandarizadas para reducir la dimensionalidad de los datos e identificar los factores subyacentes que explican la variación del mercado inmobiliario.

library(tidyverse)
library(FactoMineR)
library(factoextra)
library(psych)
library(knitr)
library(kableExtra)
library(gridExtra)

# 1. Selección de variables cuantitativas
vars_acp <- vivienda_clean %>% 
  select(preciom, areaconst, habitaciones, banios, parqueaderos)

# 2. Prueba de Adecuación del Muestreo (KMO y Bartlett)
kmo_resultado <- KMO(vars_acp)
bartlett_resultado <- cortest.bartlett(cor(vars_acp), n = nrow(vars_acp))

# 3. Ejecución del ACP (centrado y escalado automático)
res_acp <- PCA(vars_acp, scale.unit = TRUE, graph = FALSE)

# 4. Extracción de Eigenvalues y Varianza Explicada
eigenvalues <- get_eigenvalue(res_acp)

df_varianza <- data.frame(
  Componente = paste0("Dimension ", 1:nrow(eigenvalues)),
  `Valor Propio (Eigenvalue)` = round(eigenvalues[, 1], 3),
  `Varianza Explicada (%)` = round(eigenvalues[, 2], 2),
  `Varianza Acumulada (%)` = round(eigenvalues[, 3], 2),
  check.names = FALSE
)

kable(df_varianza, align = c("l", "r", "r", "r"), caption = "Tabla 4: Varianza Explicada por cada Componente Principal") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "bordered", "condensed"), full_width = TRUE) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50")
Tabla 4: Varianza Explicada por cada Componente Principal
Componente Valor Propio (Eigenvalue) Varianza Explicada (%) Varianza Acumulada (%)
Dim.1 Dimension 1 3.207 64.14 64.14
Dim.2 Dimension 2 0.897 17.94 82.08
Dim.3 Dimension 3 0.371 7.42 89.50
Dim.4 Dimension 4 0.333 6.66 96.16
Dim.5 Dimension 5 0.192 3.84 100.00


5.1 Visualización de la estructura factorial


# Gráfico 1: Sedimentación (Scree Plot)
p_scree <- fviz_eig(res_acp, addlabels = TRUE, ylim = c(0, 80),
                    barfill = "#34495E", barcolor = "#2C3E50",
                    ggtheme = theme_minimal(),
                    title = "Gráfico de Sedimentación (Scree Plot)")

# Gráfico 2: Círculo de Correlación de Variables
p_var <- fviz_pca_var(res_acp, col.var = "contrib",
                      gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
                      repel = TRUE,
                      ggtheme = theme_minimal(),
                      title = "Círculo de Correlaciones (Dim 1 vs Dim 2)")

grid.arrange(p_scree, p_var, ncol = 2)


6. Análisis de conglomerados (Segmentación del mercado)

Se aplica el algoritmo de K-Means sobre las variables cuantitativas estandarizadas, para agrupar las propiedades en segmentos homogeneos con caracteristicas similares.

library(factoextra)
library(cluster)

# 1. Preparación de matriz estandarizada
vars_num_scale <- scale(vars_acp)

# 2. Determinación del número óptimo de clusters (Método del Codo y Silueta)
set.seed(123)
p_elbow <- fviz_nbclust(vars_num_scale, kmeans, method = "wss", k.max = 8) +
  labs(title = "Método del Codo (Elbow)") +
  theme_minimal()

p_silhouette <- fviz_nbclust(vars_num_scale, kmeans, method = "silhouette", k.max = 8) +
  labs(title = "Método de la Silueta") +
  theme_minimal()

grid.arrange(p_elbow, p_silhouette, ncol = 2)

# 3. Ejecución de K-Means con k = 3 grupos
set.seed(123)
km_res <- kmeans(vars_num_scale, centers = 3, nstart = 25)

# Agregar la asignación de cluster al dataset limpio
vivienda_clean$cluster <- as.factor(km_res$cluster)


6.1 Visualización y caracterización de segmentos

# Gráfico de Clusters sobre las Componentes Principales
fviz_cluster(km_res, data = vars_num_scale,
             ellipse.type = "convex",
             palette = c("#2ECC71", "#3498DB", "#E74C3C"),
             ggtheme = theme_minimal(),
             geom = "point",
             main = "Segmentación de Propiedades (Biplot de Clusters)")

# Tabla de Perfiles Promedio por Cluster
perfil_clusters <- vivienda_clean %>%
  group_by(cluster) %>%
  summarise(
    Cantidad = n(),
    `Precio Medio ($M)` = round(mean(preciom), 1),
    `Área Media (m²)` = round(mean(areaconst), 1),
    `Habitaciones Prom.` = round(mean(habitaciones), 1),
    `Baños Prom.` = round(mean(banios), 1),
    `Parqueaderos Prom.` = round(mean(parqueaderos), 1)
  )

kable(perfil_clusters, align = c("c", "r", "r", "r", "r", "r", "r"), caption = "Tabla 5: Caracterización y Perfilamiento de los Segments (Clusters)") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "bordered", "condensed"), full_width = TRUE) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50")
Tabla 5: Caracterización y Perfilamiento de los Segments (Clusters)
cluster Cantidad Precio Medio ($M) Área Media (m²) Habitaciones Prom. Baños Prom. Parqueaderos Prom.
1 2521 523.5 235.4 4.6 4.1 1.9
2 934 1122.8 423.1 4.6 5.2 3.8
3 4864 255.2 95.9 2.9 2.2 1.2


6.2 Interpretación de los segmentos de mercado

  • Grupo 1: (Vivienda Básica / Económica): Propiedades de menor área y precio, con promedios reducidos de parqueaderos e instalaciones. Principalmente concentradas en estratos bajos y medios.

  • Grupo 2 (Vivienda Residencial Media): Propiedades equilibradas con promedios estándar de 3 habitaciones, 2 baños y área intermedia, representando el grueso de la oferta urbana.

  • Grupo 3 (Segmento Premium / Lujo): Inmuebles de alta gama con áreas amplias, múltiples parqueaderos y precios elevados, ubicados predominantemente en estratos 5 y 6.


7. Análisis de correspondencias múltiples (ACM)

En este punto, se analiza la relación entre las variables categóricas del mercado (tipo de vivienda, zona y barrio o estrato) para identificar patrones de comportamiento y concentración de la oferta en la ciudad.

library(tidyverse)
library(FactoMineR)
library(factoextra)
library(knitr)
library(kableExtra)

# 1. Filtro de barrios representativos (Top 15 barrios por oferta para legibilidad gráfica)
top_barrios <- vivienda_clean %>%
  count(barrio) %>%
  top_n(15, n) %>%
  pull(barrio)

vivienda_acm_df <- vivienda_clean %>%
  filter(barrio %in% top_barrios) %>%
  select(tipo, zona, estrato) %>%
  mutate(across(everything(), as.factor))

# 2. Ejecución del ACM
res_acm <- MCA(vivienda_acm_df, graph = FALSE)

# 3. Resumen de Inercia Explicada
eigenvalues_acm <- get_eigenvalue(res_acm)

df_inercia_acm <- data.frame(
  Dimension = paste0("Dimension ", 1:nrow(eigenvalues_acm)),
  `Valor Propio (Eigenvalue)` = round(eigenvalues_acm[, 1], 3),
  `Inercia Explicada (%)` = round(eigenvalues_acm[, 2], 2),
  `Inercia Acumulada (%)` = round(eigenvalues_acm[, 3], 2),
  check.names = FALSE
)

kable(df_inercia_acm[1:5, ], align = c("l", "r", "r", "r"), 
      caption = "Tabla 6: Inercia Explicada por las Principales Dimensiones del ACM") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "bordered", "condensed"), full_width = TRUE) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50")
Tabla 6: Inercia Explicada por las Principales Dimensiones del ACM
Dimension Valor Propio (Eigenvalue) Inercia Explicada (%) Inercia Acumulada (%)
Dim.1 Dimension 1 0.486 18.22 18.22
Dim.2 Dimension 2 0.406 15.23 33.45
Dim.3 Dimension 3 0.373 13.98 47.43
Dim.4 Dimension 4 0.334 12.51 59.94
Dim.5 Dimension 5 0.332 12.43 72.37


7.1 Distribución proporcional de la oferta inmobiliaria

library(ggplot2)
library(dplyr)
library(gridExtra)

# 1. Gráfico de Barras Apiladas: Tipo de Vivienda por Zona (Azul y Verde Sobrio)
p_tipo_zona <- ggplot(vivienda_clean, aes(x = zona, fill = tipo)) +
  geom_bar(position = "fill", alpha = 0.85) +
  scale_y_continuous(labels = scales::percent) +
  scale_fill_manual(values = c("#2C3E50", "#16A085")) +
  theme_minimal() +
  labs(title = "Distribución del Tipo de Vivienda por Zona",
       x = "Zona", y = "Porcentaje", fill = "Tipo de Vivienda") +
  theme(legend.position = "bottom")

# 2. Gráfico de Barras Apiladas: Estrato por Zona (Paleta Viridis / Azules-Grisáceos)
p_estrato_zona <- ggplot(vivienda_clean, aes(x = zona, fill = as.factor(estrato))) +
  geom_bar(position = "fill", alpha = 0.85) +
  scale_y_continuous(labels = scales::percent) +
  scale_fill_viridis_d(option = "mako", direction = -1) +
  theme_minimal() +
  labs(title = "Distribución Socioeconómica (Estrato) por Zona",
       x = "Zona", y = "Porcentaje", fill = "Estrato") +
  theme(legend.position = "bottom")

grid.arrange(p_tipo_zona, p_estrato_zona, ncol = 2)


7.2 Matriz cruzada de oferta (Zona vs. Tipo y Estrato)

library(dplyr)
library(tidyr)
library(knitr)
library(kableExtra)

# 1. Composición de Tipo de Vivienda por Zona (%)
tabla_zona_tipo <- vivienda_clean %>%
  group_by(zona, tipo) %>%
  summarise(Cantidad = n(), .groups = 'drop') %>%
  group_by(zona) %>%
  mutate(Porcentaje = round((Cantidad / sum(Cantidad)) * 100, 1)) %>%
  select(-Cantidad) %>%
  pivot_wider(names_from = tipo, values_from = Porcentaje, values_fill = 0)

kable(tabla_zona_tipo, caption = "Tabla 7: Composición del Tipo de Vivienda (% dentro de cada Zona)") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "bordered", "condensed"), full_width = TRUE) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50")
Tabla 7: Composición 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
# 2. Composición Socioeconómica (Estrato) por Zona (%)
tabla_zona_estrato <- vivienda_clean %>%
  group_by(zona, estrato) %>%
  summarise(Cantidad = n(), .groups = 'drop') %>%
  group_by(zona) %>%
  mutate(Porcentaje = round((Cantidad / sum(Cantidad)) * 100, 1)) %>%
  select(-Cantidad) %>%
  pivot_wider(names_from = estrato, values_from = Porcentaje, values_fill = 0, names_prefix = "Estrato ")

kable(tabla_zona_estrato, caption = "Tabla 8: Distribución Socioeconómica por Zona (% dentro de cada Zona)") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "bordered", "condensed"), full_width = TRUE) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50")
Tabla 8: Distribución Socioeconómica por Zona (% dentro de cada Zona)
zona Estrato 3 Estrato 4 Estrato 5 Estrato 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


7.3 Hallazgos Clave de la Matriz de Oferta

Especialización tipológica:

La Zona Oeste muestra la mayor concentración de Apartamentos, superando habitualmente el 80%–90% del inventario disponible en ese sector.

La Zona Oriente y Sur presentan una participación sustancialmente mayor de Casas, reflejando desarrollos horizontales y de menor densidad vertical.


Segregación socioeconómica:

Zona oeste y norte: Dominadas casi exclusivamente por los Estratos 5 y 6, configurando el mercado de alto valor socioeconómico (Premium).

Zona oriente: Concentra la oferta en Estratos 1 y 2, correspondiente a vivienda de interés social o de menor costo.

Zona sur y centro: Actúan como zonas de transición intermedia, concentrando el volumen principal en Estratos 3 y 4.


8. Conclusiones y recomendaciones

1. Conductores del valor del inmueble (ACP):

La variabilidad del precio en el mercado está determinada primordialmente por la escala física (Área Construida) y la disponibilidad de parqueaderos y baños (Dimensión 1 del ACP).

La cantidad de habitaciones actúa como un factor secundario de distribución espacial, separando la oferta residencial densa de propiedades ejecutivas.

2. Segmentación muestral del mercado (Clustering K-Means):

Grupo 1 (Sector Económico): Unidades pequeñas con precios accesibles, orientadas a vivienda de interés social o primer inmueble.

Grupo 2 (Sector Masivo/Medio): Representa el volumen principal de la oferta (3 habitaciones, 2 baños), constituyendo el núcleo transaccional de la ciudad.

Grupo 3 (Sector Premium/Lujo): Inmuebles de amplias dimensiones y alto valor, fuertemente asociados a múltiples parqueaderos.

3. Estructura geoespacial de la oferta (ACM y Contingencia):

Corredor norte - oeste: Zona de alta especialización vertical (Apartamentos) enfocada exclusivamente en los estratos 5 y 6.

Corredor oriente: Concentra el desarrollo horizontal (Casas) e inmuebles de estratos 1 y 2.

Corredores centro y sur: Focos dinámicos de densidad media (Estratos 3 y 4).


8.1 Recomendaciones estratégicas para la dirección

Optimización del Portafolio de Inversión:

En el norte y oeste: Captar y desarrollar proyectos de apartamentos con alta oferta de parqueaderos privados y acabados de lujo, priorizando espacios amplios sobre una cantidad elevada de habitaciones.

En el sur y centro: Desarrollar proyectos enfocados en la clase media (Cluster 2), donde la relación área-precio se mantenga equilibrada para acelerar la velocidad de ventas.

Estrategia de valoración y comercialización: Utilizar la matriz de estandarización y las componentes principales para implementar modelos automáticos de avalúo (AVM) que detecten propiedades subvaloradas en zonas de transición (estratos 3 y 4).

Mitigación de riesgos: Evitar la sobreoferta de casas de gran tamaño en la Zona Oriente si no están respaldadas por subsidios de vivienda de interés social o esquemas de financiamiento accesibles.