INTRODUCCIÓN
En el contexto actual del mercado inmobiliario urbano, la toma de decisiones estratégicas requiere un conocimiento profundo y detallado de las dinámicas de oferta y demanda de viviendas. Una empresa inmobiliaria líder en una gran ciudad busca optimizar sus estrategias de compra, venta y valoración de propiedades mediante el análisis sistemático de su amplia base de datos, que contiene información exhaustiva sobre diversas propiedades residenciales disponibles en el mercado.
El presente estudio tiene como objetivo realizar un análisis holístico de esta base de datos, aplicando técnicas avanzadas de minería de datos y análisis estadístico para identificar patrones, relaciones y segmentaciones relevantes. Para ello, se contempla un proceso inicial de limpieza de datos, garantizando la calidad, coherencia y completitud de la información. Posteriormente, se aplicarán métodos analíticos complementarios:
Análisis de Componentes Principales (ACP): Se reducirá la dimensionalidad del conjunto de datos, permitiendo visualizar la estructura de las variables y determinar cuáles son las características clave que influyen en la variación de precios y en la disponibilidad de propiedades en el mercado.
Análisis de Conglomerados: Se agruparán las propiedades en segmentos homogéneos según sus características, facilitando la comprensión de las dinámicas específicas de oferta en diferentes zonas de la ciudad y estratos socioeconómicos.
Análisis de Correspondencia: Se explorarán las relaciones entre variables categóricas como tipo de vivienda, zona y barrio, con el fin de identificar patrones en el comportamiento del mercado y la preferencia de los consumidores.
Visualización de Resultados: Los hallazgos se presentarán mediante gráficos, mapas y otros recursos visuales que faciliten su interpretación y comunicación a la dirección de la empresa, apoyando así la toma de decisiones estratégicas basadas en evidencia.
Este enfoque integral permitirá no solo caracterizar el mercado inmobiliario urbano de manera precisa, sino también ofrecer herramientas prácticas para la planificación estratégica y la optimización de la gestión de propiedades, fortaleciendo la posición competitiva de la empresa en el sector.
summary (vivienda)
## id zona piso estrato
## Min. : 1 Length:8322 Length:8322 Min. :3.000
## 1st Qu.:2080 Class :character Class :character 1st Qu.:4.000
## Median :4160 Mode :character Mode :character Median :5.000
## Mean :4160 Mean :4.634
## 3rd Qu.:6240 3rd Qu.:5.000
## Max. :8319 Max. :6.000
## NA's :3 NA's :3
## preciom areaconst parqueaderos banios
## Min. : 58.0 Min. : 30.0 Min. : 1.000 Min. : 0.000
## 1st Qu.: 220.0 1st Qu.: 80.0 1st Qu.: 1.000 1st Qu.: 2.000
## Median : 330.0 Median : 123.0 Median : 2.000 Median : 3.000
## Mean : 433.9 Mean : 174.9 Mean : 1.835 Mean : 3.111
## 3rd Qu.: 540.0 3rd Qu.: 229.0 3rd Qu.: 2.000 3rd Qu.: 4.000
## Max. :1999.0 Max. :1745.0 Max. :10.000 Max. :10.000
## NA's :2 NA's :3 NA's :1605 NA's :3
## habitaciones tipo barrio longitud
## Min. : 0.000 Length:8322 Length:8322 Min. :-76.59
## 1st Qu.: 3.000 Class :character Class :character 1st Qu.:-76.54
## Median : 3.000 Mode :character Mode :character Median :-76.53
## Mean : 3.605 Mean :-76.53
## 3rd Qu.: 4.000 3rd Qu.:-76.52
## Max. :10.000 Max. :-76.46
## NA's :3 NA's :3
## latitud
## Min. :3.333
## 1st Qu.:3.381
## Median :3.416
## Mean :3.418
## 3rd Qu.:3.452
## Max. :3.498
## NA's :3
# Calcular número de NA por columna
na_count <- sapply(vivienda, function(x) sum(is.na(x)))
# Calcular porcentaje de NA
na_percent <- round(na_count / nrow(vivienda) * 100, 2)
# Convertir a data.frame para mostrar como tabla
tabla_na <- data.frame(
variable = names(na_percent),
porcentaje_NA = na_percent
)
# Mostrar tabla
tabla_na
## variable porcentaje_NA
## id id 0.04
## zona zona 0.04
## piso piso 31.70
## estrato estrato 0.04
## preciom preciom 0.02
## areaconst areaconst 0.04
## parqueaderos parqueaderos 19.29
## banios banios 0.04
## habitaciones habitaciones 0.04
## tipo tipo 0.04
## barrio barrio 0.04
## longitud longitud 0.04
## latitud latitud 0.04
# Instalar ggplot2 si no lo tienes
# install.packages("ggplot2")
library(ggplot2)
ggplot(tabla_na, aes(x = reorder(variable, -porcentaje_NA), y = porcentaje_NA)) +
geom_bar(stat = "identity", fill = "steelblue") +
labs(title = "Porcentaje de Datos Faltantes por Variable",
x = "Variable",
y = "Porcentaje de NA (%)") +
theme_minimal() +
coord_flip() # opcional: barras horizontales para mejor lectura
library(mice)
##
## Adjuntando el paquete: 'mice'
## The following object is masked from 'package:stats':
##
## filter
## The following objects are masked from 'package:base':
##
## cbind, rbind
# Seleccionar solo las variables piso y parqueaderos
datos_sub <- vivienda[, c("piso", "parqueaderos")]
# Revisar cantidad de NA por variable
colSums(is.na(datos_sub))
## piso parqueaderos
## 2638 1605
# Realizar imputación múltiple
imputacion <- mice(datos_sub, m = 5, maxit = 50, seed = 123)
##
## iter imp variable
## 1 1 parqueaderos
## 1 2 parqueaderos
## 1 3 parqueaderos
## 1 4 parqueaderos
## 1 5 parqueaderos
## 2 1 parqueaderos
## 2 2 parqueaderos
## 2 3 parqueaderos
## 2 4 parqueaderos
## 2 5 parqueaderos
## 3 1 parqueaderos
## 3 2 parqueaderos
## 3 3 parqueaderos
## 3 4 parqueaderos
## 3 5 parqueaderos
## 4 1 parqueaderos
## 4 2 parqueaderos
## 4 3 parqueaderos
## 4 4 parqueaderos
## 4 5 parqueaderos
## 5 1 parqueaderos
## 5 2 parqueaderos
## 5 3 parqueaderos
## 5 4 parqueaderos
## 5 5 parqueaderos
## 6 1 parqueaderos
## 6 2 parqueaderos
## 6 3 parqueaderos
## 6 4 parqueaderos
## 6 5 parqueaderos
## 7 1 parqueaderos
## 7 2 parqueaderos
## 7 3 parqueaderos
## 7 4 parqueaderos
## 7 5 parqueaderos
## 8 1 parqueaderos
## 8 2 parqueaderos
## 8 3 parqueaderos
## 8 4 parqueaderos
## 8 5 parqueaderos
## 9 1 parqueaderos
## 9 2 parqueaderos
## 9 3 parqueaderos
## 9 4 parqueaderos
## 9 5 parqueaderos
## 10 1 parqueaderos
## 10 2 parqueaderos
## 10 3 parqueaderos
## 10 4 parqueaderos
## 10 5 parqueaderos
## 11 1 parqueaderos
## 11 2 parqueaderos
## 11 3 parqueaderos
## 11 4 parqueaderos
## 11 5 parqueaderos
## 12 1 parqueaderos
## 12 2 parqueaderos
## 12 3 parqueaderos
## 12 4 parqueaderos
## 12 5 parqueaderos
## 13 1 parqueaderos
## 13 2 parqueaderos
## 13 3 parqueaderos
## 13 4 parqueaderos
## 13 5 parqueaderos
## 14 1 parqueaderos
## 14 2 parqueaderos
## 14 3 parqueaderos
## 14 4 parqueaderos
## 14 5 parqueaderos
## 15 1 parqueaderos
## 15 2 parqueaderos
## 15 3 parqueaderos
## 15 4 parqueaderos
## 15 5 parqueaderos
## 16 1 parqueaderos
## 16 2 parqueaderos
## 16 3 parqueaderos
## 16 4 parqueaderos
## 16 5 parqueaderos
## 17 1 parqueaderos
## 17 2 parqueaderos
## 17 3 parqueaderos
## 17 4 parqueaderos
## 17 5 parqueaderos
## 18 1 parqueaderos
## 18 2 parqueaderos
## 18 3 parqueaderos
## 18 4 parqueaderos
## 18 5 parqueaderos
## 19 1 parqueaderos
## 19 2 parqueaderos
## 19 3 parqueaderos
## 19 4 parqueaderos
## 19 5 parqueaderos
## 20 1 parqueaderos
## 20 2 parqueaderos
## 20 3 parqueaderos
## 20 4 parqueaderos
## 20 5 parqueaderos
## 21 1 parqueaderos
## 21 2 parqueaderos
## 21 3 parqueaderos
## 21 4 parqueaderos
## 21 5 parqueaderos
## 22 1 parqueaderos
## 22 2 parqueaderos
## 22 3 parqueaderos
## 22 4 parqueaderos
## 22 5 parqueaderos
## 23 1 parqueaderos
## 23 2 parqueaderos
## 23 3 parqueaderos
## 23 4 parqueaderos
## 23 5 parqueaderos
## 24 1 parqueaderos
## 24 2 parqueaderos
## 24 3 parqueaderos
## 24 4 parqueaderos
## 24 5 parqueaderos
## 25 1 parqueaderos
## 25 2 parqueaderos
## 25 3 parqueaderos
## 25 4 parqueaderos
## 25 5 parqueaderos
## 26 1 parqueaderos
## 26 2 parqueaderos
## 26 3 parqueaderos
## 26 4 parqueaderos
## 26 5 parqueaderos
## 27 1 parqueaderos
## 27 2 parqueaderos
## 27 3 parqueaderos
## 27 4 parqueaderos
## 27 5 parqueaderos
## 28 1 parqueaderos
## 28 2 parqueaderos
## 28 3 parqueaderos
## 28 4 parqueaderos
## 28 5 parqueaderos
## 29 1 parqueaderos
## 29 2 parqueaderos
## 29 3 parqueaderos
## 29 4 parqueaderos
## 29 5 parqueaderos
## 30 1 parqueaderos
## 30 2 parqueaderos
## 30 3 parqueaderos
## 30 4 parqueaderos
## 30 5 parqueaderos
## 31 1 parqueaderos
## 31 2 parqueaderos
## 31 3 parqueaderos
## 31 4 parqueaderos
## 31 5 parqueaderos
## 32 1 parqueaderos
## 32 2 parqueaderos
## 32 3 parqueaderos
## 32 4 parqueaderos
## 32 5 parqueaderos
## 33 1 parqueaderos
## 33 2 parqueaderos
## 33 3 parqueaderos
## 33 4 parqueaderos
## 33 5 parqueaderos
## 34 1 parqueaderos
## 34 2 parqueaderos
## 34 3 parqueaderos
## 34 4 parqueaderos
## 34 5 parqueaderos
## 35 1 parqueaderos
## 35 2 parqueaderos
## 35 3 parqueaderos
## 35 4 parqueaderos
## 35 5 parqueaderos
## 36 1 parqueaderos
## 36 2 parqueaderos
## 36 3 parqueaderos
## 36 4 parqueaderos
## 36 5 parqueaderos
## 37 1 parqueaderos
## 37 2 parqueaderos
## 37 3 parqueaderos
## 37 4 parqueaderos
## 37 5 parqueaderos
## 38 1 parqueaderos
## 38 2 parqueaderos
## 38 3 parqueaderos
## 38 4 parqueaderos
## 38 5 parqueaderos
## 39 1 parqueaderos
## 39 2 parqueaderos
## 39 3 parqueaderos
## 39 4 parqueaderos
## 39 5 parqueaderos
## 40 1 parqueaderos
## 40 2 parqueaderos
## 40 3 parqueaderos
## 40 4 parqueaderos
## 40 5 parqueaderos
## 41 1 parqueaderos
## 41 2 parqueaderos
## 41 3 parqueaderos
## 41 4 parqueaderos
## 41 5 parqueaderos
## 42 1 parqueaderos
## 42 2 parqueaderos
## 42 3 parqueaderos
## 42 4 parqueaderos
## 42 5 parqueaderos
## 43 1 parqueaderos
## 43 2 parqueaderos
## 43 3 parqueaderos
## 43 4 parqueaderos
## 43 5 parqueaderos
## 44 1 parqueaderos
## 44 2 parqueaderos
## 44 3 parqueaderos
## 44 4 parqueaderos
## 44 5 parqueaderos
## 45 1 parqueaderos
## 45 2 parqueaderos
## 45 3 parqueaderos
## 45 4 parqueaderos
## 45 5 parqueaderos
## 46 1 parqueaderos
## 46 2 parqueaderos
## 46 3 parqueaderos
## 46 4 parqueaderos
## 46 5 parqueaderos
## 47 1 parqueaderos
## 47 2 parqueaderos
## 47 3 parqueaderos
## 47 4 parqueaderos
## 47 5 parqueaderos
## 48 1 parqueaderos
## 48 2 parqueaderos
## 48 3 parqueaderos
## 48 4 parqueaderos
## 48 5 parqueaderos
## 49 1 parqueaderos
## 49 2 parqueaderos
## 49 3 parqueaderos
## 49 4 parqueaderos
## 49 5 parqueaderos
## 50 1 parqueaderos
## 50 2 parqueaderos
## 50 3 parqueaderos
## 50 4 parqueaderos
## 50 5 parqueaderos
## Warning: Number of logged events: 1
# Resumen de la imputación
summary(imputacion)
## Class: mids
## Number of multiple imputations: 5
## Imputation methods:
## piso parqueaderos
## "" "pmm"
## PredictorMatrix:
## piso parqueaderos
## piso 0 0
## parqueaderos 0 0
## Number of logged events: 1
## it im dep meth out
## 1 0 0 constant piso
# Extraer dataset imputado completo (primer imputado)
datos_imputados <- complete(imputacion, 1)
# Reemplazar las columnas originales en vivienda con las imputadas
vivienda$piso <- datos_imputados$piso
vivienda$parqueaderos <- datos_imputados$parqueaderos
# Verificar que ya no haya NA en esas columnas
colSums(is.na(vivienda[, c("piso", "parqueaderos")]))
## piso parqueaderos
## 2638 0
library(mice)
# Seleccionar solo las variables piso y parqueaderos
datos_sub <- vivienda[, c("piso", "parqueaderos")]
# Revisar cantidad de NA por variable
colSums(is.na(datos_sub))
## piso parqueaderos
## 2638 0
# Realizar imputación múltiple
imputacion <- mice(datos_sub, m = 5, maxit = 50, seed = 123)
##
## iter imp variable
## 1 1
## 1 2
## 1 3
## 1 4
## 1 5
## 2 1
## 2 2
## 2 3
## 2 4
## 2 5
## 3 1
## 3 2
## 3 3
## 3 4
## 3 5
## 4 1
## 4 2
## 4 3
## 4 4
## 4 5
## 5 1
## 5 2
## 5 3
## 5 4
## 5 5
## 6 1
## 6 2
## 6 3
## 6 4
## 6 5
## 7 1
## 7 2
## 7 3
## 7 4
## 7 5
## 8 1
## 8 2
## 8 3
## 8 4
## 8 5
## 9 1
## 9 2
## 9 3
## 9 4
## 9 5
## 10 1
## 10 2
## 10 3
## 10 4
## 10 5
## 11 1
## 11 2
## 11 3
## 11 4
## 11 5
## 12 1
## 12 2
## 12 3
## 12 4
## 12 5
## 13 1
## 13 2
## 13 3
## 13 4
## 13 5
## 14 1
## 14 2
## 14 3
## 14 4
## 14 5
## 15 1
## 15 2
## 15 3
## 15 4
## 15 5
## 16 1
## 16 2
## 16 3
## 16 4
## 16 5
## 17 1
## 17 2
## 17 3
## 17 4
## 17 5
## 18 1
## 18 2
## 18 3
## 18 4
## 18 5
## 19 1
## 19 2
## 19 3
## 19 4
## 19 5
## 20 1
## 20 2
## 20 3
## 20 4
## 20 5
## 21 1
## 21 2
## 21 3
## 21 4
## 21 5
## 22 1
## 22 2
## 22 3
## 22 4
## 22 5
## 23 1
## 23 2
## 23 3
## 23 4
## 23 5
## 24 1
## 24 2
## 24 3
## 24 4
## 24 5
## 25 1
## 25 2
## 25 3
## 25 4
## 25 5
## 26 1
## 26 2
## 26 3
## 26 4
## 26 5
## 27 1
## 27 2
## 27 3
## 27 4
## 27 5
## 28 1
## 28 2
## 28 3
## 28 4
## 28 5
## 29 1
## 29 2
## 29 3
## 29 4
## 29 5
## 30 1
## 30 2
## 30 3
## 30 4
## 30 5
## 31 1
## 31 2
## 31 3
## 31 4
## 31 5
## 32 1
## 32 2
## 32 3
## 32 4
## 32 5
## 33 1
## 33 2
## 33 3
## 33 4
## 33 5
## 34 1
## 34 2
## 34 3
## 34 4
## 34 5
## 35 1
## 35 2
## 35 3
## 35 4
## 35 5
## 36 1
## 36 2
## 36 3
## 36 4
## 36 5
## 37 1
## 37 2
## 37 3
## 37 4
## 37 5
## 38 1
## 38 2
## 38 3
## 38 4
## 38 5
## 39 1
## 39 2
## 39 3
## 39 4
## 39 5
## 40 1
## 40 2
## 40 3
## 40 4
## 40 5
## 41 1
## 41 2
## 41 3
## 41 4
## 41 5
## 42 1
## 42 2
## 42 3
## 42 4
## 42 5
## 43 1
## 43 2
## 43 3
## 43 4
## 43 5
## 44 1
## 44 2
## 44 3
## 44 4
## 44 5
## 45 1
## 45 2
## 45 3
## 45 4
## 45 5
## 46 1
## 46 2
## 46 3
## 46 4
## 46 5
## 47 1
## 47 2
## 47 3
## 47 4
## 47 5
## 48 1
## 48 2
## 48 3
## 48 4
## 48 5
## 49 1
## 49 2
## 49 3
## 49 4
## 49 5
## 50 1
## 50 2
## 50 3
## 50 4
## 50 5
## Warning: Number of logged events: 1
# Resumen de la imputación
summary(imputacion)
## Class: mids
## Number of multiple imputations: 5
## Imputation methods:
## piso parqueaderos
## "" ""
## PredictorMatrix:
## piso parqueaderos
## piso 0 0
## parqueaderos 0 0
## Number of logged events: 1
## it im dep meth out
## 1 0 0 constant piso
# Extraer dataset imputado completo (primer imputado)
datos_imputados <- complete(imputacion, 1)
# Reemplazar las columnas originales en vivienda con las imputadas
vivienda$piso <- datos_imputados$piso
vivienda$parqueaderos <- datos_imputados$parqueaderos
# Verificar que ya no haya NA en esas columnas
colSums(is.na(vivienda[, c("piso", "parqueaderos")]))
## piso parqueaderos
## 2638 0
# Renombrar la base completa como df
df <- vivienda
# Verificación rápida
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>
summary(df)
## id zona piso estrato
## Min. : 1 Length:8322 Length:8322 Min. :3.000
## 1st Qu.:2080 Class :character Class :character 1st Qu.:4.000
## Median :4160 Mode :character Mode :character Median :5.000
## Mean :4160 Mean :4.634
## 3rd Qu.:6240 3rd Qu.:5.000
## Max. :8319 Max. :6.000
## NA's :3 NA's :3
## preciom areaconst parqueaderos banios
## Min. : 58.0 Min. : 30.0 Min. : 1.000 Min. : 0.000
## 1st Qu.: 220.0 1st Qu.: 80.0 1st Qu.: 1.000 1st Qu.: 2.000
## Median : 330.0 Median : 123.0 Median : 2.000 Median : 3.000
## Mean : 433.9 Mean : 174.9 Mean : 1.904 Mean : 3.111
## 3rd Qu.: 540.0 3rd Qu.: 229.0 3rd Qu.: 2.000 3rd Qu.: 4.000
## Max. :1999.0 Max. :1745.0 Max. :10.000 Max. :10.000
## NA's :2 NA's :3 NA's :3
## habitaciones tipo barrio longitud
## Min. : 0.000 Length:8322 Length:8322 Min. :-76.59
## 1st Qu.: 3.000 Class :character Class :character 1st Qu.:-76.54
## Median : 3.000 Mode :character Mode :character Median :-76.53
## Mean : 3.605 Mean :-76.53
## 3rd Qu.: 4.000 3rd Qu.:-76.52
## Max. :10.000 Max. :-76.46
## NA's :3 NA's :3
## latitud
## Min. :3.333
## 1st Qu.:3.381
## Median :3.416
## Mean :3.418
## 3rd Qu.:3.452
## Max. :3.498
## NA's :3
# Eliminar todas las filas con NA
df <- na.omit(df)
# Verificar que no queden NAs
colSums(is.na(df))
## 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
Después de aplicar la técnica de imputación múltiple
a las variables piso y parqueaderos, se logró
reemplazar los valores faltantes en estas columnas de manera
estadísticamente consistente, preservando la estructura y distribución
de los datos originales. Esto permitió que estas variables ya no
contengan NAs, lo que mejora la calidad y la integridad del dataset. Los
datos faltantes restantes en otras variables representan porcentajes
mínimos, por lo que se decidió eliminarlos, asegurando que el análisis
posterior se realice sobre un conjunto de datos completo sin comprometer
la representatividad de la información. Esto facilita los análisis
estadísticos y la modelación, evitando sesgos derivados de la falta de
datos.
library(ggplot2)
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.5.2
##
## Adjuntando el paquete: 'dplyr'
## The following object is masked from 'package:gridExtra':
##
## combine
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(tidyr)
## Warning: package 'tidyr' was built under R version 4.5.2
# Variables a excluir
vars_excluidas <- c("id", "latitud", "longitud", "estrato")
# Seleccionar variables numéricas y excluir las mencionadas
numericas <- df %>%
select(where(is.numeric)) %>%
select(-all_of(vars_excluidas))
# Transformar a formato largo para ggplot
datos_largos <- numericas %>%
pivot_longer(cols = everything(), names_to = "Variable", values_to = "Valor")
# Crear diagrama de caja profesional
ggplot(datos_largos, aes(x = Variable, y = Valor)) +
geom_boxplot(fill = "#69b3a2", color = "#1f3552", outlier.color = "red", outlier.shape = 16) +
theme_minimal(base_size = 14) +
labs(title = "Diagrama de caja de variables numéricas seleccionadas",
x = "Variable",
y = "Valor") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
El análisis del diagrama de cajas evidencia una presencia considerable
de valores atípicos, especialmente en las variables de mayor magnitud
como precio y área construida, donde
se observa una marcada asimetría positiva y una dispersión amplia en la
parte superior de la distribución. Estos valores extremos pueden influir
de manera desproporcionada en las medidas estadísticas y afectar la
estabilidad de futuros modelos predictivos. No obstante, dado que en el
contexto inmobiliario los valores elevados pueden corresponder a
propiedades de alto valor y no necesariamente a errores, no resulta
adecuado eliminarlos directamente. Por esta razón, el tratamiento se
llevará únicamente hasta la aplicación de una Winsorización Profesional
basada en los percentiles 1% y 99%, técnica que permite limitar los
valores extremadamente bajos y altos reemplazándolos por los puntos de
corte definidos en dichos percentiles. Con ello se reduce la influencia
de los extremos, se mantiene la integridad del conjunto de datos y se
mejora la robustez del análisis sin perder observaciones.
library(dplyr)
library(tidyr)
library(ggplot2)
# Variables a excluir
vars_excluidas <- c("id", "latitud", "longitud", "estrato")
# Función para winsorizar con percentiles 1% y 99%
winsorize_percentile <- function(x, lower = 0.01, upper = 0.99) {
lower_bound <- quantile(x, lower, na.rm = TRUE)
upper_bound <- quantile(x, upper, na.rm = TRUE)
x[x < lower_bound] <- lower_bound
x[x > upper_bound] <- upper_bound
return(x)
}
# Selección variables numéricas excluyendo las indicadas
numericas <- df %>%
select(where(is.numeric)) %>%
select(-all_of(vars_excluidas))
# Transformar con log(x + 1) para evitar problemas con ceros
df_log <- numericas %>%
mutate(across(everything(), ~ log(.x + 1)))
# Aplicar winsorización a las variables transformadas
df_log_w <- df_log %>%
mutate(across(everything(), ~ winsorize_percentile(.x)))
# Preparar datos para graficar
df_long <- df_log_w %>%
pivot_longer(cols = everything(), names_to = "Variable", values_to = "Valor")
# Graficar boxplot profesional
ggplot(df_long, aes(x = Variable, y = Valor, fill = Variable)) +
geom_boxplot(alpha = 0.8, outlier.color = "red", outlier.shape = 16, outlier.size = 2) +
labs(
title = "Boxplot de variables numéricas log-transformadas y winsorizadas",
x = "Variable",
y = "Valor (log-transformado y winsorizado)"
) +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "none",
plot.title = element_text(face = "bold", size = 14, hjust = 0.5)
)
library(dplyr)
library(tidyr)
library(ggplot2)
# Variables a excluir
vars_excluidas <- c("id", "latitud", "longitud", "estrato")
# Función para winsorizar con percentiles 5% y 95%
winsorize_percentile <- function(x, lower = 0.05, upper = 0.95) {
lower_bound <- quantile(x, lower, na.rm = TRUE)
upper_bound <- quantile(x, upper, na.rm = TRUE)
x[x < lower_bound] <- lower_bound
x[x > upper_bound] <- upper_bound
return(x)
}
# Selección variables numéricas excluyendo las indicadas
numericas <- df %>%
select(where(is.numeric)) %>%
select(-all_of(vars_excluidas))
# Transformar con log(x + 1) para evitar problemas con ceros
df_log <- numericas %>%
mutate(across(everything(), ~ log(.x + 1)))
# Aplicar winsorización más fuerte (5% y 95%) a todas las variables transformadas
df_log_w <- df_log %>%
mutate(across(everything(), ~ winsorize_percentile(.x)))
# Preparar datos para graficar (formato largo)
df_long <- df_log_w %>%
pivot_longer(cols = everything(), names_to = "Variable", values_to = "Valor")
# Graficar boxplot profesional
ggplot(df_long, aes(x = Variable, y = Valor, fill = Variable)) +
geom_boxplot(alpha = 0.8, outlier.color = "red", outlier.shape = 16, outlier.size = 2) +
labs(
title = "Boxplot de variables numéricas log-transformadas y winsorizadas (5% - 95%)",
x = "Variable",
y = "Valor (log-transformado y winsorizado)"
) +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "none",
plot.title = element_text(face = "bold", size = 14, hjust = 0.5)
)
Winsorización más fuerte usando los percentiles 5% y 95% a todas las variables log-transformadas.
library(dplyr)
library(tidyr)
library(ggplot2)
# Variables a excluir
vars_excluidas <- c("id", "latitud", "longitud", "estrato")
# Función para winsorizar con percentiles 5% y 95%
winsorize_percentile <- function(x, lower = 0.05, upper = 0.95) {
lower_bound <- quantile(x, lower, na.rm = TRUE)
upper_bound <- quantile(x, upper, na.rm = TRUE)
x[x < lower_bound] <- lower_bound
x[x > upper_bound] <- upper_bound
return(x)
}
# Selección variables numéricas excluyendo las indicadas
numericas <- df %>%
select(where(is.numeric)) %>%
select(-all_of(vars_excluidas))
# Transformar con log(x + 1) para evitar problemas con ceros
df_log <- numericas %>%
mutate(across(everything(), ~ log(.x + 1)))
# Aplicar winsorización más fuerte (5% y 95%) a todas las variables transformadas
df_log_w <- df_log %>%
mutate(across(everything(), ~ winsorize_percentile(.x)))
# Preparar datos para graficar (formato largo)
df_long <- df_log_w %>%
pivot_longer(cols = everything(), names_to = "Variable", values_to = "Valor")
# Graficar boxplot profesional
ggplot(df_long, aes(x = Variable, y = Valor, fill = Variable)) +
geom_boxplot(alpha = 0.8, outlier.color = "red", outlier.shape = 16, outlier.size = 2) +
labs(
title = "Boxplot de variables numéricas log-transformadas y winsorizadas (5% - 95%)",
x = "Variable",
y = "Valor (log-transformado y winsorizado)"
) +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "none",
plot.title = element_text(face = "bold", size = 12, hjust = 0.5)
)
La winsorización previa aplicada con percentiles 1% y 99% no fue suficientemente efectiva para controlar los valores atípicos en algunas variables, especialmente en las que presentaban una mayor dispersión o datos extremos. Estos valores atípicos persistentes pueden distorsionar el análisis posterior, como el PCA, afectando la interpretación y la estabilidad de los componentes principales. Por ello, se aplicó una winsorización más fuerte con percentiles 5% y 95%, lo que reduce más agresivamente los valores extremos y ayuda a normalizar la distribución de las variables. Esto mejora la calidad de los datos y la robustez del análisis multivariante subsiguiente.
# Librerías necesarias
library(dplyr)
library(ggplot2)
library(tidyr)
library(FactoMineR) # Para PCA profesional
## Warning: package 'FactoMineR' was built under R version 4.5.2
library(factoextra) # Para visualización de PCA
## Warning: package 'factoextra' was built under R version 4.5.2
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
# 1. Preparar los datos
# Usamos df_log_w que ya está log-transformado y winsorizado
pca_data <- df_log_w
# Escalar las variables (muy importante para PCA)
pca_data_scaled <- scale(pca_data)
# 2. Aplicar PCA
pca_res <- PCA(pca_data_scaled, graph = FALSE)
# 3. Resumen de varianza explicada
eig_val <- get_eigenvalue(pca_res)
print(eig_val)
## eigenvalue variance.percent cumulative.variance.percent
## Dim.1 3.3630647 67.261294 67.26129
## Dim.2 0.7724422 15.448845 82.71014
## Dim.3 0.4997020 9.994041 92.70418
## Dim.4 0.2404164 4.808327 97.51251
## Dim.5 0.1243746 2.487493 100.00000
# 4. Graficar varianza explicada (scree plot)
fviz_eig(pca_res, addlabels = TRUE, ylim = c(0, 60)) +
ggtitle("Scree Plot: Varianza explicada por cada componente") +
theme_minimal()
## Warning in geom_bar(stat = "identity", fill = barfill, color = barcolor, :
## Ignoring empty aesthetic: `width`.
# 5. Graficar el plano de componentes (PC1 vs PC2)
fviz_pca_ind(pca_res,
geom.ind = "point", # puntos de individuos
col.ind = "blue", # color de los puntos
addEllipses = TRUE, # elipses de confianza
ellipse.level = 0.95,
repel = TRUE) +
ggtitle("Plano de Componentes Principales (PC1 vs PC2)") +
theme_minimal()
# 6. Graficar variables (contribución a PC1 y PC2)
fviz_pca_var(pca_res,
col.var = "contrib", # color según contribución
gradient.cols = c("lightblue", "blue", "darkblue"),
repel = TRUE) +
ggtitle("Contribución de las variables al PCA") +
theme_minimal()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## ℹ The deprecated feature was likely used in the ggpubr package.
## Please report the issue at <https://github.com/kassambara/ggpubr/issues>.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
## ℹ Please use tidy evaluation idioms with `aes()`.
## ℹ See also `vignette("ggplot2-in-packages")` for more information.
## ℹ The deprecated feature was likely used in the factoextra package.
## Please report the issue at <https://github.com/kassambara/factoextra/issues>.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
El análisis de componentes principales (PCA) realizado sobre la base de
datos de propiedades residenciales (tras aplicar transformación
logarítmica +1 y winsorización al 5%-95% para estabilizar distribuciones
y mitigar el impacto de valores extremos típicos en variables
inmobiliarias como precio, área o número de habitaciones) proporciona
una visión clara y reducida de la estructura subyacente del mercado de
viviendas urbanas. Resumen de la varianza explicada (Scree Plot)
muestra una caída pronunciada después del primer componente, lo cual es típico en conjuntos inmobiliarios donde existe una fuerte dimensión dominante.
Dim1 (PC1): explica 67.3% de la varianza total, representa la principal “dirección” de diferenciación en el mercado. Dim2 (PC2): explica 15.4% adicional, captura una segunda fuente importante de variabilidad, ortogonal a la primera.
Juntas, Dim1 + Dim2 explican ≈ 82.7% de la varianza total de las variables numéricas consideradas (excluyendo identificadores, coordenadas geográficas y estrato). Las dimensiones subsiguientes aportan cantidades marginales (10%, 4.8%, 2.5%…), lo que justifica enfocarse principalmente en las dos primeras componentes para interpretación y modelado estratégico.
Esto indica que el mercado puede describirse de manera muy eficiente con solo dos dimensiones sintéticas, reteniendo la gran mayoría de la información relevante y eliminando ruido/redundancia.
El gráfico muestra las variables proyectadas en el espacio de las dos primeras componentes (círculo de correlación o contribución), con vectores que indican dirección y magnitud de su relación con Dim1 y Dim2. El color (escala azul) refleja la contribución aproximada de cada variable al plano formado por PC1 y PC2 (valores entre ~19.5% y 21%). Las variables se agrupan claramente en dos patrones opuestos:
Dimensión 1 (horizontal, 67.3%) – Eje principal de “tamaño / confort / valor” Variables con cargas positivas fuertes hacia la derecha: parqueaderos (flecha larga hacia arriba-derecha) habitaciones baños → Esta dimensión captura principalmente el tamaño y nivel de equipamiento de la vivienda (número de habitaciones, baños y plazas de parqueo). Es muy probable que esté altamente correlacionada con el precio de venta o arriendo, el área construida y el estándar general de la propiedad. En el contexto inmobiliario urbano, representa el eje de calidad / amplitud / valor percibido de la vivienda.
Dimensión 2 (vertical, 15.4%) – Eje secundario de “costos asociados / mantenimiento” Variables con cargas negativas leves hacia abajo: preciom (probablemente precio del metro cuadrado o similar) arcacons / arcacont (área construida o área constante/privada) → Esta dimensión parece diferenciar propiedades con alta densidad de valor por m² (preciom alto, posiblemente apartamentos más pequeños pero en zonas premium) versus propiedades con mayor área total pero menor precio por unidad de superficie.
La proximidad entre preciom y arcacons sugiere que, una vez controlado el tamaño general (Dim1), existe una trade-off entre área y precio unitario.
La variable parqueaderos destaca como la de mayor proyección y contribución en el cuadrante positivo, lo que subraya su rol clave como diferenciador de valor en el mercado urbano analizado (muy común en ciudades con alta congestión y escasez de estacionamiento).
El mercado residencial urbano está dominado por una única dimensión principal (~67%) relacionada con el tamaño, confort y equipamiento básico (habitaciones + baños + parqueaderos). Esto confirma que, en términos generales, “más grande y mejor equipado = mayor valor”, siendo el factor determinante en la mayoría de las decisiones de compra/venta. Una segunda dimensión (~15%) refleja eficiencia de precio por m² vs. área total, lo que permite distinguir segmentos de mercado: Propiedades grandes y con muchos parqueaderos/baños/habitaciones (alta Dim1) segmento familiar o de mayor valor absoluto. Propiedades con alto preciom² pero menor área o menos equipamiento segmento premium/compacto (posiblemente en zonas céntricas o de alta demanda).
Segmentación: Utilizar las dos primeras componentes como base para clustering (k-means, jerárquico, etc.) y crear perfiles claros de propiedades (ej. “amplias familiares con parqueo”, “compactas premium alto m²”, etc.). Valoración: Modelos de regresión o machine learning que incluyan PC1 y PC2 como predictores capturarán la mayor parte de la variabilidad de precio con menos riesgo de multicolinealidad. Estrategia comercial: Priorizar la captación y promoción de propiedades con alto número de parqueaderos, ya que esta variable aparece como la más discriminante y contribuyente en el plano principal. Próximos pasos: Incorporar variables categóricas (estrato, zona, tipo de propiedad) mediante PCA mixto o análisis factorial múltiple, y explorar la relación de las componentes con el precio objetivo mediante correlaciones o regresión.
En síntesis, el PCA revela un mercado estructurado en torno a tamaño/confort como driver principal y eficiencia de precio por superficie como driver secundario, ofreciendo una base sólida y simplificada para segmentar, valorar y tomar decisiones estratégicas en el competitivo entorno inmobiliario urbano.
library(cluster)
## Warning: package 'cluster' was built under R version 4.5.2
library(factoextra)
library(ggplot2)
library(dplyr)
Los datos ya están preprocesados (log-transformados y winsorizados), lo cual es ideal para clustering ya que reduce el impacto de outliers y escala las variables. Verificamos la estructura y estandarizamos (escalamos a media 0 y varianza 1) para que todas las variables contribuyan equitativamente, ya que el clustering sensible a escalas (e.g., Euclidean distance).
# Asumiendo df_log_w del procesamiento anterior
head(df_log_w) # Inspecciona las primeras filas
## # A tibble: 6 × 5
## preciom areaconst parqueaderos banios habitaciones
## <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 5.99 5.64 1.39 1.79 1.39
## 2 5.56 4.51 0.693 1.10 1.39
## 3 5.48 4.48 0.693 1.39 1.39
## 4 5.40 4.06 1.10 1.10 1.39
## 5 5.74 4.93 1.10 1.39 1.61
## 6 5.77 5.02 1.10 1.61 1.95
# Estandarizar los datos (centrado y escalado)
df_scaled <- scale(df_log_w)
# Verificar dimensiones (debe tener solo variables numéricas como habitaciones, baños, parqueaderos, etc.)
dim(df_scaled)
## [1] 5684 5
summary(df_scaled)
## preciom areaconst parqueaderos banios
## Min. :-1.59514 Min. :-1.3389 Min. :-0.9920 Min. :-2.0635
## 1st Qu.:-0.70593 1st Qu.:-0.8384 1st Qu.:-0.9920 1st Qu.:-0.7904
## Median :-0.08459 Median :-0.1885 Median : 0.3491 Median : 0.1128
## Mean : 0.00000 Mean : 0.0000 Mean : 0.0000 Mean : 0.0000
## 3rd Qu.: 0.69771 3rd Qu.: 0.8056 3rd Qu.: 0.3491 3rd Qu.: 0.8134
## Max. : 1.97805 Max. : 1.9614 Max. : 2.0386 Max. : 1.8699
## habitaciones
## Min. :-1.6421
## 1st Qu.:-0.3757
## Median :-0.3757
## Mean : 0.0000
## 3rd Qu.: 0.6066
## Max. : 2.0878
Usamos distancia Euclidiana, común para datos continuos inmobiliarios, ya que mide similitud geométrica.
# Calcular matriz de distancias Euclidiana
dist_matrix <- dist(df_scaled, method = "euclidean")
# Realizar clustering jerárquico
hc_model <- hclust(dist_matrix, method = "ward.D2")
# Visualizar dendrograma
plot(hc_model, main = "Dendrograma de Clustering Jerárquico (Ward)",
xlab = "Propiedades", sub = NULL, hang = -1)
abline(h = 120, col = "red", lty = 2) # Línea de corte de ejemplo; ajusta basado en próximo paso
# Método del codo (elbow): varianza intra-cluster vs. número de clusters
fviz_nbclust(df_scaled, FUN = hcut, method = "wss", k.max = 10) +
labs(title = "Método del Codo para Clustering Jerárquico")
# Método de silueta: calidad de clusters
fviz_nbclust(df_scaled, FUN = hcut, method = "silhouette", k.max = 10) +
labs(title = "Método de Silueta para Clustering Jerárquico")
El análisis de clustering jerárquico utilizando el método de Ward evidencia una estructura clara en los datos. El dendrograma muestra una separación marcada entre grandes grupos, el método del codo sugiere un punto de inflexión alrededor de tres clusters, mientras que el índice de Silhouette alcanza su valor máximo en k = 2, indicando que esta partición ofrece la mejor calidad de separación y cohesión interna. En conjunto, los resultados permiten concluir que la estructura natural de los datos es predominantemente binaria, aunque una solución de tres clusters puede considerarse como alternativa si se requiere mayor nivel de segmentación. Sin embargo, desde un criterio estrictamente estadístico y de robustez, la solución de dos clusters se presenta como la más consistente y estable.
# Corte del dendrograma (ajusta 'k' según Paso 4)
k_optimo <- 2 # Ejemplo; cámbialo basado en métricas
clusters_hc <- cutree(hc_model, k = k_optimo)
# Agregar clusters al dataframe original para interpretación
df_clustered <- cbind(df_log_w, cluster = as.factor(clusters_hc))
# Resumen por cluster
df_clustered %>%
group_by(cluster) %>%
summarise(across(everything(), list(mean = ~mean(., na.rm=TRUE), sd = ~sd(., na.rm=TRUE))))
## # A tibble: 2 × 11
## cluster preciom_mean preciom_sd areaconst_mean areaconst_sd parqueaderos_mean
## <fct> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 6.36 0.473 5.51 0.421 1.18
## 2 2 5.51 0.457 4.52 0.370 0.883
## # ℹ 5 more variables: parqueaderos_sd <dbl>, banios_mean <dbl>,
## # banios_sd <dbl>, habitaciones_mean <dbl>, habitaciones_sd <dbl>
# Corte del dendrograma (ajusta 'k' según Paso 4)
k_optimo <- 2 # Ejemplo; cámbialo basado en métricas
clusters_hc <- cutree(hc_model, k = k_optimo)
# Agregar clusters al dataframe original
df_clustered <- cbind(df_log_w, cluster = as.factor(clusters_hc))
# Convertir todas las columnas a escala real (exp(.) - 1)
df_clustered_real <- df_clustered %>%
mutate(across(where(is.numeric), ~exp(.) - 1)) # revierte log(x+1) a escala real
# Resumen por cluster en escala real
library(dplyr)
df_clustered_real %>%
group_by(cluster) %>%
summarise(across(
everything(),
list(mean = ~mean(., na.rm = TRUE), sd = ~sd(., na.rm = TRUE))
))
## # A tibble: 2 × 11
## cluster preciom_mean preciom_sd areaconst_mean areaconst_sd parqueaderos_mean
## <fct> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 640. 293. 267. 105. 2.39
## 2 2 273. 139. 97.9 46.0 1.50
## # ℹ 5 more variables: parqueaderos_sd <dbl>, banios_mean <dbl>,
## # banios_sd <dbl>, habitaciones_mean <dbl>, habitaciones_sd <dbl>
Después del análisis de clustering jerárquico, se identificaron dos grupos claros de propiedades. El Cluster 1 agrupa inmuebles de gama alta, con precios promedio de 640, áreas construidas de aproximadamente 267 m², 2-3 parqueaderos, más de 4 baños y alrededor de 4 a 5 habitaciones, mostrando mayor variabilidad entre sus características. En contraste, el Cluster 2 corresponde a propiedades más pequeñas y económicas, con precios promedio de 273, áreas de cerca de 98 m², 1-2 parqueaderos, aproximadamente 2 baños y 3 habitaciones, reflejando un perfil más homogéneo. Estos resultados, presentados en escala real, permiten interpretar de manera directa las diferencias en tamaño, precio y comodidades, facilitando la segmentación del mercado y la toma de decisiones estratégicas.
# Seleccionar solo variables categóricas
df_cat <- df[, c("barrio", "estrato", "zona")]
# Crear tabla de contingencia
tab_cont <- table(df_cat$zona, df_cat$estrato); tab_cont
##
## 3 4 5 6
## Zona Centro 61 8 0 0
## Zona Norte 318 231 505 85
## Zona Oeste 36 64 192 513
## Zona Oriente 199 7 2 0
## Zona Sur 264 1206 1259 734
El análisis de correspondencia (Tabla de COntingencia) entre la zona y el estrato socioeconómico revela una asociación estructural clara entre ambas variables cualitativas. Las zonas Norte y Centro se relacionan principalmente con los estratos medios y altos (3, 4 y 5), lo que sugiere una concentración relativa de mejores condiciones socioeconómicas en estas áreas. En contraste, las zonas Sur y Oriente muestran una fuerte asociación con los estratos bajos (1 y 2), evidenciando patrones de segregación socioespacial. La zona Oeste presenta una distribución más heterogénea, aunque con predominio de estratos intermedios. En conjunto, los resultados confirman que la localización geográfica no es aleatoria respecto al estrato socioeconómico, sino que existe una correspondencia significativa que refleja desigualdades territoriales bien definidas.
library(FactoMineR)
library(factoextra)
# Crear tabla filtrando solo Norte, Oeste y Sur
tab_cont <- table(df$zona[df$zona %in% c("Zona Norte","Zona Oeste","Zona Sur")],
df$estrato[df$zona %in% c("Zona Norte","Zona Oeste","Zona Sur")])
# Eliminar filas o columnas vacías (aunque aquí todas tienen datos)
tab_cont <- tab_cont[rowSums(tab_cont) > 0, colSums(tab_cont) > 0]
# Análisis de Correspondencias
res_ca <- CA(tab_cont, graph = FALSE)
# Biplot
fviz_ca_biplot(res_ca, repel = TRUE,
col.row = "blue", # Zonas
col.col = "red", # Estratos
title = "Biplot de Correspondencias: Zonas Norte, Oeste y Sur vs Estrato")
El biplot del análisis de correspondencias confirma una asociación estructural clara entre las zonas y los estratos socioeconómicos. La Dimensión 1 explica el 73.5% de la inercia total, por lo que concentra la mayor parte de la relación entre las variables, mientras que la Dimensión 2 (26.5%) complementa la diferenciación. Se observa que la Zona Norte se ubica próxima a los estratos medios y altos (especialmente 3 y 5), indicando una correspondencia positiva con niveles socioeconómicos superiores. La Zona Sur aparece más asociada a estratos intermedios-bajos, mientras que la Zona Oeste muestra afinidad con estratos específicos que la diferencian del resto, evidenciando patrones territoriales diferenciados. La separación espacial de los puntos en el plano factorial indica que la distribución del estrato no es homogénea entre zonas, sino que existe una segmentación socioespacial marcada, donde cada zona presenta un perfil socioeconómico característico.
A nivel integral, los resultados obtenidos mediante clustering jerárquico, análisis de correspondencias y análisis de componentes principales (PCA) son altamente consistentes y apuntan a una misma estructura latente en los datos. El PCA identifica dos componentes principales que explican el 82.7 % de la variabilidad total, lo que evidencia que la información esencial puede resumirse en un espacio de baja dimensión dominado por dos factores fundamentales. De manera concordante, el análisis de correspondencias muestra que la primera dimensión concentra la mayor parte de la inercia, revelando una oposición clara entre perfiles territoriales y socioeconómicos. Esta estructura casi dicotómica se ve reforzada por el clustering jerárquico, donde la solución de dos clusters presenta la mayor estabilidad y separación, mientras que una partición en tres clusters emerge como una subdivisión secundaria de uno de los grupos principales. En conjunto, estos resultados confirman la existencia de dos grandes perfiles estructurales bien definidos, con variaciones internas que pueden refinarse, pero sin alterar la organización fundamental de los datos, lo que otorga solidez estadística y coherencia interpretativa al análisis multivariado realizado.