Problema

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

Instalación y llamado de librerías necesarias

# install.packages("learnr")          # solo una vez
# install.packages("devtools")     # solo una vez
# install.packages("ade4")
# devtools::install_github("dgonxalex80/paqueteMODELOS") #descarga paquete 

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
library('tidyverse')
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.3     ✔ readr     2.1.4
## ✔ forcats   1.0.0     ✔ stringr   1.5.0
## ✔ lubridate 1.9.3     ✔ tibble    3.2.1
## ✔ purrr     1.0.2     ✔ tidyr     1.3.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::combine() masks gridExtra::combine()
## ✖ dplyr::filter()  masks 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
library('corrplot')
## corrplot 0.92 loaded
library("cluster")
library("dendextend")
## 
## ---------------------
## Welcome to dendextend version 1.17.1
## Type citation('dendextend') for how to cite the package.
## 
## Type browseVignettes(package = 'dendextend') for the package vignette.
## The github page is: https://github.com/talgalili/dendextend/
## 
## Suggestions and bug-reports can be submitted at: https://github.com/talgalili/dendextend/issues
## You may ask questions at stackoverflow, use the r and dendextend tags: 
##   https://stackoverflow.com/questions/tagged/dendextend
## 
##  To suppress this message use:  suppressPackageStartupMessages(library(dendextend))
## ---------------------
## 
## 
## Attaching package: 'dendextend'
## 
## The following object is masked from 'package:stats':
## 
##     cutree
library("factoextra")
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
library("ggplot2")
library(psych)
## 
## Attaching package: 'psych'
## 
## The following objects are masked from 'package:ggplot2':
## 
##     %+%, alpha
## 
## The following object is masked from 'package:boot':
## 
##     logit
library(factoextra)
library(cluster)
library(FactoMineR)
library(factoextra)
library(gridExtra)

Carga de base de datos

data(vivienda)
head(vivienda)
## # A tibble: 6 × 13
##      id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
##   <dbl> <chr>   <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
## 1  1147 Zona O… <NA>        3     250        70            1      3            6
## 2  1169 Zona O… <NA>        3     320       120            1      2            3
## 3  1350 Zona O… <NA>        3     350       220            2      2            4
## 4  5992 Zona S… 02          4     400       280            3      5            3
## 5  1212 Zona N… 01          5     260        90            1      2            3
## 6  1724 Zona N… 01          5     240        87            1      3            3
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>

Entendimiento y limpieza de base de datos

Inicialmente, debemos hacer una limpieza de la base de datos, en la que eliminaremos las columnas innecesarias para el objetivo del estudio, revisaremos el tipo de datos que contiene cada columna y de ser necesario, se acomodará.

Primero, se eliminan las variables id, barrio, latitud y longitud ya que no se consideran relevantes en el análisis propuesto en esta actividad:

vivienda <- vivienda[,c(-1, -11, -12, -13)]
head(vivienda)
## # A tibble: 6 × 9
##   zona    piso  estrato preciom areaconst parqueaderos banios habitaciones tipo 
##   <chr>   <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl> <chr>
## 1 Zona O… <NA>        3     250        70            1      3            6 Casa 
## 2 Zona O… <NA>        3     320       120            1      2            3 Casa 
## 3 Zona O… <NA>        3     350       220            2      2            4 Casa 
## 4 Zona S… 02          4     400       280            3      5            3 Casa 
## 5 Zona N… 01          5     260        90            1      2            3 Apar…
## 6 Zona N… 01          5     240        87            1      3            3 Apar…
str(vivienda)
## tibble [8,322 × 9] (S3: tbl_df/tbl/data.frame)
##  $ 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" ...

se observa que el tipo de variables es el adecuado en cada columna, por lo que no se considera necesario hacer algún ajuste.

A continuación, evaluaremos la cantidad de datos faltantes presentes en la base y se tomará la decisión de cómo darle manejo a este tipo de datos:

colSums(is.na(vivienda)) # Número de datos faltantes por variable
##         zona         piso      estrato      preciom    areaconst parqueaderos 
##            3         2638            3            2            3         1605 
##       banios habitaciones         tipo 
##            3            3            3

La variable piso y parqueaderos tienen un gran número de datos faltantes, por lo que se propone eliminar “piso” del dataset (para efectos prácticos) pero en el caso de parqueaderos, notamos que el valor minimo que toma es 1, por lo tanto se puede asumir que los datos faltantes en esta variable corresponden a las viviendas que no tienen parqueaderos y por tanto se puede imputar por cero.

