El presente trabajo tiene como propósito analizar el mercado de viviendas urbanas a partir de una base de datos de propiedades residenciales, con el fin de identificar patrones y características que puedan aportar información útil para la toma de decisiones en el sector inmobiliario. Para abordar este reto se realizará inicialmente una exploración y preparación de los datos, seguida de la aplicación de técnicas de análisis multivariado como el Análisis de Componentes Principales (ACP), el Análisis de Conglomerados y el Análisis de Correspondencia. Finalmente, los resultados obtenidos serán representados mediante diferentes recursos gráficos y visualizaciones, con el objetivo de interpretar los principales hallazgos y plantear conclusiones y recomendaciones a partir de la evidencia encontrada
Como primer paso procedemos a cargar el paquete MODELOS que contiene la base de datos vivienda.
library(paqueteMODELOS)
data("vivienda")
dim(vivienda)
## [1] 8322 13
A partir de esto, se identifica un total de 8322 registros y 13 variables.
A continuación, se presenta la descripción y clasificación de las
variables que componen la base de datos.
Se verifica la presencia de valores faltantes en cada una de las variables de la base de datos.
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
Con base en el resultado anterior, se observa la presencia de datos faltantes en todas las variables. Sin embargo, piso y parqueaderos presentan la mayor cantidad de valores faltantes, con 2638 y 1605 registros, respectivamente. Debido a su magnitud, se analizará el comportamiento de estos valores antes de tomar una decisión sobre su tratamiento.
table(vivienda$tipo, is.na(vivienda$piso))
##
## FALSE TRUE
## Apartamento 3719 1381
## Casa 1965 1254
table(vivienda$piso, useNA = "ifany")
##
## 01 02 03 04 05 06 07 08 09 10 11 12 <NA>
## 860 1450 1097 607 567 245 204 211 146 130 84 83 2638
table(vivienda$tipo, is.na(vivienda$piso), useNA = "ifany")
##
## FALSE TRUE
## Apartamento 3719 1381
## Casa 1965 1254
## <NA> 0 3
Al analizar los 2638 valores faltantes de la variable piso, se encontró que 1381 corresponden a apartamentos y 1254 a casas. Los tres registros restantes no tienen información asociada en la variable tipo. Por lo tanto, los valores faltantes de piso no pueden atribuirse exclusivamente a un tipo específico de vivienda.
table(vivienda$tipo, is.na(vivienda$parqueaderos), useNA = "ifany")
##
## FALSE TRUE
## Apartamento 4231 869
## Casa 2486 733
## <NA> 0 3
En la variable parqueaderos se identificaron 1.605 valores faltantes. Al analizar estos registros según el tipo de vivienda, se observa que los valores faltantes se distribuyen entre casas y apartamentos. Adicionalmente, 3 registros no presentan información sobre el tipo de vivienda, por lo que no pueden ser clasificados dentro de ninguna de estas dos categorías.
vivienda[is.na(vivienda$tipo), c("id", "tipo", "piso", "parqueaderos")]
## # A tibble: 3 × 4
## id tipo piso parqueaderos
## <dbl> <chr> <chr> <dbl>
## 1 NA <NA> <NA> NA
## 2 NA <NA> <NA> NA
## 3 NA <NA> <NA> NA
vivienda[is.na(vivienda$id), ]
## # A tibble: 3 × 13
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <dbl> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 NA <NA> <NA> NA NA NA NA NA NA
## 2 NA <NA> <NA> NA NA NA NA NA NA
## 3 NA <NA> <NA> NA 330 NA NA NA NA
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
Durante la revisión de los datos faltantes se planteó la posibilidad de que los tres registros sin información en la variable tipo correspondieran a los mismos registros faltantes identificados en otras variables. Para comprobarlo, se examinaron las observaciones asociadas a estos valores y posteriormente los registros cuyo id era faltante. Se encontró que los mismos tres registros presentan valores NA en prácticamente todas las variables de la base de datos. Uno de ellos únicamente contiene información en la variable preciom, mientras que los demás no cuentan con información suficiente para caracterizar una propiedad. Por esta razón, se considera pertinente excluir estas tres observaciones antes de continuar con los análisis posteriores.
vivienda_limpia <- vivienda[!is.na(vivienda$id), ]
dim(vivienda_limpia)
## [1] 8319 13
colSums(is.na(vivienda_limpia))
## 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
Luego de excluir los tres registros identificados, la base de datos quedó conformada por 8319 observaciones y 13 variables. Al realizar nuevamente la revisión de valores faltantes, se encontró que estos permanecen únicamente en las variables piso y parqueaderos, con 2635 y 1602 valores faltantes, respectivamente.
Se verifica la presencia de registros duplicados en la base de datos depurada (vivienda_limpia), considerando inicialmente la duplicación de filas completas.
sum(duplicated(vivienda_limpia))
## [1] 0
La revisión de duplicados no identificó registros completamente repetidos en la base de datos depurada. Ahora bien, es importante comprobar si existen identificadores (id) identicos.
sum(duplicated(vivienda_limpia$id))
## [1] 0
Al verificar la variable id, se obtuvo cero valores duplicados. Esto indica que cada una de las 8319 observaciones conservadas posee un identificador único y no se requiere realizar ninguna eliminación adicional por duplicidad.
Para la identificación de posibles valores atípicos se analizarán las variables cuantitativas relacionadas con las características físicas y económicas de las viviendas: precio, área construida, número de parqueaderos, baños y habitaciones.
variables_num <- vivienda_limpia[, c(
"preciom",
"areaconst",
"parqueaderos",
"banios",
"habitaciones"
)]
summary(variables_num)
## 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 :1602
## habitaciones
## Min. : 0.000
## 1st Qu.: 3.000
## Median : 3.000
## Mean : 3.605
## 3rd Qu.: 4.000
## Max. :10.000
##
El resumen descriptivo permite observar los rangos y principales medidas de las variables seleccionadas. Se identifican valores máximos considerablemente superiores a sus respectivas medianas en variables como precio y área construida. Asimismo, se observa la presencia de valores iguales a cero en las variables baños y habitaciones, los cuales requieren una revisión adicional antes de determinar si corresponden a valores válidos o a posibles inconsistencias en los datos
Debido a que el resumen descriptivo mostró valores mínimos iguales a cero en las variables baños y habitaciones, se revisa su frecuencia para determinar la magnitud de estos casos.
table(vivienda_limpia$banios)
##
## 0 1 2 3 4 5 6 7 8 9 10
## 45 496 2946 1993 1456 890 314 107 48 15 9
table(vivienda_limpia$habitaciones)
##
## 0 1 2 3 4 5 6 7 8 9 10
## 66 59 926 4097 1729 679 318 173 138 83 51
Se identificaron 45 propiedades registradas con cero baños y 66 con cero habitaciones. Debido a que estos valores podrían corresponder a características reales o a información registrada de manera incompleta, se procede a examinar estos casos con mayor detalle.
table(
vivienda_limpia$tipo[vivienda_limpia$banios == 0]
)
##
## Apartamento Casa
## 14 31
table(
vivienda_limpia$tipo[vivienda_limpia$habitaciones == 0]
)
##
## Apartamento Casa
## 21 45
Al analizar estos registros según el tipo de vivienda, se observa que los valores iguales a cero están presentes tanto en casas como en apartamentos. En el caso de los baños, 14 corresponden a apartamentos y 31 a casas, mientras que para habitaciones se identifican 21 apartamentos y 45 casas. Por lo tanto, estos valores no parecen estar asociados exclusivamente a un tipo específico de vivienda.
vivienda_limpia[vivienda_limpia$banios == 0,
c("id", "tipo", "preciom", "areaconst",
"banios", "habitaciones")]
## # A tibble: 45 × 6
## id tipo preciom areaconst banios habitaciones
## <dbl> <chr> <dbl> <dbl> <dbl> <dbl>
## 1 243 Casa 190 435 0 0
## 2 3273 Casa 400 324 0 0
## 3 7245 Casa 1200 752 0 0
## 4 4995 Casa 420 235 0 0
## 5 1816 Casa 750 183 0 0
## 6 7509 Casa 1200 660 0 0
## 7 6839 Apartamento 900 250 0 0
## 8 4393 Casa 380 264 0 0
## 9 1898 Apartamento 220 68 0 3
## 10 1425 Casa 395 220 0 0
## # ℹ 35 more rows
vivienda_limpia[vivienda_limpia$habitaciones == 0,
c("id", "tipo", "preciom", "areaconst",
"banios", "habitaciones")]
## # A tibble: 66 × 6
## id tipo preciom areaconst banios habitaciones
## <dbl> <chr> <dbl> <dbl> <dbl> <dbl>
## 1 243 Casa 190 435 0 0
## 2 2013 Casa 270 330 3 0
## 3 2014 Casa 270 330 3 0
## 4 2741 Casa 485 320 4 0
## 5 3273 Casa 400 324 0 0
## 6 4095 Casa 700 550 6 0
## 7 4118 Casa 620 450 3 0
## 8 4329 Casa 790 540 6 0
## 9 4512 Casa 620 300 8 0
## 10 7245 Casa 1200 752 0 0
## # ℹ 56 more rows
La inspección de los registros con valores iguales a cero permitió identificar situaciones poco habituales, como viviendas de áreas considerables registradas sin baños ni habitaciones, propiedades con habitaciones pero sin baños y propiedades con baños pero sin habitaciones. Aunque estas configuraciones son posibles, no representan características comunes en viviendas residenciales. Por esta razón, los valores se consideran potencialmente inconsistentes y se tendrán en cuenta durante la preparación de los datos para los análisis posteriores, sin asumir de manera inmediata que corresponden a errores de registro.
par(mfrow = c(2, 3))
boxplot(vivienda_limpia$preciom,
main = "Precio",
ylab = "Millones de pesos")
boxplot(vivienda_limpia$areaconst,
main = "Área construida",
ylab = "Metros cuadrados")
boxplot(vivienda_limpia$parqueaderos,
main = "Parqueaderos",
ylab = "Cantidad")
boxplot(vivienda_limpia$banios,
main = "Baños",
ylab = "Cantidad")
boxplot(vivienda_limpia$habitaciones,
main = "Habitaciones",
ylab = "Cantidad")
par(mfrow = c(1, 1))
Los diagramas de caja evidencian la presencia de valores atípicos en las variables precio, área construida, parqueaderos, baños y habitaciones. No obstante, estos valores pueden corresponder a propiedades con características particulares dentro del mercado inmobiliario, por lo que no necesariamente representan errores y se mantienen en la base de datos. En el caso de baños y habitaciones, se mantienen las posibles inconsistencias identificadas previamente en los registros con valores iguales a cero.
En primer lugar, se revisan las variables zona, tipo, piso y estrato mediante la función unique(), con el fin de identificar los valores únicos presentes en cada una de sus categorías y detectar posibles inconsistencias en su digitación. Posteriormente, se analizará de manera independiente la variable barrio, debido a que presenta una mayor cantidad de valores únicos y requiere una revisión más detallada.
unique(vivienda_limpia$zona)
## [1] "Zona Oriente" "Zona Sur" "Zona Norte" "Zona Oeste" "Zona Centro"
unique(vivienda_limpia$tipo)
## [1] "Casa" "Apartamento"
unique(vivienda_limpia$piso)
## [1] NA "02" "01" "03" "04" "05" "06" "07" "08" "09" "10" "11" "12"
unique(vivienda_limpia$estrato)
## [1] 3 4 5 6
length(unique(vivienda_limpia$barrio))
## [1] 436
El resultado anterior indica que la variable barrio presenta 436 valores únicos. Sin embargo, es importante considerar que algunos de estos valores pueden corresponder al mismo barrio, pero estar registrados con diferencias en el uso de mayúsculas y minúsculas o con espacios adicionales. R interpreta estas diferencias de escritura como categorías distintas, lo que podría generar una sobreestimación del número real de barrios. Por esta razón, se realiza una estandarización temporal mediante las funciones para arreglar mayusculas y minusculas (tolower) y espacios (trimws), con el fin de eliminar las diferencias asociadas al uso de mayúsculas y minúsculas y a la presencia de espacios innecesarios, respectivamente.
length(unique(tolower(trimws(vivienda_limpia$barrio))))
## [1] 407
barrios_originales <- unique(vivienda_limpia$barrio)
barrios_normalizados <- tolower(trimws(barrios_originales))
duplicados_barrio <- barrios_originales[
duplicated(barrios_normalizados) |
duplicated(barrios_normalizados, fromLast = TRUE)
]
sort(duplicados_barrio)
## [1] "belalcazar" "Belalcazar" "brisas de los"
## [4] "Brisas De Los" "camino real" "Camino Real"
## [7] "centenario" "Centenario" "chiminangos"
## [10] "Chiminangos" "ciudad 2000" "Ciudad 2000"
## [13] "ciudad jardín" "Ciudad Jardín" "ciudad pacifica"
## [16] "Ciudad Pacifica" "colseguros andes" "Colseguros Andes"
## [19] "el bosque" "El Bosque" "el caney"
## [22] "El Caney" "el ingenio" "El Ingenio"
## [25] "la flora" "La Flora" "la hacienda"
## [28] "La Hacienda" "los guaduales" "Los Guaduales"
## [31] "miraflores" "Miraflores" "pance"
## [34] "Pance" "prados del limonar" "Prados Del Limonar"
## [37] "prados del norte" "Prados Del Norte" "quintas de don"
## [40] "Quintas De Don" "san fernando" "San Fernando"
## [43] "santa anita" "Santa Anita" "santa isabel"
## [46] "Santa Isabel" "santa monica" "Santa Monica"
## [49] "santa teresita" "Santa Teresita" "santo domingo"
## [52] "Santo Domingo" "valle del lili" "Valle Del Lili"
## [55] "villa del prado" "Villa Del Prado" "villas de veracruz"
## [58] "Villas De Veracruz"
Los resultados anteriores indican que, al estandarizar la escritura de los nombres, la variable barrio presenta realmente 407 categorías únicas, en comparación con las 436 identificadas inicialmente. Adicionalmente, se obtuvo el listado de los valores duplicados, evidenciando que las diferencias entre estos registros se deben al uso de mayúsculas y minúsculas. Por lo tanto, se considera pertinente estandarizar la variable barrio para evitar que un mismo barrio sea interpretado como categorías diferentes.Por lo anterior, se procede a convertir los registros de la variable barrio a minúsculas, con el propósito de estandarizar la información y evitar que un mismo barrio sea considerado como categorías diferentes debido únicamente al uso de mayúsculas y minúsculas.
vivienda_limpia$barrio <- tolower(trimws(vivienda_limpia$barrio))
length(unique(vivienda_limpia$barrio))
## [1] 407
Finalmente, se realiza la misma comprobación teniendo en cuenta el uso de tildes. Es posible que algunos registros correspondan al mismo barrio, pero sean reconocidos por R como categorías diferentes debido a la presencia o ausencia de tildes en su digitación. Por esta razón, se verifica si estas diferencias están generando categorías duplicadas dentro de la variable barrio.
barrios_sin_tilde <- iconv(
vivienda_limpia$barrio,
from = "UTF-8", #indica que el texto de entrada esta escrito en caracteres especiales
to = "ASCII//TRANSLIT") #indica que el texto de salida sea su equivalente sin signos especiales
length(unique(barrios_sin_tilde))
## [1] 389
barrios_actuales <- unique(vivienda_limpia$barrio)
barrios_sin_tilde_unicos <- iconv(
barrios_actuales,
from = "UTF-8",
to = "ASCII//TRANSLIT"
)
duplicados_tilde <- barrios_actuales[
duplicated(barrios_sin_tilde_unicos) |
duplicated(barrios_sin_tilde_unicos, fromLast = TRUE)
]
sort(duplicados_tilde)
## [1] "alameda del rio" "alameda del río"
## [3] "alfonso lopez" "alfonso lópez"
## [5] "ciudad cordoba" "ciudad córdoba"
## [7] "ciudad jardin" "ciudad jardín"
## [9] "ciudad los alamos" "ciudad los álamos"
## [11] "cristobal colón" "cristóbal colón"
## [13] "junin" "junín"
## [15] "los alcazares" "los alcázares"
## [17] "los cambulos" "los cámbulos"
## [19] "napoles" "nápoles"
## [21] "normandia" "normandía"
## [23] "pacara" "pacará"
## [25] "san joaquin" "san joaquín"
## [27] "san luis" "san luís"
## [29] "san nicolas" "san nicolás"
## [31] "santa monica" "santa mónica"
## [33] "santa monica popular" "santa mónica popular"
## [35] "santa monica residencial" "santa mónica residencial"
Adicionalmente, se revisaron posibles diferencias asociadas al uso de tildes en los nombres de los barrios. Al realizar esta comparación, el número de categorías únicas disminuyó de 407 a 389, evidenciando la existencia de registros correspondientes al mismo barrio escritos con y sin tilde. Por lo tanto, estas diferencias también generan categorías duplicadas y requieren ser estandarizadas
Debido a que las diferencias en el uso de tildes generan categorías duplicadas dentro de la variable barrio, se decide estandarizar los nombres eliminando las tildes. Esta decisión permite reducir inconsistencias asociadas a la digitación y facilita la comparación de los registros, dado que en procesos de captura manual es común que las tildes no sean utilizadas de manera uniforme.
vivienda_limpia$barrio <- iconv( #modificamos los registros de la variable barrio en la base limpia
vivienda_limpia$barrio,
from = "UTF-8",
to = "ASCII//TRANSLIT"
)
length(unique(vivienda_limpia$barrio))
## [1] 389
Finalmente, luego de realizar esta última modificación, la variable barrio queda conformada por 389 categorías únicas, obteniendo así una mayor estandarización y consistencia en su información.
Una vez realizada la exploración y limpieza general de la base de datos, se procede a preparar la información que será utilizada en los diferentes análisis multivariados. Debido a que cada técnica requiere variables con características específicas, se definirán los conjuntos de variables correspondientes para el Análisis de Componentes Principales, el Análisis de Conglomerados y el Análisis de Correspondencia.
Para la aplicación del Análisis de Componentes Principales se seleccionan las variables cuantitativas precio, área construida, número de baños, parqueaderos y habitaciones, debido a que representan características económicas y físicas de las propiedades y permiten analizar las relaciones existentes entre ellas.
Creamos entonces nuestro vector de datos para pca, con las variables.
datos_pca <- vivienda_limpia[, c(
"preciom",
"areaconst",
"banios",
"parqueaderos",
"habitaciones"
)]
Como se analizo anteriormente, la variable parqueaderos presenta un total de 1602 valores faltantes. Al observar su distribución, se evidencia que el 50 % de los registros se encuentra entre 1 y 2 parqueaderos. Adicionalmente, la media (1.835) y la mediana (2) presentan valores cercanos. Sin embargo, debido a que parqueaderos es una variable cuantitativa discreta, resulta conveniente considerar valores enteros para su imputación.
table(datos_pca$parqueaderos, useNA = "ifany") #incluye los NA
##
## 1 2 3 4 5 6 7 8 9 10 <NA>
## 3155 2475 520 384 68 68 18 17 4 8 1602
summary(datos_pca$parqueaderos)
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## 1.000 1.000 2.000 1.835 2.000 10.000 1602
Aunque la mediana general de la variable es 2, se realiza una comprobación adicional de esta medida según el tipo de vivienda, con el propósito de determinar si existen diferencias entre casas y apartamentos.
tapply(
vivienda_limpia$parqueaderos,
vivienda_limpia$tipo,
median,
na.rm = TRUE
)
## Apartamento Casa
## 1 2
Los resultados muestran que la mediana del número de parqueaderos es 1 para los apartamentos y 2 para las casas. Por consiguiente, se considera más adecuado realizar la imputación de manera diferenciada según el tipo de vivienda, asignando la mediana correspondiente a cada categoría.
datos_pca$parqueaderos[
is.na(datos_pca$parqueaderos) &
vivienda_limpia$tipo == "Apartamento" #imputacion para apartamento con mediana 1
] <- 1
datos_pca$parqueaderos[
is.na(datos_pca$parqueaderos) &
vivienda_limpia$tipo == "Casa" # imnputacion para casa con mediana 2
] <- 2
sum(is.na(datos_pca$parqueaderos)) #verificamos los faltantes de nuevo
## [1] 0
Para el análisis de conglomerados se seleccionan inicialmente las variables cuantitativas precio, área construida, número de baños, parqueaderos y habitaciones, con el propósito de agrupar las viviendas según características económicas y físicas similares. Posteriormente, los grupos obtenidos serán analizados según las variables zona y estrato, con el fin de identificar cómo se distribuyen los diferentes segmentos en la ciudad y entre los niveles socioeconómicos.
colSums(is.na(vivienda_limpia[, c(
"preciom",
"areaconst",
"banios",
"parqueaderos",
"habitaciones"
)]))
## preciom areaconst banios parqueaderos habitaciones
## 0 0 0 1602 0
Al verificar los datos faltantes de las variables seleccionadas, se observa nuevamente la presencia de valores faltantes en la variable parqueaderos. Con el fin de mantener un tratamiento homogéneo de los datos, se aplicará el mismo criterio utilizado previamente para el PCA, imputando los valores faltantes mediante la mediana de parqueaderos según el tipo de vivienda.
datos_cluster <- vivienda_limpia[, c(
"id",
"zona",
"estrato",
"preciom",
"areaconst",
"banios",
"parqueaderos",
"habitaciones",
"tipo"
)]
datos_cluster$parqueaderos[
is.na(datos_cluster$parqueaderos) &
datos_cluster$tipo == "Apartamento"
] <- 1
datos_cluster$parqueaderos[
is.na(datos_cluster$parqueaderos) &
datos_cluster$tipo == "Casa"
] <- 2
colSums(is.na(datos_cluster))
## id zona estrato preciom areaconst banios
## 0 0 0 0 0 0
## parqueaderos habitaciones tipo
## 0 0 0
####analisis datos con 0 habitaciones y 0 baños
Debido a que las variables habitaciones y baños serán incluidas en el análisis de conglomerados, y durante la exploración inicial de los datos se identificaron algunos valores que llamaron la atención, se realiza una revisión antes de continuar con el análisis. A continuación, se presentan los registros que tienen 0 habitaciones y aquellos que tienen 0 baños, con el fin de verificar estos casos y determinar su tratamiento.
# Viviendas con 0 habitaciones
datos_cluster[datos_cluster$habitaciones == 0, ]
## # A tibble: 66 × 9
## id zona estrato preciom areaconst banios parqueaderos habitaciones tipo
## <dbl> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <chr>
## 1 243 Zona … 3 190 435 0 2 0 Casa
## 2 2013 Zona … 3 270 330 3 2 0 Casa
## 3 2014 Zona … 3 270 330 3 2 0 Casa
## 4 2741 Zona … 4 485 320 4 2 0 Casa
## 5 3273 Zona … 3 400 324 0 2 0 Casa
## 6 4095 Zona … 4 700 550 6 2 0 Casa
## 7 4118 Zona … 5 620 450 3 2 0 Casa
## 8 4329 Zona … 4 790 540 6 2 0 Casa
## 9 4512 Zona … 4 620 300 8 2 0 Casa
## 10 7245 Zona … 4 1200 752 0 2 0 Casa
## # ℹ 56 more rows
# Viviendas con 0 baños
datos_cluster[datos_cluster$banios == 0, ]
## # A tibble: 45 × 9
## id zona estrato preciom areaconst banios parqueaderos habitaciones tipo
## <dbl> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <chr>
## 1 243 Zona … 3 190 435 0 2 0 Casa
## 2 3273 Zona … 3 400 324 0 2 0 Casa
## 3 7245 Zona … 4 1200 752 0 2 0 Casa
## 4 4995 Zona … 3 420 235 0 2 0 Casa
## 5 1816 Zona … 3 750 183 0 2 0 Casa
## 6 7509 Zona … 5 1200 660 0 2 0 Casa
## 7 6839 Zona … 6 900 250 0 2 0 Apar…
## 8 4393 Zona … 3 380 264 0 2 0 Casa
## 9 1898 Zona … 4 220 68 0 1 3 Apar…
## 10 1425 Zona … 5 395 220 0 1 0 Casa
## # ℹ 35 more rows
sum(datos_cluster$habitaciones == 0)
## [1] 66
sum(datos_cluster$banios == 0)
## [1] 45
sum(
datos_cluster$habitaciones == 0 &
datos_cluster$banios == 0
)
## [1] 35
# Total de registros a excluir
sum(
datos_cluster$habitaciones == 0 |
datos_cluster$banios == 0
)
## [1] 76
Como resultado de la revisión, se identificaron 66 registros con 0 habitaciones y 45 con 0 baños, de los cuales 35 presentan simultáneamente ambos valores en cero. Teniendo en cuenta que se están analizando viviendas, independientemente de si corresponden a Casas o Apartamentos, no resulta coherente considerar como válidos registros que indiquen ausencia de habitaciones o baños. Además, al revisar sus demás características se observaron casos con áreas construidas y precios elevados, lo que sugiere que estos valores podrían corresponder a información no registrada.Al cruzar ambos grupos se obtiene un total de 76 registros únicos con 0 habitaciones, 0 baños o ambas condiciones. Debido a que no se cuenta con información adicional que permita completar estos valores, se decide excluir estos 76 registúnicamente del análisis de conglomerados, evitando que estas posibles inconsistencias influyan en la formación de los grupos.
# Eliminamos los 76 registros de la base para conglomerados
datos_cluster <- datos_cluster[
datos_cluster$habitaciones > 0 & datos_cluster$banios > 0,
]
# Verificar que no queden registros con valores en cero para hab y banos
sum(datos_cluster$habitaciones == 0)
## [1] 0
sum(datos_cluster$banios == 0)
## [1] 0
sum(
datos_cluster$habitaciones == 0 &
datos_cluster$banios == 0
)
## [1] 0
Con la base de datos para conglomerados ya revisada y preparada, se continúa en la Sección 3 con el análisis de conglomerados, donde se buscará identificar grupos de viviendas que compartan características similares.
Para el análisis de correspondencias se seleccionan las variables tipo de vivienda, zona y barrio, de acuerdo con el objetivo de identificar posibles relaciones entre estas características y reconocer patrones en la oferta del mercado inmobiliario. Como primer paso, se revisa la presencia de datos faltantes y la cantidad de categorías presentes en cada variable.
datos_corresp <- vivienda_limpia[, c(
"tipo",
"zona",
"barrio"
)]
str(datos_corresp)
## spc_tbl_ [8,319 × 3] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
## $ tipo : chr [1:8319] "Casa" "Casa" "Casa" "Casa" ...
## $ zona : chr [1:8319] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
## $ barrio: chr [1:8319] "20 de julio" "20 de julio" "20 de julio" "3 de julio" ...
## - 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>
colSums(is.na(datos_corresp))
## tipo zona barrio
## 0 0 0
length(unique(datos_corresp$tipo))
## [1] 2
length(unique(datos_corresp$zona))
## [1] 5
length(unique(datos_corresp$barrio))
## [1] 389
Como se observa, las variables seleccionadas no presentan datos faltantes. Adicionalmente, se identifican 2 categorías para el tipo de vivienda, 5 para la zona y 389 para el barrio. Cabe mencionar que la variable barrio fue previamente depurada durante la etapa de limpieza de los datos, donde se corrigieron errores de digitación y se unificaron los nombres correspondientes a una misma categoría. Por lo tanto, no se requiere realizar tratamientos adicionales sobre estas variables antes de continuar con el análisis de correspondencias.
Como primer paso, se obtiene la matriz de correlaciones con el fin de evaluar el grado de relación existente entre las variables seleccionadas para el ACP. Este análisis permite identificar si las variables comparten información y verificar la existencia de correlaciones muy altas que puedan indicar una posible redundancia entre ellas.
cor(datos_pca)
## preciom areaconst banios parqueaderos habitaciones
## preciom 1.0000000 0.6873520 0.6691456 0.6681768 0.2640912
## areaconst 0.6873520 1.0000000 0.6484165 0.5735257 0.5169129
## banios 0.6691456 0.6484165 1.0000000 0.5504887 0.5899064
## parqueaderos 0.6681768 0.5735257 0.5504887 1.0000000 0.2902056
## habitaciones 0.2640912 0.5169129 0.5899064 0.2902056 1.0000000
al analizar la matriz de correlaciones, se observa que la mayoría de las variables presentan relaciones positivas moderadas entre sí. Las relaciones más altas se presentan entre el precio y el área construida (0.69), el precio y los baños (0.67) y el precio y los parqueaderos (0.67), lo cual indica que, en general, las viviendas de mayor tamaño y con mayor número de baños o parqueaderos tienden a presentar precios más altos. Por otra parte, la variable habitaciones presenta relaciones más bajas con el precio (0.26) y con el número de parqueaderos (0.29).En general, no se observan correlaciones excesivamente altas entre las variables, por lo que no se evidencia una posible redundancia entre ellas. A su vez, la presencia de varias correlaciones moderadas sugiere que existe información compartida entre las variables que puede ser analizada mediante el PCA. Sin embargo, como se planteó durante la exploración inicial de los datos, se identificaron algunos registros con valores de cero en las variables habitaciones y baños que resultaban poco habituales dadas las demás características de las propiedades. Por esta razón, se realiza una comprobación adicional para determinar si estos registros están afectando las correlaciones observadas con el precio.
# Correlación utilizando todos los registros
cor(
datos_pca$preciom,
datos_pca$habitaciones
)
## [1] 0.2640912
# Correlación excluyendo viviendas con 0 habitaciones
cor(
datos_pca$preciom[datos_pca$habitaciones > 0],
datos_pca$habitaciones[datos_pca$habitaciones > 0]
)
## [1] 0.2753258
# Correlación utilizando todos los registros
cor(
datos_pca$preciom,
datos_pca$banios
)
## [1] 0.6691456
# Correlación excluyendo viviendas con 0 banos
cor(
datos_pca$preciom[datos_pca$banios > 0],
datos_pca$banios[datos_pca$banios > 0]
)
## [1] 0.6843816
Al excluir temporalmente los registros con cero habitaciones, la correlación entre el número de habitaciones y el precio pasa de 0.264 a 0.275, mientras que al excluir los registros con cero baños, la correlación entre baños y precio pasa de 0.669 a 0.684. En ambos casos los cambios son pequeños, por lo que no se evidencia que estos registros estén afectando de manera importante las relaciones observadas. Por esta razón, se mantienen estas observaciones para el desarrollo del PCA.
Debido a que las variables seleccionadas se encuentran expresadas en diferentes unidades y escalas de medición, se realiza su estandarización antes de aplicar el PCA. De esta manera, se evita que variables con escalas mayores, como el precio o el área construida, tengan una influencia desproporcionada en la construcción de los componentes.
datos_pca_est <- scale(datos_pca)
summary(datos_pca_est)
## preciom areaconst banios parqueaderos
## Min. :-1.1437 Min. :-1.0138 Min. :-2.17847 Min. :-0.7298
## 1st Qu.:-0.6508 1st Qu.:-0.6640 1st Qu.:-0.77811 1st Qu.:-0.7298
## Median :-0.3161 Median :-0.3633 Median :-0.07794 Median : 0.2273
## Mean : 0.0000 Mean : 0.0000 Mean : 0.00000 Mean : 0.0000
## 3rd Qu.: 0.3228 3rd Qu.: 0.3782 3rd Qu.: 0.62224 3rd Qu.: 0.2273
## Max. : 4.7620 Max. :10.9822 Max. : 4.82330 Max. : 7.8840
## habitaciones
## Min. :-2.4702
## 1st Qu.:-0.4148
## Median :-0.4148
## Mean : 0.0000
## 3rd Qu.: 0.2704
## Max. : 4.3813
Teniendo las variables estandarizadas y comprobando que este proceso se hizo de manera correcta (observamos la media 0 para todas las variables), proseguimos con el análisis de componentes principales.
pca <- prcomp(datos_pca_est)
pca$sdev
## [1] 1.7931121 0.9233378 0.6273765 0.5883659 0.4386577
pca$rotation
## PC1 PC2 PC3 PC4 PC5
## preciom 0.4688065 0.41321908 -0.3637543 0.15897694 0.6722199
## areaconst 0.4820725 -0.02337263 -0.3790299 -0.70227094 -0.3608485
## banios 0.4846358 -0.17272048 -0.1745056 0.68327354 -0.4878328
## parqueaderos 0.4330403 0.41806734 0.7860627 -0.08223573 -0.1141863
## habitaciones 0.3541620 -0.78999434 0.2750862 -0.08897429 0.4085210
Al analizar las cargas de las variables sobre los componentes principales, resulta interesante observar que en el primer componente (PC1) todas las cargas son positivas. Las mayores corresponden al área construida (0.482), baños (0.485) y precio (0.469), seguidas por parqueaderos (0.433) y habitaciones (0.354). Esto indica que todas las variables se relacionan en la misma dirección con este componente, por lo que PC1 parece representar una característica general asociada con el tamaño y las características de la vivienda. Para el segundo componente (PC2), las mayores cargas en valor absoluto corresponden a habitaciones (-0.790), parqueaderos (0.418) y precio (0.413). En este caso, habitaciones presenta un signo contrario al de precio y parqueaderos, indicando que estas variables se relacionan con el componente en direcciones opuestas.Finalmente, en el tercer componente (PC3) se destaca principalmente la variable parqueaderos (0.786), mientras que precio (-0.368) y área construida (-0.379) presentan cargas de menor magnitud y en dirección contraria.
Para determinar el número de componentes principales a conservar, se utiliza el gráfico de varianza explicada por componente. Como criterio para este análisis, se busca conservar el número mínimo de componentes que permita explicar al menos el 80 % de la variabilidad total de los datos.
library(factoextra)
fviz_eig(
pca,
addlabels = TRUE
)
Se observa que el primer componente explica el 64.3 % de la variabilidad y el segundo el 17.1 %. En conjunto, los dos primeros componentes explican aproximadamente el 81,4 % de la variabilidad total. Por lo tanto, de acuerdo con el criterio establecido, se decide conservar dos componentes principales.
Con el fin de visualizar la relación de las variables originales con los dos componentes principales seleccionados, se construye el círculo de correlaciones. Este gráfico permite observar la dirección y la intensidad con la que cada variable se relaciona con los componentes PC1 y PC2.
fviz_pca_var(
pca,
axes = c(1, 2),
repel = TRUE,
col.var = "contrib",
gradient.cols = c("blue", "orange", "red")
)
Al analizar el círculo de correlaciones, se observa que para la dimensión 1 (PC1) todas las variables presentan una relación positiva. La variable habitaciones presenta la menor carga sobre esta dimensión, mientras que precio, área construida, baños y parqueaderos presentan cargas relativamente similares. Esto indica que el primer componente recoge información compartida por las diferentes características de las viviendas. Para la dimensión 2 (PC2) se presenta un comportamiento diferente. Precio y parqueaderos tienen una relación positiva con esta dimensión, mientras que área construida, baños y habitaciones presentan una relación negativa. En particular, habitaciones es la variable con mayor peso en valor absoluto sobre PC2, por lo que desempeña un papel importante en la definición de este segundo componente.
En cuanto a la relación entre las variables, precio y parqueaderos presentan direcciones muy similares, lo que evidencia una asociación positiva entre ellas. De igual manera, área construida y baños se encuentran relativamente próximas. Por otra parte, la flecha correspondiente a habitaciones forma un ángulo cercano a 90° con precio y parqueaderos, lo que indica una relación débil entre estas variables. Este resultado es coherente con la matriz de correlaciones analizada previamente, donde habitaciones presentó correlaciones de apenas 0.26 con precio y 0.29 con parqueaderos.finalmente, se observa que habitaciones presenta una de las flechas más cercanas al borde del círculo, indicando que esta variable se encuentra muy bien representada en el plano formado por PC1 y PC2. En otras palabras, una parte importante de la información contenida en la variable habitaciones puede ser explicada mediante los dos componentes principales seleccionados.
En el biplot se observa que una gran cantidad de viviendas se concentra cerca del centro del gráfico. Esto indica que muchas de las propiedades presentan características similares a los valores habituales del conjunto de datos en cuanto a precio, área construida, baños, parqueaderos y habitaciones. Sin embargo, también se observan algunas viviendas más alejadas de esta concentración, especialmente hacia la derecha, lo que indica que presentan características diferentes al comportamiento de la mayoría. Además, la distribución de las viviendas se extiende principalmente sobre el primer componente, lo cual es coherente con el hecho de que este componente concentra la mayor parte de la información de los datos (64.3 %).
fviz_pca_biplot(
pca,
axes = c(1, 2),
label = "var",
col.ind = "grey",
col.var = "red",
repel = TRUE
)
Finalmente, tal como se observó en la sección teórica del curso, se realiza el análisis de los individuos con el propósito de interpretar el sentido de los dos componentes principales. Para ello, se seleccionan cuatro viviendas ubicadas en los extremos positivo y negativo de cada componente y se comparan sus características originales. Con el código presentado a continuación se identifican las viviendas ubicadas en los extremos de cada componente, es decir, aquellas que presentan los valores máximo y mínimo en PC1 y PC2.
# Casos extremos
which.max(pca$x[,1]) # PC1 positivo
## [1] 1762
which.min(pca$x[,1]) # PC1 negativo
## [1] 3108
which.max(pca$x[,2]) # PC2 positivo
## [1] 2926
which.min(pca$x[,2]) # PC2 negativo
## [1] 8199
Una vez identificadas las cuatro viviendas ubicadas en los extremos, se consultan sus características originales en términos de precio, área construida, número de baños, parqueaderos y habitaciones, con el propósito de comprender las diferencias entre ellas y, de esta manera, facilitar la interpretación de cada componente. Las viviendas 1762 y 3108 representan los extremos positivo y negativo de PC1, respectivamente, mientras que las viviendas 2926 y 8199 corresponden a los extremos positivo y negativo de PC2.
datos <- rbind(
datos_pca[1762, ],
datos_pca[3108, ],
datos_pca[2926, ],
datos_pca[8199, ]
)
datos <- as.data.frame(datos)
rownames(datos) <- c(
"Vivienda 1762",
"Vivienda 3108",
"Vivienda 2926",
"Vivienda 8199"
)
datos
## preciom areaconst banios parqueaderos habitaciones
## Vivienda 1762 1800 1586 4 10 5
## Vivienda 3108 160 76 0 1 0
## Vivienda 2926 950 280 0 10 0
## Vivienda 8199 680 452 10 1 10
Una vez identificadas las viviendas extremas de cada componente, se procede a ubicarlas en el gráfico de individuos. Los extremos correspondientes a PC1 se resaltan en color rojo, mientras que los extremos de PC2 se presentan en color azul, con el fin de facilitar su identificación y posterior interpretación.
# Casos extremos del PC1
casos1 <- rbind(
pca$x[1762, 1:2],
pca$x[3108, 1:2]
)
rownames(casos1) <- c("1762", "3108")
casos1 <- as.data.frame(casos1)
# Casos extremos del PC2
casos2 <- rbind(
pca$x[2926, 1:2],
pca$x[8199, 1:2]
)
rownames(casos2) <- c("2926", "8199")
casos2 <- as.data.frame(casos2)
fviz_pca_ind(
pca,
col.ind = "#DEDEDE"
) +
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
)
Con respecto al PC1, se observa que a medida que nos desplazamos hacia la derecha, es decir, hacia valores positivos del componente, las características de las viviendas tienden a aumentar. En el caso extremo analizado, se presenta un mayor precio, mayor área construida y un mayor número de baños, parqueaderos y habitaciones. Este comportamiento es coherente con lo observado en el círculo de correlaciones, donde todas las variables presentaron cargas positivas sobre el primer componente. Para el PC2, el comportamiento es diferente. Al desplazarnos desde la parte negativa hacia la positiva del componente, se observa un aumento en el precio y en el número de parqueaderos, mientras que disminuyen el área construida, el número de baños y, especialmente, el número de habitaciones. Este comportamiento también coincide con las cargas observadas anteriormente para el segundo componente.
Al observar los dos componentes en conjunto, se pueden identificar diferentes comportamientos entre las viviendas. Hacia la parte superior derecha se encuentran viviendas asociadas con mayores precios y un mayor número de parqueaderos, mientras que hacia la parte inferior se encuentran viviendas donde características como el número de habitaciones y baños tienen mayor importancia. Esto permite observar que las viviendas pueden diferenciarse no solo por su precio, sino también por la combinación de sus características.En conclusión, el Análisis de Componentes Principales permitió resumir las cinco características estudiadas de las viviendas en dos componentes que explican en conjunto el 81,4 % de la información. El primer componente permite diferenciar viviendas con características generales más altas o más bajas, mientras que el segundo permite observar distintas combinaciones entre estas características. Adicionalmente, se evidenció que el precio presenta una mayor relación con el área construida, el número de baños y los parqueaderos, mientras que su relación con el número de habitaciones es considerablemente menor. De esta manera, el ACP permite identificar características relevantes asociadas con las diferencias de precio y con los distintos perfiles de viviendas presentes en la oferta inmobiliaria analizada.
Para realizar el análisis de conglomerados se seleccionaron las variables precio, área construida, habitaciones, baños y parqueaderos, ya que representan algunas de las principales características de las viviendas y permiten compararlas entre sí. Debido a que estas variables se encuentran en escalas diferentes, antes de formar los grupos es necesario estandarizarlas, de manera que ninguna tenga mayor influencia en el análisis únicamente por manejar valores más altos.
# Seleccionar variables para el análisis de conglomerados
variables_cluster <- datos_cluster[, c(
"preciom",
"areaconst",
"habitaciones",
"banios",
"parqueaderos"
)]
# Estandarizar las variables
variables_cluster_est <- scale(variables_cluster)
Una vez estandarizadas las variables, se calculan las distancias euclidianas entre las viviendas. Estas distancias permiten conocer qué tan similares son las viviendas considerando conjuntamente el precio, área construida, habitaciones, baños y parqueaderos. Una distancia menor indica una mayor similitud entre las viviendas, mientras que una distancia mayor indica mayores diferencias entre sus características
# Comprobar media, debe dar aprox 0
colMeans(variables_cluster_est)
## preciom areaconst habitaciones banios parqueaderos
## -3.074520e-17 8.842607e-17 -1.756315e-17 -3.241574e-17 3.790086e-17
# Comprobar desviaciones estándar, debe dar 1
apply(variables_cluster_est, 2, sd)
## preciom areaconst habitaciones banios parqueaderos
## 1 1 1 1 1
# Distancias euclidianas
dist_euclidiana <- dist(
variables_cluster_est,
method = "euclidean"
)
summary(dist_euclidiana) #utilizamos este chunck para verificar que el calculo de las distancias se realizo
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.000 1.304 2.182 2.616 3.543 15.208
Una vez calculadas las distancias euclidianas, se continúa con la construcción del dendrograma, utilizando el método de agrupamiento complete. Este gráfico permitirá observar cómo se van agrupando las viviendas según la similitud de sus características y servirá como apoyo para determinar una posible cantidad de conglomerados. Debido al gran número de viviendas analizadas, no se presenta la matriz completa de distancias, ya que su tamaño dificultaría su visualización e interpretación.
# Clúster jerárquico
hc_cluster <- hclust(
dist_euclidiana,
method = "complete"
)
plot(
hc_cluster,
labels = FALSE,
hang = -1,
main = "Dendrograma de las viviendas",
xlab = "Viviendas",
ylab = "Distancia"
)
Debido a la cantidad de viviendas, en el dendrograma no es fácil
identificar a simple vista cuántos grupos sería conveniente formar. Por
esta razón, se utilizan otros gráficos como apoyo. Primero se presenta
el método del codo, que permite observar a partir de qué cantidad de
grupos las mejoras empiezan a ser menores. También se utiliza el gráfico
de agregaciones, en el cual se muestran únicamente las 10 últimas
uniones. Se toman estas últimas porque al comienzo se unen las viviendas
más parecidas, mientras que al final se van uniendo grupos cada vez más
diferentes. Esto permite observar con mayor claridad si hay saltos
grandes en las distancias y tener una mejor idea de dónde podría hacerse
la separación de los conglomerados.
tail(hc_cluster$height, 10)
## [1] 7.741699 7.855802 8.061207 9.169762 9.803005 10.261890 10.328276
## [8] 11.120425 12.618686 15.207713
# Últimas distancias de agregación
ultimas_distancias <- tail(hc_cluster$height, 10)
barplot(
ultimas_distancias,
horiz = TRUE,
names.arg = 10:1,
xlab = "Distancia",
ylab = "Nodo",
main = "Grafico de aglomeracion",
col="lightsteelblue"
)
# Método del codo
wss <- numeric(10)
for (k in 1:10) {
modelo <- kmeans(
variables_cluster_est,
centers = k,
nstart = 25
)
wss[k] <- modelo$tot.withinss
}
plot(
1:10,
wss,
type = "b",
pch = 19,
xlab = "Número de conglomerados",
ylab = "Suma de cuadrados dentro de los grupos",
main = "Método del codo"
)
Al observar ambos gráficos, se tiene una primera idea de la cantidad de
conglomerados que podrían formarse. El gráfico de agregaciones sugiere
una posible separación entre 3 y 4 grupos, debido a los cambios que se
observan en las últimas distancias de unión. Por su parte, en el método
del codo se observa una disminución bastante marcada entre 1 y 2
conglomerados, que continúa entre 2 y 3, aunque en menor medida. A
partir de 3 conglomerados, las diferencias empiezan a ser más pequeñas y
la curva se vuelve más estable. Por esta razón, 3 conglomerados aparece
inicialmente como una buena opción.Para comprobar si esta cantidad
permite obtener grupos bien definidos, se evaluará a continuación el
coeficiente de Silhouette.
library(cluster)
# Guardar los resultados
silhouette_promedio <- numeric(5)
for (k in 2:6) { # ciclo desde 2 clusters hasta 6.
grupos <- cutree(hc_cluster, k = k)
sil <- silhouette(
grupos,
dist_euclidiana
)
silhouette_promedio[k - 1] <- mean(sil[, 3])
}
silhouette_promedio
## [1] 0.7452525 0.4955921 0.4753889 0.4751717 0.4728740
## conglomerados silhouette
## 1 2 0.7452525
## 2 3 0.4955921
## 3 4 0.4753889
## 4 5 0.4751717
## 5 6 0.4728740
Al comparar el coeficiente de Silhouette para diferentes cantidades de conglomerados, se observa que el valor más alto se obtiene con 2 grupos (0.745). A partir de 3 conglomerados el valor disminuye considerablemente y se mantiene alrededor de 0.47–0.50. Teniendo en cuenta este resultado y lo observado anteriormente en el método del codo, se seleccionan 2 conglomerados para continuar con el análisis.
Ya teniendo asignados 2 conglomerados, vamos a asignar cada vivienda a uno de esos dos grupos mediante cutree()
# Asignar cada vivienda a uno de los 2 conglomerados
cluster_asignado <- cutree(hc_cluster, k = 2)
# Agregar el conglomerado a la base
datos_cluster$cluster <- as.factor(cluster_asignado)
table(datos_cluster$cluster)
##
## 1 2
## 8235 8
# Ver las viviendas del conglomerado 2
datos_cluster[datos_cluster$cluster == 2, ]
## # A tibble: 8 × 10
## id zona estrato preciom areaconst banios parqueaderos habitaciones tipo
## <dbl> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <chr>
## 1 1017 Zona O… 3 200 1365 1 2 4 Casa
## 2 5684 Zona S… 6 1800 1586 4 10 5 Casa
## 3 3324 Zona O… 3 255 1745 3 2 2 Casa
## 4 315 Zona S… 6 1650 1500 5 4 3 Casa
## 5 5467 Zona S… 6 1500 1500 5 5 3 Casa
## 6 5016 Zona S… 6 1500 1250 5 5 3 Casa
## 7 5396 Zona S… 6 1600 1600 6 3 6 Casa
## 8 534 Zona N… 3 370 1440 4 1 10 Casa
## # ℹ 1 more variable: cluster <fct>
# Resumen de las variables por conglomerado
aggregate(
cbind(preciom, areaconst, habitaciones, banios, parqueaderos) ~ cluster,
data = datos_cluster,
FUN = mean
)
## cluster preciom areaconst habitaciones banios parqueaderos
## 1 1 432.6107 172.7487 3.632665 3.126776 1.758834
## 2 2 1109.3750 1498.2500 4.500000 4.125000 4.000000
Al revisar la cantidad de viviendas asignadas a cada conglomerado, se observa un fuerte desbalance: el primer grupo contiene 8235 viviendas, mientras que el segundo está formado únicamente por 8 viviendas. Al revisar este segundo grupo se observa que su principal característica son las áreas construidas considerablemente mayores al resto de la base, aunque sus precios presentan diferencias importantes entre sí. Por esta razón, antes de seleccionar definitivamente dos conglomerados, se revisarán con mayor detalle estos casos.
Al revisar estas viviendas se observa que el segundo conglomerado está compuesto principalmente por casas con áreas construidas muy altas, entre aproximadamente 1250 y 1745 m². Sin embargo, tener un área superior a 1000 m² no determina por sí solo la pertenencia a este grupo, ya que se encontraron viviendas por encima de este valor y varias de ellas permanecen en el conglomerado 1. Esto indica que la separación se está dando por la combinación de las cinco características analizadas y no únicamente por el área construida.
datos_cluster[
datos_cluster$areaconst > 1000,
c("preciom", "areaconst", "habitaciones",
"banios", "parqueaderos", "tipo", "cluster")
]
## # A tibble: 16 × 7
## preciom areaconst habitaciones banios parqueaderos tipo cluster
## <dbl> <dbl> <dbl> <dbl> <dbl> <chr> <fct>
## 1 200 1365 4 1 2 Casa 2
## 2 1600 1050 6 7 6 Casa 1
## 3 1680 1040 5 7 3 Casa 1
## 4 1800 1586 5 4 10 Casa 2
## 5 650 1188 6 6 4 Casa 1
## 6 1450 1200 6 6 2 Casa 1
## 7 255 1745 2 3 2 Casa 2
## 8 1650 1500 3 5 4 Casa 2
## 9 1500 1500 3 5 5 Casa 2
## 10 1800 1090 4 5 6 Casa 1
## 11 1500 1100 4 5 2 Casa 1
## 12 1500 1250 3 5 5 Casa 2
## 13 1900 1092 5 6 5 Casa 1
## 14 1600 1600 6 6 3 Casa 2
## 15 1200 1200 10 6 2 Casa 1
## 16 370 1440 10 4 1 Casa 2
Aun así, el área construida parece tener un papel importante en esta separación. La vivienda típica de la base presenta valores considerablemente menores: la mediana es de 122 m², el 75 % de las viviendas tiene hasta 227 m², mientras que el valor máximo alcanza los 1745 m². Por lo tanto, las viviendas que conforman el segundo conglomerado presentan áreas bastante alejadas de la mayoría de los registros.
summary(datos_cluster$areaconst)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 30 80 122 174 227 1745
Estos valores no se consideran necesariamente errores, ya que pueden corresponder a viviendas realmente grandes. Sin embargo, su presencia está influyendo en la formación de los conglomerados. Por esta razón, antes de elegir definitivamente la solución de 2 grupos, se revisará cómo cambia la distribución de las viviendas al considerar 3 conglomerados.
# Asignar las viviendas a 3 conglomerados
cluster_3 <- cutree(hc_cluster, k = 3)
# Cantidad de viviendas por conglomerado
table(cluster_3)
## cluster_3
## 1 2 3
## 7070 1165 8
Al realizar el análisis con 3 conglomerados, se obtiene una distribución de 7070 viviendas en el primer grupo, 1165 en el segundo y 8 en el tercero. Se observa que las 8 viviendas identificadas anteriormente continúan formando un grupo independiente, mientras que el resto de las viviendas se divide ahora en dos conglomerados. Esta distribución resulta más interesante para el análisis, ya que permite identificar dos grupos dentro de la mayoría de las viviendas, además del pequeño grupo con características más extremas.
datos_cluster$cluster_3 <- as.factor(cluster_3)
aggregate(
cbind(preciom, areaconst, habitaciones, banios, parqueaderos) ~ cluster_3,
data = datos_cluster,
FUN = mean
)
## cluster_3 preciom areaconst habitaciones banios parqueaderos
## 1 1 362.2999 135.4590 3.303536 2.786987 1.548939
## 2 2 859.3039 399.0473 5.630043 5.188841 3.032618
## 3 3 1109.3750 1498.2500 4.500000 4.125000 4.000000
Al probar 3 conglomerados, se observa una separación más clara de las viviendas. El primer grupo reúne la mayoría de los registros (7070 viviendas) y corresponde, en general, a viviendas más pequeñas y de menor precio. El segundo grupo contiene 1165 viviendas con mayor área, precio y cantidad de habitaciones, baños y parqueaderos. Por último, aparece un tercer grupo de solo 8 viviendas, que se diferencia principalmente por tener áreas construidas extremadamente grandes. Este último grupo debe revisarse con cuidado. Durante la preparación de los datos ya se habían encontrado viviendas con características poco coherentes, como áreas muy grandes acompañadas de 0 habitaciones o 0 baños, registros que fueron eliminados antes de realizar el análisis. Aunque estas 8 viviendas ya no presentan valores en cero, algunas siguen mostrando combinaciones poco comunes, como áreas superiores a 1.000 m² con pocas habitaciones. Por lo tanto, no se puede asegurar que sean errores, pero sí se pueden considerar viviendas atípicas dentro de la base. En comparación con la solución de 2 conglomerados, trabajar con 3 grupos resulta más útil para el análisis, ya que no solamente separa estas viviendas atípicas, sino que también permite distinguir dos perfiles diferentes dentro del resto de las viviendas
Una vez revisados los resultados y seleccionada la alternativa de 3 conglomerados, se presenta un gráfico de dispersión entre el área construida y el precio para observar cómo se distribuyen las viviendas de cada grupo.
ggplot(
datos_cluster,
aes(
x = areaconst,
y = preciom,
color = cluster_3
)
) +
geom_point(alpha = 0.5) +
labs(
title = "Distribución de las viviendas por conglomerado",
x = "Área construida",
y = "Precio",
color = "Conglomerado"
) +
theme_minimal()
En el gráfico se observa una relación positiva entre el área construida
y el precio: en general, a medida que aumenta el área de la vivienda,
también aumenta su precio. El conglomerado 1 concentra principalmente
viviendas de menor área y precio, mientras que el conglomerado 2 reúne
viviendas que, en general, presentan áreas y precios más altos. Por otro
lado, el conglomerado 3 aparece bastante separado del resto y
corresponde a las 8 viviendas que habíamos identificado con áreas
construidas extremadamente grandes y características poco
comunes.También se observa cierto cruce entre los conglomerados 1 y 2,
lo cual es esperable, ya que los grupos no se formaron únicamente con
precio y área construida, sino considerando también habitaciones, baños
y parqueaderos.
ggplot(
datos_cluster,
aes(x = habitaciones, y = banios, color = cluster_3)
) +
geom_jitter(alpha = 0.4, width = 0.15, height = 0.15) +
labs(
title = "Habitaciones y baños por conglomerado",
x = "Número de habitaciones",
y = "Número de baños",
color = "Conglomerado"
) +
theme_minimal()
En el gráfico se observa que el conglomerado 1 se concentra principalmente en viviendas con menor cantidad de habitaciones y baños. Por otro lado, el conglomerado 2 tiende a presentar una mayor cantidad de ambas características, aunque existe cierto cruce entre los dos grupos. Esto muestra que los conglomerados no están completamente separados por una sola variable, sino por la combinación de todas las características utilizadas en el análisis.El conglomerado 3, al estar formado únicamente por 8 viviendas, tiene poca presencia en el gráfico y no muestra un patrón tan claro en habitaciones y baños. Esto refuerza la idea de que su principal diferencia frente a los demás grupos está relacionada especialmente con sus áreas construidas extremadamente grandes.
Una vez definidos los 3 conglomerados, se procede a revisar sus principales características con el fin de entender qué tipo de viviendas conforman cada grupo y cómo se distribuyen según el tipo de vivienda, la zona y el estrato.
# Distribución del tipo de vivienda por conglomerado
table(datos_cluster$cluster_3, datos_cluster$tipo)
##
## Apartamento Casa
## 1 4907 2163
## 2 167 998
## 3 0 8
# Proporción dentro de cada conglomerado
prop.table(
table(datos_cluster$cluster_3, datos_cluster$tipo),
margin = 1
)
##
## Apartamento Casa
## 1 0.6940594 0.3059406
## 2 0.1433476 0.8566524
## 3 0.0000000 1.0000000
ggplot(
datos_cluster,
aes(x = cluster_3, fill = tipo)
) +
geom_bar(position = "fill") +
scale_y_continuous(
labels = scales::percent_format()
) +
labs(
title = "Tipo de vivienda por conglomerado",
x = "Conglomerado",
y = "Proporción",
fill = "Tipo de vivienda"
) +
theme_minimal()
En cuanto al tipo de vivienda, se observa una diferencia clara entre los
conglomerados. En el conglomerado 1 predominan los apartamentos,
mientras que en el conglomerado 2 predominan las casas. Esto tiene
sentido con los resultados anteriores, ya que este segundo grupo se
caracteriza por viviendas con mayor área construida y, en general, las
casas presentan áreas mayores que los apartamentos. Por último, el
conglomerado 3 está compuesto únicamente por casas, lo cual también
coincide con las áreas extremadamente grandes encontradas en este
grupo.
prop.table(
table(datos_cluster$cluster_3, datos_cluster$zona),
margin = 1
)
##
## Zona Centro Zona Norte Zona Oeste Zona Oriente Zona Sur
## 1 0.01357850 0.23776521 0.14427157 0.03833098 0.56605375
## 2 0.02145923 0.17682403 0.14420601 0.06180258 0.59570815
## 3 0.00000000 0.12500000 0.00000000 0.25000000 0.62500000
ggplot(
datos_cluster,
aes(x = cluster_3, fill = zona)
) +
geom_bar(position = "fill") +
scale_y_continuous(
labels = scales::percent_format()
) +
labs(
title = "Distribución de las zonas por conglomerado",
x = "Conglomerado",
y = "Proporción",
fill = "Zona"
) +
theme_minimal()
Al revisar la distribución por zona, no se observa un patrón muy marcado
entre los conglomerados. En los grupos 1 y 2 la mayor proporción de
viviendas se encuentra en la Zona Sur, seguida principalmente por la
Zona Norte, manteniendo una distribución relativamente similar. Por lo
tanto, la ubicación por zona no parece ser una característica que
diferencie claramente estos dos conglomerados.En el conglomerado 3 se
observa una mayor participación de la Zona Oriente, pero este resultado
debe tomarse con cuidado, ya que este grupo está compuesto únicamente
por 8 viviendas.
# Proporción de estratos dentro de cada conglomerado
prop.table(
table(datos_cluster$cluster_3, datos_cluster$estrato),
margin = 1
)
##
## 3 4 5 6
## 1 0.1779349 0.2765205 0.3415842 0.2039604
## 2 0.1467811 0.1296137 0.2686695 0.4549356
## 3 0.3750000 0.0000000 0.0000000 0.6250000
ggplot(
datos_cluster,
aes(x = cluster_3, fill = as.factor(estrato))
) +
geom_bar(position = "fill") +
scale_y_continuous(
labels = scales::percent_format()
) +
labs(
title = "Distribución de los estratos por conglomerado",
x = "Conglomerado",
y = "Proporción",
fill = "Estrato"
) +
theme_minimal()
A diferencia de la zona, el estrato sí muestra algunas diferencias entre
los conglomerados. En el conglomerado 1 hay una mayor participación de
viviendas de estratos 3, 4 y 5, mientras que la proporción de estrato 6
es menor. En cambio, en el conglomerado 2 se observa una mayor
concentración en los estratos altos, especialmente 5 y 6, lo cual
coincide con las características encontradas anteriormente para este
grupo: viviendas de mayor precio, área y número de habitaciones, baños y
parqueaderos.el conglomerado 3 presenta una distribución particular, ya
que sus viviendas se encuentran únicamente en los estratos 3 y 6, sin
presencia de los estratos intermedios 4 y 5. Sin embargo, este resultado
debe interpretarse con cuidado debido a que este grupo está formado por
solamente 8 viviendas.
Recopilando lo anterior se tiene entonces: - Conglomerado 1 – Viviendas más pequeñas y económicas: Es el grupo más grande, con 7.070 viviendas. Presenta los menores valores promedio de precio y área construida, así como menos habitaciones, baños y parqueaderos. Predominan los apartamentos y hay una mayor participación de estratos 3, 4 y 5. - Conglomerado 2 – Viviendas más grandes y de mayor valor: Está formado por 1.165 viviendas. Se caracteriza por tener mayor precio y área construida, además de más habitaciones, baños y parqueaderos. Predominan claramente las casas y existe una mayor concentración de viviendas en estratos 5 y 6. - Conglomerado 3 – Viviendas con características extremas: Está compuesto únicamente por 8 viviendas, todas casas. Su principal diferencia son las áreas construidas extremadamente grandes, muy alejadas de las observadas en la mayoría de la base. Además, presentan algunas combinaciones poco comunes entre área y demás características, por lo que este grupo debe interpretarse con cuidado. Se encuentran únicamente en los estratos 3 y 6.
En cuanto a la zona, no se encontraron diferencias muy marcadas entre los conglomerados, por lo que la ubicación geográfica no parece ser un elemento que los diferencie claramente. En general, la separación de los grupos está más relacionada con el tamaño, precio y características físicas de las viviendas, además del tipo de vivienda y, en cierta medida, el estrato.
En esta sección se realiza un análisis de correspondencias con el propósito de estudiar la relación entre las variables categóricas asociadas a la oferta de vivienda. En particular, se busca responder la siguiente pregunta: determinados tipos de vivienda tienden a estar asociados con determinadas zonas y barrios de la ciudad? Para ello, inicialmente se analiza la relación entre el tipo de vivienda y la zona, con el fin de obtener una visión general de cómo se distribuye la oferta inmobiliaria en la ciudad.
Para resumir las principales relaciones encontradas entre las características de las viviendas, se presenta el círculo de correlaciones obtenido mediante el Análisis de Componentes Principales (ACP). Este permite observar de manera conjunta la relación entre el precio, el área construida, los parqueaderos, los baños y las habitaciones
Se observa principalmente una relación positiva entre el precio, los parqueaderos y el área construida, mostrando que las viviendas con mayor espacio y disponibilidad de parqueadero tienden a presentar precios más altos. En cuanto a la variable habitaciones, se identificaron algunos registros que requieren revisión, por lo que su interpretación debe realizarse con precaución. Como complemento, se presenta la relación entre el precio y el área construida según los conglomerados obtenidos. Esto permite observar de forma visual los diferentes perfiles de viviendas identificados y la presencia de un pequeño grupo con características extremas.
Se observa que el conglomerado 1 concentra principalmente viviendas de menor área y precio, mientras que el conglomerado 2 reúne viviendas de mayor tamaño y valor. El conglomerado 3 se encuentra más alejado del resto debido principalmente a sus áreas construidas extremadamente grandes.
Para observar cómo cambia el tipo de vivienda según su ubicación, se presenta la relación entre zona y tipo de vivienda. Esta visualización permite identificar qué zonas presentan una mayor orientación hacia Casas o Apartamentos dentro de la oferta analizada.
Se observa una mayor presencia de Apartamentos en las zonas Norte, Sur y
Oeste, mientras que las Casas presentan una mayor orientación hacia las
zonas Oriente y Centro. La Zona Sur presenta un comportamiento más
equilibrado entre ambos tipos de vivienda, por lo que no muestra una
asociación tan marcada como las demás zonas. Al analizar la ubicación
con mayor detalle a nivel de barrio, se observan sectores con un
predominio mucho más marcado hacia un tipo de vivienda. Los siguientes
barrios corresponden a aquellos que presentaron las mayores asociaciones
con Casas y Apartamentos en el análisis de correspondencias.
A nivel de barrio, las diferencias son más marcadas. Algunos presentan una oferta compuesta casi en su totalidad por Apartamentos, mientras que en otros predominan ampliamente las Casas. Esto muestra que el análisis por barrio permite identificar patrones específicos que pueden no observarse al analizar únicamente las zonas de la ciudad.También se revisa la composición de los conglomerados según el estrato socioeconómico. En este caso se observan algunas diferencias, principalmente entre los conglomerados 1 y 2.
El conglomerado 2 presenta una mayor participación de viviendas de estratos 5 y 6, mientras que el conglomerado 1 tiene una distribución más amplia entre los estratos 3, 4 y 5 y una menor participación del estrato 6.