1. CARGUE DE LIBRERIAS Y CONJUNTO DE DATOS

library(paqueteMODELOS)
library(dplyr)
library(ggplot2)
library(tidyr)
library(naniar)
library(reshape2)
library(mice)
library(ggbiplot)
library(factoextra)
library(FactoMineR)
library(cluster)
library(dendextend)
library(fpc)
data("vivienda")
class(vivienda)
## [1] "spec_tbl_df" "tbl_df"      "tbl"         "data.frame"
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>

El conjunto de datos obtenido corresponde a un data.frame que contiene 8322 registros correspondientes a propiedades inmobiliarias de la ciudad de Cali, y 13 variables con información de estos registros los cuales se describen a continuación:

  • ‘id’: numérico (), Identificador único para cada registro.
  • ‘zona’: carácter (), Zona geográfica.
  • ‘piso’: carácter (), Número de piso o alguna categorización similar.
  • ‘estrato’: numérico (), Categoría de estratificación socioeconómica.
  • ‘preciom’: numérico (), Valor del precio de la propiedad.
  • ‘areaconst’: numérico (), Área construida de la propiedad.
  • ‘parqueaderos’: numérico (), Número de parqueaderos disponibles.
  • ‘banios’: numérico (), Número de baños en la propiedad.
  • ‘habitaciones’: numérico (), Número de habitaciones.
  • ‘tipo’: carácter (), Tipo de propiedad.
  • ‘barrio’: carácter (), Barrio en el que se encuentra la propiedad.
  • ‘longitud’: numérico (), Coordenada de longitud geográfica.
  • ‘latitud’: numérico (), Coordenada de latitud geográfica.
head(vivienda, 6)
## # 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>
tail(vivienda, 6)
## # A tibble: 6 × 13
##      id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
##   <dbl> <chr>   <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
## 1  6417 Zona S… <NA>        6    1800       400            3      6            5
## 2  6998 Zona S… <NA>        6    1000       189            3      5            4
## 3  8139 Zona S… <NA>        5     530       142            2      4            4
## 4    NA <NA>    <NA>       NA      NA        NA           NA     NA           NA
## 5    NA <NA>    <NA>       NA      NA        NA           NA     NA           NA
## 6    NA <NA>    <NA>       NA     330        NA           NA     NA           NA
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>

2. LIMPIEZA DE LOS DATOS

Antes de realizar el análisis exploratorio propiamente dicho procedemos a revisar la integridad de nuestros datos mediante la determinación de duplicados, datos faltantes, información inconsistente o tipos de datos que no correspondan a la naturaleza de la variable. Estas tareas podrían obligar a realizar actividades de eliminación, adición o transformación de los datos con el objeto de preparar el conjunto de datos para el modelado.

2.1. Registros Duplicados

# Verificar si hay filas duplicadas en el dataframe completo
duplicados <- vivienda %>%
  filter(duplicated(.))

# Ver los duplicados
print(duplicados)
## # A tibble: 1 × 13
##      id zona  piso  estrato preciom areaconst parqueaderos banios habitaciones
##   <dbl> <chr> <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
## 1    NA <NA>  <NA>       NA      NA        NA           NA     NA           NA
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>

El conjunto de datos no contiene registros duplicados. Al aplicar la función para determinarlos solo arroja una columna con todos los valores NA. Esto sugiere que el conjunto de datos contiene dos columnas con todos los valores NA. Esto será abordado al momento de efectuar el tratamiento de los datos faltantes.

2.2. Datos Faltantes

gg_miss_var(vivienda)

