Análisis del mercado inmobiliario urbano

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.

Iniciamos cargando la base de datos del repositorio

instalamos el paquete

devtools::install_github("dgonxalex80/paqueteMOD") 
## Skipping install of 'paqueteDAT' from a github remote, the SHA1 (f93b7506) has not changed since last install.
##   Use `force = TRUE` to force installation
library(paqueteMODELOS)

Para conocer los datos que contiene la base de datos miramos los 10 primeros datos de la tabla

head(vivienda,10)

Ejecutamos el comando str() para conocer a detalle la información de la base de datos a analizar

str(vivienda)
## Classes 'spec_tbl_df', 'tbl_df', 'tbl' and 'data.frame': 8322 obs. of  13 variables:
##  $ id          : num  1147 1169 1350 5992 1212 ...
##  $ zona        : chr  "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
##  $ piso        : chr  NA NA NA "02" ...
##  $ estrato     : num  3 3 3 4 5 5 4 5 5 5 ...
##  $ preciom     : num  250 320 350 400 260 240 220 310 320 780 ...
##  $ areaconst   : num  70 120 220 280 90 87 52 137 150 380 ...
##  $ parqueaderos: num  1 1 2 3 1 1 2 2 2 2 ...
##  $ banios      : num  3 2 2 5 2 3 2 3 4 3 ...
##  $ habitaciones: num  6 3 4 3 3 3 3 4 6 3 ...
##  $ tipo        : chr  "Casa" "Casa" "Casa" "Casa" ...
##  $ barrio      : chr  "20 de julio" "20 de julio" "20 de julio" "3 de julio" ...
##  $ longitud    : num  -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num  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>

Obtenermos el resumen estadístico

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

La dimensión de la base de datos

dim(vivienda)   
## [1] 8322   13

De lo anterior tenemos que la base de datos tiene 8322 filas y 13 columnas, de las 13 variables encontradas se tiene que 10 son numericas y 3 de texto.

vivienda$parquea <- ifelse(is.na(vivienda$parquea), 0, vivienda$parquea)

Visualizamos la información de la data modificada

summary(vivienda_nueva)
##        id           zona               piso              estrato     
##  Min.   :   1   Length:4808        Length:4808        Min.   :3.000  
##  1st Qu.:2479   Class :character   Class :character   1st Qu.:4.000  
##  Median :4474   Mode  :character   Mode  :character   Median :5.000  
##  Mean   :4427                                         Mean   :4.838  
##  3rd Qu.:6413                                         3rd Qu.:6.000  
##  Max.   :8316                                         Max.   :6.000  
##     preciom         areaconst       parqueaderos        banios      
##  Min.   :  58.0   Min.   :  40.0   Min.   : 1.000   Min.   : 0.000  
##  1st Qu.: 244.5   1st Qu.:  85.0   1st Qu.: 1.000   1st Qu.: 2.000  
##  Median : 350.0   Median : 123.0   Median : 2.000   Median : 3.000  
##  Mean   : 457.2   Mean   : 174.8   Mean   : 1.815   Mean   : 3.219  
##  3rd Qu.: 560.0   3rd Qu.: 225.0   3rd Qu.: 2.000   3rd Qu.: 4.000  
##  Max.   :1999.0   Max.   :1500.0   Max.   :10.000   Max.   :10.000  
##   habitaciones        tipo              barrio             longitud     
##  Min.   : 0.000   Length:4808        Length:4808        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.564                                         Mean   :-76.53  
##  3rd Qu.: 4.000                                         3rd Qu.:-76.52  
##  Max.   :10.000                                         Max.   :-76.46  
##     latitud         parquea      
##  Min.   :3.333   Min.   : 1.000  
##  1st Qu.:3.378   1st Qu.: 1.000  
##  Median :3.408   Median : 2.000  
##  Mean   :3.414   Mean   : 1.815  
##  3rd Qu.:3.451   3rd Qu.: 2.000  
##  Max.   :3.498   Max.   :10.000
install.packages("ggplot2")
## package 'ggplot2' successfully unpacked and MD5 sums checked
## 
## The downloaded binary packages are in
##  C:\Users\Diego Ortiz\AppData\Local\Temp\RtmpiQmv5N\downloaded_packages
library(ggplot2)
# Histograma de precios de vivienda
ggplot(vivienda, aes(x = preciom)) +
  geom_histogram(binwidth = 100, fill = "blue", color = "black") +
  labs(title = "Histograma de Precios de Vivienda",
       x = "Precio",
       y = "Frecuencia")

Del histograma de precios podemos ver que la mayoria de viviendas cuestan menos de 500 millones

# Diagrama de barras de estratos
ggplot(vivienda, aes(x = factor(estrato))) +
  geom_bar(fill = "green") +
  labs(title = "Diagrama de Barras de Estratos",
       x = "Estrato",
       y = "Frecuencia")

La mayor cantidad de personas vive en el estrato 5, seguido del estrato 4, luego el 6 y por ultimo el estrato 3. por esto podemos identificar que la base de datos corresponden a personas de clase media alta.

# Gráfico de dispersión de área construida vs. precio
ggplot(vivienda_nueva, aes(x = areaconst, y = preciom)) +
  geom_point(alpha = 0.5, color = "blue") +
  labs(title = "Gráfico de Dispersiones de Área Construida vs. Precio",
       x = "Área Construida",
       y = "Precio")

Vemos que existe graficamente una relación entre el área contruida de la vivienda y su valor, es decir a mayor área construida mayor precio del inmueble.

# Diagrama de barras para la variable 'zona'
ggplot(vivienda_nueva, aes(x = zona, fill = zona)) +
  geom_bar() +
  labs(title = "Diagrama de Barras para Zona",
       x = "Zona",
       y = "Frecuencia")

La Zona Sur es la que mas participación tiene en la data, por otro lado la zona centro es la que menos tiene.

# Diagrama de barras para la variable 'tipo'
ggplot(vivienda_nueva, aes(x = tipo, fill = tipo)) +
  geom_bar() +
  labs(title = "Diagrama de Barras para Tipo de Vivienda",
       x = "Tipo",
       y = "Frecuencia")

En cuanto a tipo de vivienda se tiene que la cantidad de apartamentos es mayor que las casas

# Gráfico de dispersión de latitud y longitud por zona
ggplot(vivienda_nueva, aes(x = longitud, y = latitud, color =  zona)) +
  geom_point() +
  labs(title = "Gráfico de Dispersiones de Latitud y Longitud por Zona",
       x = "Longitud",
       y = "Latitud")

Segun los registros, podriamos concluir que existen algunas viviendas que están mal parametrizadas peusto que la longitud y latitud las posicionan en zonas diferentes

ANALISIS DE COMPONENTES PRINCIPALES

El Análisis de Componentes Principales es una técnica estadística que se utiliza para reducir la dimensionalidad de un conjunto de datos mientras conserva la mayor cantidad posible de su variabilidad original. El PCA busca transformar un conjunto de variables correlacionadas en un nuevo conjunto de variables no correlacionadas llamadas componentes principales. Estos componentes principales son combinaciones lineales de las variables originales y están ordenados de manera que el primero captura la mayor varianza posible en los datos, el segundo captura la segunda mayor varianza, y así sucesivamente.

# Seleccionar las variables numéricas para el PCA
variables_numericas <- vivienda_nueva[, c("estrato", "preciom", "areaconst", "banios", "habitaciones")]

# Estandarizar los datos (convertir a Z-scores)
variables_estandarizadas <- scale(variables_numericas)

# Realizar el PCA
pca_resultado <- prcomp(variables_estandarizadas, scale = TRUE)

# Explorar los resultados del PCA
summary(pca_resultado)
## Importance of components:
##                           PC1    PC2     PC3     PC4     PC5
## Standard deviation     1.7276 1.0783 0.64485 0.50008 0.43211
## Proportion of Variance 0.5969 0.2325 0.08317 0.05002 0.03734
## Cumulative Proportion  0.5969 0.8295 0.91264 0.96266 1.00000
# Explorar los valores propios (varianza explicada)
pca_valores_propios <- pca_resultado$sdev^2
proporcion_varianza_explicada <- pca_valores_propios / sum(pca_valores_propios)

# Gráfico de varianza explicada por componentes
fviz_screeplot(pca_resultado, addlabels = TRUE, ylim = c(0, 100))

La primera componente acumula el 59.7 % de la varianza total es decir mas de la mtiad de la varianza observada.

# Seleccionar el número de componentes principales
num_componentes <- 2  

# Obtener los valores transformados 
coordenadas_componentes <- predict(pca_resultado, newdata = variables_estandarizadas)[, 1:num_componentes]

# Matriz de cargas
matriz_cargas <- pca_resultado$rotation[, 1:num_componentes]

# Imprimir la matriz de cargas
print(matriz_cargas)
##                    PC1         PC2
## estrato      0.3135372  0.69992307
## preciom      0.5021497  0.27812247
## areaconst    0.4987508 -0.17028101
## banios       0.5190448 -0.08887462
## habitaciones 0.3624641 -0.62917507
fviz_pca_var(pca_resultado,
             col.var = "contrib",
             gradient.cols = c("green",  "red"),
             repel = TRUE)

Se puede observar que las variables que estan en el cuarto cuadrante con variables que se asocian a la infraestructura del inmueble.

fviz_pca_biplot(pca_resultado,
                col.var = "steelblue",
                gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
                repel = TRUE,
                title = "Biplot de Componentes Principales",
                habillage = as.factor(vivienda_nueva$zona), 
                geom = "point",
                pointshape = 21, pointsize = 3,
                fill.ind = "lightgray",
                alpha.var = 0.6,
                legend.title = "Zona",
                ggtheme = theme_minimal())

podemos ver graficamente como se posicionan las diferentes zonas de la data.

Conclusión

Después de realizar el análisis de componentes principales (PCA) en nuestros datos, observamos que la primera componente explica el 59.7% de la variabilidad total de los datos, mientras que la segunda componente explica el 23.3%. En conjunto, estas dos componentes alcanzan a explicar un total del 83% de la variabilidad observada. Esto significa que estas dos componentes capturan la mayor parte de la información contenida en las cinco variables originales. El PCA nos ha permitido reducir la dimensionalidad de los datos, manteniendo una proporción significativa de su variabilidad original

ANÁLISIS DE CONGLOMERADOS

El análisis de conglomerados, también conocido como análisis de clústeres, es una técnica de análisis de datos que se utiliza para agrupar objetos o elementos similares en subconjuntos llamados clústeres. El objetivo principal del análisis de conglomerados es encontrar patrones o estructuras en los datos que puedan no ser evidentes a simple vista. Se utiliza para descubrir grupos naturales o segmentos en un conjunto de datos, donde los elementos dentro de cada clúster son más similares entre sí que con los elementos en otros clústeres.

Para el análisis de conglomerados tendremos en cuenta las siguientes variables: estrato, preciom, areacons, banios, habitaciones y zona.

library(stats)

# Seleccionamos las variables 
variables_conglomerados <- vivienda_nueva[, c("estrato", "preciom", "areaconst", "banios", "habitaciones")]

# Estandarizamos los datos
variables_estandarizadas <- scale(variables_conglomerados)

Determinamos el numero optimo Cluster

# Crear un vector para lose cuadrados
sumas_cuadrados <- vector("numeric", length = 10)

# Calcular las sumas de cuadrados 
for (i in 1:10) {
  resultado_conglomerados <- kmeans(variables_estandarizadas, centers = i)
  sumas_cuadrados[i] <- resultado_conglomerados$tot.withinss
}

# Crear un gráfico de codo
grafico_codo <- ggplot(data = data.frame(Clusters = 1:10, Sumas_Cuadrados = sumas_cuadrados),
                        aes(x = Clusters, y = Sumas_Cuadrados)) +
  geom_line() +
  geom_point() +
  labs(title = "Gráfico de Codo para Determinar Número de Clústeres", x = "Número de Clústeres", y = "Sumas de Cuadrados") +
  theme_minimal()

print(grafico_codo)

Del gráfico de codo concluimos que el número optimo sería 4 puesto que es ahi donde empieza a aplanarse la recta.

# Realizar el análisis de componentes principales
pca_resultado <- prcomp(variables_estandarizadas)

# Obtener las dos primeras componentes principales 
pc1 <- pca_resultado$x[, 1]
pc2 <- pca_resultado$x[, 2]

# Definimos la variable de la cantidad clusters
num_clusters <- 4  

# Realizar el análisis de conglomerados con  k-means
resultado_conglomerados <- kmeans(variables_estandarizadas, centers = num_clusters)

# Obtener la asignación de clúster 
asignacion_cluster <- resultado_conglomerados$cluster

# Crear un dataframe con los datos originales 
datos_con_clusters <- cbind(vivienda_nueva, Clusters = asignacion_cluster)

# Crear un gráfico de dispersión 
ggplot(datos_con_clusters, aes(x = pc1, y = pc2, color = factor(Clusters))) +
  geom_point(size = 3, alpha = 0.6) +
  labs(title = "Visualización de Conglomerados en las Dos Primeras Dimensiones", x = "Dimensión 1 (PC1)", y = "Dimensión 2 (PC2)", color = "Clúster") +
  theme_minimal()

# Conclusión

El análisis de conglomerados ha demostrado ser una herramienta esencial en la resolución del desafío planteado por la empresa líder en el sector inmobiliario. Al explorar detenidamente la exhaustiva base de datos de propiedades residenciales, hemos logrado revelar agrupamientos y patrones previamente ocultos, lo que nos ha brindado una comprensión más profunda de las dinámicas del mercado de viviendas urbanas. Este análisis nos ha permitido segmentar las propiedades en grupos con características similares, como estrato, precio y habitación. A través de esta segmentación, hemos identificado relaciones subyacentes entre estas variables, lo que a su vez ha mejorado nuestra comprensión de cómo diferentes grupos de propiedades pueden influir en nuestras decisiones estratégicas, en esto casa los 4 cluster seleccionados muestran gran realación en sus diferentes caracteristicas.

# Calculamos la matriz de distancias usando dist()
dist_matrix <- dist(variables_estandarizadas)

# Realizamos análisis jerárquico de conglomerados (AHC) con método Ward
modelo_hclust <- hclust(dist_matrix, method = "ward.D2")

# Graficamos el dendrograma
plot(modelo_hclust, hang = -1, main = "Dendrograma de Análisis Jerárquico")

ANÁLISIS DE CORRESPONDECIA

El análisis de correspondencia es una técnica estadística utilizada para explorar y visualizar relaciones entre variables categóricas en un conjunto de datos. Es especialmente útil cuando se trabaja con tablas de contingencia, que son tablas que muestran la frecuencia con la que se observan combinaciones particulares de categorías en dos o más variables categóricas. El análisis de correspondencia permite revelar patrones subyacentes y relaciones entre las variables categóricas, lo que puede ayudar a entender mejor la estructura de los datos y facilitar la interpretación.

library(FactoMineR)

tabla <- table(vivienda_nueva$zona, vivienda_nueva$estrato)
tabla
##               
##                   3    4    5    6
##   Zona Centro    33    3    0    0
##   Zona Norte    141  184  482   79
##   Zona Oeste     19   51  181  502
##   Zona Oriente   94    2    1    0
##   Zona Sur      147  973 1195  721
library(FactoMineR)
library(ggplot2)
library(factoextra)
library(gridExtra)
## 
## Attaching package: 'gridExtra'
## The following object is masked from 'package:dplyr':
## 
##     combine
Plano_ca <- CA(tabla)

El estrato 6 se localiza en la Zona Occidental. Los estratos 4 y 5 tienen su principal ubicación en las Zonas Sur y Norte respectivamente. El estrato 3 abarca tanto las Zonas Oriental como Central.

# Obtener los valores propios
valores_propios <- Plano_ca$eig

# Graficar el Scree Plot
fviz_screeplot(Plano_ca, addlabels = TRUE, ylim = c(0, 70)) +
  ggtitle("Scree Plot del Análisis de Correspondencia") +
  ylab("Porcentaje de Varianza Explicado") +
  xlab("Ejes")

Conclusión

Del resultado concluimos que que la primera componente del análisis de correspondencia explica aproximadamente el 65.3% % de la variabilidad total de los datos, la segunda componente sigue con un 30,8%, y la tercera con un 3.9 %, las dos primeras componentes son altamente significativas y abarcan la mayoría de la variabilidad en los datos (96,1%). Esto confirma la estrecha relación entre estrato y zona en el proceso de segmentación del mercado inmobiliariO, ademas el estrato 6 se encuentra predominantemente en la Zona Oeste, mientras que los estratos 4 y 5 están mayormente ubicados en las Zonas Sur y Norte, respectivamente. Por su parte, el estrato 3 se reparte entre las Zonas Oriente y Centro. Los resultados destacan una relación significativa entre la ubicación de las zonas y los diferentes estratos