Problema

Una empresa inmobiliaria líder en una gran ciudad está buscando comprender en profundidad el mercado de viviendas urbanas para tomar decisiones estratégicas más informadas. La empresa posee una base de datos extensa que contiene información detallada sobre diversas propiedades residenciales disponibles en el mercado. Se requiere realizar un análisis holístico de estos datos para identificar patrones, relaciones y segmentaciones relevantes que permitan mejorar la toma de decisiones en cuanto a la compra, venta y valoración de propiedades.

En la unidad 1 se va a realizar un análisis integral y multidimensional de la base de datos para obtener una comprensión del mercado inmobiliario urbano. Se requiere aplicar diversas técnicas de análisis de datos incluyendo Análisis de componentes principales, Análisis de conglomerados, Análisis de correspondencia y visualización de resultados.


Paso 1: Análisis Exploratorio (EDA) y tratamiento de la base de datos

Se carga la base de datos con la columna barrios con sus categorías corregidas

library(readxl)
library(dplyr)      # <- Esta línea es necesaria
vivienda <- read_excel("vivienda.xlsx")
glimpse(vivienda)
Rows: 8,322
Columns: 13
$ id           <dbl> 1147, 1169, 1350, 5992, 1212, 1724, 2326, 4386, 1209, 159…
$ zona         <chr> "Zona Oriente", "Zona Oriente", "Zona Oriente", "Zona Sur…
$ piso         <chr> NA, NA, NA, "02", "01", "01", "01", "01", "02", "02", "02…
$ estrato      <dbl> 3, 3, 3, 4, 5, 5, 4, 5, 5, 5, 6, 4, 5, 6, 4, 5, 5, 4, 5, …
$ preciom      <dbl> 250, 320, 350, 400, 260, 240, 220, 310, 320, 780, 750, 62…
$ areaconst    <dbl> 70, 120, 220, 280, 90, 87, 52, 137, 150, 380, 445, 355, 2…
$ parqueaderos <dbl> 1, 1, 2, 3, 1, 1, 2, 2, 2, 2, NA, 3, 2, 2, 1, 4, 2, 2, 2,…
$ banios       <dbl> 3, 2, 2, 5, 2, 3, 2, 3, 4, 3, 7, 5, 6, 2, 4, 4, 4, 3, 2, …
$ habitaciones <dbl> 6, 3, 4, 3, 3, 3, 3, 4, 6, 3, 6, 5, 6, 2, 5, 5, 4, 3, 3, …
$ tipo         <chr> "Casa", "Casa", "Casa", "Casa", "Apartamento", "Apartamen…
$ barrio       <chr> "20 de julio", "20 de julio", "20 de julio", "3 de julio"…
$ longitud     <dbl> -76.51168, -76.51237, -76.51537, -76.54000, -76.51350, -7…
$ latitud      <dbl> 3.43382, 3.43369, 3.43566, 3.43500, 3.45891, 3.36971, 3.4…

En este conjunto de datos se identifican como variables categóricas los atributos zona, piso, tipo y barrio, ya que representan categorías o etiquetas. Por otro lado, los atributos id, estrato, precio, área construida, número de parqueaderos, habitaciones, longitud y latitud son de tipo numérico. Sin embargo, es importante destacar que algunas variables que R reconoce como numéricas pueden tener una naturaleza nominal u ordinal, por lo que se debe revisar su significado antes de incluirlas en los análisis estadísticos. Se puede evidenciar que es una base de datos de 8.322 filas y 13 columnas, con valores faltantes.

a. Como piso está en formato de texto (), la convertimos a numérica, teniendo en cuenta los valores faltantes (NA) y posibles caracteres no numéricos:

# Convertir 'piso' a numérico (R interpretará caracteres no convertibles como NA)
vivienda$piso <- as.numeric(vivienda$piso)

b. Convertir la variable estrato a categórica (factor), aunque estrato es numérico (), representa una escala ordinal, se convierte a factor ordenado:

# Convertir 'estrato' a factor ordenado (categórico)
vivienda$estrato <- factor(vivienda$estrato, 
                           levels = sort(unique(na.omit(vivienda$estrato))), 
                           ordered = TRUE)


