Evaluación de la oferta inmobiliaria urbana Cali

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.

Objetivo

Realizar un análisis integral y multidimensional de la base de datos de viviendas para obtener una comprensión del mercado inmobiliario en Cali aplicando diversas técnicas de análisis tales como: Análisis de Componentes principales, Análisis de Conglomerados y Análisis de Correspondencia.

Análisis exploratorio y limpieza de datos

Inicialmente se identifican registros cuyo ID no está definido, además de otras de sus características, por lo tanto se decide eliminar dichos registros

vivienda <- vivienda %>% filter(!is.na(id))
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

Observamos que los únicos valores en NA restantes son en las columnas de piso y parqueadero, asumiendo que estos valores no se completaron porque se asumía que la vivienda se encontraba a nivel del suelo en el caso del piso y no contaba con parqueadero en el caso de la respectiva columna, se reemplazan los valores por 1 para piso y 0 para parqueaderos.

vivienda$piso <- ifelse(is.na(vivienda$piso), 1, vivienda$piso)
vivienda$parqueaderos <- ifelse(is.na(vivienda$parqueaderos), 0, vivienda$parqueaderos)
vivienda <- na.omit(vivienda)
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

Se identifica que la variable piso es de tipo character, así que se transforma a variable numérica para poder aplicar análisis cuantivativos

Para el preciom realizamos un diagrama de caja

Podemos observar que entre 1000 y 2000 millones de pesos se encuentran varias viviendas, estos valores parecen representa una variación normal del precio de las viviendas y realmente no se trata de valores aislados, por lo tanto se mantendrán para el análisis.

A continuación realizamos un diagrama de caja para el área construida.

Se puede observar que la cantidad de datos por fuera del rango intercuartílico es significativo, por lo tanto se propone eliminar los registros que se encuentren alejados 1.5 veces este rango.

Q1 <- quantile(vivienda$areaconst, 0.25, na.rm = TRUE)
Q3 <- quantile(vivienda$areaconst, 0.75, na.rm = TRUE)

IQR <- Q3 - Q1
limite_inferior <- Q1 - 1.5 * IQR
limite_superior <- Q3 + 1.5 * IQR

# Filtrar las observaciones que no son outliers
vivienda <- subset(vivienda, areaconst >= limite_inferior & areaconst <= limite_superior)

# Verificar los resultados con un nuevo boxplot
boxplot(vivienda$areaconst, main = "Diagrama de caja sin valores atípicos", 
        ylab = "Área construida en metros cuadrados", col = "blue")

Realizamos un resumen de los datos agrupándolos por zona y calculando el promedio de sus características.

data_resumen <- vivienda %>%
  group_by(zona) %>%
  summarise(
    `Precio` = round(mean(preciom, na.rm = TRUE)),
    `Area(m^2)` = round(mean(areaconst, na.rm = TRUE)),
    `Precio_x_metro_cuadrado` = round(mean(preciom, na.rm = TRUE) / mean(areaconst, na.rm = TRUE)),
    Habitaciones = round(mean(habitaciones, na.rm = TRUE)),
    Banios = round(mean(banios, na.rm = TRUE))
  )

data_resumen <- data_resumen %>% 
  arrange(desc(Precio))

kable(data_resumen, "html") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"))
zona Precio Area(m^2) Precio_x_metro_cuadrado Habitaciones Banios
Zona Oeste 649 178 4 3 3
Zona Sur 392 150 3 4 3
Zona Norte 323 142 2 3 3
Zona Centro 303 182 2 5 3
Zona Oriente 223 173 1 5 3

Posteriormente graficamos la distribución los tipos de vivienda por zona, esto nos permite evidenciar que el set de datos cuenta en su mayoría con departamentos y la mayoría de viviendas se encuentran ubicadas en la zona sur de la ciudad.

ggplot(vivienda, aes(x = zona, fill = tipo)) +
  geom_bar() +
  labs(title = "Distribución de Tipos de Vivienda por Zona",
       x = "Zona",
       y = "Cantidad de Viviendas",
       fill = "Tipo de Vivienda") +
  theme_minimal()

A continuación creamos una matriz de correlación entre las variables numéricas.

# Columnas numéricas sin incluir ID
# vivienda <- vivienda[, !names(vivienda) %in% c("id")]
variables_numericas <- vivienda[, sapply(vivienda, is.numeric)]

# Calcular la matriz de correlación
matriz_correlacion <- cor(variables_numericas, use = "complete.obs")

