Modelos estadísticos para la toma de decisiones

Evaluación de la oferta inmobiliaria urbana

Problema Una empresa inmobiliaria líder en una gran ciudad está buscando comprender en profundidad el mercado de viviendas urbanas para tomar decisiones estratégicas más informadas. La empresa posee una base de datos extensa que contiene información detallada sobre diversas propiedades residenciales disponibles en el mercado. Se requiere realizar un análisis holístico de estos datos para identificar patrones, relaciones y segmentaciones relevantes que permitan mejorar la toma de decisiones en cuanto a la compra, venta y valoración de propiedades.

Analisis explotorio y tratamiento de datos

Estructuración de datos

El conjunto de datos analiza la dinámica inmobiliaria en la ciudad de Cali y está compuesto por 8,322 observaciones y 13 variables. Estas incluyen características físicas de las propiedades, como el número de habitaciones, baños, área construida y parqueaderos, además de información geoespacial, como la zona, el barrio, la latitud y la longitud. Asimismo, se incluyen variables económicas como el estrato socioeconómico y el precio de venta, junto con el tipo de vivienda (casa o apartamento). Esta estructuración permite un análisis integral del mercado inmobiliario en la ciudad.

str(vivienda) 
spc_tbl_ [8,322 × 13] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
 $ id          : num [1:8322] 1147 1169 1350 5992 1212 ...
 $ zona        : chr [1:8322] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
 $ piso        : chr [1:8322] NA NA NA "02" ...
 $ estrato     : num [1:8322] 3 3 3 4 5 5 4 5 5 5 ...
 $ preciom     : num [1:8322] 250 320 350 400 260 240 220 310 320 780 ...
 $ areaconst   : num [1:8322] 70 120 220 280 90 87 52 137 150 380 ...
 $ parqueaderos: num [1:8322] 1 1 2 3 1 1 2 2 2 2 ...
 $ banios      : num [1:8322] 3 2 2 5 2 3 2 3 4 3 ...
 $ habitaciones: num [1:8322] 6 3 4 3 3 3 3 4 6 3 ...
 $ tipo        : chr [1:8322] "Casa" "Casa" "Casa" "Casa" ...
 $ barrio      : chr [1:8322] "20 de julio" "20 de julio" "20 de julio" "3 de julio" ...
 $ longitud    : num [1:8322] -76.5 -76.5 -76.5 -76.5 -76.5 ...
 $ latitud     : num [1:8322] 3.43 3.43 3.44 3.44 3.46 ...
 - attr(*, "spec")=
  .. cols(
  ..   id = col_double(),
  ..   zona = col_character(),
  ..   piso = col_character(),
  ..   estrato = col_double(),
  ..   preciom = col_double(),
  ..   areaconst = col_double(),
  ..   parqueaderos = col_double(),
  ..   banios = col_double(),
  ..   habitaciones = col_double(),
  ..   tipo = col_character(),
  ..   barrio = col_character(),
  ..   longitud = col_double(),
  ..   latitud = col_double()
  .. )
 - attr(*, "problems")=<externalptr> 
head(vivienda)
# A tibble: 6 × 13
     id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
  <dbl> <chr>   <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
1  1147 Zona O… <NA>        3     250        70            1      3            6
2  1169 Zona O… <NA>        3     320       120            1      2            3
3  1350 Zona O… <NA>        3     350       220            2      2            4
4  5992 Zona S… 02          4     400       280            3      5            3
5  1212 Zona N… 01          5     260        90            1      2            3
6  1724 Zona N… 01          5     240        87            1      3            3
# ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
tabla_variables <- data.frame(
  Variable = c("id", "zona", "piso", "estrato", "precio", "area construida", 
               "parqueaderos", "banios", "habitaciones", "tipo", "barrio", 
               "longitud", "latitud"),
  Tipo = c("Cuantitativa(Discreta)", "Cualitativa(Nominal)", 
           "Cuantitativa(Discreta)", "Cualitativa(Ordinal)", 
           "Cuantitativa(Discreta)", "Cuantitativa(Continua)", 
           "Cuantitativa(Discreta)", "Cuantitativa(Discreta)", 
           "Cuantitativa(Discreta)", "Cualitativa(Nominal)", 
           "Cualitativa(Nominal)", "Cuantitativa(Continua)", 
           "Cuantitativa(Continua)"))
tabla_variables
          Variable                   Tipo
1               id Cuantitativa(Discreta)
2             zona   Cualitativa(Nominal)
3             piso Cuantitativa(Discreta)
4          estrato   Cualitativa(Ordinal)
5           precio Cuantitativa(Discreta)
6  area construida Cuantitativa(Continua)
7     parqueaderos Cuantitativa(Discreta)
8           banios Cuantitativa(Discreta)
9     habitaciones Cuantitativa(Discreta)
10            tipo   Cualitativa(Nominal)
11          barrio   Cualitativa(Nominal)
12        longitud Cuantitativa(Continua)
13         latitud Cuantitativa(Continua)