🔎 Verificamos los cambios:

str(vivienda$piso)
 num [1:8322] NA NA NA 2 1 1 1 1 2 2 ...
str(vivienda$estrato)
 Ord.factor w/ 4 levels "3"<"4"<"5"<"6": 1 1 1 2 3 3 2 3 3 3 ...


Ahora, guardamos la base de datos modificada

# Guardar la base modificada con otro nombre
vivienda_1 <- vivienda
head(vivienda_1, 20)
# A tibble: 20 × 13
      id zona    piso estrato preciom areaconst parqueaderos banios habitaciones
   <dbl> <chr>  <dbl> <ord>     <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
 1  1147 Zona …    NA 3           250        70            1      3            6
 2  1169 Zona …    NA 3           320       120            1      2            3
 3  1350 Zona …    NA 3           350       220            2      2            4
 4  5992 Zona …     2 4           400       280            3      5            3
 5  1212 Zona …     1 5           260        90            1      2            3
 6  1724 Zona …     1 5           240        87            1      3            3
 7  2326 Zona …     1 4           220        52            2      2            3
 8  4386 Zona …     1 5           310       137            2      3            4
 9  1209 Zona …     2 5           320       150            2      4            6
10  1592 Zona …     2 5           780       380            2      3            3
11  4057 Zona …     2 6           750       445           NA      7            6
12  4460 Zona …     2 4           625       355            3      5            5
13  6081 Zona …     2 5           750       237            2      6            6
14  7497 Zona …     2 6           520        98            2      2            2
15  7824 Zona …     2 4           600       160            1      4            5
16  7987 Zona …     2 5           420       200            4      4            5
17  3495 Zona …     3 5           490       118            2      4            4
18  5424 Zona …     3 4           320       108            2      3            3
19  6271 Zona …     3 5           385       103            2      2            3
20  6857 Zona …     3 3           100        49           NA      1            2
# ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>

Categorías únicas de la variable barrio

head(unique(vivienda_1$barrio), 5)
[1] "20 de julio" "3 de julio"  "acopi"       "agua blanca" "aguacatal"  


c. Identificación de los valores faltantes

vivienda_2 <- vivienda_1
colSums(is.na(vivienda_2))
          id         zona         piso      estrato      preciom    areaconst 
           3            3         2638            3            2            3 
parqueaderos       banios habitaciones         tipo       barrio     longitud 
        1605            3            3            3            3            3 
     latitud 
           3 

Según los resultados, se puede evidenciar que hay valores faltantes en cada atributo, se muestra que las variables con más datos faltantes son piso 2638 y parqueaderos 1605, lo que podría afectar el análisis si no se trata. Otras variables tienen solo 2 y 3 valores faltantes.


Para el tratamiento de los datos faltantes se recomienda eliminar primero los datos faltantes de la columna id, ya que son pocos y no afectan significativamente el análisis, además de que esta variable funciona como identificador único, por lo que no debe contener valores nulos.

d. Tratamiento de los N/A

library(dplyr)
# Eliminar solo las filas donde la columna 'id' tiene NA
vivienda_3 <- vivienda_2 %>% filter(!is.na(id))
nrow(vivienda_3)
[1] 8319

Como ya eliminamos las tres filas de datos faltantes de la columna id, procedemos aplicar el método de imputación para las otras variables que contienen datos faltantes. Por tal motivo se calculan las estadísticas descriptivas para determinar que método de imputación se aplica para cada variable.

# 1. Eliminar la columna 'id'
vivienda_1_sin_id <- vivienda_3 %>% select(-id)

# 2. Identificar variables cuantitativas (numéricas)
vars_cuantitativas <- vivienda_1_sin_id %>% select(where(is.numeric))

# 3. Identificar variables cualitativas (factor u ordinal o carácter)
vars_cualitativas <- vivienda_1_sin_id %>% select(where(~ is.factor(.) || is.character(.)))

