0. Cargue de información

El siguiente informe realiza un análisis del set de datos “Vivienda” para la empresa inmobiliaria solicitante, que requiere apoyo con un análisis holistico para identificar patrones, relaciones y segmentaciones que permitan mejorar la toma de decisiones.

options(repos = list(CRAN="http://cran.rstudio.com/"))
knitr::opts_chunk$set(echo = TRUE)
library(paqueteMETODOS)
## Loading required package: cubature
## Loading required package: GGally
## Warning: package 'GGally' was built under R version 4.3.2
## Loading required package: ggplot2
## Warning: package 'ggplot2' was built under R version 4.3.2
## Registered S3 method overwritten by 'GGally':
##   method from   
##   +.gg   ggplot2
## Loading required package: MASS
## Loading required package: summarytools
## Loading required package: psych
## Warning: package 'psych' was built under R version 4.3.2
## 
## Attaching package: 'psych'
## The following objects are masked from 'package:ggplot2':
## 
##     %+%, alpha
## Loading required package: tidyverse
## Warning: package 'tidyr' was built under R version 4.3.2
## Warning: package 'readr' was built under R version 4.3.2
## Warning: package 'purrr' was built under R version 4.3.2
## Warning: package 'dplyr' was built under R version 4.3.2
## Warning: package 'stringr' was built under R version 4.3.2
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ lubridate 1.9.3     ✔ tibble    3.2.1
## ✔ purrr     1.0.2     ✔ tidyr     1.3.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ psych::%+%()    masks ggplot2::%+%()
## ✖ psych::alpha()  masks ggplot2::alpha()
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ✖ dplyr::select() masks MASS::select()
## ✖ tibble::view()  masks summarytools::view()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
data(vivienda)
str(vivienda)
## spc_tbl_ [8,322 × 13] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ id          : num [1:8322] 1147 1169 1350 5992 1212 ...
##  $ zona        : chr [1:8322] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
##  $ piso        : chr [1:8322] NA NA NA "02" ...
##  $ estrato     : num [1:8322] 3 3 3 4 5 5 4 5 5 5 ...
##  $ preciom     : num [1:8322] 250 320 350 400 260 240 220 310 320 780 ...
##  $ areaconst   : num [1:8322] 70 120 220 280 90 87 52 137 150 380 ...
##  $ parqueaderos: num [1:8322] 1 1 2 3 1 1 2 2 2 2 ...
##  $ banios      : num [1:8322] 3 2 2 5 2 3 2 3 4 3 ...
##  $ habitaciones: num [1:8322] 6 3 4 3 3 3 3 4 6 3 ...
##  $ tipo        : chr [1:8322] "Casa" "Casa" "Casa" "Casa" ...
##  $ barrio      : chr [1:8322] "20 de julio" "20 de julio" "20 de julio" "3 de julio" ...
##  $ longitud    : num [1:8322] -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num [1:8322] 3.43 3.43 3.44 3.44 3.46 ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   id = col_double(),
##   ..   zona = col_character(),
##   ..   piso = col_character(),
##   ..   estrato = col_double(),
##   ..   preciom = col_double(),
##   ..   areaconst = col_double(),
##   ..   parqueaderos = col_double(),
##   ..   banios = col_double(),
##   ..   habitaciones = col_double(),
##   ..   tipo = col_character(),
##   ..   barrio = col_character(),
##   ..   longitud = col_double(),
##   ..   latitud = col_double()
##   .. )
##  - attr(*, "problems")=<externalptr>

1. Análisis Exploratorio

Se identifica la estructura del set de datos, extrayendo las primeras 6 lineas. Se observan 3 columnas que no serán utilizadas las cuales son el ID, la latitud y la longitud, al ser la primera solo una variable de identificación sin impacto de análisis y las otras dos no se emplearan en este informe.

head(vivienda)
vivienda2= vivienda[2:11]
head(vivienda2)

A continuación se realiza un resumen estadístico de las columnas restantes. En este primer resultado se identifica que las variables Zona, Piso, tipo y barrio son variables no numéricas. Debido a que Piso si es una variable convencionalmente numérica se transforma esta para su mejor utilización.

print(summary(vivienda2))
##      zona               piso              estrato         preciom      
##  Length:8322        Length:8322        Min.   :3.000   Min.   :  58.0  
##  Class :character   Class :character   1st Qu.:4.000   1st Qu.: 220.0  
##  Mode  :character   Mode  :character   Median :5.000   Median : 330.0  
##                                        Mean   :4.634   Mean   : 433.9  
##                                        3rd Qu.:5.000   3rd Qu.: 540.0  
##                                        Max.   :6.000   Max.   :1999.0  
##                                        NA's   :3       NA's   :2       
##    areaconst       parqueaderos        banios        habitaciones   
##  Min.   :  30.0   Min.   : 1.000   Min.   : 0.000   Min.   : 0.000  
##  1st Qu.:  80.0   1st Qu.: 1.000   1st Qu.: 2.000   1st Qu.: 3.000  
##  Median : 123.0   Median : 2.000   Median : 3.000   Median : 3.000  
##  Mean   : 174.9   Mean   : 1.835   Mean   : 3.111   Mean   : 3.605  
##  3rd Qu.: 229.0   3rd Qu.: 2.000   3rd Qu.: 4.000   3rd Qu.: 4.000  
##  Max.   :1745.0   Max.   :10.000   Max.   :10.000   Max.   :10.000  
##  NA's   :3        NA's   :1605     NA's   :3        NA's   :3       
##      tipo              barrio         
##  Length:8322        Length:8322       
##  Class :character   Class :character  
##  Mode  :character   Mode  :character  
##                                       
##                                       
##                                       
## 
vivienda2$piso <- as.numeric(vivienda2$piso)