corrplot(matriz_correlacion, method = "color", type = "full", 
         tl.col = "black", tl.srt = 45, 
         addCoef.col = "black",
         title = "Matriz de Correlación", mar = c(0, 0, 1, 0))

En el diagrama de correlación podemos darnos cuenta de que ninguna variable numérica se encuentra altamente correlacionada con las demás, por lo tanto no es necesario descargar ninguna.

Análisis de Componentes Principales

El objetivo es reducir la dimensionalidad del conjunto de datos para identificar características clave que influyen en la variación de precios y la oferta del mercado inmobiliario.

Para dicho análisis, primero tomamos las 6 variables numéricas: “Estrato”, “Precio”, “Área”, “Parqueaderos”, “Baños” y “Habitaciones”, dejando por fuera “Piso” por ser una variable categórica, “longitud” y “latitud”.

Las variables seleccionadas también serán escaladas para un mejor funcionamiento del análisis por componentes principales debido a su sensibiliad a valores grandes.

variables_seleccionadas <- variables_numericas[, c("estrato", "preciom", "areaconst", "parqueaderos", "banios", "habitaciones")]
variables_numericas <- scale(variables_seleccionadas)
head(variables_numericas)
##         estrato     preciom  areaconst parqueaderos       banios habitaciones
## [1,] -1.5654629 -0.52834912 -0.8586851   -0.3568686 -0.001244741    1.8003312
## [2,] -1.5654629 -0.28907057 -0.3444747   -0.3568686 -0.761206950   -0.3782370
## [3,] -1.5654629 -0.18652262  0.6839461    0.5205993 -0.761206950    0.3479524
## [4,] -0.5906411 -0.01560937  1.3009986    1.3980672  1.518679676   -0.3782370
## [5,]  0.3841808 -0.49416647 -0.6530010   -0.3568686 -0.761206950   -0.3782370
## [6,]  0.3841808 -0.56253177 -0.6838536   -0.3568686 -0.001244741   -0.3782370

Realizamos el cálculo de los componentes principales y graficamos el porcentaje de la varianza explicada por cada componente:

pca <- PCA(variables_numericas, scale.unit = TRUE, ncp = 5, graph = FALSE)

# Gráfico de la varianza explicada por cada componente principal
fviz_eig(pca, addlabels = TRUE, ylim = c(0, 60))

Podemos observar que la primera componente principal explica el 56.9% de la varianza y en conjunto con la segunda explican aproximadamente el 78% de la varianza total.

A continuación graficamos la contribución de las variables al primer y segundo componente principal:

fviz_contrib(pca, choice = "var", axes = 1, top = 10)

fviz_contrib(pca, choice = "var", axes = 2, top = 10)

Esto permite ver qué variables son las más importantes en los primeros componentes, que son los que explican más varianza, el precio por metro cuadrado, la cantidad de baños y el área construida son las variables con mayor peso para el cálculo del primer componente principal. Mientras que el segundo es mayormente explicado por la cantidad de habitaciones y el estrato.

Gráfico de las variables en el plano de los dos primeros componentes principales

fviz_pca_var(pca, col.var = "contrib",
             gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
             repel = TRUE)

En este gráfico, las variables que están más alineadas con un eje contribuyen más a ese componente, confirmando las suposiciones mencionadas en el anterior gráfico, por otro lado se puede apreciar que el número de habitaciones y el estrato socieconómico se encuentran ligeramente correlacionadas negativamente, mientras que el resto de variables que componen en su mayoría el primer componente se encuentran positivamente correlacionadas.

Posteriormente se realiza un gráfico de la distribución de los individuos para ver cómo las observaciones (viviendas) se distribuyen en el espacio de los componentes principales. Para el gráfico de los individuos se debe tomar una muestra más pequeña para que se puedan graficar correctamente.

viviendas_muestra <- sample_n(vivienda, 500)
variables_numericas_muestra <- viviendas_muestra[, sapply(viviendas_muestra, is.numeric)]
variables_seleccionadas_muestra <- variables_numericas_muestra[, c("estrato", "preciom", "areaconst", "parqueaderos", "banios", "habitaciones")]

variables_seleccionadas_muestra <- scale(variables_seleccionadas_muestra)
# Realizar el PCA
pca <- PCA(variables_seleccionadas_muestra, scale.unit = TRUE, ncp = 5, graph = FALSE)

# Visualizar los individuos en el plano de los primeros dos componentes principales
fviz_pca_ind(pca, col.ind = "contrib",
             gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
             repel = TRUE)

Análisis de Conglomerados

