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.
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:
1. 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.
2. 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.
3. 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.
4. 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.
Dado que usaremos los datos vivienda del paqueteMODELOS, primero investiguemos un poco sobre el mismo. A través del comando ?vivienda, que nos dice que se trata de datos extraídos mediante web scraping que contiene características de viviendas de Cali. Contiene \(8322\) registros y \(13\) variables.
| Nombre_Variable | Descripcion |
|---|---|
| id | Identificador único de la vivienda |
| zona | Zona de la ciudad |
| piso | Piso en la que está ubicada la vivienda |
| estrato | Estrato socioeconómico |
| preciom | Precio en millones de pesos |
| areaconst | Área construida en metros cuadrados |
| parqueaderos | Número de parqueaderos |
| banios | Número de baños |
| habitaciones | Número de habitaciones |
| tipo | Tipo de vivienda |
| barrio | Barrio |
| longitud | Coordenada de longitud |
| latitud | Coordenada de latitud |
Dado que ya sabemos un poco de información sobre el dataset, vamos a verificarlo:
[1] "El dataset contienes 13 atributos y 8322 instancias"
Entonces, haremos una mirada más profunda a través de la función str().
str(datos)
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>
En este caso, podemos verificar que existen dos tipos de variables, las que son numéricas y las que son del tipo carácter. En este caso, como la variable id es simplemente un identificador único de vivienda podemos eliminarlo para futuros análisis, puesto que no nos ayudará respecto a la identificación de patrones. Para hacer esto, y como realizaremos un análisis separado para cada tipo, diferenciaremos unas de otro:
datos_numericos <- datos %>% select(where(is.numeric), -id)
datos_categoricos <- datos %>% select(where(is.factor), where(is.character)) %>%
mutate_if(is.character, as.factor)
sprintf("Existen %.0f variables numéricas y %.0f categóricas", length(datos_numericos), length(datos_categoricos))
[1] "Existen 8 variables numéricas y 4 categóricas"
En este proceso, eliminaremos la variable innecesaria y haremos que las variables categóricas sean del tipo factor, puesto que se tratan de variables con diversos niveles. Ahora que ya sabemos a dónde pertenece cada columna, podemos entrar a verificar los datos faltantes.
Para esto, usaremos el paquete mice, que implementa diferentes métodos para lidiar con datos faltantes. Para ello, la función md.pattern() grafica el patrón de datos faltantes respecto a las variables.
md.pattern(datos_numericos, rotate.names = TRUE)
Figure 1: Gráfica de patrón de valores faltantes en variables numéricas.
Como podemos ver en la gráfica 1, todas las variables numéricas tienes datos faltantes. parqueaderos es la que mayor cantidad posee. Entonces, para hacer la imputación de datos, debemos identificar si son \(MCAR\) o \(MNAR\). Por tanto, usamos del paquete naniar el test mcar_test() para validar la hipótesis nula de que los datos faltantes son \(MCAR\). Eso lo haremos a través de un nivel de significación de \(\alpha = 0,05\).
if(valor_mcar <- mcar_test(datos)$p.value <= 0.05){
sprintf("Se rechaza la hipótesis nula. Las datos faltantes son MNAR.")
} else {
sprintf("Se acepta la hipótesis nula. Las datos faltantes son MCAR")
}
[1] "Se rechaza la hipótesis nula. Las datos faltantes son MNAR."
Por lo tanto, la pérdida de datos no aleatorios debe manejarse correctamente. Podría realizarse una imputación respecto a cada variable. Por eso es importante determinar los datos faltantes respecto a cada variable. Como se observa en la gráfica anterior, la variable piso y parqueaderos son los que más datos faltantes tienen, que podría haberse dado por falta de información (o datos omitidos). Usaremos el método de imputación del paquete mice:
datos_numericosTemp <- mice(datos_numericos, m=5, maxit=50, meth='pmm', seed=500)
Los argumentos son sencillos: m es el número de imputaciones, donde \(5\) es el número por defecto, maxit la cantidad de iteraciones máximas para que el algoritmo converga, meth el método de la imputación, donde 'pmm' se refiere a \(predictive\:mean\:matching\), y la semilla para reproducibilidad. A través de este método, haremos diversas imputaciones a través de la cantidad de iteraciones para encontrar los valores más óptimos.
Ya hecha la imputación, tomaremos esos datos temporales para el dataset. Para esto se usa la función complete() que transforma los valores faltantes implícitos en valores explícitos.
datos_numericos <- complete(datos_numericosTemp, 1)
Esta vez, ya no existen valores faltantes para las variables numéricos.
md.pattern(datos_numericos, rotate.names = TRUE)
Figure 2: Gráfica de patrón de valores faltantes en variables numéricas.
Verificamos si las variables categóricas tiene valores faltantes:
md.pattern(datos_categoricos, rotate.names = TRUE)
Figure 3: Gráfica de patrón de valores faltantes en variables categóricas.
Para el caso de las variables categóricas, usaremos el método 'polyreg'. Sin embargo, como se puede observar en el dataset, el atributo barrio tiene demasiados niveles, un total de \(437\), lo que haría muy dificil la imputación. Sin embargo, solo posee \(3\) datos faltantes, así que haremos la imputación de las demás variables categóricas excepto barrio, ya que esos tres valores pueden manejarse de diferente manera.
sprintf("La variable Barrio posee un total de %0.f niveles y solo %0.f datos faltantes", length(unique(datos_categoricos$barrio)), sum(is.na(datos_categoricos$barrio)))
[1] "La variable Barrio posee un total de 437 niveles y solo 3 datos faltantes"
datos_categoricos_sinbarrio <- datos_categoricos %>% select(-barrio)
Como las variables tienen varios niveles, el método 'polyreg' puede ser lento, por eso reduciremos los valores de m y de maxit.
datos_categoricosTemp <- mice(datos_categoricos_sinbarrio, m=3, maxit=10, meth='polyreg', seed=500)
datos_categoricos_imputados <- complete(datos_categoricosTemp, 1)
Asimismo, verificamos si la imputación fue manejada correctamente:
md.pattern(datos_categoricos_imputados, rotate.names = TRUE)
Figure 4: Gráfica de patrón de valores faltantes en variables categóricas.
Como observamos, ya no hay más datos faltantes que manejar, excepto los de las variable barrio. Para esta variable, haremos una imputación de la moda. Esto se hace para no perder los datos, puesto que si los eliminamos las variables numéricas y categóricas tendrán diferente cantidad de datos.
# Combinamos los datos nuevamente
datos_categoricos <- cbind(datos_categoricos_imputados, barrio = datos_categoricos$barrio)
# Encontramos la moda de la variable barrio
moda_barrio <- names(sort(table(datos_categoricos$barrio), decreasing = TRUE)[1])
# imputamos la moda en los valores faltantes
datos_categoricos$barrio[is.na(datos_categoricos$barrio)] <- moda_barrio
# Graficamos si hay o no faltantes
md.pattern(datos_categoricos, rotate.names = TRUE)
Figure 5: Gráfica de faltantes en variables categóricas.
Finalmente, ya que no existen datos faltantes y todas se han imputado correctamente, iniciaremos el análisis correspondiente de los datos.
El análisis de componentes principales (\(PCA\)) se aplica como parte del análisis exploratorio de datos. El objetivo es reducir la dimensionalidad (variables) sin perder mucha cantidad de información (varianza) que expliquen gran parte de la variabilidad de los datos. Cada dimensión o componente principal será una combinación lineal de las variables originales, y no estarán correlacionadas entre sí.
En este análisis solo usaremos las variables numéricas y escalaremos los datos para que cada una de ellas tengan la misma importancia; sin el escalamiento, las variables con una varianza o un rango de valores más grande dominarían el cálculo de las componentes principales, lo que podría llevar a resultados sesgados. Después, realizaremos el \(PCA\) a través de la función prcomp(), y con la función get_pca_var() para extraer el resultado de cada variable.
# Escalamos los datos
datos_numericosZ <- scale(datos_numericos)
# Realizamos el PCA
res.pca <- prcomp(datos_numericosZ)
# Extraemos el resultado de cada variable
var <- get_pca_var(res.pca)
Para visualizar el análisis, lo haremos con un Scree Plot, que grafica la varianza en contra de los números de componentes principales.
scree_plot <- factoextra::fviz_eig(res.pca,
addlabels = TRUE)
scree_plot
Figure 6: Scree Plot del PCA.
sprintf("Los compontentes PC1 y PC2 explican %.1f %% de los datos", sum(scree_plot$data[1:2,]$eig))
[1] "Los compontentes PC1 y PC2 explican 64.5 % de los datos"
A través de la gráfica 6 verificamos que el componente 1 y el 2 son los que más atribuyen, puesto que el punto de inflexión, o codo, se encuentra después del segundo componente. Por lo tanto, con los componentes principales \(PC1\) y \(PC2\) explicamos cerca del \(64 \:\%\) de los datos.
Ahora el Biplot nos ayudará a entender la relación entre atributos.
factoextra::fviz_pca_var(res.pca,
col.var = "contrib",
gradient.cols = c("#FF7F00", "#034D94"),
repel = TRUE)
Figure 7: Gráfica Biplot del PCA.
En la gráfica 7 verificamos que las varaibles preciom, areaconst, parqueaderos y banios están correlacionadas positivamente pues van en la misma dirección, hacia la derecha; es decir, que la \(Dim1\) respresenta viviendas con un tamaño, precio y cantidad de baños más altos. Para la \(Dim2\), tenemos hacia la izquierda la longitud y latitud, indicando que la ubicación geográfica también es importante, y sucede también con la cantidad de habitaciones. En otras palabras, preciom, areaconst, parqueaderos y banios están fuertemente correlacionados; estrato también tiene una correlación positiva con este grupo lo que significa que las propiedades más caras, más grandes y con más baños y parqueaderos tienden a estar en estratos más altos. Además, longitud y latitud están en dirección diferente a las del primer grupo, lo que sugiere que las características geográficas no están fuertemente correlacionadas con el valor y el tamaño de la propiedad en el mismo sentido que el resto de las variables.
Por lo tanto, la \(Dim1\) relaciona el valor, tamaño y estrato y la \(Dim2\), la ubicación geográfica y, en menor cantidad, la cantidad de habitaciones. Para fortalecer esta lectura, realizaremos un diagrama de barras de las contribuciones en porcentaje de cada atributo respecto a las variables.
fviz_contrib(res.pca,
choice = "var",
axes = 1,
fill = "lightblue",
color = "black",
top = 10,
ggtheme = theme_minimal()) +
labs(title = "Contribución de las variables a la Dimensión 1")
Figure 8: Gráfica de barras para PC1.
fviz_contrib(res.pca,
choice = "var",
axes = 2,
fill = "lightblue",
color = "black",
top = 10,
ggtheme = theme_minimal()) +
labs(title = "Contribución de las variables a la Dimensión 2")
Figure 9: Gráfica de barras para PC1.
En las gráficas 8 y 9 podemos relacionar con más certeza qué variables contribuyen más en cada dimensión. En este caso, las contribuciones se representan respecto al porcentaje total y la línea roja que aparece en ambas gráficas es un referencia que corresponde al valor esperado si la contribución fuera uniforme. Como referencia, cualquier columna que esté por encima de la línea podría ser considerada como importante para la contribución de la dimensión. Específicamente, para \(Dim1\) se diferencia las viviendas respecto al tamaño, al precio, a la cantidad de baños y parqueaderos, y la \(Dim2\) a la cantidad de habitaciones, su ubicación geográfica y el estrato socioeconómico.
Para mayor entendimiento, realizaremos una gráfica de individuales, o de cada vivienda. Para esto, encontraremos las viviendas que se encuentran en los extremos de estas dimensiones.
# Encontramos los individuales
ind <- get_pca_ind(res.pca)
coord_clientes <- ind$coord
# Encontrar los 4 clientes más extremos
idx_extremos <- c(
which.max(coord_clientes[, "Dim.1"]), # Extremo derecho
which.min(coord_clientes[, "Dim.1"]), # Extremo izquierdo
which.max(coord_clientes[, "Dim.2"]), # Extremo superior
which.min(coord_clientes[, "Dim.2"]) # Extremo inferior
)
datos_extremos <- datos_numericos[idx_extremos, ]
rownames(datos_extremos) <- paste("Vivienda", rownames(datos_extremos))
coordenadas_extremos <- coord_clientes[idx_extremos, ]
formattable(datos_extremos)
| estrato | preciom | areaconst | parqueaderos | banios | habitaciones | longitud | latitud | |
|---|---|---|---|---|---|---|---|---|
| Vivienda 1762 | 6 | 1800 | 1586 | 10 | 4 | 5 | -76.53798 | 3.35961 |
| Vivienda 3282 | 3 | 165 | 80 | 1 | 0 | 0 | -76.47766 | 3.42456 |
| Vivienda 8073 | 3 | 370 | 1440 | 1 | 4 | 10 | -76.49815 | 3.46343 |
| Vivienda 5217 | 6 | 285 | 57 | 1 | 2 | 1 | -76.54402 | 3.34078 |
En la tabla 1 hemos encontrado que hay cuatro viviendas en los extremos de estas dimensiones.
fviz_pca_ind(res.pca,
col.ind = "lightgray") +
geom_point(data = as.data.frame(coordenadas_extremos[1:2, ]),
aes(x = Dim.1, y = Dim.2),
color = "red", size = 3) +
geom_text(data = as.data.frame(coordenadas_extremos[1:2, ]),
aes(x = Dim.1, y = Dim.2, label = rownames(coordenadas_extremos[1:2, ])),
hjust = 1.2, vjust = 1.2, color = "red") +
geom_point(data = as.data.frame(coordenadas_extremos[3:4, ]),
aes(x = Dim.1, y = Dim.2),
color = "blue", size = 3) +
geom_text(data = as.data.frame(coordenadas_extremos[3:4, ]),
aes(x = Dim.1, y = Dim.2, label = rownames(coordenadas_extremos[3:4, ])),
hjust = 1.2, vjust = 1.2, color = "blue")
Figure 10: Gráfica de viviendas en extremos.
Además, la gráfica 10 nos muestra la ubicación de cada uno. Entonces, para los casos extremos, verificamos el \(PC1\): en la parte derecha está la vivienda \(1762\), con un estrato \(6\), un precio en millones de \(1800\) y un área de \(1586\:m^{2}\), y a la parte inferior izquierda se encuentra la vivienda \(5217\), quien tiene el mismo estrato, precio muy inferior de \(285\), un área de \(57\:m^{2}\). Entonces, podemos aseverar que el extremo del \(PC1\) está relacionado a los viviendas lujosas sin tener en cuenta el estrato social. En cambio, para \(PC2\), la vivienda \(8073\) es de estrato \(3\), tiene un número de habitaciones elevado, de \(10\), y un área de \(1440\:m^{2}\) y \(4\) baños, mas la vivienda \(3282\) tiene el mismo estrato, un área muy inferior de \(80\), no tiene habitaciones ni baños, por lo que el extremo de \(PC2\) relaciona las viviendas con varias habitaciones y baños.
Para el caso de los conglomerados, encontraremos las distancias de los atributos a través del método Euclideano, que es la forma más común. Sin embargo, el análisis se realizará con todos los datos del dataset, lo que hará que sean muchos para un proceso de determinación de clústeres, por lo que usaremos el método codo: Lo que hacemos es calcular la suma de cuadrados de las varianzas. De esa forma, a través de la función kmeans, podremos determinar la cantidad de clústeres. Entonces, a través de una gráfica deberemos determinar el codo.
Para mayor simplicidad, seguimos usando la librería factorextra que posee un método para este propósito:
fviz_nbclust(datos_numericosZ,
FUN = kmeans,
method = "wss")
Figure 11: Número óptimo de clusters
La gráfica 11 nos muestra cómo se comporta cada cantidad de \(k\), pero debemos determinar el “codo” visualmente, lo que sería un poco tediosos y podría influir en errores. Entonces, usaremos el método 'silhouette' que nos brinda el verdadero número óptimo de clústeres.
grafica_optimoCluster <- fviz_nbclust(datos_numericosZ,
kmeans,
method = "silhouette") +
theme_minimal()
grafica_optimoCluster
Figure 12: Número óptimo de clusters
Entonces, la gráfica 12 nos demuestra que la cantidad óptima de \(k\) es \(2\). De esta forma, podemos hacer el análisis de conglomerados:
# Obtenemos el K óptimo
k_optimo <- as.numeric(grafica_optimoCluster$data$clusters[which.max(grafica_optimoCluster$data$y)])
# Convertimos los datos escalados en un dataframe
datos_numericosZ_df <- as.data.frame(datos_numericosZ)
# Calcular distancias
distancias <- dist(datos_numericosZ_df, method = "euclidean")
# Clustering jerárquico
hc_emp <- hclust(distancias, method = "complete")
# Asignación de clusters
cluster_assigments <- cutree(hc_emp, k = k_optimo)
# Obtención de coordenadas PCA
coordenadas_pca <- as.data.frame(res.pca$x)
# Asignación de clusters a coordenadas PCA
assigned_cluster_pca <- coordenadas_pca %>%
mutate(cluster = as.factor(cluster_assigments))
Teniendo ya las coordenadas de los individuales del \(PCA\) y la asignación de clústeres, entonces podemos graficarlo:
ggplot(assigned_cluster_pca, aes(x = PC1, y = PC2, color = cluster)) +
geom_point(size = 2) +
geom_vline(xintercept = 0, linetype = "dashed", color = "black") +
geom_hline(yintercept = 0, linetype = "dashed", color = "black") +
labs(title = "Visualización de Conglomerados en el Espacio PCA",
x = "PC1 (Valor y tamaño de la propiedad)",
y = "PC2 (Ubicación y estrato)") +
labs(title = "Visualización de Conglomerados en el Espacio PCA",
x = "PC1 (Valor y tamaño de la propiedad)",
y = "PC2 (Ubicación y estrato)") +
labs(title = "Visualización de Conglomerados en el Espacio PCA",
x = "PC1 (Valor y tamaño de la propiedad)",
y = "PC2 (Ubicación y estrato)") +
theme_minimal()
Figure 13: Gráfica de agrupación de clústeres.
En tanto, el análisis de conglomerados nos brindó dos clústeres diferentes: el clúster \(1\) referencia a las viviendas que tiene valores “promedios” o “bajos”. Como se ve en la gráfica 13, la mayoría de ellos se ubican en las inmediaciones del origen de la gráfica. En cambio, los pocos valores del clúster \(2\) referencian a esas viviendas que tienen tanto valores, tamaños, ubicación y estrato elevados, respecto a los demás, lo que podría tratarse de esas propiedades lujosas en la ciudad.
Finalmente, encontramos el coeficiente de Silhouette:
sil <- silhouette(cluster_assigments, dist(datos_numericosZ))
sil_avg <- mean(sil[,3])
sprintf("El coeficiente de Silhouette es de %.3f", sil_avg)
[1] "El coeficiente de Silhouette es de 0.547"
Y de la misma manera, podemos graficar el comportamiento del coeficiente en cada clúster.
fviz_silhouette(sil)
cluster size ave.sil.width
1 1 8266 0.55
2 2 56 0.42
Figure 14: Visualización de la información del coeficiente de Silhouette.
Con este coeficiente y la gráfica 14 podemos determinar que, pese a que utilizamos un k óptimo, no es adecuado para la agrupación, por lo que podría significar que las observaciones y sus estimados respecto a la distancia de los clústeres no es la mejor; esto se presenta de manera más visual en la gráfica, puesto que se puede observar los valores del clúster \(2\) están cerca de viviendas del clúster \(1\) que tiene semejantes especificaciones a las “lujosas” antes determinadas. Entonces, perfilaremos cada clúster para poder analizarlos finalmente:
# Encontramos los datos de cada clúster
datos_con_clusters <- datos_numericos %>%
mutate(cluster = as.factor(cluster_assigments))
# Perfiles de los clústeres
perfiles_clusters <- datos_con_clusters %>%
group_by(cluster) %>%
summarise(
n = n(),
across(everything(), \(x) mean(x, na.rm = TRUE))
)
formattable(perfiles_clusters)
| cluster | n | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | longitud | latitud |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 8266 | 4.627147 | 428.2394 | 169.4809 | 1.740019 | 3.095814 | 3.596419 | -76.52860 | 3.417841 |
| 2 | 56 | 5.625000 | 1289.7143 | 984.8750 | 4.767857 | 5.446429 | 4.982143 | -76.52913 | 3.386713 |
Entonces, los dos perfiles de clúster se ajustan a lo antes descrito: el clúster uno refiere a las viviendas con bajos precios, áreas pequeñas, pocos parqueaderos, baños y habitaciones. Y el clúster 2, en menor cantidad, refiere a los de precio alto, áreas extensas, gran cantidad de parqueaderos, baños y habitaciones.
Además, un gráfica con LeafLet podría aclarar la ubicación de estas casas de lujo, como por ejemplo, la cercanía de estas casas respecto a su longitud y latitud en la ciudad de Cali.
paleta_colores <- colorFactor(c("red", "blue"), domain = unique(datos_con_clusters$cluster))
leaflet(datos_con_clusters) %>%
addTiles() %>%
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
color = ~paleta_colores(cluster),
radius = ~ifelse(cluster == 2, 8, 2),
stroke = FALSE,
fillOpacity = ~ifelse(cluster == 2, 0.8, 0.2)
) %>%
addLegend("bottomright",
pal = paleta_colores,
values = ~cluster,
title = "Clústeres")
Figure 15: Mapa de ubicación de viviendas respecto a los clústeres.
El mapa, gráfica 15, demuestra que sí hay una concentración aparente en algunas zonas del clúster \(2\) (puntos en azul), aunque no es exclusivo, puesto que muchas más de ellas del clúster \(1\) existen en sectores cercanos, que podría determinar que no se trata de una inmediación respecto al barrio, sino a la cualidades o amenidades que tiene la vivienda en sí, lo que los perfiles clarifican. Finalmente, podemos afirmar que el clúster \(1\) se relaciona con viviendas sencillas, con poca área y pocas amenidades, y el clúster \(2\) se relaciona con viviendas que denominaremos “lujosas”.
Ahora que ya entendemos el comportamiento de los componente principales (\(PCA\)) y los conglomerados, podemos seguir el análisis de las variables categóricas a partir de un análisis de correspondencia. Solo existen \(4\) variables categóricas: vivienda, zona, piso y barrio, sin embargo, excluiremos la columna piso, como se pide en el literal \(3\), y al mismo tiempo verificaremos la cantidad de niveles en cada variable.
# Eliminamos la variable no deseada
datos_correspondencia <- datos_categoricos %>% select(-piso)
# Creamos una función para contar los niveles de cada columna
contar_niveles <- function(x) {
if (is.factor(x)) {
return(nlevels(x))
}
}
# Aplicamos la función a cada columna y guardamos el resultado en una tabla
niveles_por_columna <- data.frame(
Niveles = sapply(datos_correspondencia, contar_niveles)
)
formattable(niveles_por_columna)
| Niveles | |
|---|---|
| zona | 5 |
| tipo | 2 |
| barrio | 436 |
Como se mira en la tabla 3, la columna barrio posee \(436\) niveles, que para un análisis de correspondencia puede ser computacionalmente costoso. Entonces, reduciremos la cantidad de niveles solo quedándonos con aquellos que tiene mayor frecuencia, en este caso, superior a 100.
frecuencia_barrio <- datos_correspondencia %>% count(barrio, sort = TRUE)
formattable(head(frecuencia_barrio))
| barrio | n |
|---|---|
| valle del lili | 1011 |
| ciudad jardín | 516 |
| pance | 409 |
| la flora | 366 |
| santa teresita | 262 |
| el caney | 208 |
n_mantener <- sum(frecuencia_barrio$n >= 100)
datos_correspondencia <- datos_correspondencia %>%
mutate(barrio = fct_lump(barrio, n = n_mantener))
sprintf("Nos hemos quedado con %0.f niveles en el atributo barrio", nlevels(datos_correspondencia$barrio))
[1] "Nos hemos quedado con 16 niveles en el atributo barrio"
Los demás quedarán con la etiqueta “Other”.
Optamos por un análisis de correspondencia múltiple, pues tenemos tres atributos para analizar.
res.mca <- MCA(datos_correspondencia)
Warning: ggrepel: 8 unlabeled data points (too many overlaps). Consider
increasing max.overlaps
Figure 16: Gráficas de MCA.
Figure 17: Gráficas de MCA.
Figure 18: Gráficas de MCA.
En la tercera gráfica de ?? vemos que los atributos barrio y zona están fuertemente relacionados en ambas dimensiones, y el atributo tipo no tiene la suficiente relación con las demás, y su importancia es bastante nula. Entonces, medimos el grado de representatividad de cada variable.
fviz_screeplot(res.mca, addlabels = TRUE, ylim = c(0, 10)) +
ggtitle("") +
ylab("Porcentaje de varianza explicado") + xlab("Ejes")
Figure 19: Scree Plot del MCA.
Por lo tanto, el codo de esta gráfica se presente en el cuarto eje, por lo que nos quedaremos hasta el tercero. Estos tres combinados explican cerca del \(24,4 \:\%\) de los datos, que corresponde al valor restante respecto a las variables numéricas que obtienen \(64 \:\%\) de los datos; entonces, barrio y zona son las variables más importantes para las dos primeras dimensiones y nos ayudarán a explicar que la diferenciación de viviendas tiene una importancia latente con la zona y el barrio en donde se encuentra, cosa que no habíamos podido entender en el análisis de conglomerados anterior.
El \(PCA\) reveló que los dos primeros componente principales explican cerca del \(64 \:\%\) de la varianza total de los datos, lo que permite una interpretación sólida sobre el mercado. La dimensión 1 tiene como factores significativos el precio, área construida, baños y parqueaderos, puesto que agrupa las viviendas respecto a su valor y tamaño. La dimensión 2 está asociada con la longitud, latitud y el estrato, puesto que las agrupa según su ubicación geográfica y nivel socioeconómico.
El análisis de conglomerados identificó dos segmentos de mercado con alta separación entre ellos. El clúster 1 (al que llamaremos segmento estándar) es el más grande y representa la mayoría de propiedades del mercado. Sus características son cercanas a la media general, lo que lo indica en un marcado masivo. El clúster 2 es mucho más pequeño, siendo cerca del \(0,5 \:\%\) de las viviendas, se asocia con altos precios (tres veces mayor), área (seis veces mayor) y amenidades diversas; este segmento define las propiedades de lujo, pero no se limita exclusivamente al estrato socioeconómico.
El \(MCA\) determinó una fuerte relación clave entre barrio y zona, que confirma que la ubicación es un factor determinante en la estructura del mercado, y también la independencia del tipo (es decir: apartamento, casa, etc.), puesto que las propiedades lujosas pueden encontrarse en casas o apartamentos, sin restricción de una zona específica.
Por lo tanto, el mercado inmobiliario de Cali está claramente divido en un segmento de propiedades estándar (las que llamamos promedio) y un de propiedades lujosas. Hay una fuerte importancia por la ubicación, lo que diferencia las diversas viviendas y el segmento lujoso se define por precio, tamaño y amenidades elevadas. Respecto a ello, se pueden desarrollar las siguientes estrategias:
Desarrollar campañas masivas que destaquen la relación calidad-precio y las características de las propiedades promedio o estándar, y una más de nicho, dirigida a las exclusividad de viviendas, utilizando un lenguaje hacia la suntuosidad y ostentación de las propiedades.
En un futuro análisis, identificar las zonas de mayor concentración de propiedades lujosas puesto que son más rentables al desarrollo del mercado inmobiliario.