A continuación se identifican los registros que en sus atributos contengan valores faltantes, identificando 3514 de 8322, lo cual corresponde a un 42% de la información presente, razón por la cual la eliminación de estos registros no es el camino a seguir.

nan_counts <- rowSums(is.na(vivienda2))

# This will give you a vector with the count of NA values in each row
# You can then sum the vector to get the total count of rows with NA

total_nan_rows <- sum(nan_counts > 0)

print(total_nan_rows)  # This will print the total count of rows with NA
## [1] 3514

Se identifican a continuación de manera discriminada cuantos datos faltantes hay en cada atributo vs registros de información, observando que hay dos filas donde sus atributos no poseen información y una fila donde solo posee información en un atributo, estas 3 filas serán eliminadas del dataset sin esperar un a alteración en los análisis consecuentes. También se observa que los atributos con mayor numero de datos faltantes es parqueaderos y pisos.

library(mice)
## 
## Attaching package: 'mice'
## The following object is masked from 'package:stats':
## 
##     filter
## The following objects are masked from 'package:base':
## 
##     cbind, rbind
md.pattern(vivienda2, plot=TRUE, rotate.names=TRUE)

##      preciom zona estrato areaconst banios habitaciones tipo barrio
## 4808       1    1       1         1      1            1    1      1
## 1909       1    1       1         1      1            1    1      1
## 876        1    1       1         1      1            1    1      1
## 726        1    1       1         1      1            1    1      1
## 1          1    0       0         0      0            0    0      0
## 2          0    0       0         0      0            0    0      0
##            2    3       3         3      3            3    3      3
##      parqueaderos piso     
## 4808            1    1    0
## 1909            1    0    1
## 876             0    1    1
## 726             0    0    2
## 1               0    0    9
## 2               0    0   10
##              1605 2638 4266

Siguiente se puede observar como se ve la matriz de datos, sin estas filas retiradas y aun manteniendo las columnas parqueaderos y piso.

vivienda3 <- vivienda2[rowSums(is.na(vivienda2)) != ncol(vivienda2), ]

vivienda3 <- vivienda3[-8320,]

md.pattern(vivienda3, plot=TRUE, rotate.names=TRUE)

##      zona estrato preciom areaconst banios habitaciones tipo barrio
## 4808    1       1       1         1      1            1    1      1
## 1909    1       1       1         1      1            1    1      1
## 876     1       1       1         1      1            1    1      1
## 726     1       1       1         1      1            1    1      1
##         0       0       0         0      0            0    0      0
##      parqueaderos piso     
## 4808            1    1    0
## 1909            1    0    1
## 876             0    1    1
## 726             0    0    2
##              1602 2635 4237
library(visdat)
vis_miss(vivienda3, sort_miss = TRUE)

De manera rápida a este ultimo data set se extraen los valores únicos para cada atributo categórico para observar que información puede tomar cada uno de ellos.

sort(table(vivienda3$zona), decreasing = TRUE)
## 
##     Zona Sur   Zona Norte   Zona Oeste Zona Oriente  Zona Centro 
##         4726         1920         1198          351          124
sort(table(vivienda3$piso), decreasing = TRUE)
## 
##    2    3    1    4    5    6    8    7    9   10   11   12 
## 1450 1097  860  607  567  245  211  204  146  130   84   83
print("Piso")
## [1] "Piso"
sort(table(vivienda3$estrato), decreasing = TRUE)
## 
##    5    4    6    3 
## 2750 2129 1987 1453
print("Estrato")
## [1] "Estrato"
sort(table(vivienda3$parqueaderos), decreasing = TRUE)
## 
##    1    2    3    4    5    6    7    8   10    9 
## 3155 2475  520  384   68   68   18   17    8    4
print("Parqueaderos")
## [1] "Parqueaderos"
sort(table(vivienda3$banios), decreasing = TRUE)
## 
##    2    3    4    5    1    6    7    8    0    9   10 
## 2946 1993 1456  890  496  314  107   48   45   15    9
print("Baños")
## [1] "Baños"
sort(table(vivienda3$habitaciones), decreasing = TRUE)
## 
##    3    4    2    5    6    7    8    9    0    1   10 
## 4097 1729  926  679  318  173  138   83   66   59   51
print("Habitaciones")
## [1] "Habitaciones"
sort(table(vivienda3$tipo), decreasing = TRUE)
## 
## Apartamento        Casa 
##        5100        3219

Se realiza nuevamente un summary de la información para hacer énfasis en los atributos con datos faltantes los cuales son Piso con 2635 (31.7%) y para parqueaderos 1602 (19.25%).