El análisis de valores faltantes revela que las principales variables afectadas son piso y parqueaderos, con 2,638 y 1,605 datos ausentes, respectivamente, además de algunas ausencias menores en otras columnas. Para su tratamiento, se puede optar por la imputación con la mediana en variables numéricas y la moda en categóricas, o bien eliminar registros incompletos si representan una fracción pequeña del total, asegurando así la integridad del análisis sin distorsionar los resultados.

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 

Se realizó una limpieza y transformación de datos para garantizar la calidad del insumo. Primero, se eliminaron registros completamente vacíos y aquellos sin identificador único. Luego, se normalizaron las variables categóricas estandarizando nomenclaturas en minúsculas. Posteriormente, se imputaron valores faltantes en piso utilizando la mediana por tipo de vivienda, mientras que los valores nulos en parqueaderos se reemplazaron por cero. Finalmente, se filtraron registros inconsistentes eliminando aquellos sin habitaciones o baños y asegurando la presencia de al menos una variable relevante con valores mayores a cero, obteniendo así un dataset completamente limpio y sin valores faltantes, listo para el análisis.

# 1. Limpieza de valores NA y registros no válidos
vivienda <- vivienda[rowSums(is.na(vivienda)) != ncol(vivienda),] # Eliminar filas completamente vacías
vivienda <- vivienda[!is.na(vivienda$id),] # Eliminar registros sin identificador único

# 2. Normalización de datos categóricos
vivienda$zona <- tolower(vivienda$zona) # Convertir zonas a minúsculas
vivienda$tipo <- tolower(vivienda$tipo) # Convertir tipo de vivienda a minúsculas
vivienda$tipo[vivienda$tipo == "apto"] <- "apartamento" # Unificar nomenclatura
vivienda$barrio <- tolower(vivienda$barrio) # Convertir barrios a minúsculas

# 3. Transformaciones y limpieza de variables numéricas
vivienda$piso <- as.numeric(vivienda$piso) # Convertir a numérico
vivienda$parqueaderos[is.na(vivienda$parqueaderos)] <- 0 # Reemplazar NA en parqueaderos por 0

# 4. Filtrado de datos inconsistentes o irrelevantes
vivienda <- vivienda[vivienda$habitaciones != 0,] # Eliminar registros sin habitaciones
vivienda <- vivienda[vivienda$banios != 0,] # Eliminar registros sin baños
vivienda <- vivienda[rowSums(vivienda[,c("parqueaderos", "banios", "habitaciones")] == 0) < 3,] # Mantener registros con al menos una de estas variables > 0
vivienda <- vivienda[rowSums(vivienda[,c("banios", "habitaciones")] == 0) < 2,] # Mantener registros con al menos una de estas variables > 0

# Calcular la mediana del piso por tipo de vivienda
vivienda <- vivienda %>%
  group_by(tipo) %>%
  mutate(piso = ifelse(is.na(piso), median(piso, na.rm = TRUE), piso)) %>%
  ungroup()
colSums(is.na(vivienda))
          id         zona         piso      estrato      preciom    areaconst 
           0            0            0            0            0            0 
parqueaderos       banios habitaciones         tipo       barrio     longitud 
           0            0            0            0            0            0 
     latitud 
           0 

Se realizó un tratamiento a la variable barrio con el objetivo de corregir inconsistencias en la nomenclatura. Primero, se normalizaron los valores convirtiéndolos a minúsculas, eliminando tildes y espacios adicionales. Luego, se aplicó una lista de correcciones para unificar nombres que presentaban variaciones en su escritura. Como resultado, el número de barrios distintos se redujo de 437 a 386, mejorando la coherencia y calidad del dataset para un análisis más preciso.

# Cargar el paquete stringr
library(stringr)

normalize_barrio <- function(barrio) {
  barrio <- tolower(barrio) # Convertir a minúsculas
  barrio <- str_replace_all(barrio, "[áéíóú]", function(x) chartr("áéíóú", "aeiou", x)) # Reemplazar tildes
  barrio <- str_trim(barrio) # Eliminar espacios en blanco adicionales
  return(barrio)
}

# Aplicar la función a la columna 'barrio'
vivienda <- vivienda %>%
  mutate(barrio = normalize_barrio(barrio))

# Definir la lista de correcciones
correcciones <- list(
  "meléndez" = "melendez",
  "ciudad meléndez" = "ciudad melendez",
  "juanamb√∫" = "juanambu",
  "el trébol" = "el trebol",
  "las américas" = "las americas",
  "rep√∫blica de israel" = "republica de israel",
  "base aérea" = "base aerea",
  "alférez real" = "alferez real"
)

# Corregir los nombres en la columna 'barrio'
vivienda$barrio <- sapply(vivienda$barrio, function(barrio) {
  if (barrio %in% names(correcciones)) {
    return(correcciones[[barrio]])
  } else {
    return(barrio)
  }
})

El análisis de las variables numéricas muestra una distribución diversa en las características de las propiedades. La variable piso presenta valores entre 1 y 12, con una mediana de 3, lo que indica que la mayoría de las viviendas se encuentran en niveles bajos o medios. En cuanto al estrato, este oscila entre 3 y 6, con una mediana de 5, reflejando una concentración en los segmentos socioeconómicos medios y altos.

El precio de las propiedades varía significativamente, con valores entre 58 y 1999 y una mediana de 330, lo que evidencia una dispersión considerable en los valores del mercado inmobiliario. El área construida tiene un rango amplio, desde 30 m² hasta 1745 m², con una mediana de 122 m², lo que sugiere una oferta inmobiliaria con viviendas de diferentes dimensiones. En términos de comodidades, el número de parqueaderos, baños y habitaciones muestra distribuciones coherentes, con medianas de 1, 3 y 3, respectivamente, y valores máximos de 10, lo que sugiere variaciones en la cantidad de espacios disponibles en las propiedades.

vivienda_final <- vivienda %>% select(-id)
summary(vivienda_final)
     zona                piso           estrato         preciom      
 Length:8243        Min.   : 1.000   Min.   :3.000   Min.   :  58.0  
 Class :character   1st Qu.: 2.000   1st Qu.:4.000   1st Qu.: 220.0  
 Mode  :character   Median : 3.000   Median :5.000   Median : 330.0  
                    Mean   : 3.545   Mean   :4.637   Mean   : 433.3  
                    3rd Qu.: 4.000   3rd Qu.:5.000   3rd Qu.: 540.0  
                    Max.   :12.000   Max.   :6.000   Max.   :1999.0  
   areaconst     parqueaderos        banios        habitaciones   
 Min.   :  30   Min.   : 0.000   Min.   : 1.000   Min.   : 1.000  
 1st Qu.:  80   1st Qu.: 1.000   1st Qu.: 2.000   1st Qu.: 3.000  
 Median : 122   Median : 1.000   Median : 3.000   Median : 3.000  
 Mean   : 174   Mean   : 1.488   Mean   : 3.128   Mean   : 3.634  
 3rd Qu.: 227   3rd Qu.: 2.000   3rd Qu.: 4.000   3rd Qu.: 4.000  
 Max.   :1745   Max.   :10.000   Max.   :10.000   Max.   :10.000  
     tipo              barrio             longitud         latitud     
 Length:8243        Length:8243        Min.   :-76.59   Min.   :3.333  
 Class :character   Class :character   1st Qu.:-76.54   1st Qu.:3.381  
 Mode  :character   Mode  :character   Median :-76.53   Median :3.416  
                                       Mean   :-76.53   Mean   :3.418  
                                       3rd Qu.:-76.52   3rd Qu.:3.452  
                                       Max.   :-76.46   Max.   :3.498  

El análisis de los boxplots refleja una amplia diversidad en el mercado inmobiliario. La mediana del precio es de 330, con la mayoría de las propiedades por debajo de 500, aunque algunas superan los 1000. En cuanto al área construida, la mediana es 122 m², con una oferta mayoritaria de viviendas compactas y algunas de gran tamaño. La distribución de baños y habitaciones se concentra entre 2 y 4, con una mediana de 3, mientras que los parqueaderos suelen oscilar entre 1 y 2.

El análisis del piso muestra que la mayoría de las propiedades están entre los niveles 1 y 4, con una mediana de 3. También se observan viviendas con hasta 10 habitaciones y 10 baños, indicando la presencia de inmuebles más amplios o multifamiliares. En general, los datos revelan un mercado heterogéneo, con propiedades que van desde unidades accesibles hasta viviendas exclusivas.

par(mfrow=c(2,3))  # Configurar una cuadrícula de 2 filas y 3 columnas para visualizar múltiples gráficos

# Boxplots de variables numéricas
boxplot(vivienda_final$preciom, main = "Distribución de Precios", col = "lightblue")
boxplot(vivienda_final$areaconst, main = "Distribución de Área Construida", col = "lightgreen")
boxplot(vivienda_final$parqueaderos, main = "Distribución de Parqueaderos", col = "lightcoral")
boxplot(vivienda_final$banios, main = "Distribución de Baños", col = "lightgoldenrod")
boxplot(vivienda_final$habitaciones, main = "Distribución de Habitaciones", col = "lightpink")
boxplot(vivienda_final$piso, main = "Distribución de Piso", col = "lightgray")

