Evaluación de la oferta inmobiliaria urbana

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.

Análisis de Componentes Principales

-Cargue de los datos

-Caracteristicas de los datos:

# Ver las primeras filas del dataset
head(vivienda)
# Ver resumen de cada columna
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
# Ver 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>

–Manejo de datos faltantes

  • Cantidad de faltantes:
# Ver la cantidad de valores NA en cada columna
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

Como podemos ver, se debe analizar esos faltantes de las columnas de 3 faltantes, si esos 3 registros son iguales para todas las columnas

  • los faltantes en la columna Zona:
missing_rows_column <- which(is.na(vivienda$zona))
vivienda[missing_rows_column, ]

Vemos que para esos 3 faltantes de zona son los mismos tres altantes en todas las demas columnas, se va a eliminar esos registros ya que no aportan información a la base de datos

  • Eliminar los faltantes en la columna Zona:
viviendaR = vivienda ####se hace una copia de la base de datos
# 1. Eliminar filas con valores faltantes
viviendaR <- subset(viviendaR, !is.na(zona))
missing_rows_column <- which(is.na(viviendaR$zona))
viviendaR[missing_rows_column, ]
  • Verificamos los faltantes nuevamente:
# Ver la cantidad de valores NA en cada columna
colSums(is.na(viviendaR))
##           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

Se verifica los valores unicos de la columna de faltantes, validaremos si existe el piso 0 y la cantidad 0 de parqueaderos

#se hace un analisis de valores unicos en piso y parqueaderos para validar si el valor NA es porque esta en el no tiene parqueaderos o esta en el piso 1
# Valores únicos en una columna específica, por ejemplo, "column_name"
unique(viviendaR$piso)
##  [1] NA   "02" "01" "03" "04" "05" "06" "07" "08" "09" "10" "11" "12"
unique(viviendaR$parqueaderos)
##  [1]  1  2  3 NA  4  7  5  8  6  9 10

Se analiza el resultado y se decide remplazar los faltantes por 0, ya que indica que no tiene mas de 1 piso o no tiene parqueadero.

  • Remplazo de faltantes por el valor 0 y analisis de faltantes:
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.3.3
## 
## Attaching package: 'dplyr'
## The following object is masked from 'package:gridExtra':
## 
##     combine
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
viviendaR <- viviendaR %>%
  mutate(across(everything(), ~ifelse(is.na(.), 00, .)))
viviendaR <- viviendaR %>%
  mutate(across(everything(), ~ifelse(is.na(.), 00, .)))

# Ver la cantidad de valores NA en cada columna
colSums(is.na(viviendaR))
##           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

Ahora quedo la base de datos sin datos faltantes.

  • Analizamos los valores unicos para los campos diferentes a precio o area
#analizamos los valores unicos para cada columna, validar ajustes extras antes de analisis completo
cat ("Los valores únicos en zona son:", unique(viviendaR$zona), "\n")
## Los valores únicos en zona son: Zona Oriente Zona Sur Zona Norte Zona Oeste Zona Centro
cat ("Los valores únicos en zona son:", unique(viviendaR$piso), "\n")
## Los valores únicos en zona son: 0 02 01 03 04 05 06 07 08 09 10 11 12
cat ("Los valores únicos en zona son:", unique(viviendaR$estrato), "\n")
## Los valores únicos en zona son: 3 4 5 6
cat ("Los valores únicos en zona son:", unique(viviendaR$parqueaderos), "\n")
## Los valores únicos en zona son: 1 2 3 0 4 7 5 8 6 9 10
cat ("Los valores únicos en zona son:", unique(viviendaR$banios), "\n")
## Los valores únicos en zona son: 3 2 5 4 7 6 1 0 8 10 9
cat ("Los valores únicos en zona son:", unique(viviendaR$habitaciones), "\n")
## Los valores únicos en zona son: 6 3 4 5 2 0 1 8 7 10 9
cat ("Los valores únicos en zona son:", unique(viviendaR$tipo), "\n")
## Los valores únicos en zona son: Casa Apartamento
  • Se convierte el campo piso en numerico y se verifica
# Convertir 'piso' a numérico
viviendaR$piso <- as.numeric(viviendaR$piso)

