Evaluación de la oferta inmobiliaria urbana

En el presente análisis se procede a realizar un análisis de la oferta inmoboliaria en la ciudad de Cali, a través de una serie de procedimientos multivariados que permitan obtener información valiosa que facilite la toma de decisiones de la empresa inmobiliaria.

Para realizar dicho procedimiento, se utilizaron técnicas de análisis multivariado que permitan mejorar la comprensión del comportamiento de las diferentes variables

Análisis de Componentes Principales

Para realizar el presente trabajo, comenzamos realizando un análisis de ACP que nos permita disminuir la dimensionalidad de los datos. Esta técnica permite realizar un proceso de agrupamiento de los datos con el objetivo de generar nuevas características a partir de los datos originales. Para ello, comenzamos el proceso cargando los datos de la libreria correspondiente.

library(paqueteMODELOS)
## Loading required package: boot
## Loading required package: broom
## Loading required package: GGally
## Loading required package: ggplot2
## Registered S3 method overwritten by 'GGally':
##   method from   
##   +.gg   ggplot2
## Loading required package: gridExtra
## Loading required package: knitr
## Loading required package: summarytools
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")=List of 3
##   ..$ cols   :List of 13
##   .. ..$ id          : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ zona        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ piso        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ estrato     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ preciom     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ areaconst   : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ parqueaderos: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ banios      : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ habitaciones: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ tipo        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ barrio      : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ longitud    : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ latitud     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   ..$ default: list()
##   .. ..- attr(*, "class")= chr [1:2] "collector_guess" "collector"
##   ..$ delim  : chr ";"
##   ..- attr(*, "class")= chr "col_spec"
##  - attr(*, "problems")=<externalptr>

Luego de cargar los documentos y las bases de datos, procedemos a comenzar el análisis exploratorio de los datos, para ello, comenzamos identificando si existen datos faltantes.

library (mice)
## 
## Attaching package: 'mice'
## The following object is masked from 'package:stats':
## 
##     filter
## The following objects are masked from 'package:base':
## 
##     cbind, rbind
library (ggplot2)
library (tidyverse)
## ── 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() ──
## ✖ dplyr::combine() masks gridExtra::combine()
## ✖ dplyr::filter()  masks mice::filter(), stats::filter()
## ✖ dplyr::lag()     masks stats::lag()
## ✖ tibble::view()   masks summarytools::view()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
md.pattern(vivienda)

##      preciom id zona estrato areaconst banios habitaciones tipo barrio longitud
## 4808       1  1    1       1         1      1            1    1      1        1
## 1909       1  1    1       1         1      1            1    1      1        1
## 876        1  1    1       1         1      1            1    1      1        1
## 726        1  1    1       1         1      1            1    1      1        1
## 1          1  0    0       0         0      0            0    0      0        0
## 2          0  0    0       0         0      0            0    0      0        0
##            2  3    3       3         3      3            3    3      3        3
##      latitud parqueaderos piso     
## 4808       1            1    1    0
## 1909       1            1    0    1
## 876        1            0    1    1
## 726        1            0    0    2
## 1          0            0    0   12
## 2          0            0    0   13
##            3         1605 2638 4275

La gráfica, evidencia que existen datos faltantes. A raíz de ello, procedemos a cuantificar en qué variables se encuentran la mayor parte de dichos datos.

missing_values <-sapply(vivienda, function(x) sum(is.na(x))) 
missing_values
##           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

La anterior información, nos permite reconocer que las variables con mayor número de datos faltantes es el piso y los parqueaderos. Sin embargo, para complementar dicho análisis, procedemos a valorar el peso porcentual de los datos faltantes para identificar qué acciones podríamos realizar con los datos.

porcentaje_perdido <- function(x) {sum(is.na(x))/length(x)*100} 
apply(vivienda, 2, porcentaje_perdido)
##           id         zona         piso      estrato      preciom    areaconst 
##   0.03604903   0.03604903  31.69911079   0.03604903   0.02403268   0.03604903 
## parqueaderos       banios habitaciones         tipo       barrio     longitud 
##  19.28622927   0.03604903   0.03604903   0.03604903   0.03604903   0.03604903 
##      latitud 
##   0.03604903

Considerando los resultados anteriores, se encuentra que las variables piso y parqueaderos tienen un porcentaje de datos ausentes del 31.69% y 19.28% respectivamente. De acuerdo a dichos resultados, decidimos eliminar dichas variables debido al extenso número de datos ausentes. Igualmente, eliminamos el ID que no se requiere para el análisis. Realizamos este proceso antes de comenzar con el análisis de componentes principales.

