knitr::opts_chunk$set(
echo = TRUE,
warning = FALSE,
message = FALSE,
fig.align = 'center',
comment = NA
)# Instalación y carga de librerías necesarias
# devtools::install_github("centromagis/paqueteMODELOS", force = TRUE)
library(paqueteMODELOS)
library(tidyverse)
library(FactoMineR)
library(factoextra)
library(cluster)
library(ggplot2)
library(corrplot)
library(knitr)
library(kableExtra)
library(gridExtra)
library(RColorBrewer)
library(plotly)El presente informe presenta un análisis multidimensional exhaustivo del mercado inmobiliario urbano, aplicando técnicas estadísticas avanzadas para identificar patrones, segmentaciones y relaciones entre las características de las propiedades residenciales.
Objetivos principales:
Metodología: Se emplearon técnicas de análisis multivariado sobre una base de datos comprensiva de propiedades residenciales, utilizando el lenguaje R y diversas librerías especializadas en análisis estadístico.
El mercado inmobiliario urbano se caracteriza por su complejidad y dinamismo, donde múltiples factores influyen en la valoración y demanda de propiedades. Para una empresa inmobiliaria líder, comprender estos patrones es fundamental para:
El análisis de datos aplicado al sector inmobiliario permite transformar grandes volúmenes de información en conocimiento accionable, proporcionando ventajas competitivas significativas en un mercado altamente competitivo.
Objetivo General: Realizar un análisis integral y multidimensional de la base de datos de propiedades residenciales para obtener una comprensión profunda del mercado inmobiliario urbano.
Objetivos Específicos:
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>
Dimensiones del dataset:
Número de observaciones: 8322
Número de variables: 13
# Primeras observaciones
kable(head(vivienda, 10),
caption = "Primeras 10 observaciones del dataset") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE,
font_size = 12)| id | zona | piso | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | barrio | longitud | latitud |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1147 | Zona Oriente | NA | 3 | 250 | 70 | 1 | 3 | 6 | Casa | 20 de julio | -76.51168 | 3.43382 |
| 1169 | Zona Oriente | NA | 3 | 320 | 120 | 1 | 2 | 3 | Casa | 20 de julio | -76.51237 | 3.43369 |
| 1350 | Zona Oriente | NA | 3 | 350 | 220 | 2 | 2 | 4 | Casa | 20 de julio | -76.51537 | 3.43566 |
| 5992 | Zona Sur | 02 | 4 | 400 | 280 | 3 | 5 | 3 | Casa | 3 de julio | -76.54000 | 3.43500 |
| 1212 | Zona Norte | 01 | 5 | 260 | 90 | 1 | 2 | 3 | Apartamento | acopi | -76.51350 | 3.45891 |
| 1724 | Zona Norte | 01 | 5 | 240 | 87 | 1 | 3 | 3 | Apartamento | acopi | -76.51700 | 3.36971 |
| 2326 | Zona Norte | 01 | 4 | 220 | 52 | 2 | 2 | 3 | Apartamento | acopi | -76.51974 | 3.42627 |
| 4386 | Zona Norte | 01 | 5 | 310 | 137 | 2 | 3 | 4 | Apartamento | acopi | -76.53105 | 3.38296 |
| 1209 | Zona Norte | 02 | 5 | 320 | 150 | 2 | 4 | 6 | Casa | acopi | -76.51341 | 3.47968 |
| 1592 | Zona Norte | 02 | 5 | 780 | 380 | 2 | 3 | 3 | Casa | acopi | -76.51674 | 3.48721 |
# Resumen estadístico de variables numéricas
summary(vivienda) %>%
kable(caption = "Resumen estadístico de las variables") %>%
kable_styling(bootstrap_options = c("striped", "hover"))| id | zona | piso | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | barrio | longitud | latitud | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Min. : 1 | Length:8322 | Length:8322 | Min. :3.000 | Min. : 58.0 | Min. : 30.0 | Min. : 1.000 | Min. : 0.000 | Min. : 0.000 | Length:8322 | Length:8322 | Min. :-76.59 | Min. :3.333 | |
| 1st Qu.:2080 | Class :character | Class :character | 1st Qu.:4.000 | 1st Qu.: 220.0 | 1st Qu.: 80.0 | 1st Qu.: 1.000 | 1st Qu.: 2.000 | 1st Qu.: 3.000 | Class :character | Class :character | 1st Qu.:-76.54 | 1st Qu.:3.381 | |
| Median :4160 | Mode :character | Mode :character | Median :5.000 | Median : 330.0 | Median : 123.0 | Median : 2.000 | Median : 3.000 | Median : 3.000 | Mode :character | Mode :character | Median :-76.53 | Median :3.416 | |
| Mean :4160 | NA | NA | Mean :4.634 | Mean : 433.9 | Mean : 174.9 | Mean : 1.835 | Mean : 3.111 | Mean : 3.605 | NA | NA | Mean :-76.53 | Mean :3.418 | |
| 3rd Qu.:6240 | NA | NA | 3rd Qu.:5.000 | 3rd Qu.: 540.0 | 3rd Qu.: 229.0 | 3rd Qu.: 2.000 | 3rd Qu.: 4.000 | 3rd Qu.: 4.000 | NA | NA | 3rd Qu.:-76.52 | 3rd Qu.:3.452 | |
| Max. :8319 | NA | NA | Max. :6.000 | Max. :1999.0 | Max. :1745.0 | Max. :10.000 | Max. :10.000 | Max. :10.000 | NA | NA | Max. :-76.46 | Max. :3.498 | |
| NA’s :3 | NA | NA | NA’s :3 | NA’s :2 | NA’s :3 | NA’s :1605 | NA’s :3 | NA’s :3 | NA | NA | NA’s :3 | NA’s :3 |
En esta sección se presenta una primera aproximación a los datos, identificando:
# Análisis de valores faltantes
valores_na <- colSums(is.na(vivienda))
df_na <- data.frame(
Variable = names(valores_na),
Valores_NA = valores_na,
Porcentaje = round(valores_na / nrow(vivienda) * 100, 2)
)
df_na %>%
filter(Valores_NA > 0) %>%
kable(caption = "Variables con valores faltantes") %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Variable | Valores_NA | Porcentaje | |
|---|---|---|---|
| id | id | 3 | 0.04 |
| zona | zona | 3 | 0.04 |
| piso | piso | 2638 | 31.70 |
| estrato | estrato | 3 | 0.04 |
| preciom | preciom | 2 | 0.02 |
| areaconst | areaconst | 3 | 0.04 |
| parqueaderos | parqueaderos | 1605 | 19.29 |
| banios | banios | 3 | 0.04 |
| habitaciones | habitaciones | 3 | 0.04 |
| tipo | tipo | 3 | 0.04 |
| barrio | barrio | 3 | 0.04 |
| longitud | longitud | 3 | 0.04 |
| latitud | latitud | 3 | 0.04 |
# Identificar variables numéricas
vars_numericas <- vivienda %>%
select_if(is.numeric) %>%
names()
cat("Variables numéricas identificadas:\n")Variables numéricas identificadas:
[1] "id" "estrato" "preciom" "areaconst" "parqueaderos"
[6] "banios" "habitaciones" "longitud" "latitud"
# Identificar variables categóricas
vars_categoricas <- vivienda %>%
select_if(function(x) is.factor(x) | is.character(x)) %>%
names()
cat("\nVariables categóricas identificadas:\n")
Variables categóricas identificadas:
[1] "zona" "piso" "tipo" "barrio"
# Distribución de variables categóricas principales
if(length(vars_categoricas) > 0) {
plots_cat <- list()
for(i in 1:min(4, length(vars_categoricas))) {
var <- vars_categoricas[i]
p <- ggplot(vivienda, aes_string(x = var)) +
geom_bar(fill = "steelblue", alpha = 0.7) +
theme_minimal() +
labs(title = paste("Distribución de", var),
x = var, y = "Frecuencia") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
plots_cat[[i]] <- p
}
do.call(grid.arrange, c(plots_cat, ncol = 2))
}# Histogramas de variables numéricas
if(length(vars_numericas) > 0) {
plots_num <- list()
for(i in 1:min(6, length(vars_numericas))) {
var <- vars_numericas[i]
p <- ggplot(vivienda, aes_string(x = var)) +
geom_histogram(fill = "coral", alpha = 0.7, bins = 30) +
theme_minimal() +
labs(title = paste("Distribución de", var),
x = var, y = "Frecuencia")
plots_num[[i]] <- p
}
do.call(grid.arrange, c(plots_num, ncol = 2))
}Los datos presentan las siguientes características fundamentales:
El Análisis de Componentes Principales es una técnica de reducción de dimensionalidad que transforma un conjunto de variables posiblemente correlacionadas en un conjunto menor de variables no correlacionadas llamadas componentes principales. Esta técnica permite:
# Seleccionar solo variables numéricas para PCA
datos_numericos <- vivienda %>%
select_if(is.numeric) %>%
na.omit() # Eliminar filas con NA
# Mostrar correlaciones
matriz_cor <- cor(datos_numericos)
corrplot(matriz_cor,
method = "color",
type = "upper",
tl.col = "black",
tl.srt = 45,
addCoef.col = "black",
number.cex = 0.7,
title = "Matriz de Correlación de Variables Numéricas",
mar = c(0,0,1,0))La matriz de correlación revela:
# Ejecutar PCA
pca_resultado <- PCA(datos_numericos,
scale.unit = TRUE, # Estandarizar variables
graph = FALSE)
# Resumen del PCA
summary(pca_resultado)
Call:
PCA(X = datos_numericos, scale.unit = TRUE, graph = FALSE)
Eigenvalues
Dim.1 Dim.2 Dim.3 Dim.4 Dim.5 Dim.6 Dim.7
Variance 3.853 1.884 1.019 0.936 0.481 0.357 0.244
% of var. 42.806 20.930 11.322 10.396 5.349 3.966 2.714
Cumulative % of var. 42.806 63.736 75.058 85.455 90.804 94.770 97.483
Dim.8 Dim.9
Variance 0.188 0.039
% of var. 2.086 0.431
Cumulative % of var. 99.569 100.000
Individuals (the 10 first)
Dist Dim.1 ctr cos2 Dim.2 ctr cos2 Dim.3
1 | 3.446 | -1.934 0.014 0.315 | 2.208 0.039 0.410 | 1.259
2 | 3.006 | -2.508 0.024 0.696 | 1.218 0.012 0.164 | 0.121
3 | 2.769 | -1.612 0.010 0.339 | 1.724 0.023 0.388 | 0.416
4 | 2.272 | 1.033 0.004 0.207 | 0.064 0.000 0.001 | 0.770
5 | 2.535 | -2.026 0.016 0.639 | 0.672 0.004 0.070 | -0.506
6 | 2.202 | -1.429 0.008 0.421 | 0.260 0.001 0.014 | -1.208
7 | 2.116 | -1.853 0.013 0.767 | 0.376 0.001 0.032 | -0.266
8 | 1.025 | -0.145 0.000 0.020 | -0.213 0.000 0.043 | -0.230
9 | 2.987 | -0.370 0.001 0.015 | 2.267 0.041 0.576 | 0.677
10 | 2.842 | 0.126 0.000 0.002 | 1.605 0.020 0.319 | -0.437
ctr cos2
1 0.023 0.134 |
2 0.000 0.002 |
3 0.003 0.023 |
4 0.009 0.115 |
5 0.004 0.040 |
6 0.021 0.301 |
7 0.001 0.016 |
8 0.001 0.050 |
9 0.007 0.051 |
10 0.003 0.024 |
Variables
Dim.1 ctr cos2 Dim.2 ctr cos2 Dim.3 ctr
id | 0.571 8.463 0.326 | -0.722 27.644 0.521 | 0.360 12.736
estrato | 0.630 10.290 0.396 | -0.338 6.049 0.114 | -0.452 20.046
preciom | 0.865 19.419 0.748 | 0.103 0.564 0.011 | -0.220 4.755
areaconst | 0.763 15.124 0.583 | 0.417 9.251 0.174 | 0.070 0.483
parqueaderos | 0.765 15.201 0.586 | 0.180 1.722 0.032 | -0.235 5.399
banios | 0.818 17.387 0.670 | 0.324 5.584 0.105 | 0.075 0.556
habitaciones | 0.459 5.466 0.211 | 0.569 17.193 0.324 | 0.495 24.091
longitud | -0.544 7.671 0.296 | 0.743 29.308 0.552 | -0.355 12.367
latitud | -0.194 0.980 0.038 | 0.225 2.685 0.051 | 0.447 19.566
cos2
id 0.130 |
estrato 0.204 |
preciom 0.048 |
areaconst 0.005 |
parqueaderos 0.055 |
banios 0.006 |
habitaciones 0.245 |
longitud 0.126 |
latitud 0.199 |
# Valores propios (eigenvalues)
eigenvalues <- get_eigenvalue(pca_resultado)
kable(eigenvalues,
caption = "Valores propios y varianza explicada",
digits = 2) %>%
kable_styling(bootstrap_options = c("striped", "hover"))| eigenvalue | variance.percent | cumulative.variance.percent | |
|---|---|---|---|
| Dim.1 | 3.85 | 42.81 | 42.81 |
| Dim.2 | 1.88 | 20.93 | 63.74 |
| Dim.3 | 1.02 | 11.32 | 75.06 |
| Dim.4 | 0.94 | 10.40 | 85.45 |
| Dim.5 | 0.48 | 5.35 | 90.80 |
| Dim.6 | 0.36 | 3.97 | 94.77 |
| Dim.7 | 0.24 | 2.71 | 97.48 |
| Dim.8 | 0.19 | 2.09 | 99.57 |
| Dim.9 | 0.04 | 0.43 | 100.00 |
# Scree plot
fviz_eig(pca_resultado,
addlabels = TRUE,
ylim = c(0, 50),
main = "Scree Plot - Varianza Explicada por Componente",
xlab = "Componentes Principales",
ylab = "Porcentaje de Varianza Explicada") +
theme_minimal() +
geom_hline(yintercept = 100/ncol(datos_numericos),
linetype = "dashed",
color = "red")Según el criterio de Kaiser, se retienen los componentes con eigenvalues > 1. El scree plot muestra un “codo” que sugiere el número óptimo de componentes a retener. La línea roja representa el promedio de varianza si todas las componentes fueran igualmente importantes.
# Contribución de las variables a las primeras componentes
fviz_contrib(pca_resultado,
choice = "var",
axes = 1,
top = 10,
title = "Contribución de Variables a PC1") +
theme_minimal()fviz_contrib(pca_resultado,
choice = "var",
axes = 2,
top = 10,
title = "Contribución de Variables a PC2") +
theme_minimal()# Biplot
fviz_pca_biplot(pca_resultado,
repel = TRUE,
col.var = "contrib",
gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
col.ind = "gray",
alpha.ind = 0.3,
title = "Biplot PCA - Variables e Individuos") +
theme_minimal()# Círculo de correlaciones
fviz_pca_var(pca_resultado,
col.var = "contrib",
gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
repel = TRUE,
title = "Círculo de Correlaciones - Variables") +
theme_minimal()# Calidad de representación (cos2)
var_cos2 <- get_pca_var(pca_resultado)$cos2
# Top variables mejor representadas en PC1 y PC2
cos2_pc1_pc2 <- var_cos2[, 1:2]
total_cos2 <- rowSums(cos2_pc1_pc2)
top_vars <- head(sort(total_cos2, decreasing = TRUE), 10)
data.frame(
Variable = names(top_vars),
Cos2_Total = round(top_vars, 3)
) %>%
kable(caption = "Variables mejor representadas en PC1 y PC2") %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Variable | Cos2_Total | |
|---|---|---|
| longitud | longitud | 0.848 |
| id | id | 0.847 |
| banios | banios | 0.775 |
| preciom | preciom | 0.759 |
| areaconst | areaconst | 0.757 |
| parqueaderos | parqueaderos | 0.618 |
| habitaciones | habitaciones | 0.534 |
| estrato | estrato | 0.510 |
| latitud | latitud | 0.088 |
Componentes Principales Identificadas:
Hallazgos Clave:
Implicaciones para el Mercado Inmobiliario:
El análisis de conglomerados permite agrupar propiedades con características similares, identificando segmentos naturales en el mercado inmobiliario. Esto facilita:
# Estandarizar datos para clustering
datos_escalados <- scale(datos_numericos)
# Convertir a data frame
datos_clustering <- as.data.frame(datos_escalados)# Método del codo para determinar número óptimo de clusters
set.seed(123)
wss <- sapply(1:10, function(k) {
kmeans(datos_clustering, centers = k, nstart = 25)$tot.withinss
})
# Gráfico del método del codo
plot_codo <- data.frame(
k = 1:10,
wss = wss
) %>%
ggplot(aes(x = k, y = wss)) +
geom_line(color = "steelblue", size = 1) +
geom_point(color = "steelblue", size = 3) +
theme_minimal() +
labs(title = "Método del Codo - Determinación de K Óptimo",
x = "Número de Clusters (k)",
y = "Suma de Cuadrados Intra-cluster") +
scale_x_continuous(breaks = 1:10)
print(plot_codo)# Método de Silueta
fviz_nbclust(datos_clustering,
kmeans,
method = "silhouette",
k.max = 10) +
theme_minimal() +
labs(title = "Método de Silueta - Número Óptimo de Clusters")# Gap Statistic - Versión Optimizada
set.seed(123)
# Reducir el tamaño de la muestra si el dataset es muy grande
if(nrow(datos_clustering) > 1000) {
muestra_indices <- sample(1:nrow(datos_clustering), 1000)
datos_gap <- datos_clustering[muestra_indices, ]
} else {
datos_gap <- datos_clustering
}
# Calcular Gap Statistic con parámetros optimizados
gap_stat <- clusGap(datos_gap,
FUN = kmeans,
nstart = 25,
K.max = 8, # Reducir K.max
B = 100, # Aumentar B para mejor estimación
iter.max = 50) # Limitar iteraciones
# Visualización mejorada
fviz_gap_stat(gap_stat) +
theme_minimal() +
labs(title = "Gap Statistic - Número Óptimo de Clusters",
subtitle = "Criterio: Primer K donde Gap(k) >= Gap(k+1) - se(k+1)",
x = "Número de Clusters (k)",
y = "Gap Statistic") +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
plot.subtitle = element_text(hjust = 0.5))
Resultados del Gap Statistic:
Clustering Gap statistic ["clusGap"] from call:
clusGap(x = datos_gap, FUNcluster = kmeans, K.max = 8, B = 100, nstart = 25, iter.max = 50)
B=100 simulated reference sets, k = 1..8; spaceH0="scaledPCA"
--> Number of clusters (method 'firstSEmax', SE.factor=1): 8
logW E.logW gap SE.sim
[1,] 6.903992 7.800000 0.8960077 0.006265464
[2,] 6.736034 7.671368 0.9353335 0.005550987
[3,] 6.629714 7.616628 0.9869149 0.005416204
[4,] 6.564516 7.569861 1.0053441 0.005469715
[5,] 6.513386 7.534497 1.0211115 0.005597158
[6,] 6.455823 7.502732 1.0469083 0.005343079
[7,] 6.409134 7.475433 1.0662995 0.005264446
[8,] 6.375589 7.450567 1.0749778 0.005112401
# Número óptimo según criterio firstSEmax
k_optimo_gap <- maxSE(gap_stat$Tab[, "gap"],
gap_stat$Tab[, "SE.sim"],
method = "firstSEmax")
cat("\nNúmero óptimo de clusters según Gap Statistic:", k_optimo_gap, "\n")
Número óptimo de clusters según Gap Statistic: 8
Se utilizaron tres métodos complementarios:
Decisión: Basándonos en los tres métodos, se seleccionan [K] clusters para el análisis.
# K-means con el número óptimo de clusters
set.seed(123)
k_optimo <- 4 # Ajustar según resultados anteriores
kmeans_resultado <- kmeans(datos_clustering,
centers = k_optimo,
nstart = 25,
iter.max = 100)
# Agregar clusters al dataset original
vivienda_clustering <- datos_numericos
vivienda_clustering$cluster <- as.factor(kmeans_resultado$cluster)# Tamaño de cada cluster
table(kmeans_resultado$cluster) %>%
as.data.frame() %>%
rename(Cluster = Var1, Cantidad = Freq) %>%
mutate(Porcentaje = round(Cantidad / sum(Cantidad) * 100, 2)) %>%
kable(caption = "Distribución de Propiedades por Cluster") %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Cluster | Cantidad | Porcentaje |
|---|---|---|
| 1 | 2539 | 37.80 |
| 2 | 746 | 11.11 |
| 3 | 989 | 14.72 |
| 4 | 2443 | 36.37 |
# Centros de los clusters
centros <- as.data.frame(kmeans_resultado$centers)
centros$Cluster <- paste("Cluster", 1:k_optimo)
kable(centros,
caption = "Centros de los Clusters (valores estandarizados)",
digits = 3) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
font_size = 11) %>%
scroll_box(width = "100%")| id | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | longitud | latitud | Cluster |
|---|---|---|---|---|---|---|---|---|---|
| 0.773 | 0.354 | -0.142 | -0.281 | -0.127 | -0.136 | -0.287 | -0.707 | -0.138 | Cluster 1 |
| -0.347 | -0.602 | 0.053 | 0.900 | 0.058 | 0.918 | 1.837 | 0.322 | 0.373 | Cluster 2 |
| 0.586 | 1.002 | 1.876 | 1.415 | 1.606 | 1.302 | 0.451 | -0.527 | -0.376 | Cluster 3 |
| -0.934 | -0.589 | -0.628 | -0.556 | -0.536 | -0.666 | -0.445 | 0.850 | 0.182 | Cluster 4 |
# Visualización de clusters en espacio PCA
fviz_cluster(kmeans_resultado,
data = datos_clustering,
palette = "jco",
ellipse.type = "convex",
star.plot = TRUE,
repel = TRUE,
ggtheme = theme_minimal(),
main = "Visualización de Clusters en Espacio PCA")# Visualización 3D interactiva (opcional)
library(plotly)
pca_coords <- pca_resultado$ind$coord[, 1:3]
pca_df <- as.data.frame(pca_coords)
pca_df$cluster <- as.factor(kmeans_resultado$cluster)
plot_ly(pca_df,
x = ~Dim.1,
y = ~Dim.2,
z = ~Dim.3,
color = ~cluster,
type = "scatter3d",
mode = "markers",
marker = list(size = 5)) %>%
layout(title = "Clusters en Espacio 3D PCA",
scene = list(
xaxis = list(title = "PC1"),
yaxis = list(title = "PC2"),
zaxis = list(title = "PC3")
))# Perfil de clusters - valores promedio de cada variable
centros_originales <- aggregate(. ~ cluster,
data = vivienda_clustering,
FUN = mean)
# Gráfico de perfiles paralelos
centros_long <- centros_originales %>%
pivot_longer(cols = -cluster,
names_to = "Variable",
values_to = "Valor")
ggplot(centros_long, aes(x = Variable, y = Valor,
group = cluster, color = cluster)) +
geom_line(size = 1) +
geom_point(size = 2) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5)) +
labs(title = "Perfil de Clusters - Coordenadas Paralelas",
x = "Variables",
y = "Valor Promedio",
color = "Cluster") +
scale_color_brewer(palette = "Set1")# Caracterización detallada por cluster
for(i in 1:k_optimo) {
cat("\n### Cluster", i, "\n\n")
datos_cluster <- vivienda_clustering %>%
filter(cluster == i) %>%
select(-cluster)
cat("**Tamaño del cluster:**", nrow(datos_cluster), "propiedades\n\n")
cat("**Estadísticas descriptivas:**\n\n")
resumen <- summary(datos_cluster)
print(resumen)
cat("\n")
}
### Cluster 1
**Tamaño del cluster:** 2539 propiedades
**Estadísticas descriptivas:**
id estrato preciom areaconst
Min. :1854 Min. :3.000 Min. : 78.0 Min. : 43.0
1st Qu.:5143 1st Qu.:5.000 1st Qu.: 290.0 1st Qu.: 95.0
Median :6347 Median :5.000 Median : 395.0 Median :126.0
Mean :6209 Mean :5.166 Mean : 421.4 Mean :140.6
3rd Qu.:7387 3rd Qu.:6.000 3rd Qu.: 548.0 3rd Qu.:170.0
Max. :8318 Max. :6.000 Max. :1106.0 Max. :660.0
parqueaderos banios habitaciones longitud
Min. :1.000 Min. :0.000 Min. :0.000 Min. :-76.59
1st Qu.:1.000 1st Qu.:2.000 1st Qu.:3.000 1st Qu.:-76.55
Median :2.000 Median :3.000 Median :3.000 Median :-76.54
Mean :1.693 Mean :3.068 Mean :3.219 Mean :-76.54
3rd Qu.:2.000 3rd Qu.:4.000 3rd Qu.:4.000 3rd Qu.:-76.53
Max. :4.000 Max. :6.000 Max. :6.000 Max. :-76.52
latitud
Min. :3.333
1st Qu.:3.383
Median :3.408
Mean :3.409
3rd Qu.:3.445
Max. :3.493
### Cluster 2
**Tamaño del cluster:** 746 propiedades
**Estadísticas descriptivas:**
id estrato preciom areaconst
Min. : 2 Min. :3.000 Min. : 150.0 Min. : 70.0
1st Qu.:1838 1st Qu.:4.000 1st Qu.: 360.0 1st Qu.: 222.2
Median :3522 Median :4.000 Median : 450.0 Median : 300.0
Mean :3606 Mean :4.259 Mean : 486.6 Mean : 310.8
3rd Qu.:5131 3rd Qu.:5.000 3rd Qu.: 588.8 3rd Qu.: 375.0
Max. :8242 Max. :6.000 Max. :1500.0 Max. :1745.0
parqueaderos banios habitaciones longitud
Min. :1.000 Min. : 0.000 Min. : 2.000 Min. :-76.56
1st Qu.:1.000 1st Qu.: 4.000 1st Qu.: 5.000 1st Qu.:-76.53
Median :2.000 Median : 4.000 Median : 6.000 Median :-76.53
Mean :1.901 Mean : 4.523 Mean : 6.117 Mean :-76.53
3rd Qu.:2.000 3rd Qu.: 5.000 3rd Qu.: 7.000 3rd Qu.:-76.52
Max. :7.000 Max. :10.000 Max. :10.000 Max. :-76.46
latitud
Min. :3.338
1st Qu.:3.401
Median :3.432
Mean :3.431
3rd Qu.:3.460
Max. :3.494
### Cluster 3
**Tamaño del cluster:** 989 propiedades
**Estadísticas descriptivas:**
id estrato preciom areaconst
Min. : 4 Min. :3.000 Min. : 299 Min. : 138.0
1st Qu.:4838 1st Qu.:6.000 1st Qu.: 850 1st Qu.: 258.0
Median :5863 Median :6.000 Median :1050 Median : 330.0
Mean :5776 Mean :5.782 Mean :1098 Mean : 385.1
3rd Qu.:6927 3rd Qu.:6.000 3rd Qu.:1300 3rd Qu.: 450.0
Max. :8319 Max. :6.000 Max. :1999 Max. :1600.0
parqueaderos banios habitaciones longitud
Min. : 1.000 Min. : 0.000 Min. : 0.000 Min. :-76.59
1st Qu.: 3.000 1st Qu.: 4.000 1st Qu.: 3.000 1st Qu.:-76.55
Median : 3.000 Median : 5.000 Median : 4.000 Median :-76.54
Mean : 3.642 Mean : 5.053 Mean : 4.226 Mean :-76.54
3rd Qu.: 4.000 3rd Qu.: 6.000 3rd Qu.: 5.000 3rd Qu.:-76.53
Max. :10.000 Max. :10.000 Max. :10.000 Max. :-76.46
latitud
Min. :3.333
1st Qu.:3.355
Median :3.386
Mean :3.399
3rd Qu.:3.450
Max. :3.493
### Cluster 4
**Tamaño del cluster:** 2443 propiedades
**Estadísticas descriptivas:**
id estrato preciom areaconst
Min. : 1 Min. :3.000 Min. : 58.0 Min. : 30.0
1st Qu.:1317 1st Qu.:4.000 1st Qu.: 180.0 1st Qu.: 69.0
Median :2233 Median :4.000 Median : 250.0 Median : 85.0
Mean :2242 Mean :4.271 Mean : 258.3 Mean :101.1
3rd Qu.:3076 3rd Qu.:5.000 3rd Qu.: 315.0 3rd Qu.:110.0
Max. :6282 Max. :6.000 Max. :1200.0 Max. :550.0
parqueaderos banios habitaciones longitud
Min. : 1.000 Min. :0.000 Min. :0.000 Min. :-76.54
1st Qu.: 1.000 1st Qu.:2.000 1st Qu.:3.000 1st Qu.:-76.52
Median : 1.000 Median :2.000 Median :3.000 Median :-76.52
Mean : 1.232 Mean :2.336 Mean :3.003 Mean :-76.52
3rd Qu.: 1.000 3rd Qu.:3.000 3rd Qu.:3.000 3rd Qu.:-76.52
Max. :10.000 Max. :5.000 Max. :6.000 Max. :-76.46
latitud
Min. :3.343
1st Qu.:3.376
Median :3.413
Mean :3.423
3rd Qu.:3.471
Max. :3.498
Cluster 1: [Nombre descriptivo] - Tamaño: [N] propiedades ([%]%) - Características distintivas: - [Característica 1] - [Característica 2] - Interpretación: Este segmento representa [descripción del tipo de propiedad] - Estrategia recomendada: [Recomendación específica]
Cluster 2: [Nombre descriptivo] - Tamaño: [N] propiedades ([%]%) - Características distintivas: - [Característica 1] - [Característica 2] - Interpretación: [Descripción] - Estrategia recomendada: [Recomendación]
[Repetir para todos los clusters]
El Análisis de Correspondencia es una técnica para explorar relaciones entre variables categóricas, permitiendo visualizar asociaciones en un espacio de baja dimensión. En el contexto inmobiliario, permite identificar:
# Seleccionar variables categóricas relevantes
# Ajustar según las variables disponibles en tu dataset
vars_cat_analisis <- vivienda %>%
select_if(function(x) is.factor(x) | is.character(x)) %>%
select(1:2) # Ajustar según variables disponibles
# Verificar estructura
str(vars_cat_analisis)tibble [8,322 × 2] (S3: tbl_df/tbl/data.frame)
$ zona: chr [1:8322] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
$ piso: chr [1:8322] NA NA NA "02" ...
# Crear tabla de contingencia entre dos variables categóricas
# Ejemplo: tipo de vivienda vs zona (ajustar según tus variables)
if(ncol(vars_cat_analisis) >= 2) {
var1 <- names(vars_cat_analisis)[1]
var2 <- names(vars_cat_analisis)[2]
tabla_contingencia <- table(vars_cat_analisis[[var1]],
vars_cat_analisis[[var2]])
kable(tabla_contingencia,
caption = paste("Tabla de Contingencia:", var1, "vs", var2)) %>%
kable_styling(bootstrap_options = c("striped", "hover")) %>%
scroll_box(width = "100%")
}| 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Zona Centro | 33 | 16 | 8 | 5 | 5 | 0 | 0 | 0 | 0 | 0 | 2 | 0 |
| Zona Norte | 177 | 293 | 189 | 129 | 117 | 33 | 33 | 39 | 32 | 27 | 33 | 37 |
| Zona Oeste | 86 | 121 | 134 | 99 | 84 | 80 | 57 | 43 | 41 | 28 | 20 | 12 |
| Zona Oriente | 74 | 63 | 54 | 8 | 8 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| Zona Sur | 490 | 957 | 712 | 366 | 353 | 132 | 114 | 129 | 73 | 74 | 29 | 34 |
# Test de independencia Chi-cuadrado
if(ncol(vars_cat_analisis) >= 2) {
chi_test <- chisq.test(tabla_contingencia)
cat("Test de Independencia Chi-Cuadrado\n")
cat("===================================\n")
cat("Chi-cuadrado:", round(chi_test$statistic, 2), "\n")
cat("Grados de libertad:", chi_test$parameter, "\n")
cat("p-valor:", format.pval(chi_test$p.value), "\n\n")
if(chi_test$p.value < 0.05) {
cat("Conclusión: Existe asociación significativa entre las variables (p < 0.05)\n")
} else {
cat("Conclusión: No se rechaza la hipótesis de independencia (p >= 0.05)\n")
}
}Test de Independencia Chi-Cuadrado
===================================
Chi-cuadrado: 443.53
Grados de libertad: 44
p-valor: < 2.22e-16
Conclusión: Existe asociación significativa entre las variables (p < 0.05)
# Ejecutar Análisis de Correspondencia
if(ncol(vars_cat_analisis) >= 2) {
ca_resultado <- CA(tabla_contingencia, graph = FALSE)
# Resumen
summary(ca_resultado)
}
Call:
CA(X = tabla_contingencia, graph = FALSE)
The chi square of independence between the two variables is equal to 443.5327 (p-value = 1.934402e-67 ).
Eigenvalues
Dim.1 Dim.2 Dim.3 Dim.4
Variance 0.052 0.014 0.011 0.001
% of var. 66.060 18.078 14.467 1.395
Cumulative % of var. 66.060 84.138 98.605 100.000
Rows
Iner*1000 Dim.1 ctr cos2 Dim.2 ctr cos2 Dim.3
Zona Centro | 11.543 | -0.712 11.954 0.534 | 0.588 29.707 0.363 | -0.228
Zona Norte | 9.294 | -0.002 0.002 0.000 | 0.126 22.524 0.342 | 0.174
Zona Oeste | 30.436 | 0.431 51.128 0.866 | 0.105 11.045 0.051 | -0.133
Zona Oriente | 21.045 | -0.699 34.700 0.850 | 0.138 4.937 0.033 | -0.236
Zona Sur | 5.714 | -0.043 2.216 0.200 | -0.086 31.787 0.785 | -0.008
ctr cos2
Zona Centro 5.575 0.055 |
Zona Norte 53.827 0.654 |
Zona Oeste 22.208 0.082 |
Zona Oriente 18.076 0.097 |
Zona Sur 0.314 0.006 |
Columns (the 10 first)
Iner*1000 Dim.1 ctr cos2 Dim.2 ctr cos2 Dim.3
01 | 20.949 | -0.306 27.523 0.677 | 0.185 36.601 0.246 | -0.102
02 | 7.532 | -0.138 9.407 0.644 | -0.084 12.685 0.238 | 0.059
03 | 2.994 | -0.068 1.730 0.298 | -0.085 9.893 0.466 | -0.042
04 | 2.167 | 0.126 3.300 0.785 | -0.010 0.078 0.005 | 0.054
05 | 1.545 | 0.089 1.548 0.516 | -0.040 1.136 0.104 | 0.058
06 | 13.786 | 0.516 22.284 0.833 | 0.042 0.539 0.006 | -0.227
07 | 6.977 | 0.423 12.434 0.919 | 0.015 0.054 0.001 | -0.125
08 | 2.867 | 0.269 5.198 0.934 | -0.066 1.135 0.056 | 0.004
09 | 5.314 | 0.436 9.468 0.918 | 0.119 2.588 0.069 | -0.028
10 | 1.745 | 0.275 3.350 0.990 | 0.008 0.011 0.001 | 0.013
ctr cos2
01 14.019 0.076 |
02 7.893 0.118 |
03 3.046 0.115 |
04 2.743 0.143 |
05 2.946 0.215 |
06 19.641 0.161 |
07 4.957 0.080 |
08 0.005 0.000 |
09 0.182 0.004 |
10 0.033 0.002 |
# Valores propios del AC
if(exists("ca_resultado")) {
eigenvalues_ca <- get_eigenvalue(ca_resultado)
kable(eigenvalues_ca,
caption = "Valores propios - Análisis de Correspondencia",
digits = 2) %>%
kable_styling(bootstrap_options = c("striped", "hover"))
}| eigenvalue | variance.percent | cumulative.variance.percent | |
|---|---|---|---|
| Dim.1 | 0.05 | 66.06 | 66.06 |
| Dim.2 | 0.01 | 18.08 | 84.14 |
| Dim.3 | 0.01 | 14.47 | 98.61 |
| Dim.4 | 0.00 | 1.39 | 100.00 |
# Scree plot AC
if(exists("ca_resultado")) {
fviz_screeplot(ca_resultado,
addlabels = TRUE,
main = "Scree Plot - Análisis de Correspondencia") +
theme_minimal()
}# Biplot simétrico
if(exists("ca_resultado")) {
fviz_ca_biplot(ca_resultado,
repel = TRUE,
col.row = "steelblue",
col.col = "coral",
title = paste("Biplot AC:", var1, "vs", var2)) +
theme_minimal()
}# Contribución de las filas
if(exists("ca_resultado")) {
fviz_contrib(ca_resultado,
choice = "row",
axes = 1:2,
top = 10,
title = paste("Contribución de categorías de", var1)) +
theme_minimal()
}# Contribución de las columnas
if(exists("ca_resultado")) {
fviz_contrib(ca_resultado,
choice = "col",
axes = 1:2,
top = 10,
title = paste("Contribución de categorías de", var2)) +
theme_minimal()
}# Calidad de representación (cos2)
if(exists("ca_resultado")) {
# Filas
row_cos2 <- get_ca_row(ca_resultado)$cos2
fviz_cos2(ca_resultado,
choice = "row",
axes = 1:2,
top = 10,
title = "Calidad de Representación - Filas") +
theme_minimal()
# Columnas
col_cos2 <- get_ca_col(ca_resultado)$cos2
fviz_cos2(ca_resultado,
choice = "col",
axes = 1:2,
top = 10,
title = "Calidad de Representación - Columnas") +
theme_minimal()
}Dimensionalidad:
Patrones Identificados:
Implicaciones Estratégicas:
Cuando se dispone de más de dos variables categóricas, el Análisis de Correspondencia Múltiple permite analizar las relaciones entre todas ellas simultáneamente.
# Si hay más de 2 variables categóricas
if(ncol(vars_cat_analisis) > 2) {
# Ejecutar MCA
mca_resultado <- MCA(vars_cat_analisis, graph = FALSE)
# Resumen
summary(mca_resultado)
}# Scree plot MCA
if(exists("mca_resultado")) {
fviz_screeplot(mca_resultado,
addlabels = TRUE,
main = "Scree Plot - MCA") +
theme_minimal()
}En esta sección se integran los resultados de las tres técnicas aplicadas para obtener una visión holística del mercado inmobiliario.
# Visualizar clusters en espacio PCA con características
vivienda_completo <- datos_numericos
vivienda_completo$cluster <- kmeans_resultado$cluster
# Proyectar en PCA
pca_ind <- as.data.frame(pca_resultado$ind$coord[, 1:2])
pca_ind$cluster <- as.factor(vivienda_completo$cluster)
ggplot(pca_ind, aes(x = Dim.1, y = Dim.2, color = cluster)) +
geom_point(alpha = 0.6, size = 2) +
stat_ellipse(level = 0.95, size = 1) +
theme_minimal() +
labs(title = "Integración: Clusters en Espacio PCA",
x = paste0("PC1 (", round(eigenvalues[1, 2], 1), "%)"),
y = paste0("PC2 (", round(eigenvalues[2, 2], 1), "%)"),
color = "Cluster") +
scale_color_brewer(palette = "Set1")# Tabla resumen: características promedio por cluster
resumen_clusters <- vivienda_completo %>%
group_by(cluster) %>%
summarise(
n = n(),
across(where(is.numeric), mean, .names = "mean_{.col}")
)
kable(resumen_clusters,
caption = "Resumen de Características por Cluster",
digits = 2) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
font_size = 10) %>%
scroll_box(width = "100%", height = "400px")| cluster | n | mean_id | mean_estrato | mean_preciom | mean_areaconst | mean_parqueaderos | mean_banios | mean_habitaciones | mean_longitud | mean_latitud | mean_n |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 2539 | 6208.64 | 5.17 | 421.40 | 140.64 | 1.69 | 3.07 | 3.22 | -76.54 | 3.41 | 2539 |
| 2 | 746 | 3606.20 | 4.26 | 486.58 | 310.79 | 1.90 | 4.52 | 6.12 | -76.53 | 3.43 | 746 |
| 3 | 989 | 5775.61 | 5.78 | 1097.56 | 385.09 | 3.64 | 5.05 | 4.23 | -76.54 | 3.40 | 989 |
| 4 | 2443 | 2241.73 | 4.27 | 258.31 | 101.06 | 1.23 | 2.34 | 3.00 | -76.52 | 3.42 | 2443 |
Integración PCA + Clustering:
Integración Clustering + AC:
Modelo Integral del Mercado:
El mercado inmobiliario puede describirse mediante:
Presentación de visualizaciones integradas para comunicar hallazgos a stakeholders.
# Dashboard de clusters
library(gridExtra)
# Gráfico 1: Distribución de clusters
p1 <- ggplot(vivienda_completo, aes(x = cluster, fill = cluster)) +
geom_bar() +
theme_minimal() +
labs(title = "Distribución de Clusters",
x = "Cluster", y = "Frecuencia") +
scale_fill_brewer(palette = "Set1") +
theme(legend.position = "none")
# Gráfico 2: Boxplot de una variable clave por cluster
var_key <- names(datos_numericos)[1] # Ajustar según variable de interés
p2 <- ggplot(vivienda_completo, aes(x = cluster, y = .data[[var_key]], fill = cluster)) +
geom_boxplot() +
theme_minimal() +
labs(title = paste("Distribución de", var_key, "por Cluster"),
x = "Cluster", y = var_key) +
scale_fill_brewer(palette = "Set1") +
theme(legend.position = "none")
# Gráfico 3: Dispersión en PCA coloreada por cluster
p3 <- ggplot(pca_ind, aes(x = Dim.1, y = Dim.2, color = cluster)) +
geom_point(alpha = 0.5) +
theme_minimal() +
labs(title = "Clusters en Espacio PCA") +
scale_color_brewer(palette = "Set1")
# Combinar
grid.arrange(p1, p2, p3, ncol = 2, heights = c(1, 1.2))# Heatmap de características por cluster usando pheatmap
library(pheatmap)
# Preparar datos
centros_df <- as.data.frame(kmeans_resultado$centers)
rownames(centros_df) <- paste("Cluster", 1:nrow(centros_df))
# Normalizar para mejor visualización
centros_norm <- scale(t(centros_df))
centros_norm <- t(centros_norm)
# Crear heatmap profesional
pheatmap(centros_norm,
cluster_rows = FALSE,
cluster_cols = TRUE,
color = colorRampPalette(c("blue", "white", "red"))(100),
main = "Heatmap: Perfil de Clusters (Valores Estandarizados)",
fontsize = 10,
fontsize_row = 11,
fontsize_col = 9,
angle_col = 45,
display_numbers = FALSE,
border_color = "grey60",
cellwidth = 20,
cellheight = 30,
legend = TRUE,
breaks = seq(-3, 3, length.out = 101))El análisis de componentes principales reveló que:
El análisis de conglomerados identificó [K] segmentos claramente diferenciados:
Segmento 1: [Descripción] - Representa [%]% del mercado - Caracterizado por [características principales] - Oportunidad: [describir]
Segmento 2: [Descripción] - Representa [%]% del mercado - Caracterizado por [características principales] - Oportunidad: [describir]
[Continuar para todos los segmentos]
El análisis de correspondencia reveló:
Jolliffe, I. T., & Cadima, J. (2016). Principal component analysis: a review and recent developments. Philosophical Transactions of the Royal Society A, 374(2065), 20150202.
Kaufman, L., & Rousseeuw, P. J. (2009). Finding groups in data: an introduction to cluster analysis. John Wiley & Sons.
Greenacre, M. (2017). Correspondence analysis in practice. Chapman and Hall/CRC.
Husson, F., Lê, S., & Pagès, J. (2017). Exploratory multivariate analysis by example using R. CRC press.
James, G., Witten, D., Hastie, T., & Tibshirani, R. (2013). An introduction to statistical learning. Springer.
El código completo utilizado en este análisis está disponible.
Tabla detallada con descripción de cada variable del dataset
Pruebas de normalidad, homogeneidad de varianza, etc. si aplican.
Resultados con diferentes números de clusters, componentes, etc.
Para consultas sobre este análisis:
Fin del informe