vivienda <- vivienda[,c(-2)]
vivienda$parqueaderos[is.na(vivienda$parqueaderos)] <-0 #se imputa el dato ausente por cero en la variable parqueaderos

Dado que la cantidad de datos ausente por variable no supera 3 datos, de 8322, podemos eliminar las filas que presenten estos datos y así tratar de no introducir mucho ruido en la base.

vivienda <- na.omit(vivienda)

colSums(is.na(vivienda)) # verificamos el número de datos faltantes 
##         zona      estrato      preciom    areaconst parqueaderos       banios 
##            0            0            0            0            0            0 
## habitaciones         tipo 
##            0            0
nrow(vivienda) # Número de datos resultantes al quitar los faltantes
## [1] 8319

Análisis descriptivo de la base

summarytools::descr(vivienda)
## Non-numerical variable(s) ignored: zona, tipo
## Descriptive Statistics  
## vivienda  
## N: 8319  
## 
##                     areaconst    banios   estrato   habitaciones   parqueaderos   preciom
## ----------------- ----------- --------- --------- -------------- -------------- ---------
##              Mean      174.93      3.11      4.63           3.61           1.48    433.90
##           Std.Dev      142.96      1.43      1.03           1.46           1.24    328.67
##               Min       30.00      0.00      3.00           0.00           0.00     58.00
##                Q1       80.00      2.00      4.00           3.00           1.00    220.00
##            Median      123.00      3.00      5.00           3.00           1.00    330.00
##                Q3      229.00      4.00      5.00           4.00           2.00    540.00
##               Max     1745.00     10.00      6.00          10.00          10.00   1999.00
##               MAD       84.51      1.48      1.48           1.48           1.48    207.56
##               IQR      149.00      2.00      1.00           1.00           1.00    320.00
##                CV        0.82      0.46      0.22           0.40           0.84      0.76
##          Skewness        2.69      0.93     -0.18           1.63           1.65      1.85
##       SE.Skewness        0.03      0.03      0.03           0.03           0.03      0.03
##          Kurtosis       12.91      1.13     -1.11           3.98           5.42      3.67
##           N.Valid     8319.00   8319.00   8319.00        8319.00        8319.00   8319.00
##         Pct.Valid      100.00    100.00    100.00         100.00         100.00    100.00
table(vivienda$zona)
## 
##  Zona Centro   Zona Norte   Zona Oeste Zona Oriente     Zona Sur 
##          124         1920         1198          351         4726
table(vivienda$tipo)
## 
## Apartamento        Casa 
##        5100        3219

El estrato oscila entre 3 y 6, siendo estratos medios y altos los que contempla esta fuente de información, el precio va entre 58 y casi 2 mil millones de pesos, siendo en promedio 434 millones, el 75% de las viviendas tienen 229 metros cuadrados o menos de construidas, cuentan con 2 parqueaderos, 4 baños y 4 habitaciones. Encontramos un mayor número de viviendas ubicadas al sur y norte de la ciudad de la ciudad y la mayoría son de tipo apartamento.

table(vivienda$zona, vivienda$estrato) %>% 
  prop.table(margin = 1)*100
##               
##                         3          4          5          6
##   Zona Centro  84.6774194 11.2903226  3.2258065  0.8064516
##   Zona Norte   29.7916667 21.1979167 40.0520833  8.9583333
##   Zona Oeste    4.5075125  7.0116861 24.2070117 64.2737896
##   Zona Oriente 96.8660969  2.2792023  0.5698006  0.2849003
##   Zona Sur      8.0829454 34.1938214 35.6538299 22.0694033

El \(85\%\) de las viviendas en la zona centro son de estrato 3, mientras que en la zona norte tenemos mayoría de viviendas de estrato 5 pero tambien una participación importante en el estraro 3 y 4. En el oeste prima el estrato 6 mientras que casi la totalidad de viviendas de la zona oriente son de estrato 6. Finalmente el sur tiene casi que igual participación de viviendas de estrato 4 y 5, seguidos del estrato 6.

x <- vivienda[c("tipo","zona")]
conteo <- table(x$tipo,x$zona)

barplot(conteo, main="Número de viviendas por zona y tipo",  
        ylab="Número de viviendas",
        col=c("#0d3b66","#f4d35e"),
        legend = rownames(conteo),
        las=2, 
        names.arg=c("Centro","Norte","Oeste","Oriente","Sur"),
        args.legend = list(x = "top"))

Frente al tipo de vivienda, se aprecia que el centro tiene la minoría de viviendas, pero casi en su totalidad son de tipo casa. En la zona sur y norte es equitativo la proporción de casas y apartamentos, mientras que en el oeste en su gran mayoría son de apartamentos, contrario al oriente que priman las casas.

