knitr::opts_chunk$set(echo = TRUE)
knitr::opts_chunk$set(comment = "")

1 Cargue del dataset

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…

se convierte la variable piso en formato numero por que esta en formato texto

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

convertir la varibale estrato a categorica factor

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

se guarda 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>

se verifica que las categorias de la varibale son unicas

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

se identifica 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 

se eliminan las filas con valores faltantes en la columna id y ya que son pocos ( 3)

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

se calculan las estadisticas descriptivas para varibales cuantitativas.

# 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                                        

como ya se calculaorn las estadisticas descriptivas se prodece aplicar un metodo de imputacion reemplazando los valores faltantes en las variables categoricas con la moda y en las varibales numericas con la mediana.

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 

el dataset ya no tiene valores faltantes en ninguna de sus columnas.

# 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 separado por variable
ggplot(vivienda_long, aes(x = Variable, y = Valor, fill = Variable)) +
  geom_boxplot() +
  facet_wrap(~ Variable, scales = "free") +   # 🔹 separar cada boxplot
  theme_minimal() +
  labs(title = "Boxplots Individuales de Variables Cuantitativas",
       x = "Variable",
       y = "Valor") +
  theme(
    axis.text.x = element_blank(),  # 🔹 ocultar texto en eje X (ya está en el facet)
    legend.position = "none",
    plot.title = element_text(hjust = 0.5)   # Centrar título
  )

se puedene videnciar que si se puede visualizar que si hay valores atipicos por lo tanto se va aa dar tratamiento a los valores atipicos.

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)
Warning: package 'DescTools' was built under R version 4.4.3
# 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

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

# Graficar boxplots uno por uno
for (var in unique(vivienda_long$variable)) {
  p <- ggplot(
    vivienda_long %>% filter(variable == var),
    aes(x = variable, y = valor, fill = variable)
  ) +
    geom_boxplot() +
    theme_minimal() +
    labs(
      title = paste("Boxplot de", var, "con capping"),
      x = "Variable",
      y = "Valor"
    ) +
    theme(
      legend.position = "none",
      plot.title = element_text(hjust = 0.5, face = "bold", size = 14)
    )
  
  print(p)  # muestra cada gráfico individual
}

se visualiza en el grafico que se le dio tratmaiento alos valores atipicos.

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>

se puede observar que la base de datos tiene id repetidos y esot puede ser inconsistencia a la base de datos

paso 2 analisis de componentes principales

Estandarizacion

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

ELECCION DEL NUMERO 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 grafico muestra la varianza exolicada por cada componente principal en un analisis de componentes principales pca, evidencia que el primer componente explica del 44 porciento de la variabilidad de los datos, seguido por el segundo con 15.5 porciento y el tercero con 12.7 porciento a partir del cuarto componente 9.7 porciento la contribucion disminuye progresivamente, llegando a valores al 5 porciento desde el sexto en adelante, lo que sugiere que la mayor parte de la informacion se concentra en los tres primero componentes 72.2 porciento acumulado y que los restantes variabilidad marginal por que lo que podria descartarse para reducir la dimencionalida dy perdida significaiva de la informacion…