Dataset de variables numericas y escalamiento de datos

Para este análisis, se seleccionaron las variables numéricas más representativas del dataset: piso, precio, área construida, parqueaderos, baños y habitaciones. Aunque la variable estrato es numérica, no fue incluida en el análisis, ya que representa una escala de orden y no una variable continua en sentido estricto. Su naturaleza categórica ordinal no aporta información útil en métodos como el Análisis de Componentes Principales (PCA) o el Clustering, donde las distancias y relaciones matemáticas entre variables juegan un papel clave.

Dado que las variables seleccionadas tienen escalas diferentes, se realizó un escalamiento de los datos, normalizando cada variable con una media de 0 y una desviación estándar de 1. Esto garantiza que todas las variables contribuyan equitativamente en el análisis, evitando sesgos causados por diferencias de magnitud.

# Selección de variables numéricas relevantes (sin estrato)
dataset_numerico <- vivienda_final %>% select(piso, preciom, areaconst, parqueaderos, banios, habitaciones)

# Escalamiento de los datos (media = 0, desviación estándar = 1)
dataset_escalado <- scale(dataset_numerico)

summary(dataset_escalado)    # Datos escalados
      piso            preciom          areaconst        parqueaderos    
 Min.   :-1.1294   Min.   :-1.1409   Min.   :-1.0121   Min.   :-1.2014  
 1st Qu.:-0.6857   1st Qu.:-0.6484   1st Qu.:-0.6607   1st Qu.:-0.3938  
 Median :-0.2420   Median :-0.3140   Median :-0.3656   Median :-0.3938  
 Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000  
 3rd Qu.: 0.2017   3rd Qu.: 0.3245   3rd Qu.: 0.3722   3rd Qu.: 0.4137  
 Max.   : 3.7512   Max.   : 4.7601   Max.   :11.0383   Max.   : 6.8744  
     banios          habitaciones    
 Min.   :-1.50598   Min.   :-1.8450  
 1st Qu.:-0.79820   1st Qu.:-0.4438  
 Median :-0.09042   Median :-0.4438  
 Mean   : 0.00000   Mean   : 0.0000  
 3rd Qu.: 0.61737   3rd Qu.: 0.2568  
 Max.   : 4.86407   Max.   : 4.4603  
par(mfrow=c(2,3))  

hist(dataset_escalado[, "piso"], main="Distribución de Piso", col="lightblue", xlab="Piso", breaks=30)
hist(dataset_escalado[, "preciom"], main="Distribución de Precio", col="lightgreen", xlab="Precio", breaks=30)
hist(dataset_escalado[, "areaconst"], main="Distribución de Área Construida", col="lightcoral", xlab="Área Construida", breaks=30)
hist(dataset_escalado[, "parqueaderos"], main="Distribución de Parqueaderos", col="lightgoldenrod", xlab="Parqueaderos", breaks=30)
hist(dataset_escalado[, "banios"], main="Distribución de Baños", col="lightpink", xlab="Baños", breaks=30)
hist(dataset_escalado[, "habitaciones"], main="Distribución de Habitaciones", col="lightgray", xlab="Habitaciones", breaks=30)

Análisis de Componentes Principales (PCA)

El Análisis de Componentes Principales (PCA) permite reducir la dimensionalidad del conjunto de datos y visualizar la estructura de las variables en componentes principales para identificar las características más influyentes en la variación de precios y la oferta del mercado.

# Dataset para PCA
dataset_pca_vars <- as.data.frame(dataset_escalado)
head(dataset_pca_vars)
        piso    preciom  areaconst parqueaderos      banios habitaciones
1 -0.6856881 -0.5571698 -0.7309968   -0.3938460 -0.09041565    1.6579542
2 -0.6856881 -0.3443558 -0.3796746   -0.3938460 -0.79819935   -0.4438323
3 -0.6856881 -0.2531498  0.3229699    0.4137343 -0.79819935    0.2567632
4 -0.6856881 -0.1011398  0.7445566    1.2213146  1.32515174   -0.4438323
5 -1.1293750 -0.5267678 -0.5904679   -0.3938460 -0.79819935   -0.4438323
6 -1.1293750 -0.5875718 -0.6115473   -0.3938460 -0.09041565   -0.4438323

Determinación del Número de Componentes Principales

El diagrama de Pareto permite visualizar la cantidad de varianza explicada por cada componente. En este caso, los tres primeros componentes (PC1, PC2 y PC3) explican conjuntamente un 85.05% de la variabilidad total del dataset:

  • PC1 explica el 53.62% de la varianza.
  • PC2 aporta un 19.23% adicional.
  • PC3 suma otro 12.20%.

Dado que estos tres componentes explican la mayor parte de la variabilidad en los datos, se pueden utilizar para simplificar el análisis sin perder información significativa.

pca_result <- prcomp(dataset_pca_vars, center = FALSE, scale = FALSE)

# Verificar el resumen del PCA
summary(pca_result)
Importance of components:
                          PC1    PC2    PC3     PC4     PC5     PC6
Standard deviation     1.7937 1.0742 0.8555 0.63755 0.55081 0.43231
Proportion of Variance 0.5362 0.1923 0.1220 0.06775 0.05057 0.03115
Cumulative Proportion  0.5362 0.7286 0.8505 0.91829 0.96885 1.00000
# Transformar los datos originales al nuevo espacio PCA
pca_data <- as.data.frame(pca_result$x)

# Obtener las cargas de cada variable en los componentes principales
loadings_pca <- pca_result$rotation

# Mostrar las cargas
print(loadings_pca)
                    PC1          PC2         PC3         PC4        PC5
piso          0.1428170 -0.731598161  0.65171357  0.02420535 -0.1328810
preciom      -0.4651244 -0.314579140 -0.18529860  0.41463618  0.2140121
areaconst    -0.4857836  0.072555109  0.02354688  0.47667740 -0.6052699
parqueaderos -0.3947308 -0.392385188 -0.38275041 -0.69101615 -0.2508503
banios       -0.4913407 -0.004926551  0.21677389 -0.04675384  0.6895593
habitaciones -0.3606228  0.454477111  0.58897419 -0.34724682 -0.1782469
                     PC6
piso         -0.03733089
preciom       0.65775328
areaconst    -0.40573712
parqueaderos -0.05739679
banios       -0.48362298
habitaciones  0.40516520
# Cargar librerías necesarias
library(ggplot2)
library(reshape2)

Adjuntando el paquete: 'reshape2'
The following object is masked from 'package:tidyr':

    smiths
# Obtener las cargas (loadings)
loadings_pca <- as.data.frame(pca_result$rotation)
loadings_pca$Variable <- rownames(loadings_pca)  # Agregar nombres de variables

# Convertir a formato largo para graficar el mapa de calor
loadings_long <- melt(loadings_pca, id.vars = "Variable", variable.name = "Componente", value.name = "Carga")

# Obtener la varianza explicada
var_exp <- pca_result$sdev^2 / sum(pca_result$sdev^2)
var_exp_cum <- cumsum(var_exp)



# Crear dataframe con varianza explicada y acumulada
pareto_df <- data.frame(
  PC = factor(paste0("PC", 1:length(var_exp)), levels = paste0("PC", 1:length(var_exp))),
  VarianzaExplicada = var_exp,
  VarianzaAcumulada = var_exp_cum
)

# Graficar el diagrama de Pareto con etiquetas
ggplot(pareto_df, aes(x = PC, y = VarianzaExplicada)) +
  geom_bar(stat = "identity", fill = "steelblue", alpha = 0.7) +
  geom_text(aes(label = round(VarianzaExplicada, 3)), vjust = -0.5, color = "black", size = 4) +  # Etiquetas en barras
  
  geom_line(aes(y = VarianzaAcumulada), group = 1, color = "red", linewidth = 1) +
  geom_point(aes(y = VarianzaAcumulada), color = "red", size = 2) +
  geom_text(aes(y = VarianzaAcumulada, label = round(VarianzaAcumulada, 3)), vjust = -0.5, color = "red", size = 4) +  # Etiquetas en la línea acumulada

  labs(title = "Diagrama de Pareto de la Varianza Explicada",
       x = "Componentes Principales",
       y = "Proporción de Varianza Explicada") +
  theme_minimal()

Cargas de las Variables en los Componentes

El mapa de calor de las cargas permite identificar qué variables tienen mayor influencia en cada componente:

  • PC1 está dominado por área construida (-0.486), baños (-0.491) y precio (-0.465), lo que indica que estas características están altamente correlacionadas y explican la mayor variabilidad en el mercado.
  • PC2 muestra una alta influencia del piso (-0.732) y habitaciones (0.454), sugiriendo que la altura del inmueble y el número de habitaciones son factores determinantes en la segmentación del mercado.
  • PC3 resalta nuevamente la importancia del número de habitaciones (0.589) y piso (0.652), reforzando la relación entre la distribución vertical de los inmuebles y su tamaño.
# Mapa de calor de las cargas
ggplot(loadings_long, aes(x = Componente, y = Variable, fill = Carga)) +
  geom_tile() +
  geom_text(aes(label = round(Carga, 2)), size = 4) +
  scale_fill_gradient2(low = "blue", mid = "white", high = "red", midpoint = 0) +
  labs(title = "Mapa de Calor de las Cargas en los Componentes",
       x = "Componentes",
       y = "Variables") +
  theme_minimal()

