# Cargar los datos limpios y estandarizados
vivienda_std <- readRDS("data/vivienda_std.rds") # matriz estandarizada (solo numéricas)
vivienda_final <- readRDS("data/vivienda_final.rds") # df limpio con factores para colorear

1 Exploración y preparación de los datos

Antes de realizar los análisis, se llevó a cabo una limpieza y preparación exhaustiva de la base de datos para garantizar la calidad y consistencia de los resultados. Para detalles completos del procedimiento y el código relevante de limpieza, ver Anexos al final de este informe.

1.1 Tipos de datos y depuración inicial

  • Se verificaron y ajustaron los tipos de variables (numéricas/categóricas).
  • Se eliminaron registros duplicados y filas vacías para minimizar sesgos.

1.2 Tratamiento de inconsistencias

  • En la variable tipo (Casa/Apartamento/Desconocido), se detectaron incoherencias en la variable piso.
    Por ejemplo, algunas propiedades clasificadas como Casa tenían valores de piso superiores (e.g., piso 7), lo cual no es lógico en el contexto inmobiliario.
  • Solución: Se imputó piso = 1 en todas las casas con valores faltantes o incoherentes.
    Para los apartamentos, se conservaron los valores reportados o se imputaron mediante técnicas estadísticas.

1.3 Manejo de valores faltantes

  • Métodos aplicados según el tipo de variable:
    • Predictive Mean Matching para variables numéricas.
    • Modelos de árbol para variables categóricas.
    • Asignaciones lógicas (como el caso de piso).

1.4 Detección de atípicos

  • Se depuraron valores extremos en variables clave (área construida, precio) usando el percentil 99 como umbral.

1.5 Estandarización

  • Se generó una versión estandarizada de los datos numéricos para facilitar el análisis de componentes principales (PCA), asegurando comparabilidad entre variables.

Estas transformaciones aseguran que los análisis posteriores (PCA, clustering, etc.) se basen en datos consistentes, reduciendo el riesgo de conclusiones erróneas debido a artefactos en los datos.

2 Análisis de Componentes Principales (PCA)

# Paquetes para PCA y visualización
# install.packages(c("FactoMineR","factoextra"))
library(FactoMineR)
library(factoextra)
library(dplyr)
library(tibble)
#install.packages("kableExtra")
library(kableExtra)

library(scales)

2.1 Varianza explicada por componente

# Ejecutar PCA (los datos ya están estandarizados)
res.pca <- prcomp(vivienda_std, center = FALSE, scale. = FALSE)

# Varianza explicada (Scree plot)
fviz_eig(res.pca, addlabels = TRUE) +
  ggtitle("Varianza explicada por componente")

El gráfico de varianza explicada (scree plot) muestra el porcentaje de varianza total que explica cada componente principal.

En este caso:

  • El Componente 1 explica el 42.4% de la varianza total.

  • El Componente 2 aporta un 16.8% adicional.

  • Juntos, los dos primeros componentes explican aproximadamente un 59.2% de la varianza acumulada.

  • A partir del Componente 4 en adelante, cada uno explica menos del 10% de la varianza, lo que indica que su aporte a la estructura general de los datos es menor.

Interpretación:
El alto porcentaje de varianza explicada por el primer componente sugiere que existe una dimensión dominante que captura gran parte de la información de las variables originales. La reducción a los dos o tres primeros componentes permitiría simplificar el análisis conservando una proporción considerable de la información, lo que es útil para visualización y detección de patrones sin perder demasiada precisión.

2.2 Varianza explicada y selección de componentes

# Obtener tabla de varianza explicada
eig <- factoextra::get_eig(res.pca) %>%
  mutate(
    variance.percent = round(variance.percent, 2),
    cumulative.variance.percent = round(cumulative.variance.percent, 2),
    eigenvalue = round(eigenvalue, 3)
  )

# Mostrar tabla con formato
eig %>%
  kbl(
    caption = "Varianza explicada por componente",
    col.names = c("Componente", "Valor propio", "% Varianza", "% Varianza acumulada"),
    align = c("l", "c", "c", "c")
  ) %>%
  kable_styling(
    full_width = FALSE,
    bootstrap_options = c("striped", "hover", "condensed")
  )
Varianza explicada por componente
Componente Valor propio % Varianza % Varianza acumulada
Dim.1 3.392 42.40 42.40
Dim.2 1.341 16.76 59.16
Dim.3 1.036 12.96 72.12
Dim.4 0.721 9.02 81.13
Dim.5 0.677 8.46 89.59
Dim.6 0.371 4.63 94.22
Dim.7 0.284 3.55 97.78
Dim.8 0.178 2.22 100.00

La tabla muestra cuánta información (varianza) explica cada componente principal y el porcentaje acumulado al ir sumándolos.

Lectura: - Dim1 explica el 42.4% de toda la información presente en las variables originales.
⇒ Es el eje más importante para separar las propiedades. - Dim2 añade un 16.8% más.
⇒ Junto con Dim1, ya tenemos 59.2% de la información total. - Dim3 aporta un 13.0% adicional, llegando a 72.1% acumulado. - A partir de Dim4, cada componente explica menos del 10% por separado.

Interpretación sencilla:
Si pensamos en la información como una película completa, Dim1 nos cuenta casi la mitad de la historia, Dim2 suma otra parte importante, y Dim3 completa casi tres cuartas partes.
Usar los dos primeros componentes ya permite representar bien la estructura general de los datos y es suficiente para visualización.
Si necesitamos un resumen más completo (≥70% de la información), se pueden considerar tres componentes.

Conclusión práctica:
Para este análisis, retener Dim1 y Dim2 es suficiente para gráficos y exploración visual, y añadir Dim3 sería útil si queremos un modelo con mayor fidelidad a la información original.

2.3 Contribución de las variables a los componentes principales