Inicialmente se seleccionan las variables numéricas relevantes para luego escalarlas, de esta forma evitar que las variables con mayores rangos de valores dominen el análisis. Tomamos nuevamente “Estrato”, “Precio por m cuadrado”, “Área construida”, “Parqueaderos”, “Bañios” y “Habitaciones”.

A continuación calculamos la matriz de distancia para con ella obtener el clustering jerárquico y graficar el dendograma.

# Calcular la matriz de distancia
distancia <- dist(variables_numericas, method = "euclidean")

# Realizar el clustering jerárquico
clustering_jerarquico <- hclust(distancia, method = "ward.D2")

# Visualizar el dendrograma
plot(clustering_jerarquico, labels = FALSE, hang = -1, main = "Dendrograma del Clustering Jerárquico")

El siguiente paso es identificar el número óptimo de clusters ya que o crucial en un análisis de conglomerados, porque ayuda a determinar cuántos grupos naturales existen en los datos. Existen varios métodos para hacerlo, el método del codo es uno de los métodos más utilizados para determinar el número óptimo de clusters. Consiste en graficar la suma de los errores cuadráticos dentro del cluster (inertia) para diferentes números de clusters y buscar el “codo” en la gráfica, que indica el punto donde añadir más clusters no mejora significativamente la agrupación.

set.seed(123)  # Fijar semilla para reproducibilidad
fviz_nbclust(variables_numericas, kmeans, method = "wss") + 
  labs(subtitle = "Método del Codo para Determinar el Número de Clusters")

Una vez determinado el número óptimo de clusters, en este caso 2, procedemos realizar el clustering por el método k-means, esta es una técnica común que particiona los datos en k clusters, minimizando la variación dentro de los clusters.

Evaluamos la calidad del clustering con el índice de silueta, El índice de silueta mide cómo de similares son los objetos dentro de un cluster comparado con objetos de otros clusters. Un valor de silueta cercano a 1 indica que las observaciones están bien agrupadas.

fviz_silhouette(silhouette(kmeans_resultado$cluster, distancia))
##   cluster size ave.sil.width
## 1       1 5132          0.51
## 2       2 2805          0.17

El valor reportado para la anchura media de la silueta es de 0.4 indicando que la calidad de clustering es moderadamente baja, lo que significa que los puntos no están claramente agrupados, sin embargo este indicador disminuye a medida que se aumenta la cantidad de clusters sugiriendo que 2 a 3 clusters pueden ser los más indicados.

# Visualizar los clusters en un plano 2D usando PCA
fviz_cluster(kmeans_resultado, data = variables_numericas, geom = "point", 
             ellipse.type = "convex", 
             palette = "jco", ggtheme = theme_minimal())

En la gráfica del cluster en 2D se puede evidenciar que a pesar de que ambos clusters se encuentran separados, el límite entre ellos no es muy claro, indicando que los individuos comparten características significativas.

Análisis de Correspondencia

Para este caso se decide por realizar el análisis teniendo en cuenta el tipo, zona y el estrado en lugar de barrio, de todas formas la zona ya nos indica una ubicación de la vivienda.

En una primera instancia creamos una tabla de contigencia para las variables selccionadas.

tabla_correspondencia <- ftable(vivienda$tipo, vivienda$zona, vivienda$estrato)
tabla_correspondencia
##                              3    4    5    6
##                                              
## Apartamento Zona Centro     14    7    3    0
##             Zona Norte     337  246  497  116
##             Zona Oeste      29   58  228  695
##             Zona Oriente    58    2    1    1
##             Zona Sur       201 1091 1028  460
## Casa        Zona Centro     89    5    1    1
##             Zona Norte     228  151  226   40
##             Zona Oeste      24   23   40   43
##             Zona Oriente   270    5    1    0
##             Zona Sur       174  496  600  448

A continuación realizamos un análisis de correspondencia múltiple (MCA) ya que se trata de más de dos variables.

# Realizar el Análisis de Correspondencia Múltiple (MCA)
mca <- MCA(vivienda[, c("tipo", "zona", "estrato")], graph = FALSE)