# Verificar la conversión
summary(viviendaR$piso)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   0.000   0.000   2.000   2.577   4.000  12.000
str(viviendaR$piso)
##  num [1:8319] 0 0 0 2 1 1 1 1 2 2 ...
library(ggplot2)
library(dplyr)
library(tidyr)
## Warning: package 'tidyr' was built under R version 4.3.3
viviendaR %>%
  select_if(is.numeric) %>%
  select(-id) %>%  # Excluir la columna ID
  pivot_longer(everything()) %>%
  ggplot(aes(value)) +
  facet_wrap(~name, scales = "free") +
  geom_histogram(bins = 30) +
  theme_minimal()

# Boxplots para detectar outliers
viviendaR %>% 
  select_if(is.numeric) %>%
  select(-id) %>%  # Excluir la columna ID
  pivot_longer(everything()) %>%
  ggplot(aes(x = name, y = value)) +
  geom_boxplot() +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 90, hjust = 1))

Se verifica la distribucion de los datos y se encuentran distribuciones normales con tendencia positiva la mayoria.

y en el grafico de cajas vemos que debemos omitir el analisis del campo ID ya que no es un campo valido para determinar el valor de una vivienda, sin este campo los campos areaconst y preciom tiene datos atipicos de resto los campos no tienen atipicidad en sus datos.

# Resumen estadístico de cada columna
summary(viviendaR)
##        id           zona                piso           estrato     
##  Min.   :   1   Length:8319        Min.   : 0.000   Min.   :3.000  
##  1st Qu.:2080   Class :character   1st Qu.: 0.000   1st Qu.:4.000  
##  Median :4160   Mode  :character   Median : 2.000   Median :5.000  
##  Mean   :4160                      Mean   : 2.577   Mean   :4.634  
##  3rd Qu.:6240                      3rd Qu.: 4.000   3rd Qu.:5.000  
##  Max.   :8319                      Max.   :12.000   Max.   :6.000  
##     preciom         areaconst       parqueaderos        banios      
##  Min.   :  58.0   Min.   :  30.0   Min.   : 0.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 : 1.000   Median : 3.000  
##  Mean   : 433.9   Mean   : 174.9   Mean   : 1.482   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  
##   habitaciones        tipo              barrio             longitud     
##  Min.   : 0.000   Length:8319        Length:8319        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  
##     latitud     
##  Min.   :3.333  
##  1st Qu.:3.381  
##  Median :3.416  
##  Mean   :3.418  
##  3rd Qu.:3.452  
##  Max.   :3.498
# Verificar valores únicos en columnas categóricas
sapply(viviendaR, function(x) if(is.factor(x)) unique(x) else NULL)
## $id
## NULL
## 
## $zona
## NULL
## 
## $piso
## NULL
## 
## $estrato
## NULL
## 
## $preciom
## NULL
## 
## $areaconst
## NULL
## 
## $parqueaderos
## NULL
## 
## $banios
## NULL
## 
## $habitaciones
## NULL
## 
## $tipo
## NULL
## 
## $barrio
## NULL
## 
## $longitud
## NULL
## 
## $latitud
## NULL

-Analisis de componentes principales (CPA)

  • Escalamiento de los datos
# Seleccionar columnas a escalar
columns_to_scale <- c("piso", "estrato", "preciom", "areaconst", "parqueaderos", "banios", "habitaciones")

# Escalar las columnas seleccionadas
scaled_data <- viviendaR
scaled_data[columns_to_scale] <- scale(viviendaR[columns_to_scale])

scaled_data
  • Analisis PCA
# Realizar PCA
pca_result <- prcomp(scaled_data[columns_to_scale], center = TRUE, scale. = TRUE)

# Ver resumen de los resultados
summary(pca_result)
## Importance of components:
##                          PC1    PC2    PC3     PC4    PC5    PC6     PC7
## Standard deviation     1.850 1.1819 0.9308 0.67690 0.6519 0.4907 0.43572
## Proportion of Variance 0.489 0.1996 0.1238 0.06546 0.0607 0.0344 0.02712
## Cumulative Proportion  0.489 0.6886 0.8123 0.87778 0.9385 0.9729 1.00000