# Eliminar las variables "piso" y "parqueadero"
vivienda_limpia <- vivienda[, !names(vivienda) %in% c("id","piso", "parqueaderos")]

# Eliminar las filas con datos faltantes
vivienda_limpia_2 <- vivienda_limpia[complete.cases(vivienda_limpia), ]

vivienda_limpia_2
## # A tibble: 8,319 × 10
##    zona      estrato preciom areaconst banios habitaciones tipo  barrio longitud
##    <chr>       <dbl>   <dbl>     <dbl>  <dbl>        <dbl> <chr> <chr>     <dbl>
##  1 Zona Ori…       3     250        70      3            6 Casa  20 de…    -76.5
##  2 Zona Ori…       3     320       120      2            3 Casa  20 de…    -76.5
##  3 Zona Ori…       3     350       220      2            4 Casa  20 de…    -76.5
##  4 Zona Sur        4     400       280      5            3 Casa  3 de …    -76.5
##  5 Zona Nor…       5     260        90      2            3 Apar… acopi     -76.5
##  6 Zona Nor…       5     240        87      3            3 Apar… acopi     -76.5
##  7 Zona Nor…       4     220        52      2            3 Apar… acopi     -76.5
##  8 Zona Nor…       5     310       137      3            4 Apar… acopi     -76.5
##  9 Zona Nor…       5     320       150      4            6 Casa  acopi     -76.5
## 10 Zona Nor…       5     780       380      3            3 Casa  acopi     -76.5
## # ℹ 8,309 more rows
## # ℹ 1 more variable: latitud <dbl>

Procedemos a verificar nuevamente los datos faltantes con la gráfica

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

##      zona estrato preciom areaconst banios habitaciones tipo barrio longitud
## 8319    1       1       1         1      1            1    1      1        1
##         0       0       0         0      0            0    0      0        0
##      latitud  
## 8319       1 0
##            0 0

Como ya se eliminaron del dataframe, procedemos a estandarizar los datos para que todas las variables tengan la misma escala. Este procedimiento solo se realiza a las variables numéricas y tiene gran relevancia porque al estandarizar los datos se asegura que la escala de las diferentes variables no vaya a afectar el procedimiento de ACP.

viviendaZ <- vivienda_limpia_2 %>%
  select_if(is.numeric) %>%
  scale()

head (viviendaZ)
##         estrato    preciom  areaconst      banios habitaciones   longitud
## [1,] -1.5872276 -0.5595498 -0.7339949 -0.07793773    1.6406840  0.9728466
## [2,] -1.5872276 -0.3465670 -0.3842568 -0.77811479   -0.4147626  0.9331875
## [3,] -1.5872276 -0.2552886  0.3152194 -0.77811479    0.2703863  0.7607566
## [4,] -0.6156201 -0.1031580  0.7349051  1.32241640   -0.4147626 -0.6549016
## [5,]  0.3559875 -0.5291236 -0.5940997 -0.77811479   -0.4147626  0.8682385
## [6,]  0.3559875 -0.5899759 -0.6150839 -0.07793773   -0.4147626  0.6670691
##         latitud
## [1,]  0.3793708
## [2,]  0.3763219
## [3,]  0.4225243
## [4,]  0.4070454
## [5,]  0.9678065
## [6,] -1.1242009

Luego de escalar los datos, procedemos a calcular la desviación estandar de los datos escalados.

prcomp(viviendaZ)
## Standard deviations (1, .., p=7):
## [1] 1.7649945 1.2197986 0.9408170 0.8090903 0.6534211 0.4902176 0.4357175
## 
## Rotation (n x k) = (7 x 7):
##                     PC1         PC2         PC3         PC4         PC5
## estrato       0.3596495 -0.46160840  0.29253122 -0.24988384  0.47695341
## preciom       0.4947460 -0.06764679  0.25051923 -0.25237458 -0.22943062
## areaconst     0.4556538  0.26756925  0.02809938 -0.12641620 -0.64147176
## banios        0.4903682  0.18623745 -0.06882523 -0.01680433  0.39400870
## habitaciones  0.2974110  0.54943309 -0.32731570  0.32355603  0.33736244
## longitud     -0.2672367  0.46217187 -0.01581921 -0.82481589  0.17733905
## latitud      -0.1338978  0.40072976  0.85950759  0.26942892  0.08880096
##                       PC6         PC7
## estrato      -0.501853021  0.17381740
## preciom       0.219896274 -0.72332891
## areaconst    -0.292270190  0.45509101
## banios        0.652112622  0.37328490
## habitaciones -0.431734658 -0.31247031
## longitud     -0.052076162 -0.01578471
## latitud       0.003114001  0.04739724