summary(vivienda3)
##      zona                piso           estrato         preciom      
##  Length:8319        Min.   : 1.000   Min.   :3.000   Min.   :  58.0  
##  Class :character   1st Qu.: 2.000   1st Qu.:4.000   1st Qu.: 220.0  
##  Mode  :character   Median : 3.000   Median :5.000   Median : 330.0  
##                     Mean   : 3.771   Mean   :4.634   Mean   : 433.9  
##                     3rd Qu.: 5.000   3rd Qu.:5.000   3rd Qu.: 540.0  
##                     Max.   :12.000   Max.   :6.000   Max.   :1999.0  
##                     NA's   :2635                                     
##    areaconst       parqueaderos        banios        habitaciones   
##  Min.   :  30.0   Min.   : 1.000   Min.   : 0.000   Min.   : 0.000  
##  1st Qu.:  80.0   1st Qu.: 1.000   1st Qu.: 2.000   1st Qu.: 3.000  
##  Median : 123.0   Median : 2.000   Median : 3.000   Median : 3.000  
##  Mean   : 174.9   Mean   : 1.835   Mean   : 3.111   Mean   : 3.605  
##  3rd Qu.: 229.0   3rd Qu.: 2.000   3rd Qu.: 4.000   3rd Qu.: 4.000  
##  Max.   :1745.0   Max.   :10.000   Max.   :10.000   Max.   :10.000  
##                   NA's   :1602                                      
##      tipo              barrio         
##  Length:8319        Length:8319       
##  Class :character   Class :character  
##  Mode  :character   Mode  :character  
##                                       
##                                       
##                                       
## 

Debido a la magnitud de información faltante no es conveniente eliminar los registros con todos los atributos donde esto hacen falta ni realizar imputaciones con moda o promedios ya que generarían un byass inducido a la información. Razón por la cual para el alcance de este informe se eliminaran de los siguientes pasos.

Posterior a esta selección de atributos, para hacer los análisis estadístico requerimos realizar un escalamiento/estandarización de los datos numéricos especialmente cuando se trabajan en escalas de magnitudes diferentes: Estrato, precio, área, baños y habitaciones.

vivienda_numZ <- scale(vivienda3[,c(3,4,5,7,8)]) 
head(vivienda_numZ)
##         estrato    preciom  areaconst      banios habitaciones
## [1,] -1.5872276 -0.5595498 -0.7339949 -0.07793773    1.6406840
## [2,] -1.5872276 -0.3465670 -0.3842568 -0.77811479   -0.4147626
## [3,] -1.5872276 -0.2552886  0.3152194 -0.77811479    0.2703863
## [4,] -0.6156201 -0.1031580  0.7349051  1.32241640   -0.4147626
## [5,]  0.3559875 -0.5291236 -0.5940997 -0.77811479   -0.4147626
## [6,]  0.3559875 -0.5899759 -0.6150839 -0.07793773   -0.4147626

2. Análisis de componentes principales

A continuación se realiza el análisis de componentes principales (PCA), y examinando los valores propios se identifica que las primeras dos componentes principales explican el 82.4% de la variación, un valor bastante alto. Esto concuerda con el Scree Plot a continuación, donde el tercer, cuarto y quinto componentes tienen aporte relativamente bajos comparados con los dos primeros.

library(FactoMineR) 
## Warning: package 'FactoMineR' was built under R version 4.3.2
library(factoextra)
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
res.pca1 <- prcomp(vivienda_numZ)

res.pca <- PCA(vivienda_numZ, graph = FALSE)

eig.val <- get_eigenvalue(res.pca) 
eig.val
##       eigenvalue variance.percent cumulative.variance.percent
## Dim.1  2.9331575        58.663151                    58.66315
## Dim.2  1.1883211        23.766422                    82.42957
## Dim.3  0.4453505         8.907009                    91.33658
## Dim.4  0.2416783         4.833567                    96.17015
## Dim.5  0.1914925         3.829851                   100.00000
fviz_eig(res.pca1, addlabels = TRUE)

Para entender estos componentes principales obtenemos los aportes/contribuciones de las variables a los componentes principales. Donde para le primer componente su mayor aporte esta dado por el precio, el área y el numero de baños, mientras que para el segundo componente se observa que son estrato y habitaciones.

var <- get_pca_var(res.pca)
head(var$contrib)
##                 Dim.1     Dim.2     Dim.3     Dim.4     Dim.5
## estrato      10.89021 45.486434 17.715123 22.997252  2.910980
## preciom      25.69187  7.882934  9.093046  4.901212 52.430939
## areaconst    24.40828  2.683486 42.580497  8.908083 21.419659
## banios       26.93215  1.194279 14.195179 44.191225 13.487170
## habitaciones 12.07750 42.752867 16.416155 19.002228  9.751252

A continuación se realiza el gráfico de correlación de variables, mostrando la relación entre todas las variables, concluyendo lo siguiente:

  1. El área construida y el número de baños por vivienda están correlacionadas positivamente y fuertemente al estar en el mismo cuadrante.
  2. A mayor distancia entre la variable y el origen, se considera una mejor representación de la variable en las dimensiones, por ende para este caso se puede considerar que todas las variables evaluadas en el dataframe se encuentran fuertemente representadas.
  3. No hay una correlación negativa entre las variables evaluadas ya que ninguna de ellas se encuentra en cuadrantes opuestos. Si una de estas variables crecen las otras también lo haran.