# 4. Estadísticas descriptivas para cuantitativas
summary(vars_cuantitativas)
      piso           preciom         areaconst       parqueaderos   
 Min.   : 1.000   Min.   :  58.0   Min.   :  30.0   Min.   : 1.000  
 1st Qu.: 2.000   1st Qu.: 220.0   1st Qu.:  80.0   1st Qu.: 1.000  
 Median : 3.000   Median : 330.0   Median : 123.0   Median : 2.000  
 Mean   : 3.771   Mean   : 433.9   Mean   : 174.9   Mean   : 1.835  
 3rd Qu.: 5.000   3rd Qu.: 540.0   3rd Qu.: 229.0   3rd Qu.: 2.000  
 Max.   :12.000   Max.   :1999.0   Max.   :1745.0   Max.   :10.000  
 NA's   :2635                                       NA's   :1602    
     banios        habitaciones       longitud         latitud     
 Min.   : 0.000   Min.   : 0.000   Min.   :-76.59   Min.   :3.333  
 1st Qu.: 2.000   1st Qu.: 3.000   1st Qu.:-76.54   1st Qu.:3.381  
 Median : 3.000   Median : 3.000   Median :-76.53   Median :3.416  
 Mean   : 3.111   Mean   : 3.605   Mean   :-76.53   Mean   :3.418  
 3rd Qu.: 4.000   3rd Qu.: 4.000   3rd Qu.:-76.52   3rd Qu.:3.452  
 Max.   :10.000   Max.   :10.000   Max.   :-76.46   Max.   :3.498  
                                                                   
# 5. Estadísticas descriptivas para cualitativas
summary(vars_cualitativas)
     zona           estrato      tipo              barrio         
 Length:8319        3:1453   Length:8319        Length:8319       
 Class :character   4:2129   Class :character   Class :character  
 Mode  :character   5:2750   Mode  :character   Mode  :character  
                    6:1987                                        

Una vez obtenidas las estadísticas descriptivas, se procede a aplicar un método de imputación, reemplazando los valores faltantes en las variables categóricas con la moda, y en las variables numéricas con la mediana, ya que esta última es menos sensible a la influencia de valores atípicos.

# 1. Separar la columna id
id_col <- vivienda_3 %>% select(id)

# 2. Separar variables cuantitativas y cualitativas (excluyendo id)
vars_cuantitativas <- vivienda_3 %>% select(where(is.numeric), -id)
vars_cualitativas <- vivienda_3 %>% select(where(~ is.factor(.) || is.character(.)))

# 3. Función para imputar con mediana
imputar_mediana <- function(x) {
  x[is.na(x)] <- median(x, na.rm = TRUE)
  return(x)
}

# 4. Función para imputar con moda
moda <- function(x) {
  ux <- na.omit(unique(x))
  ux[which.max(tabulate(match(x, ux)))]
}
imputar_moda <- function(x) {
  x[is.na(x)] <- moda(x)
  return(x)
}

# 5. Imputar
vars_cuantitativas_imputadas <- vars_cuantitativas %>% mutate(across(everything(), imputar_mediana))
vars_cualitativas_imputadas <- vars_cualitativas %>% mutate(across(everything(), imputar_moda))

# 6. Volver a unir todo (id + categóricas + numéricas)
vivienda_4 <- bind_cols(id_col, vars_cualitativas_imputadas, vars_cuantitativas_imputadas)

