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:
Los resultados permitirán segmentar el mercado y apoyar decisiones estratégicas de compra, venta y valoración de propiedades.
# -----------------------------------------------------------
# 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
# -----------------------------------------------------------
# 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>
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.
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
fviz_eig(pca_model, addlabels = TRUE)
fviz_pca_biplot(pca_model, repel = TRUE)
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.
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")
``` 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
fviz_cluster(k3, data = df_scaled)
El análisis de conglomerados permite agrupar propiedades similares.
Esto ayuda a la empresa inmobiliaria a identificar segmentos de mercado:
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.
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.
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)"
)
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)"
)
Los boxplots permiten comparar zonas.
Los diagramas de dispersión muestran relaciones directas como:
A partir del análisis multivariado realizado, se concluye que:
La empresa inmobiliaria puede usar estos hallazgos para: