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:
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.
data(vivienda)
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>
library(knitr)
kable(head(vivienda))
| id | zona | piso | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | barrio | longitud | latitud |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1147 | Zona Oriente | NA | 3 | 250 | 70 | 1 | 3 | 6 | Casa | 20 de julio | -76.51168 | 3.43382 |
| 1169 | Zona Oriente | NA | 3 | 320 | 120 | 1 | 2 | 3 | Casa | 20 de julio | -76.51237 | 3.43369 |
| 1350 | Zona Oriente | NA | 3 | 350 | 220 | 2 | 2 | 4 | Casa | 20 de julio | -76.51537 | 3.43566 |
| 5992 | Zona Sur | 02 | 4 | 400 | 280 | 3 | 5 | 3 | Casa | 3 de julio | -76.54000 | 3.43500 |
| 1212 | Zona Norte | 01 | 5 | 260 | 90 | 1 | 2 | 3 | Apartamento | acopi | -76.51350 | 3.45891 |
| 1724 | Zona Norte | 01 | 5 | 240 | 87 | 1 | 3 | 3 | Apartamento | acopi | -76.51700 | 3.36971 |
dimension <- dim(vivienda)
dimension
## [1] 8322 13
# Calcula el número de datos faltantes por variable
valores_faltantes <- colSums(is.na(vivienda))
# Muestra el resultado
kable(valores_faltantes)
| x | |
|---|---|
| id | 3 |
| zona | 3 |
| piso | 2638 |
| estrato | 3 |
| preciom | 2 |
| areaconst | 3 |
| parqueaderos | 1605 |
| banios | 3 |
| habitaciones | 3 |
| tipo | 3 |
| barrio | 3 |
| longitud | 3 |
| latitud | 3 |
En este caso podemos ver que las variables con mayor cantidad de datos faltantes es piso con 2.638 registros faltantes y parqueaderos con 1.605 registros faltantes.
# Obtener el tipo de variable de cada columna
column_types <- sapply(vivienda, class)
# Imprimir los tipos de variable
kable(column_types)
| x | |
|---|---|
| id | numeric |
| zona | character |
| piso | character |
| estrato | numeric |
| preciom | numeric |
| areaconst | numeric |
| parqueaderos | numeric |
| banios | numeric |
| habitaciones | numeric |
| tipo | character |
| barrio | character |
| longitud | numeric |
| latitud | numeric |
Distribución por barrio
Ahora realizaremos un analisis de la distribución del número de inmuebes por barrio, ya que es un tipo de dato categorico y posee tres registros faltantes. Y procederemos a limpiar los datos.
# Utiliza la función table() para obtener la frecuencia de cada barrio
frecuencia_barrios <- table(vivienda$barrio)
# Ordena la frecuencia de los barrios de mayor a menor
frecuencia_ordenada <- sort(frecuencia_barrios, decreasing = TRUE)
# Muestra la frecuencia de cada barrio
kable(head(frecuencia_ordenada))
| Var1 | Freq |
|---|---|
| valle del lili | 1008 |
| ciudad jardín | 516 |
| pance | 409 |
| la flora | 366 |
| santa teresita | 262 |
| el caney | 208 |
Ya que el porcentaje de datos faltantes perteneces a la variable piso, y no podemos eliminar esa cantidad de valores desconocidos, para no sesgar los datos imputaremos esta columna.
# Eliminar filas con más del 30% de sus variables con valor NA
vivienda_input <- vivienda[rowSums(is.na(vivienda)) < (ncol(vivienda) - 4), ]
# Convertir todos los valores a minúsculas para la variable barrio
vivienda_input$barrio <- tolower(vivienda_input$barrio)
# Declarar función para eliminar tildes
eliminar_tildes <- function(cadena) {
conversion <- function(letra) {
switch(letra,
á = "a",
é = "e",
í = "i",
ó = "o",
ú = "u",
letra)
}
cadena <- strsplit(cadena, "")[[1]]
cadena <- sapply(cadena, conversion)
return(paste(cadena, collapse = ""))
}
# Ejecutar función eliminar_tildes a la variable barrio, eliminar caracteres especiales y verificar nuevos valores únicos
vivienda_input$barrio <- sapply(vivienda_input$barrio, eliminar_tildes)
vivienda_input$barrio <- sub('é', 'e', vivienda_input$barrio)
vivienda_input$barrio <- sub('√∫', 'u', vivienda_input$barrio)
# Para la variable númerica parqueaderos se convierten los valores NA en cero, para evitar errores en los análisis posteriores.
vivienda_input$parqueaderos[is.na(vivienda_input$parqueaderos)] <- 0
#pisos
vivienda_input <- subset(vivienda_input, select = -piso)
Ya despues de la limpieza, nos queda una dimensión de 8319 x 12
dimension2 <- dim(vivienda_input)
dimension2
## [1] 8319 12
Distribución por zonas
A continuación se muestra la distribución del número de inmuebles por zona. La mayor proporción (56.8%) se observó en la zona sur, seguida de la zona norte (23.1%), zona oeste (14.4%), zona oriente (4.2%) y zona centro (1.5%).
# Conteo de valores en la columna 'zona'
zona <- table(vivienda_input$zona)
# Convertir a dataframe
zona_df <- as.data.frame(zona)
# Seleccionar colores para las secciones
colores <- c("#FF6F61", "#6B5B95", "#88B04B", "#F7CAC9", "#92A8D1", "#955251", "#B565A7", "#009B77", "#DD4124", "#D65076")
# Crear el diagrama de sectores con etiquetas en la mitad
pie(zona_df$Freq,
labels = paste0(zona_df$Var1, " (", round(zona_df$Freq/sum(zona_df$Freq)*100, 1), "%)"),
col = colores, clockwise = TRUE, radius = 1, init.angle = 90)
# Leyenda
leyenda_etiquetas <- zona_df$Var1
legend("topright", legend = leyenda_etiquetas, fill = colores, bty = "n")
Distribución por estrato del inmuble
A continuación se puede concluir que el estrato con el mayor porcentaje de inmuebles es el cinco, con un 33%, seguido por el estrato 4 (26%), el estrato 6 (24%) y el estrato 3 (17%).
# Conteo de valores en la columna 'estrato'
estrato <- table(vivienda_input$estrato)
estrato_df <- as.data.frame(estrato)
# Obtener los valores de la columna "Valores"
valores <- round(estrato_df$Freq / sum(estrato_df$Freq), 2)
# Etiquetas para las barras
etiquetas <- estrato_df$Var1
# Colores personalizados para las barras
colores <- c("#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf")
# Crear el diagrama de barras
barplot(valores,
names.arg = etiquetas,
col = colores,
xlab = "Estrato",
ylab = "Proporción",
main = "Proporción de Estratos")
# Agregar el valor a cada barra
text(x = 1:length(valores),
y = valores,
labels = paste0(valores*100, "%"),
pos = 3,
col = "black")
Distribución por tipo de inmueble
tipo_inmueble <- table(vivienda_input$tipo )
tipo_inmueble_df <- as.data.frame(tipo_inmueble )
kable(tipo_inmueble_df)
| Var1 | Freq |
|---|---|
| Apartamento | 5100 |
| Casa | 3219 |
# Seleccionar solo las columnas deseadas para el cálculo de la correlación
columnas_s <- vivienda_input[, c("preciom", "areaconst", "habitaciones", "banios")]
# Calcular la matriz de correlación redondeada a 1 decimal
matriz_correlacion <- round(cor(columnas_s), 1)
# Calcular la matriz de correlación
matriz_correlacion <- cor(columnas_s)
# Cargar la librería corrplot
library(corrplot)
# Crear el mapa de calor de la matriz de correlación
corrplot(matriz_correlacion, method = "color", type = "upper",
tl.col = "black", tl.srt = 45, tl.pos = "lt",
diag = FALSE, addCoef.col = "black",
title = "Mapa de Calor de Correlación")
chart.Correlation(columnas_s, histogram = TRUE, method = "pearson")
## Warning in par(usr): argument 1 does not name a graphical parameter
## Warning in par(usr): argument 1 does not name a graphical parameter
## Warning in par(usr): argument 1 does not name a graphical parameter
## Warning in par(usr): argument 1 does not name a graphical parameter
## Warning in par(usr): argument 1 does not name a graphical parameter
## Warning in par(usr): argument 1 does not name a graphical parameter
Basándonos en el análisis de correlación, podemos inferir lo siguiente:
El coeficiente de Pearson, que mide la asociación lineal, revela que el precio está positivamente correlacionado con el área construida (0.7), el número de habitaciones (0.3) y el número de baños (0.7). Esto quiere decir que a medida que aumenta el área construida, así como el número de habitaciones y baños, también aumenta el precio del inmueble.
Asimismo, la correlación entre el área construida y el número de habitaciones es de 0.5, mientras que con el número de baños es de 0.6. Estos valores indican que a medida que la superficie construida aumenta, también lo hacen el número de habitaciones y baños.
Por último, la correlación entre el número de habitaciones y el número de baños es de 0.6, lo que señala que a medida que aumenta el número de habitaciones, también tiende a aumentar el número de baños.
Análisis de Componentes Principales (PCA)
A continuación, se procederá con un Análisis de Componentes Principales para identificar posibles agrupaciones de inmuebles según similitudes en los datos
# Seleccionar las variables de interés
variables <- vivienda_input[, c("preciom", "areaconst", "habitaciones", "banios")]
# Realizar el PCA
pca_resultados <- prcomp(variables, scale. = TRUE)
# Resumen de los resultados
summary(pca_resultados)
## Importance of components:
## PC1 PC2 PC3 PC4
## Standard deviation 1.6454 0.8659 0.58879 0.44288
## Proportion of Variance 0.6769 0.1874 0.08667 0.04904
## Cumulative Proportion 0.6769 0.8643 0.95096 1.00000
# Gráfico de la varianza explicada
plot(pca_resultados, type = "l", main = "Varianza Explicada por Componente Principal")
PC1 (preciom):
Desviación estándar: 1.6454 Proporción de varianza: 67.69%
El PC1 captura la mayor parte de la varianza en los datos. Representa la dirección de máxima variación en el conjunto de datos. Como la proporción de varianza es alta (67.69%).
PC2 (areaconst):
Desviación estándar: 0.8659 Proporción de varianza: 18.74%
El PC2 representa la segunda mayor fuente de variación en los datos después del PC1. Aunque su desviación estándar es menor que la del PC1, aún captura una cantidad significativa de información sobre la estructura de los datos.
PC3 (habitaciones):
Desviación estándar: 0.58879 Proporción de varianza: 8.67%
El PC3 representa una parte más pequeña de la varianza en los datos en comparación con PC1 y PC2. Aunque su contribución es menor, aún puede ser relevante para capturar ciertas características o patrones específicos en los datos que no están capturados por los primeros dos componentes.
PC4 (banios):
Desviación estándar: 0.44288 Proporción de varianza: 4.90%
El PC4 representa la menor cantidad de varianza explicada en los datos. Aunque su contribución es la más baja, aún puede contener información importante sobre la estructura de los datos, especialmente cuando se considera junto con los otros componentes.
En resumen, estos resultados indican que la mayoría de la variabilidad en los datos se explica mediante los primeros dos componentes principales (PC1 y PC2), mientras que los componentes restantes capturan variabilidad adicional pero en menor medida.
# Insertar los resultados del PCA en un data frame y añadir la variable categórica zona
pca_df_zona <- data.frame(Dim.1 = pca_resultados$x[,1], Dim.2 = pca_resultados$x[,2], zona = vivienda_input$zona)
# Visualizar el PCA en función de la zona con ggplot2
library(ggplot2)
# Crear un vector de colores basado en las zonas
colores_zona <- c("Zona Norte" = "dodgerblue", "Zona Sur" = "darkorchid",
"Zona Oriente" = "darkorange", "Zona Oeste" = "darkgreen",
"Zona Centro" = "firebrick")
# Crear el gráfico de dispersión
ggplot(pca_df_zona, aes(x = Dim.1, y = Dim.2, color = zona)) +
geom_point() +
scale_color_manual(values = colores_zona) +
labs(title = "Gráfico de dispersión PCA en función de la zona",
x = "Dimensión 1",
y = "Dimensión 2") +
theme_minimal()
biplot(pca_resultados, scale=0.5)
fviz_pca_var(pca_resultados, col.var = "cos2",
geom.var = "arrow",
labelsize = 2, repel = FALSE)
# Marca el valor de la contribución de las variables a los componentes con un color personalizado
fviz_screeplot(pca_resultados, addlabels = TRUE, ylim = c(0, 100), main = "Gráfico de Scree Plot",
xlab = "Número de Componentes Principales", ylab = "Varianza Explicada (%)",
barcolor = "red") # Cambia el color de las barras a rojo
A continuación se realizan dos análisis de Conglomerados acerca del precio de la vivienda en función del área, la cantidad de baños:
#library(dplyr)
#library(ggplot2)
# Seleccionar las variables de interés y estandarizarlas
variables_estandarizadas <- vivienda_input %>%
select(precio_metro_cuadrado = preciom, area_construida = areaconst) %>%
scale()
# Crear un dataframe combinado con las variables estandarizadas y la zona
datos_con_zona <- cbind(variables_estandarizadas, zona = vivienda_input$zona)
# Convertir a data frame
datos_con_zona <- as.data.frame(datos_con_zona)
# Calcular la distancia euclidiana
distancia_euclidiana <- dist(variables_estandarizadas, method = 'euclidean')
# Realizar el clustering jerárquico
cluster_jerarquico <- hclust(distancia_euclidiana, method = 'complete')
# Determinar la pertenencia de cada observación al cluster
asignaciones_cluster <- cutree(cluster_jerarquico, k = 4)
# Asignar los clusters al dataframe
datos_con_clusters <- mutate(datos_con_zona, cluster = as.factor(asignaciones_cluster))
# Gráfico de puntos
ggplot(datos_con_clusters, aes(x = area_construida, y = precio_metro_cuadrado, color = cluster, label = cluster)) +
geom_point(size = 4) +
geom_text(vjust = -0.8) +
scale_color_manual(values = c("red", "blue", "green", "purple")) + # Personalizar los colores de los clusters
labs(title = "Clusterización de Precios y Áreas por Zona",
x = "Área Construida (Estandarizada)",
y = "Precio por Metro Cuadrado (Estandarizado)",
color = "Cluster") +
theme_classic()
El gráfico revela una notable agrupación de propiedades en el clúster 1, indicando una relación robusta entre el área y el precio. En el clúster 2, esta relación se mantiene, aunque con una distribución más uniforme. En contraste, los clústeres 3 y 4 muestran una menor cohesión, sugiriendo una relación menos definida entre el área y el precio. Es probable que para estas propiedades, otros factores influyan en el precio además del tamaño del área. Además, se observan algunos valores atípicos en los clústeres 2 y 3, que pueden requerir una atención adicional para comprender su influencia en la relación entre el área y el precio.
# Dendograma
plot(cluster_jerarquico, cex = 0.6, main = "Dendograma de Precios y Área", las = 1,
ylab = "Distancia Euclidiana", xlab = "Grupos")
rect.hclust(cluster_jerarquico, k = 2, border = 2:5)
# Seleccionar las variables de interés y estandarizarlas
variables_estandarizadas <- vivienda_input %>%
select(precio_metro_cuadrado = preciom, banios) %>%
scale()
# Crear un dataframe combinado con las variables estandarizadas y la zona
datos_con_zona <- cbind(variables_estandarizadas, zona = vivienda_input$zona)
# Convertir a data frame
datos_con_zona <- as.data.frame(datos_con_zona)
# Calcular la distancia euclidiana
distancia_euclidiana <- dist(variables_estandarizadas, method = 'euclidean')
# Realizar el clustering jerárquico
cluster_jerarquico <- hclust(distancia_euclidiana, method = 'complete')
# Determinar la pertenencia de cada observación al cluster
asignaciones_cluster <- cutree(cluster_jerarquico, k = 4)
# Asignar los clusters al dataframe
datos_con_clusters <- mutate(datos_con_zona, cluster = as.factor(asignaciones_cluster))
# Gráfico de puntos
ggplot(datos_con_clusters, aes(x = banios, y = precio_metro_cuadrado, color = cluster, label = cluster)) +
geom_point(size = 4) +
geom_text(vjust = -0.8) +
scale_color_manual(values = c("red", "blue", "green", "purple")) + # Personalizar los colores de los clusters
labs(title = "Clusterización de Precios y Baños por Zona",
x = "Número de Baños (Estandarizado)",
y = "Precio por Metro Cuadrado (Estandarizado)",
color = "Cluster") +
theme_classic()
# Dendograma
plot(cluster_jerarquico, cex = 0.6, main = "Dendograma de Precios y baños", las = 1,
ylab = "Distancia Euclidiana", xlab = "Grupos")
rect.hclust(cluster_jerarquico, k = 2, border = 2:5)
El gráfico revela una notable agrupación de propiedades en el clúster 1, indicando una relación robusta entre el numero de baños y el precio. En el clúster 2, esta relación se mantiene, aunque con una distribución más uniforme. En contraste, los clústeres 3 y 4 muestran una menor cohesión, sugiriendo una relación menos definida entre los baños y el precio.
# Instalar y cargar el paquete necesario
#install.packages("ca")
library(ca)
# Crear la tabla de contingencia de las variables categóricas
tabla_contingencia <- table(vivienda_input$zona, vivienda_input$estrato)
# Realizar el análisis de correspondencia
resultado_ac <- ca(tabla_contingencia)
# Visualizar los resultados
summary(resultado_ac)
##
## Principal inertias (eigenvalues):
##
## dim value % cum% scree plot
## 1 0.322152 70.0 70.0 *****************
## 2 0.127451 27.7 97.6 *******
## 3 0.010841 2.4 100.0 *
## -------- -----
## Total: 0.460444 100.0
##
##
## Rows:
## name mass qlt inr k=1 cor ctr k=2 cor ctr
## 1 | ZnCn | 15 984 102 | -1725 942 138 | 364 42 15 |
## 2 | ZnNr | 231 857 102 | -390 750 109 | -147 107 39 |
## 3 | ZnOs | 144 999 293 | 569 345 145 | 783 653 692 |
## 4 | ZnOr | 42 994 401 | -2015 928 532 | 537 66 96 |
## 5 | ZnSr | 568 956 102 | 209 528 77 | -188 428 158 |
##
## Columns:
## name mass qlt inr k=1 cor ctr k=2 cor ctr
## 1 | 3 | 175 1000 550 | -1187 970 763 | 207 29 59 |
## 2 | 4 | 256 901 104 | 154 128 19 | -380 773 290 |
## 3 | 5 | 331 769 55 | 132 227 18 | -204 542 108 |
## 4 | 6 | 239 999 291 | 519 481 200 | 539 518 544 |
Inercias principales (valores propios):
Dimensión 1: 0.322152 (70.0% de la varianza total)
Dimensión 2: 0.127451 (27.7% de la varianza total)
Dimensión 3: 0.010841 (2.4% de la varianza total)
La primera dimensión explica el 70.0% de la varianza total, lo que indica que es la dimensión más importante y contiene la mayor parte de la información.
La segunda dimensión explica el 27.7% de la varianza total, lo que sugiere que también es relevante pero menos importante que la primera dimensión.
La tercera dimensión explica solo el 2.4% de la varianza total, lo que indica que contribuye con muy poca información al análisis.
Filas (categoría de zona): Se proporcionan algunas estadísticas para cada categoría de zona (ZnCn, ZnNr, ZnOs, ZnOr, ZnSr).
Columnas (categoría de estrato): Se proporcionan algunas estadísticas para cada categoría de estrato (3, 4, 5, 6).