# Ver estructura final
glimpse(vivienda_4)
Rows: 8,319
Columns: 13
$ id           <dbl> 1147, 1169, 1350, 5992, 1212, 1724, 2326, 4386, 1209, 159…
$ zona         <chr> "Zona Oriente", "Zona Oriente", "Zona Oriente", "Zona Sur…
$ estrato      <ord> 3, 3, 3, 4, 5, 5, 4, 5, 5, 5, 6, 4, 5, 6, 4, 5, 5, 4, 5, …
$ tipo         <chr> "Casa", "Casa", "Casa", "Casa", "Apartamento", "Apartamen…
$ barrio       <chr> "20 de julio", "20 de julio", "20 de julio", "3 de julio"…
$ piso         <dbl> 3, 3, 3, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, …
$ preciom      <dbl> 250, 320, 350, 400, 260, 240, 220, 310, 320, 780, 750, 62…
$ areaconst    <dbl> 70, 120, 220, 280, 90, 87, 52, 137, 150, 380, 445, 355, 2…
$ parqueaderos <dbl> 1, 1, 2, 3, 1, 1, 2, 2, 2, 2, 2, 3, 2, 2, 1, 4, 2, 2, 2, …
$ banios       <dbl> 3, 2, 2, 5, 2, 3, 2, 3, 4, 3, 7, 5, 6, 2, 4, 4, 4, 3, 2, …
$ habitaciones <dbl> 6, 3, 4, 3, 3, 3, 3, 4, 6, 3, 6, 5, 6, 2, 5, 5, 4, 3, 3, …
$ longitud     <dbl> -76.51168, -76.51237, -76.51537, -76.54000, -76.51350, -7…
$ latitud      <dbl> 3.43382, 3.43369, 3.43566, 3.43500, 3.45891, 3.36971, 3.4…
colSums(is.na(vivienda_4))
          id         zona      estrato         tipo       barrio         piso 
           0            0            0            0            0            0 
     preciom    areaconst parqueaderos       banios habitaciones     longitud 
           0            0            0            0            0            0 
     latitud 
           0 


Verificamos que efectivamente se le dió tratamiento a los valores faltantes.

e. Visualización de valores atípicos

library(dplyr)
library(ggplot2)
library(tidyr)

# 1. Seleccionar solo las variables numéricas, excluyendo 'id'
vivienda_cuantitativas <- vivienda_4 %>% 
  select(where(is.numeric)) %>% 
  select(-id)

# 2. Convertir a formato largo para ggplot2
vivienda_long <- vivienda_cuantitativas %>% 
  pivot_longer(cols = everything(), names_to = "Variable", values_to = "Valor")

# 3. Crear el boxplot con colores diferentes por variable
ggplot(vivienda_long, aes(x = Variable, y = Valor, fill = Variable)) +
  geom_boxplot() +
  theme_minimal() +
  labs(title = "Boxplot de Variables Cuantitativas",
       x = "Variable",
       y = "Valor") +
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1),
    legend.position = "none",
    plot.title = element_text(hjust = 0.5)  # Centrar título
  )


La imagen muestra un boxplot comparativo de varias variables cuantitativas de la base de datos de viviendas. Se observa que las variables “areaconst” y “preciom” presentan una mayor dispersión y presencia de valores atípicos, lo que se evidencia en la longitud de sus bigotes y la cantidad de puntos fuera de los límites del boxplot. Estas variables, además, tienen medianas elevadas en relación con el resto, indicando una mayor variabilidad en los datos de área construida y precio. Por otro lado, las variables como “baños”, “habitaciones”, “parqueaderos”, “piso”, “latitud” y “longitud” muestran una variabilidad mucho menor, con cajas más compactas y pocos valores extremos, lo que sugiere que sus datos están más concentrados en torno a la mediana. En conjunto, el gráfico permite identificar las variables con mayor heterogeneidad y la presencia de posibles outliers que podrían requerir un tratamiento especial en el análisis posterior.


f. Tratamiento de los valores atípicos


Para dar tratamiento a los valores atípicos, se utiliza el método de capping o Winsorización, que consiste en limitar los valores extremos de las variables cuantitativas a determinados percentiles (en este caso, los percentiles 5 y 95). De esta manera, se atenúa el efecto de los valores atípicos sin eliminarlos completamente, reemplazándolos por los valores límites definidos, lo cual permite conservar la estructura general de los datos y minimizar el sesgo que podrían generar en los análisis estadísticos.

# Cargar el paquete si vas a usar Winsorize
# install.packages("DescTools")
library(DescTools)

# Crear una copia del data frame para no modificar el original
vivienda_5 <- vivienda_4

# Aplicar capping del 5% y 95% a cada variable numérica
numeric_vars <- sapply(vivienda_5, is.numeric)

