Análisis integral y multidimensional

Planteamiento del problema

Una empresa inmobiliaria de la ciudad de Cali cuenta con una base de datos extensa sobre viviendas urbanas, pero requiere un análisis adecuado que le permita transformar esta información en conocimiento útil. La falta de un análisis integral limita la identificación de patrones, relaciones y segmentaciones relevantes, lo cual dificulta la toma de decisiones estratégicas en procesos de compra, venta y valoración de inmuebles. Por ello, se hace necesario aplicar técnicas de análisis de datos que apoyen una comprensión más profunda del mercado inmobiliario.

Objetivo

Analizar la base de datos de viviendas urbanas para identificar patrones y relaciones relevantes que apoyen la toma de decisiones estratégicas en la compra, venta y valoración de propiedades.

Fase 1: Análisis Exploratorio

Para iniciar el análisis, se llevó a cabo una exploración inicial de la base de datos denominada vivienda, con el propósito de comprender su estructura y contenido. En esta etapa se determinó la cantidad de registros y variables presentes en el conjunto de datos, así como los nombres de las variables que lo conforman. Asimismo, se verificó el tipo de datos de cada variable, se calcularon algunas medidas de tendencia central y de distribución, y se identificó la presencia de valores faltantes o registros ausentes. Finalmente, se procedió a la descarga de la base de datos vivienda para su posterior análisis.

#Librerias
library(paqueteMODELOS)
library(mice)
library(factoextra)
library(NbClust)
library(FactoMineR)

Se carga la base de datos.

#Base de datos
data("vivienda")
head(str(vivienda))
## spc_tbl_ [8,322 × 13] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ id          : num [1:8322] 1147 1169 1350 5992 1212 ...
##  $ zona        : chr [1:8322] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
##  $ piso        : chr [1:8322] NA NA NA "02" ...
##  $ estrato     : num [1:8322] 3 3 3 4 5 5 4 5 5 5 ...
##  $ preciom     : num [1:8322] 250 320 350 400 260 240 220 310 320 780 ...
##  $ areaconst   : num [1:8322] 70 120 220 280 90 87 52 137 150 380 ...
##  $ parqueaderos: num [1:8322] 1 1 2 3 1 1 2 2 2 2 ...
##  $ banios      : num [1:8322] 3 2 2 5 2 3 2 3 4 3 ...
##  $ habitaciones: num [1:8322] 6 3 4 3 3 3 3 4 6 3 ...
##  $ tipo        : chr [1:8322] "Casa" "Casa" "Casa" "Casa" ...
##  $ barrio      : chr [1:8322] "20 de julio" "20 de julio" "20 de julio" "3 de julio" ...
##  $ longitud    : num [1:8322] -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num [1:8322] 3.43 3.43 3.44 3.44 3.46 ...
##  - attr(*, "spec")=List of 3
##   ..$ cols   :List of 13
##   .. ..$ id          : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ zona        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ piso        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ estrato     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ preciom     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ areaconst   : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ parqueaderos: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ banios      : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ habitaciones: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ tipo        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ barrio      : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ longitud    : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ latitud     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   ..$ default: list()
##   .. ..- attr(*, "class")= chr [1:2] "collector_guess" "collector"
##   ..$ delim  : chr ";"
##   ..- attr(*, "class")= chr "col_spec"
##  - attr(*, "problems")=<externalptr>
## NULL

Se obtiene la cantidad de registros y variables que hay en la base de datos.

#Contar la cantidad de registros
cantidad_registros = nrow(vivienda)
#Imprimir el resultado
cat("La base de datos tiene", cantidad_registros, "registros.\n")
## La base de datos tiene 8322 registros.
#Contar la cantidad de variables
cantidad_variables = ncol(vivienda)
#Imprimir el resultado
cat("La base de datos tiene", cantidad_variables, "variables.\n")
## La base de datos tiene 13 variables.

Se obtienen los estadisticos descriptivos de la base de datos.

summarytools::descr(vivienda)
## Non-numerical variable(s) ignored: zona, piso, tipo, barrio
## Descriptive Statistics  
## vivienda  
## N: 8322  
## 
##                     areaconst    banios   estrato   habitaciones        id   latitud   longitud
## ----------------- ----------- --------- --------- -------------- --------- --------- ----------
##              Mean      174.93      3.11      4.63           3.61   4160.00      3.42     -76.53
##           Std.Dev      142.96      1.43      1.03           1.46   2401.63      0.04       0.02
##               Min       30.00      0.00      3.00           0.00      1.00      3.33     -76.59
##                Q1       80.00      2.00      4.00           3.00   2080.00      3.38     -76.54
##            Median      123.00      3.00      5.00           3.00   4160.00      3.42     -76.53
##                Q3      229.00      4.00      5.00           4.00   6240.00      3.45     -76.52
##               Max     1745.00     10.00      6.00          10.00   8319.00      3.50     -76.46
##               MAD       84.51      1.48      1.48           1.48   3083.81      0.05       0.02
##               IQR      149.00      2.00      1.00           1.00   4159.00      0.07       0.02
##                CV        0.82      0.46      0.22           0.40      0.58      0.01       0.00
##          Skewness        2.69      0.93     -0.18           1.63      0.00      0.03       0.65
##       SE.Skewness        0.03      0.03      0.03           0.03      0.03      0.03       0.03
##          Kurtosis       12.91      1.13     -1.11           3.98     -1.20     -1.15       0.58
##           N.Valid     8319.00   8319.00   8319.00        8319.00   8319.00   8319.00    8319.00
##                 N     8322.00   8322.00   8322.00        8322.00   8322.00   8322.00    8322.00
##         Pct.Valid       99.96     99.96     99.96          99.96     99.96     99.96      99.96
## 
## Table: Table continues below
## 
##  
## 
##                     parqueaderos   preciom
## ----------------- -------------- ---------
##              Mean           1.84    433.89
##           Std.Dev           1.12    328.65
##               Min           1.00     58.00
##                Q1           1.00    220.00
##            Median           2.00    330.00
##                Q3           2.00    540.00
##               Max          10.00   1999.00
##               MAD           1.48    207.56
##               IQR           1.00    320.00
##                CV           0.61      0.76
##          Skewness           2.33      1.85
##       SE.Skewness           0.03      0.03
##          Kurtosis           8.31      3.67
##           N.Valid        6717.00   8320.00
##                 N        8322.00   8322.00
##         Pct.Valid          80.71     99.98