# Para que los NA se vean en blanco en la tabla
options(knitr.kable.NA = "")

# Matriz de cargas (asegúrate de haber corrido el PCA antes)
loadings <- as.data.frame(res.pca$rotation)

# Preparación y redondeo
tab <- loadings %>%
  rownames_to_column("variable") %>%
  mutate(across(-variable, ~round(.x, 3)))

# Rango de la escala (solo columnas numéricas)
rng <- range(as.matrix(tab[ , -1, drop = FALSE]), na.rm = TRUE)

# Paleta divergente continua con menor saturación (transparencia)
pal <- scales::col_numeric(
  palette = c(alpha("#d73027", 0.35), "white", alpha("#1a9850", 0.35)),  # rojo → blanco → verde
  domain  = rng
)

# Umbrales de estilo
thr_bold <- 0.60  # negrita para |loading| altos
thr_text <- 0.55  # texto blanco para legibilidad en celdas intensas

# Aplicar estilos por celda
tab_col <- tab %>%
  mutate(across(
    -variable,
    ~cell_spec(
      .x,
      background = ifelse(is.na(.x), NA, pal(.x)),
      color      = ifelse(is.na(.x), "black", ifelse(abs(.x) >= thr_text, "white", "black")),
      bold       = ifelse(is.na(.x), FALSE, abs(.x) >= thr_bold)
    )
  ))

# Render de tabla
kable(
  tab_col,
  format  = "html",
  escape  = FALSE,
  caption = "Cargas (loadings) por componente — gradiente continuo rojo/verde",
  align   = c("l", rep("c", ncol(tab_col) - 1))
) %>%
  kable_styling(
    full_width = FALSE,
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  column_spec(1, bold = TRUE) %>%
  footnote(
    general = "Rojo: carga negativa; Verde: carga positiva; Intensidad: magnitud |loading|. En negrita: |loading| ≥ 0.60; texto blanco: |loading| ≥ 0.55.",
    general_title = "Leyenda: ",
    escape = FALSE
  )
Cargas (loadings) por componente — gradiente continuo rojo/verde
variable PC1 PC2 PC3 PC4 PC5 PC6 PC7 PC8
preciom 0.457 -0.218 0.215 0.125 -0.199 -0.38 0.068 0.703
piso -0.124 -0.463 0.625 -0.573 0.116 0.04 -0.172 -0.081
areaconst 0.473 0.156 0.051 0.038 -0.029 -0.424 -0.596 -0.461
habitaciones 0.329 0.463 -0.093 -0.423 0.426 0.358 -0.213 0.368
banios 0.471 0.058 0.047 -0.254 0.117 -0.123 0.74 -0.363
parqueaderos 0.414 -0.146 0.151 0.189 -0.463 0.715 -0.069 -0.132
longitud -0.206 0.547 0.173 -0.379 -0.68 -0.13 0.058 0.05
latitud -0.09 0.419 0.705 0.481 0.275 0.045 0.099 -0.02
Leyenda:
Rojo: carga negativa; Verde: carga positiva; Intensidad: magnitud |loading|. En negrita: |loading| ≥ 0.60; texto blanco: |loading| ≥ 0.55.

La matriz de cargas muestra el grado y la dirección en que cada variable original contribuye a la formación de los componentes principales (PC).

Principales hallazgos:

  • PC1: Presenta fuertes cargas positivas en variables asociadas al tamaño y valor del inmueble:
    areaconst (0.473), baños (0.471), preciom (0.457) y parqueaderos (0.414).
    Esto sugiere que PC1 representa una dimensión general de tamaño–valor.

  • PC2: Destaca por cargas positivas en longitud (0.547) y habitaciones (0.463), junto con una carga negativa en piso (-0.463).
    Puede interpretarse como una dimensión de ubicación geográfica y distribución interna.

  • PC3: Sobresalen latitud (0.705) y piso (0.625) como variables con mayor aporte, lo que apunta a un eje relacionado con la localización y la altura del inmueble.

  • PC4 y posteriores: Capturan variaciones más específicas y de menor peso en la varianza total, como parqueaderos (0.189 en PC4) y baños (0.740 en PC7).

Interpretación general:
Las dos primeras componentes concentran gran parte de la información vinculada al tamaño, valor y localización del inmueble. Los componentes restantes reflejan patrones más específicos, útiles para análisis complementarios, pero con menor influencia en la estructura global de los datos.

2.4 Variables en el plano de los dos primeros componentes

# Variables en el plano PC1–PC2 (color según contribución)
fviz_pca_var(res.pca,
             col.var = "contrib",
             gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
             repel = TRUE) +
  ggtitle("Variables en el plano de los dos primeros componentes")

El biplot de variables muestra cómo se proyectan las variables originales en el espacio definido por los dos primeros componentes principales (Dim1 y Dim2), codificando por color su nivel de contribución.

Principales hallazgos:

  • Dim1 (42.4% de varianza): Asociada principalmente a variables que miden características físicas y de valor del inmueble. Destacan preciom, areaconst, baños, parqueaderos y habitaciones, todas orientadas hacia el lado positivo del eje y con altas contribuciones (tonos anaranjados/rojos).

  • Dim2 (16.8% de varianza): Más vinculada a la ubicación geográfica y altura del inmueble. Sobresalen longitud y latitud (orientadas hacia el cuadrante superior) y piso (orientada hacia el cuadrante inferior), con contribuciones relevantes.

  • La dirección y cercanía entre vectores indica que variables como preciom, areaconst y baños están positivamente correlacionadas entre sí, mientras que piso muestra una correlación negativa con el grupo de variables asociadas al tamaño/valor.

Interpretación general:
El plano PC1–PC2 revela que la estructura principal de los datos está determinada por dos ejes:
1) un eje de tamaño–valor, y
2) un eje de ubicación–altura.
Esto respalda las interpretaciones previas de la matriz de cargas, confirmando que las variables relacionadas con el tamaño y valor explican la mayor parte de la varianza, mientras que las de ubicación aportan un patrón secundario pero significativo.

