En esta actividad se realiza un análisis estadístico multidimensional para identificar patrones, relaciones y segmentaciones relevantes sobre una base de datos que contiene información sobre diversas propiedades residenciales disponibles en el mercado. A partir del análisis realizado se pretende ofrecer a una empresa inmobiliaria orientación sobre la toma de decisiones relacionadas a la compra, venta y valoración de las propiedades. En este proceso se aplicarán técnicas como Análisis de Componentes Principales, Análisis de Conglomerados y Análisis de Correspondencia, de los cuales se espera obtener información que proporcione ventajas competitivas de optimización e inversión en la dinámica del mercado inmobiliario en la ciudad.
En este caso utilizaremos la base de datos vivienda, la
cual es importada desde paqueteMODELOS. La siguiente es una
muestra aleatoria de la base de datos.
muestra <- vivienda %>% sample_n(5)
kable(muestra, "html") %>%
kable_styling("striped", full_width = FALSE) %>%
row_spec(0, bold = TRUE)| id | zona | piso | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | barrio | longitud | latitud |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 5783 | Zona Sur | 04 | 5 | 240 | 87 | 1 | 3 | 3 | Apartamento | multicentro | -76.53821 | 3.37845 |
| 744 | Zona Norte | 04 | 4 | 158 | 77 | 1 | 2 | 3 | Apartamento | prados del norte | -76.50227 | 3.40102 |
| 6633 | Zona Sur | 02 | 6 | 365 | 108 | 2 | 3 | 3 | Apartamento | ciudad jardín | -76.54387 | 3.35730 |
| 2410 | Zona Norte | 11 | 5 | 390 | 110 | 2 | 3 | 3 | Apartamento | la flora | -76.52003 | 3.48960 |
| 5878 | Zona Sur | 04 | 6 | 410 | 120 | 2 | 3 | 3 | Apartamento | ciudad jardín | -76.53900 | 3.36400 |
Se importó un dataframe con 13 variables: 3 cualitativas, 9 cuantitativas y 1 como identificador del registro. Las tres variables cualitativas son nominales, y la variable identificador puede ser descartada para el análisis.
Inicialmente el dataframe cuenta con 8322 registros.
Iniciamos haciendo un conteo de los datos faltantes en todo el dataframe y los agrupamos para cada variable
faltantes <- colSums(is.na(vivienda)) %>%
as.data.frame()
conteo <- kable(faltantes, "html", caption = "Conteo de faltantes") %>%
kable_styling("striped", full_width = FALSE) %>%
row_spec(0, bold = TRUE)
conteo| . | |
|---|---|
| id | 3 |
| zona | 3 |
| piso | 2638 |
| estrato | 3 |
| preciom | 2 |
| areaconst | 3 |
| parqueaderos | 1605 |
| banios | 3 |
| habitaciones | 3 |
| tipo | 3 |
| barrio | 3 |
| longitud | 3 |
| latitud | 3 |
A partir de la tabla conteo de faltantes podemos identificar que hay
3 registros que no presentan información, los cuales serán eliminados de
la base de datos. Los atributos piso y
paqueadero son los que presentan datos faltantes, los
cuales serán imputados.
Primero analizaremos la información de la variable piso
para determinar como realizar la imputación.
viviendas_1$piso <- as.integer(viviendas_1$piso)
resumen_piso <- summarytools::descr(viviendas_1$piso)
kable(resumen_piso, "html", caption = "Resumen de Piso") %>%
kable_styling("striped", full_width = FALSE) %>%
row_spec(0, bold = TRUE)| piso | |
|---|---|
| Mean | 3.7709360 |
| Std.Dev | 2.6148024 |
| Min | 1.0000000 |
| Q1 | 2.0000000 |
| Median | 3.0000000 |
| Q3 | 5.0000000 |
| Max | 12.0000000 |
| MAD | 1.4826000 |
| IQR | 3.0000000 |
| CV | 0.6934094 |
| Skewness | 1.2795930 |
| SE.Skewness | 0.0324813 |
| Kurtosis | 1.0542476 |
| N.Valid | 5684.0000000 |
| Pct.Valid | 68.3255199 |
par(mfrow = c(1, 2))
histograma <- hist(viviendas_1$piso,
las=1,
main = "Distribucion de Piso",
xlab="Piso",
ylab = "Frecuencia",
col =c("lightblue")
)
box <- boxplot(viviendas_1$piso,
names = "Piso",
main = "Diagrama de Caja - Piso",
col = c("lightblue"),
xlab="Piso",
ylab = "Valores",
border = "black"
)Al identificar que los datos atípicos tienen una frecuencia
relevante, y que no hay un dato específico que pueda reemplazar los
valores nulos, se imputarán los datos a partir de comparaciones de las
variables zona, estrato, banios,
habitaciones y tipo.
Este proceso se realizará creando dos dataframe, uno con los
registros que tienen datos de piso y otro con registro que no tienen
datos de piso. Se utiliza la función left_join para comprar
cada uno de estos dataframes en las variables zona,
estrato, banios, habitaciones y
tipo, generando un nuevo dataframe con los registros que
coincidan en estas variables. Ya que el dataframe de coincidencias es
many-to-many, en la comparación se obtendrán varios
registros para cada registro comparado, por lo que se deberá realizar
otra validación adicional. Para la validación adicional se utiliza la
variable preciom, ya que al estudiar la correlación de ésta
variable con la variable piso, se presenta una menor
correlación negativa que la que hay entre piso y
areaconst, que era la otra opción a tener en cuenta para
una segunda comparación.
##
## Pearson's product-moment correlation
##
## data: viviendas_1$piso and viviendas_1$areaconst
## t = -16.021, df = 5682, p-value < 0.00000000000000022
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
## -0.2326420 -0.1828922
## sample estimates:
## cor
## -0.2079016
##
## Pearson's product-moment correlation
##
## data: viviendas_1$piso and viviendas_1$preciom
## t = -1.1103, df = 5682, p-value = 0.2669
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
## -0.04071050 0.01127397
## sample estimates:
## cor
## -0.01472822
viviendas_1_sinpiso <- subset(viviendas_1, is.na(piso))
viviendas_1_conpiso <- subset(viviendas_1, !is.na(piso))
matched_rows <- left_join(viviendas_1_sinpiso, viviendas_1_conpiso, by = c("zona", "estrato", "banios", "habitaciones", "tipo"))
no_pisos <- viviendas_1_sinpiso
for (i in 1:nrow(no_pisos)) {
row <- no_pisos[i, ]
matched <- matched_rows[matched_rows$id.x==row$id,]
if (nrow(matched)>0) {
matched$diferencia = 0
for (j in 1:nrow(matched)) {
row_match <- matched[j, ]
diff <- abs(row$preciom-row_match$preciom.y)
matched[j, ]$diferencia = diff
}
final_match <- matched[matched$diferencia==min(matched$diferencia),]
piso = final_match[1,]$piso.y
no_pisos[i, ]$piso = piso
}
}En la comparación con la variable preciom, se toma el
registro que tenga mayor cercanía en el dato de precio en el registro
sin información de piso, y así se obtiene un dato de piso
aproximado.
nuevos_pisos <- subset(no_pisos,!is.na(piso))
for (i in 1:nrow(nuevos_pisos)) {
row <- nuevos_pisos[i, ]
viviendas_1[viviendas_1$id==row$id,]$piso = row$piso
}
viviendas_2 <- viviendas_1[!is.na(viviendas_1$piso),]Los registro que no lograron imputarse en la comparación son eliminados, dejando una nueva base de datos con 8126 registros.
El proceso de imputación de datos para la variable
parquea es idéntico al proceso utilizado en la imputación
de datos de la variable piso.
resumen_parquea <- summarytools::descr(viviendas_2$parqueaderos)
kable(resumen_parquea, "html", caption = "Resumen de Piso") %>%
kable_styling("striped", full_width = FALSE) %>%
row_spec(0, bold = TRUE)| parqueaderos | |
|---|---|
| Mean | 1.8314471 |
| Std.Dev | 1.1158874 |
| Min | 1.0000000 |
| Q1 | 1.0000000 |
| Median | 2.0000000 |
| Q3 | 2.0000000 |
| Max | 10.0000000 |
| MAD | 1.4826000 |
| IQR | 1.0000000 |
| CV | 0.6092927 |
| Skewness | 2.3056212 |
| SE.Skewness | 0.0300828 |
| Kurtosis | 8.1627126 |
| N.Valid | 6627.0000000 |
| Pct.Valid | 81.5530396 |
par(mfrow = c(1, 2))
histograma <- hist(viviendas_2$parqueaderos,
las=1,
main = "Distribucion de Parqueadero",
xlab="Piso",
ylab = "Frecuencia",
col =c("lightblue")
)
box <- boxplot(viviendas_2$parqueaderos,
names = "Piso",
main = "Diagrama de Caja - Parqueadero",
col = c("lightblue"),
xlab="Parqueadero",
ylab = "Valores",
border = "black"
)Aquí se realiza el mismo proceso de crear dos dataframe, uno con los
registro que tiene datos de parqueadero y otro con los que no tienen
datos de parqueadero, validando coincidencias en las mismas variables:
zona, estrato, banios,
habitac y tipo.
De igual manera la validación adicional se hace con la variable
preciom, que en este caso sí presenta una mayor correlación
con la variable parquea, en comparación a la correlación
que hay entre parquea y areaconst.
##
## Pearson's product-moment correlation
##
## data: viviendas_2$parqueaderos and viviendas_2$areaconst
## t = 59.544, df = 6625, p-value < 0.00000000000000022
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
## 0.5745186 0.6058923
## sample estimates:
## cor
## 0.5904284
##
## Pearson's product-moment correlation
##
## data: viviendas_2$parqueaderos and viviendas_2$preciom
## t = 77.978, df = 6625, p-value < 0.00000000000000022
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
## 0.6790262 0.7041418
## sample estimates:
## cor
## 0.6917932
viviendas_2_sinparque <- subset(viviendas_2, is.na(parqueaderos))
viviendas_2_conparque <- subset(viviendas_2, !is.na(parqueaderos))
matched_parquea <- left_join(viviendas_2_sinparque, viviendas_2_conparque, by = c("zona", "estrato", "banios", "habitaciones", "tipo"))
no_parquea <- viviendas_2_sinparque
for (i in 1:nrow(no_parquea)) {
row <- no_parquea[i, ]
matched_p <- matched_parquea[matched_parquea$id.x==row$id,]
if (nrow(matched_p)>0) {
matched_p$diferencia = 0
for (j in 1:nrow(matched_p)) {
row_match <- matched_p[j, ]
diff <- abs(row$preciom-row_match$preciom.y)
matched_p[j, ]$diferencia = diff
}
final_match <- matched_p[matched_p$diferencia==min(matched_p$diferencia),]
parqueaderos = final_match[1,]$parqueaderos.y
no_parquea[i, ]$parqueaderos = parqueaderos
}
}Igualmente se toma el registro que tenga mayor cercanía en el dato de
precio en el registro sin información de parqueadero para obtener el
dato de parqueaderos.
nuevos_parquea <- subset(no_parquea,!is.na(parqueaderos))
for (i in 1:nrow(nuevos_parquea)) {
row <- nuevos_parquea[i, ]
viviendas_2[viviendas_2$id==row$id,]$parqueaderos = row$parqueaderos
}
viviendas_3 <- viviendas_2[!is.na(viviendas_2$parqueaderos),]Se eliminan los registros sin datos de parqueadero obteniendo una base de datos final con 8013 registros completos.
Las tres variables categóricas están en escala nominal. La variable
zona, tipo y barrio se pueden
resumir en las siguientes tablas de frecuencia.
viviendas_4 <- viviendas_3
zonas_freq <- viviendas_4 %>%
group_by(zona) %>%
summarize(conteo = n()) %>%
arrange(desc(conteo))
zonas_freq$freq_rel <- zonas_freq$conteo / sum(zonas_freq$conteo)
tipo_freq <- viviendas_4 %>%
group_by(tipo) %>%
summarize(conteo = n()) %>%
arrange(desc(conteo))
tipo_freq$freq_rel <- tipo_freq$conteo / sum(tipo_freq$conteo)
barrio_freq <- viviendas_4 %>%
group_by(barrio) %>%
summarize(conteo = n()) %>%
arrange(desc(conteo))
barrio_freq$freq_rel <- barrio_freq$conteo / sum(barrio_freq$conteo)
kable(zonas_freq, "html", caption = "Frecuencias Zona") %>%
kable_styling("striped", full_width = FALSE) %>%
row_spec(0, bold = TRUE)| zona | conteo | freq_rel |
|---|---|---|
| Zona Sur | 4653 | 0.5806814 |
| Zona Norte | 1809 | 0.2257581 |
| Zona Oeste | 1153 | 0.1438912 |
| Zona Oriente | 320 | 0.0399351 |
| Zona Centro | 78 | 0.0097342 |
kable(tipo_freq, "html", caption = "Frecuencias Tipo") %>%
kable_styling("striped", full_width = FALSE) %>%
row_spec(0, bold = TRUE)| tipo | conteo | freq_rel |
|---|---|---|
| Apartamento | 5026 | 0.6272308 |
| Casa | 2987 | 0.3727692 |
kable(head(barrio_freq, 15), "html", caption = "Frecuencia de barrios") %>%
kable_styling("striped", full_width = FALSE) %>%
row_spec(0, bold = TRUE)| barrio | conteo | freq_rel |
|---|---|---|
| valle del lili | 1007 | 0.1256708 |
| ciudad jardín | 513 | 0.0640210 |
| pance | 405 | 0.0505429 |
| la flora | 364 | 0.0454262 |
| santa teresita | 260 | 0.0324473 |
| el caney | 208 | 0.0259578 |
| el ingenio | 201 | 0.0250842 |
| la hacienda | 164 | 0.0204667 |
| normandía | 154 | 0.0192188 |
| los cristales | 150 | 0.0187196 |
| el limonar | 133 | 0.0165980 |
| acopi | 132 | 0.0164732 |
| prados del norte | 124 | 0.0154749 |
| el refugio | 118 | 0.0147261 |
| aguacatal | 102 | 0.0127293 |
Las frecuencias de esta variables también podemos observarlas gráficamente de la siguiente manera.
PieChart(zona, hole=0 ,values="%", data=viviendas_4, fill="blues", main="Porcentajes Zona", values_size=1)PieChart(tipo, hole=0 ,values="%", data=viviendas_4, fill="blues", main="Porcentajes Tipo", values_size=1)barrio_freq_1 <- viviendas_4 %>%
group_by(barrio) %>%
summarize(conteo = n()) %>%
arrange(desc(conteo))
barrio_freq_2 <- barrio_freq_1[barrio_freq_1$conteo>70,]
barrio_freq_3 <- barrio_freq_1[barrio_freq_1$conteo<=70,]
barrio_freq_t <- rbind(barrio_freq_2, c("OTROS", sum(barrio_freq_3$conteo)))
barrio_freq_t$conteo <- as.integer(barrio_freq_t$conteo)
ggplot(barrio_freq_t, aes(x = barrio, y = conteo, fill = barrio)) +
geom_bar(stat = "identity", width = 0.7) +
geom_text(aes(label = conteo), vjust = -0.5, size = 3) +
labs(title = "Distribucion por Barrios",
x = "Barrio",
y = "Conteo") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) +
theme(legend.position = "none") Claramente se puede observar que el mayor mercado inmobiliario se da en en la Zona Sur, específicamente en los barrios Valle del Lili y Ciudad Jardín, y el tipo vivienda más ofertada son los apartamentos con un 63% de los registros.
Ahora podemos analizar la variable preciom con la
variable categórica tipo para comparar a detalle los
valores de las viviendas para estas dos categorías.
ggplot(viviendas_4, aes(y=preciom, x=tipo))+
geom_jitter(color="#034A94", size=1, alpha=0.9) +
aes(color=paleta6)+
labs(title = "Tipo de vivienda - Precio de venta",
y= "Precio (millones)",
x= "Tipo")ggplot(viviendas_4, aes(x = tipo, y = preciom, fill = tipo)) +
geom_boxplot() +
labs(title = "Distribucion deprecio por tipo de vivienda",
x = "Tipo",
y = "Precio (millones)") +
scale_fill_manual(values = c("#f4d35e", "#ee964b")) +
theme_minimal()Podemos hacer el mismo procedimiento para observar la variable
preciom con la variable categórica zona.
ggplot(viviendas_4, aes(y=preciom, x=zona))+
geom_jitter(color="#034A94", size=1, alpha=0.9) +
aes(color=paleta6)+
labs(title = "Zona - Precio de venta",
y= "Precio (millones)",
x= "Zona")ggplot(viviendas_4, aes(x = zona, y = preciom, fill = zona)) +
geom_boxplot() +
labs(title = "Distribucion del precio por zona",
x = "Zona",
y = "Precio (millones)") +
scale_fill_manual(values = c("#f95738","#ee964b", "#f4d35e", "#faf0ca", "#0d3b66")) +
theme_minimal()
Tomando la variable
preciom como base de comparación,
debido a que estamos en un análisis descriptivo de mercado, también
sería de ayuda encontrar la relación que existe entre esta y las
variables estrato, cantidad de baños, cantidad de habitaciones y
cantidad de parqueaderos. Primero revisaremos la distribución de
frecuencias de estas variables cuantitativas discretas.
hist_par = ggplot(viviendas_4, aes(x = parqueaderos)) +
geom_histogram(bins = 10, fill = "lightblue", color = "white", alpha = 1) +
labs(title = "Distribucion de Parqueaderos",
x = "Parqueaderos",
y = "Frecuencia") +
theme_minimal() +
facet_wrap(~ tipo)
hist_ban = ggplot(viviendas_4, aes(x = banios)) +
geom_histogram(bins = 11, fill = "lightblue", color = "white", alpha = 1) +
labs(title = "Distribucion de Banios",
x = "Banios",
y = "Frecuencia") +
theme_minimal() +
facet_wrap(~ tipo)
hist_habi = ggplot(viviendas_4, aes(x = habitaciones)) +
geom_histogram(bins = 11, fill = "lightblue", color = "white", alpha = 1) +
labs(title = "Distribucion de Habitaciones",
x = "Habitaciones",
y = "Frecuencia") +
theme_minimal() +
facet_wrap(~ tipo)
hist_estra = ggplot(viviendas_4, aes(x = estrato)) +
geom_histogram(bins = 7, fill = "lightblue", color = "white", alpha = 1) +
labs(title = "Distribucion de Estrato",
x = "Estrato",
y = "Frecuencia") +
theme_minimal() +
facet_wrap(~ tipo)
ggplotly(hist_par)También podemos ver la correlación que hay entre estas variables y la
variable preciom referente al precio del inmueble.
viviendas_num <- subset(viviendas_4, select = c("preciom", "areaconst", "estrato", "parqueaderos", "banios", "habitaciones"))
matriz_cor <- cor(viviendas_num)
kable(matriz_cor, "html", caption = "Tabla de correlaciones de variables") %>%
kable_styling("striped", full_width = FALSE) %>%
row_spec(0, bold = TRUE)| preciom | areaconst | estrato | parqueaderos | banios | habitaciones | |
|---|---|---|---|---|---|---|
| preciom | 1.0000000 | 0.6902987 | 0.6225161 | 0.6875771 | 0.6979994 | 0.2748459 |
| areaconst | 0.6902987 | 1.0000000 | 0.2923154 | 0.5764458 | 0.6701126 | 0.5370090 |
| estrato | 0.6225161 | 0.2923154 | 1.0000000 | 0.4400703 | 0.4615877 | -0.0619810 |
| parqueaderos | 0.6875771 | 0.5764458 | 0.4400703 | 1.0000000 | 0.5792035 | 0.2773853 |
| banios | 0.6979994 | 0.6701126 | 0.4615877 | 0.5792035 | 1.0000000 | 0.5842138 |
| habitaciones | 0.2748459 | 0.5370090 | -0.0619810 | 0.2773853 | 0.5842138 | 1.0000000 |
Es de destacar que exista correlación significativa entre el precio y el área construida. De igual forma es importante la correlación entre el precio y el estrato, el número de parqueaderos y el número de baños.
Ahora nos enfocamos en las variables área construida y precio.
ggplot(viviendas_4, aes(x = areaconst, y = preciom)) +
geom_point(position = position_jitter(width = 0.2), color = "#034A94") +
#facet_wrap(~ tipo) +
stat_smooth(method = "loess" , formula =y ~ x) +
labs(title = "Dispercion precio - area - tipo", x = "Area Construida", y = "Precio (millones)") +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) +
theme_minimal()En la dispersión de datos área construída y precio, se observa en general un comportamiento natural de que a mayor área construida mayor precio de venta, y eso se puede ver en el crecimiento casi lineal de la mayoría de los registros. Pero también se ven datos atípicos de viviendas con precios bajos para áreas construidas grandes.
Para complementar el análisis segmentado de viviendas por zona, estrato y tipo, podemos revisar indicadores de centro de precio y área construída para cada zona, estrato y tipo.
calcular_moda <- function(x) {
tabla_frec <- table(x)
moda <- as.numeric(names(tabla_frec)[which.max(tabla_frec)])
return(moda)
}
resumen <- viviendas_4 %>%
group_by(zona, tipo, estrato) %>%
summarize(promedio_precio = mean(preciom),
mediana_precio = median(preciom),
moda_precio = calcular_moda(preciom),
promedio_area = mean(areaconst),
mediana_area = median(areaconst),
moda_area = calcular_moda(areaconst)) %>%
arrange(zona)
#datatable(resumen)
kable(resumen, "html", caption = "Indicadores de centro agrupados por zona - tipo - estrato") %>%
kable_styling("striped", full_width = FALSE) %>%
row_spec(0, bold = TRUE)| zona | tipo | estrato | promedio_precio | mediana_precio | moda_precio | promedio_area | mediana_area | moda_area |
|---|---|---|---|---|---|---|---|---|
| Zona Centro | Apartamento | 3 | 153.0000 | 120.0 | 120 | 86.95200 | 84.0 | 84 |
| Zona Centro | Apartamento | 4 | 208.6667 | 166.0 | 150 | 99.83333 | 108.0 | 120 |
| Zona Centro | Casa | 3 | 309.9394 | 287.5 | 350 | 196.52273 | 167.0 | 150 |
| Zona Centro | Casa | 4 | 450.0000 | 450.0 | 450 | 228.00000 | 228.0 | 228 |
| Zona Norte | Apartamento | 3 | 119.1329 | 119.0 | 120 | 60.23565 | 59.0 | 60 |
| Zona Norte | Apartamento | 4 | 209.7300 | 190.0 | 160 | 82.70717 | 73.0 | 60 |
| Zona Norte | Apartamento | 5 | 347.7647 | 320.0 | 320 | 112.28621 | 100.0 | 100 |
| Zona Norte | Apartamento | 6 | 649.1429 | 592.5 | 950 | 176.77616 | 163.0 | 227 |
| Zona Norte | Casa | 3 | 228.8929 | 200.0 | 170 | 155.37694 | 120.0 | 120 |
| Zona Norte | Casa | 4 | 407.3517 | 380.0 | 330 | 250.14379 | 258.0 | 120 |
| Zona Norte | Casa | 5 | 540.3106 | 480.0 | 350 | 322.13333 | 296.0 | 300 |
| Zona Norte | Casa | 6 | 799.0000 | 780.0 | 850 | 378.71290 | 304.0 | 298 |
| Zona Oeste | Apartamento | 3 | 149.8000 | 128.0 | 98 | 67.44000 | 60.0 | 60 |
| Zona Oeste | Apartamento | 4 | 233.9286 | 175.0 | 165 | 85.99911 | 66.5 | 61 |
| Zona Oeste | Apartamento | 5 | 494.8261 | 420.5 | 350 | 142.31443 | 117.5 | 110 |
| Zona Oeste | Apartamento | 6 | 780.6144 | 655.0 | 1400 | 193.06158 | 180.0 | 125 |
| Zona Oeste | Casa | 3 | 487.2857 | 400.0 | 299 | 252.44357 | 256.5 | 55 |
| Zona Oeste | Casa | 4 | 451.5500 | 395.0 | 395 | 285.10000 | 268.0 | 114 |
| Zona Oeste | Casa | 5 | 693.2857 | 650.0 | 1200 | 368.70408 | 300.0 | 487 |
| Zona Oeste | Casa | 6 | 1012.3922 | 920.0 | 1200 | 383.58235 | 370.0 | 400 |
| Zona Oriente | Apartamento | 3 | 114.8163 | 113.0 | 113 | 80.69265 | 62.0 | 60 |
| Zona Oriente | Apartamento | 4 | 262.5000 | 262.5 | 240 | 87.00000 | 87.0 | 84 |
| Zona Oriente | Apartamento | 5 | 105.0000 | 105.0 | 105 | 60.00000 | 60.0 | 60 |
| Zona Oriente | Casa | 3 | 239.5918 | 230.0 | 350 | 212.82397 | 179.0 | 90 |
| Zona Oriente | Casa | 4 | 265.0000 | 265.0 | 265 | 162.00000 | 162.0 | 162 |
| Zona Sur | Apartamento | 3 | 138.6615 | 127.0 | 115 | 66.39169 | 60.0 | 60 |
| Zona Sur | Apartamento | 4 | 203.1381 | 187.0 | 150 | 75.48757 | 70.0 | 60 |
| Zona Sur | Apartamento | 5 | 292.2556 | 280.0 | 250 | 101.42870 | 90.0 | 90 |
| Zona Sur | Apartamento | 6 | 593.6182 | 580.0 | 650 | 149.34848 | 136.0 | 130 |
| Zona Sur | Casa | 3 | 291.8025 | 270.0 | 350 | 202.28025 | 196.0 | 200 |
| Zona Sur | Casa | 4 | 387.2961 | 350.0 | 450 | 218.06529 | 200.0 | 200 |
| Zona Sur | Casa | 5 | 527.6209 | 470.0 | 450 | 261.89950 | 240.0 | 300 |
| Zona Sur | Casa | 6 | 995.6551 | 900.0 | 850 | 379.69162 | 330.0 | 300 |
Así pudimos ver diferentes indicadores y visualizaciones estadísticas del conjunto de datos sobre el comercio inmobiliario, caracterizando y resaltando comportamientos y valores desde una perspectiva de analitica descriptiva.
Se realiza Análisis de Componentes Principales (PCA) para reducir la dimensionalidad del conjunto de datos, escogiendo los componentes que expliquen la mayor cantidad de varianza, lo cuales son una combinación lineal de las variables originales.
Se toman las variables numéricas del conjunto de datos que no tiene
datos faltantes: piso, estrato,
preciom, areaconst, parqueaderos,
banios, habitaciones.
viviendas_5 <- viviendas_4 %>% select(piso,estrato,preciom,areaconst,parqueaderos,banios,habitaciones)Es necesario realizar una normalización del conjunto de datos con el fin de evitar afectaciones o sesgos en las estimaciones.
viviendas_5_z= scale(viviendas_5)
kable(head(viviendas_5_z), "html") %>%
kable_styling("striped", full_width = FALSE) %>%
row_spec(0, bold = TRUE)| piso | estrato | preciom | areaconst | parqueaderos | banios | habitaciones |
|---|---|---|---|---|---|---|
| -0.2506099 | -1.6307671 | -0.5548732 | -0.7231613 | -0.6863173 | -0.0701159 | 1.7996874 |
| -1.0401272 | -1.6307671 | -0.3409172 | -0.3665318 | -0.6863173 | -0.8035864 | -0.4229011 |
| -0.6453686 | -1.6307671 | -0.2492217 | 0.3467271 | 0.2285822 | -0.8035864 | 0.3179617 |
| -0.6453686 | -0.6474473 | -0.0963960 | 0.7746824 | 1.1434816 | 1.3968252 | -0.4229011 |
| -1.0401272 | 0.3358725 | -0.5243081 | -0.5805095 | -0.6863173 | -0.8035864 | -0.4229011 |
| -1.0401272 | 0.3358725 | -0.5854384 | -0.6019073 | -0.6863173 | -0.0701159 | -0.4229011 |
Se realiza la estimación de los Componentes Principales.
pca_result <- prcomp(viviendas_5_z)
pca_scores <- as.data.frame(pca_result$rotation)
kable(pca_scores, "html") %>%
kable_styling("striped", full_width = FALSE) %>%
row_spec(0, bold = TRUE)| PC1 | PC2 | PC3 | PC4 | PC5 | PC6 | PC7 | |
|---|---|---|---|---|---|---|---|
| piso | 0.0777397 | -0.5737330 | 0.7916306 | 0.1818656 | -0.0430778 | 0.0361065 | 0.0432842 |
| estrato | -0.3097066 | -0.5373852 | -0.2531937 | -0.5561080 | 0.1106928 | 0.4627736 | 0.1245933 |
| preciom | -0.4677010 | -0.2171763 | -0.0903084 | 0.0718681 | -0.2840432 | -0.2562475 | -0.7579091 |
| areaconst | -0.4462483 | 0.1854727 | 0.0580706 | 0.2096016 | -0.6967934 | 0.2567932 | 0.4095039 |
| parqueaderos | -0.4196444 | -0.1116545 | -0.1763923 | 0.6686756 | 0.5477983 | 0.1316973 | 0.1255525 |
| banios | -0.4657343 | 0.0648996 | 0.1902629 | -0.3306012 | 0.1993783 | -0.6764841 | 0.3687818 |
| habitaciones | -0.2952062 | 0.5327319 | 0.4799708 | -0.2283565 | 0.2825865 | 0.4218489 | -0.2978588 |
En este caso se desarrollan algunas gráficas para explicar la estimación de los componentes principales y para determinar la cantidad de varianza explicada por cada uno.
El primer componente principal explica el \(50.8\%\) de la varianza contenida en el conjunto de datos, y el segundo componente principal explica el \(19.9\%\) de la varianza, completando un total de \(70.7\%\) de varianza explicada entre los dos primeros componentes. Los dos primeros componentes principales explican la mayor parte de la variabilidad del conjunto de datos.
Ahora podemos ver la contribución de las variables en cada componente principal.
kable(data.frame(get_pca_var(pca_result)$contrib[,1:3]), "html") %>%
kable_styling("striped", full_width = FALSE) %>%
row_spec(0, bold = TRUE)| Dim.1 | Dim.2 | Dim.3 | |
|---|---|---|---|
| piso | 0.6043468 | 32.916959 | 62.6678964 |
| estrato | 9.5918157 | 28.878284 | 6.4107060 |
| preciom | 21.8744247 | 4.716554 | 0.8155599 |
| areaconst | 19.9137585 | 3.440012 | 0.3372197 |
| parqueaderos | 17.6101385 | 1.246672 | 3.1114232 |
| banios | 21.6908471 | 0.421196 | 3.6199987 |
| habitaciones | 8.7146687 | 28.380323 | 23.0371960 |
Las variables preciom, banios y
areaconst son las que tienen un mayor porcentaje de
contribución en el CP1, mientras que las variables piso,
estrato y habitaciones presentan mayor
influencia en el CP2. De esto se podría decir que las variables
banios y areaconst afectan en mayor proporción
la variabilidad de los precios de los inmuebles.
También podemos observar gráficas de contribución de las variables en el plano de dos dimensiones.
fviz_pca_var(pca_result,
col.var = "contrib", # Color by contributions to the PC
gradient.cols = c("#FF7F00", "#034D94"),
repel = TRUE # Avoid text overlapping
)El gráfico en el plano de las dimensiones de los componentes
principales nos permite validar la contribución de cada variable y la
dirección de los vectores propios. El gráfico en el plano de las
dimensiones de los componentes principales nos permite validar la
contribución de cada variable y la dirección de los vectores propios. Se
observa que las variable preciom, banios,
areaconst y parqueaderos estan juntas, lo que
significa que ejercen influencia en la variabilidad del mismo
componente, en este caso el componente principal 1. La longitud del
vector hace referencia a la significancia en la representación de la
varianza en el componente principal.
precios <- rbind(viviendas_5[5654,],
viviendas_5[283,])
precios <- as.data.frame(precios)
rownames(precios) = c("5654","283")
casos1 <- rbind(pca_result$x[5654,1:2],pca_result$x[283,1:2]) # CP1
rownames(casos1) = c("5654","283")
casos1 <- as.data.frame(casos1)
fviz_pca_ind(pca_result, col.ind = "#DEDEDE", gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07")) +
geom_point(data = casos1, aes(x = PC1, y = PC2), color = c("red","#00AFBB"), size = 3)
Este gráfico permite observar la dirección en el plano: el punto azul
representa el inmueble más barato en el conjunto de datos, mientras que
el punto rojo representa el inmueble más costoso.
Por medio de este análisis se realizan agrupaciones de registros similares en función de sus características, con el fin de generar grupos homogéneos de registros dentro de la base de datos de ventas de inmuebles.
En este caso se realiza un análisis de conglomerados jerárquico
aglomerativo con distancias euclidianas iniciando con cuatro
agrupaciones \(k=4\), y se utiliza el
mismo conjunto de datos normalizado que se utilizó en el análisis de
componentes principales. Para realizar este análisis se seleccionan las
variables: piso, estrato,
preciom, areaconst, parqueaderos,
banios y habitaciones.
viviendas_5_z = as.data.frame(viviendas_5_z)
dist <- dist(viviendas_5_z, method="euclidean")
# Cluster jerarquico
hc <- hclust(dist, method='complete')
# Determinación de cada registro, definimos 4 clusters
clusters <- cutree(hc, k=4)
# Asignamos los clusters
assigned_cluster <- viviendas_5_z %>% mutate(cluster = as.factor(clusters))Aquí podemos ver con un gráfico de dispersión como quedan
categorizados los registros en un plano entre las variables
preciom y areaconst, las cuales son variables
de gran interés
ggplot(assigned_cluster, aes(x = areaconst, y = preciom, color = cluster)) +
geom_point(size = 2, alpha = 0.5) +
geom_text(aes(label = cluster), vjust = -.8) +
theme_classic()También podemos observar un Dendograma
plot(hc, cex = 0.6, main = "Dendograma de Viviendas", las=1,
ylab = "Distancia euclidiana", xlab = "Grupos")
rect.hclust(hc, k = 4, border = 2:5)Debido a que el gráfico de un Dendograma no proporciona mucha información, podemos hacer el conteo de los registros que pertenecen a cada cluster y también podemos ver un gráfico de dispersión de los clusters en función de las agrupaciones realizadas.
cluster_counts <- table(clusters)
kable(cluster_counts, "html") %>%
kable_styling("striped", full_width = FALSE) %>%
row_spec(0, bold = TRUE)| clusters | Freq |
|---|---|
| 1 | 7851 |
| 2 | 59 |
| 3 | 14 |
| 4 | 89 |
fviz_cluster(list(data = viviendas_5, cluster = clusters),
geom = "point",
ellipse.type = "convex",
ggtheme = theme_minimal())Validamos el número óptimo de clusters midiendo el índice de Silhouette promedio para valorar la mejor alternativa en la elección del número de conglomerados.
silhouette_res = c()
for(i in 2:6){
dist_ev <- dist(viviendas_5_z, method = 'euclidean')
hc_ev <- hclust(dist_ev, method = 'complete')
cluster_ev <- cutree(hc_ev, k = i)
# Calcular el coeficiente de Silhouette
sil <- silhouette(cluster_ev, dist(viviendas_5_z))
sil_avg <- mean(sil[,3])
silhouette_res = c(silhouette_res, sil_avg)
}
sil_df <- data.frame(K = c(2,3,4,5,6),silhouette_value = silhouette_res)
kable(sil_df, "html") %>%
kable_styling("striped", full_width = FALSE) %>%
row_spec(0, bold = TRUE)| K | silhouette_value |
|---|---|
| 2 | 0.5398479 |
| 3 | 0.5074854 |
| 4 | 0.4988204 |
| 5 | 0.2367314 |
| 6 | 0.2366606 |
La tabla anterior indica que los mejores resultados de agrupación se dan cuando \(k=2\), lo que significa que solo deberían haber dos clusters de agrupamiento.
Realizaremos un último análisis de conglomerados con \(k=2\) y observaremos las gráficas de representación de los los clusters.
clusters_final <- cutree(hc, k=2)
assigned_cluster_final <- viviendas_5_z %>% mutate(cluster = as.factor(clusters_final))ggplot(assigned_cluster_final, aes(x = areaconst, y = preciom, color = cluster)) +
geom_point(size = 2, alpha = 0.5) +
geom_text(aes(label = cluster), vjust = -.8) +
theme_classic()cluster_counts_fin <- table(clusters_final)
kable(cluster_counts_fin, "html") %>%
kable_styling("striped", full_width = FALSE) %>%
row_spec(0, bold = TRUE)| clusters_final | Freq |
|---|---|
| 1 | 7910 |
| 2 | 103 |
fviz_cluster(list(data = viviendas_5, cluster = clusters_final),
geom = "point",
ellipse.type = "convex",
ggtheme = theme_minimal())Finalmente obtenemos dos grupos en donde cada uno de los registros de cada grupo presentan similitudes importantes en sus características que ayudarán a la inmobiliaria a definir las estrategias de mercado específicas para cada grupo.
Al conjunto de datos original se le puede agregar una columna que identifique a cada registro con el cluster al cual pertenece, siendo esta columna una etiqueta de análisis.
viviendas_5$cluster <- clusters_final
muestra_ale <- viviendas_5[sample(nrow(viviendas_5), 10), ]
kable(muestra_ale, "html") %>%
kable_styling("striped", full_width = FALSE) %>%
row_spec(0, bold = TRUE)| piso | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | cluster |
|---|---|---|---|---|---|---|---|
| 3 | 5 | 750 | 433.00 | 1 | 5 | 10 | 1 |
| 6 | 6 | 850 | 190.00 | 3 | 4 | 3 | 1 |
| 3 | 4 | 600 | 420.00 | 4 | 5 | 5 | 1 |
| 9 | 5 | 220 | 85.00 | 2 | 2 | 3 | 1 |
| 2 | 4 | 225 | 130.00 | 1 | 3 | 3 | 1 |
| 1 | 3 | 62 | 61.00 | 1 | 1 | 2 | 1 |
| 3 | 6 | 850 | 350.00 | 4 | 3 | 3 | 1 |
| 3 | 4 | 560 | 500.00 | 4 | 8 | 8 | 1 |
| 3 | 4 | 245 | 72.00 | 1 | 2 | 3 | 1 |
| 8 | 5 | 246 | 159.38 | 2 | 4 | 4 | 1 |
viviendas_5$tipo <- viviendas_4$tipo
viviendas_5$zona <- viviendas_4$zona
tabla_5_1 <- table(viviendas_5$cluster, viviendas_5$tipo)
tabla_5_2 <- table(viviendas_5$cluster, viviendas_5$zona)
barplot(tabla_5_1, beside = FALSE, col = c("skyblue", "pink"),
legend = TRUE, xlab = "Tipo", ylab = "Frecuencia")barplot(tabla_5_2, beside = FALSE, col = c("skyblue", "pink"),
legend = TRUE, xlab = "Zona", ylab = "Frecuencia")Con el gráfico de dispersión entre preciom y
areaconst, junto con los gráficos de barras de distribución
para zona y para tipo, podemos concluir que el
cluster 2 hacer referencia a inmuebles que son únicamente de tipo Casa,
que tienen un gran área construida, que generalmente presentan precios
altos y que se encuentran en la Zona Sur, Zona Oriente y Zona Norte.
En este análisis intentaremos presentar posibles asociaciones entre
variables categóricas, definiendo patrones o estructuras definidas en
los datos. Se analizarán las variables categóricas zona,
estrato y tipo. La variable
barrio se excluye debido a la gran cantidad de posibles
resultados.
viviendas_6 <- viviendas_4 %>% select(zona,estrato,tipo)
kable(head(viviendas_6), "html") %>%
kable_styling("striped", full_width = FALSE) %>%
row_spec(0, bold = TRUE)| zona | estrato | tipo |
|---|---|---|
| Zona Oriente | 3 | Casa |
| Zona Oriente | 3 | Casa |
| Zona Oriente | 3 | Casa |
| Zona Sur | 4 | Casa |
| Zona Norte | 5 | Apartamento |
| Zona Norte | 5 | Apartamento |
Iniciamos haciendo el análisis de correspondencia simple entre
zona y estrato construyendo una tabla cruzada.
También validamos la independencia entre las variables mediante una
prueba Chi-squared.
viviendas_ca_1 <- viviendas_6 %>% select(zona,estrato)
viviendas_ca_1$estrato <- as.factor(viviendas_ca_1$estrato)
tabla_cruz_1 <- table(viviendas_ca_1$zona, viviendas_ca_1$estrato)
colnames(tabla_cruz_1) <- c("Estrato 3", "Estrato 4", "Estrato 5", "Estrato 6" )
kable(tabla_cruz_1, "html") %>%
kable_styling("striped", full_width = FALSE) %>%
row_spec(0, bold = TRUE)| Estrato 3 | Estrato 4 | Estrato 5 | Estrato 6 | |
|---|---|---|---|---|
| Zona Centro | 71 | 7 | 0 | 0 |
| Zona Norte | 527 | 382 | 757 | 143 |
| Zona Oeste | 39 | 76 | 279 | 759 |
| Zona Oriente | 316 | 3 | 1 | 0 |
| Zona Sur | 352 | 1596 | 1670 | 1035 |
##
## Pearson's Chi-squared test
##
## data: tabla_cruz_1
## X-squared = 3852.4, df = 12, p-value < 0.00000000000000022
El resultado de la prueba Chi-squared indica que se debe rechazar la
hipótesis de independencia entre las variables zona y
estrato, lo que significa que existe entre ellas algún
grado de relación. Ahora ejecutamos el análisis de correspondencia para
observar el resultado gráficamente.
Del gráfico resultante del análisis de correspondencia entre
zona y estrato podemos destacar lo siguiente:
La Zona Oriente en su gran mayoría son estrato 4, la Zona Oeste es
estrato 6, Zona Sur y Zona Norte tienen estrato 5 y estrato 4.
Finalmente medimos el grado de representatividad del proceso calculando los valores de la varianza acumulada.
fviz_screeplot(resultados_ca_1, addlabels = TRUE, ylim = c(0, 80))+ggtitle("")+
ylab("Porcentaje de varianza explicado") + xlab("Ejes")La gráfica indica que el primer componente explica el \(69.5\%\) de la varianza, y los dos primeros componentes resumen un \(97.5\%\) de los datos.
En este caso se realiza el proceso anterior para las variables
zona y tipo.
viviendas_ca_2 <- viviendas_6 %>% select(zona,tipo)
tabla_cruz_2 <- table(viviendas_ca_2$zona, viviendas_ca_2$tipo )
kable(tabla_cruz_2, "html") %>%
kable_styling("striped", full_width = FALSE) %>%
row_spec(0, bold = TRUE)| Apartamento | Casa | |
|---|---|---|
| Zona Centro | 11 | 67 |
| Zona Norte | 1173 | 636 |
| Zona Oeste | 1019 | 134 |
| Zona Oriente | 52 | 268 |
| Zona Sur | 2771 | 1882 |
##
## Pearson's Chi-squared test
##
## data: tabla_cruz_1
## X-squared = 3852.4, df = 12, p-value < 0.00000000000000022
## eigenvalue percentage of variance cumulative percentage of variance
## dim 1 0.09016511 100 100
Al analizar la variable tipo el resultado solo genera
una dimensión que explica el \(100\%\)
de la varianza, por lo cual no es posible graficar la relación en el
plano de dos dimensiones. Esto es debido a que la variable
tipo solo tiene dos valores posibles.
Finalmente realizaremos un análisis de correspondencia múltiple para analizar las tres variables en conjunto.
viviendas_7 <- viviendas_6
viviendas_7$estrato <- as.factor(viviendas_7$estrato)
resultados_mca <- MCA(viviendas_7)De este análisis podemos concluir que los inmuebles de tipo Casa están más relacionados con la Zona Norte, y que los inmuebles de tipo Apartamento están más relacionados a las Zona Sur y a su vez al estrato 5.
El análisis de componentes principales permitió reducir la
dimensionalidad de las variables numéricas en únicamente dos componentes
que contribuyen en la mayoría de la explicación de la variabilidad de
los datos. Esto nos ayuda a observar qué variables presentan mayor
relevancia en la información contenida en el conjunto de datos y en las
definiciones de las estrategias relacionadas al mercado inmobiliario en
la ciudad. Esta actividad nos permitió visualizar los datos en dos
dimensiones, observando patrones, influencia y relaciones entre las
variables, particularmente se observa como las variables
preciom, banios y areaconst se
relacionan e influyen en la varianza general del conjunto de datos de
ventas de inmuebles.
El análisis de conglomerados sirvió para agrupar registros que presentan características similares. Este proceso nos permitió identificar cual es el número adecuado de clusters en los cuales se pueden agrupar los datos, y así mismo validar cómo se realiza el agrupamiento. A partir de este agrupamiento se pueden generar estrategias de mercado específicas para cada cluster, identificando también diferentes características que sobresalen en cada grupo. En este caso hay características muy particulares para los inmuebles de tipo casa que se ubican en la zona sur de la ciudad; en general son inmuebles que presentan una gran área construída, que hacen parte del mismo barrio y que sus precios son variados. Para estos casos la estrategia de mercado debe ajustarse a las características y a las necesidades, asegurando así un método exitoso de venta de los inmuebles agrupados.
En el análisis de correspondencia se asociaron registros
basándose en las definiciones de las variables categóricas. En este caso
se pudo hacer un buen análisis entre las variables zona y
estrato, que permitió identificar que hay cierto grado de
relación entre ellas. En efecto se determinó que la Zona Oeste se
relaciona con el estrato 5, que el estrato 4 y 5 está relacionado con la
Zona Sur, y que la Zona Oriente y la Zona Centro podrían relacionarse al
estrato 3. Visto desde una perspectiva de mercado inmobiliario, esta
segmentación de datos ayuda a enfocar diferentes esfuerzos a diferentes
grupos, los cuales tendrán diferentes comportamientos en el precio de
venta del inmueble.