Validando estos resultados tenemos que el PC! explica el 48,9% de la varianza total y si vemos el acomulado se diria que los 3 primeros PCA explicanun 81,2% de la varianza total.

  • Vizualización de importancia de cada componente
# Ver la importancia de cada componente
pca_result$sdev^2 / sum(pca_result$sdev^2)
## [1] 0.48899526 0.19956824 0.12375796 0.06545665 0.06070279 0.03439692 0.02712218
library(ggplot2)
library(ggfortify)
# Biplot de las dos primeras componentes principales
autoplot(pca_result, data = scaled_data, colour = 'zona')

# Ver las cargas de cada variable en las componentes principales
pca_result$rotation
##                      PC1        PC2         PC3         PC4        PC5
## piso          0.05624508  0.5051515 -0.84823031  0.04601540 -0.1397874
## estrato      -0.32632057  0.5210267  0.24346981 -0.54797999  0.1712473
## preciom      -0.47660417  0.1683184  0.11405920 -0.01219188 -0.3592424
## areaconst    -0.44355148 -0.2205624 -0.03079337  0.20603152 -0.6496296
## parqueaderos -0.40706306  0.2474119  0.08106990  0.72275475  0.4872424
## banios       -0.46672129 -0.1339445 -0.17622708 -0.33001544  0.2103781
## habitaciones -0.28798060 -0.5632233 -0.41187588 -0.15400738  0.3440872
##                      PC6          PC7
## piso         -0.02062750  0.008965092
## estrato      -0.45625352  0.158093347
## preciom       0.21541366 -0.745587739
## areaconst    -0.28414812  0.456572351
## parqueaderos -0.04430878  0.069078491
## banios        0.68056871  0.341805824
## habitaciones -0.44623648 -0.298267787
# Ver las coordenadas de cada observación en las componentes principales
##pca_result$x

Al analizar estos resultados se concluye que el PC1 da una correlacion fuerte entre Preciom, Baños, Areaconst y parqueaderos. en el PC2 las variables mas influyentes son el Piso, Estrato y Habitaciones, podria indicar una caracteristica estructural o socioeconomica de la propiedad. En el PC3 se puede analizar una variacion especifica entre el numeroi de piso en relacion Estrato y Precio.

Analisis de Conglomerados

-Determinacion optimo de Clousters

set.seed(123) # Para reproducibilidad
wss <- (nrow(scaled_data[columns_to_scale])-1)*sum(apply(scaled_data[columns_to_scale], 2, var))

for (i in 2:15) {
  wss[i] <- sum(kmeans(scaled_data[columns_to_scale], centers=i)$tot.withinss)
}

# Plot del método del codo
plot(1:15, wss, type="b", pch=19, frame=FALSE, 
     xlab="Número de Clusters K",
     ylab="Suma de Cuadrados Total Dentro del Cluster (WSS)")

Segun el grafico resultante se seleccionan 5 K

  • Vizualizacion de resultados
# Realizar clustering con K-means
k <- 5
kmeans_result <- kmeans(scaled_data[columns_to_scale], centers=k, nstart=25)

# Añadir los resultados del clustering al dataset original
scaled_data$cluster <- kmeans_result$cluster

###Vizualizacion
library(ggplot2)
ggplot(scaled_data, aes(x = longitud, y = latitud, color = as.factor(cluster))) +
  geom_point(size = 2) +
  labs(title = "Clusters de propiedades residenciales", color = "Cluster") +
  theme_minimal()

- Resumen de resultados

# Resumen estadístico de los clusters
aggregate(scaled_data[columns_to_scale], by = list(cluster = scaled_data$cluster), mean)
# Frecuencia de clusters por estrato
table(scaled_data$estrato, scaled_data$cluster)
##                     
##                         1    2    3    4    5
##   -1.58722760689987   354    5 1076    5   13
##   -0.615620067400236  262  184 1339   25  319
##   0.355987472099397   204 1157  491  195  703
##   1.32759501159903     15  989    7  753  223

Analizando los resultados obtenidos, se concluye que:

El Clouster 1 agrupa las propiedades que se encuentran en pisos altos, con estratos en el promedio pero con precios y tamaño de vivienda mas pequeño del promedio.

