Evaluación de la oferta inmobiliaria urbana

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.

Retos:

El reto principal consisten en realizar un análisis integral y multidimensional de la base de datos para obtener una comprensión del mercado inmobiliario urbano. Se requiere aplicar diversas técnicas de análisis de datos, incluyendo:

Análisis de Componentes Principales: 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.

Análisis de Conglomerados: 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.

Análisis de Correspondencia : Examinar la relación entre las variables categóricas (tipo de vivienda, zona y barrio), para identificar patrones de comportamiento de la oferta en mercado inmobiliario.

Visualización de resultados: Presentar gráficos, mapas y otros recursos visuales para comunicar los hallazgos de manera clara y efectiva a la dirección de la empresa.

El informe final debe incluir análisis detallados de los resultados obtenidos, las conclusiones clave y las recomendaciones específicas para guiar las decisiones estratégicas de la empresa inmobiliaria. Se espera que este análisis de datos proporcione ventajas competitivas en el mercado, optimizando la inversión y maximizando los beneficios en un entorno altamente competitivo y en constante cambio.

Carga de librerias

1.Exploración de los datos

Resumen del contenido de vivienda:

# Generar una casilla interactiva para ocultar/mostrar el código
data("vivienda")
summary(vivienda)
##        id           zona               piso              estrato     
##  Min.   :   1   Length:8322        Length:8322        Min.   :3.000  
##  1st Qu.:2080   Class :character   Class :character   1st Qu.:4.000  
##  Median :4160   Mode  :character   Mode  :character   Median :5.000  
##  Mean   :4160                                         Mean   :4.634  
##  3rd Qu.:6240                                         3rd Qu.:5.000  
##  Max.   :8319                                         Max.   :6.000  
##  NA's   :3                                            NA's   :3      
##     preciom         areaconst       parqueaderos        banios      
##  Min.   :  58.0   Min.   :  30.0   Min.   : 1.000   Min.   : 0.000  
##  1st Qu.: 220.0   1st Qu.:  80.0   1st Qu.: 1.000   1st Qu.: 2.000  
##  Median : 330.0   Median : 123.0   Median : 2.000   Median : 3.000  
##  Mean   : 433.9   Mean   : 174.9   Mean   : 1.835   Mean   : 3.111  
##  3rd Qu.: 540.0   3rd Qu.: 229.0   3rd Qu.: 2.000   3rd Qu.: 4.000  
##  Max.   :1999.0   Max.   :1745.0   Max.   :10.000   Max.   :10.000  
##  NA's   :2        NA's   :3        NA's   :1605     NA's   :3       
##   habitaciones        tipo              barrio             longitud     
##  Min.   : 0.000   Length:8322        Length:8322        Min.   :-76.59  
##  1st Qu.: 3.000   Class :character   Class :character   1st Qu.:-76.54  
##  Median : 3.000   Mode  :character   Mode  :character   Median :-76.53  
##  Mean   : 3.605                                         Mean   :-76.53  
##  3rd Qu.: 4.000                                         3rd Qu.:-76.52  
##  Max.   :10.000                                         Max.   :-76.46  
##  NA's   :3                                              NA's   :3       
##     latitud     
##  Min.   :3.333  
##  1st Qu.:3.381  
##  Median :3.416  
##  Mean   :3.418  
##  3rd Qu.:3.452  
##  Max.   :3.498  
##  NA's   :3

Se observa que la base de datos contiene variables de tipo categóricas y también cuantitativas.

viviendau <- unique(vivienda)
vis_miss(viviendau)

Se genera otro dataframe eliminando valores repetidos y se verifican los valores faltantes. Acá se observa que piso contiene un 32 % de datos faltantes y parqueaderon un 19% por lo que se procederá a trabajar únicamente con los casos completos:

viv_sin_na <- viviendau[complete.cases(viviendau),]
glimpse(viv_sin_na)
## Rows: 4,808
## Columns: 13
## $ id           <dbl> 5992, 1212, 1724, 2326, 4386, 1209, 1592, 4460, 6081, 749…
## $ zona         <chr> "Zona Sur", "Zona Norte", "Zona Norte", "Zona Norte", "Zo…
## $ piso         <chr> "02", "01", "01", "01", "01", "02", "02", "02", "02", "02…
## $ estrato      <dbl> 4, 5, 5, 4, 5, 5, 5, 4, 5, 6, 4, 5, 5, 4, 5, 3, 6, 6, 4, …
## $ preciom      <dbl> 400, 260, 240, 220, 310, 320, 780, 625, 750, 520, 600, 42…
## $ areaconst    <dbl> 280, 90, 87, 52, 137, 150, 380, 355, 237, 98, 160, 200, 1…
## $ parqueaderos <dbl> 3, 1, 1, 2, 2, 2, 2, 3, 2, 2, 1, 4, 2, 2, 2, 1, 1, 2, 1, …
## $ banios       <dbl> 5, 2, 3, 2, 3, 4, 3, 5, 6, 2, 4, 4, 4, 3, 2, 2, 4, 3, 2, …
## $ habitaciones <dbl> 3, 3, 3, 3, 4, 6, 3, 5, 6, 2, 5, 5, 4, 3, 3, 3, 4, 4, 3, …
## $ tipo         <chr> "Casa", "Apartamento", "Apartamento", "Apartamento", "Apa…
## $ barrio       <chr> "3 de julio", "acopi", "acopi", "acopi", "acopi", "acopi"…
## $ longitud     <dbl> -76.54000, -76.51350, -76.51700, -76.51974, -76.53105, -7…
## $ latitud      <dbl> 3.43500, 3.45891, 3.36971, 3.42627, 3.38296, 3.47968, 3.4…

Al realizar este paso anterior, el dataframe pasó de tener 8321 observaciones a 4808 observaciones de 13 variables.

boxplot e histogramas

pairs.panels(viv_sin_na, gap=0)

par(mfrow=c(1,2))
hist(viv_sin_na$preciom, col="#87CEFA", breaks=15, main="Precio")
hist(viv_sin_na$areaconst, col="#90EE90", breaks=15, main="Area")

par(mfrow=c(1,2))
boxplot(viv_sin_na$preciom, col="blue", main="Precio")
boxplot(viv_sin_na$areaconst, col="orange", main="Area")

par(mfrow=c(1,3))
hist(viv_sin_na$parqueaderos, col="green", breaks=15, main="Parqueaderos")
hist(viv_sin_na$banios, col="#87CEFA", breaks=15, main="Baños")
hist(viv_sin_na$habitaciones, col="#90EE90", breaks=15, main="Habitaciones")

par(mfrow=c(1,3))
boxplot(viv_sin_na$parqueaderos, col="green", main="Parquea")
boxplot(viv_sin_na$banios, col="blue", main="Baños")
boxplot(viv_sin_na$habitaciones, col="orange", main="habitaciones")

Se obtienen datos importantes sobre el comportamiento de las vivienda de Cali, observando por ejemplo en el precio una mediana por debajo de las 500 miCOP pero algunas viviendas con valores bastante elevados que incluso alcanzan los 2000 miCOP. Se observaron valores atípicos en el area construida.

En el gráfico de correlaciones se observa una correlaciòn positiva entre las variables precio y parqueaderos y precio contra baños, habitaciones presenta también una correlación positiva media con precio.

Verificamos que no tenga faltantes:

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

##      id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## 4808  1    1    1       1       1         1            1      1            1
##       0    0    0       0       0         0            0      0            0
##      tipo barrio longitud latitud  
## 4808    1      1        1       1 0
##         0      0        0       0 0

2. Análisis de componentes principales

Para realizar el PCA se trabaja con los valores cuantitativos, por lo que se procede a eliminar las columnas categóricas:

viv_no_col <- viv_sin_na[, -c(1, 2, 3, 4, 10, 11, 12, 13)]
head(viv_no_col)
## # A tibble: 6 × 5
##   preciom areaconst parqueaderos banios habitaciones
##     <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
## 1     400       280            3      5            3
## 2     260        90            1      2            3
## 3     240        87            1      3            3
## 4     220        52            2      2            3
## 5     310       137            2      3            4
## 6     320       150            2      4            6

Se escala el dataframe:

vivZ= scale(viv_no_col[,1:5])
head(vivZ) # primeros 6 registros
##         preciom  areaconst parqueaderos     banios habitaciones
## [1,] -0.1756310  0.7609789    1.0779092  1.3178809   -0.4241459
## [2,] -0.6055839 -0.6129041   -0.7415001 -0.9022913   -0.4241459
## [3,] -0.6670057 -0.6345970   -0.7415001 -0.1622339   -0.4241459
## [4,] -0.7284276 -0.8876807    0.1682046 -0.9022913   -0.4241459
## [5,] -0.4520293 -0.2730489    0.1682046 -0.1622339    0.3272519
## [6,] -0.4213184 -0.1790463    0.1682046  0.5778235    1.8300475

Código componentes principales

prcomp(vivZ)
## Standard deviations (1, .., p=5):
## [1] 1.8224613 0.9091784 0.5887286 0.5626534 0.4345677
## 
## Rotation (n x k) = (5 x 5):
##                    PC1         PC2         PC3        PC4         PC5
## preciom      0.4653012  0.41103273  0.37266427 -0.1604645  0.67076031
## areaconst    0.4808370 -0.06039466  0.43996032  0.6504368 -0.38537656
## parqueaderos 0.4372183  0.42611283 -0.76586641  0.1772046 -0.09651501
## banios       0.4835455 -0.13634371  0.09449621 -0.7158474 -0.47563341
## habitaciones 0.3568116 -0.79198663 -0.26846856  0.0856976  0.40745949
res.pca <- prcomp(vivZ)
fviz_eig(res.pca, addlabels = TRUE)

summary(res.pca)
## Importance of components:
##                           PC1    PC2     PC3     PC4     PC5
## Standard deviation     1.8225 0.9092 0.58873 0.56265 0.43457
## Proportion of Variance 0.6643 0.1653 0.06932 0.06332 0.03777
## Cumulative Proportion  0.6643 0.8296 0.89891 0.96223 1.00000

Se observa que los dos primeros componentes principales explican aproximadamente el 82% de la variabilidad, por lo que dependiendo del porcentaje que busquemos, podemos reducir la matriz a los primeros dos o tres componentes principales.

Gráficos los componentes principales.

fviz_contrib(res.pca, choice = "var",axes = 1)

fviz_contrib(res.pca, choice = "var",axes = 2)

fviz_contrib(res.pca, choice = "var",axes = 3)

fviz_contrib(res.pca, choice = "var",axes = 4)

fviz_contrib(res.pca, choice = "var",axes = 5)

En la primera dimensión, las variables que más peso le asignan al modelo son baños, area contruida y precio, mientas que en la segunda dimensión

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

En este gráfico de las dos primeras dimensiones se observa que variables como precio y parqueadero se mueven en el mismo sentido, de igual forma lo hacen area construida y baños, mientras que habitaciones tiene una contribución alta.

library(viridis)
# Colores personalizados y transparencia
fviz_pca_biplot(res.pca, col.ind = viv_no_col$preciom, 
                gradient.cols = c("blue", "yellow", "red"),
               col.var = "black",  # Escala de colores continua
               alpha.var = 0.9  # Transparencia de las flechas
               )

Se observa cómo está distribuidas las viviendas, teniendo en cuenta las dos primeras dimensiones y basándonos en la variable precio. Cerca al origen se encuentra una densidad alta de viviendas con precios promedio, mientras que en la parte superior derecha se observa la vivienda correpondiente al número 3008 la cual tiene un precio por encima de los 1500 millones,

3. Análisis de conglomerados

# distancia euclidiana
de <- dist(vivZ, method = 'euclidean')
# Cluster jerarquico con el método complete
hc1 <- hclust(de, method = 'complete')
plot(hc1, labels = FALSE, hang = -1, main = "Dendrograma de Clustering Jerárquico")

Dendograma de cluster jerarquico

Cálculo de la cantidad de cluster para agrupar las viviendas

Se utilizaran tres métodos para calcular la cantidad de clusters a implementar:

Método silueta:
#Método de silueta
fviz_nbclust(vivZ, kmeans, method = "silhouette") + 
  labs(subtitle = "Silhouette method")

Sugiere 2 cluster

Método de codo:
# Método de codo
fviz_nbclust(vivZ, kmeans, method = "wss") + 
  labs(subtitle = "Elbow method")

Este método sugiere tres cluster

Función NbClust

Realiza varios métodos y sugiere el número de clusters utilizando la distancia euclidiana:

res.nbclust <- NbClust (vivZ, distance = "euclidean", min.nc = 2,
                        max.nc = 9, method = "complete", index = "all")

## *** : The Hubert index is a graphical method of determining the number of clusters.
##                 In the plot of Hubert index, we seek a significant knee that corresponds to a 
##                 significant increase of the value of the measure i.e the significant peak in Hubert
##                 index second differences plot. 
## 

## *** : The D index is a graphical method of determining the number of clusters. 
##                 In the plot of D index, we seek a significant knee (the significant peak in Dindex
##                 second differences plot) that corresponds to a significant increase of the value of
##                 the measure. 
##  
## ******************************************************************* 
## * Among all indices:                                                
## * 10 proposed 2 as the best number of clusters 
## * 2 proposed 3 as the best number of clusters 
## * 1 proposed 4 as the best number of clusters 
## * 2 proposed 6 as the best number of clusters 
## * 8 proposed 7 as the best number of clusters 
## * 1 proposed 9 as the best number of clusters 
## 
##                    ***** Conclusion *****                            
##  
## * According to the majority rule, the best number of clusters is  2 
##  
##  
## *******************************************************************

Se definen 2 cluster, ya que por la función NbClust 10 métodos sugieren esta cantidad.

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

# Añadir la columna de clusters usando mutate
assigned_cluster <- viv_no_col %>% mutate(cluster = as.factor(cluster_assignments))

Gráfico de los dos grupos

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

Se observa un grupo bastante alto de viviendas que muestran tener una amplia variedad precios y otro grupo más reducido en el que la principal diferencia se centra en el area contruida.

4. Análisis de correspondencias

Se convieten las variables categóricas con las que queremos trabajar a tipo factor:

viv_MCA <- viv_sin_na
viv_MCA$zona <- as.factor(viv_sin_na$zona)
viv_MCA$tipo <- as.factor(viv_sin_na$tipo)
viv_MCA$estrato <- as.factor(viv_sin_na$estrato)
str(viv_MCA[, c("zona", "tipo", "estrato")])
## tibble [4,808 × 3] (S3: tbl_df/tbl/data.frame)
##  $ zona   : Factor w/ 5 levels "Zona Centro",..: 5 2 2 2 2 2 2 2 2 2 ...
##  $ tipo   : Factor w/ 2 levels "Apartamento",..: 2 1 1 1 1 2 2 2 2 1 ...
##  $ estrato: Factor w/ 4 levels "3","4","5","6": 2 3 3 2 3 3 3 2 3 4 ...

Selección de variables para MCA

# Seleccionar las variables para el MCA
v_mca <- viv_MCA[, c("zona", "tipo", "estrato")]

Resultados MCA

resultados_mca <- MCA(v_mca, graph = FALSE)

Gráfica MCA

# Visualizar los resultados
fviz_mca_biplot(resultados_mca)

En esta gráfica se observa claramente la distribución de las viviendas y como los grupos se relacionan con las variables categóricas estrato, tipo de vivienda y zona de la ciudad, encontrando, por ejemplo, que la zona norte esta relacionada con casas y además con el estrato 4, la zona oeste relacionada al estrato 6.

CONCLUSIONES

Los métodos trabajados en el presente informe, sirven para complementar los análisis descriptivos vistos previamente, también permiten reducir las dimensiones de conjuntos de datos grandes, permitiendo mediante PCA, determinar las variables que más aportan a explicar la varianza y las correlaciones entre las mismas, para así poder tener un conjunto de datos mucho más pequeño, pero que explique en un alto porcentaje el comportamiento general de una población o muestra.

El análisis de conglomerados puede ser útil en la segmentación de clientes cuando no son evidentes estas agrupaciones, permitiendo realizar por ejemplo campañas de mercadeo a grupos especificos que presentan comportamientos similares.