Modelos estadísticos para la toma de decisiones - Grupo A
En un contexto de creciente competencia en el mercado inmobiliario urbano, resulta fundamental comprender las dinámicas de oferta y demanda para tomar decisiones estratégicas acertadas. Este estudio busca analizar de manera integral una base de datos de propiedades residenciales, identificando patrones y segmentaciones clave que influyen en la valoración y comercialización de viviendas en la ciudad de Cali..
Para ello se usará la siguiente metodología:
1. Análisis de Componentes Principales (PCA).
Se empleará el Análisis de Componentes Principales (PCA) para reducir la dimensionalidad del conjunto de datos, permitiendo visualizar la estructura de las variables principales que influyen en la variación de precios y la oferta del mercado. Esto facilitará la identificación de las características clave que tienen mayor impacto en la distribución de la vivienda en la ciudad.
2. Análisis de Correspondencia.
El Análisis de Correspondencia permitirá examinar la relación entre variables categóricas como el tipo de vivienda, la zona y el barrio. Este método ayudará a identificar patrones de comportamiento en la oferta del mercado inmobiliario y a comprender cómo se distribuyen las viviendas en función de estas categorías.
3. Análisis de Conglomerados.
Se aplicarán técnicas de agrupamiento para segmentar las propiedades residenciales en distintos grupos. Esto permitirá comprender las dinámicas de las ofertas específicas en diferentes zonas de la ciudad y en distintos estratos socioeconómicos. Con esta metodología, se podrán identificar segmentos de mercado con características similares y establecer patrones de demanda y oferta.
En primer lugar se realiza la carga del conjunto de datos llamado “vivienda” que contiene datos sobre la oferta inmobiliaria en la ciudad de Cali, el cual dispone de un total de 8.319 registros con características sobre los distintos tipos de vivienda en las zonas de la ciudad.
#Instalar todas las librerias usadas en la actividad.
# install.packages(c("devtools", "tidyverse", "ggplot2", "dplyr", "tidyr", "kableExtra", "mice", "factoextra", "cluster", "stringi", "leaflet"))
# install.packages("devtools") # solo una vez
# devtools::install_github("centromagis/paqueteMODELOS") #descarga paquete
data(package = "paqueteMODELOS")
# devtools::install_github("centromagis/paqueteMODELOS", force = TRUE)
library(paqueteMODELOS)
data("vivienda")
#str(vivienda)
Se identifica el tipo de datos de cada una de las variables.
library(tibble)
library(knitr)
library(kableExtra)
# Crear tabla
df_info <- tibble(
Columna = names(vivienda),
Tipo = sapply(vivienda, function(x) paste(class(x), collapse = ", ")),
DatosFaltantes = colSums(is.na(vivienda))
)
# Mostrar tabla kable
kable(df_info, "html") %>%
kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed", "responsive"),position = "center")
| Columna | Tipo | DatosFaltantes |
|---|---|---|
| id | numeric | 3 |
| zona | character | 3 |
| piso | character | 2638 |
| estrato | numeric | 3 |
| preciom | numeric | 2 |
| areaconst | numeric | 3 |
| parqueaderos | numeric | 1605 |
| banios | numeric | 3 |
| habitaciones | numeric | 3 |
| tipo | character | 3 |
| barrio | character | 3 |
| longitud | numeric | 3 |
| latitud | numeric | 3 |
También se cuentan los valores faltantes por cada columna y se eliminan aquellos registros que contienen valores nulos en 3 o más columnas para no afectar los análisis posteriores.
# Contar los valores faltantes en cada fila
filas_con_muchos_na <- apply(vivienda, 1, function(x) sum(is.na(x)) > 3)
# Filtrar las filas que tienen 3 o menos valores faltantes
vivienda <- vivienda[!filas_con_muchos_na, ]
Se muestra la distribución de variables numéricas como areaconst, banios (baños), estrato , habitaciones, parqueaderos y preciom. Se utilizan gráficos de violín y cajas para visualizar la dispersión y tendencias de los datos. Resalta la presencia de valores atípicos en variables como “areaconst”, “banios”, “habitaciones” y “parqueaderos”.
#Distribución de variables
# Cargar librerías necesarias
library(ggplot2)
library(tidyr)
# Seleccionar solo columnas numéricas, excluyendo "id", "piso", "latitud" y "longitud"
vivienda_num <- vivienda[sapply(vivienda, is.numeric)]
vivienda_num <- vivienda_num[, !names(vivienda_num) %in% c("id", "piso", "latitud", "longitud")]
# Convertir a formato largo para ggplot
vivienda_long <- pivot_longer(vivienda_num, cols = everything(), names_to = "Variable", values_to = "Valor")
# Definir qué variables serán gráficos de violín
violin_vars <- c("areaconst", "preciom")
# Crear la gráfica con boxplot y violin plot según la variable
ggplot(vivienda_long, aes(x = "", y = Valor)) +
geom_violin(data = subset(vivienda_long, Variable %in% violin_vars),
fill = "lightblue", alpha = 0.6) + # Graficar violín solo para las variables elegidas
geom_boxplot(data = subset(vivienda_long, !Variable %in% violin_vars),
fill = "skyblue", alpha = 0.7, outlier.color = "red") + # Boxplots para el resto
facet_wrap(~Variable, scales = "free") + # Cada variable con su propia escala
theme_minimal() +
labs( x = "", y = "Valor")
# "Relación entre Área Construida y Precio"
# Cargar librerías necesarias
library(ggplot2)
# Crear scatter plot con color según "tipo"
ggplot(vivienda, aes(x = areaconst, y = preciom, color = tipo)) +
geom_point(alpha = 0.6) + # Puntos semitransparentes para mejor visualización
theme_minimal() +
labs(
x = "Área Construida (m²)",
y = "Precio",
color = "Tipo de Vivienda") +
scale_color_brewer(palette = "Set1") # Paleta de colores llamativa
La gráfica muestra la relación entre el área construida y el precio de viviendas, diferenciando entre apartamentos y casas. Se observa una tendencia general de aumento de precio a medida que aumenta el área construida, lo cual es lógico. Sin embargo, los apartamentos parecen tener un precio más concentrado en un rango menor de área construida, mientras que las casas presentan una mayor dispersión tanto en área como en precio. Esto sugiere que el tipo de vivienda influye en la relación entre estas dos variables.
library(ggplot2)
# Crear el histograma
ggplot(vivienda, aes(x = preciom, fill = tipo)) +
geom_histogram(bins = 50, alpha = 0.6, position = "identity") +
labs(title = "",
x = "Precio m2",
y = "Frecuencia") +
theme_minimal()
En el gráfico compara la distribución de precios entre apartamentos y casas, mostrando una concentración de propiedades en el rango de precios más bajo y una disminución en la frecuencia a medida que aumentan los precios. Los apartamentos parecen ser más comunes en el rango de precios más bajo, mientras que las casas muestran una presencia relativamente mayor en el rango medio. La presencia de algunos valores atípicos sugiere la existencia de propiedades de alto valor como por ejemplo propiedades de lujo..
library(ggplot2)
# Crear el histograma
ggplot(vivienda, aes(x = areaconst, fill = tipo)) +
geom_histogram(bins = 50, alpha = 0.6, position = "identity") +
labs(title = "",
x = "Área (m2)",
y = "Frecuencia") +
theme_minimal()
En el gráfico se aprecia la distribución de las áreas por tipo de vivienda, resaltando que en general tienen un área menor concentrándose entre los 50 y 100 metros cuadrados, mientras las casas se concentran entre los 100 y 200 metros cuadrados, mostrando una mayor cantidad de viviendas con grandes áreas, sugiriendo que se tratan de viviendas de lujo.
library(dplyr)
library(kableExtra)
# Crear la tabla resumen
tabla_vivienda <- vivienda %>%
group_by(zona, tipo) %>%
summarise(Cantidad = n(), .groups = "drop") %>%
mutate(Frecuencia = sprintf("%.2f%%", (Cantidad / sum(Cantidad)) * 100))
# Agregar la fila de totales
totales <- tabla_vivienda %>%
summarise(zona = "Total",
tipo = "Total",
Cantidad = sum(as.numeric(Cantidad)),
Frecuencia = "100.00%")
tabla_vivienda <- bind_rows(tabla_vivienda, totales)
# Mostrar la tabla con kableExtra
tabla_vivienda %>%
kable(format = "html", align = "c", col.names = c("Zona", "Tipo", "Cantidad", "Frecuencia")) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"))
| Zona | Tipo | Cantidad | Frecuencia |
|---|---|---|---|
| Zona Centro | Apartamento | 24 | 0.29% |
| Zona Centro | Casa | 100 | 1.20% |
| Zona Norte | Apartamento | 1198 | 14.40% |
| Zona Norte | Casa | 722 | 8.68% |
| Zona Oeste | Apartamento | 1029 | 12.37% |
| Zona Oeste | Casa | 169 | 2.03% |
| Zona Oriente | Apartamento | 62 | 0.75% |
| Zona Oriente | Casa | 289 | 3.47% |
| Zona Sur | Apartamento | 2787 | 33.50% |
| Zona Sur | Casa | 1939 | 23.31% |
| Total | Total | 8319 | 100.00% |
La tabla muestra que la Zona Sur concentra la mayor cantidad de viviendas, representando el 57% del total. Además, se observa una notable diferencia entre la cantidad de apartamentos y casas en el conjunto de datos. Los apartamentos predominan en las zonas Sur, Oeste y Norte, mientras que las casas son más comunes en las zonas Oeste y Centro, aunque su representación en el total general es reducida.
library(tibble)
library(knitr)
library(kableExtra)
# Crear tabla sin nombres duplicados
df_info <- tibble(
Columna = names(vivienda),
Tipo = sapply(vivienda, function(x) paste(class(x), collapse = ", ")),
DatosFaltantes = colSums(is.na(vivienda))
)
# Mostrar tabla con kable
kable(df_info, "html") %>%
kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed", "responsive"))
| Columna | Tipo | DatosFaltantes |
|---|---|---|
| id | numeric | 0 |
| zona | character | 0 |
| piso | character | 2635 |
| estrato | numeric | 0 |
| preciom | numeric | 0 |
| areaconst | numeric | 0 |
| parqueaderos | numeric | 1602 |
| banios | numeric | 0 |
| habitaciones | numeric | 0 |
| tipo | character | 0 |
| barrio | character | 0 |
| longitud | numeric | 0 |
| latitud | numeric | 0 |
Mediante el paquete “mice” se analizan los patrones de los valores faltantes encontratos, dado que estos pueden estar ligados a ciertas características de las viviendas, en este caso no se identifica ninguna relación de las dos variables con faltantes (2.635 para piso y 1.602 para parqueaderos).
#install.packages("mice")
library(mice)
md.pattern(vivienda, rotate.names = TRUE)
También se identifica los valores únicos de las variables categóricas para identificar posibles inconsistencias o problemas con los datos, en este caso no se evidencian problemas.
#Identificar los valores únicos para las variables categóricas.
lapply(vivienda[sapply(vivienda, is.character) & names(vivienda) != "barrio"], unique)
## $zona
## [1] "Zona Oriente" "Zona Sur" "Zona Norte" "Zona Oeste" "Zona Centro"
##
## $piso
## [1] NA "02" "01" "03" "04" "05" "06" "07" "08" "09" "10" "11" "12"
##
## $tipo
## [1] "Casa" "Apartamento"
Para tratar los valores faltantes, se decidió imputarlos utilizando la moda dentro de cada grupo de tipo de vivienda y estrato para las variables parqueaderos y piso. Además, se identificaron valores erróneos en la cantidad de baños y habitaciones, ya que se asume que toda vivienda debe contar con al menos uno de ellos.
#Buscar la moda por tipo de vivienda y estrato y reemplazar los valores faltantes y erroneos por esta.
library(dplyr)
# Función para calcular la moda
moda <- function(x) {
ux <- unique(na.omit(x)) # Eliminar NA y obtener valores únicos
ux[which.max(tabulate(match(x, ux)))] # Devolver el valor más frecuente
}
# Convertir variables en formato númerico.
vivienda <- vivienda %>%
mutate(
piso = as.numeric(piso),
banios = as.numeric(banios)
)
# Calcular las modas por estrato y tipo
modas_calculadas <- vivienda %>%
group_by(tipo, estrato) %>%
summarise(
moda_habitaciones = moda(habitaciones),
moda_parqueaderos = moda(parqueaderos),
moda_piso = moda(piso),
moda_bano = moda(banios)
)
# Reemplazar los valores faltantes y cero en el dataframe original
vivienda <- vivienda %>%
left_join(modas_calculadas, by = c("tipo", "estrato")) %>%
mutate(
# Reemplazar NA en 'parqueaderos' y 'piso' por la moda.
parqueaderos = ifelse(is.na(parqueaderos), moda_parqueaderos, parqueaderos),
piso = ifelse(is.na(piso), moda_piso, piso),
# Reemplazar 0 en 'habitaciones' y 'banios' por la moda.
habitaciones = ifelse(habitaciones == 0, moda_habitaciones, habitaciones),
banios = ifelse(banios == 0, moda_bano, banios)
) %>%
select(-moda_habitaciones, -moda_parqueaderos, -moda_piso, -moda_bano) # Eliminar columnas temporales
Se vuelve a verificar los patrones de valores faltantes para evidenciar que la imputación re realizó de manera correcta.
library(mice)
md.pattern(vivienda, rotate.names = TRUE)
A continuación, se describen las variables incluidas en el conjunto de datos:
El Análisis de Componentes Principales (ACP) es una técnica estadística usada para reducir la dimensionalidad de un conjunto de datos, transformando las variables originales en un nuevo conjunto de variables o componentes principales. Estos componentes son combinaciones lineales de las variables originales y se ordenan en función de la cantidad de varianza que explican, con el primer componente capturando la mayor parte de la variabilidad de los datos. El ACP es útil para simplificar los datos, facilitando su interpretación y visualización, y para detectar patrones o correlaciones entre las variables. En este caso se usarán las siguientes variables del dataset vivienda: “preciom”, “areaconst”, “parqueaderos”, “banios”, “habitaciones”.
La matriz de covarianzas muestra las relaciones lineales entre las variables numéricas de un conjunto de datos. Los valores cercanos a 1 indican una fuerte correlación positiva, mientras que los valores cercanos a 0 indican una relación débil . En este caso, se observa que “preciom” (precio del metro cuadrado) tiene una correlación moderada con “areaconst” (área construida), “parqueaderos” (número de parqueaderos), y “baños”, con valores de 0.69, 0.69, y 0.68, respectivamente, lo que sugiere que a medida que aumenta el precio del metro cuadrado, también tienden a aumentar estas otras variables. Sin embargo, la correlación con “habitaciones” es más débil (0.28), lo que indica que el número de habitaciones tiene una relación menos directa con el precio del metro cuadrado. En cuanto a “areaconst”, hay correlaciones moderadas con “banios” (0.67) y “habitaciones” (0.54), lo que sugiere que, generalmente, un mayor área construida tiende a estar asociado con más baños y habitaciones. Finalmente, la menor correlación se observa entre “habitaciones” y “parqueaderos” (0.24), lo que indica una relación débil entre estas dos variables.
#title = "Matriz de Covarianzas"
#install.packages("reshape2")
library(reshape2)
# Seleccionamos variables numéricas para PCA
vivienda_num <- vivienda[, c("preciom", "areaconst", "parqueaderos", "banios", "habitaciones")]
# Estandarizar las variables
vivienda_num <- scale(vivienda_num)
# Calcular la matriz de covarianzas
cov_matrix <- cov(vivienda_num)
# Convertir la matriz en un formato largo para ggplot
cov_matrix_melt <- melt(cov_matrix)
# Graficar con ggplot2
ggplot(cov_matrix_melt, aes(Var1, Var2, fill = value)) +
geom_raster() + # Usar geom_raster para una visualización más limpia
scale_fill_gradient2(midpoint = 0, low = "blue", high = "red", mid = "white") +
theme_minimal() +
labs(x = "Variables", y = "Variables") +
theme(axis.text.x = element_text(angle = 45, hjust = 1), # Ajustar los textos de los ejes
axis.text.y = element_text(angle = 0)) +
geom_text(aes(label = round(value, 2)), color = "black", size = 4, fontface = "bold") + # Añadir valores con formato
theme(panel.grid.major = element_blank(), # Quitar las líneas de cuadrícula para mayor claridad
panel.grid.minor = element_blank()) +
theme(plot.title = element_text(hjust = 0.5)) # Centrar el títu
En el apartado anterior se creo el conjunto de datos numéricos (vivienda_num) el cual contiene las variables mencionadas anteriormente de forma estandarizada, lo que significa que tienen media o y desviación estándar 1, esto es de especial importancia para evitar que las variables con mayor varianza dominen el análisis.
# Aplicamos PCA
pca_result <- prcomp(vivienda_num, scale = TRUE)
#Importancia de los componentes principales.
# Cargar librerías necesarias
library(kableExtra)
library(tibble)
# Seleccionamos solo las variables numéricas para PCA
vivienda_num <- vivienda[, c("preciom", "areaconst", "parqueaderos", "banios", "habitaciones")]
# Estandarizamos las variables (media 0, desviación estándar 1)
vivienda_num <- scale(vivienda_num)
# Aplicamos PCA
pca_result <- prcomp(vivienda_num, scale = TRUE)
# Extraer la importancia de los componentes
summary_pca <- summary(pca_result)
# Crear tabla de importancia de componentes
importancia_componentes <- data.frame(
PC1 = summary_pca$importance[1:3, 1],
PC2 = summary_pca$importance[1:3, 2],
PC3 = summary_pca$importance[1:3, 3],
PC4 = summary_pca$importance[1:3, 4],
PC5 = summary_pca$importance[1:3, 5]
)
# Tabla con kableExtra
kable(importancia_componentes, digits = 4) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive")) %>%
column_spec(1, bold = TRUE, background = "", color = "") %>%
column_spec(2:6, background = "")
| PC1 | PC2 | PC3 | PC4 | PC5 | |
|---|---|---|---|---|---|
| Standard deviation | 1.7981 | 0.9428 | 0.6047 | 0.5701 | 0.4330 |
| Proportion of Variance | 0.6466 | 0.1778 | 0.0731 | 0.0650 | 0.0375 |
| Cumulative Proportion | 0.6466 | 0.8244 | 0.8975 | 0.9625 | 1.0000 |
# "Varianza Explicada por Cada Componente Principal"
#install.packages("factoextra")
library(factoextra)
# varianza explicada por cada componente
fviz_eig(pca_result, addlabels = TRUE) +
ggtitle("") + # Título en español
xlab("Componentes Principales") + # Etiqueta del eje X
ylab("Porcentaje de Varianza Explicada") # Etiqueta del eje Y
Los resultados del PCA muestran que el primer componente principal (PC1) explica el 64.66% de la varianza total de los datos, mientras que la segunda (PC2) agrega un 17.78%, acumulando juntas el 82.44% de la varianza. La tercera y cuarta componentes aportan menos información (7.31% y 6.50%, respectivamente), y la quinta apenas un 3.75%. Dado que PC1 y PC2 capturan la mayor parte de la variabilidad (más del 80%), se puede reducir la dimensionalidad a estas dos sin perder demasiada información.
#"Matriz de Cargas de los Componentes Principales"
# Extraer la matriz de cargas
matriz_cargas <- as.data.frame(pca_result$rotation) %>%
rownames_to_column(var = "Variable")
# Tabla con kableExtra
kable(matriz_cargas, digits = 4) %>%
kable_styling(bootstrap_options = c("striped", "hover", "responsive")) %>%
column_spec(1, bold = TRUE, background = "")
| Variable | PC1 | PC2 | PC3 | PC4 | PC5 |
|---|---|---|---|---|---|
| preciom | 0.4728 | 0.3831 | 0.3440 | -0.2378 | 0.6744 |
| areaconst | 0.4827 | -0.0846 | 0.5484 | 0.5677 | -0.3699 |
| parqueaderos | 0.4257 | 0.4801 | -0.6936 | 0.3089 | -0.1085 |
| banios | 0.4885 | -0.1517 | -0.0453 | -0.7091 | -0.4831 |
| habitaciones | 0.3515 | -0.7698 | -0.3127 | 0.1517 | 0.4038 |
Respecto a la matriz de cargas se resalta lo siguiente :
preciom (Precio por metro cuadrado):
PC1 (0.4750): Tiene una carga positiva y relativamente alta en PC1. Esto significa que el precio por metro cuadrado contribuye de manera importante y positiva a la formación del primer componente principal. A mayor precio por metro cuadrado, mayor será la puntuación en PC1.
PC2 (0.3828): También tiene una carga positiva en PC2, aunque menor que en PC1. Esto sugiere que el precio por metro cuadrado también influye en el segundo componente, aunque en menor medida.
areaconst (Área construida):
PC1 (0.4817): Similar al precio, el área construida tiene una carga positiva y alta en PC1, lo que indica una fuerte contribución a este componente. A mayor área construida, mayor puntuación en PC1.
PC2 (-0.0552): Tiene una carga muy baja y negativa en PC2, lo que sugiere que el área construida tiene poca influencia en este componente.
parqueaderos (Número de parqueaderos):
PC1 (0.4295): Carga positiva y considerable en PC1, mostrando que el número de parqueaderos también contribuye a este componente. Más parqueaderos se asocian con puntuaciones más altas en PC1.
PC2 (0.4652): Carga positiva y también considerable en PC2. Esto sugiere que el número de parqueaderos influye no solo en el tamaño/precio (PC1) sino también en otra dimensión capturada por PC2.
banios (Número de baños):
PC1 (0.4870): Carga positiva y alta en PC1, similar a las variables de precio y área. Más baños se asocian con puntuaciones más altas en PC1.
PC2 (-0.1751): Carga negativa y baja en PC2, lo que sugiere una relación inversa débil con este componente.
habitaciones (Número de habitaciones):
PC1 (0.3473): Carga positiva en PC1, pero más baja que las variables de precio, área, parqueaderos y baños. Esto indica que el número de habitaciones también influye en el tamaño/precio, pero en menor medida.
PC2 (-0.7768): Carga negativa y muy alta en PC2. Este es el valor más alto (en valor absoluto) en toda la tabla para PC2. Esto indica que el número de habitaciones es el principal contribuyente a PC2, y la relación es inversa. Más habitaciones se asocian con puntuaciones más bajas en PC2.
#Variables PCA
library(factoextra) # Para visualización del PCA
library(ggplot2) # Para gráficos
fviz_pca_var(pca_result,
col.var = "contrib",
gradient.cols = c("#FF7F00", "#034D94", "#C71585"),
repel = TRUE) +
ggtitle("") # Elimina el título
Respecto al gráfico de vectores se puede resaltar lo siguiente:
Variables más importantes: Las variables de “parqueaderos”, “precio/m^2”, “área construida” y “baños” son las que más contribuyen a la primera componente principal (Dim1), ya que sus vectores son largos y están alineados principalmente con este eje.
Correlaciones: Existe una correlación positiva entre “parqueaderos”, “precio/m^2”, “área construida” y “baños”, ya que sus vectores apuntan en la misma dirección.
Habitaciones: La variable “habitaciones” parece tener una relación que no se capta, ya que su vector no se alinea fuertemente con ninguno de las dos primeros componentes principales
# Encontrar los puntos cercanos a los ejes para analizarlos
# Definir la tolerancia para los valores cercanos a 0
tolerancia <- 0.2
# Para PC1, buscamos puntos cercanos a 0 en PC2 (horizontales), y luego los más alejados en PC1
puntos_PC1_cerca_0 <- which(abs(pca_result$x[, 2]) < tolerancia) # Puntos donde PC2 es cercano a 0
max_PC1 <- which.max(pca_result$x[puntos_PC1_cerca_0, 1]) # Punto más alejado a la derecha (PC1 máximo)
min_PC1 <- which.min(pca_result$x[puntos_PC1_cerca_0, 1]) # Punto más alejado a la izquierda (PC1 mínimo)
# Para PC2, buscamos puntos cercanos a 0 en PC1 (verticales), y luego los más alejados en PC2
puntos_PC2_cerca_0 <- which(abs(pca_result$x[, 1]) < tolerancia) # Puntos donde PC1 es cercano a 0
max_PC2 <- which.max(pca_result$x[puntos_PC2_cerca_0, 2]) # Punto más alejado hacia arriba (PC2 máximo)
min_PC2 <- which.min(pca_result$x[puntos_PC2_cerca_0, 2]) # Punto más alejado hacia abajo (PC2 mínimo)
# Extraer las coordenadas de esos puntos extremos
puntos_extremos_coord <- data.frame(
Punto = c("(max, 0)", "(min, 0)", "(0, max)", "(0, min)"),
PC1 = c(pca_result$x[puntos_PC1_cerca_0[max_PC1], 1], pca_result$x[puntos_PC1_cerca_0[min_PC1], 1],
pca_result$x[puntos_PC2_cerca_0[max_PC2], 1], pca_result$x[puntos_PC2_cerca_0[min_PC2], 1]),
PC2 = c(pca_result$x[puntos_PC1_cerca_0[max_PC1], 2], pca_result$x[puntos_PC1_cerca_0[min_PC1], 2],
pca_result$x[puntos_PC2_cerca_0[max_PC2], 2], pca_result$x[puntos_PC2_cerca_0[min_PC2], 2]),
ID = c(vivienda$id[puntos_PC1_cerca_0[max_PC1]], vivienda$id[puntos_PC1_cerca_0[min_PC1]],
vivienda$id[puntos_PC2_cerca_0[max_PC2]], vivienda$id[puntos_PC2_cerca_0[min_PC2]])
)
# Mostrar las coordenadas de los puntos extremos con los IDs
# Graficar los puntos con su respectivo ID
library(ggplot2)
fviz_pca_ind(pca_result,
col.ind = "#DEDEDE", # Color gris para todos los puntos
gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07")) + # Paleta de colores
geom_point(data = puntos_extremos_coord, aes(x = PC1, y = PC2), color = "red", size = 4) + # Resaltar los puntos más alejados
geom_text(data = puntos_extremos_coord, aes(x = PC1, y = PC2, label = ID), vjust = -1, color = "black", size = 5) + # Etiquetas con la columna 'id'
ggtitle("")
#Observaciones Originales para los Puntos Más Alejados en el PCA
# Obtener los índices de los puntos extremos para obtener las observaciones originales
indices_extremos <- c(puntos_PC1_cerca_0[max_PC1], puntos_PC1_cerca_0[min_PC1],
puntos_PC2_cerca_0[max_PC2], puntos_PC2_cerca_0[min_PC2])
# Extraer las observaciones completas para esos puntos extremos
observaciones_extremos <- vivienda[indices_extremos, ]
# Mostrar las observaciones originales de los puntos extremos
library(kableExtra)
kable(observaciones_extremos) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"))
| id | zona | piso | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | barrio | longitud | latitud |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 5388 | Zona Sur | 3 | 6 | 1250 | 660 | 5 | 8 | 7 | Casa | ciudadela pasoancho | -76.53615 | 3.38560 |
| 3951 | Zona Norte | 5 | 3 | 70 | 45 | 1 | 1 | 3 | Apartamento | los alcazares | -76.52900 | 3.41300 |
| 6151 | Zona Sur | 2 | 4 | 1500 | 235 | 1 | 1 | 1 | Casa | eucarístico | -76.54100 | 3.42400 |
| 7221 | Zona Sur | 3 | 3 | 240 | 90 | 1 | 3 | 8 | Casa | nápoles | -76.54814 | 3.38677 |
El análisis de componentes principales (ACP) revela que el primer componente principal (PC1) distingue principalmente entre propiedades con altos precios por metro cuadrado y grandes áreas construidas, frente a aquellas con precios más bajos y menor tamaño. Por otro parte, el segundo componente principal (PC2) diferencia las propiedades según sus comodidades y equipamientos, como la cantidad de baños y parqueaderos. Esto sugiere que el ACP logra capturar dos dimensiones clave del mercado inmobiliario: una relacionada con la exclusividad y el valor del metro cuadrado, y otra con las características adicionales que influyen en la percepción de confort y funcionalidad.
El análisis de correspondencias múltiples (ACM) es una técnica estadística utilizada para analizar la relación entre tres variables categóricas: tipo, barrio y zona. Cada una de estas variables tiene varias categorías (por ejemplo, diferentes tipos de vivienda, distintos barrios y zonas geográficas). El ACM permite identificar patrones y asociaciones entre estas categorías y representarlas gráficamente en un espacio reducido. Este análisis es útil para explorar cómo se distribuyen las viviendas en relación con su tipo, ubicación en distintos barrios y zonas, y cómo estas características se interrelacionan entre sí. Realizar el ACM en este contexto permite comprender de mejor manera las dinámicas entre las viviendas, ayudando a identificar agrupamientos o tendencias en las relaciones entre las variables
En primer lugar, debido a la gran cantidad de barrios presentes en el conjunto de datos, se eliminaron las mayúsculas y las tildes de cada uno, esto con el fin de unificar las categorías y facilitar los análisis posteriores, así mismo se tomó una muestra con los barrios mas representativos (mas de 50 viviendas), para evitar que los barrios con pocos registros (como aquellos con solo una vivienda) distorsionaran los resultados del análisis. De esta manera, se asegura que los patrones identificados en el análisis refleje mejor las tendencias de la población general. La filtración de barrios con más de 50 viviendas permite centrarse en áreas con una mayor cantidad de datos mejorando la estabilidad y precisión de los resultados.
# Cargar la librería stringi para la manipulación de caracteres
library(stringi)
# Convertir los nombres de los barrios a minúsculas y eliminar las tildes
vivienda$barrio_estandarizado <- stri_trans_general(tolower(vivienda$barrio), "Latin-ASCII")
# Cargar el paquete dplyr
library(dplyr)
# Agrupar por barrio y contar cuántas viviendas hay en cada barrio
barrios_count <- vivienda %>%
group_by(barrio) %>%
summarise(n_viviendas = n()) %>%
filter(n_viviendas > 50)
# Filtrar el dataframe original para mantener solo los barrios filtrados
vivienda_filtrada <- vivienda %>%
filter(barrio %in% barrios_count$barrio)
Seguidamente se convierten las tres variables variables a factor, asegurando que cada nivel de la variable sea tratado como una categoría distinta, lo que permite calcular las relaciones y asociaciones entre ellas.
#install.packages("FactoMineR")
#install.packages("factoextra")
library(FactoMineR)
library(factoextra)
vivienda_filtrada$tipo <- as.factor(vivienda_filtrada$tipo)
vivienda_filtrada$barrio_estandarizado <- as.factor(vivienda_filtrada$barrio_estandarizado)
vivienda_filtrada$zona <- as.factor(vivienda_filtrada$zona)
A continuación de aplica la función MCA() de la librería FactoMineR que realiza un Análisis de Correspondencias Múltiples (ACM) con las variables seleccionadas.
Se aprecia que el barrio y la zona se encuentran ubicados en la esquina superior derecha, lo que sugiere que estas dos variables están fuertemente asociadas, lo cual es probable que se deba a que los barrios se encuentran en zonas específicas y son un subconjunto de la otra. Asimismo, el tipo de vivienda se encuentra en la esquina inferior izquierda, lo que sugiere que esta variable no está influenciada por la zona ni el barrio en el que se ubica una vivienda.
fviz_mca_var(acm_resultado,
repel = TRUE,
col.var = "cos2",
gradient.cols = c("blue", "purple", "red"))
El gráfico de correspondencia multiple revela patrones en la distribución de viviendas en Cali, destacando la concentración de viviendas en la Zona Sur y barrios específicos dentro de cada zona. Esto permite comprender la distribución espacial y las áreas de mayor demanda, como los barrios más representativos de cada zona y los tipos de vivienda predominantes en ellas. La medida de cos², que se utiliza en el análisis de correspondencia, indica qué tan bien una variable está representada en las dos dimensiones del gráfico. Este valor refleja el grado de asociación entre las variables y las dimensiones, mostrando cuán cerca están las categorías de cada variable respecto a los ejes principales. Un cos² alto sugiere que la variable está fuertemente representada en las dimensiones seleccionadas, ayudando a identificar los patrones y relaciones más significativos.
Los eigen-valores reflejan la importancia de cada dimensión en la identificación de patrones y asociaciones entre las variables categóricas. La primera dimensión, que explica el 5.35% de la varianza, captura las relaciones más significativas en los datos, como la distribución de casas y apartamentos en las diferentes zonas, mientras que las dimensiones adicionales explican gradualmente menos varianza. La acumulación de varianza en las primeras dimensiones muestra que los factores clave en la distribución de viviendas se encuentran principalmente en estas dimensiones iniciales, lo que permite interpretar las relaciones más destacadas entre las zonas y los tipos de viviendas. Finalmente es pertinente resaltar que las dimensiones con menor participación aportan principalmente en patrones menos evidentes o mas complejos de visualizar.
# Eigenvalues (Varianza explicada por dimensión)
# Instalar kableExtra si no está instalado
if (!requireNamespace("kableExtra", quietly = TRUE)) {
install.packages("kableExtra")
}
# Cargar las librerías necesarias
library(FactoMineR)
library(factoextra)
library(knitr)
library(kableExtra)
# Obtener los valores propios del ACM
eigenval <- get_eigenvalue(acm_resultado)
# Mostrar la tabla con desplazamiento
kable(eigenval) %>%
kable_styling(full_width = FALSE) %>%
scroll_box(height = "300px")
| eigenvalue | variance.percent | cumulative.variance.percent | |
|---|---|---|---|
| Dim.1 | 0.6955779 | 5.3505991 | 5.350599 |
| Dim.2 | 0.6611719 | 5.0859376 | 10.436537 |
| Dim.3 | 0.4244573 | 3.2650564 | 13.701593 |
| Dim.4 | 0.3799862 | 2.9229710 | 16.624564 |
| Dim.5 | 0.3485451 | 2.6811162 | 19.305680 |
| Dim.6 | 0.3333333 | 2.5641026 | 21.869783 |
| Dim.7 | 0.3333333 | 2.5641026 | 24.433885 |
| Dim.8 | 0.3333333 | 2.5641026 | 26.997988 |
| Dim.9 | 0.3333333 | 2.5641026 | 29.562091 |
| Dim.10 | 0.3333333 | 2.5641026 | 32.126193 |
| Dim.11 | 0.3333333 | 2.5641026 | 34.690296 |
| Dim.12 | 0.3333333 | 2.5641026 | 37.254398 |
| Dim.13 | 0.3333333 | 2.5641026 | 39.818501 |
| Dim.14 | 0.3333333 | 2.5641026 | 42.382603 |
| Dim.15 | 0.3333333 | 2.5641026 | 44.946706 |
| Dim.16 | 0.3333333 | 2.5641026 | 47.510809 |
| Dim.17 | 0.3333333 | 2.5641026 | 50.074911 |
| Dim.18 | 0.3333333 | 2.5641026 | 52.639014 |
| Dim.19 | 0.3333333 | 2.5641026 | 55.203116 |
| Dim.20 | 0.3333333 | 2.5641026 | 57.767219 |
| Dim.21 | 0.3333333 | 2.5641026 | 60.331321 |
| Dim.22 | 0.3333333 | 2.5641026 | 62.895424 |
| Dim.23 | 0.3333333 | 2.5641026 | 65.459526 |
| Dim.24 | 0.3333333 | 2.5641026 | 68.023629 |
| Dim.25 | 0.3333333 | 2.5641026 | 70.587732 |
| Dim.26 | 0.3333333 | 2.5641026 | 73.151834 |
| Dim.27 | 0.3333333 | 2.5641026 | 75.715937 |
| Dim.28 | 0.3333333 | 2.5641026 | 78.280039 |
| Dim.29 | 0.3333333 | 2.5641026 | 80.844142 |
| Dim.30 | 0.3333333 | 2.5641026 | 83.408244 |
| Dim.31 | 0.3333333 | 2.5641026 | 85.972347 |
| Dim.32 | 0.3333333 | 2.5641026 | 88.536450 |
| Dim.33 | 0.3333333 | 2.5641026 | 91.100552 |
| Dim.34 | 0.3333333 | 2.5641026 | 93.664655 |
| Dim.35 | 0.3183086 | 2.4485276 | 96.113182 |
| Dim.36 | 0.2854264 | 2.1955876 | 98.308770 |
| Dim.37 | 0.2069136 | 1.5916427 | 99.900413 |
| Dim.38 | 0.0084840 | 0.0652617 | 99.965674 |
| Dim.39 | 0.0044623 | 0.0343257 | 100.000000 |
El eta² (eta cuadrado) es una medida estadística que indica la cantidad de varianza de una variable dependiente que se explica por una variable independiente, en este caso, por las dimensiones del análisis de correspondencia. En este contexto, cada valor de eta² muestra la fuerza de la relación entre cada variable categórica y las dimensiones derivadas del análisis de correspondencia.
Al observar la tabla X, se pueden identificar variables que tienen una fuerte relación con ciertas dimensiones. Por ejemplo, la variable “Zona Oeste” muestra un valor alto de eta² en la Dim.1 (33.13), lo que sugiere una relación significativa con esta dimensión, mientras que otras variables, como “Zona Norte”, tienen una fuerte relación con Dim.2 (39.23). Además, algunas variables, como “Zona Oriente”, muestran valores altos en dimensiones posteriores, como Dim.5 (50.23), lo que puede indicar que la variable es relevante en el patrón representado en esa dimensión específica. Esto puede ayudar a comprender cómo las distintas zonas, tipos de vivienda y barrios se agrupan en el análisis y cómo se distribuyen en cada una de las dimensiones.
#Categorical variables (eta²)
# Instalar kableExtra si no está instalado
if (!requireNamespace("kableExtra", quietly = TRUE)) {
install.packages("kableExtra")
}
# Cargar las librerías necesarias
library(knitr)
library(dplyr)
library(kableExtra)
# Extraer la contribución de cada categoría
eta_cuadrado <- as.data.frame(acm_resultado$var$contrib)
# Extraer los nombres de las variables principales de cada categoría
categorias <- rownames(eta_cuadrado)
variables <- sub("_.*", "", categorias) # Asume que el formato es "variable_categoria"
# Agregar la columna de variables al dataframe
eta_cuadrado$Variable <- variables
# Agrupar por variable y calcular la suma del eta² en cada dimensión
eta_por_variable <- eta_cuadrado %>%
group_by(Variable) %>%
summarise(across(starts_with("Dim"), sum)) # Calcula la suma por variable
# Mostrar la tabla en formato kable con desplazamiento
kable(eta_por_variable) %>%
kable_styling(full_width = FALSE) %>%
scroll_box(height = "300px")
| Variable | Dim 1 | Dim 2 | Dim 3 | Dim 4 | Dim 5 |
|---|---|---|---|---|---|
| Apartamento | 3.1547641 | 0.0086938 | 11.2241896 | 0.2506926 | 0.0108638 |
| Casa | 6.7264012 | 0.0185364 | 23.9315526 | 0.5345119 | 0.0231632 |
| Zona Centro | 0.0061764 | 0.0713493 | 1.8234692 | 47.8522532 | 0.0007006 |
| Zona Norte | 0.5448992 | 39.2295007 | 0.0318809 | 0.0936229 | 0.0000004 |
| Zona Oeste | 33.1292015 | 4.6780279 | 2.9246506 | 0.0479905 | 0.0014819 |
| Zona Oriente | 0.0011799 | 0.0019150 | 0.0000002 | 0.0007689 | 50.2313023 |
| Zona Sur | 10.9101013 | 6.0036225 | 0.8315699 | 0.0300334 | 0.0099935 |
| acopi | 0.0146754 | 5.7930789 | 1.4989542 | 0.2912940 | 0.0366031 |
| aguacatal | 4.1845672 | 0.6192083 | 0.5628809 | 0.0161348 | 0.0009794 |
| brisas de los | 0.0487471 | 2.9900102 | 0.0087115 | 0.4534786 | 0.0021800 |
| caney | 0.2519629 | 0.1571588 | 0.1631799 | 0.0256357 | 0.1393830 |
| capri | 0.1101435 | 0.1025407 | 0.6992709 | 0.0771505 | 0.0408178 |
| ciudad 2000 | 0.8070858 | 0.1521310 | 5.7885154 | 0.4335883 | 0.8363293 |
| ciudad jardin | 2.7141674 | 0.8160400 | 5.4875704 | 0.3280479 | 2.2987661 |
| cristales | 3.0181129 | 0.4291691 | 0.6534893 | 0.0221754 | 0.0034029 |
| el caney | 0.7193755 | 0.3660810 | 0.0022906 | 0.0062156 | 0.4634894 |
| el ingenio | 0.6255878 | 0.3586333 | 0.1473112 | 0.0309633 | 0.3698706 |
| el lido | 0.2071928 | 0.0846447 | 0.0065455 | 0.0003878 | 0.1412009 |
| el limonar | 0.7039185 | 0.2288779 | 1.2744121 | 0.0768819 | 0.5868283 |
| el penon | 2.3591229 | 0.3057475 | 0.1469674 | 0.0012409 | 0.0001502 |
| el refugio | 0.3626887 | 0.2134450 | 0.1233318 | 0.0230388 | 0.2101587 |
| juanamb√∫ | 0.1624500 | 1.1386704 | 0.0020511 | 0.2098426 | 0.0017331 |
| la flora | 0.2184095 | 13.4196087 | 0.0465894 | 2.0535610 | 0.0099660 |
| la hacienda | 0.4703869 | 0.2928493 | 0.2991840 | 0.0472052 | 0.2606115 |
| los cristales | 5.9923612 | 0.8806453 | 0.9932555 | 0.0327949 | 0.0025049 |
| melendez | 0.1017432 | 0.0952462 | 0.6594427 | 0.0726205 | 0.0374497 |
| normandia | 6.3378512 | 0.8853809 | 0.0982409 | 0.0003234 | 0.0052620 |
| nueva tequendama | 0.3002963 | 0.0938744 | 0.0323973 | 8.9015981 | 0.2687276 |
| pance | 1.8113698 | 0.7045981 | 1.2474536 | 0.0600574 | 42.3333562 |
| parcelaciones pance | 0.4321022 | 0.0999649 | 2.1285824 | 0.1526905 | 0.4165610 |
| prados del norte | 0.0992987 | 4.5216730 | 0.2523248 | 1.7563732 | 0.0040584 |
| quintas de don | 0.1220636 | 0.1329752 | 1.3229301 | 0.1398125 | 0.0366491 |
| san fernando | 0.1992052 | 0.0625604 | 1.1406388 | 0.0668821 | 0.2627386 |
| santa isabel | 0.9233625 | 0.2779527 | 1.2841165 | 0.0725304 | 0.0589741 |
| santa monica | 0.0224583 | 1.8866334 | 0.0134300 | 0.2293416 | 0.0000027 |
| santa teresita | 10.0841100 | 1.4023263 | 0.2665459 | 0.0000017 | 0.0033682 |
| torres de comfandi | 0.1154213 | 2.0358647 | 3.4797203 | 32.1489487 | 0.0297908 |
| urbanizacion la flora | 0.0480811 | 3.0646951 | 0.0039462 | 0.4521605 | 0.0017109 |
| valle del lili | 1.4978746 | 1.7933476 | 23.5310717 | 2.4564174 | 0.3695085 |
| versalles | 0.0533437 | 2.5253984 | 0.1173512 | 0.4857724 | 0.0072605 |
| villa del prado | 0.0177319 | 1.9320009 | 5.0089793 | 0.0197653 | 0.1551223 |
| zona sur | 0.3900070 | 0.1253220 | 0.7410041 | 0.0451938 | 0.3269783 |
El análisis de conglomerados es una técnica de aprendizaje no supervisado utilizada para agrupar observaciones con características similares dentro de un conjunto de datos. En este caso, se aplicarán dos métodos de agrupamiento, K-Means y Clustering Jerárquico, sobre el conjunto de datos de vivienda en Cali. Las variables consideradas para la segmentación incluyen el estrato socioeconómico, precio, área, número de baños, número de habitaciones y zonas de la ciudad. El objetivo es identificar patrones en el mercado inmobiliario y entender cómo se agrupan las viviendas según sus características.
El método K-Means es una técnica de agrupamiento que busca dividir un conjunto de datos en K grupos basados en la similitud entre observaciones. Este algoritmo funciona asignando aleatoriamente centroides iniciales y luego ajustándolos iterativamente para minimizar la distancia entre los puntos y sus centroides respectivos.
En este análisis, se aplicó K-Means a los datos de vivienda en Cali, utilizando variables como estrato socioeconómico, precio, área construida, número de baños, número de habitaciones y zona. Para ello, primero se realizó One-Hot Encoding sobre la variable categórica ‘zona’, generando variables binarias. Luego, se seleccionaron las variables numéricas relevantes y se combinaron con las variables dummies de ‘zona’. Posteriormente, se escalaron los datos para garantizar que todas las variables tuvieran la misma importancia en el cálculo de distancias.
El modelo K-Means se ejecutó con 6 clusters (centers = 6), utilizando 500 repeticiones diferentes (nstart = 500) para mejorar la estabilidad de los resultados. Finalmente, se agregaron los clusters identificados al dataset original y se visualizó la segmentación con fviz_cluster(). Además, se realizó un conteo de las viviendas en cada cluster para analizar la distribución de los grupos.
#Clustering de viviendas en Cali usando K-means
# Cargar paquetes necesarios
library(tidyverse)
library(factoextra)
# Aplicar One-Hot Encoding a 'zona'
vivienda_dummies <- model.matrix(~ zona - 1, data = vivienda) # Crea columnas dummy sin la intercepción
# Seleccionar otras variables numéricas
vivienda_kmeans <- vivienda %>% select(estrato, preciom, areaconst, banios, habitaciones)
# Unir variables numéricas con las dummies de 'zona'
vivienda_final <- cbind(vivienda_kmeans, vivienda_dummies)
# Estandarizar los datos
vivienda_scaled <- scale(vivienda_final)
# Aplicar K-Means
set.seed(1234)
kmeans_result <- kmeans(vivienda_scaled, centers = 6, nstart = 500)
# Agregar los clusters al dataset original
vivienda$cluster <- as.factor(kmeans_result$cluster)
# Visualizar los clusters
fviz_cluster(kmeans_result, data = vivienda_scaled)
# Ver cantidad de viviendas en cada cluster
table(vivienda$cluster)
##
## 1 2 3 4 5 6
## 1353 124 351 1764 3559 1168
Mediante el método del codo, se busca determinar cuántos grupos son los más adecuados para dividir los datos de las viviendas, utilizando la medida WSS (Within Sum of Squares). Posteriormente, se genera un gráfico en el que se identifica el “punto de codo”, que representa el número óptimo de clusters al equilibrar la simplicidad del modelo y la calidad de la agrupación.
#Número óptimo de clusters (K-means) - Método del codo
library(factoextra)
fviz_nbclust(vivienda_scaled, FUN = kmeans, method = "wss") +
labs(
subtitle = "Silhouette Method")
En el análisis de los datos de vivienda en Cali, la varianza explicada del 67.80% indica que el modelo de K-Means ha logrado capturar el 67.80% de la variabilidad total en las características de las viviendas, como el precio, área, estrato, número de baños, habitaciones y zona
# Calcular la varianza explicada
total_ss <- kmeans_result$totss # Suma total de cuadrados
within_ss <- kmeans_result$tot.withinss # Suma de cuadrados dentro de los clusters
between_ss <- total_ss - within_ss # Suma de cuadrados entre los clusters
explained_variance <- (between_ss / total_ss) * 100 # Porcentaje de varianza explicada
# Crear una tabla con los valores
varianza_tabla <- data.frame(
Métrica = c(
"Varianza explicada (%)"),
Valor = c( explained_variance)
)
# Mostrar la tabla
print(varianza_tabla)
## Métrica Valor
## 1 Varianza explicada (%) 67.80163
El coeficiente de Silhouette promedio de 0.44 indica una calidad moderada en la agrupación realizada por el modelo de K-Means sobre las viviendas en Cali. Este valor se encuentra entre 0 y 1, donde valores cercanos a 1 indican que las observaciones están bien agrupadas, mientras que valores cercanos a 0 sugieren que los clusters están mal definidos.
library(cluster)
# Calcular el coeficiente de Silhouette
silhouette_kmeans <- silhouette(kmeans_result$cluster, dist(vivienda_scaled))
mean_silhouette <- mean(silhouette_kmeans[, 3])
cat("Coeficiente de Silhouette promedio: ", mean_silhouette)
## Coeficiente de Silhouette promedio: 0.4473662
El clustering jerárquico es un método de agrupamiento que busca organizar las viviendas en una jerarquía de clusters basándose en sus características, como el precio, área, estrato, número de baños, habitaciones y zona. A diferencia del K-Means, que requiere un número predeterminado de clusters, el clustering jerárquico construye una estructura de árbol, llamada dendrograma, que muestra cómo las viviendas se agrupan progresivamente en clusters.
Para llevar a cabo este método se calcula la distancia euclidiana entre las viviendas, y luego se asignan a 6 clusters usando el dendrograma generado. Los clusters se añaden al dataset original sin modificarlo y se visualiza la distribución de los clusters mediante un gráfico de dispersión. Además, se genera un dendrograma para mostrar cómo se agrupan las viviendas y se calcula el coeficiente de Silhouette para evaluar la calidad de la segmentación
# Dendograma de Viviendas
# Cargar paquetes necesarios
library(tidyverse)
library(cluster)
library(factoextra)
# Calcular distancias euclidianas
dist_vivienda <- dist(vivienda_scaled, method = "euclidean")
# Clúster jerárquico con el método de enlace completo
hc_vivienda <- hclust(dist_vivienda, method = "ward.D2")
# Asignación de clusters (k=6)
k <- 6
cluster_assignments <- cutree(hc_vivienda, k = k)
# Agregar clusters al dataset original SIN modificarlo
vivienda$cluster2 <- as.factor(cluster_assignments)
# Visualizar distribución de clusters
ggplot(vivienda, aes(x = areaconst, y = preciom, color = cluster2)) +
geom_point(size = 4) +
geom_text(aes(label = cluster2), vjust = -.8) +
theme_classic()
# Dendrograma de viviendas
#plot(hc_vivienda, cex = 0.6, main = "", las = 1,
#ylab = "Distancia euclidiana", xlab = "Grupos")
#rect.hclust(hc_vivienda, k = k, border = 2:5)
# Índice de Silhouette
sil <- silhouette(cluster_assignments, dist_vivienda)
sil_avg <- mean(sil[,3])
cat("Coeficiente de Silhouette promedio k =", k, ":", sil_avg)
## Coeficiente de Silhouette promedio k = 6 : 0.4108861
El coeficiente de Silhouette promedio de 0.4109 para k=6 en el clustering jerárquico sugiere que, en general, los datos están moderadamente bien agrupados, aunque este valor indica cierta superposición entre los clusters. A pesar de que la segmentación no es completamente definida, en el gráfico de clusters por área y precio se pueden identificar agrupaciones con características que serán analizadas en detalle posteriormente
# Dendrograma de viviendas
plot(hc_vivienda, cex = 0.6, main = "", las = 1,
ylab = "Distancia euclidiana", xlab = "Grupos")
rect.hclust(hc_vivienda, k = k, border = 2:5)
library(factoextra)
fviz_nbclust(vivienda_scaled, FUN = hcut, method = "silhouette") +
labs(title= "",
subtitle = "Silhouette Method - Hierarchical Clustering")
Finalmente, vamos a evidenciar dentro de cada cluster creado por los dos métodos cuales son sus características más representativas, las cuales ayudaran para la toma de decisiones. Para ello, analizaremos la moda y la media de las variables clave en cada cluster, lo que nos permitirá identificar tendencias y patrones generales en la oferta de cada tipo de vivienda.
library(knitr)
library(kableExtra)
# Función para calcular la moda
moda <- function(x) {
ux <- unique(x)
ux[which.max(tabulate(match(x, ux)))]
}
# Función para generar tabla por tipo de vivienda
generar_tabla <- function(tipo_vivienda) {
tabla <- vivienda %>%
filter(tipo == tipo_vivienda) %>% # Filtrar por tipo (Casa o Apartamento)
select(-id, -piso, -longitud, -latitud) %>% # Excluir variables no deseadas
group_by(cluster) %>%
summarise(
cantidad_viviendas = n(), # Contar viviendas en cada cluster
estrato = moda(estrato),
parqueaderos = moda(parqueaderos),
banios = moda(banios),
habitaciones = moda(habitaciones),
across(where(is.numeric), mean, na.rm = TRUE) # Promedio para las demás variables
)
# Crear tabla
kable(tabla, format = "html", digits = 2,
caption = paste0("<center><strong style='color: black;'>Cluster Kmeans - ", tipo_vivienda, "</strong></center>")) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"),
full_width = FALSE, position = "center")
}
# Tablas para casas y apartamentos
tabla_casas <- generar_tabla("Casa")
tabla_apartamentos <- generar_tabla("Apartamento")
tabla_casas
| cluster | cantidad_viviendas | estrato | parqueaderos | banios | habitaciones | preciom | areaconst |
|---|---|---|---|---|---|---|---|
| 1 | 1167 | 6 | 2 | 5 | 4 | 844.06 | 399.36 |
| 2 | 100 | 3 | 1 | 2 | 4 | 339.24 | 217.79 |
| 3 | 289 | 3 | 1 | 2 | 3 | 244.82 | 213.37 |
| 4 | 585 | 3 | 1 | 3 | 4 | 363.57 | 212.57 |
| 5 | 931 | 4 | 1 | 3 | 3 | 359.92 | 173.82 |
| 6 | 147 | 6 | 2 | 4 | 4 | 685.51 | 302.17 |
tabla_apartamentos
| cluster | cantidad_viviendas | estrato | parqueaderos | banios | habitaciones | preciom | areaconst |
|---|---|---|---|---|---|---|---|
| 1 | 186 | 6 | 2 | 5 | 4 | 868.69 | 246.81 |
| 2 | 24 | 3 | 1 | 2 | 3 | 186.58 | 95.07 |
| 3 | 62 | 3 | 1 | 2 | 3 | 152.60 | 94.24 |
| 4 | 1179 | 5 | 1 | 2 | 3 | 273.40 | 95.05 |
| 5 | 2628 | 4 | 1 | 2 | 3 | 265.74 | 89.69 |
| 6 | 1021 | 6 | 2 | 4 | 3 | 661.31 | 169.82 |
Los resultados de los clusters realizados muestran diferencias notables entre las casas y los apartamentos. En general, las casas tienden a tener un tamaño mayor y un precio por metro cuadrado más alto, especialmente en estratos superiores, mientras que los apartamentos presentan una distribución más homogénea en tamaño y precio, concentrándose principalmente en estratos medios y altos.
En cuanto a la distribución de habitaciones y baños, las casas suelen tener más habitaciones y baños en todos los clusters, con valores que oscilan entre 3 y 5 baños y entre 3 y 4 habitaciones, mientras que en los apartamentos la mayoría de los clusters presentan 3 habitaciones y 2 baños, salvo en estratos altos, donde algunos tienen hasta 4 o 5 baños.
Respecto a los parqueaderos, en los apartamentos predominan 1 o 2 parqueaderos, mientras que en las casas, especialmente en estratos altos, es más común contar con 2 parqueaderos, lo que sugiere una diferencia en el nivel de equipamiento y comodidad entre ambos tipos de vivienda.
library(knitr)
library(kableExtra)
# Función para calcular la moda
moda <- function(x) {
ux <- unique(x)
ux[which.max(tabulate(match(x, ux)))]
}
# Función para generar tabla por tipo de vivienda con título centrado, en negro y con recuento
generar_tabla <- function(tipo_vivienda) {
tabla <- vivienda %>%
filter(tipo == tipo_vivienda) %>% # Filtrar por tipo (Casa o Apartamento)
select(-id, -piso, -longitud, -latitud) %>% # Excluir variables no deseadas
group_by(cluster2) %>%
summarise(
cantidad_viviendas = n(), # Contar viviendas en cada cluster
estrato = moda(estrato),
parqueaderos = moda(parqueaderos),
banios = moda(banios),
habitaciones = moda(habitaciones),
across(where(is.numeric), mean, na.rm = TRUE) # Promedio para las demás variables
)
# Crear tabla con título centrado, en negro y con el nuevo recuento
kable(tabla, format = "html", digits = 2,
caption = paste0("<center><strong style='color: black;'>Clustering Jerárquico - ", tipo_vivienda, "</strong></center>")) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"),
full_width = FALSE, position = "center")
}
# Generar y mostrar tablas con títulos centrados, en negro y con recuento de viviendas
tabla_casas <- generar_tabla("Casa")
tabla_apartamentos <- generar_tabla("Apartamento")
tabla_casas
| cluster2 | cantidad_viviendas | estrato | parqueaderos | banios | habitaciones | preciom | areaconst |
|---|---|---|---|---|---|---|---|
| 1 | 287 | 3 | 1 | 2 | 3 | 244.94 | 204.02 |
| 2 | 1450 | 6 | 2 | 4 | 4 | 714.86 | 334.43 |
| 3 | 716 | 5 | 1 | 3 | 4 | 437.97 | 259.28 |
| 4 | 499 | 5 | 1 | 3 | 3 | 324.74 | 146.51 |
| 5 | 167 | 6 | 2 | 4 | 4 | 729.60 | 335.84 |
| 6 | 100 | 3 | 1 | 2 | 4 | 339.24 | 217.79 |
tabla_apartamentos
| cluster2 | cantidad_viviendas | estrato | parqueaderos | banios | habitaciones | preciom | areaconst |
|---|---|---|---|---|---|---|---|
| 1 | 62 | 3 | 1 | 2 | 3 | 152.60 | 94.24 |
| 2 | 345 | 6 | 2 | 4 | 3 | 654.55 | 188.13 |
| 3 | 1198 | 5 | 1 | 2 | 3 | 285.16 | 98.61 |
| 4 | 2442 | 4 | 1 | 2 | 3 | 246.82 | 84.66 |
| 5 | 1029 | 6 | 2 | 4 | 3 | 667.93 | 172.28 |
| 6 | 24 | 3 | 1 | 2 | 3 | 186.58 | 95.07 |
Los resultados del clustering jerárquico muestran diferencias claras entre las casas y los apartamentos, con patrones similares a los observados en el clustering K-Means, pero con algunas variaciones en la segmentación. En general, las casas siguen siendo más grandes en área construida y precio, especialmente en estratos altos (clusters 2 y 5), donde superan los 300 m² y presentan precios significativamente más elevados. En contraste, los apartamentos muestran una distribución más homogénea, con la mayoría de los clusters concentrados en estratos 4 y 5, y con áreas construidas que rara vez superan los 190 m².
En cuanto a habitaciones y baños, las casas presentan una mayor diversidad: los clusters en estratos más altos (clusters 2 y 5) tienen 4 habitaciones y hasta 4 baños, mientras que en los estratos medios (clusters 1 y 3) predominan las casas con 3 habitaciones y 2 o 3 baños. En los apartamentos, la mayoría de los clusters tienen 3 habitaciones y 2 baños, excepto en los estratos altos (clusters 2 y 5), donde aparecen algunos con 4 baños.Respecto a los parqueaderos, en los apartamentos sigue predominando 1 o 2 parqueaderos, mientras que en las casas de estratos altos es más común encontrar 2 parqueaderos, lo que sugiere una mayor disponibilidad de espacio en estas viviendas.
Como complemento al análisis de conglomerados con K-Means, se presenta el mapa, donde se pueden apreciar las distintas zonas identificadas por cada cluster. Sin embargo, los resultados del coeficiente de Silhouette indican que los clusters no están perfectamente definidos, por lo que es pertinente explorar otras alternativas para mejorar la segmentación, como probar un número diferente de clusters, utilizar otros algoritmos o aplicar técnicas de reducción de dimensionalidad como PCA para optimizar la separación de los grupos y la calidad de los resultados.
# Instalar y cargar paquetes necesarios
if (!require("leaflet")) install.packages("leaflet", dependencies = TRUE)
if (!require("dplyr")) install.packages("dplyr", dependencies = TRUE)
# Cargar librerías
library(leaflet)
library(dplyr)
# Asignar colores a los clusters
colores <- colorFactor(palette = "Set1", domain = vivienda$cluster)
# Crear un mapa interactivo con Leaflet
mapa <- leaflet(vivienda) %>%
addTiles() %>%
addCircleMarkers(
lng = ~longitud, lat = ~latitud,
color = ~colores(cluster),
fillOpacity = 0.7,
radius = 5,
popup = ~paste("Cluster:", cluster)
) %>%
addLegend("bottomright", pal = colores, values = vivienda$cluster, title = "Clusters")
# Mostrar el mapa
mapa
Análisis de Componentes Principales (PCA):
El PCA reveló que el primer componente principal (PC1) explica el 64.65% de la varianza total, capturando principalmente la relación entre el precio por metro cuadrado y el área construida. El segundo componente principal (PC2) agregó un 17.75% de la varianza, diferenciando las propiedades según sus comodidades, como el número de baños y parqueaderos. Esto sugiere que el mercado inmobiliario se divide en dos dimensiones clave: una relacionada con el valor y la exclusividad de las propiedades, y otra con las características funcionales y de confort.
Análisis de Correspondencia Múltiple (ACM):
El ACM permitió identificar patrones en la distribución de las viviendas según su tipo, ubicación (barrio y zona) y otras características . Se observó que ciertas zonas, como la Zona Oeste y la Zona Sur, tienen una fuerte relación con dimensiones específicas, lo que indica que estas áreas presentan características distintivas en términos de tipo de vivienda y nivel socioeconómico. Esto puede ser útil para segmentar el mercado y dirigir estrategias de marketing específicas.
Análisis de Conglomerados (K-Means y Clustering Jerárquico):
Los métodos de agrupamiento mostraron diferencias claras entre casas y apartamentos. Las casas tienden a tener un área construida mayor y un precio por metro cuadrado más alto, especialmente en estratos socioeconómicos superiores. Por otro lado, los apartamentos presentan una distribución más homogénea, con menor variabilidad en tamaño y precio. Además, se identificaron clusters específicos que agrupan propiedades con características similares, lo que permite una mejor comprensión de las preferencias del mercado y la segmentación de la oferta.
Conclusiones Clave
Segmentación del Mercado: El estudio permitió identificar segmentos en el mercado inmobiliario, diferenciando entre propiedades de alto valor (casas en estratos altos) y propiedades más accesibles (apartamentos en estratos medios). Esto sugiere que la empresa puede enfocar sus esfuerzos en segmentos específicos según las características de la demanda de cada grupo identificado.
Influencia de las Características de las Viviendas: Variables como el área construida, el número de baños, habitaciones y parqueaderos tienen un impacto significativo en la valoración de las propiedades. Las casas, en general, ofrecen mayor espacio y comodidades, lo que justifica su mayor precio en comparación con los apartamentos.
Ubicación y Zonas Estratégicas: La ubicación geográfica (zona y barrio) juega un papel crucial en la valoración de las propiedades. Zonas como la Oeste y la Sur presentan características distintivas que pueden ser aprovechadas para estrategias de inversión y comercialización.
Recomendaciones Específicas
Enfoque en Segmentos de Alto Valor: Dado que las casas en estratos altos presentan un mayor precio por metro cuadrado y un área construida más amplia, se recomienda enfocar esfuerzos en este segmento, ofreciendo propiedades con características premium (más baños, habitaciones y parqueaderos) para atraer a compradores de alto poder adquisitivo.
Optimización de la Oferta de Apartamentos: Para los apartamentos, se sugiere mantener una oferta homogénea en términos de tamaño y precio, enfocándose en estratos medios y altos. Además, se podrían ofrecer opciones con mayor número de baños o parqueaderos en zonas estratégicas para aumentar su atractivo.
Estrategias de Marketing Segmentadas: Utilizar los resultados del ACM para diseñar campañas de marketing dirigidas a zonas específicas, resaltando las características únicas de cada área (por ejemplo, la exclusividad de la Zona Oeste o la accesibilidad de la Zona Sur).
Inversión en Zonas con Potencial: Identificar zonas con menor presencia en el mercado pero con potencial de crecimiento, como por ejemplo aquellas con barrios que tienen pocas viviendas registradas. Esto podría representar una oportunidad para expandir la oferta en estas áreas.
Monitoreo Continuo del Mercado: Dado que el mercado inmobiliario es dinámico y está en constante cambio, se recomienda realizar análisis periódicos para actualizar los patrones de oferta y demanda, ajustando las estrategias según las nuevas tendencias.