fviz_pca_var(res.pca1,
col.var = "contrib", # Color by contributions to the PC
gradient.cols = c("#FF7F00",  "#034D94"),
repel = TRUE     # Avoid text overlapping
)

A continuación se realiza la matriz de correlación para poder identificar que variables afectan en en mayor media cada una de las dimensiones. Se confirma que a la primera dimensión las variables con mayor contribución a su comportamiento son el precio, el área y el baños, mientras que para la segunda es el estrato y habitaciones. Se puede concluir que la reducción de dimensionalidad es correcta al resumir todas las variables a dos dimensiones sin perder la información original al depender de las 5 variables originales.

library("corrplot") 
## corrplot 0.92 loaded
var <- get_pca_var(res.pca1) 
corrplot(var$contrib, is.corr=FALSE)

3. Análisis de Conglomerado

A continuación se realiza el análisis de conglomerado empleando las columnas preciom, areaconst, baños y habitaciones, con el objetivo de evaluar si existen agrupaciones entre los registros de viviendas con características similares y cuantos grupos hay. El primer paso es realizar el escalamiento de las variables numéricas previamente mencionadas.

viv_congl <- scale(vivienda3[,c(4,5,7,8)])

head(viv_congl)
##         preciom  areaconst      banios habitaciones
## [1,] -0.5595498 -0.7339949 -0.07793773    1.6406840
## [2,] -0.3465670 -0.3842568 -0.77811479   -0.4147626
## [3,] -0.2552886  0.3152194 -0.77811479    0.2703863
## [4,] -0.1031580  0.7349051  1.32241640   -0.4147626
## [5,] -0.5291236 -0.5940997 -0.77811479   -0.4147626
## [6,] -0.5899759 -0.6150839 -0.07793773   -0.4147626

Posteriormente con este nuevo dataframe buscaremos el numero optimo de clusters empleando el metodo “Elbow” o codo, encontrando que el valor optimo aparenta ser 4. Sin embargo al realizar este mismo ejercicio de definición de clusters con la metodología de Silhouette se observa que el optimo aparenta ser 2.

fviz_nbclust(viv_congl, kmeans, method = "wss") +
geom_vline(xintercept = 4, linetype = 2)

# Silhouette method
fviz_nbclust(viv_congl, kmeans, method = "silhouette")+
  labs(subtitle = "Silhouette method")

library(cluster)
library(tidyverse)
# distancia euclidiana
dist_emp <- dist(viv_congl, method = 'euclidean')

# Cluster jerarquico con el método complete
hc_emp <- hclust(dist_emp, method = 'complete')

# Determinamos a dónde pertenece cada observación
cluster_assigments2 <- cutree(hc_emp, k = 2)
cluster_assigments3 <- cutree(hc_emp, k = 3)
cluster_assigments4 <- cutree(hc_emp, k = 4)
cluster_assigments5 <- cutree(hc_emp, k = 5)

# Calcular el coeficiente de Silhouette
sil2 <- silhouette(cluster_assigments2, dist(viv_congl))
sil3 <- silhouette(cluster_assigments3, dist(viv_congl))
sil4 <- silhouette(cluster_assigments4, dist(viv_congl))
sil5 <- silhouette(cluster_assigments5, dist(viv_congl))
sil_avg2 <- mean(sil2[,3])
sil_avg3 <- mean(sil3[,3])
sil_avg4 <- mean(sil4[,3])
sil_avg5 <- mean(sil5[,3])

# Imprimir el coeficiente de Silhouette promedio
cat("Coeficiente de Silhouette promedio k=2 : ", sil_avg2,"\n", "Coeficiente de Silhouette promedio k=3 : ", sil_avg3,"\n", "Coeficiente de Silhouette promedio k=4 : ", sil_avg4, "\n", "Coeficiente de Silhouette promedio k=5 : ", sil_avg5)
## Coeficiente de Silhouette promedio k=2 :  0.6585581 
##  Coeficiente de Silhouette promedio k=3 :  0.5153922 
##  Coeficiente de Silhouette promedio k=4 :  0.5127123 
##  Coeficiente de Silhouette promedio k=5 :  0.1851427

Se encuentra que ambas metodologías son validas, y para la evaluación de este estudio se decide tomar el caso de agrupación por 4 clusters.

Número de Clusters: 4

Se genera un dataframe con una columna atributo que referencia el cluster al que pertenece cada registro, y se realiza una tabla comparativa de las medias, medianas y mínimos-máximos para cada agrupación con respecto a cada atributo.

new_df <- data.frame(cluster_assigments4)

viv_congl2 <- as.data.frame(viv_congl)

viv_congl2[['cluster']] <- new_df[,c(1)]

head(viv_congl2)

En la tabla a continuación se observa que para cada atributo los grupos su media y mediana son diferentes, indicando que esta agrupación de viviendas puede ser viable al presentar medianas y medias diferentes entre cada uno de ellos. Debe ser notado que la mayoría de viviendas entran en el grupo 1 con 7233 viviendas, ~87% de los registros evaluados.

