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.

Exploración de datos

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

Preparación del dataset

# 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

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.

# 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.

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.

# 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.

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.

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.

Conclusiones

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.

Recomendaciones Estratégicas

  • Enfoque en Propiedades de Alto Valor:

Invertir en la promoción y mejora de propiedades en los Clusters 1 y 3, que representan el segmento de mayor valor económico.

  • Diversificación de la Oferta:

En la Zona Centro, enfocarse en la venta de casas, mientras que en las Zonas Sur y Norte, promover apartamentos.

  • Optimización de Propiedades de Bajo Valor:

Implementar estrategias para mejorar la valoración de propiedades en el Cluster 4, como renovaciones o mejoras en la infraestructura.

  • Análisis Continuo:

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.