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.
-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
# 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
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
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, ]
# 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.
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 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
# 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)
# 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
# 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.
# 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.
-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
# 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.
-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.
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.
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.