Elección del número de componentes principales

Luego de realizar el escalamiento y calcular las desviaciones estandar, procedemos a elegir el número de componentes principales evaluando la varianza acumulada de los principales componentes principales.

library (factoextra)
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
res.pca <- prcomp(viviendaZ)
fviz_eig(res.pca, addlabels = TRUE)

De acuerdo con el gráfico anterior, se evidencia como el primer componente explica el 44.5% de la variabilidad contenida en los datos, y el segundo componente del 21.3%. En conjunto los tres primeros componentes suman cerca del 77% de la varianza de los datos. Al sumar al cuarto componente se obtiene una variabilidad cercana al 90% (valor recomendado), para lo cual, los próximos cálculos se realizaran con los cuatros componentes principales. Para continuar con el análisis de los componentes principales se realiza un análisis de contribución por dimensiones.

fviz_contrib(res.pca,axes = 1, choice = "var", addlabels = TRUE) # dimensión 1 

fviz_contrib(res.pca,axes = 2, choice = "var", addlabels = TRUE) # dimensión 2 

fviz_contrib(res.pca,axes = 3, choice = "var", addlabels = TRUE) # dimensión 3

fviz_contrib(res.pca,axes = 4, choice = "var", addlabels = TRUE) # dimensión 4 

El análisis de las contribuciones permite reconocer que en la primera dimensión el componente precio, baños y área construida son las variables que más aportan a la explicación del primer componente principal. En el segundo componente la mayor contribución está dada por habitaciones, longitud, estrato y latitud. En el tercer y cuarto componente solo las variables latitud y longitud, correspondientemente son los que explican la mayor variación.

Para continuar con el análisis, se realiza un gráfico para la contribución de los componentes principales, de la siguiente manera:

fviz_pca_var(res.pca,
col.var = "contrib", 
gradient.cols = c("#FF7F00",  "#034D94"),
repel = TRUE     
)

El resultado de la anterior gráfica corrobora los análisis de las contribuciones de cada dimensión, con lo cual se reconoce que las variables baños y precio por metro cuadrado son los que explican la mayor variabilidad. Seguidamente, se puede encontrar las variables estrato, área construida y habitaciones. Los atributos longitud y latitud son las características que menos aportan a la variabilidad de los atributos en el mercado inmobiliario en la ciudad de Cali.

Al considerar el porcentaje de contribución de cada componente, se procede a realizar un análisis extrayendo 4 muestras del conjunto de datos.

datos<- rbind(viviendaZ[112,], 
viviendaZ[1088,],
viviendaZ[21,],
viviendaZ[497,])

datos <- as.data.frame(datos)
rownames(datos) = c("Dato 112","Dato 1088","Dato 021","Dato 497")
datos
##              estrato    preciom  areaconst     banios habitaciones     longitud
## Dato 112  -0.6156201 -0.8851092 -0.8179320 -1.4782919   -0.4147626 -0.092201884
## Dato 1088 -0.6156201  0.9617560  0.8748003  1.3224164    0.2703863  0.045168107
## Dato 021  -1.5872276 -0.7877456 -0.6640473 -0.7781148   -0.4147626 -0.008285488
## Dato 497   1.3275950  0.2923815 -0.4332201  0.6222393   -0.4147626 -1.114717428
##              latitud
## Dato 112  0.20581862
## Dato 1088 1.36744563
## Dato 021  0.05665756
## Dato 497  0.66502833

De acuerdo con los anteriores resultados, encontramos que el dato 1088 presenta valores diferentes al grupo seleccionado en relación con el área construida y los baños. De igual manera, se puede reconocer que el dato 497 también tiene valores diferentes a los anteriores especialmente en relación con las variables de precio por metro cuadrado y baños.Luego de obtener esta información, procedemos a graficar estos datos.

casos1 <- rbind(res.pca$x[112,1:4],res.pca$x[1088,1:4]) # CP1
rownames(casos1) = c("112","1088")
casos1 <- as.data.frame(casos1)

casos2 <- rbind(res.pca$x[021,1:4], res.pca$x[497,1:4]) # CP2
rownames(casos2) = c("021","497")
casos2 <- as.data.frame(casos2)