# Resumen del análisis
summary(mca)
## 
## Call:
## MCA(X = vivienda[, c("tipo", "zona", "estrato")], graph = FALSE) 
## 
## 
## Eigenvalues
##                        Dim.1   Dim.2   Dim.3   Dim.4   Dim.5   Dim.6   Dim.7
## Variance               0.570   0.460   0.377   0.333   0.319   0.266   0.198
## % of var.             21.370  17.249  14.147  12.501  11.969   9.991   7.436
## Cumulative % of var.  21.370  38.619  52.767  65.268  77.237  87.228  94.663
##                        Dim.8
## Variance               0.142
## % of var.              5.337
## Cumulative % of var. 100.000
## 
## Individuals (the 10 first)
##                 Dim.1    ctr   cos2    Dim.2    ctr   cos2    Dim.3    ctr
## 1            |  2.377  0.125  0.588 |  1.019  0.028  0.108 |  0.537  0.010
## 2            |  2.377  0.125  0.588 |  1.019  0.028  0.108 |  0.537  0.010
## 3            |  2.377  0.125  0.588 |  1.019  0.028  0.108 |  0.537  0.010
## 4            |  0.160  0.001  0.014 | -0.729  0.015  0.299 |  0.895  0.027
## 5            | -0.072  0.000  0.003 | -0.297  0.002  0.045 | -1.306  0.057
## 6            | -0.072  0.000  0.003 | -0.297  0.002  0.045 | -1.306  0.057
## 7            | -0.059  0.000  0.002 | -0.499  0.007  0.112 | -0.500  0.008
## 8            | -0.072  0.000  0.003 | -0.297  0.002  0.045 | -1.306  0.057
## 9            |  0.428  0.004  0.077 | -0.368  0.004  0.057 | -0.936  0.029
## 10           |  0.428  0.004  0.077 | -0.368  0.004  0.057 | -0.936  0.029
##                cos2  
## 1             0.030 |
## 2             0.030 |
## 3             0.030 |
## 4             0.450 |
## 5             0.867 |
## 6             0.867 |
## 7             0.112 |
## 8             0.867 |
## 9             0.370 |
## 10            0.370 |
## 
## Categories (the 10 first)
##                  Dim.1     ctr    cos2  v.test     Dim.2     ctr    cos2
## Apartamento  |  -0.409   6.240   0.296 -48.430 |   0.052   0.127   0.005
## Casa         |   0.723  11.047   0.296  48.430 |  -0.093   0.225   0.005
## Zona Centro  |   2.672   6.313   0.110  29.490 |   1.100   1.326   0.019
## Zona Norte   |   0.443   2.668   0.059  21.709 |  -0.182   0.557   0.010
## Zona Oeste   |  -1.127  10.667   0.213 -41.110 |   1.732  31.212   0.503
## Zona Oriente |   2.994  22.329   0.399  56.251 |   1.494   6.888   0.099
## Zona Sur     |  -0.192   1.224   0.048 -19.579 |  -0.506  10.514   0.335
## 3            |   1.665  29.101   0.606  69.366 |   0.671   5.858   0.099
## 4            |  -0.168   0.435   0.010  -8.949 |  -0.885  14.908   0.279
## 5            |  -0.197   0.753   0.019 -12.358 |  -0.474   5.376   0.111
##               v.test     Dim.3     ctr    cos2  v.test  
## Apartamento    6.211 |  -0.246   3.421   0.107 -29.178 |
## Casa          -6.211 |   0.436   6.057   0.107  29.178 |
## Zona Centro   12.143 |   1.179   1.856   0.021  13.012 |
## Zona Norte    -8.915 |  -1.393  39.768   0.586 -68.196 |
## Zona Oeste    63.178 |  -0.057   0.041   0.001  -2.071 |
## Zona Oriente  28.069 |   0.780   2.291   0.027  14.661 |
## Zona Sur     -51.549 |   0.494  12.241   0.320  50.375 |
## 3             27.961 |  -0.226   0.812   0.011  -9.425 |
## 4            -47.053 |   0.719  11.985   0.184  38.206 |
## 5            -29.660 |  -0.767  17.195   0.291 -48.037 |
## 
## Categorical variables (eta2)
##                Dim.1 Dim.2 Dim.3  
## tipo         | 0.296 0.005 0.107 |
## zona         | 0.739 0.697 0.636 |
## estrato      | 0.675 0.678 0.388 |

Con el propósito de poder interpretar estos resultados de una mejor manera, se realiza un gráfico biplot del análisis obtenido:

fviz_mca_biplot(mca, label = "var", repel = TRUE, 
                title = "Biplot del Análisis de Correspondencia Múltiple",
                gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"))

A partir del análisis de correspondencia se puede identificar que “Zona Oriente” y “Zona Centro” comparten características similares en térmidos de tipos de vivienda y estrados, además el estrato más común en estas zonas es el 3.

Por otro lado, la “Zona Sur”, “Zona Norte” y “Zona Oeste” están más relacionadas con los estratos más altos, especialmente la “Zona Oeste” en la que predominaría teóricamente el estrado 6.

Conclusiones

A partir de los análisis expuestos a través de todo el reporte, se puede concluir que: