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.
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
# Primeros y últimos registros del dataset:
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>
tail(vivienda)
## # A tibble: 6 × 13
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <dbl> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 6417 Zona S… <NA> 6 1800 400 3 6 5
## 2 6998 Zona S… <NA> 6 1000 189 3 5 4
## 3 8139 Zona S… <NA> 5 530 142 2 4 4
## 4 NA <NA> <NA> NA NA NA NA NA NA
## 5 NA <NA> <NA> NA NA NA NA NA NA
## 6 NA <NA> <NA> NA 330 NA NA NA NA
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
# Estructura del dataset
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>
#Cantidad de datos nulos
colSums(is.na(vivienda))
## 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
# Revisión y eliminación de datos nulos
colSums(is.na(vivienda))
## 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
# Eliminar registros sin datos
sapply(vivienda,function(x) sum(is.na(x)))
## 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
vivienda <- vivienda[-c (8320, 8321, 8322), ]
colSums(is.na(vivienda))
## id zona piso estrato preciom areaconst
## 0 0 2635 0 0 0
## parqueaderos banios habitaciones tipo barrio longitud
## 1602 0 0 0 0 0
## latitud
## 0
# Reemplazar los valores nulos de parqueadero por cero
vivienda$parqueaderos[is.na(vivienda$parqueaderos)] <- 0
colSums(is.na(vivienda))
## id zona piso estrato preciom areaconst
## 0 0 2635 0 0 0
## parqueaderos banios habitaciones tipo barrio longitud
## 0 0 0 0 0 0
## latitud
## 0
# Gráfico de barras para determinar que valores imputar en la variable piso
ggplot(vivienda, aes(x = factor(piso), fill = tipo)) +
geom_bar(position = "dodge") +
labs(title = "Número de Pisos por Tipo de Vivienda",
x = "Pisos",
y = "Cantidad",
fill = "Tipo de Vivienda") +
theme_minimal()
# Imputar la media para los nulos de las casas
if (!is.numeric(vivienda$piso)) {
vivienda$piso <- as.numeric(as.character(vivienda$piso))
}
class(vivienda$piso)
## [1] "numeric"
media_piso_casa <- round(mean(vivienda$piso[vivienda$tipo == "Casa"], na.rm = TRUE))
vivienda$piso[is.na(vivienda$piso) & vivienda$tipo == "Casa"] <- media_piso_casa
colSums(is.na(vivienda))
## id zona piso estrato preciom areaconst
## 0 0 1381 0 0 0
## parqueaderos banios habitaciones tipo barrio longitud
## 0 0 0 0 0 0
## latitud
## 0
# Imputar la moda para los nulos de los apartamentos
calcular_moda <- function(x) {
tabla <- table(x)
moda <- as.numeric(names(tabla)[which.max(tabla)])
return(moda)
}
moda_piso_apartamento <- calcular_moda(vivienda$piso[vivienda$tipo == "Apartamento" & !is.na(vivienda$piso)])
vivienda$piso[is.na(vivienda$piso) & vivienda$tipo == "Apartamento"] <- moda_piso_apartamento
colSums(is.na(vivienda))
## id zona piso estrato preciom areaconst
## 0 0 0 0 0 0
## parqueaderos banios habitaciones tipo barrio longitud
## 0 0 0 0 0 0
## latitud
## 0
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.
# Validar el tipo de datos
sapply(vivienda, class)
## id zona piso estrato preciom areaconst
## "numeric" "character" "numeric" "numeric" "numeric" "numeric"
## parqueaderos banios habitaciones tipo barrio longitud
## "numeric" "numeric" "numeric" "character" "character" "numeric"
## latitud
## "numeric"
sapply(vivienda, typeof)
## id zona piso estrato preciom areaconst
## "double" "character" "double" "double" "double" "double"
## parqueaderos banios habitaciones tipo barrio longitud
## "double" "double" "double" "character" "character" "double"
## latitud
## "double"
vivienda$id <- as.character(vivienda$id)
# Estandarizar las variables numéricas
vivienda_std <- scale(vivienda[, sapply(vivienda, is.numeric)])
# PCA
pca_result <- prcomp(vivienda_std, scale. = TRUE)
summary(pca_result)
## Importance of components:
## PC1 PC2 PC3 PC4 PC5 PC6 PC7
## Standard deviation 1.9028 1.2724 1.0126 0.87715 0.83133 0.66028 0.64151
## Proportion of Variance 0.4023 0.1799 0.1139 0.08549 0.07679 0.04844 0.04573
## Cumulative Proportion 0.4023 0.5822 0.6961 0.78159 0.85838 0.90683 0.95255
## PC8 PC9
## Standard deviation 0.48923 0.43322
## Proportion of Variance 0.02659 0.02085
## Cumulative Proportion 0.97915 1.00000
# Gráfico de varianza explicada
var_explained <- pca_result$sdev^2 / sum(pca_result$sdev^2)
qplot(c(1:length(var_explained)), var_explained) +
geom_line() +
xlab("Componente Principal") +
ylab("Varianza Explicada") +
ggtitle("Gráfico de Varianza Explicada por Componente Principal")
## Warning: `qplot()` was deprecated in ggplot2 3.4.0.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
loadings <- pca_result$rotation
loadings
## PC1 PC2 PC3 PC4 PC5
## piso 0.06582148 -0.40199133 0.60671117 0.64503688 -0.13138953
## estrato -0.34165150 -0.44258548 0.08042422 -0.17441198 0.10750433
## preciom -0.46227615 -0.06853886 0.15843644 -0.13721117 0.14261350
## areaconst -0.41403579 0.27845802 0.05592635 -0.02266888 0.06001867
## parqueaderos -0.39740486 -0.12695076 0.12365561 -0.09755728 0.35170915
## banios -0.44387605 0.18279108 0.05480780 0.20748350 -0.08152174
## habitaciones -0.25436175 0.53803692 -0.02865602 0.39033379 -0.32346411
## longitud 0.24204488 0.37752920 0.24998638 0.18766444 0.79565365
## latitud 0.12479872 0.28061276 0.71804921 -0.54223783 -0.28142473
## PC6 PC7 PC8 PC9
## piso -0.01627993 -0.17634230 0.026756811 -0.02363696
## estrato 0.37628589 0.49824541 0.473650608 -0.15736301
## preciom 0.24762212 -0.23410368 -0.209743170 0.74647312
## areaconst 0.21934310 -0.64151018 0.279933214 -0.45363236
## parqueaderos -0.81774658 0.03932064 0.041738733 -0.07380863
## banios 0.14959602 0.35615914 -0.671063064 -0.34250320
## habitaciones -0.13980131 0.28980776 0.445119115 0.29383963
## longitud 0.19044496 0.17079488 0.048441041 0.01798358
## latitud -0.04258703 0.11296099 -0.006249316 -0.04746000
loadings_df <- as.data.frame(loadings)
loadings_df$variable <- rownames(loadings_df)
# Gráfico de contribuciones
ggplot(loadings_df, aes(x = PC1, y = PC2, label = variable)) +
geom_segment(aes(x = 0, y = 0, xend = PC1, yend = PC2), arrow = arrow(length = unit(0.2, "cm")), color = "blue") +
geom_text(vjust = 1.5, hjust = 1.5, color = "red") +
xlab("PC1") +
ylab("PC2") +
ggtitle("Contribuciones de las variables a PC1 y PC2") +
theme_minimal()
El gráfico de varianza explicada muestra cuánta variabilidad en los datos es capturada por cada componente principal.
PC1: Explica la mayor parte de la varianza; PC2: Explica una cantidad significativa, pero menor que PC1. Pos otra parte PC3, PC4, PC5: Explican proporciones más pequeñas de la varianza.
Los primeros dos componentes capturan la mayor parte de la información relevante en los datos.
PC1 (Valor económico y comodidades):
Las variables más importantes son el precio, el número de baños, el área construida y los parqueaderos.
Este componente representa la dimensión relacionada con el valor económico y las comodidades de las viviendas.
Las viviendas con mayores valores en PC1 tienden a ser más caras, tener más baños, mayor área construida y más parqueaderos.
PC2 (Tamaño y ubicación):
Las variables más importantes son el número de habitaciones, el área construida y la ubicación geográfica.
Este componente representa la dimensión relacionada con el tamaño (Las viviendas con mayores valores en PC2 tienden tener más habiteaciones) y acorde con la longitud, podría inferirse que están ubicadas en cierta zona.
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.
# Variables para clustering
variables_clustering <- vivienda[, c("preciom", "banios", "areaconst", "parqueaderos", "habitaciones", "estrato", "latitud", "longitud")]
variables_clustering_std <- scale(variables_clustering)
# Número de clusters
wcss <- vector()
for (i in 1:10) {
kmeans_model <- kmeans(variables_clustering_std, centers = i, nstart = 25)
wcss[i] <- kmeans_model$tot.withinss
}
## Warning: Quick-TRANSfer stage steps exceeded maximum (= 415950)
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
plot(1:10, wcss, type = "b", pch = 19, frame = FALSE,
xlab = "Número de clusters",
main = "K-means")
# K_MEANS
set.seed(257)
kmeans_result <- kmeans(variables_clustering_std, centers = 4, nstart = 25)
centroides <- kmeans_result$centers
centroides
## preciom banios areaconst parqueaderos habitaciones estrato
## 1 0.04724664 0.92144732 0.8927966 -0.2217859 1.9409011 -0.6746331
## 2 -0.01625463 0.01940719 -0.2017760 0.1041682 -0.2402354 0.5749974
## 3 -0.68461855 -0.77071145 -0.5895818 -0.6099807 -0.4955491 -0.7600692
## 4 1.94265650 1.35349310 1.5008692 1.6103396 0.4702214 1.0600509
## latitud longitud
## 1 0.3471140 0.2638044
## 2 -0.2391313 -0.5014081
## 3 0.2697938 0.6285387
## 4 -0.3655262 -0.5565493
# Agregar la asignación de clusters al conjunto de datos original
vivienda$cluster <- kmeans_result$cluster
# Visualizar los clusters en un gráfico de dispersión (PC1 y PC2)
pca_values <- as.data.frame(pca_result$x[, 1:2])
colnames(pca_values) <- c("PC1", "PC2")
vivienda <- cbind(vivienda, pca_values)
ggplot(vivienda, aes(x = PC1, y = PC2, color = as.factor(cluster))) +
geom_point(size = 2) +
labs(title = "Clusters de Viviendas",
x = "PC1 (Valor económico y comodidades)",
y = "PC2 (Tamaño y ubicación)",
color = "Cluster") +
theme_minimal()
Interpretación de Clusters PC1 y PC2:
PC1: A la derecha están las viviendas con mayor valor económico y comodidades, mientras que a la izquierda están las más económicas o con menos comodidades.
PC2: Las viviendas con valores más altos en PC2 tienden a ser más grandes o estar en mejores ubicaciones, mientras que las que tienen valores bajos pueden ser más pequeñas o estar en ubicaciones menos privilegiadas.
Clusters:
Cluster 1 (rojo): Se encuentra en la parte superior derecha, lo que indica viviendas con mayor valor económico y mayor tamaño/ubicación favorable.
Cluster 2 (verde): Está en la parte inferior central, sugiriendo viviendas de menor valor económico, pero con un tamaño o ubicación intermedios.
Cluster 3 (azul): Se agrupa en la parte derecha, indicando que estas viviendas tienen un valor económico alto pero con variabilidad en el tamaño y la ubicación.
Cluster 4 (morado): Se ubica en la parte izquierda, lo que indica viviendas con menor valor económico y menor tamaño o ubicación menos llamativa.
Los clusters 1 y 3 podrían representar viviendas con mejor valoración económica.
El cluster 4 representa viviendas con características muy distintas (probablemente las más económicas y pequeñas).
Existen ciertas áreas donde los clusters se mezclan, lo que indica que algunos grupos comparten características similares.
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.
variables_categoricas <- vivienda[, c("tipo", "zona")]
variables_categoricas <- variables_categoricas %>%
mutate(across(everything(), as.factor))
mca_result <- MCA(variables_categoricas, graph = FALSE)
plot(mca_result,
invisible = "ind",
title = "Análisis de Correspondencia Múltiple (MCA)",
cex = 0.8)
Con base a la gráfica anterior se evidencia que los apartamentos parecen
estar más relacionados con la Zona Sur y Zona Norte, ya que estos puntos
están cercanos en el espacio factorial. Las casas están más cercanas a
la Zona Centro, lo que sugiere que en esta zona es más común encontrar
casas en comparación con otras áreas. La Zona Oriente se encuentra más
alejada, lo que indica que tiene características diferentes en
comparación con el resto de las zonas en términos de tipo de vivienda
disponible.
Con base en el PCA, las variables más influyentes en el mercado son el precio, el número de baños, el área construida y el número de habitaciones. La ubicación geográfica (longitud y latitud) también juega un papel importante, especialmente en la diferenciación de propiedades por zona.
El mercado se divide en cuatro segmentos claros, cada uno con características distintivas en términos de valor económico, tamaño y ubicación. Los clusters 1 y 3 representan propiedades de alto valor, mientras que el cluster 4 representa propiedades más económicas y pequeñas.
EL análisis de correspondencia demostró que la Zona Centro es mejor para la promoción de casas, mientras que las Zonas Sur y Norte son más adecuadas para apartamentos.
Invertir en la promoción y mejora de propiedades en los Clusters 1 y 3, que representan el segmento de mayor valor económico.
En la Zona Centro, enfocarse en la venta de casas, mientras que en las Zonas Sur y Norte, promover apartamentos.
Implementar estrategias para mejorar la valoración de propiedades en el Cluster 4, como renovaciones o mejoras en la infraestructura.
Realizar análisis periódicos para monitorear cambios en el mercado y ajustar las estrategias en consecuencia.
Este análisis proporciona una visión detallada del mercado de viviendas urbanas, identificando segmentos clave, variables influyentes y patrones de oferta por zona. Con estas insights, la empresa puede optimizar sus estrategias de inversión, maximizar beneficios y mantenerse competitiva en un mercado en constante cambio.