require(table1)
## Loading required package: table1
## 
## Attaching package: 'table1'
## The following objects are masked from 'package:summarytools':
## 
##     label, label<-
## The following objects are masked from 'package:base':
## 
##     units, units<-
table1(~preciom+areaconst+banios+habitaciones|cluster, data=viv_congl2)
## Warning in table1.formula(~preciom + areaconst + banios + habitaciones | :
## Terms to the right of '|' in formula 'x' define table columns and are expected
## to be factors with meaningful labels.
1
(N=7233)
2
(N=1021)
3
(N=58)
4
(N=7)
Overall
(N=8319)
preciom
Mean (SD) -0.234 (0.646) 1.49 (1.42) 2.75 (1.16) 1.89 (2.24) -0.0000000000000000337 (1.00)
Median [Min, Max] -0.407 [-1.14, 3.70] 1.33 [-0.864, 4.61] 3.02 [0.536, 4.76] 3.24 [-0.712, 4.16] -0.316 [-1.14, 4.76]
areaconst
Mean (SD) -0.246 (0.621) 1.40 (0.981) 4.94 (1.09) 9.50 (0.863) 0.0000000000000000123 (1.00)
Median [Min, Max] -0.454 [-1.01, 4.04] 1.32 [-0.734, 5.49] 4.72 [2.95, 7.52] 9.27 [8.32, 11.0] -0.363 [-1.01, 11.0]
banios
Mean (SD) -0.214 (0.804) 1.40 (1.00) 1.95 (0.970) 0.622 (1.14) 0.000000000000000103 (1.00)
Median [Min, Max] -0.0779 [-2.18, 4.82] 1.32 [-2.18, 4.82] 2.02 [-0.0779, 4.82] 0.622 [-1.48, 2.02] -0.0779 [-2.18, 4.82]
habitaciones
Mean (SD) -0.225 (0.674) 1.50 (1.43) 1.59 (1.53) 0.760 (1.84) -0.0000000000000000481 (1.00)
Median [Min, Max] -0.415 [-2.47, 2.33] 1.64 [-1.10, 4.38] 0.956 [-0.415, 4.38] 0.270 [-1.10, 4.38] -0.415 [-2.47, 4.38]

Para entender este comportamiento se gráfica el precio vs el área (escalados ambos) , observando que el cluster 1 corresponde a las viviendas con menores precios y menores área, mientras que el cluster 4 correspondería a las viviendas con áreas mayores al resto de las agrupaciones sin distinguir del precio, es decir hogares con altos o bajos precios son agrupados por su área principalmente. El cluster 3 y 2 son grupos de transición entre estos grupos extremos, donde el 3 corresponde a viviendas de precios altos con áreas por encima de los grupo 1 y 2 pero por debajo al grupo 4.

# gráfico de puntos
ggplot(viv_congl2, aes(x = areaconst, y = preciom, color = cluster)) +
geom_point(size = 2) +
geom_text(aes(label = cluster), vjust = -.8) + # Agregar etiquetas del clúster
theme_classic()

En el siguiente gráfico se muestra habitaciones vs baños y su agrupación. Debido a que estos atributos son categóricos su distribución se observa como una malla, sin embargo se logra apreciar que las viviendas de grupo 1 corresponden principalmente a aquellas con un numero de baños y habitaciones, y los otros 3 grupos se ubican en el espectro de mayor conteo de baños y habitaciones.

# gráfico de puntos
ggplot(viv_congl2, aes(x = habitaciones, y = banios, color = cluster)) +
geom_point(size = 2) +
geom_text(aes(label = cluster), vjust = -.8) + # Agregar etiquetas del clúster
theme_classic()

viv_congl2$zona <- vivienda3$zona
viv_congl2$estrato <- vivienda3$estrato
colnames(viv_congl2)[6] <- "zona"
colnames(viv_congl2)[7] <- "estrato"

viv_congl2$estrato = as.factor(viv_congl2$estrato)
viv_congl2$cluster = as.factor(viv_congl2$cluster)
congl_anl <- viv_congl2 %>% group_by(cluster)

grouped_data_estr <- aggregate(preciom ~ estrato+cluster, data = viv_congl2, FUN = length)
colnames(grouped_data_estr)[3] <- "conteo"

grouped_data_estr$estrato = as.factor(grouped_data_estr$estrato)
grouped_data_estr$cluster = as.factor(grouped_data_estr$cluster)

A continuación se gráfica el conteo de registros por cluster para cada estrato, encontrando que:

  1. El cluster 1 de viviendas puede encontrarse en cualquiera de los estratos socio-economicos con preferencialidad en el estrato 5.
  2. El cluster 2 presenta un comportamiento similar al 1 sin embargo se encuentran presencialmente en el estrato 6
  3. El cluster 3 se encuentra en los estratos 4 a 6 con mayor presencia en el 6.
  4. El cluster 4 se encuentra en los estratos 3 y 6, indicando que en estos estratos es factible hallar viviendas con áreas mayores al de las otras agrupaciones.
p_estr <- ggplot(data = grouped_data_estr, aes(x= estrato,fill = cluster))+
  geom_bar(aes(y=conteo), position = "dodge", stat = "identity")+
   geom_text(aes(y=conteo,label=conteo), size=3)+
  scale_y_log10()

p_estr

grouped_data_zona <- aggregate(preciom ~ zona+cluster, data = viv_congl2, FUN = length)

colnames(grouped_data_zona)[3] <- "conteo"