2.5 Propiedades en el plano PC1–PC2 por estrato

fviz_pca_ind(res.pca,
  habillage = vivienda_final$estrato,
  addEllipses = TRUE, ellipse.level = 0.95,
  repel = FALSE, # no muestra las etiquetas
  geom = "point", # puntos en lugar de texto
  palette = "Dark2") +
  ggtitle("Propiedades en el plano PC1–PC2 por estrato")

El gráfico muestra la proyección de las propiedades en el espacio definido por los dos primeros componentes principales (Dim1 y Dim2), diferenciadas por estrato socioeconómico y con elipses que representan la dispersión y concentración de cada grupo con un 95% de confianza.

Principales hallazgos:

  • Estrato 3 (verde): Se concentra principalmente en valores negativos de Dim1 y presenta una amplia dispersión en Dim2, lo que indica mayor variabilidad en la segunda dimensión.
    De acuerdo con el biplot de variables, esta zona se asocia a valores más bajos en preciom, areaconst y baños.

  • Estrato 4 (naranja): Ubicado cerca del centro de la nube de puntos, con una dispersión moderada en ambos ejes, lo que sugiere características intermedias en tamaño, valor y ubicación.

  • Estrato 5 (morado) y Estrato 6 (rosado): Se distribuyen mayoritariamente hacia el lado positivo de Dim1, lo que está asociado, según el biplot de variables, a mayores valores en preciom, areaconst, baños y parqueaderos.
    Además, presentan elipses más compactas, lo que indica menor variabilidad interna.

Interpretación general:
El plano PC1–PC2 evidencia que el estrato socioeconómico está estrechamente relacionado con las dimensiones detectadas en el PCA. Estratos más altos (5 y 6) se asocian con inmuebles de mayor tamaño, valor y mejor dotación, mientras que los estratos intermedios o bajos muestran mayor dispersión y diversidad de características, especialmente en variables de ubicación y altura.

2.6 Biplot PCA — Variables y Propiedades por estrato

fviz_pca_biplot(res.pca,
  repel = TRUE,
  col.var = "#034A94",
  habillage = vivienda_final$estrato,
  addEllipses = TRUE, ellipse.level = 0.95,
  geom.ind = "point", alpha.ind = 0.5
) +
  ggtitle("Biplot PCA - Variables y Propiedades por estrato")

El biplot superpone las variables (vectores azules) y las observaciones (propiedades) coloreadas por estrato, con elipses de concentración al 95%. Este gráfico permite leer, en un solo plano, qué variables impulsan cada dirección y dónde se ubican los estratos respecto a esos ejes.

Lectura del biplot (reglas rápidas):

  • La longitud de cada vector indica su contribución a Dim1–Dim2.

  • El ángulo entre vectores aproxima la correlación (pequeño = positiva; ~180° = negativa).

  • Proyectar mentalmente cada grupo sobre un vector muestra su asociación con esa variable.

Cómo se conecta con los resultados previos:

  • Del scree plot, Dim1 (≈42.4%) es la dimensión dominante; del análisis de cargas, la definen areaconst, baños, preciom y parqueaderos (eje tamaño–valor).
    En el biplot, estos vectores apuntan al lado positivo de Dim1, confirmando que allí se concentran propiedades de mayor tamaño/valor y dotación.

  • Dim2 (≈16.8%) se asocia a ubicación/altura: latitud, longitud y piso.
    En el biplot, latitud/longitud cargan hacia la parte superior, mientras piso se proyecta hacia el inferior, capturando diferencias de localización y altura.

Patrones por estrato (proyección de grupos sobre los vectores):

  • Estrato 5–6 (morado/rosado): Se sitúan mayormente en la zona positiva de Dim1, proyectándose en la dirección de areaconst, baños, parqueaderos y preciom.
    Asociados a inmuebles más grandes, de mayor valor y con mejor dotación.

  • Estrato 4 (naranja): Ocupa el centro del plano, con proyección moderada hacia el eje tamaño–valor y dispersión en Dim2.
    Perfil intermedio, con mezcla de tamaños/valores y variación en ubicación/altura.

  • Estrato 3 (verde): Se concentra en la zona negativa de Dim1 y muestra mayor dispersión en Dim2, alejándose de los vectores de preciom y areaconst.
    Asociado a inmuebles de menor tamaño/valor, con mayor heterogeneidad en ubicación y altura.

Relaciones entre variables destacadas:

  • areaconst, baños, parqueaderos y preciom forman un bloque correlacionado (vectores cercanos), definiendo el eje tamaño–valor.

  • piso se orienta en sentido opuesto a ese bloque sobre Dim1, sugiriendo un trade-off: mayor altura tiende a no coincidir con el mismo patrón de tamaño–valor en este conjunto.

  • latitud y longitud cargan de forma conjunta sobre Dim2, reforzando la dimensión geográfica.

Conclusión integrada:

El biplot confirma la lectura global del PCA:
1) Dim1 = tamaño–valor/dotación concentra la mayor varianza y separa claramente a los estratos altos (5–6) del 3;
2) Dim2 = ubicación–altura introduce un patrón secundario que explica diversidad dentro de cada estrato.
Esta estructura respalda el uso de los dos primeros componentes para visualizar y segmentar el mercado, mientras que componentes posteriores capturan matices más finos.

# Nota: En este paso se extraen y guardan las coordenadas de cada observación 
# en los primeros k componentes principales (PC1, PC2, PC3) según el PCA.
# Esto permite contar con un conjunto reducido de variables no correlacionadas,
# útil para análisis posteriores (por ejemplo, clustering o modelado predictivo).
# No se presenta este resultado en el informe final, ya que es un paso técnico 
# que no aporta interpretación directa a los resultados.