# --- 2. Biplot profesional ---
fviz_pca_var(pca_res,
                   col.var = "contrib",
                   gradient.cols = c("red", "#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

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.

paso 3: Analisis de conglomerados

Estandarizacion

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

Metodo del del codo

# 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 grafica muestra una fuerte disminucion de la varianza interna hasta k igual a dos, junto a partir del cual la reduccion e mas suave, el numero optimo de conglomerados para el analisis 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 grafica muestra, del valor mas alto del ancho promedio de silueta que se alcanza en k igaul a 2 lo que indica que 2 conglomerados producen la mejor separacion y coversion 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-reans agrupo los datos en dos cluster con 4977 observaciones en el primero y 3342 en el segundo, mostrando que el primer grupo es mas numeroso.

fviz_cluster(km_res, data = vivienda_scaled,
             geom = "point",
             ellipse.type = "convex",
             palette = c("green", "red")) # naranja y azul

el grafico de dispercion muestra informacion en dos cluster: el cluster 1 en verde se concentra en valores negativos de la primera dimension mientras que el cluster 2 el rojo ocupa valores positivos la frontera entre ambos grupos esta claramente delimitada lo que indica una buena diferenciacion segun las dos primeras dimensiones principales, que explican en conjunto en el 61.8 porciento d ela 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
1     3.004018 -76.52449 3.419481
2     4.059844 -76.53635 3.415958

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.

 # Añadir columna de cluster a vivienda_5
vivienda_5$cluster <- km_res$cluster
 

ggplot(vivienda_5, aes(x = zona, fill = factor(cluster))) +
  geom_bar(position = "dodge") +
  labs(fill = "Cluster", x = "Zona", y = "Cantidad de propiedades") +
  scale_fill_manual(values = c("#E69F00", "#56B4E9")) +  # Colores personalizados
  theme_minimal()

el cluster amarillo predomina claramente en la zona sur y zona norte, mientras que el cluster dos azul tiene mayor presenta en la zona oete y tambien aparece en la zona sur, aunque en menor cantidad que el cluster 1. en la zona centro y zona oriente, ambos clsuter tienen baja representacion esto indica que las zonas geograficas tienen uan fuerte influencia en la pertencia al cluster, con el cluster 1 concentrado en zonas con el mayor numero de propiedades en general y el cluster dos mas equilibrada su distribucion destacando en la zona oeste

Paso 4: Analisis de Correspondencia se convierte en tipo factor la variable tipo zona:

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

se contruye una tabla cruzada con las vairbales involucradas en el analisis

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

en la zona norte oeste y suer predonminan los apartamentos, con cantindades significativamente mayores que en las casas lo que sugiere una amyor densidad residencial o urbanizacion vertical en estas areas en contraste, la zona centro y oriente presentan mas casas que apartamentos indicando un patron de vivienda ams tradicional o de menor densidad. Estos dos reflejan diferencias claras en la distribucion del tipo de vivienda segun la zona, lo que puede ser util para planificaicon urbana y analisis demograficos.

chisq.test(tabla)

    Pearson's Chi-squared test

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

este valor de la prueba del chi cuadrado es de 690.93 con cuatro graados de libertad y un p-value practicamente de cero lo que indica que existe una asocion significativo entre la socidad geografica y el tipo de vivienda esto significa que la distribucion de casa y apartamentos varia segun la zona rechanzando la hipotesis de estas variables sean independientes

se realiza el analisis de correspondencia que cosniste en estimar las coordenadas, para cada uno de los niveles de ambas variables

resultados_ac <- CA(tabla)

para medir el grado de representatividad del proceso calcula los valores de la varianza acumulada para ello los valores propios de los valores de la matriz de discrepancia

valores_prop <-resultados_ac$eig ; valores_prop
      eigenvalue percentage of variance cumulative percentage of variance
dim 1 0.08305442                    100                               100
library(pheatmap)
Warning: package 'pheatmap' was built under R version 4.4.3
# Asegúrate de que la tabla sea matriz
tabla <- as.matrix(tabla)

pheatmap(tabla,
         cluster_rows = FALSE,       # No agrupa filas
         cluster_cols = FALSE,       # No agrupa columnas
         display_numbers = TRUE,     # Muestra los números en las celdas
         number_format = "%.0f",     # Números sin decimales
         number_color = "black",     # Color de los números
         color = colorRampPalette(c("white", "lightblue", "darkblue"))(100), # Paleta
         fontsize = 12,              # Tamaño de fuente general
         fontsize_number = 10,       # Tamaño de números en celdas
         border_color = "grey60",    # Bordes más discretos
         main = "Mapa de calor de la tabla de contingencia"  # Título
)

se observa que la mayor concentracion de viviendas tanto apartamentos como casa esta en la zona sur; especialmente lo apartamentos cuya cifra 2787 destaca sobre el resto y se presenta con el color azul intenso. la zona centro por el contrario tien una cantidad singificativa menor de viviendas de ambos tipos zona centro, evidenciada por el color blanco en las demas zonas la distribucion es intermedia, con una mayor presencia de apartamentos respecto a casas, excepto en la zona oritnete donde hay mas casas que apartamentos. esta visualizaciion permite identificar las zonas con mayor desarrollo habitacional y las referencias de tipo de vivienda en cada area lo que puede hacer util para el analisis urbanos o decisiones de mercado inmobiliario.