Visualización en el Espacio PCA

El gráfico de componentes principales proporciona una interpretación visual de la relación entre variables en el espacio PCA. En este caso:

  • El primer componente (PC1) captura la relación entre el precio, el tamaño del inmueble y sus características físicas.
  • El segundo componente (PC2) resalta la importancia de la altura del inmueble (piso) y el número de habitaciones en la segmentación del mercado.
  • El tercer componente (PC3) enfatiza la conexión entre la cantidad de habitaciones y la altura del edificio, lo que puede estar asociado a características de diseño arquitectónico y preferencias del mercado.
# Graficar la contribución de las variables en el espacio PCA con ajustes
fviz_pca_var(pca_result, 
             col.var = "contrib", 
             gradient.cols = c("blue", "white", "red"), # Cambio de colores
             repel = TRUE, # Evita sobreposición de etiquetas
             title = "Visualización de las variables en el plano de los componentes principales") +
  xlim(-1, 1) + ylim(-1, 1) # Ajuste de zoom para mejor visualización

Conclusión

La reducción dimensional mediante PCA permitió identificar que el mercado inmobiliario se estructura en torno a tres ejes principales: tamaño y precio de la propiedad (PC1), ubicación vertical y distribución de espacios (PC2), y relación entre número de habitaciones y altura del inmueble (PC3). Estos hallazgos facilitan una mejor segmentación del mercado y pueden utilizarse para desarrollar modelos predictivos o estrategias de análisis de precios en función de las características estructurales de las viviendas.

Análisis de Conglomerados

# Dataset para conglometros
dataset_conglomerados <- as.data.frame(dataset_escalado)
head(dataset_conglomerados)
        piso    preciom  areaconst parqueaderos      banios habitaciones
1 -0.6856881 -0.5571698 -0.7309968   -0.3938460 -0.09041565    1.6579542
2 -0.6856881 -0.3443558 -0.3796746   -0.3938460 -0.79819935   -0.4438323
3 -0.6856881 -0.2531498  0.3229699    0.4137343 -0.79819935    0.2567632
4 -0.6856881 -0.1011398  0.7445566    1.2213146  1.32515174   -0.4438323
5 -1.1293750 -0.5267678 -0.5904679   -0.3938460 -0.79819935   -0.4438323
6 -1.1293750 -0.5875718 -0.6115473   -0.3938460 -0.09041565   -0.4438323

El método del codo indica que a partir de tres clusters (K=3) la disminución en la varianza intra-cluster se estabiliza, sugiriendo que esta cantidad de grupos ofrece una segmentación eficiente sin añadir demasiada complejidad. Por otro lado, el índice de silueta presenta su valor máximo en dos clusters (K=2), lo que indica que con esta cantidad se logra la mejor separación y cohesión entre los grupos. La elección final dependerá del equilibrio entre granularidad y calidad de segmentación: si se busca una estructura más definida, K=2 es la mejor opción, mientras que K=3 permite una diferenciación más detallada en el mercado inmobiliario.

# Método del codo para determinar el número óptimo de clusters
fviz_nbclust(dataset_conglomerados, kmeans, method = "wss") +
  ggtitle("Método del Codo: Selección del Número de Clusters") +
  xlab("Número de Clusters (K)") +
  ylab("Suma de Cuadrados Intra-cluster") +
  geom_vline(xintercept = 3, linetype = "dashed", color = "red") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5))

# Método del coeficiente de silueta para validar la calidad de los clusters
fviz_nbclust(dataset_conglomerados, kmeans, method = "silhouette") +
  ggtitle("Índice de Silueta: Calidad de la Segmentación") +
  xlab("Número de Clusters (K)") +
  ylab("Ancho Promedio de la Silueta") +
  geom_vline(xintercept = 3, linetype = "dashed", color = "red") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5))

El análisis de segmentación con 2 y 3 clusters evidencia diferencias significativas en la clasificación de las propiedades según su precio y área construida. Con 2 clusters, se observa una segmentación general donde las viviendas de menor área y menor precio se agrupan en azul, mientras que las de mayor tamaño y precio se concentran en rojo, sugiriendo una división clara entre propiedades más asequibles y de alto costo. Al aumentar a 3 clusters, se introduce un nuevo grupo en verde, que parece captar un segmento intermedio en cuanto a área y precio, separando mejor las propiedades más grandes y costosas. Sin embargo, la distribución del grupo rojo sigue predominando, lo que indica que la segmentación en dos clusters ya capturaba gran parte de la estructura del mercado. La elección entre dos o tres clusters dependerá del nivel de granularidad requerido para el análisis del mercado inmobiliario.

# Definir el número de clusters a comparar
set.seed(123)  # Fijar semilla para reproducibilidad