fviz_pca_ind(res.pca, col.ind = "#DEDEDE", gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07")) +
geom_point(data = casos1, aes(x = PC1, y = PC2), color = "red", size = 3) +
geom_point(data = casos2, aes(x = PC1, y = PC2), color = "blue", size = 3)

Posterior al análisis con algunos ejemplos tomados de la data set, procedemos a realizar un estudio con las variables categóricas de zona y tipo de vivienda para identificar algunos patrones en los datos y que permitan ir estableciendo algunas relaciones entre el dataframe.

Análisis con variables categóricas

length(vivienda_limpia_2$tipo)
## [1] 8319
nrow(res.pca$x)
## [1] 8319
fviz_pca_biplot(
  res.pca, 
  habillage = vivienda_limpia_2$tipo,
  repel = TRUE,
  col.var = "#034A94", # Variables color
  col.ind = c("#DEDEDE", "#034A94")  # Individuals color
)

De acuerdo con la anterior gráfica, se logra visualizar que en el tipo de vivienda apartamento, las variables que tienen un mayor peso explicativo están relacionadas con el precio por metro cuadrado y el estrato. En contraste, para el tipo de vivienda de casa, se evidencia que los atributos que tienen un mayor peso explicativo son las habitaciones, área construida y número de baños.

fviz_pca_biplot(res.pca, 
repel = TRUE,
habillage = vivienda_limpia_2$zona,
col.var = "#034A94", # Variables color
col.ind = c("#DEDEDE", "#034A94")  # Individuals color
)

Los resultados de la anterior gráfica permiten comprender que los atributos de los componentes principales varían de acuerdo a la zona de la vivienda. A raíz de ello, se logra encontrar que en la zona sur el área construida es el principal atributo explicativo. Para la zona oeste se mira una mayor contirbución de las variables número de baños y precio por metro cuadrado. En la zona sur se encuentra el estrato; mientras que en la zona norte se evalúan las caraterísticas de habitaciones, longitud y latitud. Para la zona centro no se evidencia algun patron explicativo.

El ACP ofrece una visión preliminar sobre los atributos y su peso explicativo el sector de la vivienda de la ciudad de Cali. Sin embargo, dicho proceso se puede profundizar a través de un análisis de conglomerados.

Análisis de conglomerados

Los análisis de conglomerados es una técnica estadística que tiene como finalidad principal agrupar objetos similares en función de sus características. Este análisis permite agrupar objetos similares en un clúster, al tiempo que existan diferencias significativas en las otras agrupaciones. Para realizar el análisis de conglomerados, vamos a seleccionar las variables con las cuales vamos a trabajar. En este caso seleccionaremos las variables que se muestran a continuación:

vivienda_3 <- vivienda_limpia_2 [, c("zona", "estrato", "preciom", "areaconst", "habitaciones", "banios")]

vivienda_3$zona <- factor(vivienda_3$zona)
niveles <- levels(vivienda_3$zona)
vivienda_3$zona <- as.integer(vivienda_3$zona) # Asignar valores numéricos a cada nivel del factor
for (i in 1:length(niveles)) {
  cat("Categoría:", niveles[i], " - Valor numérico asignado:", i, "\n")
}
## Categoría: Zona Centro  - Valor numérico asignado: 1 
## Categoría: Zona Norte  - Valor numérico asignado: 2 
## Categoría: Zona Oeste  - Valor numérico asignado: 3 
## Categoría: Zona Oriente  - Valor numérico asignado: 4 
## Categoría: Zona Sur  - Valor numérico asignado: 5
head (vivienda_3)
## # A tibble: 6 × 6
##    zona estrato preciom areaconst habitaciones banios
##   <int>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>
## 1     4       3     250        70            6      3
## 2     4       3     320       120            3      2
## 3     4       3     350       220            4      2
## 4     5       4     400       280            3      5
## 5     2       5     260        90            3      2
## 6     2       5     240        87            3      3

Al realizar la transformación de los datos en el apartado anterior, proseguimos con la estandarización de los datos, con el objetivo de que su media sea cero y su desviación estandar 1.

vivienda_3_esc =scale(vivienda_3)
vivienda_3_esc = as.data.frame(vivienda_3_esc)
head(vivienda_3_esc)
##          zona    estrato    preciom  areaconst habitaciones      banios
## 1  0.06192582 -1.5872276 -0.5595498 -0.7339949    1.6406840 -0.07793773
## 2  0.06192582 -1.5872276 -0.3465670 -0.3842568   -0.4147626 -0.77811479
## 3  0.06192582 -1.5872276 -0.2552886  0.3152194    0.2703863 -0.77811479
## 4  0.81508501 -0.6156201 -0.1031580  0.7349051   -0.4147626  1.32241640
## 5 -1.44439256  0.3559875 -0.5291236 -0.5940997   -0.4147626 -0.77811479
## 6 -1.44439256  0.3559875 -0.5899759 -0.6150839   -0.4147626 -0.07793773

Al realizar las transformaciones previas procedemos a realizar los análisis necesarios para identificar el número de clústeres que se requieren en el presente análisis. Para ello, existen dos técnicas los métodos jerárquicos y no jerárquicos.

Métodos no jerárquicos

Para continuar con el análisis de los clústeres se procederá a evaluar diferentes métodos de agrupación de los datos con el objetivo de identificar el número más óptimo de clústeres de acuerdo a la dataframe que se tiene. Para ello comenzaremos con la construcción del siguiente gráfico

scv <- (nrow(vivienda_3_esc) - 1) * sum(apply(vivienda_3_esc, 2, var))
for (i in 2:15) scv[i] <- sum(kmeans(vivienda_3_esc,
                                     centers = i)$withinss)
plot(1:15, scv, 
     type = "b", 
     xlab = "Cantidad de Clusters",
     ylab="Suma de cuadrados dentro de grupos")

La anterior gráfica evidencia la cantidad de clústeres que se pueden realizar con la data disponible. Sin embargo, la anterior gráfica no permite esclarecer la cantidad de clústeres necesarios para el análisis. Debido a ello, procedemos a evaluar el número de clúster mediante el método del codo el cual se puede representar mediante la siguiente figura

fviz_nbclust(vivienda_3_esc, kmeans, method = "wss") + 
  geom_vline(xintercept = 4, linetype = 2) + labs (subtitle="Método del Codo")

La gráfica anterior, nos permite evidenciar que el número de clústeres necesarios para la explicación de la data disponible es de 4. Sin embargo, analizamos otras opciones que nos permitan tener una mayor seguridad y permitan hacer comparaciones sobre el número de clústeres necesarios para su selección. Así, continuamos con el método de la silueta a través de la siguiente gráfica

set.seed(123)  
wcss <- numeric(length = 10)  

for (i in 1:10) {
  kmeans_model <- kmeans(vivienda_3_esc, centers = i)
  wcss[i] <- kmeans_model$tot.withinss
}

plot(1:10, wcss, type = "b", pch = 19, frame = FALSE, xlab = "Número de Clusters", ylab = "WCSS")
abline(v = 3, col = "red", lty = 2) 

De acuerdo con la anterior gráfica se establece que el mejor número de clústeres es de 3. En virtud de lo anterior, procedemos a realizar la clusterización con K=3

set.seed(123)
kmeans_model<- kmeans(vivienda_3_esc, 3) 
vivienda_3_esc <- data.frame(vivienda_3_esc,
                             kmeans_model$cluster) 
aggregate(vivienda_3,
          by = list(vivienda_3_esc$kmeans_model.cluster),
          FUN = median)  
##   Group.1 zona estrato preciom areaconst habitaciones banios
## 1       1    5       6     750       300            4      5
## 2       2    5       4     252        92            3      2
## 3       3    2       5     300       103            3      2

Seguidamente, procedemos a indagar el número de registros que apareceran en cada uno de los clústeres para identificar cuántos datos existen en cada agrupación.

table(vivienda_3_esc$kmeans_model.cluster)
## 
##    1    2    3 
## 2243 3717 2359

A raíz de lo anterior, se logró reconocer que en el primer clúster existen un total de 2.668 registros, en el segundo 3.469 registros; mientras que en el tercero hay 2.189.

A partir de ello, se procede a realizar la gráfica de clúster

fviz_cluster(list(data = vivienda_3_esc[,1:6], 
                  cluster = vivienda_3_esc$kmeans_model.cluster),
             palette = c("#2E9FDF",  "#E7B800", "#FC4E07"),
             ellipse.type = "convex",repel = F, 
             show.clust.cent = FALSE, ggtheme = theme_minimal())

De acuerdo a la anterior gráfica, se logra evidenciar que efectivamente existen 3 clústeres claramente identificados porque se evidencia que el clúster 1 y 2 están apartados claramente, mientras que el clúster número 2 parace compartir algunos atributos similares con el número 3.

Debido a esta situación, se procede a evaluar el número de clústeres a través del método jerárquico para obtener una mejor comprensión sobre el tipo de agrupaciones que se deben realizar.

Métodos jerárquicos

El anterior epigrafe demosotró que de acuerdo a los métodos no jerarquicos el número óptimo de clústeres era 3. Sin embargo, podemos ratificar dicho proceso mediante la clusterización con el enfoque jerárquico. Para este procedimiento, comenzamos el proceso con el cálculo del índice de Sulhouette para valorar la major alternativa para la elección de los conglomerados.

library(cluster)
# distancia euclidiana
dist_vivi_k2 <- dist(vivienda_3_esc, method = 'euclidean')

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

# Determinamos a dónde pertenece cada observación
cluster_assigments_k2 <- cutree(hc_viv_k2, k = 2)

# Calcular el coeficiente de Silhouette
sil_k2 <- silhouette(cluster_assigments_k2, dist(vivienda_3_esc))
sil_avg_k2 <- mean(sil_k2[,3])

# Imprimir el coeficiente de Silhouette promedio
cat("Coeficiente de Silhouette promedio k=2 : ", sil_avg_k2)
## Coeficiente de Silhouette promedio k=2 :  0.6655843
# distancia euclidiana
dist_vivi_k3 <- dist(vivienda_3_esc, method = 'euclidean')

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

# Determinamos a dónde pertenece cada observación
cluster_assigments_k3 <- cutree(hc_viv_k3, k = 3)

# Calcular el coeficiente de Silhouette
sil_k3 <- silhouette(cluster_assigments_k3, dist(vivienda_3_esc))
sil_avg_k3 <- mean(sil_k3[,3])

# Imprimir el coeficiente de Silhouette promedio
cat("Coeficiente de Silhouette promedio k=3 : ", sil_avg_k3)
## Coeficiente de Silhouette promedio k=3 :  0.3590902
# distancia euclidiana
dist_vivi_k4 <- dist(vivienda_3_esc, method = 'euclidean')

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

# Determinamos a dónde pertenece cada observación
cluster_assigments_k4 <- cutree(hc_viv_k4, k = 4)

# Calcular el coeficiente de Silhouette
sil_k4 <- silhouette(cluster_assigments_k4, dist(vivienda_3_esc))
sil_avg_k4 <- mean(sil_k4[,3])

# Imprimir el coeficiente de Silhouette promedio
cat("Coeficiente de Silhouette promedio k=4 : ", sil_avg_k4)
## Coeficiente de Silhouette promedio k=4 :  0.3179575
# distancia euclidiana
dist_vivi_k5 <- dist(vivienda_3_esc, method = 'euclidean')

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

# Determinamos a dónde pertenece cada observación
cluster_assigments_k5 <- cutree(hc_viv_k5, k = 5)

# Calcular el coeficiente de Silhouette
sil_k5 <- silhouette(cluster_assigments_k5, dist(vivienda_3_esc))
sil_avg_k5 <- mean(sil_k5[,3])

# Imprimir el coeficiente de Silhouette promedio
cat("Coeficiente de Silhouette promedio k=5 : ", sil_avg_k5)
## Coeficiente de Silhouette promedio k=5 :  0.3091564

Teniendo en cuenta los resultados previos, se concluye que el mejor número de conglomerados para el presente ejercicio es 2. Para ello, procedemos a realizar la clústerización con K=2

set.seed(123)
kmeans_model_2<- kmeans(vivienda_3_esc, 2) 
vivienda_3_esc <- data.frame(vivienda_3_esc,
                             kmeans_model_2$cluster) 
aggregate(vivienda_3,
          by = list(vivienda_3_esc$kmeans_model_2.cluster),
          FUN = median)  
##   Group.1 zona estrato preciom areaconst habitaciones banios
## 1       1    5       6     750       300            4      5
## 2       2    5       4     270        96            3      2
table(vivienda_3_esc$kmeans_model_2.cluster)
## 
##    1    2 
## 2244 6075

Los anteriores resultados demuestran que el número de datos por conglomerados es de 2.668 para el primer grupo y de 5.651 para el segundo. A diferencia del conglomerado con tres clústeres, aqui se logra identificar una mayor presencia de de datos en el clúster número 2. Para continuar con el análisis procedemos a realizar su respectiva gráfica.

fviz_cluster(list(data = vivienda_3_esc[,1:6], 
                  cluster = vivienda_3_esc$kmeans_model_2.cluster),
             palette = c("#2E9FDF",  "#E7B800", "#FC4E07"),
             ellipse.type = "convex",repel = F, 
             show.clust.cent = FALSE, ggtheme = theme_minimal())

De acuerdo a la gráfica anterior, se logra observar un patron mucho más claro entre los clústeres 1 y 2. Sin embargo, para tener una visión más precisa es necesario realizar una evaluación adicional.

Por ello, procedemos a relizar el dentograma y continuamos con la valoración de datos atipicos en los cuatro clústeres de acuerdo a las variables más relevantes para el ejercicio.

# Calculamos la matriz de distancia
d <- dist(vivienda_3_esc, method = "euclidean")

# Clustering jerárquico con liga completa
hc1 <- hclust(d, method = "complete")

# Hacemos el dendrograma
plot(hc1, cex = 0.6, main = "Dendrograma de Viviendas", hang = -1, ylab = "Distancia Euclidiana", xlab = "Grupos")
rect.hclust(hc1, k = 3, border = 2:10)

# Calculamos la matriz de distancia
d2 <- dist(vivienda_3_esc, method = "euclidean")

# Clustering jerárquico con liga completa
hc2 <- hclust(d, method = "complete")

# Hacemos el dendrograma
plot(hc2, cex = 0.6, main = "Dendrograma de Viviendas 2", hang = -1, ylab = "Distancia Euclidiana", xlab = "Grupos")
rect.hclust(hc2, k = 2, border = 2:10)

Con los dendogramas analizados podemos evidenciar que con la agrupación de 3 clústeres se observan mejores relaciones que con 2 debido a que en el último los datos se distribuyen de una forma no tan normal. En relación con la elección de los clústeres es importante reconocer que no existe una fórmula única y que existen diferentes metodología para la selección de los clústeres. Por ello, es importante validar los diferentes métodos para obtener una visión más clara de lo que puede ocurrir. En ese orden de ideas, se concluye que la configuración de tres clústeres es mejor que la de dos, y por ello, se realizan los análisis finales con este número de clústeres.

En ese orden de ideas, procedemos a realizar un evaluación de los clústeres seleccionados de acuerdo con variables como precio, área construida, estrato y número de habitaciones.

set.seed(0)
clusterfinal <- kmeans(vivienda_3, center=3, nstart = 20)

vivienda_3_esc %>% 
ggplot(aes(x = factor(clusterfinal$cluster), y = preciom, fill = factor(clusterfinal$cluster))) + geom_boxplot() + geom_point() + xlab("Cluster") + labs(fill="Cluster")

En la anterior gráfica, logramos visualizar que en el conglomerado número 1 se presentan precios bajos y existe una menor dispersión en los valores de la vivienda. En un segundo momento, se evidencia que en el clúster tres los precios son superiores, se presenran más datos atípicos y existe mayor variabilidad de los datos. Finalmente, en el clúster número dos presenta los precios más altos con mayor dispersión y mayor presencia de datos atipicos sobre el outlier superior, lo cual indica que en este clúster existen gran cantidad de vivienda con precios muy distantes a la media.

vivienda_3_esc %>% 
ggplot(aes(x = factor(clusterfinal$cluster), y = areaconst, fill = factor(clusterfinal$cluster))) + geom_boxplot() + geom_point() + xlab("Cluster") + labs(fill="Cluster")

En relación con el área construida, se evidencia que en los tres clústeres se presenta menor dispersión de datos. Nuevamente, en el clúster dos se encuentran las áreas construidas con un media superior pero gran cantidad de datos atípicos. En el tercer clúster se evidencia una menor media en el área de construcción pero también se reconoce que existen muchas observaciones por encima del outlier superior indicando que existen vivienda con área que distan mucha de la media en este clúster.

vivienda_3_esc %>% 
ggplot(aes(x = factor(clusterfinal$cluster), y = estrato, fill = factor(clusterfinal$cluster))) + geom_boxplot() + geom_point() + xlab("Cluster") + labs(fill="Cluster")

En la gráfica anterior, se observa que los los clústeres 1 y 3 presentan un comportamiento dentro de lo esperado con una dispersión de datos alta. Sin embargo, en el clúster 2 se reconoce que existen muchos datos atípicos que no permiten visualizar de forma clara la caja del mismo.

vivienda_3_esc %>% 
ggplot(aes(x = factor(clusterfinal$cluster), y = habitaciones, fill = factor(clusterfinal$cluster))) + geom_boxplot() + geom_point() + xlab("Cluster") + labs(fill="Cluster")

En relación con el número de habitaciones se evidencia que el clúster 1 es el que presenta menor número de ellas; mientras que los clústeres dos y tres presentan mayor número de habitaciones. En los tres clústeres se evidencia datos atípicos.

vivienda_3_esc %>% 
ggplot(aes(x = factor(clusterfinal$cluster), y = banios, fill = factor(clusterfinal$cluster))) + geom_boxplot() + geom_point() + xlab("Cluster") + labs(fill="Cluster")

Finalmente, en el análisis del número de baños se identificó al clúster número dos como aquel que presenta mayor número de estos. Seguido del clúster tres y finalizando con el número uno.

Análisis de correspondencia

El análisis de correspondencia es un método de análisis estadístico para explorar y visualizar relaciones entre variables categóricas. Por ello, para este análisis se procede a realizar entre las variables tipo y zona.

tabla <- table(vivienda$zona, vivienda$estrato)
tabla
##               
##                   3    4    5    6
##   Zona Centro   105   14    4    1
##   Zona Norte    572  407  769  172
##   Zona Oeste     54   84  290  770
##   Zona Oriente  340    8    2    1
##   Zona Sur      382 1616 1685 1043

Procedemos a valorar la independencia de las variables a través del método de chi -cuadrado.

chisq.test(tabla)
## 
##  Pearson's Chi-squared test
## 
## data:  tabla
## X-squared = 3830.4, df = 12, p-value < 2.2e-16

El resultado anterior indica que se rechaza la hipótesis nula (p valor: 0.0000), lo cual indica que existe una relación entre las variables analizadas. Luego de realizar dicho procedemiento, procedemos a generar el análisis de correspondencia donde se estiman las coordenadas para cada uno de los niveles de las dos variables analizadas y representarlas en un plano cartesiano.

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

resultados_ac <- CA(tabla)

En la anterior gráfica, se logran evidenciar patrones claros de comportamiento. En primer lugar se reconoce que el estrato seis se encuentra ubicado en la Zona Oeste de la ciudad de Cali; mientras que en la Zona sur están los estratos 4 y 5. Así mismo, el estrato tres se ubica principalmente en la zona centro y oriente.

valores_prop <-resultados_ac$eig ; valores_prop
##       eigenvalue percentage of variance cumulative percentage of variance
## dim 1 0.32215213              69.965515                          69.96551
## dim 2 0.12745096              27.680002                          97.64552
## dim 3 0.01084108               2.354483                         100.00000
fviz_screeplot(resultados_ac, addlabels = TRUE, ylim = c(0, 80))+ggtitle("")+
ylab("Porcentaje de varianza explicado") + xlab("Ejes")

La gráfica anterior, permite reconoce que en los patrones encontrados previamente, se evidencia que el primer componente explica el 70% de los cambios y sumados con el segundo se explica el 97.7% de los cambios del dataset. A raíz de ello, se concluye que en la relación entre estrato y precio existe una alto grado de certeza que permitan explicar las conclusiones extraidad de la gráfica de CA factor.

Finalmente, procedemos a realizar un análisis entre la zona y el precio de la siguiente manera.

library(dplyr)
q1 <- quantile(vivienda_limpia_2$preciom, c(0.25), type = 6)
q2 <- quantile(vivienda_limpia_2$preciom, c(0.50), type = 6)
q3 <- quantile(vivienda_limpia_2$preciom, c(0.75), type = 6)

datacorresp <- vivienda_limpia_2 %>% mutate(value = ifelse(preciom <= q1, "bajo", ifelse(preciom <= q2,"medio bajo",ifelse(preciom <= q3,"medio alto","alto"))))

tabla2 <- table(datacorresp$zona, datacorresp$value)

chisq.test(tabla2)
## 
##  Pearson's Chi-squared test
## 
## data:  tabla2
## X-squared = 1137.6, df = 12, p-value < 2.2e-16

El resultado anterior indica que las variables no son independiente, con lo cual podemos continuar con el proceso análitico.

resultados_ac_2 <- CA(tabla2)

En la gráfica anterior, se logra reconocer que los ingresos medio alto y alto se encuentran en la zona oeste, afirmación que se fortalece con la situación del estrato. En la zona sur y centro se encuentran los ingresos medio bajo que también encuentra correlación con los datos obtenidos en el gráfico de estrato. Finalmente, en la zona norte y oriente se encuentran las vivienda de precio bajo.

fviz_screeplot(resultados_ac_2, addlabels = TRUE, ylim = c(0, 80))+ggtitle("")+
ylab("Porcentaje de varianza explicado") + xlab("Ejes")

La gráfica anterior explica el nivel de varianza del análisis precio-zona indicando que en los dos primeros componente se explica más del 98% de las variaciones.

Conclusiones

Luego de realizar el análisis de componentes, conglomerados y de correspondencia del mercado inmobiliario en Cali se logró encontrar una serie de conclusiones importantes entre los cuales se destacan: 1. Las variables precio, habitaciones, número de baños, área construida y zona son los atributos con mayor explicación y varianza en la base de dato. Estas variables permitirán realizar análisis a profunidad para establecer patrones sobre los comportamiento de la vivienda en la ciudad. 2. Al realizar el análisis de conglomerados se evidencia la necesidad de agrupar los datos en tres clústeres que indican una mejor representación de los datos, una mejor agrupación y un mejor balanceo de los mismos. En este análisis también se analizó algunas características de los clústeres con algunas variables de interés. 3. El análisis de correspondencia evidencia relaciones claras y coherentes entre variables como la zona, el precio y el estrato. Estas relaciones son contudentes y con un alto grado de explicación de la varianza.