k <- 3  # ajusta según el 'codo' del scree plot
pcs <- as.data.frame(res.pca$x[, 1:k])
colnames(pcs) <- paste0("PC", 1:k)

# Unir PCs a la base final para análisis posteriores
vivienda_pca <- cbind(vivienda_final, pcs)
head(vivienda_pca)
##     id         zona piso estrato preciom areaconst parqueaderos banios
## 1 1147 Zona Oriente    1       3     250        70            1      3
## 2 1169 Zona Oriente    1       3     320       120            1      2
## 3 1350 Zona Oriente    1       3     350       220            2      2
## 4 5992     Zona Sur    2       4     400       280            3      5
## 5 1212   Zona Norte    1       5     260        90            1      2
## 6 1724   Zona Norte    1       5     240        87            1      3
##   habitaciones        tipo      barrio  longitud latitud        PC1         PC2
## 1            6        Casa 20 de julio -76.51168 3.43382 -0.5089228  1.99428409
## 2            3        Casa 20 de julio -76.51237 3.43369 -1.2165259  0.97575174
## 3            4        Casa 20 de julio -76.51537 3.43566 -0.1008534  1.19034003
## 4            3        Casa  3 de julio -76.54000 3.43500  1.6569662 -0.06012338
## 5            3 Apartamento       acopi -76.51350 3.45891 -1.4712135  1.19394425
## 6            3 Apartamento       acopi -76.51700 3.36971 -0.9452461  0.25633579
##            PC3
## 1 -0.579927303
## 2 -0.354807603
## 3 -0.202881292
## 4  0.146913061
## 5 -0.004843382
## 6 -1.502391656

2.7 Selección de Componentes Principales y Creación de Variables Derivadas

Con el objetivo de reducir la dimensionalidad y sintetizar la información de las variables cuantitativas (precio, piso, área construida, habitaciones, baños, parqueaderos y coordenadas geográficas), se aplicó un Análisis de Componentes Principales (PCA) sobre los datos estandarizados.

Para determinar cuántos componentes conservar, se utilizaron dos criterios:
1. El gráfico de varianza explicada (scree plot), identificando el “codo” de la curva.
2. El criterio de Kaiser (valores propios mayores a 1).

Ambos coincidieron en sugerir la retención de los tres primeros componentes, que concentran más del 70 % de la variabilidad total, permitiendo una representación en 2–3 dimensiones sin pérdida sustancial de información.

El plano PC1–PC2 muestra la relación entre variables:
- Flechas más largas representan mayor contribución al componente.
- Ángulos pequeños entre vectores indican correlación positiva.
- Ángulos cercanos a 180° señalan correlación negativa.

El primer componente (PC1) captura principalmente la variación asociada al tamaño y precio del inmueble (área construida, precio, número de baños y parqueaderos).
El segundo componente (PC2) refleja contrastes relacionados con la localización geográfica (latitud y longitud) y ciertas características internas como el número de habitaciones.

La proyección de las observaciones, coloreadas por estrato, evidencia posibles patrones de agrupamiento que se aprovecharán en la fase de clustering para segmentar el mercado. Finalmente, los tres componentes seleccionados se incorporaron a la base de datos final para su uso en los análisis posteriores.

3 Análisis de Conglomerados

Para el análisis de conglomerados se utilizaron las coordenadas de las propiedades en los tres primeros componentes principales (PC1, PC2, PC3), obtenidas del PCA. Estos componentes ya están centrados y escalados, lo que los hace adecuados para aplicar técnicas de agrupamiento.

# Usaremos los 3 primeros componentes (ya están centrados/escalados por el PCA)
X <- as.matrix(vivienda_pca[, c("PC1", "PC2", "PC3")])
dim(X)
## [1] 8164    3

3.1 Dendrograma y análisis jerárquico de conglomerados

#Distancias y clustering jerárquico
# Distancias euclidianas
D <- dist(X, method = "euclidean")

# Clustering jerárquico (enlace completo)
hc <- hclust(D, method = "complete")

# Dendrograma básico
plot(hc, labels = FALSE, main = "Dendrograma de propiedades", xlab = "", sub = "")
abline(h = 0, col = "white")  # limpieza visual

El dendrograma muestra cómo se agrupan las propiedades de forma jerárquica según sus similitudes en los tres primeros componentes del PCA. El eje vertical (Height) indica la distancia o diferencia entre los grupos; uniones bajas representan propiedades muy similares, mientras que uniones altas corresponden a grupos más distintos.

Este gráfico permite identificar patrones de similitud y seleccionar el número óptimo de clústeres cortando el árbol a una determinada altura, lo que servirá como base para la segmentación en el análisis posterior.

3.2 Determinación del número óptimo de clusters

# Elección de k (número de clusters)
# Método del "codo" / silueta con k-means sobre PCs (rápido y estable)

# Forzar UTF-8 para que las tildes y la ñ no den error
Sys.setlocale("LC_ALL", "Spanish_Spain.UTF-8")
## [1] "LC_COLLATE=Spanish_Spain.utf8;LC_CTYPE=Spanish_Spain.utf8;LC_MONETARY=Spanish_Spain.utf8;LC_NUMERIC=C;LC_TIME=Spanish_Spain.utf8"
library(factoextra)

set.seed(123)
fviz_nbclust(X, kmeans, method = "wss") +
  ggtitle("Número óptimo de clusters (Codo)")

set.seed(123)
fviz_nbclust(X, kmeans, method = "silhouette") +
  ggtitle("Número óptimo de clusters (Silhouette)")