vivienda_5[numeric_vars] <- lapply(vivienda_5[numeric_vars], function(x) {
  p5 <- quantile(x, 0.10, na.rm = TRUE)
  p95 <- quantile(x, 0.90, na.rm = TRUE)
  x[x < p5] <- p5
  x[x > p95] <- p95
  return(x)
})


Después de aplicar el método de capping, se procede a graficar los boxplots con el fin de verificar que los valores atípicos han sido controlados de manera adecuada.


library(tidyr)
library(ggplot2)

# Eliminar 'id' si es numérica y está en el conjunto
vars_sin_id <- names(vivienda_5)[sapply(vivienda_5, is.numeric) & names(vivienda_5) != "id"]

# Formato largo solo con variables numéricas sin 'id'
vivienda_long <- pivot_longer(
  vivienda_5, 
  cols = all_of(vars_sin_id),
  names_to = "variable", 
  values_to = "valor"
)

# Crear boxplot con colores y título centrado
ggplot(vivienda_long, aes(x = variable, y = valor, fill = variable)) +
  geom_boxplot() +
  theme_minimal() +
  labs(
    title = enc2utf8("Boxplots de variables numéricas con capping"),
    x = "Variable",
    y = "Valor"
  ) +
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1),
    legend.position = "none",
    plot.title = element_text(hjust = 0.5, face = "bold", size = 14) # centrado y en negrita
  )


Se visualiza en el gráfico que efectivamente, ya no aparecen valores atípicos en las varibles del área construida y el precio.

g. Ver todas las filas que comparten un id repetido

Ahora vamos a comprobar si hay filas duplicadas, teniendo en cuenta el identificador único:

vivienda_5[vivienda_5$id %in% vivienda_5$id[duplicated(vivienda_5$id)], ]
# A tibble: 1,664 × 13
      id zona   estrato tipo  barrio  piso preciom areaconst parqueaderos banios
   <dbl> <chr>  <ord>   <chr> <chr>  <dbl>   <dbl>     <dbl>        <dbl>  <dbl>
 1 7487. Zona … 6       Apar… acopi      2     520        98            2      2
 2 7487. Zona … 4       Casa  acopi      2     600       160            1      4
 3 7487. Zona … 5       Casa  acopi      2     420       200            3      4
 4  833. Zona … 6       Apar… acopi      5     820       350            1      4
 5  833. Zona … 3       Casa  acopi      3     230       160            2      2
 6  833. Zona … 5       Apar… acopi      3     430       105            2      3
 7  833. Zona … 3       Casa  acopi      3     190       350            2      2
 8  833. Zona … 3       Casa  acopi      3     180       120            2      3
 9  833. Zona … 3       Casa  acopi      3     500       210            2      5
10  833. Zona … 3       Apar… acopi      3     199       176            2      2
# ℹ 1,654 more rows
# ℹ 3 more variables: habitaciones <dbl>, longitud <dbl>, latitud <dbl>


Hay valores duplicados, esto puede ser que el id este asignado por piso, ya que las filas poseen diferentes caraterísticas. Por lo tanto, no se realiza ninguna modificación a estos valores.



Paso 2: Análisis de Componentes Principales


Reducir la dimensionalidad del conjunto de datos y visualizar la estructura de las variables en componentes principales para identificar características clave que influyen en la variación de precios y oferta del mercado.


a. Estandarización:

Con el fin de evitar que las variables que tienen una escala con valores más grandes afecten las estimaciones realizadas (sesgos) se realiza la estandarización de las variables antes de proceder a realizar el proceso de estimación de los componentes principales. Se utilizó el paquete FactoMineR::PCA dónde la estandarización ya va incluida por defecto.

library(FactoMineR)
library(factoextra)

# PCA solo con variables numéricas (sin 'id')
vars_sin_id <- names(vivienda_5)[sapply(vivienda_5, is.numeric) & names(vivienda_5) != "id"]
pca_res <- PCA(vivienda_5[vars_sin_id], graph = FALSE)

b. Elección del número de componentes principales