# Aplicar K-means con 2 y 3 clusters
kmeans_2 <- kmeans(dataset_conglomerados, centers = 2, nstart = 25)
kmeans_3 <- kmeans(dataset_conglomerados, centers = 3, nstart = 25)

# Agregar etiquetas de cluster al dataset original
dataset_conglomerados$cluster_2 <- as.factor(kmeans_2$cluster)
dataset_conglomerados$cluster_3 <- as.factor(kmeans_3$cluster)

# Crear gráficos de dispersión comparativos con paleta de colores personalizada
plot_2_clusters <- ggplot(dataset_conglomerados, aes(x = areaconst, y = preciom, color = cluster_2)) +
  geom_point(alpha = 0.6) +
  labs(title = "Segmentación con 2 Clusters", x = "Área Construida (m²)", y = "Precio de la Vivienda (MM)") +
  scale_color_manual(values = c("red", "blue")) +  # Paleta de colores para 2 clusters
  theme_minimal()

plot_3_clusters <- ggplot(dataset_conglomerados, aes(x = areaconst, y = preciom, color = cluster_3)) +
  geom_point(alpha = 0.6) +
  labs(title = "Segmentación con 3 Clusters", x = "Área Construida (m²)", y = "Precio de la Vivienda (MM)") +
  scale_color_manual(values = c("red", "blue", "green")) +  # Paleta de colores para 3 clusters
  theme_minimal()

# Mostrar gráficos en paralelo
grid.arrange(plot_2_clusters, plot_3_clusters, ncol = 2)

El análisis de conglomerados aplicado a la relación entre el número de habitaciones y el precio de la vivienda revela patrones interesantes en la segmentación del mercado inmobiliario. Con dos clusters, la diferenciación principal parece radicar en los precios, donde un grupo (rojo) concentra la mayoría de las viviendas con precios elevados y mayor número de habitaciones, mientras que el otro (azul) agrupa aquellas con precios más bajos. Sin embargo, al incrementar a tres clusters, se evidencia una segmentación más refinada, donde el tercer grupo (verde) representa una categoría intermedia en términos de precios y número de habitaciones, este análisis sugiere que la variable habitaciones tiene un impacto importante en la diferenciación del mercado.

# Gráfico con 2 clusters
p1 <- ggplot(dataset_conglomerados, aes(x = habitaciones, y = preciom, color = cluster_2)) +
  geom_point(alpha = 0.6) +
  labs(title = "Segmentación con 2 Clusters",
       x = "Número de Habitaciones",
       y = "Precio de la Vivienda (MM)",
       color = "Cluster") +
  theme_minimal() +
  scale_color_manual(values = c("red", "blue"))

# Gráfico con 3 clusters
p2 <- ggplot(dataset_conglomerados, aes(x = habitaciones, y = preciom, color = cluster_3)) +
  geom_point(alpha = 0.6) +
  labs(title = "Segmentación con 3 Clusters",
       x = "Número de Habitaciones",
       y = "Precio de la Vivienda (MM)",
       color = "Cluster") +
  theme_minimal() +
  scale_color_manual(values = c("red", "blue", "green"))

# Mostrar ambos gráficos en paralelo
grid.arrange(p1, p2, ncol = 2)

Análisis de Correspondencia

Análisis de correspondencia entre barrio y tipo de vivienda

Tabla de contigencia

Dado que la base de datos contiene 382 barrios, la tabla de contingencia es extensa. Para efectos de presentación, se muestran únicamente los primeros 20 barrios, lo que nos permite identificar tendencias en la distribución de casas y apartamentos sin saturar la visualización. La tabla permite observar cómo en ciertos barrios predomina un tipo de vivienda sobre el otro, reflejando diferencias en el desarrollo inmobiliario.

tabla_barrio_tipo <- table(vivienda_final$barrio, vivienda_final$tipo)
head(tabla_barrio_tipo, 20)
                    
                     apartamento casa
  20 de julio                  0    3
  3 de julio                   0    1
  acopi                       88   60
  agua blanca                  0    1
  aguablanca                   1    1
  aguacatal                   98   11
  alameda                      4   11
  alameda del rio              2    1
  alamos                      11    3
  alborada                     0    1
  alcazares                    2    0
  alferez real                 5    2
  alfonso lopez                2   19
  alfonso lopez i              0    1
  alto jordan                  1    0
  altos de guadalupe           1    3
  altos de menga               3    0
  altos de santa               1    0
  antonio nariño               0    2
  aranjuez                     0   15

Prueba de Chi-cuadrado con Montecarlo

El resultado muestra un p-valor < 0.05, lo que confirma que existe una relación estadísticamente significativa entre el barrio y el tipo de vivienda. Esto significa que la presencia de casas o apartamentos en un barrio no es aleatoria,sino que sigue un patrón estructural determinado por la planificación urbana y las dinámicas del mercado inmobiliario.