El cluster 2 incluye viviendas en pisos mas bajos, estratos mas bajos con un precio promedio, pero en tamaño grande y con mas habitaciones.

El clouster 3 representa las propiedades que son pequeñas, economicas y se encuentran en estratos bajos.

El clouster 4 agrupa las propiedades de alto estrato, precios elevados, grandes y con parqueadero y baños.

El cluster 5 representa las viviendas en estratos altos con caracteristicas y precio cercano al promedio.

Analisis de correspondencia (CA)

-Convercion de variables categoricas a factores

# Convertir variables categóricas a factores
scaled_data$tipo <- as.factor(scaled_data$tipo)
scaled_data$zona <- as.factor(scaled_data$zona)
scaled_data$barrio <- as.factor(scaled_data$barrio)

-Creación de tablas de contingencia

# Crear tablas de contingencia bidimensionales
contingency_table_tipo_zona <- table(scaled_data$tipo, scaled_data$zona)
contingency_table_tipo_barrio <- table(scaled_data$tipo, scaled_data$barrio)
contingency_table_zona_barrio <- table(scaled_data$zona, scaled_data$barrio)

-Analisis CA

# Instalar y cargar el paquete ca
install.packages("ca")
## Installing package into 'C:/Users/Cesar Fuentes/AppData/Local/R/win-library/4.3'
## (as 'lib' is unspecified)
## package 'ca' successfully unpacked and MD5 sums checked
## 
## The downloaded binary packages are in
##  C:\Users\Cesar Fuentes\AppData\Local\Temp\RtmpYdwUXh\downloaded_packages
library(ca)

# Análisis de Correspondencia para tipo de vivienda vs zona
ca_result_tipo_zona <- ca(contingency_table_tipo_zona)
plot(ca_result_tipo_zona, dim = c(1, 1))###no relacionados

# Realizar el Análisis de Correspondencia para tipo de vivienda vs barrio
##ca_result_tipo_barrio <- ca(contingency_table_tipo_barrio)
##plot(ca_result_tipo_barrio, dim = c(1, 1))###no relacionados

# Realizar el Análisis de Correspondencia para zona vs barrio
##ca_result_zona_barrio <- ca(contingency_table_zona_barrio)
##plot(ca_result_zona_barrio)

Con los graficos Obtenidos podemos analizar las similitudes entre los componentes analizados este caso Tipo vs Zona, Tipo vs Barrio y Zona vs Barrio

En el primer grafico se puede observar que los Apartamentos son comunes en la Zona Oeste, Norte y Sur; Las casas son comunes en las Zonas Oriente, Centro, Norte y Sur. Las zonas Norte y sur son las zonas mas promedio de todas, las zonas Oriente y Centros son similares entre si y la Zona Oeste es distinta al resto.

Conclusion

Cuando se realiza un analisis de PCA es importante entender y conocer previamente la base de datos ya que se puede estar analizando informacióin basura dentro de los datos, por eso se debe realizar una limpieza y un entendimiento de los datos para finalmente entender que nos explica el analisis que se realiza a la información.

El Análisis de Correspondencia nos permite identificar y visualizar las relaciones entre variables categóricas en un espacio de baja dimensión. Al realizar el análisis en tablas de contingencia bidimensionales, podemos examinar las asociaciones entre pares de variables categóricas, lo que nos ayuda a entender mejor los patrones de comportamiento en la oferta del mercado inmobiliario.

Aplicando estos analisis multidimencionales se evidencia un mejor entendimiento del mercado, trayendo a visualizar diferentes aplicaciones en el mundo real.

Recomendaciones

Con la información obtenida, se recomienda estudiar las diferentes clasificaciones obtenidas del mercado, enfocar los planes de accion en el enfoque que quiera dar la empresa, un ejemplo es que si se quiere abarcar un mercado de personas con alto poder adquisitivo debemos enfocarnos en ofrecer las viviendas agrupadas en los clouster 4 y 5, son menos oferta que se tiene epro es lo que las personas con alto poder adquisitivo esta buscando. Otro analisis importante es la relacion obtenida en el analisis de PCA porque podemos ver una fuerte relacion entre el precio y las area construida, baños y parqueaderos, ahi hay un posible enfoque de accion para la empresa.