# --- 1. Scree plot ---
fviz_eig(pca_res, 
               addlabels = TRUE,
               barfill = "#56B4E9",
               barcolor = "#1F78B4",
               linecolor = "#E69F00") +
  labs(title = "Varianza explicada por componente",
       x = "Componentes principales",
       y = "Porcentaje de varianza explicada") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5))  # Centrar título

El gráfico muestra la varianza explicada por cada componente principal en un análisis de componentes principales (PCA), evidenciando que el primer componente explica el 44% de la variabilidad total de los datos, seguido por el segundo con un 15.5%, y el tercero con un 12.7%; a partir del cuarto componente (9.7%) la contribución disminuye progresivamente, llegando a valores menores al 5% desde el sexto en adelante, lo que sugiere que la mayor parte de la información se concentra en los tres primeros componentes (72.2% acumulado) y que los restantes aportan variabilidad marginal, por lo que podrían descartarse para reducir la dimensionalidad sin pérdida significativa de información.


# --- 2. Biplot profesional ---
fviz_pca_var(pca_res,
                   col.var = "contrib",
                   gradient.cols = c("#E69F00", "#56B4E9", "#009E73"),
                   repel = TRUE,
                   arrowsize = 0.5,
                   labelsize = 4,
                   title = "Biplot - Variables") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5))  # Centrar título

Al visualizar las variables en el plano de los componentes principales permite identificar el sentido y la caracterización de los componentes (característica capturada por los vectores propios de Σ). El biplot de variables muestra la proyección de las variables originales en el plano formado por los dos primeros componentes principales, que explican conjuntamente el 59.5% de la varianza 44% en Dim1 y 15.5% en Dim2. En este plano, variables como precio, parqueaderos, baños, areaconst y habitaciones se alinean fuertemente con Dim1, indicando que este componente está asociado principalmente a características físicas y de valor de las propiedades; en cambio, longitud y latitud se relacionan con Dim2, reflejando una dimensión geográfica. La variable piso tiene una contribución moderada y se proyecta en sentido opuesto a las características de tamaño y precio, sugiriendo una relación inversa. La intensidad y longitud de las flechas representan la importancia de cada variable en la construcción de los componentes, destacándose precio, areaconst y longitud como las de mayor contribución.


Conclusión

El análisis de componentes principales permitió reducir la dimensionalidad del conjunto de datos de 8 variables originales a 3 componentes principales que retienen el 72.2% de la variabilidad total, sin pérdida significativa de información. El primer componente está asociado a variables de tamaño y valor de las propiedades precio, área construida, número de baños, parqueaderos y habitaciones, mientras que el segundo se relaciona con factores geográficos (longitud y latitud) y el tercero con características de localización y piso. Esta reducción facilita la identificación de patrones clave que influyen en la variación de precios y oferta en el mercado, simplificando el análisis y mejorando la interpretación de la información.


Paso 3: Análisis de Conglomerados:

Este bloque de código selecciona únicamente las variables numéricas de la base vivienda_4 y las estandariza, es decir, las transforma para que todas tengan media cero y desviación estándar uno. Esto asegura que cada variable tenga el mismo peso en el análisis de conglomerados, evitando que aquellas con valores más grandes influyan de forma desproporcionada en la formación de los grupos.

# Escalar todas las columnas numéricas (ya limpias)
# Seleccionar solo variables numéricas
vivienda_num <- dplyr::select_if(vivienda_5, is.numeric)
# Escalar
vivienda_scaled <- scale(vivienda_num)
library(factoextra)
# Método del codo con título centrado
fviz_nbclust(vivienda_scaled, kmeans, method = "wss") +
  ggtitle("Método del Codo") +
  theme(plot.title = element_text(hjust = 0.5))

La gráfica del método del codo muestra una fuerte disminución de la varianza interna hasta k = 2, punto a partir del cual la reducción es más suave, indicando que el número óptimo de conglomerados para este análisis es 2.

fviz_nbclust(vivienda_scaled, kmeans, method = "silhouette") +
  ggtitle("Optimal Number of Clusters - Silhouette Method") +
  theme(plot.title = element_text(hjust = 0.5))

La gráfica del método de la silueta muestra que el valor más alto del ancho promedio de silueta se alcanza en k = 2, lo que indica que dos conglomerados producen la mejor separación y cohesión interna entre grupos en estos datos.

set.seed(123)
km_res <- kmeans(vivienda_scaled, centers = 2, nstart = 25)
# Ver distribución de observaciones
table(km_res$cluster)

   1    2 
4977 3342 

El resultado indica que el algoritmo k-means agrupó los datos en dos clusters, con 4.977 observaciones en el primero y 3.342 en el segundo, mostrando que el primer grupo es más numeroso.

vivienda_5$cluster <- km_res$cluster

fviz_cluster(km_res, data = vivienda_scaled,
             geom = "point",
             ellipse.type = "convex",
             palette = "jco")

El gráfico de dispersión muestra la separación de las observaciones en dos clusters bien definidos: el cluster 1 (en azul) se concentra principalmente en valores negativos de la primera dimensión, mientras que el cluster 2 (en amarillo) ocupa valores positivos. La frontera entre ambos grupos está claramente delimitada, lo que indica una buena diferenciación según las dos primeras dimensiones principales, que explican en conjunto el 61,8 % de la variabilidad de los datos.


aggregate(vivienda_5[ , sapply(vivienda_5, is.numeric)], 
          by = list(Cluster = km_res$cluster), 
          mean)
  Cluster       id     piso  preciom areaconst parqueaderos   banios
1       1 3387.364 3.531846 254.2449  99.11312     1.444043 2.373518
2       2 5310.632 3.027229 619.6864 251.44524     2.209156 4.152902
  habitaciones  longitud  latitud cluster
1     3.004018 -76.52449 3.419481       1
2     4.059844 -76.53635 3.415958       2

Este resultado muestra los valores promedio de las variables para cada uno de los dos clusters formados. El Cluster 1 agrupa propiedades con menor precio promedio aproximadamente 254 millones, menor área construida aproximadamente 99 m², menos baños y parqueaderos, así como menor número de habitaciones, mientras que el Cluster 2 agrupa propiedades con precios más altos de apróximadamente 620 millones, áreas construidas más amplias apróximadamente 251 m² y más comodidades (baños, parqueaderos y habitaciones). En resumen, los clusters diferencian claramente viviendas más pequeñas y económicas frente a viviendas más grandes y costosas.

library(ggplot2)
# Ejemplo: distribución de zonas por cluster
ggplot(vivienda_5, aes(x = zona, fill = factor(cluster))) +
  geom_bar(position = "dodge") +
  labs(fill = "Cluster", x = "Zona", y = "Cantidad de propiedades")

La gráfica muestra la distribución de propiedades por zona y cluster. El Cluster 1 (rojo) predomina claramente en la Zona Sur y Zona Norte, mientras que el Cluster 2 (azul) tiene mayor presencia relativa en la Zona Oeste y también aparece en la Zona Sur, aunque en menor cantidad que el Cluster 1. En la Zona Centro y Zona Oriente, ambos clusters tienen baja representación. Esto indica que las zonas geográficas tienen una fuerte influencia en la pertenencia al cluster, con el Cluster 1 concentrado en zonas con mayor número de propiedades en general y el Cluster 2 más equilibrado en su distribución, destacando en la Zona Oeste.


Conclusión:

La aplicación del análisis de conglomerados permitió agrupar las propiedades residenciales en segmentos homogéneos según sus características, evidenciando patrones diferenciados en la oferta inmobiliaria según la ubicación y el estrato socioeconómico, lo que facilita comprender las dinámicas del mercado en distintas zonas de la ciudad.


Paso 4: Análisis de Correspondencia

Examinar la relación entre las variables categóricas (tipo de vivienda, zona y barrio), para identificar patrones de comportamiento de la oferta en mercado inmobiliari

Se convierte en variable tipo factor la variable tipo y zona:

vivienda_5$tipo <- as.factor(vivienda_5$tipo)
vivienda_5$zona <- as.factor(vivienda_5$zona)


Se revisa si la base tiene datos faltantes (rectángulos de color rojo)