Para identificar el número adecuado de grupos, se aplicaron dos métodos complementarios sobre las coordenadas obtenidas del PCA:

  • Método del codo (WSS): la curva muestra una disminución pronunciada del error intra-cluster hasta k = 2, a partir de donde las mejoras son marginales.
  • Método de la silueta: el valor máximo de la anchura media de silueta también se alcanza en k = 2, indicando que esta partición logra la mayor cohesión interna y separación externa entre grupos.

Conclusión: ambos métodos coinciden en que 2 clusters es la elección más adecuada para representar la estructura de los datos.

3.3 Selección del número de clusters

#Cortar el árbol y validar con Silhouette

k <- 2 # se ajusta segun los anteriores resultados

# Cortar el dendrograma
grupos_hc <- cutree(hc, k = k)

# Silhouette (sobre distancias D)
library(cluster)
sil <- silhouette(grupos_hc, D)

# Resumen y gráfico silhouette
summary(sil)
## Silhouette of 8164 units in 2 clusters from silhouette.default(x = grupos_hc, dist = D) :
##  Cluster sizes and average silhouette widths:
##      7869       295 
## 0.4351962 0.5561662 
## Individual silhouette widths:
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
## -0.6016  0.3947  0.5515  0.4396  0.6133  0.6823
fviz_silhouette(sil) + ggtitle(paste("Silhouette promedio (k =", k, ")"))
##   cluster size ave.sil.width
## 1       1 7869          0.44
## 2       2  295          0.56

Para determinar el número óptimo de grupos se aplicaron dos métodos complementarios: el criterio del “codo” y el índice de Silhouette. El método del codo sugirió un punto de inflexión alrededor de 2–4 clusters, mientras que el análisis de Silhouette mostró que la configuración con k = 2 alcanzó el valor promedio más alto (0.44), indicando una mayor cohesión interna y mejor separación entre grupos. En contraste, la configuración con k = 4 presentó valores de Silhouette más bajos (entre 0.19 y 0.35) y grupos con menor consistencia interna. Por lo tanto, se seleccionó k = 2 como la opción más adecuada para el análisis posterior.

3.4 Visualización de clusters en el plano PC1–PC2

#k-means como alternativa/afinamiento
set.seed(123)
km <- kmeans(X, centers = k, nstart = 25)

# Etiquetas finales (elige km$cluster o grupos_hc)
vivienda_clusters <- vivienda_pca
vivienda_clusters$cluster <- factor(km$cluster)

# Visualización en el plano PC1–PC2
library(ggplot2)
ggplot(vivienda_clusters, aes(PC1, PC2, color = cluster)) +
  geom_point(alpha = 0.6, size = 1.6) +
  stat_ellipse(level = 0.95, linewidth = 0.3) +
  theme_minimal() +
  labs(title = "Clusters en el plano PC1–PC2", x = "PC1", y = "PC2")

La proyección de los datos sobre los dos primeros componentes principales (PC1 y PC2) muestra una separación clara entre los dos clusters identificados mediante k-means.
- El Cluster 1 (rojo) se concentra principalmente en valores negativos y cercanos a cero en PC1, mientras que el Cluster 2 (azul) se ubica mayoritariamente en valores positivos de PC1.
- Ambos clusters presentan cierta superposición en la zona central, lo que indica similitudes en algunos casos, pero mantienen una separación dominante a lo largo del eje PC1.
- Las elipses de confianza al 95% confirman que la variabilidad interna de cada grupo es relativamente homogénea y que la distinción entre ellos es consistente.

En conjunto, el gráfico respalda que la solución con k = 2 refleja dos patrones diferenciados en las características originales de las propiedades.

3.5 Perfilamiento de clusters

#Resumen numérico por cluster (media y mediana)
library(dplyr)

numeric_vars <- c("preciom", "areaconst", "habitaciones",
                  "banios", "parqueaderos", "piso")

perfil_num <- vivienda_clusters |>
  group_by(cluster) |>
  summarise(
    across(all_of(numeric_vars),
           list(media = ~mean(.x, na.rm = TRUE),
                mediana = ~median(.x, na.rm = TRUE)),
           .names = "{.col}_{.fn}")
  )

perfil_num
## # A tibble: 2 × 13
##   cluster preciom_media preciom_mediana areaconst_media areaconst_mediana
##   <fct>           <dbl>           <dbl>           <dbl>             <dbl>
## 1 1                272.             250            105.                90
## 2 2                711.             650            291.               270
## # ℹ 8 more variables: habitaciones_media <dbl>, habitaciones_mediana <dbl>,
## #   banios_media <dbl>, banios_mediana <dbl>, parqueaderos_media <dbl>,
## #   parqueaderos_mediana <dbl>, piso_media <dbl>, piso_mediana <dbl>
#Moda de estrato por cluster
# función de moda segura
moda <- function(x){
  x <- x[!is.na(x)]
  if (length(x) == 0) return(NA)
  ux <- unique(x)
  ux[which.max(tabulate(match(x, ux)))]
}

perfil_estrato_moda <- vivienda_clusters |>
  group_by(cluster) |>
  summarise(estrato_moda = moda(estrato), .groups = "drop")

perfil_estrato_moda
## # A tibble: 2 × 2
##   cluster estrato_moda
##   <fct>   <fct>       
## 1 1       5           
## 2 2       6
# Distribución de estratos (%) por cluster
perfil_estrato_dist <- vivienda_clusters |>
  count(cluster, estrato, name = "n") |>
  group_by(cluster) |>
  mutate(pct = round(100 * n / sum(n), 1)) |>
  arrange(cluster, desc(pct))

perfil_estrato_dist
## # A tibble: 8 × 4
## # Groups:   cluster [2]
##   cluster estrato     n   pct
##   <fct>   <fct>   <int> <dbl>
## 1 1       5        1928  34.9
## 2 1       4        1780  32.2
## 3 1       3        1188  21.5
## 4 1       6         631  11.4
## 5 2       6        1243  47.1
## 6 2       5         792  30  
## 7 2       4         341  12.9
## 8 2       3         261   9.9

