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.
# 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.
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
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
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.
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,
# 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
Se utilizaran tres métodos para calcular la cantidad de clusters a implementar:
#Método de silueta
fviz_nbclust(vivZ, kmeans, method = "silhouette") +
labs(subtitle = "Silhouette method")
Sugiere 2 cluster
# Método de codo
fviz_nbclust(vivZ, kmeans, method = "wss") +
labs(subtitle = "Elbow method")
Este método sugiere tres cluster
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 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.
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)
# 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.
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.