library(mice)
md.pattern(vivienda_5, rotate.names = TRUE)


Se puede evidenciar que no hay datos faltantes ya que fueron tratados en el paso 1.

Se construye entonces una tabla cruzada con las variables involucradas en el análisis:

Tipo: Casa y Apartamento.

Zona: Centro, Norte, Oeste, Oriente, Sur

library(FactoMineR)
tabla <- table(vivienda_5$tipo, vivienda_5$zona)
colnames(tabla) <- c("Zona Centro", "Zona Norte", "Zona Oeste",  "Zona Oriente", "Zona Sur" )
tabla
             
              Zona Centro Zona Norte Zona Oeste Zona Oriente Zona Sur
  Apartamento          24       1198       1029           62     2787
  Casa                100        722        169          289     1939

La tabla muestra que en las zonas Norte, Oeste y Sur predominan los apartamentos, con cantidades significativamente mayores que las casas, lo que sugiere una mayor densidad residencial o urbanización vertical en estas áreas. En contraste, las zonas Centro y Oriente presentan más casas que apartamentos, indicando un patrón de vivienda más tradicional o de menor densidad. Estos datos reflejan diferencias claras en la distribución del tipo de vivienda según la zona, lo que puede ser útil para planificación urbana y análisis demográficos.

chisq.test(tabla)

    Pearson's Chi-squared test

data:  tabla
X-squared = 690.93, df = 4, p-value < 2.2e-16


La prueba chi-cuadrado de Pearson muestra un valor estadístico muy alto (690.93) con 4 grados de libertad y un p-valor prácticamente cero, lo que indica que existe una asociación significativa entre la zona geográfica y el tipo de vivienda. Esto significa que la distribución de casas y apartamentos varía según la zona, rechazando la hipótesis de que ambas variables sean independientes.


Finalmente se procede a realizar el análisis de correspondencia que consiste en estimar las coordenadas para cada uno de los niveles de ambas variables

library(FactoMineR)
library(factoextra)
library(gridExtra)

Adjuntando el paquete: 'gridExtra'
The following object is masked from 'package:dplyr':

    combine
resultados_ac <- CA(tabla)

Para medir el grado de representatividad del proceso calculas los valores de la varianza acumulada, utilizando para ellos los valores propios de la matriz de discrepancias

valores_prop <-resultados_ac$eig ; valores_prop
      eigenvalue percentage of variance cumulative percentage of variance
dim 1 0.08305442                    100                               100

El valor propio eigenvalue de la dimensión 1 es aproximadamente 0.083, y esta única dimensión explica el 100% de la varianza total en los datos, con un porcentaje acumulado también del 100%. Esto indica que todo el patrón de asociación entre las variables categóricas de la tabla de contingencia se resume completamente en esta única dimensión. En otras palabras, no existen dimensiones adicionales que aporten información significativa, por lo que el análisis de correspondencias identifica un solo eje principal que captura toda la variabilidad estructural entre las categorías.

library(pheatmap)
pheatmap(tabla,
         cluster_rows = FALSE,  # No agrupa filas
         cluster_cols = FALSE,  # No agrupa columnas
         display_numbers = TRUE, # Muestra los números en las celdas
         main = "Mapa de calor de la tabla de contingencia")

La gráfica muestra un mapa de calor de la tabla de contingencia entre tipo de vivienda y zona geográfica. Se observa que la mayor concentración de viviendas, tanto apartamentos como casas, está en la Zona Sur; especialmente los apartamentos, cuya cifra (2787) destaca sobre el resto y se representa con el color rojo más intenso. La Zona Centro, por el contrario, tiene una cantidad significativamente menor de viviendas de ambos tipos, evidenciada por los tonos azules. En las demás zonas, la distribución es intermedia, con una mayor presencia de apartamentos frente a casas, excepto en la Zona Oriente, donde hay más casas que apartamentos. Esta visualización permite identificar rápidamente las zonas con mayor desarrollo habitacional y las preferencias de tipo de vivienda en cada área, lo que puede ser útil para análisis urbanos o decisiones de mercado inmobiliario.