3.5.1 Perfilamiento de clusters

A partir de las variables originales, se describieron los dos clusters identificados mediante el análisis de conglomerados, considerando indicadores numéricos, la moda de estrato y la distribución porcentual de estratos.

  • Cluster 1:
    • Precio promedio: $272,2 millones (mediana: $250 millones).
    • Área construida: 105,18 m² en promedio (mediana: 90 m²).
    • Habitaciones: 3,05 en promedio (mediana: 3).
    • Baños: 2,36 en promedio (mediana: 2).
    • Parqueaderos: 1,30 en promedio (mediana: 1).
    • Piso: 3,89 en promedio (mediana: 3).
    • Moda de estrato: 5.
    • Distribución de estratos:
      • Estrato 5: 34,9%
      • Estrato 4: 32,2%
      • Estrato 3: 21,5%
      • Estrato 6: 11,4%
    • Interpretación: Viviendas de tamaño y precio moderado, ubicadas principalmente en estratos medios-altos, pero con cierta diversidad socioeconómica.
  • Cluster 2:
    • Precio promedio: $711,1 millones (mediana: $650 millones).
    • Área construida: 290,62 m² en promedio (mediana: 270 m²).
    • Habitaciones: 4,69 en promedio (mediana: 4).
    • Baños: 4,56 en promedio (mediana: 4).
    • Parqueaderos: 2,56 en promedio (mediana: 2).
    • Piso: 2,82 en promedio (mediana: 2).
    • Moda de estrato: 6.
    • Distribución de estratos:
      • Estrato 6: 47,1%
      • Estrato 5: 30,0%
      • Estrato 4: 12,9%
      • Estrato 3: 9,9%
    • Interpretación: Viviendas amplias y de alto costo, localizadas en su mayoría en estrato 6, con más habitaciones, baños y parqueaderos.

Conclusión general:
El perfilamiento confirma una segmentación clara del mercado:
- El Cluster 1 agrupa propiedades medianas y de precio moderado, con predominio de estratos 4 y 5, pero con presencia de estratos más bajos y altos.
- El Cluster 2 concentra viviendas de lujo, de gran tamaño y alto valor, localizadas mayoritariamente en zonas exclusivas de estrato 6.

3.6 Distribución geográfica de los clusters

#Mapa por zona/barrio con color de cluster
# Si quieres un vistazo espacial rápido:
ggplot(vivienda_clusters, aes(longitud, latitud, color = cluster)) +
  geom_point(alpha = 0.5, size = 1) +
  theme_minimal() +
  labs(title = "Clusters en el plano geográfico", x = "Longitud", y = "Latitud")

3.7 Síntesis espacial y perfiles de clusters

La distribución geográfica refuerza el perfilamiento previo de los grupos:

  • Cluster 1 (rojo): Presencia amplia y dispersa —incluye zonas periféricas y parte del corredor sur–oriente—, coherente con su perfil de viviendas medianas y de precio moderado (mediana: $250 millones; 90 m²) y una composición socioeconómica mixta (predominio de estratos 4–5, con participación de estrato 3).

  • Cluster 2 (turquesa): Mayor concentración en áreas centrales y norte, alineada con su carácter de viviendas de mayor tamaño y valor (mediana: $650 millones; 270 m²) y predominio de estrato 6 (seguido de estrato 5).

Implicación: Los clusters capturan no solo diferencias en tamaño, dotación y precio, sino también patrones de localización. Esto sugiere segmentaciones de mercado asociadas a centralidad/amenidades (Cluster 2) frente a áreas más diversas y extensas (Cluster 1), útiles para estrategias de oferta, valoración y focalización territorial.

# Guardar dataset con etiquetas de cluster

# Para reutilizar en otras secciones del informe
dir.create("data", showWarnings = FALSE)
saveRDS(vivienda_clusters, "data/vivienda_clusters.rds")

4 Análisis de Correspondencia

En esta etapa se estudia la relación entre la zona y el tipo de vivienda.
El objetivo es identificar patrones de asociación entre estas dos variables categóricas, lo que permitirá reconocer si ciertos tipos de vivienda se concentran más en unas zonas que en otras.

# 1) Selección y limpieza (zona–tipo) — ACS
# Nota: Este bloque prepara solo dos variables categóricas (zona y tipo) para un AC simple,
# eliminando NA y placeholders, y depurando niveles vacíos.

library(dplyr)
library(forcats)

vars_ac <- vivienda_clusters %>%
  select(zona, tipo) %>%
  filter(!is.na(zona), !is.na(tipo)) %>%                 # quitar NA
  filter(zona != "Sin zona", tipo != "Desconocido") %>%  # quitar placeholders
  mutate(across(c(zona, tipo), ~ fct_drop(as.factor(.x)))) %>%
  droplevels()

4.1 Tabla de Contingencia

# 2) Tabla de contingencia zona × tipo
tabla_ac <- table(vars_ac$zona, vars_ac$tipo)
tabla_ac
##               
##                Apartamento Casa
##   Zona Centro           24   99
##   Zona Norte          1198  699
##   Zona Oeste          1005  160
##   Zona Oriente          62  286
##   Zona Sur            2783 1847

La tabla de contingencia entre zona y tipo de vivienda permite identificar la distribución conjunta de estas dos variables categóricas.
Se observa, por ejemplo, que la mayor concentración de apartamentos se encuentra en la Zona Sur (2783 casos) y la Zona Norte (1198 casos), mientras que las casas son más comunes en la Zona Sur (1847 casos) y la Zona Norte (699 casos).
Estas diferencias servirán como base para el Análisis de Correspondencia, que representará gráficamente las asociaciones entre zonas y tipos de vivienda.

4.2 prueba Chi-cuadrado de independencia

