library(ggplot2) # Para gráficos
library(dplyr) # Para manipulación de datos
##
## Adjuntando el paquete: '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
library(cluster) # Para clustering
library(factoextra) # Para visualización
## Warning: package 'factoextra' was built under R version 4.4.2
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
library(tidyr)
library(tinytex)
library(GGally)
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>
Primeramente, para conocer la data que se realiza un resumen de estadísticas descriptivas. De esta manera se puede observar:
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
Se tienen 8,322 observaciones y las siguientes variables:
Identificación y ubicación: Los registros se pueden identificar con id individual. Como variables de tipo carácter están los registros de “zona” y “barrio”, adicionalmente se encuentra la “longitud” y “latitud” que son variables númericas que indican la ubicación geográfica de las propiedades.
Dentro de las características físicas de los inmuebles podemos encontrar: el piso, estrato, área construida (areconst), habitaciones, baños (banios), parqueaderos y tipo (posiblemente casa o apartamento),
En cuanto al precio de las viviendas, los valores van desde 58 hasta 1.999 (posiblemente la unidad de medida es millones de pesos). La mediana de los precios es 330 y la distribución sugiere una amplia variación en los precios, al igual que con el área construida lo que podría estár fuertemente relacionado.
# Variables numéricas relevantes
vars_numericas <- c("estrato", "preciom", "areaconst", "parqueaderos",
"banios", "habitaciones", "longitud", "latitud")
# Nuevo data frame con las variables seleccionadas
df_numeric <- vivienda[, vars_numericas]
# Se eliminan filas con NA
df_numeric <- na.omit(df_numeric)
head(df_numeric)
## # A tibble: 6 × 8
## estrato preciom areaconst parqueaderos banios habitaciones longitud latitud
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 3 250 70 1 3 6 -76.5 3.43
## 2 3 320 120 1 2 3 -76.5 3.43
## 3 3 350 220 2 2 4 -76.5 3.44
## 4 4 400 280 3 5 3 -76.5 3.44
## 5 5 260 90 1 2 3 -76.5 3.46
## 6 5 240 87 1 3 3 -76.5 3.37
# Ejecutar el PCA con centrado y escalado
pca_result <- prcomp(df_numeric, center = TRUE, scale. = TRUE)
# Resumen del PCA para ver la varianza explicada
summary(pca_result)
## Importance of components:
## PC1 PC2 PC3 PC4 PC5 PC6 PC7
## Standard deviation 1.9007 1.2022 0.9675 0.86369 0.68670 0.59739 0.49409
## Proportion of Variance 0.4516 0.1807 0.1170 0.09324 0.05895 0.04461 0.03052
## Cumulative Proportion 0.4516 0.6322 0.7492 0.84246 0.90141 0.94602 0.97653
## PC8
## Standard deviation 0.43327
## Proportion of Variance 0.02347
## Cumulative Proportion 1.00000
fviz_eig(pca_result, addlabels = TRUE, ylim = c(0, 60)) +
ggtitle("Scree Plot: Varianza Explicada por Componentes")
fviz_pca_biplot(pca_result,
geom.ind = "point", # Representa las observaciones como puntos
col.ind = "blue", # Color para las observaciones
repel = TRUE, # Evita que las etiquetas se solapen
title = "Biplot de PCA")
fviz_pca_var(pca_result,
col.var = "contrib", # Colorea según la contribución
gradient.cols = c("blue", "yellow", "red"),
repel = TRUE, # Evita solapamientos en las etiquetas
title = "Contribución de Variables en el PCA")
El precio (preciom) está fuertemente influenciado por características
estructurales como área construida, cantidad de baños, parqueaderos y
estrato. Las variables de localización (latitud y longitud) contribuyen
menos a la variabilidad principal del dataset, pero aún pueden ser
importantes en Dim2. El PCA ha reducido la dimensionalidad a dos
componentes que explican el 63.3% de la variabilidad (45.2% + 18.1%), lo
que sugiere que estas dimensiones capturan gran parte de la estructura
del mercado inmobiliario.
Escalar variables para evitar que algunas dominen la agrupación
# Escalar las variables para normalizarlas (importante en clustering)
df_scaled <- scale(df_numeric)
# Verificar estructura
summary(df_scaled)
## estrato preciom areaconst parqueaderos
## Min. :-1.9279 Min. :-1.2264 Min. :-1.0488 Min. :-0.7425
## 1st Qu.:-0.8747 1st Qu.:-0.6593 1st Qu.:-0.6602 1st Qu.:-0.7425
## Median : 0.1786 Median :-0.3399 Median :-0.3549 Median : 0.1465
## Mean : 0.0000 Mean : 0.0000 Mean : 0.0000 Mean : 0.0000
## 3rd Qu.: 1.2319 3rd Qu.: 0.3317 3rd Qu.: 0.3599 3rd Qu.: 0.1465
## Max. : 1.2319 Max. : 4.5669 Max. :10.8526 Max. : 7.2582
## banios habitaciones longitud latitud
## Min. :-2.3587 Min. :-2.6466 Min. :-3.57020 Min. :-1.91557
## 1st Qu.:-0.9096 1st Qu.:-0.4477 1st Qu.:-0.74209 1st Qu.:-0.84733
## Median :-0.1851 Median :-0.4477 Median :-0.08256 Median :-0.07056
## Mean : 0.0000 Mean : 0.0000 Mean : 0.00000 Mean : 0.00000
## 3rd Qu.: 0.5394 3rd Qu.: 0.2852 3rd Qu.: 0.60741 3rd Qu.: 0.84611
## Max. : 4.8866 Max. : 4.6831 Max. : 4.11210 Max. : 1.93093
Luego se determina el número óptimo de clústeres
# Método de Silhouette
fviz_nbclust(df_scaled, kmeans, method = "silhouette") +
ggtitle("Número óptimo de Clústeres - Silhouette")
Una vez determinado el número óptimo de clústeres (en este caso, k = 2), aplicamos K-Means.
# Aplicar K-Means con k óptimo
set.seed(123) # Asegurar reproducibilidad
k <- 2 # número óptimo encontrado
km_res <- kmeans(df_scaled, centers = k, nstart = 25)
# Agregar los clusters al data frame original
df_numeric$cluster <- as.factor(km_res$cluster)
# Ver resumen de cada grupo
aggregate(df_numeric, by = list(df_numeric$cluster), mean)
## Warning in mean.default(X[[i]], ...): argument is not numeric or logical:
## returning NA
## Warning in mean.default(X[[i]], ...): argument is not numeric or logical:
## returning NA
## Group.1 estrato preciom areaconst parqueaderos banios habitaciones
## 1 1 4.506104 296.6242 116.5592 1.353385 2.545616 3.191121
## 2 2 5.490958 819.7012 312.6555 2.816456 4.701175 4.465642
## longitud latitud cluster
## 1 -76.52690 3.418279 NA
## 2 -76.53791 3.408386 NA
Visualización de los conglomerados
fviz_cluster(km_res, data = df_scaled,
ellipse.type = "norm", geom = "point",
palette = "jco", ggtheme = theme_minimal()) +
ggtitle("Visualización de Clústeres en Componentes Principales")
ggplot(df_numeric, aes(x = longitud, y = latitud, color = cluster)) +
geom_point(alpha = 0.6) +
scale_color_manual(values = c("red", "blue", "green", "purple")) +
theme_minimal() +
ggtitle("Distribución Geoespacial de los Clústeres")
En cuanto al cluster 1 las viviendas son de menor precio, área construida y comodidades (menos baños, habitaciones y parqueaderos) mientras el cluster 2 tiene viviendas de mayor estrato y mejores caracteristicas. En cuanto a la ubicación geografica no hay diferencias significativas por lo que se podría decir que la ubicación no es un diferencial importante para el precio.
# Seleccionar solo las variables categóricas
df_correspondencia <- vivienda %>% select(tipo, zona, barrio)
# Eliminar valores nulos
df_correspondencia <- na.omit(df_correspondencia)
# Crear una tabla de contingencia entre 'tipo de vivienda' y 'zona'
tabla_contingencia <- table(df_correspondencia$tipo, df_correspondencia$zona)
# Mostrar la tabla
print(tabla_contingencia)
##
## Zona Centro Zona Norte Zona Oeste Zona Oriente Zona Sur
## Apartamento 24 1198 1029 62 2787
## Casa 100 722 169 289 1939