Se visualizan los datos faltantes.

#verificar datos faltantes graficamente
md.pattern(vivienda, plot = TRUE, rotate.names = TRUE)

##      preciom id zona estrato areaconst banios habitaciones tipo barrio longitud
## 4808       1  1    1       1         1      1            1    1      1        1
## 1909       1  1    1       1         1      1            1    1      1        1
## 876        1  1    1       1         1      1            1    1      1        1
## 726        1  1    1       1         1      1            1    1      1        1
## 1          1  0    0       0         0      0            0    0      0        0
## 2          0  0    0       0         0      0            0    0      0        0
##            2  3    3       3         3      3            3    3      3        3
##      latitud parqueaderos piso     
## 4808       1            1    1    0
## 1909       1            1    0    1
## 876        1            0    1    1
## 726        1            0    0    2
## 1          0            0    0   12
## 2          0            0    0   13
##            3         1605 2638 4275

Análisis de Estadísticos preliminares:

La base de datos vivienda está compuesta por 13 variables y 8.322 registros. Entre las variables se identifican variables cualitativas de tipo nominal, como la zona de ubicación, el tipo de vivienda y el nombre del barrio; variables cuantitativas discretas, como el estrato, el número de habitaciones, el número de baños, el número de parqueaderos y el número de piso; y variables cuantitativas continuas, como el precio, el área construida y la ubicación geográfica representada por la latitud y la longitud. Adicionalmente, se observa que dos variables, piso y parqueaderos, presentan un porcentaje significativo de valores faltantes, superior al 19% en cada caso (1.605 y 2.638 registros faltantes, respectivamente). A esto se suma la presencia de tres registros faltantes adicionales en cada una de las variables de la base de datos.

El resumen estadístico permite identificar medidas de tendencia central como la media y la mediana. En variables de interés como el precio (en millones) y el área construida, la mediana se encuentra por debajo de la media, lo que sugiere una distribución asimétrica influenciada por valores elevados. Asimismo, se evidencia la presencia de datos atípicos en la mayoría de las variables numéricas, con excepción del estrato, la latitud y la longitud, ya que en las demás variables los valores máximos superan el límite superior definido por el tercer cuartil más 1.5 veces el rango intercuartílico (IQR). En cuanto a las medidas de dispersión, la desviación estándar presenta valores altos en relación con la media, lo cual indica una alta variabilidad y una amplia dispersión de los datos alrededor del valor central.

Con base en este panorama general de la base de datos, se procede a realizar las actividades de limpieza, imputación y estandarización de los datos, como etapa previa al desarrollo de análisis multivariados. En particular, se grafican las variables piso y parqueaderos con el fin de analizar sus medidas descriptivas —moda, media y mediana— y así seleccionar el estadístico más adecuado para la imputación de los valores faltantes.

#Se crea base de datos ordenada
vivienda_ordenada <- vivienda[order(vivienda$piso), ]
#tabla de frecuencia absoluta de atributo piso
frecuencia_abs = table(vivienda_ordenada$piso)

par(mfrow = c(1, 2))
#grafico de barras de frecuencia absoluta de piso
barplot(frecuencia_abs, beside = TRUE, legend = TRUE,
        main = "Frecuencia abs. de la variable Piso",
        xlab = "Categorías",
        ylab = "Frecuencia absoluta")

#grafico de cajas de parqueadero
boxplot(vivienda_ordenada$parqueaderos, 
        main = "Diagrama de Cajas de Parqueaderos", ylab = "Parqueaderos")

Al observar las gráficas descriptivas, se identifica que la variable piso presenta una mayor concentración de observaciones en los valores bajos, siendo el piso 2 el valor más frecuente. Dado que se trata de una variable discreta, se selecciona la moda como medida adecuada para la imputación de sus valores faltantes. En el caso de la variable parqueaderos, el diagrama de cajas muestra una distribución asimétrica con presencia de valores atípicos, lo que hace que la media no sea representativa. Por esta razón, se elige la mediana, cuyo valor es 2, como estadístico robusto para la imputación de los datos faltantes.

Con base en lo anterior, se realiza la imputación de los valores faltantes en las variables piso y parqueaderos, y posteriormente se generan nuevamente las gráficas para verificar que la base de datos no contiene registros faltantes.

Se realiza la imputación de los datos faltantes para los atributos piso y parqueadero. y se grafica para verificar que la base no tenga ningún dato faltante