# 3) Prueba Chi-cuadrado (con simulación para evitar advertencias por celdas pequeñas)
chisq_res <- chisq.test(tabla_ac, simulate.p.value = TRUE, B = 5000)
chisq_res
## 
##  Pearson's Chi-squared test with simulated p-value (based on 5000
##  replicates)
## 
## data:  tabla_ac
## X-squared = 682.75, df = NA, p-value = 2e-04
# (Opcional) imprimir resumen formateado
cat(
  "Chi-cuadrado =", round(unname(chisq_res$statistic), 2),
  "| gl =", chisq_res$parameter,
  "| p-valor (simulado) =", signif(chisq_res$p.value, 3), "\n"
)
## Chi-cuadrado = 682.75 | gl = NA | p-valor (simulado) = 2e-04

La prueba Chi-cuadrado de independencia entre las variables zona y tipo de vivienda muestra un valor de estadístico de 682.75 y un p-valor simulado de 0.0002, lo que indica que existe una asociación estadísticamente significativa entre ambas variables.
Esto justifica la aplicación del Análisis de Correspondencia para explorar y visualizar estas relaciones.

4.3 Análisis de Correspondencia

# 4) Análisis de correspondencia
library(FactoMineR)
library(factoextra)

res_ac <- CA(tabla_ac, graph = FALSE)

# 5) Varianza explicada
res_ac$eig
##       eigenvalue percentage of variance cumulative percentage of variance
## dim 1 0.08364018                    100                               100

En el Análisis de Correspondencia aplicado a la tabla zona × tipo de vivienda, la única dimensión generada explica el 100 % de la variabilidad asociada a la relación entre ambas variables. Esto se debe a que el cruce involucra únicamente dos categorías para la variable tipo de vivienda, lo que limita el análisis a un solo eje.

# 6) Screeplot
fviz_screeplot(res_ac, addlabels = TRUE, ylim = c(0, 80)) +
  ggtitle("Porcentaje de varianza explicada - AC (zona × tipo)") +
  ylab("Porcentaje") + xlab("Dimensiones")

El gráfico de varianza explicada confirma que la única dimensión obtenida concentra el 100 % de la variabilidad, lo cual es esperable dado que la variable tipo de vivienda solo presenta dos categorías.

4.4 Mapa factorial unidimensional

# 7) Visualización robusta (2D si hay ≥2 dims; si no, 1D)
library(ggplot2)

dims_row <- if (is.null(dim(res_ac$row$coord))) 1 else ncol(res_ac$row$coord)
dims_col <- if (is.null(dim(res_ac$col$coord))) 1 else ncol(res_ac$col$coord)
dims_use <- min(dims_row, dims_col)

if (!is.na(dims_use) && dims_use >= 2) {
  # Biplot 2D
  factoextra::fviz_ca_biplot(res_ac, axes = c(1, 2), repel = TRUE) +
    ggtitle("Mapa factorial - Zona vs Tipo de vivienda") +
    theme_minimal()
} else {
  # Construir 1D de forma segura (soporta vector o matriz)
  get_dim1 <- function(obj) {
    if (is.null(obj)) return(list(values = numeric(0), names = character(0)))
    if (is.null(dim(obj))) {
      vals <- as.numeric(obj)
      nms  <- names(obj)
    } else {
      vals <- obj[, 1, drop = TRUE]
      nms  <- rownames(obj)
    }
    list(values = vals, names = nms)
  }

  r1 <- get_dim1(res_ac$row$coord)
  c1 <- get_dim1(res_ac$col$coord)

  df1d <- rbind(
    data.frame(cat = r1$names, dim1 = r1$values, grupo = "Zona"),
    data.frame(cat = c1$names, dim1 = c1$values, grupo = "Tipo")
  )

  ggplot(df1d, aes(x = dim1, y = cat, color = grupo, shape = grupo)) +
    geom_point(size = 3) +
    geom_vline(xintercept = 0, linetype = 2) +
    labs(
      title = "AC 1D: posiciones sobre la Dimensión 1 (zona × tipo)",
      x = paste0("Dim 1 (", round(res_ac$eig[1, 2], 1), "%)"),
      y = NULL
    ) +
    theme_minimal()
}

El mapa factorial unidimensional muestra una clara separación entre las categorías Apartamento y Casa. Los apartamentos se asocian principalmente con las zonas Norte y Oeste, mientras que las casas tienden a concentrarse en las zonas Centro y Oriente. La Zona Sur y la Zona Norte se encuentran más próximas al origen, lo que indica una distribución más equilibrada entre ambos tipos de vivienda.

5 Conclusiones

El análisis integral realizado permitió cumplir el objetivo de comprender de forma multidimensional el mercado inmobiliario urbano, aplicando técnicas complementarias que aportaron valor en distintos niveles:

  • Análisis de Componentes Principales (PCA): redujo la complejidad de las variables cuantitativas, identificando que el tamaño y precio de los inmuebles son los factores que más explican la variabilidad del mercado.
  • Análisis de Conglomerados: segmentó la oferta en dos grupos principales con diferencias claras en precio, tamaño y ubicación, evidenciando patrones asociados al estrato socioeconómico y la localización geográfica.
  • Análisis de Correspondencia: confirmó la relación significativa entre zona y tipo de vivienda, mostrando una mayor concentración de apartamentos en zonas norte y oeste, y de casas en zonas centro y oriente.

En conjunto, los hallazgos proporcionan insumos clave para diseñar estrategias comerciales y de inversión orientadas a cada segmento identificado, optimizar la oferta según las características de cada zona y mejorar el posicionamiento de la empresa en un mercado competitivo y dinámico.

6 Recomendaciones estratégicas

6.1 Optimizar la oferta según segmento identificado:

  • Para el Cluster 1 (propiedades de menor precio y tamaño, predominantes en estratos 3–5 y ubicadas en zonas centro, oriente y parte de sur), enfocar campañas dirigidas a compradores de vivienda familiar o inversión en arriendo.
  • Para el Cluster 2 (propiedades de alto valor y mayor tamaño, concentradas en estratos 5–6 y en zonas norte y oeste), potenciar estrategias de marketing premium y alianzas con desarrolladores de alto nivel.

6.2 Ajustar la estrategia de precios y portafolio:

  • Mantener precios competitivos en zonas con alta competencia interna (norte y sur).
  • Evaluar oportunidades de valorización y desarrollo en zonas en transición con crecimiento de demanda (oeste y parte del centro).

6.3 Segmentar la comunicación y canales de venta:

  • Usar mensajes diferenciados por tipo de vivienda y ubicación, destacando atributos clave según el perfil del comprador objetivo.
  • Enfocar la publicidad digital y presencial en los barrios más representativos de cada segmento.

6.4 Aprovechar la relación zona–tipo identificada:

  • Priorizar apartamentos en campañas para zonas norte y oeste, y casas para zonas oriente y centro, alineando la oferta a la demanda predominante en cada área.

6.5 Monitoreo y actualización continua:

  • Implementar un sistema de seguimiento trimestral del mercado para detectar cambios en patrones de oferta y demanda.
  • Incorporar datos adicionales como tiempo en mercado y velocidad de ventas para afinar las estrategias.

Estas acciones permitirán orientar la inversión hacia los segmentos con mayor potencial, diferenciar la oferta según las preferencias de cada zona y aumentar la competitividad de la empresa en un entorno dinámico.

7 Anexos

7.1 Anexo A: Limpieza y preprocesamiento de datos

Este anexo documenta el flujo de EDA y preprocesamiento aplicado antes del análisis (PCA, clustering y AC). Se abordan: - Conversión de tipos, eliminación de filas vacías y duplicados
- Reglas lógicas (ej.: piso = 1 para casas con piso faltante)
- Imputación (numéricas con pmm, categóricas con cart)
- Control de atípicos (percentil 99)
- Estandarización de variables numéricas para PCA
- Guardado de objetos limpios y estandarizados (data/vivienda_final.rds, data/vivienda_std.rds)

Nota: este anexo incluye el código más relevante del proceso de limpieza y preprocesamiento de datos, con el fin de garantizar la reproducibilidad de los resultados. No se incluyen gráficos exploratorios ni salidas intermedias menores, sino únicamente los pasos esenciales que afectan directamente la conformación de la base final (conversión de tipos, imputaciones, eliminación de duplicados, control de atípicos y estandarización). En caso de ejecutarlo, verifique rutas, paquetes y considere fijar una semilla para garantizar la replicabilidad.

library(mice)    # Imputación múltiple
library(tidyr)   # drop_na
library(dplyr)   # Pipes y manipulación
# library(VIM)   # (opcional) visualización de NA

# 1. Conversión de tipos
vivienda$piso    <- as.numeric(vivienda$piso)    # Piso numérico
vivienda$estrato <- as.factor(vivienda$estrato)  # Estrato categórico

# 2. Eliminación de filas totalmente vacías
vivienda_clean <- vivienda[rowSums(!is.na(vivienda) & vivienda != "") > 0, ]

# 3. Detección y eliminación de duplicados
dup_vector <- duplicated(vivienda_clean$id)
vivienda_clean <- vivienda_clean[!dup_vector, ]

# 4. Tratamiento especial a 'piso' según tipo de vivienda
vivienda_clean$piso <- ifelse(
  is.na(vivienda_clean$piso) & vivienda_clean$tipo == "Casa",
  1,
  vivienda_clean$piso
)

# 5. Imputación de NA en variables categóricas
vivienda_clean$tipo <- factor(
  replace(vivienda_clean$tipo, is.na(vivienda_clean$tipo), "Desconocido")
)
vivienda_clean$zona <- factor(
  replace(vivienda_clean$zona, is.na(vivienda_clean$zona), "Sin zona")
)
vivienda_clean <- droplevels(vivienda_clean)

# 6. Imputación múltiple con mice (numéricas pmm, categórica cart)
cols_na <- names(vivienda_clean)[colSums(is.na(vivienda_clean)) > 0]

metodos <- make.method(vivienda_clean)
metodos[cols_na] <- "pmm"
metodos["parqueaderos"] <- "cart"  # usar CART para imputar variable discreta

set.seed(123)
imp <- mice(vivienda_clean, m = 5, method = metodos, seed = 123)
vivienda_final <- complete(imp, 1)

# 7. Control de outliers por percentil 99
p99_area <- quantile(vivienda_final$areaconst, 0.99, na.rm = TRUE)
p99_prec <- quantile(vivienda_final$preciom,   0.99, na.rm = TRUE)

vivienda_final <- vivienda_final |>
  filter(areaconst <= p99_area,
         preciom   <= p99_prec)

# 8. Imputar último NA en 'barrio'
vivienda_final$barrio <- factor(
  replace(vivienda_final$barrio, is.na(vivienda_final$barrio), "Desconocido")
)
vivienda_final <- droplevels(vivienda_final)

# 9. Verificación final de NA
stopifnot(all(colSums(is.na(vivienda_final)) == 0))

# 10. Estandarización para Análisis de Componentes Principales (PCA)
vars_pca <- c("preciom", "piso", "areaconst",
              "habitaciones", "banios",
              "parqueaderos", "longitud", "latitud")

vivienda_std <- scale(vivienda_final[, vars_pca])

# --- Listo para PCA ---
head(vivienda_std)

# Crear carpeta si no existe
if (!dir.exists("data")) dir.create("data")

# Guardar datos procesados
saveRDS(vivienda_std, "data/vivienda_std.rds")
saveRDS(vivienda_final, "data/vivienda_final.rds")