library(paqueteMODELOS)
library(knitr)
library(mice)
library(factoextra)
library(dplyr)
data("vivienda")
#str(vivienda)
df <- vivienda
df$piso <- as.numeric(as.character(df$piso))
head(df, 5)
## # A tibble: 5 × 13
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <dbl> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1147 Zona O… NA 3 250 70 1 3 6
## 2 1169 Zona O… NA 3 320 120 1 2 3
## 3 1350 Zona O… NA 3 350 220 2 2 4
## 4 5992 Zona S… 2 4 400 280 3 5 3
## 5 1212 Zona N… 1 5 260 90 1 2 3
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
summary(df)
## id zona piso estrato
## Min. : 1 Length:8322 Min. : 1.000 Min. :3.000
## 1st Qu.:2080 Class :character 1st Qu.: 2.000 1st Qu.:4.000
## Median :4160 Mode :character Median : 3.000 Median :5.000
## Mean :4160 Mean : 3.771 Mean :4.634
## 3rd Qu.:6240 3rd Qu.: 5.000 3rd Qu.:5.000
## Max. :8319 Max. :12.000 Max. :6.000
## NA's :3 NA's :2638 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
Revisando las medidas de tendencia central, se observa un insight importante referente al número de baños y habitaciones con valor igual a 0, lo que genera preguntarse ¿cómo es posible una casa/apartamento sin baños ni habitaciones?
colSums(df[, c("banios", "habitaciones")] == 0, na.rm = TRUE)
## banios habitaciones
## 45 66
Con un total de 8319 registros, las viviendas sin baños ni habitaciones representan el 0.54% y 0.79% respectivamente. Dado que los valores son muy pequeños, se procede a extraerlos del dataset, eliminando en total el 0.88% de los registros.
data <- df[!(df$banios == 0 | df$habitaciones == 0), ]
Se realiza un histograma para las variables de estudio con el objetivo de ver su distribución y ayudar a la comprensión de los datos
library(ggplot2)
library(tidyr)
library(dplyr)
df_long <- data %>%
select(areaconst, banios, estrato, habitaciones, parqueaderos, piso, preciom) %>%
pivot_longer(cols = everything(),
names_to = "variable",
values_to = "value")
ggplot(df_long, aes(x = value)) +
geom_histogram(bins = 30, fill = "skyblue", color = "black") +
facet_wrap(~ variable, scales = "free_x") +
theme_minimal()
## Warning: Removed 4169 rows containing non-finite outside the scale range
## (`stat_bin()`).
md.pattern(data,rotate.names = TRUE)
## id zona estrato preciom areaconst banios habitaciones tipo barrio longitud
## 4787 1 1 1 1 1 1 1 1 1 1
## 1901 1 1 1 1 1 1 1 1 1 1
## 863 1 1 1 1 1 1 1 1 1 1
## 692 1 1 1 1 1 1 1 1 1 1
## 3 0 0 0 0 0 0 0 0 0 0
## 3 3 3 3 3 3 3 3 3 3
## latitud parqueaderos piso
## 4787 1 1 1 0
## 1901 1 1 0 1
## 863 1 0 1 1
## 692 1 0 0 2
## 3 0 0 0 13
## 3 1558 2596 4187
Se identifica que parqueadero y piso, son las variables con mayor número de missing values. Dado que son variables de números enteros, se calcula media redondeada para reemplazar los missing values. Posterior a ello se verifican los resultados obtenidos.
# Back up de seguridad
data_clean1 <- data
# Calculo de la media aproximada
mean_piso <- round(mean(data$piso, na.rm = TRUE), 0)
# Replace NAs with the mean
data_clean1$piso[is.na(data_clean1$piso)] <- mean_piso
# Calculo de la media aproximada
mean_parking <- round(mean(data$parqueaderos, na.rm = TRUE),0)
# Replace NAs with the mean
data_clean1$parqueaderos[is.na(data_clean1$parqueaderos)] <- mean_parking
# Eliminar los registros con NA de columna id
data_clean1 <- data_clean1[!is.na(data_clean1$id), ]
# Verificar que no quedan NAs
colSums(is.na(data_clean1))
## 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
Asimismo, también está la opción de eliminar los missing values del dataset. Adicional de los 3 valores de la columna id, que no pueden ser reemplazados por algún método estadístico. Nuevamente se verifican los resultados obtenidos.
# Selección de variables de interés para el análisis de pca
data_clean2 <- data %>%
select(piso, preciom, areaconst, parqueaderos, banios, habitaciones)
data_clean2 <- na.omit(data_clean2)
# Verificar que no quedan NAs
colSums(is.na(data_clean2))
## piso preciom areaconst parqueaderos banios habitaciones
## 0 0 0 0 0 0
# Ajuste del dataset para dejar solo variables cuantitativas y obtención de escala
dataz = scale(data_clean1[,c(3,5:9)])
head(dataz) # primeros 6 registros
## piso preciom areaconst parqueaderos banios habitaciones
## [1,] 0.0734874 -0.5571698 -0.7309968 -0.8552405 -0.09041565 1.6579542
## [2,] 0.0734874 -0.3443558 -0.3796746 -0.8552405 -0.79819935 -0.4438323
## [3,] 0.0734874 -0.2531498 0.3229699 0.1335039 -0.79819935 0.2567632
## [4,] -0.8520384 -0.1011398 0.7445566 1.1222482 1.32515174 -0.4438323
## [5,] -1.3148013 -0.5267678 -0.5904679 -0.8552405 -0.79819935 -0.4438323
## [6,] -1.3148013 -0.5875718 -0.6115473 -0.8552405 -0.09041565 -0.4438323
prcomp(dataz)
## Standard deviations (1, .., p=6):
## [1] 1.7886541 1.0284987 0.8967369 0.6587619 0.5650832 0.4306779
##
## Rotation (n x k) = (6 x 6):
## PC1 PC2 PC3 PC4 PC5
## piso -0.1013236 -0.84999301 0.48910107 0.1235941 0.10727765
## preciom 0.4645859 -0.29991362 -0.24758344 -0.4324310 -0.09333790
## areaconst 0.4896222 0.03581693 0.03890712 -0.2570613 0.74128498
## parqueaderos 0.4034387 -0.21958594 -0.48675140 0.7379107 -0.01773786
## banios 0.4877129 -0.02307691 0.21963826 -0.2015360 -0.65398411
## habitaciones 0.3654271 0.37085082 0.64249586 0.3827773 0.04760531
## PC6
## piso -0.03523615
## preciom 0.66121529
## areaconst -0.37668232
## parqueaderos -0.08523772
## banios -0.49502593
## habitaciones 0.40908072
En el siguiente gráfico con los resultados del PCA, se observa que el primer componente principal concentra el 53.7% de la variabilidad de la base de datos, mientras que los dos primeros en conjunto explican el 70.9%. Esto indica que una gran proporción de la variabilidad puede resumirse empleando únicamente el primer componente (CP1), el cual resulta de una combinación lineal de las variables originales
res.pca <- prcomp(dataz)
fviz_eig(res.pca, addlabels = TRUE)
Al
analizar las variables en el plano de los componentes principales, es
posible identificar la orientación y el significado de cada uno. En este
caso, el primer componente principal se relaciona principalmente con las
variables piso y precio, mientras que el segundo componente se asocia en
mayor medida con areacons y banios, orientadas en la misma dirección, lo
que evidencia una fuerte correlación entre ellas. Así, la Dimensión 2
podría interpretarse como un factor relacionado con el tamaño y la
cantidad de baños, mientras que la Dimensión 1 estaría vinculada al
valor y a la ubicación en altura o por el número de pisos (ya que el
dataset no especifica)
fviz_pca_var(res.pca,
col.var = "contrib", # Color by contributions to the PC
gradient.cols = c("#A9CBB7","#E7B800", "#034D94"),
repel = TRUE # Avoid text overlapping
)
vivienda_num <- data_clean1 %>%
select(piso, preciom, areaconst, parqueaderos, banios, habitaciones)
datos<- rbind(vivienda_num[100,],
vivienda_num[101,],
vivienda_num[102,],
vivienda_num[103,])
datos <- as.data.frame(datos)
rownames(datos) = c("vivienda 100","vivienda 101","vivienda 102","vivienda 103")
datos
## piso preciom areaconst parqueaderos banios habitaciones
## vivienda 100 4 520 140 2 3 3
## vivienda 101 4 750 445 2 6 7
## vivienda 102 4 450 118 2 3 3
## vivienda 103 4 800 306 2 3 3
casos1 <- rbind(res.pca$x[400,1:2],res.pca$x[101,1:2]) # CP1
rownames(casos1) = c("400","600")
casos1 <- as.data.frame(casos1)
casos2 <- rbind(res.pca$x[102,1:2], res.pca$x[103,1:2]) # CP2
rownames(casos2) = c("1000","2000")
casos2 <- as.data.frame(casos2)
fviz_pca_ind(res.pca, col.ind = "#DEDEDE", gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07")) +
geom_point(data = casos1, aes(x = PC1, y = PC2), color = "red", size = 3) +
geom_point(data = casos2, aes(x = PC1, y = PC2), color = "blue", size = 3)
fviz_pca_biplot(res.pca,
repel = TRUE,
habillage = data_clean1$piso,
col.var = "#034A94", # Variables color
col.ind = c("#DEDEDE", "#034A94") # Individuals color
)
Se decide hacer otra iteración del modelo pero con el dataset con missing values eliminados
data_clean2_numeric <- data_clean2 %>%
dplyr::select_if(is.numeric) %>%
scale()
head(data_clean2_numeric)
## piso preciom areaconst parqueaderos banios habitaciones
## [1,] -0.7061271 -0.1750766 0.7649053 1.0772608 1.3177459 -0.4388435
## [2,] -1.0810833 -0.6045496 -0.6119369 -0.7407711 -0.9099349 -0.4388435
## [3,] -1.0810833 -0.6659029 -0.6336765 -0.7407711 -0.1673747 -0.4388435
## [4,] -1.0810833 -0.7272561 -0.8873053 0.1682449 -0.9099349 -0.4388435
## [5,] -1.0810833 -0.4511664 -0.2713496 0.1682449 -0.1673747 0.3234003
## [6,] -0.7061271 -0.4204898 -0.1771446 0.1682449 0.5751856 1.8478880
En esta versión, el primer componenete toma mayor fuerza, aumentando 3.1 puntos, es decir una mejora del 6% aproximadamente. Para el componente 2 el incremento fue leve pasando de un 17,6% a un 17,8%. Finalmente el componente tres, que muestra una pequeña influencia, en esta caso disminuye 1.3 puntos
res.pca2 <- prcomp(data_clean2_numeric)
fviz_eig(res.pca2, addlabels = TRUE)
Un
hallazgo relevante de este modelo corresponde a la orientación que
adoptan las variables en el plano factorial. Se mantiene la alta
contribución de las variables piso y preciom, junto con el conjunto de
variables que se proyectan en la misma dirección, lo que evidencia una
fuerte correlación entre ellas.
La dirección de cada vector refleja la relación existente entre las variables. Por ejemplo, la variable habitaciones presenta una orientación definida hacia el cuadrante positivo de la Dimensión 2, lo que sugiere que, a medida que aumenta el número de habitaciones, también se incrementa la varianza explicada en dicha dimensión.
En cuanto a las correlaciones, se identifica que baños, habitaciones y área construida mantienen una correlación positiva entre sí. Por otro lado, la variable precio se orienta en una dirección que la vincula con variables como parqueaderos, lo que sugiere que el valor de la propiedad podría estar influenciado por la disponibilidad de espacios de estacionamiento.
fviz_pca_var(res.pca2,
col.var = "contrib", # Color by contributions to the PC
gradient.cols = c("#A9CBB7","#E7B800", "#034D94"),
repel = TRUE # Avoid text overlapping
)
Para explicar el sentido de los ejes, se escogen cuatro casos extremos conformados por los siguientes clientes:
vivienda_num2 <- data_clean2 %>%
select(piso, preciom, areaconst, parqueaderos, banios, habitaciones)
datos2<- rbind(vivienda_num2[100,],
vivienda_num2[390,],
vivienda_num2[70,],
vivienda_num2[530,])
datos2 <- as.data.frame(datos2)
rownames(datos2) = c("vivienda 100","vivienda 280","vivienda 10","vivienda 500")
datos2
## piso preciom areaconst parqueaderos banios habitaciones
## vivienda 100 1 240 350 1 3 8
## vivienda 280 6 320 130 1 3 3
## vivienda 10 11 900 184 2 4 3
## vivienda 500 8 225 73 1 2 3
casos2_1 <- rbind(res.pca$x[100,1:2],res.pca$x[280,1:2]) # CP1
rownames(casos2_1) = c("100","280")
casos2_1 <- as.data.frame(casos2_1)
casos2_2 <- rbind(res.pca$x[200,1:2], res.pca$x[300,1:2]) # CP2
rownames(casos2_2) = c("10","500")
casos2_2 <- as.data.frame(casos2_2)
fviz_pca_ind(res.pca, col.ind = "#DEDEDE", gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07")) +
geom_point(data = casos2_1, aes(x = PC1, y = PC2), color = "red", size = 3) +
geom_point(data = casos2_2, aes(x = PC1, y = PC2), color = "blue", size = 3)
fviz_pca_biplot(res.pca2,
repel = TRUE,
habillage = data_clean2$habitaciones,
col.var = "#034A94", # Variables color
col.ind = c("#DEDEDE", "#034A94") # Individuals color
)
Se realiza ejercicio utilizando dos métodos para el cálculo de distancias
library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.4.3
## Warning: package 'tibble' was built under R version 4.4.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ forcats 1.0.0 ✔ readr 2.1.5
## ✔ lubridate 1.9.4 ✔ stringr 1.5.1
## ✔ purrr 1.0.2 ✔ tibble 3.3.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::combine() masks gridExtra::combine()
## ✖ dplyr::filter() masks mice::filter(), stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ✖ tibble::view() masks summarytools::view()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
vivienda_clusters <- data_clean1 %>%
select(piso, estrato, preciom, areaconst, parqueaderos, banios, habitaciones)
vivienda_clustersz = scale(vivienda_clusters)
vivienda_clustersz = as.data.frame(vivienda_clustersz)
#vivienda_clustersz
# distancia euclidiana
dist_emp <- dist(vivienda_clustersz, method = 'euclidean')
# Clúster jerárquico con el método complete
hc_emp <- hclust(dist_emp, method = 'complete')
# Determinamos a dónde pertenece cada observación
cluster_assigments <- cutree(hc_emp, k = 3)
# asignamos los clusters
assigned_cluster <- vivienda_clustersz %>% mutate(cluster = as.factor(cluster_assigments))
# Luego, crear el gráfico
ggplot(assigned_cluster, aes(x = preciom, y = areaconst, color = cluster)) +
geom_point(size = 4) +
geom_text(aes(label = cluster), vjust = -.8) + # Agregar etiquetas del clúster
theme_classic()
## Distancia de Manhattan
Se realiza la clusterización utilizando otro tipo de cálculo de distancia, sin embargo los resultados fueron muy similiares
# distancia manhattan
dist_manh <- dist(vivienda_clustersz, method = 'euclidean')
# Clúster jerárquico con el método complete
hc_emp2 <- hclust(dist_manh, method = 'complete')
# Determinamos a dónde pertenece cada observación
cluster_assigments2 <- cutree(hc_emp2, k = 3)
# asignamos los clusters
assigned_cluster2 <- vivienda_clustersz %>% mutate(cluster = as.factor(cluster_assigments2))
# Luego, crear el gráfico
ggplot(assigned_cluster2, aes(x = preciom, y = areaconst, color = cluster)) +
geom_point(size = 4) +
geom_text(aes(label = cluster), vjust = -.8) + # Agregar etiquetas del clúster
theme_classic()
Del análisis de conglomerados, se puede concluir:
dfviviendacategoria <- data_clean1 %>% dplyr::select(zona, estrato, tipo)
zonaestrato <- table(dfviviendacategoria$zona, dfviviendacategoria$estrato)
zonaestrato
##
## 3 4 5 6
## Zona Centro 102 14 4 1
## Zona Norte 566 394 758 170
## Zona Oeste 52 83 286 767
## Zona Oriente 334 8 2 1
## Zona Sur 378 1607 1678 1038
Test chi cuadrado para las zonas y los estratos:
El resultado indica que se rechaza la hipótesis de independencia de las variables Zona~estrato (p-value: 0.00000000000000022).
chisq.test(zonaestrato)
##
## Pearson's Chi-squared test
##
## data: zonaestrato
## X-squared = 3801.8, df = 12, p-value < 2.2e-16
En el siguiente gráfico se muestra la relación entre el estrato socioeconómico y la zona de ubicación del inmueble. Los resultados indican que:
El estrato 6 se concentra principalmente en la zona oeste.
Los estratos 4 y 5 predominan en la zona sur.
El estrato 3 se localiza principalmente en las zonas centro y oriente.
Para evaluar el grado de representatividad del análisis, se calcularon los valores de la varianza acumulada a partir de los valores propios de la matriz de discrepancias.
library(FactoMineR)
resultados_ac <- CA(zonaestrato)
valores_prop <-resultados_ac$eig
valores_prop
## eigenvalue percentage of variance cumulative percentage of variance
## dim 1 0.32216299 69.851179 69.85118
## dim 2 0.12791612 27.734694 97.58587
## dim 3 0.01113428 2.414127 100.00000
fviz_screeplot(resultados_ac, addlabels = TRUE, ylim = c(0, 80))+ggtitle("")+
ylab("Porcentaje de varianza explicado") + xlab("Ejes")
Los resultados muestran que la primera componente principal explica
aproximadamente el 70% de la variabilidad y que los dos primeros ejes
factoriales, en conjunto, resumen el 97.6% de la información contenida
en los datos.
A partir de todos los hallazgos, se concluye y recomienda a la inmobiliaria lo siguiente:
El análisis de componentes principales (PCA) reveló una marcada relación entre el precio de la vivienda y el piso en el que se encuentra el apartamento o el número de pisos que tiene la casa, un mayor detalle en esta variable del piso podría determinar mejor los factores que tienen un impacto significativo en el precio final de la propiedad.
Por otro lado, ll análisis de conglomerados identificó distintos segmentos de mercado, por lo que se sugiere al área de marketing diseñar estrategias específicas adaptadas a cada segmento identificado.