par(mfrow=c(3,2))

boxplot(vivienda$habitaciones~vivienda$zona,
        main = "Distribución habitaciones por zona",
        ylab="habitaciones",
        xlab = "Zona", las=2,
        col=c("burlywood1","#ee964b", "cadetblue1", "pink", "darkolivegreen1"),
        names = c("Centro","Norte","Oeste", "Oriente", "Sur"))

boxplot(vivienda$banios~vivienda$zona,
        main = "Distribución baños por zona",
        ylab="baños",
        xlab = "Zona", las=2,
        col=c("burlywood1","#ee964b", "cadetblue1", "pink", "darkolivegreen1"),
        names = c("Centro","Norte","Oeste", "Oriente", "Sur"))


boxplot(vivienda$parqueaderos ~ vivienda$zona,
        main = "Distribución parqueaderos por zona",
        ylab="parqueaderos",
        xlab = "Zona", las=2,
        col=c("burlywood1","#ee964b", "cadetblue1", "pink", "darkolivegreen1"),
        names = c("Centro","Norte","Oeste", "Oriente", "Sur"))

###

boxplot(vivienda$habitaciones~vivienda$tipo,
        main = "Distribución habitaciones por tipo",
        ylab="habitaciones",
        xlab = "tipo", las=2,
        col=c("burlywood1", "darkolivegreen1"),
        names = c("Apartamento","Casa"))

boxplot(vivienda$banios~vivienda$tipo,
        main = "Distribución baños por tipo",
        ylab="baños",
        xlab = "tipo", las=2,
        col=c("burlywood1", "darkolivegreen1"),
        names = c("Apartamento","Casa"))


boxplot(vivienda$parqueaderos ~ vivienda$tipo,
        main = "Distribución parqueaderos por tipo",
        ylab="parqueaderos",
        xlab = "tipo", las=2,
        col=c("burlywood1", "darkolivegreen1"),
        names = c("Apartamento","Sur"))

Finalmente, podemos concluir que el número de habitaciones de las viviendas en la zona norte, oeste y sur tienen una distribución similar y esto se debe a que en su mayoría son de tipo apartamento, donde priman entre 2 y 4 habitaciones, mientras que las zonas cuyo nivel de participación de casas es mayor, tienen mas habitaciones, oscilando entre 3 y 5 para el centro y 4 y 7 para el oriente. Al evaluar el número de baños, observamos que las viviendas en la zona norte ti tienen menor número de barrios que las otras zonas, mientras que el oeste tiene un comportamiento similar al observado en las zonas con más casas.

En el centro y oriente es menor la disponibilidad de parqueaderos, teniendo que el \(25\%\) de las viviendas no tienen este beneficio, mientras que en el oeste es común encontrar apartamentos con al menos un parqueadero disponible.

Al comparar las variables anteriores por tipo de vivienda, es evidente que las casas tienen mayor número de habitaciones, baños y paqueadero disponible y esto se puede intuir que es porque tienen mayor espacio construído que los apartamentos.

Análisis de correlaciones.

# Correlaciones
library(corrplot)

str(vivienda)
## tibble [8,319 × 8] (S3: tbl_df/tbl/data.frame)
##  $ zona        : chr [1:8319] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
##  $ estrato     : num [1:8319] 3 3 3 4 5 5 4 5 5 5 ...
##  $ preciom     : num [1:8319] 250 320 350 400 260 240 220 310 320 780 ...
##  $ areaconst   : num [1:8319] 70 120 220 280 90 87 52 137 150 380 ...
##  $ parqueaderos: num [1:8319] 1 1 2 3 1 1 2 2 2 2 ...
##  $ banios      : num [1:8319] 3 2 2 5 2 3 2 3 4 3 ...
##  $ habitaciones: num [1:8319] 6 3 4 3 3 3 3 4 6 3 ...
##  $ tipo        : chr [1:8319] "Casa" "Casa" "Casa" "Casa" ...
##  - attr(*, "na.action")= 'omit' Named int [1:3] 8320 8321 8322
##   ..- attr(*, "names")= chr [1:3] "8320" "8321" "8322"
num <- vivienda[,c(-1, -8)]
str(num)
## tibble [8,319 × 6] (S3: tbl_df/tbl/data.frame)
##  $ estrato     : num [1:8319] 3 3 3 4 5 5 4 5 5 5 ...
##  $ preciom     : num [1:8319] 250 320 350 400 260 240 220 310 320 780 ...
##  $ areaconst   : num [1:8319] 70 120 220 280 90 87 52 137 150 380 ...
##  $ parqueaderos: num [1:8319] 1 1 2 3 1 1 2 2 2 2 ...
##  $ banios      : num [1:8319] 3 2 2 5 2 3 2 3 4 3 ...
##  $ habitaciones: num [1:8319] 6 3 4 3 3 3 3 4 6 3 ...
##  - attr(*, "na.action")= 'omit' Named int [1:3] 8320 8321 8322
##   ..- attr(*, "names")= chr [1:3] "8320" "8321" "8322"
corrplot(cor(num, use = "complete.obs", method="spearman"), method = "color", addCoef.col="grey", order = "AOE")

Dado que las variables númericas son discretas, se realiza el análisis de correlaciones con el metodo de Spearman, donde es evidente el alto nivel de correlación entre el precio de la vivienda vs el estrato, el número de parqueaderos con el estrato y el precio. el número de baños con el precio y claramente el precio con el area construida. Por otra parte, el estraro y el número de habitaciones no tienen relacion, al igual que el número de parqueaderos con el número de habitaciones.

Análisis de Componentes Principales - ACP

El objetivo del análisis, es reducir la dimensionalidad del conjunto de datos y visualizar la estructura de las variables en componentes principales para identificar características clave que influyen en la variación de precios y oferta del mercado.

El Análisis de Componentes Principales, en adelante ACP, se realiza sobre las variables númericas mencionadas a continuación:

head(num)
## # A tibble: 6 × 6
##   estrato preciom areaconst parqueaderos banios habitaciones
##     <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
## 1       3     250        70            1      3            6
## 2       3     320       120            1      2            3
## 3       3     350       220            2      2            4
## 4       4     400       280            3      5            3
## 5       5     260        90            1      2            3
## 6       5     240        87            1      3            3

Sin embargo, antes de realizar el ACP, evaluamos la viabilidad de aplicarlo; para ello usamos el test Kaiser-Meyer-Olkin (KMO) que evalúa la proporción de varianza en las variables que puede ser explicada por factores subyacentes. Valores altos cercanos a 1.0 sugieren que un análisis factorial podría ser beneficioso con los datos disponibles. Por otro lado, valores por debajo de 0.50 indican que los resultados del análisis factorial probablemente no serán significativos o útiles.

KMO(num)
## Kaiser-Meyer-Olkin factor adequacy
## Call: KMO(r = num)
## Overall MSA =  0.77
## MSA for each item = 
##      estrato      preciom    areaconst parqueaderos       banios habitaciones 
##         0.71         0.76         0.80         0.91         0.79         0.60

De acuerdo al KMO, es util realizar el ACP, además todas las variables tienen un indice alto, por lo que todas se pueden considerar en el análisis.

Usamos tambien el test de Bartlett cuya hipótesis es que la matriz de correlaciones es una matriz de identidad, lo que indicaría que las variables no están relacionadas y, por lo tanto, no son adecuadas para la detección de componentes.

cortest.bartlett(cor(num),n=1819)
## $chisq
## [1] 5873.168
## 
## $p.value
## [1] 0
## 
## $df
## [1] 15

Como resultado, se rechaza la hipotesis nula, por lo tanto es util hacer el ACP

Dado lo anterior, procedemos con la construcción del ACP, pero antes, debemos escalar las variables para llevarlas todas a un mismo rango y permitir que sean comparables entre ellas:

### Escalar las variables
numZ= num[1:6] %>%
  scale()
head(numZ) 
##         estrato    preciom  areaconst parqueaderos      banios habitaciones
## [1,] -1.5872276 -0.5595498 -0.7339949   -0.3875522 -0.07793773    1.6406840
## [2,] -1.5872276 -0.3465670 -0.3842568   -0.3875522 -0.77811479   -0.4147626
## [3,] -1.5872276 -0.2552886  0.3152194    0.4168506 -0.77811479    0.2703863
## [4,] -0.6156201 -0.1031580  0.7349051    1.2212534  1.32241640   -0.4147626
## [5,]  0.3559875 -0.5291236 -0.5940997   -0.3875522 -0.77811479   -0.4147626
## [6,]  0.3559875 -0.5899759 -0.6150839   -0.3875522 -0.07793773   -0.4147626
summary(numZ)
##     estrato           preciom          areaconst        parqueaderos    
##  Min.   :-1.5872   Min.   :-1.1437   Min.   :-1.0138   Min.   :-1.1920  
##  1st Qu.:-0.6156   1st Qu.:-0.6508   1st Qu.:-0.6640   1st Qu.:-0.3876  
##  Median : 0.3560   Median :-0.3161   Median :-0.3633   Median :-0.3876  
##  Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000  
##  3rd Qu.: 0.3560   3rd Qu.: 0.3228   3rd Qu.: 0.3782   3rd Qu.: 0.4169  
##  Max.   : 1.3276   Max.   : 4.7620   Max.   :10.9822   Max.   : 6.8521  
##      banios          habitaciones    
##  Min.   :-2.17847   Min.   :-2.4702  
##  1st Qu.:-0.77812   1st Qu.:-0.4148  
##  Median :-0.07794   Median :-0.4148  
##  Mean   : 0.00000   Mean   : 0.0000  
##  3rd Qu.: 0.62224   3rd Qu.: 0.2704  
##  Max.   : 4.82330   Max.   : 4.3813
acp <- prcomp(numZ)

A continuación, se escoge el número de componentes adecuado:

## Elección del numero de componentes

fviz_eig(acp, addlabels = TRUE)

summary(acp)
## Importance of components:
##                           PC1    PC2    PC3     PC4     PC5     PC6
## Standard deviation     1.8481 1.1222 0.6780 0.65924 0.49098 0.43579
## Proportion of Variance 0.5692 0.2099 0.0766 0.07243 0.04018 0.03165
## Cumulative Proportion  0.5692 0.7791 0.8557 0.92817 0.96835 1.00000

Aquí, se logra apreciar que con las dos primeras componentes se logra explicar casi el \(78\%\) de la varianza, por lo tanto se escoge este número de componentes. Ahora, se pretende explicar la contribución que tienen las variables en la construcción de los ejes:

fviz_pca_var(acp,
             col.var = "contrib", # Color by contributions to the PC
             gradient.cols = c("#FF7F00",  "#034D94"),
             repel = TRUE     # Avoid text overlapping
)

# Contribución a los ejes
PC1<-acp[[2]][,1]
PC2<-acp[[2]][,2]

Componentes<-cbind(PC1,PC2)
head(Componentes)
##                    PC1        PC2
## estrato      0.3306904 -0.5816950
## preciom      0.4781754 -0.1883533
## areaconst    0.4417250  0.2388377
## parqueaderos 0.4096354 -0.2607395
## banios       0.4668745  0.1921461
## habitaciones 0.2847775  0.6813261
contrib<-Componentes*Componentes
contrib <- as.matrix(contrib)
corrplot(contrib,is.corr=FALSE)

Gráfica y numéricamente se puede observar que la primera componente tiene mayor contibución de las variables precio, area construída, parqueaderos y baños, mientras que la segunda componente la componen el estrato (de manera inversa) y el número de habitaciones. Además, se confirma la relación existente entre el area y el número de baños y el precio con el número de parqueaderos. Tambien se confirma la poca relación entre el número de habitaciones y el estrato al que pertenece la vivienda.

Podemos concluir que en la primera componente encontramos viviendas de areas grandes, con varios baños y disponibilidad de parqueaderos, pero con precios elevados, mientras que en la segunda componente la caracteristica principal es que tienen varias habitaciones pero pertenecen a estratos 3, por lo que se deduce que está representado por viviendas ubicadas en la zona centro, cuyas caracteristicas cumplen con la descripción mencionada.

Análisis de conglomerados.

El objetivo de este análisis, es agrupar las propiedades residenciales en segmentos homogéneos con características similares para entender las dinámicas de las ofertas específicas en diferentes partes de la ciudad y en diferentes estratos socioeconómicos.

Nuevamente iniciamos escalando la matriz de modo que tengan media cero y desviación estándar 1:

## Estandarizamos los datos de tal modo que quedan con media cero y sd 1