#Imputación de datos
vivienda_imp <- vivienda
#Imputación de la variable piso por la moda
moda_piso <- as.numeric(
  names(which.max(table(vivienda_imp$piso)))
)

vivienda_imp$piso[is.na(vivienda_imp$piso)] <- moda_piso
#Imputación de la variable parqueadero por la mediana
mediana_parq <- median(vivienda_imp$parqueaderos, na.rm = TRUE)

vivienda_imp$parqueaderos[is.na(vivienda_imp$parqueaderos)] <- mediana_parq
#Verificación numerica
colSums(is.na(vivienda_imp))
##           id         zona         piso      estrato      preciom    areaconst 
##            3            3            0            3            2            3 
## parqueaderos       banios habitaciones         tipo       barrio     longitud 
##            0            3            3            3            3            3 
##      latitud 
##            3
#Verificación grafica de la imputacion
md.pattern(vivienda_imp, plot = TRUE, rotate.names = TRUE)

##      piso parqueaderos preciom id zona estrato areaconst banios habitaciones
## 8319    1            1       1  1    1       1         1      1            1
## 1       1            1       1  0    0       0         0      0            0
## 2       1            1       0  0    0       0         0      0            0
##         0            0       2  3    3       3         3      3            3
##      tipo barrio longitud latitud   
## 8319    1      1        1       1  0
## 1       0      0        0       0 10
## 2       0      0        0       0 11
##         3      3        3       3 32

Fase 2: Análisis de Componentes Principales

Con la base de datos depurada y lista para el análisis, se procede a la selección de las variables que serán utilizadas en el Análisis de Componentes Principales (PCA). En este caso, se consideran únicamente los atributos numéricos, incorporando la variable piso tratada como numérica nominal. Por otro lado, se excluyen las variables estrato, por corresponder a una variable categórica ordinal, así como id, longitud y latitud, dado que no aportan información relevante para los objetivos del análisis.

Posteriormente, se realiza la estandarización de las variables seleccionadas, con el fin de garantizar que todas se encuentren en una misma escala de medición y evitar que aquellas con mayor magnitud dominen el análisis.

# Selección de variables numéricas para el PCA
vivienda_final <- vivienda[, c(
  "preciom",
  "areaconst",
  "parqueaderos",
  "banios",
  "habitaciones",
  "piso"
)]
head(str(vivienda_final))
## spc_tbl_ [8,322 × 6] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ 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 ...
##  $ piso        : chr [1:8322] NA NA NA "02" ...
##  - attr(*, "spec")=List of 3
##   ..$ cols   :List of 13
##   .. ..$ id          : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ zona        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ piso        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ estrato     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ preciom     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ areaconst   : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ parqueaderos: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ banios      : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ habitaciones: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ tipo        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ barrio      : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ longitud    : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ latitud     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   ..$ default: list()
##   .. ..- attr(*, "class")= chr [1:2] "collector_guess" "collector"
##   ..$ delim  : chr ";"
##   ..- attr(*, "class")= chr "col_spec"
##  - attr(*, "problems")=<externalptr>
## NULL
#Conversión numerica de la variable piso
vivienda_final$piso <- as.numeric(vivienda_final$piso)
#Verificación de la conversión
head(str(vivienda_final))
## spc_tbl_ [8,322 × 6] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ 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 ...
##  $ piso        : num [1:8322] NA NA NA 2 1 1 1 1 2 2 ...
##  - attr(*, "spec")=List of 3
##   ..$ cols   :List of 13
##   .. ..$ id          : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ zona        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ piso        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ estrato     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ preciom     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ areaconst   : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ parqueaderos: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ banios      : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ habitaciones: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ tipo        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ barrio      : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ longitud    : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ latitud     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   ..$ default: list()
##   .. ..- attr(*, "class")= chr [1:2] "collector_guess" "collector"
##   ..$ delim  : chr ";"
##   ..- attr(*, "class")= chr "col_spec"
##  - attr(*, "problems")=<externalptr>
## NULL
#Estandarización de la base para analizar por PCA
viviendaz <- scale(vivienda_final)
head(viviendaz)
##         preciom  areaconst parqueaderos      banios habitaciones       piso
## [1,] -0.5595420 -0.7339949   -0.7424551 -0.07793773    1.6406840         NA
## [2,] -0.3465477 -0.3842568   -0.7424551 -0.77811479   -0.4147626         NA
## [3,] -0.2552644  0.3152194    0.1465058 -0.77811479    0.2703863         NA
## [4,] -0.1031256  0.7349051    1.0354668  1.32241640   -0.4147626 -0.6772734
## [5,] -0.5291143 -0.5940997   -0.7424551 -0.77811479   -0.4147626 -1.0597114
## [6,] -0.5899698 -0.6150839   -0.7424551 -0.07793773   -0.4147626 -1.0597114
#Imputación de la variable piso por la Moda = 2
vivienda_final$piso[is.na(vivienda_final$piso)] <- 2
#Verificación de la imputación
sum(is.na(vivienda_final$piso))
## [1] 0
#Estandarización de la base de datos corregida
viviendaz <- scale(vivienda_final)
head(viviendaz)
##         preciom  areaconst parqueaderos      banios habitaciones       piso
## [1,] -0.5595420 -0.7339949   -0.7424551 -0.07793773    1.6406840 -0.5230042
## [2,] -0.3465477 -0.3842568   -0.7424551 -0.77811479   -0.4147626 -0.5230042
## [3,] -0.2552644  0.3152194    0.1465058 -0.77811479    0.2703863 -0.5230042
## [4,] -0.1031256  0.7349051    1.0354668  1.32241640   -0.4147626 -0.5230042
## [5,] -0.5291143 -0.5940997   -0.7424551 -0.77811479   -0.4147626 -0.9553945
## [6,] -0.5899698 -0.6150839   -0.7424551 -0.07793773   -0.4147626 -0.9553945
#Verificación de datos faltantes
sum(is.na(viviendaz))
## [1] 1616
sum(is.infinite(viviendaz))
## [1] 0
#Eliminación de filas y columnas problema
viviendaz <- viviendaz[complete.cases(viviendaz), ]
#Verificación del ajuste
sum(is.na(viviendaz))
## [1] 0
#Gráficos del PCA
res.pca <- prcomp(viviendaz)
fviz_eig(res.pca, addlabels = TRUE) +
  ggtitle("Valores propios del PCA")

