1. Introducción

El mercado inmobiliario urbano es altamente dinámico y depende de múltiples factores como la ubicación, el estrato socioeconómico, el tamaño de la vivienda y sus características internas.

En este trabajo se realiza un análisis integral sobre una base de datos de 8.322 propiedades residenciales obtenidas mediante webscraping desde OLX.

El objetivo principal es identificar patrones relevantes en la oferta inmobiliaria utilizando:

  • Análisis de Componentes Principales (PCA)
  • Análisis de Conglomerados (Clustering)
  • Análisis de Correspondencia para variables categóricas

Los resultados permitirán segmentar el mercado y apoyar decisiones estratégicas de compra, venta y valoración de propiedades.

2. Configuración e instalación de librerías

# -----------------------------------------------------------
# CONFIGURACIÓN GLOBAL DEL DOCUMENTO
# -----------------------------------------------------------

knitr::opts_chunk$set(
  echo = TRUE,
  warning = FALSE,
  message = FALSE
)
# -----------------------------------------------------------
# CARGA DE LIBRERÍAS PRINCIPALES
# -----------------------------------------------------------

library(tidyverse)      # Manipulación y visualización
library(FactoMineR)     # PCA y Correspondencia
library(factoextra)     # Gráficos multivariados
library(cluster)        # Clustering
library(paqueteMODELOS) # Dataset vivienda

3. Carga y exploración inicial del dataset

# -----------------------------------------------------------
# CARGA DEL DATASET OFICIAL
# -----------------------------------------------------------

# Ver datasets disponibles en el paquete
data(package = "paqueteMODELOS")

# Dataset correcto: vivienda
data("vivienda")

# Guardar en df para trabajar más fácil
df <- vivienda

# Confirmación de carga
cat("✅ Dataset cargado correctamente \n")
## ✅ Dataset cargado correctamente
# Dimensiones: filas y columnas
dim(df)
## [1] 8322   13
# Estructura general
str(df)
## 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>
# Primeras filas
head(df)
## # 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>

4. Preparación y limpieza de los datos

En este paso se dejan los datos listos para PCA y clustering.

# -----------------------------------------------------------
# PASO 4 — LIMPIEZA Y PREPARACIÓN DE LOS DATOS
# -----------------------------------------------------------

# Revisar valores faltantes en todo el dataset
colSums(is.na(df))
##           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
# -----------------------------------------------------------
# Seleccionar solo variables numéricas relevantes
# (PCA y clustering solo funcionan con números)
# -----------------------------------------------------------

df_num <- df %>%
  select(preciom, areaconst, parqueaderos, banios, habitaciones, estrato)

# Verificar nuevamente faltantes en variables numéricas
colSums(is.na(df_num))
##      preciom    areaconst parqueaderos       banios habitaciones      estrato 
##            2            3         1605            3            3            3
# -----------------------------------------------------------
# Eliminar filas incompletas
# (para evitar errores en PCA y modelos)
# -----------------------------------------------------------

df_num_clean <- na.omit(df_num)

# Confirmar dimensiones después de limpieza
cat("Dimensiones originales:", dim(df_num), "\n")
## Dimensiones originales: 8322 6
cat("Dimensiones sin NA:", dim(df_num_clean), "\n")
## Dimensiones sin NA: 6717 6
# -----------------------------------------------------------
# Escalamiento (MUY IMPORTANTE)
# PCA y clustering dependen de la escala
# -----------------------------------------------------------

df_scaled <- scale(df_num_clean)

Observación:

Se seleccionan únicamente variables numéricas porque los métodos como PCA y clustering requieren datos cuantitativos.

Además, se eliminan registros incompletos y se escalan las variables para evitar que el precio domine sobre otras variables como baños o parqueaderos.

5. Análisis de Componentes Principales (PCA)

Se reduce dimensionalidad e identificamos variables clave.

# -----------------------------------------------------------
# PASO 5 — ANÁLISIS DE COMPONENTES PRINCIPALES (PCA)
# -----------------------------------------------------------

pca_model <- PCA(df_scaled, graph = FALSE)

# Valores propios y varianza explicada
pca_model$eig
##        eigenvalue percentage of variance cumulative percentage of variance
## comp 1  3.4837687              58.062812                          58.06281
## comp 2  1.2230753              20.384588                          78.44740
## comp 3  0.4994925               8.324875                          86.77227
## comp 4  0.3595346               5.992243                          92.76452
## comp 5  0.2443838               4.073063                          96.83758
## comp 6  0.1897451               3.162419                         100.00000

Varianza explicada

fviz_eig(pca_model, addlabels = TRUE)


Biplot PCA

fviz_pca_biplot(pca_model, repel = TRUE)


Observación:

El PCA permite identificar qué variables explican la mayor variación del mercado inmobiliario.

Normalmente, variables como área construida y estrato son determinantes en el precio.

6. Análisis de Conglomerados (Clustering)

Segmentamos propiedades en grupos homogéneos.

# -----------------------------------------------------------
# PASO 6 — CLUSTERING (Segmentación del mercado)
# -----------------------------------------------------------

# Método del codo para elegir K
fviz_nbclust(df_scaled, kmeans, method = "wss")


Entrenamiento final con K = 3