numS <- num %>% mutate_all(~(scale(.) %>% as.vector))
summarytools::descr(numS)
## Descriptive Statistics  
## numS  
## N: 8319  
## 
##                                areaconst                 banios                estrato
## ----------------- ---------------------- ---------------------- ----------------------
##              Mean                   0.00                   0.00                   0.00
##           Std.Dev                   1.00                   1.00                   1.00
##               Min                  -1.01                  -2.18                  -1.59
##                Q1                  -0.66                  -0.78                  -0.62
##            Median                  -0.36                  -0.08                   0.36
##                Q3                   0.38                   0.62                   0.36
##               Max                  10.98                   4.82                   1.33
##               MAD                   0.59                   1.04                   1.44
##               IQR                   1.04                   1.40                   0.97
##                CV   -9930211125493004.00   -4761446946698595.00   -1868927009707192.50
##          Skewness                   2.69                   0.93                  -0.18
##       SE.Skewness                   0.03                   0.03                   0.03
##          Kurtosis                  12.91                   1.13                  -1.11
##           N.Valid                8319.00                8319.00                8319.00
##         Pct.Valid                 100.00                 100.00                 100.00
## 
## Table: Table continues below
## 
##  
## 
##                            habitaciones         parqueaderos               preciom
## ----------------- --------------------- -------------------- ---------------------
##              Mean                  0.00                 0.00                  0.00
##           Std.Dev                  1.00                 1.00                  1.00
##               Min                 -2.47                -1.19                 -1.14
##                Q1                 -0.41                -0.39                 -0.65
##            Median                 -0.41                -0.39                 -0.32
##                Q3                  0.27                 0.42                  0.32
##               Max                  4.38                 6.85                  4.76
##               MAD                  1.02                 1.19                  0.63
##               IQR                  0.69                 0.80                  0.97
##                CV   -702626407488378.88   717701339031022.62   7975613688152231.00
##          Skewness                  1.63                 1.65                  1.85
##       SE.Skewness                  0.03                 0.03                  0.03
##          Kurtosis                  3.98                 5.42                  3.67
##           N.Valid               8319.00              8319.00               8319.00
##         Pct.Valid                100.00               100.00                100.00

Inicialmente, evaluaremos un método jerárquico. Para ello, calculamos la matriz de distancias d:

# distancia euclidiana
d <- dist(numS, method = 'euclidean')

A continuación, llevamos a cabo el agrupamiento y se evalúa el número de cluster apropiados:

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

# Elección de numero de cluster

barplot(sort(hc_num$height, decreasing = TRUE), horiz = TRUE, 
        main = "Agregaciones (distancias euclidianas)",
        col = "lightblue", ylab = "Nodo", xlab = "Peso", xlim = c(0, 2.5))

Asumiendo 4 cluster, se evalúa la distribución de los clúster de manera gráfica:

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

# asignamos los clusters
cl <- numS %>% mutate(cluster = as.factor(cluster_assigments))

# Observamos los individuos por cluster
fviz_cluster(list(data= num, cluster=cluster_assigments))

Se puede evidenciar que, dado el volumen de datos, el método jerarquico puede presentar algunas restricciones computacionales, como no poder gráficar el dendograma, además los cluster no presentan una separación clara, al menos gráficamente. Por lo anterior, evaluamos otro método de agrupamiento, en este caso el K-Means:

K-Means

Primero evaluamos el número optimo de cluster:

### K means

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

De la gráfica, podemos concluir que se puede ajustar el k-means con 4 clúster:

set.seed(1995)
km.res <- kmeans(numS, 4)

## Vemos el cluster

fviz_cluster(km.res, data = num,
             palette = c("#2E9FDF", "#00AFBB", "#E7B800", "#FC4E07"),
             ggtheme = theme_minimal()
)

Al comparar el gráfico de los cluster obtenidos en el k-means vs el jerárquico, se observa una mejor partición y agrupamiento de los datos, sin embargo, el coeficiente de silueta es de solo 0.3.

## Coeficiente de silueta
library(cluster)

sil <- silhouette(km.res$cluster, dist(numS))
sil_avg <- mean(sil[,3])
sil_avg
## [1] 0.3033401

No obstante se procede a describir estos grupos:

# Tamaño de los cluster
km.res$size
## [1] 3537  881 2895 1006
# Puntos medios de agrupación
km.res$centers
##      estrato     preciom  areaconst parqueaderos     banios habitaciones
## 1 -0.6906127 -0.67653762 -0.5956852   -0.5983753 -0.7725720   -0.4974763
## 2 -0.7170819  0.05693307  0.8877774   -0.2350717  0.9830570    2.0287536
## 3  0.6899251  0.10023766 -0.0939632    0.2101233  0.1687568   -0.1757296
## 4  1.0706888  2.04032552  1.5873063    1.7050146  1.3697445    0.4781104

Para tener una descripción mas clara, se evaluan las demás variables que contiene la base total:

df <- cbind(vivienda, cluster = km.res$cluster)
head(df)
##           zona estrato preciom areaconst parqueaderos banios habitaciones
## 1 Zona Oriente       3     250        70            1      3            6
## 2 Zona Oriente       3     320       120            1      2            3
## 3 Zona Oriente       3     350       220            2      2            4
## 4     Zona Sur       4     400       280            3      5            3
## 5   Zona Norte       5     260        90            1      2            3
## 6   Zona Norte       5     240        87            1      3            3
##          tipo cluster
## 1        Casa       2
## 2        Casa       1
## 3        Casa       1
## 4        Casa       3
## 5 Apartamento       1
## 6 Apartamento       3
table(df$cluster)
## 
##    1    2    3    4 
## 3537  881 2895 1006
x <- df[c("tipo","cluster")]
conteo <- table(x$tipo,x$cluster)