#Gráficos del PCA2
fviz_contrib(res.pca, choice ="var", axes = 2)

#Gráficos del PCA3
fviz_contrib(res.pca, choice ="var", axes = 3)

Análisis del PCA

El Análisis de Componentes Principales (PCA) muestra que las primeras dimensiones concentran la mayor parte de la variabilidad del conjunto de datos. En particular, la Dimensión 1 explica aproximadamente el 54.2% de la variabilidad total, lo que indica que existe un patrón dominante en los datos, asociado principalmente a características generales de las viviendas. Al incorporar la Dimensión 2, la varianza explicada acumulada supera el 70%, y con la Dimensión 3 se alcanza cerca del 85%, por lo que estas tres componentes resultan suficientes para representar adecuadamente la información original sin una pérdida significativa. El gráfico de contribuciones revela que la Dimensión 2 está fuertemente dominada por la variable piso, lo que sugiere que esta dimensión captura diferencias relacionadas con la altura o nivel de la vivienda. Por su parte, la Dimensión 3 está explicada principalmente por habitaciones y parqueaderos, indicando que esta componente resume aspectos asociados al tamaño funcional y la capacidad del inmueble. En conjunto, los resultados del PCA permiten reducir la dimensionalidad del problema y evidencian que variables estructurales y de distribución interna de la vivienda son las que más influyen en la variabilidad del conjunto de datos.

#Variables PCA
fviz_pca_var(res.pca, col.var ="contrib", 
             gradient.cols=c("#FF7F00", "#034D94"), 
              repel=TRUE)

El gráfico de variables del PCA muestra la relación entre las variables originales y las dos primeras componentes principales, que explican conjuntamente cerca del 73% de la variabilidad total (Dimensión 1: 54.2% y Dimensión 2: 18.8%). En la Dimensión 1 se observa que preciom, areaconst, banios, habitaciones y parqueaderos apuntan en la misma dirección, lo que indica una correlación positiva entre estas variables, asociada principalmente al tamaño y nivel general de la vivienda: a mayor área construida, número de habitaciones, baños y parqueaderos, mayor tiende a ser el precio. Por otro lado, la Dimensión 2 está dominada por la variable piso, la cual aparece casi perpendicular al resto, sugiriendo que el nivel o altura de la vivienda aporta información diferente e independiente respecto a las variables de tamaño y precio. La longitud de los vectores indica que piso, preciom y areaconst tienen una alta contribución a las componentes principales, mientras que la orientación opuesta de habitaciones respecto a piso en la Dimensión 2 sugiere una relación inversa entre ambas en esta componente. En conjunto, el gráfico confirma que el PCA separa claramente dos ejes interpretables: uno relacionado con la escala y valor del inmueble y otro asociado a la altura o piso de la vivienda.

#Individuos PCA
fviz_pca_ind(res.pca, geom.ind = "point",
             col.ind = "#fc4e07",
             axes = c(1,2),
             pointsize = 1.5)

El gráfico de individuos del PCA muestra la proyección de las viviendas sobre las dos primeras componentes, las cuales explican cerca del 73% de la variabilidad total. La Dimensión 1 (54.2%) concentra la mayor dispersión y separa principalmente las viviendas de menor tamaño y precio de aquellas con mayor área, más habitaciones, baños, parqueaderos y mayor valor, por lo que puede interpretarse como un eje de escala o nivel de la vivienda. La Dimensión 2 (18.8%) aporta una diferenciación adicional asociada principalmente al piso, aunque sin generar grupos claramente definidos. La concentración de puntos cerca del origen indica que la mayoría de las viviendas tienen características promedio, mientras que los individuos alejados del centro corresponden a viviendas con características más extremas o atípicas.

#Graficar los individuos + variables
fviz_pca_biplot(res.pca,
                col.var = "#034a94",
                col.ind = "#dedede")

El biplot del PCA integra la información de variables e individuos en las dos primeras componentes, que explican el 73% de la variabilidad total. La Dimensión 1 (54.2%) está asociada principalmente con área construida, número de baños, parqueaderos, habitaciones y precio, indicando un eje de tamaño y nivel de la vivienda. La Dimensión 2 (18.8%) está dominada por la variable piso, la cual aporta una diferenciación independiente del tamaño. Las flechas en direcciones similares evidencian correlaciones positivas entre variables como precio, área y número de baños, mientras que la dispersión de los individuos alrededor del origen sugiere que la mayoría de las viviendas presentan características promedio, con algunos casos extremos alejados del centro.

Conclusiones del Análisis de PCA:

A partir del Análisis de Componentes Principales (PCA) se logró reducir la dimensionalidad del conjunto de datos conservando una proporción significativa de la información original. Las dos primeras componentes explican aproximadamente el 73% de la variabilidad total, lo que indica que el comportamiento del mercado inmobiliario puede describirse adecuadamente mediante un número reducido de factores.

La primera componente principal concentra la mayor variabilidad y está fuertemente asociada con variables relacionadas con el tamaño y valor del inmueble, como el área construida, el número de habitaciones, baños, parqueaderos y el precio, representando un eje de características físicas y económicas de la vivienda. Por su parte, la segunda componente está dominada principalmente por la variable piso, lo que sugiere que esta característica aporta información complementaria e independiente al resto de atributos.

Los gráficos de contribución y el biplot evidencian relaciones positivas entre variables clave del mercado inmobiliario, así como una diferenciación clara entre viviendas de mayor y menor nivel. En conjunto, el PCA permitió identificar patrones estructurales relevantes en los datos, facilitando una comprensión más clara del mercado y sentando las bases para posteriores análisis, como segmentación de viviendas o modelos predictivos.

Fase 3: Análisis de Conglomerados

Se llevó a cabo un análisis de conglomerados mediante el método K-means, con el objetivo de agrupar las viviendas en distintos clusters de acuerdo con sus características. Este método fue elegido por su eficiencia computacional, su capacidad para manejar grandes volúmenes de datos y su efectividad en la identificación de patrones dentro de conjuntos de datos multivariados.

Posteriormente, se visualizaron los resultados del proceso de agrupamiento considerando diferentes números de clusters, específicamente entre 2 y 5. Esta comparación permitió analizar cómo variaba la estructura de los grupos a medida que aumentaba el número de conglomerados, facilitando la identificación de tendencias en la formación de los clusters y el grado de separación entre las observaciones.

df_viviendaz <- as.data.frame(viviendaz)
par(mfrow = c(1, 2))
#calculamos los clauster que creemos tener
k2 = kmeans(df_viviendaz, centers = 2, nstart = 100)
fviz_cluster(k2, data = df_viviendaz)

k3 = kmeans(df_viviendaz, centers = 3, nstart = 100)
fviz_cluster(k3, data = df_viviendaz)

k4 = kmeans(df_viviendaz, centers = 4, nstart = 100)
fviz_cluster(k4, data = df_viviendaz)

k5 = kmeans(df_viviendaz, centers = 5, nstart = 100)
fviz_cluster(k5, data = df_viviendaz)

Análsis de Clusters

El análisis de las cuatro soluciones revela una distribución de datos donde la mayor variabilidad se concentra en la primera dimensión (Dim1, 54.8%), la cual actúa como el principal factor de discriminación al separar un núcleo denso de observaciones a la izquierda de un segmento mucho más disperso y heterogéneo hacia la derecha. A medida que el número de clústeres aumenta de K=2 a K=5, se observa una transición desde una segmentación macroscópica y clara hacia una micro-segmentación donde los grupos comienzan a solaparse visualmente, especialmente en el centro del gráfico. Esto sugiere que, si bien el algoritmo intenta capturar matices adicionales en Dim2 (17.6%), la estructura subyacente de los datos tiende a ser continua, lo que provoca que los límites entre los grupos en las soluciones de 4 y 5 clústeres sean menos definidos y potencialmente arbitrarios.

En conclusión, la configuración de 3 clústeres parece ser la más equilibrada desde una perspectiva analítica, ya que logra capturar la diferencia fundamental en el eje horizontal mientras segmenta de forma útil la densidad vertical, sin llegar a la redundancia de las soluciones superiores. Se recomienda optar por esta estructura o la de 2 clústeres para garantizar que cada grupo tenga una identidad estadística lo suficientemente diferenciada para la toma de decisiones. Superar los 3 grupos en este conjunto de datos específico podría derivar en un “sobreajuste” (overfitting) visual, donde se crean categorías que no representan diferencias reales o significativas en el comportamiento de los sujetos estudiados.

Con el fin de dotar al análisis de una mayor objetividad, se empleó la función NbClust para determinar el número óptimo de conglomerados. Este procedimiento es fundamental, ya que sintetiza múltiples índices de validación interna y criterios estadísticos simultáneamente, permitiendo identificar la estructura de agrupación que mejor representa la naturaleza de los datos, más allá de la inspección visual.

#utilizamos la funcion nbclust para hacer un concenso de mayoria
res.nbclust = NbClust(df_viviendaz, distance = "euclidean", min.nc=2, max.nc=9, 
                      method="complete", index="all")

## *** : The Hubert index is a graphical method of determining the number of clusters.
##                 In the plot of Hubert index, we seek a significant knee that corresponds to a 
##                 significant increase of the value of the measure i.e the significant peak in Hubert
##                 index second differences plot. 
## 

## *** : The D index is a graphical method of determining the number of clusters. 
##                 In the plot of D index, we seek a significant knee (the significant peak in Dindex
##                 second differences plot) that corresponds to a significant increase of the value of
##                 the measure. 
##  
## ******************************************************************* 
## * Among all indices:                                                
## * 6 proposed 2 as the best number of clusters 
## * 10 proposed 3 as the best number of clusters 
## * 4 proposed 4 as the best number of clusters 
## * 3 proposed 8 as the best number of clusters 
## * 1 proposed 9 as the best number of clusters 
## 
##                    ***** Conclusion *****                            
##  
## * According to the majority rule, the best number of clusters is  3 
##  
##  
## *******************************************************************
best_nc = as.numeric(res.nbclust$Best.nc[1,])
nc_counts = table(best_nc)

nc_df = as.data.frame(nc_counts)
names(nc_df) = c("clusters", "frequency")

#graficamos los resultados de los cluster optimos segun nbclus
ggplot(nc_df, aes(x=clusters, y=frequency))+
  geom_bar(stat = 'identity', fill='skyblue')+
  theme_minimal()+ ggtitle("Numero optimo de cluster de NbClust")

Con base en el número óptimo de clusters determinado por NbClust (k = 3), se realizó el análisis de las variables originales mediante boxplots para interpretar las características de cada grupo.

k3 = kmeans(df_viviendaz, centers = 3, nstart = 100)

df_viviendaz$cluster <- factor(k3$cluster)
ggplot(df_viviendaz, aes(x = cluster, y = areaconst, fill = cluster)) +
  geom_boxplot() +
  geom_point(alpha = 0.3) +
  labs(x = "Clusters", fill = "Cluster")

El gráfico de Área de construcción muestra una diferenciación clara entre los tres grupos: Cluster 2 presenta las mayores medianas y mayor dispersión en área construida, indicando viviendas significativamente más amplias. Cluster 1 concentra las áreas más pequeñas, con menor variabilidad. Cluster 3 presenta valores intermedios.

Es decir, el tamaño de la vivienda es una de las variables que más contribuye a la segmentación, diferenciando claramente viviendas grandes, pequeñas e intermedias.

df_viviendaz %>%
  ggplot(aes(x=factor(k3$cluster), y=preciom, fill = factor(k3$cluster)))+
  geom_boxplot()+geom_point()+xlab("Clusters")+labs(fill="Cluster")

El boxplot de Precio por metro cuadraro, evidencia una relación directa con el área: Cluster 2 registra los precios más altos. Cluster 1 los más bajos. Cluster 3 valores intermedios.

Podría interpretarse como el precio refuerza la segmentación estructural observada en el área. Esto indica coherencia económica en los grupos formados: viviendas más grandes tienden a tener mayor valor.

par(mfrow = c(1, 2))
df_viviendaz %>%
  ggplot(aes(x=factor(k3$cluster), y=piso, fill = factor(k3$cluster)))+
  geom_boxplot()+geom_point()+xlab("Clusters")+labs(fill="Cluster")

El boxplot relacionado con la variable Piso evidencia que uno de los clusters concentra viviendas ubicadas en pisos más altos, lo cual puede asociarse con:

Mejor ubicación dentro del edificio. Mayor valor comercial. Segmento de mercado diferente.

par(mfrow = c(1, 2))
df_viviendaz %>%
  ggplot(aes(x=factor(k3$cluster), y=habitaciones, fill = factor(k3$cluster)))+
  geom_boxplot()+geom_point()+xlab("Clusters")+labs(fill="Cluster")

df_viviendaz %>%
  ggplot(aes(x=factor(k3$cluster), y=banios, fill = factor(k3$cluster)))+
  geom_boxplot()+geom_point()+xlab("Clusters")+labs(fill="Cluster")

df_viviendaz %>%
  ggplot(aes(x=factor(k3$cluster), y=parqueaderos, fill = factor(k3$cluster)))+
  geom_boxplot()+geom_point()+xlab("Clusters")+labs(fill="Cluster")

En cuanto a los boxplots de las variables Habitaciones, baños y parqueaderos se muestran con un patrón consistente:

Cluster 2 presenta mayor número de habitaciones, baños y parqueaderos. Cluster 1 presenta las cantidades más bajas. Cluster 3 mantiene valores medios.

Los clusters no solo se diferencian en tamaño y precio, sino también en nivel de equipamiento y comodidad.

En cuanto a las variables categóricas se visualizan mediante gráficos de barras con el fin de saber si contribuyen a los análisis del PCA.

#Identificación defilas despues de la eliminación de NA
which(complete.cases(vivienda_final))
#Indices originales
indices_validos <- which(complete.cases(vivienda_final))
#Base para analizar clusters
viviendas_cluster <- vivienda_ordenada[indices_validos, ]
viviendas_cluster$cluster3 <- factor(k3$cluster)
#Verificación
nrow(viviendas_cluster)
## [1] 6717
length(k3$cluster)
## [1] 6717
#Estrato
table(viviendas_cluster$estrato, viviendas_cluster$cluster3)
##    
##        1    2    3
##   3  180  728  225
##   4  244 1048  395
##   5  304 1359  597
##   6  197 1036  404
#Tipo de vivienda
table(viviendas_cluster$tipo, viviendas_cluster$cluster3)
##              
##                  1    2    3
##   Apartamento  562 2573  991
##   Casa         363 1598  630
#Zona urbana
table(viviendas_cluster$zona, viviendas_cluster$cluster3)
##               
##                   1    2    3
##   Zona Centro    24   48   18
##   Zona Norte    274 1023  298
##   Zona Oeste    142  653  213
##   Zona Oriente   23  184   62
##   Zona Sur      462 2263 1030
#Grafico de barras para Estrato
ggplot(viviendas_cluster,
       aes(x = factor(estrato), fill = cluster3)) +
  geom_bar(position = "dodge") +
  theme_minimal()