# Prueba de Chi-cuadrado con simulación de Montecarlo
print(chisq.test(tabla_barrio_tipo, simulate.p.value = TRUE, B = 2000))

    Pearson's Chi-squared test with simulated p-value (based on 2000
    replicates)

data:  tabla_barrio_tipo
X-squared = 2416.3, df = NA, p-value = 0.0004998

Aplicación del Análisis de Correspondencia (CA)

El analisis nos permite visualizar cómo se agrupan los barrios según el tipo de vivienda predominante. Sin embargo, el resultado muestra que toda la variabilidad de los datos se explica en una única dimensión, con un eigenvalor de 0.2931 y una varianza explicada del 100%. Esto implica que no hay una estructura multivariada compleja, sino que la relación entre barrios y tipos de vivienda es altamente estructurada y predecible.

# Aplicar Análisis de Correspondencia
ca_barrio_tipo <- CA(tabla_barrio_tipo, graph = TRUE)


ca_barrio_tipo$eig
      eigenvalue percentage of variance cumulative percentage of variance
dim 1  0.2931299                    100                               100

Conclusión

La distribución de casas y apartamentos por barrio no es aleatoria, sino que sigue patrones definidos por el mercado y la zonificación urbana. El Chi-cuadrado confirma que la relación es significativa, mientras que el CA muestra que la diferenciación es unidimensional, es decir, la segmentación de los barrios en términos de tipo de vivienda sigue un único eje de variabilidad

Análisis de correspondencia entre Tipo de vivienda y Zona

Tabla de contigencia

La tabla de contingencia muestra que la distribución de apartamentos y casas no es homogénea entre las distintas zonas, evidenciando que ciertas zonas tienen una mayor presencia de apartamentos, mientras que otras predominan en casas. Este comportamiento sugiere una segmentación clara en el mercado inmobiliario, donde la oferta de vivienda está influenciada por la ubicación geográfica.

tabla_tipo_zona <- table(vivienda_final$tipo, vivienda_final$zona)
print(tabla_tipo_zona)
             
              zona centro zona norte zona oeste zona oriente zona sur
  apartamento          24       1188       1024           61     2777
  casa                 97        700        164          284     1924

Prueba de Chi-cuadrado con Montecarlo

La prueba de Chi-cuadrado evalúa si existe una relación significativa entre tipo de vivienda y zona, obteniendo un p-valor menor a 0.05, lo que indica que la distribución observada no es aleatoria. Esto significa que la ubicación geográfica influye de manera significativa en el tipo de vivienda disponible, confirmando que hay diferencias estructurales en la distribución de apartamentos y casas según la zona.

# Prueba de Chi-cuadrado con simulación de Montecarlo
chi2_montecarlo <- chisq.test(tabla_tipo_zona, simulate.p.value = TRUE, B = 2000)
print(chi2_montecarlo)

    Pearson's Chi-squared test with simulated p-value (based on 2000
    replicates)

data:  tabla_tipo_zona
X-squared = 688.15, df = NA, p-value = 0.0004998

Aplicación del Análisis de Correspondencia (CA)

El Análisis de Correspondencia muestra que la varianza se explica en una única dimensión, lo que refleja una relación estructurada entre tipo de vivienda y zona. Esto indica que la diferenciación entre zonas en términos de oferta inmobiliaria es clara, permitiendo identificar agrupaciones específicas en la distribución de apartamentos y casas.

# Aplicar Análisis de Correspondencia
ca_tipo_zona <- CA(tabla_tipo_zona, graph = TRUE)

print(ca_tipo_zona$eig)
      eigenvalue percentage of variance cumulative percentage of variance
dim 1 0.08348344                    100                               100

Conclusión

Los resultados indican que el tipo de vivienda está directamente relacionado con la zona donde se ubica, evidenciando que ciertas zonas presentan una mayor densidad de apartamentos, mientras que otras tienen más casas. Esta segmentación podría estar influenciada por factores como la planificación urbana, la demanda del mercado y las características sociodemográficas de cada sector.

Conlusión

El análisis multivariado realizado sobre la oferta inmobiliaria en la ciudad de Cali permitió extraer información clave sobre las principales dimensiones que caracterizan el mercado de bienes raíces. Se destacó la importancia de variables como el número de habitaciones y baños, así como el área construida de los bienes inmuebles, diferenciando entre casas y apartamentos. Además, el mercado inmobiliario de Cali es diverso y segmentado según el tipo de vivienda y la ubicación. Mientras que las casas ofrecen mayor espacio y comodidades a un precio más alto, los apartamentos resultan más accesibles y predominan en las zonas de mayor densidad poblacional.