barplot(conteo, main="Número de viviendas por tipo y cluster",  
        ylab="Número de viviendas",
        col=c("#0d3b66","#f4d35e"),
        legend = rownames(conteo),
        las=2, 
        names.arg=c("CL1","CL2","CL3","CL4"),
        args.legend = list(x = "top"))

Se evidencia que el clusteer 2 y 4 están representados mayormente por viviendas tipo casa, mientras que los cluster 1 y 3 son viviendas de tipo apartamento.

x <- df[c("zona","cluster")]
conteo <- table(x$zona,x$cluster)
#View(conteo)

barplot(conteo, main="Número de viviendas por zona y cluster",  
        ylab="Número de viviendas",
        col=c("#2E9FDF", "#00AFBB", "#E7B800", "#FC4E07", "#0d3b66"),
        legend = rownames(conteo),
        las=2, 
        names.arg=c("CL1","CL2","CL3","CL4"),
        args.legend = list(x = "top"))

Los cluster 3 y 4 contienen viviendas de las zonas de mayor estrato: sur, oeste y norte, mientras que los cluster 1 y 2 contienen viviendas de todos los estratos.

par(mfrow=c(3,2))

boxplot(df$habitaciones~df$cluster,
        main = "Distribución habitaciones por cluster",
        ylab="habitaciones",
        xlab = "Cluster", las=2,
        col=c("#ee964b", "cadetblue1", "pink", "darkolivegreen1"),
        names = c("CL1","CL2","CL3","CL4"))

boxplot(df$banios~df$cluster,
        main = "Distribución baños por cluster",
        ylab="baños",
        xlab = "Cluster", las=2,
        col=c("#ee964b", "cadetblue1", "pink", "darkolivegreen1"),
        names = c("CL1","CL2","CL3","CL4"))


boxplot(df$parqueaderos ~ df$cluster,
        main = "Distribución parqueaderos por cluster",
        ylab="parqueaderos",
        xlab = "Cluster", las=2,
        col=c("#ee964b", "cadetblue1", "pink", "darkolivegreen1"),
        names = c("CL1","CL2","CL3","CL4"))

boxplot(df$areaconst~df$cluster,
        main = "Distribución área por cluster",
        ylab="Area",
        xlab = "Cluster", las=2,
        col=c("#ee964b", "cadetblue1", "pink", "darkolivegreen1"),
        names = c("CL1","CL2","CL3","CL4"))

boxplot(df$preciom~df$cluster,
        main = "Distribución precio por cluster",
        ylab="Precio",
        xlab = "Cluster", las=2,
        col=c("#ee964b", "cadetblue1", "pink", "darkolivegreen1"),
        names = c("CL1","CL2","CL3","CL4"))

## Promedio de cada grupo respecto a cada variable
prom <- round(aggregate(num, by=list(cluster=km.res$cluster), mean),0)
prom
##   cluster estrato preciom areaconst parqueaderos banios habitaciones
## 1       1       4     212        90            1      2            3
## 2       2       4     453       302            1      5            7
## 3       3       5     467       162            2      3            3
## 4       4       6    1104       402            4      5            4

El cluster 1 está conformado por viviendas de estrato 4, con un precio promedio de 212 millones de pesos, un area construida de 90 metros cuadrados, 1 parqueadero, dos baños y 3 habitaciones. El cluster 2 contiene viviendas de estrato 4, con un precio de 453 millones, areas más grandes que el cluster 1 y 1 parqueadero, 5 baños y 7 habitaciones.

El cluster 3 tiene viviendas más costosas pero con menos metros cuadrados que el cluster 2, con mayor número de parqueaderos y 3 habitaciones, 3 baños. Finalmente el cluster 4 tiene las viviendas más costosas, con más metros cuadrados construidos, más parqueaderos y un número importante de baños y habitaciones, por lo que se puede concluir que este cluster está agrupando esas viviendas con un comportamiento superior.

Análisis de Correspondencia

El objetivo de este análisis es examinar la relación entre las variables categóricas para identificar patrones de comportamiento de la oferta en mercado inmobiliario. Cabe aclarar que se debe incluír la variable estrato como catégorica para poder hacer el ejercicio, ya que tipo solo tiene dos opciones y esto limita el análisis si solo se tiene en cuenta tipo y zona.