El análisis de la variable estrato por conglomerado evidencia que los tres clusters están presentes en todos los niveles socioeconómicos considerados (3 a 6). Sin embargo, el Cluster 3 concentra la mayor cantidad de viviendas en estratos 5 y 6, lo que sugiere que agrupa propiedades de mayor nivel socioeconómico.

El Cluster 1 presenta una distribución intermedia, predominando en estratos 4 y 5, mientras que el Cluster 2, aunque menos numeroso, también tiene presencia relevante en estratos altos.

Estos resultados indican que la segmentación obtenida no está determinada exclusivamente por el estrato, sino por una combinación de variables estructurales como área, precio, número de baños y parqueaderos.

#Grafico de barras para tipo de vivienda
ggplot(viviendas_cluster, 
       aes(x = factor(cluster3), fill = tipo)) +
  geom_bar(position = "fill") +
  scale_y_continuous(labels = scales::percent_format()) +
  labs(title = "Distribución del tipo de vivienda por cluster",
       x = "Cluster",
       y = "Proporción",
       fill = "Tipo de vivienda") +
  theme_minimal()

El análisis de la variable tipo de vivienda muestra que la distribución porcentual de casas y apartamentos es similar en los tres clusters. Esto indica que el algoritmo K-means no segmentó las viviendas principalmente por su tipología, sino por características cuantitativas como precio, área y número de habitaciones. En consecuencia, el tipo de vivienda no constituye un factor diferenciador relevante dentro de la estructura de conglomerados obtenida.

Conclusiones de los Análisis de Conglomerados:

El análisis de conglomerados mediante el método K-means, con un número óptimo de tres clusters determinado por NbClust, permitió identificar tres segmentos diferenciados dentro del mercado inmobiliario analizado.

Los clusters se distinguen principalmente por el área construida, el precio por metro cuadrado y el número de habitaciones, baños y parqueaderos. El segundo cluster agrupa viviendas de mayor tamaño, mayor precio y mejor equipamiento, representando el segmento de alto valor. El primer cluster corresponde a viviendas más pequeñas y económicas, mientras que el tercero representa un segmento intermedio.

La incorporación de la variable estrato confirma la coherencia socioeconómica de la segmentación, evidenciando que las viviendas de mayor valor tienden a ubicarse en estratos más altos, mientras que las de menor valor se concentran en estratos más bajos o medios.

En conjunto, el modelo logró una segmentación consistente y coherente con la estructura real del mercado, permitiendo identificar perfiles diferenciados de vivienda que pueden ser útiles para análisis estratégicos, comerciales o de planificación urbana.

Fase 4: Análisis de Correspondencia Múltiple

Se realizó un Análisis de Correspondencia Múltiple (ACM) con las variables zona, estrato y tipo de vivienda, con el objetivo de explorar asociaciones entre categorías y detectar patrones estructurales en los datos. Esta técnica permite representar simultáneamente las relaciones entre múltiples variables cualitativas en un espacio reducido de dimensiones, facilitando la identificación de perfiles característicos de vivienda.

#Preparación y verificación de la base de datos para ACM
datos_acm <- vivienda_ordenada[, c("zona", "estrato", "tipo")]
datos_acm[] <- lapply(datos_acm, as.factor)
#Verficación de datos NA
colSums(is.na(datos_acm))
##    zona estrato    tipo 
##       3       3       3
#Base de datos lista para el análisis
datos_acm <- na.omit(datos_acm)
colSums(is.na(datos_acm))
##    zona estrato    tipo 
##       0       0       0
#Ejecución del ACM y eigenvalues
acm <- MCA(datos_acm, graph = FALSE)
acm$eig
##       eigenvalue percentage of variance cumulative percentage of variance
## dim 1  0.5620882              21.078306                          21.07831
## dim 2  0.4531232              16.992119                          38.07043
## dim 3  0.3796450              14.236687                          52.30711
## dim 4  0.3334444              12.504164                          64.81128
## dim 5  0.3233137              12.124263                          76.93554
## dim 6  0.2717762              10.191608                          87.12715
## dim 7  0.2013563               7.550862                          94.67801
## dim 8  0.1419197               5.321990                         100.00000
#Visualización de la varianza
fviz_screeplot(acm, addlabels = TRUE, ylim = c(0, 25))

El scree plot muestra la proporción de varianza explicada por cada una de las ocho dimensiones del Análisis de Correspondencia Múltiple. Se observa que la Dimensión 1 explica el 21.1% de la variabilidad total, seguida por la Dimensión 2 con 17% y la Dimensión 3 con 14.2%. A partir de la tercera dimensión, la disminución en la varianza explicada es progresiva y menos pronunciada, sin evidenciar un punto de quiebre abrupto (codo marcado).

Las dos primeras dimensiones acumulan aproximadamente el 38% de la inercia total, lo que justifica su utilización para la representación gráfica e interpretación principal del análisis. Aunque las dimensiones posteriores aportan información adicional, su contribución individual es menor, por lo que el plano factorial formado por las dos primeras dimensiones resulta adecuado para describir la estructura general de las asociaciones entre las variables categóricas analizadas.

#Gráfica del ACM para D1/D2
fviz_mca_var(acm,
             repel = TRUE,
             ggtheme = theme_minimal())

El plano factorial conformado por las dimensiones 1 (21.1%) y 2 (17%) explica conjuntamente el 38.07% de la variabilidad total. La Dimensión 1 parece estar asociada principalmente con un gradiente socioeconómico, ya que separa claramente los estratos bajos (3 y 4), ubicados hacia el lado izquierdo del plano, de los estratos más altos, especialmente el estrato 3 en el extremo derecho junto con las categorías Zona Centro y Zona Oriente. Esta proximidad sugiere una asociación entre estos sectores geográficos y determinados niveles socioeconómicos.

Por su parte, la Dimensión 2 diferencia principalmente ciertas zonas específicas, destacándose Zona Oeste y el estrato 6 en la parte superior del plano, lo que indica que estas categorías comparten características similares dentro de la estructura de los datos. En contraste, Zona Sur y los estratos 4 y 5 se ubican en la parte inferior, evidenciando un perfil distinto.

En cuanto al tipo de vivienda, “Casa” se posiciona hacia el lado positivo de la Dimensión 1, mientras que “Apartamento” se ubica más hacia el lado negativo, lo que sugiere una diferenciación moderada entre ambas tipologías en relación con el nivel socioeconómico y la localización.

#Gráfica del ACM para D1/D3
fviz_mca_var(acm,
             axes = c(1, 3),
             repel = TRUE,
             ggtheme = theme_minimal())

La Dimensión 3 introduce una diferenciación más clara entre zonas geográficas. Zona Centro y Zona Oriente aparecen en la parte superior del plano, mientras que Zona Norte se ubica claramente en la parte inferior, lo que sugiere que esta dimensión está capturando una estructura territorial distinta a la observada en la Dimensión 2.

Asimismo, el tipo de vivienda muestra una mayor separación en esta dimensión: “Casa” se sitúa en la parte superior, mientras que “Apartamento” aparece en la parte inferior, indicando que la Dimensión 3 podría estar relacionada con diferencias en la tipología habitacional.

En conjunto, aunque la Dimensión 3 explica una proporción menor de la variabilidad, aporta información complementaria relevante, particularmente en la diferenciación territorial y en el contraste entre tipos de vivienda.

Conclusiones de Análisis de correspondencia

El Análisis de Correspondencia Múltiple evidencia que existe una asociación estructural entre zona geográfica, estrato socioeconómico y tipo de vivienda. La primera dimensión recoge principalmente diferencias socioeconómicas, mientras que las dimensiones segunda y tercera capturan variaciones territoriales y tipológicas. Esto sugiere que la configuración del mercado de vivienda no es aleatoria, sino que presenta patrones definidos según localización y nivel socioeconómico.

Conclusiones generales

El presente estudio permitió transformar una base de datos extensa de viviendas urbanas en información estructurada y analíticamente útil para la empresa inmobiliaria. A través de un proceso riguroso de depuración, tratamiento de valores faltantes y análisis exploratorio, se garantizó la calidad de los datos, sentando una base sólida para la aplicación de técnicas multivariadas orientadas a la identificación de patrones relevantes del mercado inmobiliario en la ciudad de Cali.

El análisis de segmentación mediante clustering evidenció que el mercado de viviendas no es homogéneo, sino que se encuentra conformado por grupos diferenciados con características específicas. Esta segmentación permite identificar perfiles de vivienda con comportamientos similares, facilitando procesos de focalización comercial, diseño de estrategias de venta y definición de rangos de valoración más ajustados a cada segmento.

Por su parte, el Análisis de Correspondencia Múltiple permitió identificar asociaciones estructurales entre zona geográfica, estrato socioeconómico y tipo de vivienda, mostrando que estas variables no operan de manera independiente, sino que configuran patrones territoriales y socioeconómicos definidos. Las dimensiones principales del análisis evidencian un gradiente socioeconómico y una diferenciación territorial que explican parte importante de la estructura del mercado, aportando una comprensión más profunda de la dinámica inmobiliaria urbana.

En conjunto, los resultados obtenidos cumplen con el objetivo propuesto, al identificar patrones y relaciones relevantes que pueden apoyar la toma de decisiones estratégicas en procesos de compra, venta y valoración de propiedades. El análisis desarrollado transforma datos descriptivos en conocimiento aplicado, reduciendo la incertidumbre y fortaleciendo el soporte técnico para la gestión inmobiliaria.

Recomendaciones

Se recomienda que la empresa incorpore de manera sistemática el análisis de datos en sus procesos estratégicos, utilizando la segmentación identificada como base para diseñar estrategias diferenciadas de comercialización y valoración según perfil de vivienda y ubicación geográfica. Esto permitiría optimizar la asignación de recursos y mejorar la precisión en la estimación de precios.

Asimismo, se sugiere ampliar el análisis incorporando variables adicionales como área construida, valor comercial, antigüedad del inmueble y características estructurales, lo que permitiría construir modelos predictivos más robustos para la estimación de precios y la identificación de oportunidades de inversión.

Finalmente, se recomienda mantener protocolos claros de actualización, depuración y control de calidad de la base de datos, ya que la confiabilidad de los análisis depende directamente de la consistencia de la información. Una gestión adecuada de los datos fortalecerá la capacidad de la empresa para convertir información en ventaja competitiva dentro del mercado inmobiliario.

Referencias

1. Han, J., Pei, J., & Kamber, M. (2011). Data mining: Concepts and techniques (3rd ed.). Morgan Kaufmann.

2. Greenacre, M. (2017). Correspondence analysis in practice (3rd ed.). Chapman & Hall/CRC.

3. James, G., Witten, D., Hastie, T., & Tibshirani, R. (2021). An introduction to statistical learning: With applications in R (2nd ed.). Springer. https://doi.org/10.1007/978-1-0716-1418-1