md.pattern(vivienda, 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

Para corregir el problema de los datos faltantes se procedió de la siguiente forma:

vivienda <- vivienda[rowSums(is.na(vivienda)) != ncol(vivienda), ]
vivienda <- vivienda[apply(is.na(vivienda), 1, function(x) sum(x) != ncol(vivienda) - 1), ]


moda_piso_por_zona <- vivienda %>%
  group_by(zona) %>%
  summarise(moda_piso = as.numeric(names(sort(table(piso), decreasing = TRUE)[1])), .groups = 'drop')
  

vivienda <- vivienda %>%
  mutate(parqueaderos = replace_na(parqueaderos, 0))


vivienda <- vivienda %>%
  left_join(moda_piso_por_zona, by = "zona") %>%
  mutate(piso = ifelse(is.na(piso), moda_piso, piso)) %>%
  select(-moda_piso)

Una vez aplicado el código donde se aplica el tratamiento a los datos faltantes verificamos que todo haya sido aplicado correctamente.

gg_miss_var(vivienda)

2.3. Cambio de Tipo de Datos

Se cambia el tipo de datos de chr a factor de las variables zona, tipo y barrio. Estas variables claramente corresponden a variables categóricas que pueden ser transformadas para facilitar el análisis.

# columnas categóricas
vivienda %>% count(zona)
## # A tibble: 5 × 2
##   zona             n
##   <chr>        <int>
## 1 Zona Centro    124
## 2 Zona Norte    1920
## 3 Zona Oeste    1198
## 4 Zona Oriente   351
## 5 Zona Sur      4726
vivienda %>% count(barrio)
## # A tibble: 436 × 2
##    barrio            n
##    <chr>         <int>
##  1 20 de julio       3
##  2 3 de julio        1
##  3 Belalcazar        1
##  4 Brisas De Los     1
##  5 Bueno Madrid      1
##  6 Cali             37
##  7 Camino Real       1
##  8 Centenario        1
##  9 Chiminangos       1
## 10 Ciudad 2000       1
## # ℹ 426 more rows
vivienda %>% count(tipo)
## # A tibble: 2 × 2
##   tipo            n
##   <chr>       <int>
## 1 Apartamento  5100
## 2 Casa         3219
vivienda %>% count(piso)
## # A tibble: 15 × 2
##    piso      n
##    <chr> <int>
##  1 01      860
##  2 02     1450
##  3 03     1097
##  4 04      607
##  5 05      567
##  6 06      245
##  7 07      204
##  8 08      211
##  9 09      146
## 10 1       198
## 11 10      130
## 12 11       84
## 13 12       83
## 14 2      2044
## 15 3       393
# Convertir variables categoricas a factor
vivienda <- vivienda %>% mutate(
  zona = as.factor(zona),        
  tipo = as.factor(tipo),     
  barrio = as.factor(barrio)  
)

Por otra parte la variable ‘piso’ puede ser convertida de tipo chr a int dado que en realidad corresponde a un numero entero que por alguna razón esta mal formateado en el conjunto de datos original, por lo que duplica categorías que en realidad deben ser una sola, por ejemplo 01 y 1. Con este enfoque quedan correctamente etiqueda las categorias de esta variable.

vivienda <- vivienda %>%
  mutate(piso = as.integer(piso))

Finalmente verificamos que se hayan aplicado correctamente la limpieza y transformación del conjunto de datos.

head(vivienda)
## # A tibble: 6 × 13
##      id zona     piso estrato preciom areaconst parqueaderos banios habitaciones
##   <dbl> <fct>   <int>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
## 1  1147 Zona O…     1       3     250        70            1      3            6
## 2  1169 Zona O…     1       3     320       120            1      2            3
## 3  1350 Zona O…     1       3     350       220            2      2            4
## 4  5992 Zona S…     2       4     400       280            3      5            3
## 5  1212 Zona N…     1       5     260        90            1      2            3
## 6  1724 Zona N…     1       5     240        87            1      3            3
## # ℹ 4 more variables: tipo <fct>, barrio <fct>, longitud <dbl>, latitud <dbl>

3. ANÁLISIS EXPLORATORIO DE DATOS

3.1. Análisis Univariado.

Generamos las estadísticas descriptivas del conjunto de datos:

summary(vivienda)
##        id                 zona           piso           estrato     
##  Min.   :   1   Zona Centro : 124   Min.   : 1.000   Min.   :3.000  
##  1st Qu.:2080   Zona Norte  :1920   1st Qu.: 2.000   1st Qu.:4.000  
##  Median :4160   Zona Oeste  :1198   Median : 2.000   Median :5.000  
##  Mean   :4160   Zona Oriente: 351   Mean   : 3.233   Mean   :4.634  
##  3rd Qu.:6240   Zona Sur    :4726   3rd Qu.: 4.000   3rd Qu.:5.000  
##  Max.   :8319                       Max.   :12.000   Max.   :6.000  
##                                                                     
##     preciom         areaconst       parqueaderos        banios      
##  Min.   :  58.0   Min.   :  30.0   Min.   : 0.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 : 1.000   Median : 3.000  
##  Mean   : 433.9   Mean   : 174.9   Mean   : 1.482   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  
##                                                                     
##   habitaciones             tipo                 barrio        longitud     
##  Min.   : 0.000   Apartamento:5100   valle del lili:1008   Min.   :-76.59  
##  1st Qu.: 3.000   Casa       :3219   ciudad jardín : 516   1st Qu.:-76.54  
##  Median : 3.000                      pance         : 409   Median :-76.53  
##  Mean   : 3.605                      la flora      : 366   Mean   :-76.53  
##  3rd Qu.: 4.000                      santa teresita: 262   3rd Qu.:-76.52  
##  Max.   :10.000                      el caney      : 208   Max.   :-76.46  
##                                      (Other)       :5550                   
##     latitud     
##  Min.   :3.333  
##  1st Qu.:3.381  
##  Median :3.416  
##  Mean   :3.418  
##  3rd Qu.:3.452  
##  Max.   :3.498  
## 

A simple vista notamos que las variables de nuestro conjunto de datos se encuentran en rangos diferentes. Esto podría dificultar tanto el análisis preliminar de los datos, como la aplicación de técnicas como el Análisis de Componentes Principales (PCA) y el Análisis de Conglomerados. Por tal motivo, adicional a las transformaciones ya efectuadas procedemos a estandarizar las variables numericas del conjunto.

vivienda_estandarizado <- vivienda %>%
  select(-zona, -barrio, -tipo, -id) %>%
  scale() %>%
  as.data.frame()

head(vivienda_estandarizado)
##         piso    estrato    preciom  areaconst parqueaderos      banios
## 1 -0.9643519 -1.5872276 -0.5595498 -0.7339949   -0.3875522 -0.07793773
## 2 -0.9643519 -1.5872276 -0.3465670 -0.3842568   -0.3875522 -0.77811479
## 3 -0.9643519 -1.5872276 -0.2552886  0.3152194    0.4168506 -0.77811479
## 4 -0.5325734 -0.6156201 -0.1031580  0.7349051    1.2212534  1.32241640
## 5 -0.9643519  0.3559875 -0.5291236 -0.5940997   -0.3875522 -0.77811479
## 6 -0.9643519  0.3559875 -0.5899759 -0.6150839   -0.3875522 -0.07793773
##   habitaciones   longitud    latitud
## 1    1.6406840  0.9728466  0.3793708
## 2   -0.4147626  0.9331875  0.3763219
## 3    0.2703863  0.7607566  0.4225243
## 4   -0.4147626 -0.6549016  0.4070454
## 5   -0.4147626  0.8682385  0.9678065
## 6   -0.4147626  0.6670691 -1.1242009

Inicialmente haremos la observaciones de las variables numéricas estandarizadas mediante diagramas de caja y bigotes para cada una de ellas:

library(RColorBrewer)


vivienda_pivot <- vivienda_estandarizado %>%
  select_if(is.numeric) %>% 
  pivot_longer(cols = everything(), names_to = "variable", values_to = "valor")


n_colores <- length(unique(vivienda_pivot$variable))
colores_pastel <- RColorBrewer::brewer.pal(n_colores, "Pastel1")


ggplot(vivienda_pivot, aes(x = variable, y = valor, fill = variable)) +
  geom_boxplot() +
  scale_fill_manual(values = colores_pastel) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  labs(title = "Boxplot de todas las variables numéricas",
       x = "Variable", y = "Valor") +
  theme_minimal() +
  theme(legend.position = "none") 

Los diagramas de boxplot nos muestran que en efecto las variables se encuentran con distribuciones muy diversas, la mayoría de ellas con sesgo y valores atípicos, sin embargo por ser el sector inmobiliario muy dependiente de las condiciones socio-económicas de la población no le daremos tratamiento a estos valores atípicos ya que corresponden a un patrón normal e incluso esperable en las ciencias económicas.

cor(vivienda_estandarizado[, sapply(vivienda_estandarizado, is.numeric)], use="complete.obs")
##                      piso     estrato      preciom   areaconst parqueaderos
## piso          1.000000000  0.14659113 -0.005885967 -0.17745968   0.01634177
## estrato       0.146591126  1.00000000  0.609806641  0.27432332   0.51278892
## preciom      -0.005885967  0.60980664  1.000000000  0.68735196   0.63977788
## areaconst    -0.177459683  0.27432332  0.687351963  1.00000000   0.48238397
## parqueaderos  0.016341771  0.51278892  0.639777883  0.48238397   1.00000000
## banios       -0.074514213  0.42032178  0.669145579  0.64841648   0.52312994
## habitaciones -0.199898962 -0.07137615  0.264091206  0.51691292   0.19875551
## longitud     -0.048363839 -0.44414040 -0.343588223 -0.17374283  -0.27974065
## latitud       0.042289415 -0.22749944 -0.115667569 -0.05188781  -0.15629165
##                   banios habitaciones     longitud     latitud
## piso         -0.07451421 -0.199898962 -0.048363839  0.04228941
## estrato       0.42032178 -0.071376147 -0.444140396 -0.22749944
## preciom       0.66914558  0.264091206 -0.343588223 -0.11566757
## areaconst     0.64841648  0.516912916 -0.173742834 -0.05188781
## parqueaderos  0.52312994  0.198755515 -0.279740654 -0.15629165
## banios        1.00000000  0.589906412 -0.249569746 -0.13003729
## habitaciones  0.58990641  1.000000000 -0.008002016  0.02125096
## longitud     -0.24956975 -0.008002016  1.000000000  0.23606961
## latitud      -0.13003729  0.021250965  0.236069614  1.00000000
cor_matrix <- cor(vivienda_estandarizado[, c("preciom", "areaconst", "parqueaderos", "banios", "habitaciones", "piso", "estrato", "longitud", "latitud")], use = "complete.obs")


cor_long <- as.data.frame(as.table(cor_matrix))


ggplot(cor_long, aes(Var1, Var2, fill = Freq)) +
  geom_tile() +
  scale_fill_gradient2(low = "red", high = "green", mid = "yellow", midpoint = 0, limit = c(-1, 1)) +
  geom_text(aes(label = round(Freq, 2)), color = "black", size = 4) +
  theme_minimal() +
  labs(title = "Mapa de Calor de las Correlaciones", x = "Variable", y = "Variable", fill = "Correlación") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

4. ANÁLISIS DE COMPONENTES PRINCIPALES

Para el desarrollo del análisis de componentes principales, excluiremos del conjunto de datos a las tres variables que muestran menor correlación con las demás variables del conjunto de datos estas son: piso, longitud y latitud. Sobre el particular cabe resaltar que estas variables poco aportan al modelo ya que a pesar de tener formato numérico su naturaleza es meramente informativa y pueden ser consideradas categóricas, de hecho dos de estas variables corresponden a las coordenadas geográficas por lo que son explicadas por las variables categóricas zona y barrio. De igual forma se excluyó la variable estrato dado es una variable categórica ordinal.

vivienda_estandarizado_numer <- vivienda_estandarizado %>%
  select(-piso, -longitud, -latitud, -estrato)


pca_resultado <- prcomp(vivienda_estandarizado_numer)


summary(pca_resultado)
## Importance of components:
##                           PC1    PC2     PC3     PC4     PC5
## Standard deviation     1.7687 0.9580 0.65962 0.56977 0.44053
## Proportion of Variance 0.6257 0.1836 0.08702 0.06493 0.03881
## Cumulative Proportion  0.6257 0.8092 0.89626 0.96119 1.00000
library(factoextra)
res.pca <- prcomp(vivienda_estandarizado_numer)
fviz_eig(res.pca, addlabels = TRUE)

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

ggbiplot(pca_resultado, obs.scale = 1, var.scale = 1, groups = vivienda$zona, 
         ellipse = TRUE, circle = TRUE) + 
  theme_minimal() +
  labs(title = "PCA Biplot - Dos primeros componentes")

scores_pca <- pca_resultado$x
head(scores_pca)
##              PC1         PC2         PC3          PC4        PC5
## [1,] -0.24152830 -1.56481451  0.87782085  0.005259334  0.6499093
## [2,] -1.04207572  0.14095015 -0.14392551 -0.209788875  0.1579068
## [3,] -0.09005095  0.01620188  0.18306840 -0.974807022  0.1572797
## [4,]  1.31443366  0.64126217  0.45715767  0.155313505 -1.2613214
## [5,] -1.23096999  0.09318946  0.03951942 -0.144980451  0.1185791
## [6,] -0.92302403 -0.03652024  0.15796687  0.336771657 -0.2510437
plot(scores_pca[, 1], scores_pca[, 2], 
     xlab = "Componente Principal 1", 
     ylab = "Componente Principal 2", 
     main = "Distribución de Observaciones en los Primeros 2 Componentes",
     col = vivienda$zona, pch = 16)

El PCA aplicado sobre 5 variables numéricas del conjunto de datos, explica con dos componentes principales el 81% de la varianza de los datos. El modelo con 3 componentes principales logra explicar cerca del 90% de dicha varianza, así las cosas se pueden escoger tres componentes para efectuar los análisis subsiguientes que requiera la empresa.

El primer componente, con una proporción de varianza de 0.6257 captura en mayor medida la información de las variables ‘areaconst’ y ‘baños’, mientras que el segundo componente captura mas información de las variables ‘habitaciones’ y ‘parqueaderos’ como se puede observar el el gráfico Variable - PCA.

Anexamos ademas gráficos adicionales donde se observa la distribución de los casos. Por ejemplo, podemos notar en el PCA Biplot los casos mas influenciados por los vectores ubicados en el plano por zona.

5. ANÁLISIS DE CONGLOMERADOS

Determinaremos primero mediante la ayuda gráfica del metodo del codo una aproximación al número de clústeres optimo para nuestros datos:

buck <- numeric(6) 

for (i in 1:6) {
  kmeans_result <- kmeans(vivienda_estandarizado_numer, centers = i)
  buck[i] <- kmeans_result$tot.withinss }


plot(1:6, buck, type = "b", pch = 19, col = "blue", 
     xlab = "Número de Clusters", ylab = "WCSS", 
     main = "Método del Codo (Elbow Method)")

Conforme al análisis de la gráfica anterior probaremos ajustar el modelo k-means con parámetros k de 2 y 3 y escogeremos de los dos el que muestre mejor desempeño.

distancia <- dist(vivienda_estandarizado_numer, method = "euclidean")
cluster_jerarquico <- hclust(distancia, method = "complete")


plot(cluster_jerarquico, main = "Dendograma - Método Jerárquico", xlab = "Individuos", ylab = "Distancia")

rect.hclust(cluster_jerarquico, k = 3, border = "red")  

set.seed(123)  
kmeans_resultado <- kmeans(vivienda_estandarizado_numer, centers = 3) 


table(kmeans_resultado$cluster)  
## 
##    1    2    3 
##  942 2554 4823
vivienda$cluster_kmeans <- kmeans_resultado$cluster


ggplot(vivienda, aes(x = preciom, y = areaconst, color = factor(cluster_kmeans))) +
  geom_point() +
  labs(title = "Grupos según K-means", x = "Precio por metro cuadrado", y = "Área de construcción") +
  theme_minimal()

clust_stats <- cluster.stats(distancia, kmeans_resultado$cluster)
cat("Índice de Rand ajustado:", clust_stats$corrected.rand, "\n")
## Índice de Rand ajustado:
cluster_jerarquico <- hclust(distancia, method = "complete")
grupo_jerarquico <- cutree(cluster_jerarquico, k = 3)

distancia_e <- dist(vivienda_estandarizado_numer, method = "euclidean")
dendograma <- hclust(distancia_e, method = "average")

barplot(sort(dendograma$height, decreasing = TRUE), horiz = TRUE, 
main = "Agregaciones (distancias euclidianas)",
col = "lightblue", ylab = "Nodo", xlab = "Peso", xlim = c(0, 2.5))

El algoritmo K-means logra un mayor ajuste con un valor k=3. El gráfico de grupos en K-Means nos muestra una separación relativa y definida entre los miembros de los clusteres aunque con valores extrapolados entre los mismos. Se podría optar por enfoques mas avanzados teniendo en cuenta la complejidad y variables involucradas en este análisis.

centroides <- kmeans_result$centers
centroides
##      preciom  areaconst parqueaderos     banios habitaciones
## 1  1.8431854  0.9261133   1.53063912  1.0663462    0.1464862
## 2  2.5347971  3.2184808   2.32658389  2.0251121    1.2611411
## 3 -0.7043255 -0.6541809  -0.67498859 -0.8761852   -0.5725476
## 4 -0.2321571 -0.3431604   0.08855472 -0.1896681   -0.2994667
## 5  0.0601238  0.9192358  -0.28452520  1.1062698    2.5024640
## 6  0.3446819  0.3716605   0.18117564  0.6794979    0.2536216
distancia_centroides <- dist(centroides, method = "euclidean")
distancia_centroides
##          1        2        3        4        5
## 2 2.920358                                    
## 3 4.259386 6.802852                           
## 4 3.126365 5.717591 1.203559                  
## 5 3.467910 4.540218 4.074102 3.368758         
## 6 2.129580 4.508829 2.447139 1.383346 2.416012
vivienda$cluster_kmeans <- kmeans_result$cluster
cluster1_data <- subset(vivienda, cluster_kmeans == 1)
cluster2_data <- subset(vivienda, cluster_kmeans == 2)
cluster3_data <- subset(vivienda, cluster_kmeans == 3)
distancia_cluster1 <- dist(cluster1_data[, c("piso", "estrato", "preciom")], method = "euclidean")
distancia_cluster2 <- dist(cluster2_data[, c("piso", "estrato", "preciom")], method = "euclidean")
distancia_cluster3 <- dist(cluster3_data[, c("piso", "estrato", "preciom")], method = "euclidean")
mean(distancia_cluster1)
## [1] 353.1534
mean(distancia_cluster2)
## [1] 431.5424
mean(distancia_cluster3)
## [1] 88.98669
heatmap(as.matrix(distancia_centroides), main = "Matriz de Distancia entre Centroides")

set.seed(123)
kmeans_result <- kmeans(vivienda[, c("piso", "estrato", "preciom")], centers = 3)
centroides <- kmeans_result$centers
distancia_centroides <- dist(centroides, method = "euclidean")
distancia_tabla <- as.matrix(distancia_centroides)
distancia_tabla
##           1         2        3
## 1    0.0000 1092.4754 387.7355
## 2 1092.4754    0.0000 704.7408
## 3  387.7355  704.7408   0.0000

DISTANCIA ENTRE CLUSTERES

Cluster 1 y 2: Tiene una distancia bastante alta, lo que indica que los centroides de los clusters 1 y 2 están bastante separados en el espacio de características para el clustering. Es decir las observaciones en estos dos clusters son bastante diferentes entre sí.

Cluster 1 y 3: La distancia es considerablemente más pequeña que la de la comparación anterior. Esto nos indica que los centroides del cluster 1 y 3 están relativamente más cerca, lo que indica que las observaciones en estos dos clusters podrían ser más similares entre sí que con el cluster 2.

Cluster 2 y 3: está entre las dos anteriores. Es una distancia intermedia, lo que significa que los clusters 2 y 3 no están tan alejados como los clusters 1 y 2, pero tampoco están tan cerca como los clusters 1 y 3.

En conclusión Los resultados indican que el cluster 1 es muy diferente del cluster 2, mientras que el cluster 1 y el cluster 3 tienen características similares. El cluster 2 parece estar más alejado de los otros dos clusters en términos de las características utilizadas para el análisis.

6. ANÁLISIS DE CORRESPONDENCIA

AC1 <- table(vivienda$tipo, vivienda$zona)
chisq.test(AC1)
## 
##  Pearson's Chi-squared test
## 
## data:  AC1
## X-squared = 690.93, df = 4, p-value < 2.2e-16
resul_AC1 <- CA(AC1)
eigen_AC1 <- resul_AC1$eig ; eigen_AC1
##       eigenvalue percentage of variance cumulative percentage of variance
## dim 1 0.08305442                    100                               100
fviz_screeplot(resul_AC1, addlabels = TRUE, ylim = c(0, 100)) + 
  ggtitle("Scree Plot: Zona vs Tipo") +
  ylab("Porcentaje de varianza explicado") + 
  xlab("Ejes")

AC2 <- table(vivienda$tipo, vivienda$barrio)
chisq.test(AC2)
## 
##  Pearson's Chi-squared test
## 
## data:  AC2
## X-squared = 2468.3, df = 435, p-value < 2.2e-16
resul_AC2 <- CA(AC2)
eigen_AC2 <- resul_AC2$eig ; eigen_AC2
##       eigenvalue percentage of variance cumulative percentage of variance
## dim 1  0.2967032                    100                               100
fviz_screeplot(resul_AC1, addlabels = TRUE, ylim = c(0, 100)) + 
  ggtitle("Scree Plot: Zona vs Tipo") +
  ylab("Porcentaje de varianza explicado") + 
  xlab("Ejes")

AC3 <- table(vivienda$barrio, vivienda$zona)
chisq.test(AC3)
## 
##  Pearson's Chi-squared test
## 
## data:  AC3
## X-squared = 29343, df = 1740, p-value < 2.2e-16
resul_AC3 <- CA(AC3)

eigen_AC3 <- resul_AC3$eig ; eigen_AC3
##       eigenvalue percentage of variance cumulative percentage of variance
## dim 1  0.9619479               27.27168                          27.27168
## dim 2  0.9298275               26.36105                          53.63272
## dim 3  0.8951910               25.37909                          79.01181
## dim 4  0.7403118               20.98819                         100.00000
fviz_screeplot(resul_AC3, addlabels = TRUE, ylim = c(0, 80))+ggtitle("")+
ylab("Porcentaje de varianza explicado") + xlab("Ejes")

acp_resultado <- CA(tabla_contingencia) # Análisis de Correspondencia

PRUEBA CHI-CUADRADO Para las tres combinaciones de las variables evaluadas:

La hipótesis nula en una prueba Chi-cuadrado es que no existe asociación entre las dos variables categóricas. La hipótesis alternativa es que sí existe una relación entre las variables categóricas. En los tres casos, los valores p son muy pequeños, menos que 0.05, lo que deja entrever que hay evidencia estadística suficiente para rechazar la hipótesis nula de que las variables no están relacionadas.

ANÁLISIS DE CORRESPONDENCIA ENTRE VARIABLES CATEGORICAS

Variables Tipo - Zona y Tipo - Barrio

En estos dos casos era previsible y evidente que solo una dimensión es significativa, de hecho la variable tipo al tener solo dos clases en combinación con las otras dos variables no aporta grandes sorpresas en este análisis. Esto lo podemos determinar por lo siguiente:

Variables Zona y Barrio

Esta combinación de variables categóricas explica los datos mediante cuatro dimensiones:

7. CONCLUSIONES

El análisis integral y multidimensional de la base de datos inmobiliaria de la Ciudad de Calí nos ha permitido tener una visión detallada de las dinámicas que rigen el mercado urbano en este municipio. Mediante la aplicación de técnicas avanzadas como el Análisis de Componentes Principales, el Análisis de Conglomerados y el Análisis de Correspondencia, se ha logrado identificar patrones clave que explican la variabilidad en los precios y la oferta inmobiliaria, así como las relaciones entre variables categóricas que afectan el comportamiento del mercado en diversas zonas y estratos socioeconómicos. La visualización de estos resultados ha facilitado la comprensión de los hallazgos y proporcionado una base sólida para la toma de decisiones estratégicas.

Con los resultados obtenidos, la empresa inmobiliaria cuenta con herramientas valiosas para optimizar sus inversiones, segmentar el mercado de manera más eficiente y, en última instancia, maximizar los beneficios. Este análisis no solo ofrece una ventaja competitiva significativa en un entorno dinámico, sino que también contribuye a una mejor comprensión del mercado, permitiendo a la empresa anticipar tendencias y adaptarse rápidamente a los cambios del sector inmobiliario.

EXITOS