str(vivienda)
## tibble [8,319 × 8] (S3: tbl_df/tbl/data.frame)
##  $ zona        : chr [1:8319] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
##  $ estrato     : num [1:8319] 3 3 3 4 5 5 4 5 5 5 ...
##  $ preciom     : num [1:8319] 250 320 350 400 260 240 220 310 320 780 ...
##  $ areaconst   : num [1:8319] 70 120 220 280 90 87 52 137 150 380 ...
##  $ parqueaderos: num [1:8319] 1 1 2 3 1 1 2 2 2 2 ...
##  $ banios      : num [1:8319] 3 2 2 5 2 3 2 3 4 3 ...
##  $ habitaciones: num [1:8319] 6 3 4 3 3 3 3 4 6 3 ...
##  $ tipo        : chr [1:8319] "Casa" "Casa" "Casa" "Casa" ...
##  - attr(*, "na.action")= 'omit' Named int [1:3] 8320 8321 8322
##   ..- attr(*, "names")= chr [1:3] "8320" "8321" "8322"
cat <- vivienda[,c(1, 2, 8)]
cat$estrato <- as.character(cat$estrato)
str(cat)
## tibble [8,319 × 3] (S3: tbl_df/tbl/data.frame)
##  $ zona   : chr [1:8319] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
##  $ estrato: chr [1:8319] "3" "3" "3" "4" ...
##  $ tipo   : chr [1:8319] "Casa" "Casa" "Casa" "Casa" ...
##  - attr(*, "na.action")= 'omit' Named int [1:3] 8320 8321 8322
##   ..- attr(*, "names")= chr [1:3] "8320" "8321" "8322"
## Tablas cruzadas
tabla1 <- table(cat$estrato, cat$zona)
tabla1
##    
##     Zona Centro Zona Norte Zona Oeste Zona Oriente Zona Sur
##   3         105        572         54          340      382
##   4          14        407         84            8     1616
##   5           4        769        290            2     1685
##   6           1        172        770            1     1043
tabla2 <- table(cat$estrato, cat$tipo )
tabla2
##    
##     Apartamento Casa
##   3         639  814
##   4        1404  725
##   5        1766  984
##   6        1291  696
tabla3 <- table(cat$zona, cat$tipo )
tabla3
##               
##                Apartamento Casa
##   Zona Centro           24  100
##   Zona Norte          1198  722
##   Zona Oeste          1029  169
##   Zona Oriente          62  289
##   Zona Sur            2787 1939
## Analisis de correspondencia

res.mca <- MCA(cat, graph = TRUE)

res.mca$eig
##       eigenvalue percentage of variance cumulative percentage of variance
## 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

Gráficamente se puede evidenciar que la zona oeste está caracterizada por ser de estrato 6, la zona oriente y centro comparte similitudes comportamentales entre si, la zona norte y sur se caracterizan por tener viviendas de estratos 4 y 5, tanto casas como apartamentos.

Respecto a la contribución de los ejes, observamos que los dos primeros ejes solo explican el \(38.07\%\) de la varianza, que el eje x está representado por el tipo de vivienda y el eje y por estrato y zona.

Cabe resaltar que para alcanzar el \(77\%\) de la varianza explicada, se deben tomar 5 componentes.

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

Conclusión

A nivel técnico, en este informe se evaluaron tres tipos de modelos multivariados, cuyos resultados permiten concluir que para el conjunto de datos de viviendas, el ACP y ACM tienen una gran potencia para describir el comportamiento y caracteristicas de los predios en estudio, mientras que los métodos de agrupamiento tienen métricas poco aceptables pero permiten agrupar las viviendas de acuerdo al número de parqueaderos, baños, habitaciones y precio.

Por otra parte, se pudo evidenciar que las viviendas de estrato 3 están ubicadas mayormente en las zonas centro y oriente, además, el precio por metro cuadrado en estas zonas es inferior que en las demás y son las zonas que contienen más casas, por lo tanto cuentan con un gran tamaño, ideales par familias numerosas o que necesitan tener espacio suficiente para sus actividades, pero deben sacrificar comodidades como tener parqueadero propio. La zona sur y norte tienen caracteristicas similares, siendo más frecuente la aparición de viviendas tipo apartamento, de estratos 4 y 5, con precios mas elevados que la zona centro y oriente pero inferiores al oeste, además estas zonas cuentan con disponibilidad de parqueadero, 2 baños en promedio y entre 3 y 4 habitaciones, siendo ideales para familiar de entre 3 y 4 miembros. Por último, la zona oeste tiene las viviendas con mayor número de parqueaderos, son apartamentos de estrato 6 y con precios superiores al resto de la ciudad; se caracterizan por tener amplios espacios y son ideales para familias con ingresos altos, que prefieren invertir más en sus viviendas para tener mayores comodidades.