A continuación se gráfica el conteo de registros por cluster para cada zona de la ciudad, encontrando que:

  1. El cluster 1 y 2 se encuentran formados por viviendas que pueden encontrarse en cualquiera de las zonas de la ciudad con preferencialidad en la zona sur.
  2. El cluster 3 se encuentra en todas las zonas exceptuando la Oriente, preferencialmente Sur.
  3. El cluster 4 se encuentra las zonas Oriente, Norte y Sur.
  4. Para todas los clusters la zona con mayor densidad de registros de viviendas es la Zona Sur.
p_zona <- ggplot(data = grouped_data_zona, aes(x= zona,fill = cluster))+
  geom_bar(aes(y=conteo), position = "dodge", stat = "identity")+
   geom_text(aes(y=conteo,label=conteo), size=3)+
  scale_y_log10()

p_zona

df2 <- viv_congl2 %>% group_by(cluster,zona) %>% 
  summarise(total_count=n(),.groups = 'drop') %>%
  as.data.frame()
df2

4. Análisis de correspondencia

A continuación se realiza el análisis de correspondencia empleando las columnas zona, estrato, tipo y barrio, con el objetivo de evaluar si existen relaciones entre estas variables categoricas e identificar patrones de comportamiento. El primer paso es la selección de estas y evaluar si existen datos faltantes.

viv_cat <- vivienda3[,c(1,3,9,10)]

viv_cat$estrato <- as.factor(viv_cat$estrato)

head(viv_cat)
md.pattern(viv_cat, rotate.names = TRUE)
##  /\     /\
## {  `---'  }
## {  O   O  }
## ==>  V <==  No need for mice. This data set is completely observed.
##  \  \|/  /
##   `-----'

##      zona estrato tipo barrio  
## 8319    1       1    1      1 0
##         0       0    0      0 0

Se eraliza la prueba de Chi cuadrado para evaluar la independencia de las variableses, conrimando esta independencia.

zona_tipo <- table(viv_cat$zona, viv_cat$tipo)
zona_barrio <- table(viv_cat$zona, viv_cat$barrio)
tipo_barrio <- table(viv_cat$tipo, viv_cat$barrio)
chisq.test(zona_tipo, simulate.p.value = TRUE)
## 
##  Pearson's Chi-squared test with simulated p-value (based on 2000
##  replicates)
## 
## data:  zona_tipo
## X-squared = 690.93, df = NA, p-value = 0.0004998
chisq.test(zona_barrio, simulate.p.value = TRUE)
## 
##  Pearson's Chi-squared test with simulated p-value (based on 2000
##  replicates)
## 
## data:  zona_barrio
## X-squared = 29343, df = NA, p-value = 0.0004998
chisq.test(tipo_barrio, simulate.p.value = TRUE)
## 
##  Pearson's Chi-squared test with simulated p-value (based on 2000
##  replicates)
## 
## data:  tipo_barrio
## X-squared = 2468.3, df = NA, p-value = 0.0004998

A continuación se realiza el análisis de correspondencia multiple “MCA” debido a que se poseen mas de dos variables categóricas, y se empleara la librería “FactorMineR” para analizar los datos y la librería “factoextra” para la visualizacion de los datos.

library(FactoMineR)
library(factoextra)
library(gridExtra)
## 
## Attaching package: 'gridExtra'
## The following object is masked from 'package:dplyr':
## 
##     combine
mca_lb <- MCA(viv_cat, graph = FALSE)

El MCA genera nuevos componentes siguiendo la misma lógica del PCA, por lo cual es importante identificar y revisa la proporción de varianzas que retiene cada una de las dimensiones/ejes extrayendo los eigenvalores o varianzas retenidas. Como se observa en las primeras 10 dimensiones la varianza acumulativa es de apenas un 3.7% por lo cual no es muy exitosa la reducción de dimensionalidad, y esto puede deberse a la variedad de barrios que presenta el dataframe siendo analizado. Una metodología alterna para determinar el numero de dimensiones es el Screeplot, el cual es evaluado a continuación, confirmando que el porcentaje de inercia para cada dimensión es bajo.

eig_val <- factoextra::get_eigenvalue(mca_lb)
head(eig_val, 10)
##        eigenvalue variance.percent cumulative.variance.percent
## Dim.1   0.6522383        0.5889285                   0.5889285
## Dim.2   0.5698680        0.5145535                   1.1034820
## Dim.3   0.5087651        0.4593816                   1.5628636
## Dim.4   0.4686469        0.4231575                   1.9860211
## Dim.5   0.3932353        0.3550657                   2.3410867
## Dim.6   0.3789529        0.3421696                   2.6832564
## Dim.7   0.3341129        0.3016821                   2.9849385
## Dim.8   0.3191709        0.2881904                   3.2731290
## Dim.9   0.2500000        0.2257336                   3.4988626
## Dim.10  0.2500000        0.2257336                   3.7245962
fviz_screeplot(mca_lb, addlabels = TRUE)

Se evalúa la contribución de cada categoría de variables en la construcción de las primeras 5 dimensiones, con el objetivo de evaluar el motivo por el cual las varianzas previamente planteadas son bajas, encontrando que las categorías de zona y estrato son las que mayor contribución generan en estas primeras dimensiones. Con esta revisión se decide realizar un análisis de MCA donde se excluyan los barrios y sea enfocado en zonas, estratos y tipo de vivienda.