``` r
set.seed(123)

k3 <- kmeans(df_scaled, centers = 3, nstart = 25)

# Agregar cluster al dataset limpio
df_clusters <- df_num_clean
df_clusters$cluster <- as.factor(k3$cluster)

head(df_clusters)
## # A tibble: 6 × 7
##   preciom areaconst parqueaderos banios habitaciones estrato cluster
##     <dbl>     <dbl>        <dbl>  <dbl>        <dbl>   <dbl> <fct>  
## 1     250        70            1      3            6       3 2      
## 2     320       120            1      2            3       3 2      
## 3     350       220            2      2            4       3 2      
## 4     400       280            3      5            3       4 3      
## 5     260        90            1      2            3       5 2      
## 6     240        87            1      3            3       5 2

Visualización de clusters

fviz_cluster(k3, data = df_scaled)


Observación:

El análisis de conglomerados permite agrupar propiedades similares.

Esto ayuda a la empresa inmobiliaria a identificar segmentos de mercado:

  • Viviendas económicas
  • Viviendas de nivel medio
  • Viviendas premium

7. Análisis de Correspondencia (CA)

Analizamos relaciones entre variables categóricas como zona y tipo.

# -----------------------------------------------------------
# PASO 7 — ANÁLISIS DE CORRESPONDENCIA (CA)
# -----------------------------------------------------------
# Relación entre variables categóricas:
# Zona geográfica vs Tipo de vivienda
# -----------------------------------------------------------

library(FactoMineR)
library(factoextra)

# 1. Filtrar datos válidos (sin NA)
df_cat <- df %>%
  filter(!is.na(zona), !is.na(tipo))

# 2. Crear tabla cruzada
tabla_cat <- table(df_cat$zona, df_cat$tipo)

cat("Tabla cruzada zona vs tipo:\n")
## Tabla cruzada zona vs tipo:
print(tabla_cat)
##               
##                Apartamento Casa
##   Zona Centro           24  100
##   Zona Norte          1198  722
##   Zona Oeste          1029  169
##   Zona Oriente          62  289
##   Zona Sur            2787 1939
# 3. Validación mínima
if(nrow(tabla_cat) < 2 | ncol(tabla_cat) < 2){
  cat(" No es posible aplicar CA: pocas categorías disponibles.\n")

} else {

  # 4. Ejecutar modelo CA
  ca_model <- CA(tabla_cat, graph = FALSE)

  cat("CA ejecutado correctamente.")

  # Mostrar autovalores
  print(ca_model$eig)

  # -----------------------------------------------------------
  # 5. Intentar visualizar el biplot
  # Si falla, usamos alternativa robusta
  # -----------------------------------------------------------

  tryCatch({

    fviz_ca_biplot(ca_model, repel = TRUE)

  }, error = function(e){

    cat("\n  El gráfico CA no pudo generarse (tabla degenerada).\n")
    cat(" Se mostrará un heatmap como alternativa válida.\n\n")

    # Alternativa: Heatmap de frecuencias
    df_freq <- as.data.frame(tabla_cat)

    ggplot(df_freq, aes(Var1, Var2, fill = Freq)) +
      geom_tile() +
      labs(
        title = "Frecuencia de Tipo de Vivienda por Zona",
        x = "Zona",
        y = "Tipo"
      ) +
      theme_minimal() +
      theme(axis.text.x = element_text(angle = 45, hjust = 1))
  })
}
## CA ejecutado correctamente.      eigenvalue percentage of variance cumulative percentage of variance
## dim 1 0.08305442                    100                               100
## 
##   El gráfico CA no pudo generarse (tabla degenerada).
##  Se mostrará un heatmap como alternativa válida.

Observación:

El análisis de correspondencia requiere una distribución suficiente de categorías en la tabla cruzada. En este dataset, algunas combinaciones zona–tipo presentan baja frecuencia, lo que puede generar inestabilidad gráfica en factoextra.

Por esta razón, se presenta adicionalmente un mapa de calor (heatmap) como alternativa robusta para visualizar la relación entre variables categóricas.

8. Visualizaciones adicionales

Distribución del precio por zona

# -----------------------------------------------------------
# PASO 8 — VISUALIZACIONES
# -----------------------------------------------------------

ggplot(df, aes(x = zona, y = preciom)) +
  geom_boxplot() +
  coord_flip() +
  labs(
    title = "Distribución del precio por zona",
    x = "Zona",
    y = "Precio (millones)"
  )


Precio vs Área construida

ggplot(df, aes(x = areaconst, y = preciom)) +
  geom_point(alpha = 0.4) +
  labs(
    title = "Relación entre Área Construida y Precio",
    x = "Área construida (m²)",
    y = "Precio (millones)"
  )


Observación:

Los boxplots permiten comparar zonas.

Los diagramas de dispersión muestran relaciones directas como:

  • Mayor área construida → mayor precio.

9. Conclusiones

A partir del análisis multivariado realizado, se concluye que:

  • El precio está fuertemente asociado al área construida, estrato y número de habitaciones.
  • El PCA permitió reducir la dimensionalidad, identificando variables clave del mercado.
  • El clustering segmentó propiedades en grupos homogéneos útiles para estrategias comerciales.
  • El análisis de correspondencia mostró patrones entre tipo de vivienda y zona geográfica.

Recomendación estratégica

La empresa inmobiliaria puede usar estos hallazgos para:

  • Definir precios más competitivos
  • Identificar zonas de alta valorización
  • Segmentar clientes según tipo de propiedad
  • Optimizar decisiones de inversión y venta