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>
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
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:
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)
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.
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:
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:
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
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:
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())