fviz_contrib(mca_lb, choice = "var", axes = 1:5, top = 15)

Se genera nuevamente un data frame esta vez solo incluyendo los atributos de zona, estrato y tipo de vivienda

viv_cat2 <- vivienda3[,c(1,3,9)]

viv_cat2$estrato <- as.factor(viv_cat2$estrato)

head(viv_cat2)

Se realiza nuevamente prueba de Chi cuadrado para corroborar la independencia de las variables, encontrando que cumplen esta prueba de hipotesis.

zona_tipo <- table(viv_cat$zona, viv_cat$tipo)
zona_estrato <- table(viv_cat$zona, viv_cat$estrato)
tipo_estrato <- table(viv_cat$tipo, viv_cat$estrato)
chisq.test(zona_tipo)
## 
##  Pearson's Chi-squared test
## 
## data:  zona_tipo
## X-squared = 690.93, df = 4, p-value < 2.2e-16
chisq.test(zona_estrato)
## 
##  Pearson's Chi-squared test
## 
## data:  zona_estrato
## X-squared = 3830.4, df = 12, p-value < 2.2e-16
chisq.test(tipo_estrato)
## 
##  Pearson's Chi-squared test
## 
## data:  tipo_estrato
## X-squared = 224.33, df = 3, p-value < 2.2e-16

Se realiza el análisis de correspondencia multiple “MCA” nuevamente.

mca_lb2 <- MCA(viv_cat2, graph = FALSE)

Se evalúa nuevamente los eigenvalores y se observaque la varianza acumulada no requiere más de 5 dimensiones para explicar el 76% de esta, mejorando con respecto al análisis previo donde si se incluían los barrios.

eig_val <- get_eigenvalue(mca_lb2)
eig_val
##       eigenvalue variance.percent cumulative.variance.percent
## Dim.1  0.5620882        21.078306                    21.07831
## Dim.2  0.4531232        16.992119                    38.07043
## Dim.3  0.3796450        14.236687                    52.30711
## Dim.4  0.3334444        12.504164                    64.81128
## Dim.5  0.3233137        12.124263                    76.93554
## Dim.6  0.2717762        10.191608                    87.12715
## Dim.7  0.2013563         7.550862                    94.67801
## Dim.8  0.1419197         5.321990                   100.00000
fviz_screeplot(mca_lb2, addlabels = TRUE)

Se identifica a continuación los niveles de correspondencia de las variables y las categorías con las primeras dos dimensiones que explican entre ellas el 38% de la varianza acumulada. En el gráfico a continuación se observa la correlación de las variables con estas dos primeras dimensiones, encontrando que el estrato y la zona están correlacionados con ambas dimensiones, sin embargo el tipo de vivienda se correlaciona fuertemente con la dimensión 1.

fviz_mca_var(mca_lb2, # objeto lista de resultados mca
             choice = "mca.cor", # tipo de análisis solicitado: correlaciones
            repel = TRUE, # evitar la superposición de etiquetas
            ggtheme = theme_minimal()
            )

Si bien la gráfica anterior muestra la correlación de cada una de las variables con respecto a las dimensiones, allí no se puede distinguir la presencia de las categorías de respuestas en el mismo plano cartesiano. Para ello, es importante trabajar con el valor de las coordenadas de cada categoría y en el gráfico a continuación se puede observar esta asociación entre cada categoría y las dimensiones 1 y 2. Observando que:

  1. Los estratos 4 y 5 presentan un perfil similar al de la Zona Sur, por ende viviendas se puede dar a la idea de que viviendas de estos estratos pueden ser encontrados en esta zona. Mientras que el estrato 3 tiene un perfil mas símil al de las Zonas Oriente y Centro. Estos dos grupos de categorías se encuentran en polos opuestos correlacionando de manera negativa.
  2. El tipo Casa y Zona Norte son mas semejantes y cercanos en el cuadrante, mostrando una tendencia a que este tipo de vivienda es mas factible encontrarla en esta región de la ciudad.
  3. El estrato 6 se encuentra cercano al perfil de la zona Oeste y opuesto al de la Zona Norte, mostrando una correlación a encontrar apartamentos de este estrato en la zona oeste y con mayor tendencia a ser apartamentos que casas.
fviz_mca_var(mca_lb2, #objeto lista con resultados mca 
             repel = TRUE, # evitar el traslape de etiquetas
             ggtheme = theme_minimal(),
             max.overlaps = "ggrepel.max.overlaps"
             )
## Warning in (function (mapping = NULL, data = NULL, stat = "identity", position
## = "identity", : Ignoring unknown parameters: `max.overlaps`

Para evaluar el comportamiento de los individuos o registros y se generan elipses de concentración que muestran el tamaño de la dispersión de cada grupo y encontrar correlación entre estos mismos. En el primer gráfico se realiza esta agrupación teniendo como base las zonas de la ciudad, identificando que las zonas oeste y centro son las que agrupan de mejor manera y lo mas excluyente posible a sus individuos, mientra que la zona oriente es abarcada en su mayoría por la del grupo centro, y las zonas Norte y Sur se solapan en varios de su puntos. Estas agrupaciones e intersecciones de las elipses puedan dar a inferir que las zonas seria posible agruparlas en tres (ignorando su definición geografica) en tres: 1) Centro+Oriente, 2)Norte y Sur, 3) Oeste

fviz_mca_ind(mca_lb2, #objeto tipo lista con resultados mca 
             label = "none", # ocultar las etiquetas de los individuos
             habillage = "zona", # colorear a los grupos 
             addEllipses = TRUE,
             ggtheme = theme_minimal()
             )

Siguiendo la misma linea de pensamiento del gráfico anterior, se realiza esta misma agrupación por elipse esta vez en función del estrato, observando un caso similar al anterior. El estrato 3 y 6 hacen una separación mejor y más excluyente que los otros estratos, sin embargo los estratos 4 y 5 se sobreponen fuertemente y son parcialmente cubiertos por los estratos mencionados previamente, pudiendo indicar que la variable estrato seria factible integrarla en 3 grupos al unificar los estratos 4 y 5 en uno solo

fviz_mca_ind(mca_lb2, #objeto tipo lista con resultados mca 
             label = "none", # ocultar las etiquetas de los individuos
             habillage = "estrato", # colorear a los grupos 
             addEllipses = TRUE,
             ggtheme = theme_minimal())

5. Conclusiones

  1. El área construida y el número de baños por vivienda están correlacionadas positivamente y fuertemente al estar en el mismo cuadrante.
  2. A mayor distancia entre la variable y el origen, se considera una mejor representación de la variable en las dimensiones, por ende para este caso se puede considerar que todas las variables evaluadas en el dataframe se encuentran fuertemente representadas.
  3. No hay una correlación negativa entre las variables evaluadas ya que ninguna de ellas se encuentra en cuadrantes opuestos. Si una de estas variables crecen las otras también lo harán.
  4. Del análisis de componentes principales (PCA), se identifica que las primeras dos componentes principales explican el 82.4% de la variación, un valor bastante alto.
  5. Se puede concluir que la reducción de dimensionalidad es correcta al resumir todas las variables a dos dimensiones sin perder la información original al depender de las 5 variables originales.
  6. El numero de clusters optimo para un dataframe de las columnas preciom, areaconst, baños y habitaciones puede ser 4 o 2 en función dela metodología de evaluación seleccionada.
  7. El 87% de las viviendas evaluadas se categorizan en el grupo 1, el cual presenta el menor numero de baños, habitaciones, la menor área construida y el menor preció.
  8. Las viviendas en el cluster 4 corresponden a las viviendas con áreas mayores al resto de las agrupaciones sin distinguir del precio, es decir hogares con altos o bajos precios son agrupados por su área principalmente.
  9. El cluster 3 y 2 son grupos de transición entre estos grupos extremos, donde el 3 corresponde a viviendas de precios altos con áreas por encima de los grupo 1 y 2 pero por debajo al grupo 4.
  10. Las viviendas del cluster 1 corresponden principalmente a aquellas con un número de baños y habitaciones, y los otros 3 grupos se ubican en el espectro de mayor conteo de baños y habitaciones.
  11. El cluster 1 de viviendas puede encontrarse en cualquiera de los estratos socio-económicos con preferencialidad en el estrato 5.
  12. El cluster 2 presenta un comportamiento similar al 1 sin embargo se encuentran presencialmente en el estrato 6
  13. El cluster 3 se encuentra en los estratos 4 a 6 con mayor presencia en el 6.
  14. El cluster 4 se encuentra en los estratos 3 y 6, indicando que en estos estratos es factible hallar viviendas con áreas mayores al de las otras agrupaciones.
  15. El cluster 1 y 2 se encuentran formados por viviendas que pueden encontrarse en cualquiera de las zonas de la ciudad con preferencialidad en la zona sur.
  16. El cluster 3 se encuentra en todas las zonas exceptuando la Oriente, preferencialmente Sur.
  17. El cluster 4 se encuentra las zonas Oriente, Norte y Sur.
  18. Para todas los clusters la zona con mayor densidad de registros de viviendas es la Zona Sur.
  19. La reducción de dimensionalidad de un dataframe de variables categoricas incluyendo los barrios no es exitosa al presentar una varianza de 3.7% en las 10 dimensiones con mayor contribución, y esto puede deberse a la variedad de barrios que presenta el dataframe siendo analizado.
  20. Los estratos 4 y 5 presentan un perfil similar al de la Zona Sur, por ende viviendas se puede dar a la idea de que viviendas de estos estratos pueden ser encontrados en esta zona. Mientras que el estrato 3 tiene un perfil mas símil al de las Zonas Oriente y Centro. Estos dos grupos de categorías se encuentran en polos opuestos correlacionando de manera negativa.
  21. El tipo Casa y Zona Norte son mas semejantes y cercanos en el cuadrante, mostrando una tendencia a que este tipo de vivienda es mas factible encontrarla en esta región de la ciudad.
  22. El estrato 6 se encuentra cercano al perfil de la zona Oeste y opuesto al de la Zona Norte, mostrando una correlación a encontrar apartamentos de este estrato en la zona oeste y con mayor tendencia a ser apartamentos que casas.
  23. Las zonas seria posible agruparlas en tres (ignorando su definición geográfica) en tres: 1) Centro+Oriente, 2)Norte y Sur, 3) Oeste.
  24. La variable estrato seria factible integrarla en 3 grupos al unificar los estratos 4 y 5 en uno solo.