1. Análisis de Componentes Principales:

Reducir la dimensionalidad del conjunto de datos y visualizar la estructura de las variables en componentes principales para identificar características clave que influyen en la variación de precios y oferta del mercado.

1.1. Cargue del paquete.

#install.packages("devtools")
#devtools::install_github("dgonxalex80/paqueteMODELOS", force = TRUE)
library(paqueteMODELOS)
## Cargando paquete requerido: boot
## Cargando paquete requerido: broom
## Cargando paquete requerido: GGally
## Cargando paquete requerido: ggplot2
## Registered S3 method overwritten by 'GGally':
##   method from   
##   +.gg   ggplot2
## Cargando paquete requerido: gridExtra
## Cargando paquete requerido: knitr
## Cargando paquete requerido: summarytools
data(vivenda)
## Warning in data(vivenda): data set 'vivenda' not found
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>

1.2. Análisis exploratorio

1.2.1. Resumen estadístico de las viviendas:

summary(vivienda) 
##        id           zona               piso              estrato     
##  Min.   :   1   Length:8322        Length:8322        Min.   :3.000  
##  1st Qu.:2080   Class :character   Class :character   1st Qu.:4.000  
##  Median :4160   Mode  :character   Mode  :character   Median :5.000  
##  Mean   :4160                                         Mean   :4.634  
##  3rd Qu.:6240                                         3rd Qu.:5.000  
##  Max.   :8319                                         Max.   :6.000  
##  NA's   :3                                            NA's   :3      
##     preciom         areaconst       parqueaderos        banios      
##  Min.   :  58.0   Min.   :  30.0   Min.   : 1.000   Min.   : 0.000  
##  1st Qu.: 220.0   1st Qu.:  80.0   1st Qu.: 1.000   1st Qu.: 2.000  
##  Median : 330.0   Median : 123.0   Median : 2.000   Median : 3.000  
##  Mean   : 433.9   Mean   : 174.9   Mean   : 1.835   Mean   : 3.111  
##  3rd Qu.: 540.0   3rd Qu.: 229.0   3rd Qu.: 2.000   3rd Qu.: 4.000  
##  Max.   :1999.0   Max.   :1745.0   Max.   :10.000   Max.   :10.000  
##  NA's   :2        NA's   :3        NA's   :1605     NA's   :3       
##   habitaciones        tipo              barrio             longitud     
##  Min.   : 0.000   Length:8322        Length:8322        Min.   :-76.59  
##  1st Qu.: 3.000   Class :character   Class :character   1st Qu.:-76.54  
##  Median : 3.000   Mode  :character   Mode  :character   Median :-76.53  
##  Mean   : 3.605                                         Mean   :-76.53  
##  3rd Qu.: 4.000                                         3rd Qu.:-76.52  
##  Max.   :10.000                                         Max.   :-76.46  
##  NA's   :3                                              NA's   :3       
##     latitud     
##  Min.   :3.333  
##  1st Qu.:3.381  
##  Median :3.416  
##  Mean   :3.418  
##  3rd Qu.:3.452  
##  Max.   :3.498  
##  NA's   :3

Teniendo en cuenta los resultados del resument estadístico presentado anteriormente, es importante evidenciar que las viviendas se encuentran distribuidas hacia un nivel socioeconómico alto, ya que la media es de 4.63, aunqe el estrato mínimo sea de 3 y el más alto de 6.

En cuanto al precio promedio, se identifica una alta variabilidad, con un rango entre 58 y 1.999 y una media de 433.9.

Por otro lado, también se visualiza una gran variabilidad de los metros cuadrados de las viviendas, ya que se encuentran entre 30 y 1.745, con una media de 174.9 m2.

La mayoría de las viviendas tienen entre 2 a 4 baños, así como entre 3 y 4 habitaciones.

1.2.2. Revisión de las variables de la base de datos.

head(vivienda)
## # 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>

1.2.3. Tipo de datos de las variables.

Se revisan el tipo de datos de cada variable, en donde se evidencia que “piso” es de tipo character; por lo tanto, es importante más adelante modificarlo a a tipo numérico, ya que de esta forma será posible utilizar cálculos para realizar comparaciones y análisis estadísticos, permitiendo realizar operaciones como promedios, distribuciones, etc.

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>

1.2.4. Revisión de los datos faltantes

Con el código a continuación, se evidencian las variables con datos faltantes, en donde “piso” y “parqueaderos” cuentan con mayor registros sin datos, siendo estos de 2.638 y 1.605 respectivamente.

n_nas_por_columna <- colSums(is.na(vivienda))
print(n_nas_por_columna)
##           id         zona         piso      estrato      preciom    areaconst 
##            3            3         2638            3            2            3 
## parqueaderos       banios habitaciones         tipo       barrio     longitud 
##         1605            3            3            3            3            3 
##      latitud 
##            3

1.3. Limpieza de datos

1.3.1. Utilización de la moda para los datos faltantes

Teniendo en cuenta que hay datos faltantes mayoritarios en las variables piso y parqueaderos, se revisa a profundidad la información de las variables tipo, zona y estrato, con el fin de poder crear un nuevo data frame con la moda, teniendo en cuenta estas tres variables.

Se crea nueva base llamado vivienda_modificado_2

vivienda_modificado_2 <- vivienda
library(dplyr)
## 
## Adjuntando el paquete: 'dplyr'
## The following object is masked from 'package:gridExtra':
## 
##     combine
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
# Función para calcular la moda
moda <- function(x) {
  uniq_x <- unique(x[!is.na(x)])
  uniq_x[which.max(tabulate(match(x, uniq_x)))]
}

# Calcular la moda para 'piso' y 'parqueadero' en cada combinación de 'tipo', 'zona', y 'estrato'
moda_imputacion <- vivienda_modificado_2 %>%
  group_by(tipo, zona, estrato) %>%
  summarise(
    moda_piso = moda(piso),
    moda_parqueaderos = moda(parqueaderos),
    .groups = 'drop'
  )
# Unir los resultados de moda con los datos originales
vivienda_modificado_2 <- vivienda_modificado_2 %>%
  left_join(moda_imputacion, by = c("tipo", "zona", "estrato"))

# Imputar los valores faltantes con la moda
vivienda_modificado_2 <- vivienda_modificado_2 %>%
  mutate(
    piso = ifelse(is.na(piso), moda_piso, piso),
    parqueaderos = ifelse(is.na(parqueaderos), moda_parqueaderos, parqueaderos)
  ) %>%
  select(-moda_piso, -moda_parqueaderos) # Eliminar las columnas auxiliares
n_nas_por_columna <- colSums(is.na(vivienda_modificado_2))
print(n_nas_por_columna)
##           id         zona         piso      estrato      preciom    areaconst 
##            3            3            9            3            2            3 
## parqueaderos       banios habitaciones         tipo       barrio     longitud 
##            6            3            3            3            3            3 
##      latitud 
##            3
# Identificar filas con al menos un valor NA
filas_con_na <- which(rowSums(is.na(vivienda_modificado_2)) > 0)

# Ver las filas con datos faltantes
vivienda_modificado_2[filas_con_na, ]
## # A tibble: 10 × 13
##       id zona   piso  estrato preciom areaconst parqueaderos banios habitaciones
##    <dbl> <chr>  <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
##  1  1132 Zona … <NA>        5     325        89            1      2            3
##  2  4999 Zona … <NA>        6    1000       400           NA      5           10
##  3  5165 Zona … <NA>        5     180        98            1      2            3
##  4  6937 Zona … <NA>        6    1350       212            3      4            3
##  5  1214 Zona … 01          5     148       150           NA      1            3
##  6  5475 Zona … <NA>        5     650       129           NA      2            1
##  7   482 Zona … <NA>        5     350       152            1      4            3
##  8    NA <NA>   <NA>       NA      NA        NA           NA     NA           NA
##  9    NA <NA>   <NA>       NA      NA        NA           NA     NA           NA
## 10    NA <NA>   <NA>       NA     330        NA           NA     NA           NA
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>

Como se ve en la tabla anterior, los registros faltantes están dentro de los mismos registros, para un total de 10, por lo tanto, es posible eliminar esas filas.

# Eliminar filas con datos faltantes
vivienda_modificado_2 <- na.omit(vivienda_modificado_2)
summary(is.na(vivienda_modificado_2))
##      id             zona            piso          estrato       
##  Mode :logical   Mode :logical   Mode :logical   Mode :logical  
##  FALSE:8312      FALSE:8312      FALSE:8312      FALSE:8312     
##   preciom        areaconst       parqueaderos      banios       
##  Mode :logical   Mode :logical   Mode :logical   Mode :logical  
##  FALSE:8312      FALSE:8312      FALSE:8312      FALSE:8312     
##  habitaciones       tipo           barrio         longitud      
##  Mode :logical   Mode :logical   Mode :logical   Mode :logical  
##  FALSE:8312      FALSE:8312      FALSE:8312      FALSE:8312     
##   latitud       
##  Mode :logical  
##  FALSE:8312
n_nas_por_columna <- colSums(is.na(vivienda_modificado_2))
print(n_nas_por_columna)
##           id         zona         piso      estrato      preciom    areaconst 
##            0            0            0            0            0            0 
## parqueaderos       banios habitaciones         tipo       barrio     longitud 
##            0            0            0            0            0            0 
##      latitud 
##            0

1.3.2. Eliminación de la variable ID, Longitud y Latitud dentro de la base de datos.

Se eliminan la columnas ya que no son reelevantes y ocasionan inconvenientes en el momentode la revision de los datos:

library(dplyr)

# Eliminar la columna 'ID'
vivienda_modificado_2 <- vivienda_modificado_2 %>% select(-id, -longitud, -latitud)


print(vivienda_modificado_2)
## # A tibble: 8,312 × 10
##    zona   piso  estrato preciom areaconst parqueaderos banios habitaciones tipo 
##    <chr>  <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl> <chr>
##  1 Zona … 01          3     250        70            1      3            6 Casa 
##  2 Zona … 01          3     320       120            1      2            3 Casa 
##  3 Zona … 01          3     350       220            2      2            4 Casa 
##  4 Zona … 02          4     400       280            3      5            3 Casa 
##  5 Zona … 01          5     260        90            1      2            3 Apar…
##  6 Zona … 01          5     240        87            1      3            3 Apar…
##  7 Zona … 01          4     220        52            2      2            3 Apar…
##  8 Zona … 01          5     310       137            2      3            4 Apar…
##  9 Zona … 02          5     320       150            2      4            6 Casa 
## 10 Zona … 02          5     780       380            2      3            3 Casa 
## # ℹ 8,302 more rows
## # ℹ 1 more variable: barrio <chr>
# Verificar las columnas del dataframe
print(names(vivienda_modificado_2))
##  [1] "zona"         "piso"         "estrato"      "preciom"      "areaconst"   
##  [6] "parqueaderos" "banios"       "habitaciones" "tipo"         "barrio"

1.3.3. Se convierte la variable “piso” a numérica.

# Convertir la variable 'piso' de character a numeric
vivienda_modificado_2$piso <- as.numeric(vivienda_modificado_2$piso)

# Verificar la conversión
str(vivienda_modificado_2$piso)
##  num [1:8312] 1 1 1 2 1 1 1 1 2 2 ...

1.3.4 Se revisan los registros en cero

# Contar cuántos registros tienen valor cero en cada variable
zero_counts <- sapply(vivienda_modificado_2, function(x) sum(x == 0, na.rm = TRUE))

# Mostrar los resultados
print(zero_counts)
##         zona         piso      estrato      preciom    areaconst parqueaderos 
##            0            0            0            0            0            0 
##       banios habitaciones         tipo       barrio 
##           45           66            0            0

Según el código anterior, se evidencia que variables importantes como baños y habitaciones, cuentan con 45 y 66 registros en cero respectivamente, lo que hace necesario normalizarlas ya que no es posible que una vivienda cuente con cero baños y habitaciones y menos aún teniendo en cuenta la estratificación de las viviendas, las cuales oscilan entre 3 y 6. Por lo tanto, se imputa la moda.

# Función para calcular la moda
calculate_mode <- function(x) {
  unique_x <- unique(x)
  unique_x[which.max(tabulate(match(x, unique_x)))]
}

# Calcular la moda para 'habitaciones' y 'banios' sin contar los ceros
mode_habitaciones <- calculate_mode(vivienda_modificado_2$habitaciones[vivienda_modificado_2$habitaciones != 0])
mode_banios <- calculate_mode(vivienda_modificado_2$banios[vivienda_modificado_2$banios != 0])

# Imputar la moda en 'habitaciones' y 'banios' donde el valor es 0
vivienda_modificado_2$habitaciones[vivienda_modificado_2$habitaciones == 0] <- mode_habitaciones
vivienda_modificado_2$banios[vivienda_modificado_2$banios == 0] <- mode_banios

Volvemos a verificar que las correcciones anteriores hayan quedado en el data set:

str(vivienda_modificado_2)
## spc_tbl_ [8,312 × 10] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ zona        : chr [1:8312] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
##  $ piso        : num [1:8312] 1 1 1 2 1 1 1 1 2 2 ...
##  $ estrato     : num [1:8312] 3 3 3 4 5 5 4 5 5 5 ...
##  $ preciom     : num [1:8312] 250 320 350 400 260 240 220 310 320 780 ...
##  $ areaconst   : num [1:8312] 70 120 220 280 90 87 52 137 150 380 ...
##  $ parqueaderos: num [1:8312] 1 1 2 3 1 1 2 2 2 2 ...
##  $ banios      : num [1:8312] 3 2 2 5 2 3 2 3 4 3 ...
##  $ habitaciones: num [1:8312] 6 3 4 3 3 3 3 4 6 3 ...
##  $ tipo        : chr [1:8312] "Casa" "Casa" "Casa" "Casa" ...
##  $ barrio      : chr [1:8312] "20 de julio" "20 de julio" "20 de julio" "3 de julio" ...
##  - 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> 
##  - attr(*, "na.action")= 'omit' Named int [1:10] 987 1005 4913 5427 5814 6023 6115 8320 8321 8322
##   ..- attr(*, "names")= chr [1:10] "987" "1005" "4913" "5427" ...
# Contar cuántos registros tienen valor cero en cada variable
zero_counts <- sapply(vivienda_modificado_2, function(x) sum(x == 0, na.rm = TRUE))

# Mostrar los resultados
print(zero_counts)
##         zona         piso      estrato      preciom    areaconst parqueaderos 
##            0            0            0            0            0            0 
##       banios habitaciones         tipo       barrio 
##            0            0            0            0

1.3.5. Limpieza y unificación de los nombres de los barrios.

Se revisan los diferentes datos que están dentro de la variable “barrio”:

summary(vivienda_modificado_2$barrio)
##    Length     Class      Mode 
##      8312 character character
#print(unique(vivienda_modificado_2$barrio))

tabla_barrio <- table(vivienda_modificado_2$barrio)
#print(tabla_barrio)

Tenieneo en cuenta que de las tres variables más importantes para realizar la clasificación y conjuntos de datos, la bariable “barrio” cuenta con nombres diferentes para un solo lugar, se hace necesario crear un nuevo data frame en donde se unifiquen estos datos.

# Cargar las bibliotecas necesarias
library(dplyr)
library(ggplot2)
library(tidyr)

# Corrección de barrios
barrio_corregido <- table(vivienda_modificado_2$barrio) %>% data.frame()

# Se corrigen todos los nombres de los barrios y se llevan a minúscula
vivienda_modificado_2$barrio <- tolower(vivienda_modificado_2$barrio)

# Se eliminan las tildes
vivienda_modificado_2$barrio <- iconv(vivienda_modificado_2$barrio, to = "ASCII//TRANSLIT")

# Se hace limpieza de los nombres de los barrios
vivienda_modificado_2 <- vivienda_modificado_2 %>%
  mutate(barrio = ifelse(barrio == "agua blanca", "aguablanca", barrio),
         barrio = ifelse(barrio == "alf?crez real", "alferez real", barrio),
         barrio = ifelse(barrio == "alfonso lopez i", "alfonso lopez", barrio),
         barrio = ifelse(barrio == "arboledas", "arboleda", barrio),
         barrio = ifelse(barrio == "cali bella", "calibella", barrio),
         barrio = ifelse(barrio == "cali canto", "calicanto", barrio),
         barrio = ifelse(barrio == "calicanto viii", "calicanto", barrio),
         barrio = ifelse(barrio == "ciudad mel?cndez", "ciudadela melendez", barrio),
         barrio = ifelse(barrio == "ciudad melendez", "ciudadela melendez", barrio),
         barrio = ifelse(barrio == "ciudadela pasoancho", "ciudadela paso ancho", barrio),
         barrio = ifelse(barrio == "el tr?cbol", "el troncal", barrio),
         barrio = ifelse(barrio == "ingenio i", "el ingenio i", barrio),
         barrio = ifelse(barrio == "ingenio ii", "el ingenio ii", barrio),
         barrio = ifelse(barrio == "laflora", "la flora", barrio),
         barrio = ifelse(barrio == "las am?cricas", "las americas", barrio),
         barrio = ifelse(barrio == "las vegas de", "las vegas", barrio),
         barrio = ifelse(barrio == "mel?cndez", "melendez", barrio),
         barrio = ifelse(barrio == "pampalinda", "pampa linda", barrio),
         barrio = ifelse(barrio == "portales de comfandi", "portada de comfandi", barrio),
         barrio = ifelse(barrio == "rep??blica de israel", "republica de israel", barrio),
         barrio = ifelse(barrio == "rincon de la", "rincon de salomia", barrio),
         barrio = ifelse(barrio == "san judas", "san judas tadeo", barrio),
         barrio = ifelse(barrio == "tequendeme", "tequendama", barrio),
         barrio = ifelse(barrio == "valle de lili", "valle del lili", barrio),
         barrio = ifelse(barrio == "zona norte los", "zona norte", barrio))

1.4. Gráficos principales

# Número de viviendas por tipo

numero_viviendas_tipo <- table(vivienda_modificado_2$tipo)
suma_total_tipo_vivienda <- sum(numero_viviendas_tipo) # Se suman las viviendas por tipo

barplot((numero_viviendas_tipo), 
        col = c("skyblue", "grey"),
        xlab = "Tipo", ylab = "Cantidades",
        main = "NUmero de viviendas por tipo")

for (i in seq_along(numero_viviendas_tipo)) {
  text(i, numero_viviendas_tipo[i], label = paste(numero_viviendas_tipo[i], sprintf("(%.1f%%)", numero_viviendas_tipo[i]/suma_total_tipo_vivienda*100)), pos = 1)
}
legend("topright", legend = names(numero_viviendas_tipo), fill = c("skyblue", "skyblue")
)

# Número de viviendas por zona

numero_viviendas_zona <- table(vivienda_modificado_2$zona)
suma_total_zona_vivienda <- sum(numero_viviendas_zona) # Se suman las viviendas por zona

barplot((numero_viviendas_zona), 
        col = c("skyblue", "grey", "darkblue", "lightblue", "darkgrey"),
        xlab = "Zona", ylab = "Cantidades",
        main = "Numero de viviendas por zona")

for (i in seq_along(numero_viviendas_zona)) {
  text(i, numero_viviendas_zona[i], label = paste(numero_viviendas_zona[i], sprintf("(%.1f%%)", numero_viviendas_zona[i]/suma_total_zona_vivienda*100)), pos = 1)
}

# Número de viviendas por estrato

numero_viviendas_estrato <- table(vivienda_modificado_2$estrato)
suma_total_estrato_vivienda <- sum(numero_viviendas_estrato) # Se suman las viviendas por zona

barplot((numero_viviendas_estrato), 
        col = c("skyblue", "grey", "darkblue", "lightblue", "darkgrey"),
        xlab = "Estrato", ylab = "Cantidades")

for (i in seq_along(numero_viviendas_estrato)) {
  text(i, numero_viviendas_estrato[i], label = paste(numero_viviendas_estrato[i], sprintf("(%.1f%%)", numero_viviendas_estrato[i]/suma_total_estrato_vivienda*100)), pos = 1)
}
legend("topright", legend = names(numero_viviendas_estrato), fill = c("skyblue", "skyblue")
)

# Precio promedio de las viviendas por tipo

promedio_valor_tipo <- tapply(vivienda_modificado_2$preciom, vivienda_modificado_2$tipo, mean)

barplot((promedio_valor_tipo),
  col = c("skyblue","grey"),
  xlab = "Tipo", ylab = "Promedio",
  main = "Precio promedio de las viviendas por tipo")

# Agregamos valores al gráfico

text(
  barplot(promedio_valor_tipo, col = "skyblue", plot = FALSE),
  promedio_valor_tipo,
  labels = sprintf("%.0f", promedio_valor_tipo),
  pos = 1,
  col = "black",
  cex = 0.8
)

# Precio promedio de las viviendas por zona

promedio_valor_viviendas_zona <- tapply(vivienda_modificado_2$preciom, vivienda_modificado_2$zona, mean)

barplot((promedio_valor_viviendas_zona),
  col = c("skyblue", "grey", "darkblue", "lightblue", "darkgrey"),
  xlab = "Zona", ylab = "Promedio",
  main = "Precio promedio de las viviendas por zona")

# Agregamos valores al gráfico

text(
  barplot(promedio_valor_viviendas_zona, col = "skyblue", plot = FALSE),
  promedio_valor_viviendas_zona,
  labels = sprintf("%.0f", promedio_valor_viviendas_zona),
  pos = 1,
  col = "black",
  cex = 0.8
)

# Precio máximo de las viviendas por zona

precio_maximo_viviendas_zona <- tapply(vivienda_modificado_2$preciom, vivienda_modificado_2$zona, max)

barplot((precio_maximo_viviendas_zona),
  col = c("skyblue", "grey", "darkblue", "lightblue", "darkgrey"),
  xlab = "Zona", ylab = "Precio",
  main = "Precio maximo de las viviendas por zona")

# Agregamos valores al gráfico

text(
  barplot(precio_maximo_viviendas_zona, col = "skyblue", plot = FALSE),
  precio_maximo_viviendas_zona,
  labels = sprintf("%.0f", precio_maximo_viviendas_zona),
  pos = 1,
  col = "black",
  cex = 0.8
)

1.5. Identificación de Componentes:

A continuación se realiza el análisis de los componentes principales:

# Con el siguiente código se seleccionan solo las columnas numéricas:

data_numeric <- vivienda_modificado_2[, sapply(vivienda_modificado_2, is.numeric)]

# Se estandarizan los datos:

data_scaled <- scale(data_numeric)

# Se realiza el análisis de componentes principales:

resultado_analisis_componentes_principales_2 <- prcomp(data_scaled, center = TRUE, scale. = TRUE)

# Se visualian los datos:

summary(resultado_analisis_componentes_principales_2)
## Importance of components:
##                           PC1    PC2    PC3    PC4     PC5     PC6     PC7
## Standard deviation     1.8810 1.1828 0.9026 0.6848 0.60107 0.48302 0.42969
## Proportion of Variance 0.5054 0.1999 0.1164 0.0670 0.05161 0.03333 0.02638
## Cumulative Proportion  0.5054 0.7053 0.8217 0.8887 0.94029 0.97362 1.00000
identificacion_componentes_principales_2 <- resultado_analisis_componentes_principales_2$rotation

# Ver las cargas para los primeros componentes
identificacion_componentes_principales_2[, 1:6]  # Muestra las cargas para PC1 y PC2
##                     PC1        PC2         PC3         PC4         PC5
## piso          0.1233824 -0.5097239  0.83952589  0.12449399  0.03649808
## estrato      -0.3031274 -0.5522896 -0.23506651 -0.56801182 -0.04239433
## preciom      -0.4670897 -0.2232240 -0.04891294  0.08987985  0.30763839
## areaconst    -0.4468059  0.1841179  0.07504580  0.28400769  0.65286066
## parqueaderos -0.4186572 -0.1891176 -0.12913065  0.61745514 -0.60532938
## banios       -0.4608633  0.0906164  0.20718848 -0.36719310 -0.17824463
## habitaciones -0.2956577  0.5544840  0.41510706 -0.23883770 -0.27893975
##                      PC6
## piso          0.04547167
## estrato       0.45141705
## preciom      -0.23569101
## areaconst     0.24661643
## parqueaderos  0.10384991
## banios       -0.67461139
## habitaciones  0.46032568

Con la tabla anterior, se muestra como entre los dos componentes principales (PC1 y PC2) explican el 70.54% de la varianza, lo que indica que son los dos componentes que capturan la mayor parte de los datos.

Anteriormente, sin la eliminación de las variables latitud y longitud, la explicación de la variable en PC1 & PC2 se encontraba en solo 64.35%; por lo tanto, resultó eficiente haber eliminado las variables poco reelevantes.

Las variables más importantes para PC1 resultan ser las variables PrecioM, baños, área construída y parqueadero, lo que hace que PC1 esté influenciado por estas variables.

library(dplyr)

# Proporción de varianza explicada
varianza_explicada_2 <- summary(resultado_analisis_componentes_principales_2)$importance[2, ]

# Crear el scree plot
barplot(varianza_explicada_2, 
        main = "Proporcion de Varianza Explicada por Componentes Principales",
        xlab = "Componentes Principales", 
        ylab = "Proporcion de Varianza Explicada",
        col = "skyblue")

# Crear un biplot
biplot(resultado_analisis_componentes_principales_2, 
       main = "Biplot de analisis de Componentes Principales",
       xlab = "Componente Principal 1",
       ylab = "Componente Principal 2",
       col = c("grey", "red"))

Con el gráfico anterior, el eje “x” representa el componente principal, mientras el eje “y” el segundo componente que captura la mayor parte de la varianza.

La variable habitaciones, al tener la longitud de la flecha más larga que el resto, indica que tiene mayor contribución; por otro lado, también muestra una correlación negativa con la variable piso, lo que indica que mientras una aumenta la otra disminuye.

Se visualiza una correlación positiva entre el área construída y el número de baños, indicando que ambos incrementan al mismo

1.6. Reducción de la dimensionalidad del conjunto de los datos

Teniendo en cuenta que los dos primeros PC están capturando el 70.54% de los datos, se realiza la reducción de la dimensionalidad del conjunto de los datos a 2 PC.

# Se usan los dos primeros PC
data_reduced_2 <- predict(resultado_analisis_componentes_principales_2)[, 1:2]  
# Extraer las cargas de los componentes principales del resultado del PCA
identificacion_componentes_principales_2 <- resultado_analisis_componentes_principales_2$rotation

# Seleccionar las cargas de los dos primeros componentes principales (PC1 a PC2), ya que juntos, explican el 78.49% de la varianza.
cargas_cuatro_componentes_principales_2 <- identificacion_componentes_principales_2[, 1:2]

# Mostrar las cargas para los primeros cuatro componentes principales
print(cargas_cuatro_componentes_principales_2)
##                     PC1        PC2
## piso          0.1233824 -0.5097239
## estrato      -0.3031274 -0.5522896
## preciom      -0.4670897 -0.2232240
## areaconst    -0.4468059  0.1841179
## parqueaderos -0.4186572 -0.1891176
## banios       -0.4608633  0.0906164
## habitaciones -0.2956577  0.5544840

Con la tabla anterior, se muestra que PC1 captura la variabilidad asociada a las variables como: precio promedio, area construída, parqueaderos y baños; por otro lado, PC2 está influenciado por piso, estrato y habiaciones.

if (!require(psych)) install.packages("psych")
## Cargando paquete requerido: psych
## 
## Adjuntando el paquete: 'psych'
## The following objects are masked from 'package:ggplot2':
## 
##     %+%, alpha
## The following object is masked from 'package:boot':
## 
##     logit
library(psych)


cargas_originales <- resultado_analisis_componentes_principales_2$rotation

rotacion_varimax <- varimax(cargas_originales)

cargas_rotadas <- rotacion_varimax$loadings


print(round(cargas_rotadas, 2))
## 
## Loadings:
##              PC1 PC2 PC3 PC4 PC5 PC6 PC7
## piso                  1                 
## estrato       1                         
## preciom                               1 
## areaconst                     1         
## parqueaderos              1             
## banios                           -1     
## habitaciones      1                     
## 
##                  PC1   PC2   PC3   PC4   PC5   PC6   PC7
## SS loadings    1.000 1.000 1.000 1.000 1.000 1.000 1.000
## Proportion Var 0.143 0.143 0.143 0.143 0.143 0.143 0.143
## Cumulative Var 0.143 0.286 0.429 0.571 0.714 0.857 1.000

Con la tabla anterior, se tiene un análisis de los componentes principales al aplicar una rotación Varimax con el fin de facilitar la interpretación de cargas.

En el PC1 la variable estrato está fuertemente asociada, capturando una gran cantidad de datos; en cuanto a PC2, el número de habitaciones tiene una gran predominancia.

en Proportion var, se evidencia que cada componente explica el 14.3% de la varianza totales en los datos, lo que indica que la varianza está distribuída equitativamente.

# Crear un biplot de los primeros dos componentes principales
biplot(resultado_analisis_componentes_principales_2, scale = 0)

Con el gráfico anterior, se evidencia que la variable piso tiene una carga positiva hacia el componente principal (PC1), así que mientras esta variable incrementa se genera un aumento en el primer componente principal. En cuanto a la variable habitaciones, a diferencia de piso, cuenta con una carga negativa frente a PC1, por lo tanto, si esta variable incrementa, el valor de PC1 disminuye.

Por otro lado, en cuanto a PC2, las variables área construída y baño cuentan con una carga similar hacia dicho componente, generando un crecimiento entre ambas; así que se podría decir que si el área construída incrementa, también incrementará el número de baños, disminuyendo el valor del componente principal (PC1).

  if (!require(factoextra)) install.packages("factoextra")
## Cargando paquete requerido: factoextra
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
  library(factoextra)
  
  # Visualizar las variables en función de su contribución a los primeros componentes principales
  fviz_pca_var(resultado_analisis_componentes_principales_2,
                col.var = "contrib", # Color por contribuciones a los PC
                gradient.cols = c("#FF7F00", "#034D94"), # Colores del gradiente
                repel = TRUE, # Evitar superposición de texto
                axes = c(1, 2) # Cambiar los ejes a los primeros dos componentes principales
                )

Con el gráfico anterior, se evidencia como la variable “piso”, cuenta con una dirección de carga distinta a diferencia con las demás variables, pero teniendo una relación positiva hacia PC1. Por otro lado, la variable “estrato” también genera una relación positiva hacia el componente principal.

# Seleccionar solo columnas numéricas
vivienda_modificado_2_numeric <- vivienda_modificado_2[, sapply(vivienda_modificado_2, is.numeric)]

# Realizar el PCA con las columnas numéricas
resultado_analisis_componentes_principales <- prcomp(vivienda_modificado_2_numeric, center = TRUE, scale. = TRUE)
if (!require(factoextra)) install.packages("factoextra")
library(factoextra)

# Realizar el PCA en tu dataframe
resultado_analisis_componentes_principales_2 <- prcomp(vivienda_modificado_2_numeric, center = TRUE, scale. = TRUE)

# Extraer los resultados del PCA
res.pca <- resultado_analisis_componentes_principales_2

# Extraer las puntuaciones para los primeros cuatro componentes principales
puntuaciones <- res.pca$x[, 1:3]
colnames(puntuaciones) <- c("PC1", "PC2", "PC3")

# Crear un dataframe con puntuaciones específicas para casos de interés
casos1 <- rbind(puntuaciones[98, 1:3], puntuaciones[778, 1:3]) 
rownames(casos1) <- c("98", "778") 
casos1 <- as.data.frame(casos1)

casos2 <- rbind(puntuaciones[6, 1:3], puntuaciones[462, 1:3]) 
rownames(casos2) <- c("6", "462") 
casos2 <- as.data.frame(casos2)


fviz_pca_ind(res.pca, 
              col.ind = "#DEDEDE", 
              gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07")) +
  geom_point(data = casos1, aes(x = PC1, y = PC2), color = "red", size = 3) +
  geom_point(data = casos2, aes(x = PC1, y = PC2), color = "blue", size = 3) +
  labs(title = "Visualizacion de Casos Especificos en los Primeros Dos Componentes Principales") +
  theme_minimal()

2. Análisis de Conglomerados:

Agrupar las propiedades residenciales en segmentos homogéneos con características similares para entender las dinámicas de las ofertas específicas en diferentes partes de la ciudad y en diferentes estratos socioeconómicos.

# Cargar los datos
if (!exists("vivienda_modificado2"))
  
variables_clustering <- select(vivienda_modificado_2, estrato, preciom, areaconst, banios, habitaciones, parqueaderos)


# Seleccionar las variables relevantes para el clustering
variables_clustering <- select(vivienda_modificado_2, estrato, preciom, areaconst, banios, habitaciones, parqueaderos)

# Escalar las variables si es necesario
variables_clustering_scaled <- scale(variables_clustering)

# Determinar el número óptimo de clusters usando el método del codo
wcss <- numeric(10)
for (i in 1:10) {
  kmeans_fit <- kmeans(variables_clustering_scaled, centers = i, nstart = 10)
  wcss[i] <- kmeans_fit$tot.withinss
}

# Graficar el método del codo
plot(1:10, wcss, type = "b", pch = 19, frame = FALSE, 
     xlab = "Numero de clusters (k)", ylab = "WCSS")

fviz_nbclust(x = variables_clustering_scaled, FUNcluster = kmeans, method = "wss", k.max = 10, 
             diss = get_dist(variables_clustering_scaled, method = "euclidean"), nstart = 50)
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations

## 2.1. Resulado del clustering

2.1.1 Cinco componentes principales

library(cluster)

require(cluster)

pam.res <- pam(variables_clustering_scaled, 5)

fviz_cluster(pam.res, geom = "point", ellipse.type = "norm", show.clust.cent = TRUE, star.plot = TRUE) + labs(title = "Resultado del clustering 5 PC") + theme_bw()

### 2.1.2 Cuatro componentes principales

library(cluster)

require(cluster)

pam.res <- pam(variables_clustering_scaled, 4)

fviz_cluster(pam.res, geom = "point", ellipse.type = "norm", show.clust.cent = TRUE, star.plot = TRUE) + labs(title = "Resultado del clustering 4 PC") + theme_bw()

2.1.3 Tres componentes principales

library(cluster)

require(cluster)

pam.res <- pam(variables_clustering_scaled, 3)

fviz_cluster(pam.res, geom = "point", ellipse.type = "norm", show.clust.cent = TRUE, star.plot = TRUE) + labs(title = "Resultado del clustering 3 PC") + theme_bw()

2.1.4 Dos componentes principales

library(cluster)

require(cluster)

pam.res <- pam(variables_clustering_scaled, 2)

fviz_cluster(pam.res, geom = "point", ellipse.type = "norm", show.clust.cent = TRUE, star.plot = TRUE) + labs(title = "Resultado del clustering 2 PC") + theme_bw()

Con las graficas anteriores, se evidencia la gran cantidad de datos que recogen los dos primeros PC (PC1 & PC2); por lo tanto, no solo a nivel de cifras, sino también teniendo en cuenta aspectos visuales, se logra determinar la importancia de estos 2 PC para la captura de los datos.

2.2. Análisis de correlación

correlation_matriz <- cor(variables_clustering_scaled)

correlation_df <- as.data.frame(as.table(correlation_matriz))

colnames(correlation_df) <- c("Var1", "Var2", "Correlation")

# Convertir las correlaciones a porcentaje
correlation_df$Correlation_Percentage <- round(correlation_df$Correlation * 100, 1)

# Graficar la matriz de correlación como un mapa de calor con los valores de correlación en porcentaje
ggplot(data = correlation_df, aes(x = Var1, y = Var2, fill = Correlation)) +
  geom_tile() +
  scale_fill_gradient2(low = "green", high = "red", mid = "white", midpoint = 0) +
  theme_minimal() +
  labs(title = "Matriz de Correlación") +
  geom_text(aes(label = paste0(Correlation_Percentage, "%")), color = "black", size = 3)  # Agregar valores de correlación

Con la matriz de correlacion, es posible afirmar que no existe una estrecha y alta correlación entre la diferentes variables; sin embargo, es importante detectar aquellas tres variables con más alta correlación, las cuales son:

69.3%: parqueaderos y precio 68.8%: área construída y precio 68%: baños y precio

Por lo tanto, se puede evidenciar que el precio promedio cuenta con una correlacion entre diferentes variables.

2.3. Gráficos clusterizados

k_optimo <- 2

set.seed(444)

modelo_kmeans <- kmeans(variables_clustering_scaled, centers = k_optimo, nstart = 10)

cluster_labels <- as.factor(modelo_kmeans$cluster)

vivienda_limpia_con_cluster <- mutate(vivienda_modificado_2, cluster = cluster_labels)

# Visualizar los clusters en función de algunas variables relevantes
ggplot(vivienda_limpia_con_cluster, aes(x = estrato, y = preciom, color = cluster)) +
  geom_point() +
  labs(title = "Clusters de Propiedades Residenciales",
       x = "Estrato Socioeconómico",
       y = "Precio de la Propiedad") +
  theme_minimal() +
  scale_color_discrete(name = "Cluster")

Una vez el obtenido el gráfico por cada uno de los dos clusters, relacionados a las variables precio y estrato socieconómico, se evidencia que PC1 está afectado por las viviendas de menor precio en los diferentes estratos; en cambio, PC2 está asociado por las viviendas de precio alto.

ggplot(vivienda_limpia_con_cluster, aes(x = estrato, fill = cluster)) +
  geom_density(alpha = 0.5) +
  labs(title = "Distribución de Estrato por Cluster", x = "estrato", y = "Densidad") +
  theme_minimal()

Con la gráfica anterior, se evidencia que en la variable estrato, PC1 está muy influenciado por los estratos 3 y 4, a diferencia del PC2 que tiene una alta influencia por el estrato 6.

ggplot(vivienda_limpia_con_cluster, aes(x = preciom, fill = cluster)) +
  geom_density(alpha = 0.5) +
  labs(title = "Distribución de Precios por Cluster", x = "Precio", y = "Densidad") +
  theme_minimal()

En cuanto a la variable precio, el PC1 tiene gran influencia por las viviendas con precios hasta 400. El PC2 tiene una relación moderada entre 600 y 800.

ggplot(vivienda_limpia_con_cluster, aes(x = areaconst, fill = cluster)) +
  geom_density(alpha = 0.5) +
  labs(title = "Distribución de Área construída por Cluster", x = "areaconst", y = "Densidad") +
  theme_minimal()

Area construída tiene una fuerte densidad en viviendas con área construída hasta 125 metros en el cluster 1. En cuanto al cluster 2, la densidad es baja, sin embargo, se visualiza un alto desempeño en áreas construídas entre 120 y 270 metros.

ggplot(vivienda_limpia_con_cluster, aes(x = banios, fill = cluster)) +
  geom_density(alpha = 0.5) +
  labs(title = "Distribución de Baños por Cluster", x = "banios", y = "Densidad") +
  theme_minimal()

Por otro lado, en la variable baños la densidad más alta para el cluster 1, oscila entre 2 y 3 baños; en cambio, el cluster 2 tiene una densidad alta entre 4 y 5 baños.

ggplot(vivienda_limpia_con_cluster, aes(x = habitaciones, fill = cluster)) +
  geom_density(alpha = 0.5) +
  labs(title = "Distribución de Habitaciones por Cluster", x = "Habitaciones", y = "Densidad") +
  theme_minimal()

El Cluster 1 tiene una alta densidad entre 2.5 y 3 habitaciones.

ggplot(vivienda_limpia_con_cluster, aes(x = parqueaderos, fill = cluster)) +
  geom_density(alpha = 0.5) +
  labs(title = "Distribución de Parqueaderos por Cluster", x = "Parqueaderos", y = "Densidad") +
  theme_minimal()

El cluster 1 tiene una alta densidad cuando se tiene 1 parqueadero.

3. Análisis de Correspondencia

Examinar la relación entre las variables categóricas (tipo de vivienda, zona y barrio), para identificar patrones de comportamiento de la oferta en mercado inmobiliario.

library(FactoMineR)

library(ggplot2)

library(factoextra)

library(Rcpp)

library(pander)
## 
## Adjuntando el paquete: 'pander'
## The following object is masked from 'package:GGally':
## 
##     wrap
library(corrplot)
## corrplot 0.92 loaded
library(gridExtra)

datos_inicial <- subset(vivienda_modificado_2, select = c("barrio", "zona", "tipo",  "estrato"))

Barrio <- datos_inicial$barrio
Zona <- datos_inicial$zona
Tipo <- datos_inicial$tipo
Estrato <- datos_inicial$estrato

Datos <- cbind(datos_inicial, Barrio, Zona, Tipo, Estrato)

Datos[, 1:4] <- NULL

summary(Datos)
##     Barrio              Zona               Tipo              Estrato     
##  Length:8312        Length:8312        Length:8312        Min.   :3.000  
##  Class :character   Class :character   Class :character   1st Qu.:4.000  
##  Mode  :character   Mode  :character   Mode  :character   Median :5.000  
##                                                           Mean   :4.633  
##                                                           3rd Qu.:5.000  
##                                                           Max.   :6.000
F1<-ggplot(Datos, aes(x=Zona)) + geom_bar(fill= "red")
F2<-ggplot(Datos, aes(x=Tipo)) + geom_bar(fill= "#FFD4A5")
F3<-ggplot(Datos, aes(x=Barrio)) + geom_bar(fill= "green")
F4<-ggplot(Datos, aes(x=Estrato)) + geom_bar(fill= "orange")
F5 <- grid.arrange(F1,F2,F3,F4, nrow = 4)

## 3.1. Relación barrio y zona

library(FactoMineR)
tabla_barrio_zona <- table(Datos$Barrio, Datos$Zona)
head(tabla_barrio_zona, n=10)
##                  
##                   Zona Centro Zona Norte Zona Oeste Zona Oriente Zona Sur
##   20 de julio               0          0          0            3        0
##   3 de julio                0          0          0            0        1
##   acopi                     0        157          0            0        1
##   aguablanca                0          0          0            2        1
##   aguacatal                 0          0        108            0        1
##   alameda                  11          0          0            0        5
##   alameda del rio           0          3          0            0        0
##   alamos                    0         14          0            0        0
##   alborada                  0          0          0            0        1
##   alcazares                 0          2          0            0        0
chisq.test(tabla_barrio_zona)
## 
##  Pearson's Chi-squared test
## 
## data:  tabla_barrio_zona
## X-squared = 29302, df = 1476, p-value < 2.2e-16
library(FactoMineR)
library(factoextra)
library(gridExtra)
resultados_barrio_zona <- CA(tabla_barrio_zona)

library(factoextra)
eig.val <- get_eigenvalue(resultados_barrio_zona)
eig.val
##       eigenvalue variance.percent cumulative.variance.percent
## Dim.1  0.9622565         27.29569                    27.29569
## Dim.2  0.9290207         26.35291                    53.64860
## Dim.3  0.8963285         25.42555                    79.07415
## Dim.4  0.7377002         20.92585                   100.00000
fviz_screeplot(resultados_barrio_zona, addlabels = TRUE, ylim = c(0, 30))+ggtitle("")+
  ylab("Porcentaje de varianza explicado") + xlab("Ejes")

3.2. Relación barrio y tipo

library(FactoMineR)
tabla_barrio_tipo <- table(Datos$Barrio, Datos$Tipo)
head(tabla_barrio_tipo, n=10)
##                  
##                   Apartamento Casa
##   20 de julio               0    3
##   3 de julio                0    1
##   acopi                    88   70
##   aguablanca                1    2
##   aguacatal                98   11
##   alameda                   4   12
##   alameda del rio           2    1
##   alamos                   11    3
##   alborada                  0    1
##   alcazares                 2    0
chisq.test(tabla_barrio_tipo)
## 
##  Pearson's Chi-squared test
## 
## data:  tabla_barrio_tipo
## X-squared = 2416.6, df = 369, p-value < 2.2e-16
library(FactoMineR)
library(factoextra)
library(gridExtra)
resultados_barrio_tipo <- CA(tabla_barrio_tipo)
library(factoextra)
eig.val <- get_eigenvalue(resultados_barrio_tipo)
eig.val
##       eigenvalue variance.percent cumulative.variance.percent
## Dim.1   0.290731              100                         100

3.3. Relación barrio y estrato

library(FactoMineR)
tabla_barrio_estrato <- table(Datos$Barrio, Datos$Estrato)
head(tabla_barrio_estrato, n=10)
##                  
##                    3  4  5  6
##   20 de julio      3  0  0  0
##   3 de julio       0  1  0  0
##   acopi           21 51 59 27
##   aguablanca       3  0  0  0
##   aguacatal       20 24  4 61
##   alameda         13  3  0  0
##   alameda del rio  3  0  0  0
##   alamos           7  7  0  0
##   alborada         0  1  0  0
##   alcazares        2  0  0  0
chisq.test(tabla_barrio_estrato)
## 
##  Pearson's Chi-squared test
## 
## data:  tabla_barrio_estrato
## X-squared = 14864, df = 1107, p-value < 2.2e-16
library(FactoMineR)
library(factoextra)
library(gridExtra)
resultados_barrio_estrato <- CA(tabla_barrio_estrato)

library(factoextra)
eig.val <- get_eigenvalue(resultados_barrio_estrato)
eig.val
##       eigenvalue variance.percent cumulative.variance.percent
## Dim.1  0.8217910         45.95375                    45.95375
## Dim.2  0.6844665         38.27470                    84.22844
## Dim.3  0.2820429         15.77156                   100.00000
fviz_screeplot(resultados_barrio_estrato, addlabels = TRUE, ylim = c(0, 30))+ggtitle("")+
  ylab("Porcentaje de varianza explicado") + xlab("Ejes")

3.4. Relación zona y tipo

library(FactoMineR)
tabla_zona_tipo <- table(Datos$Zona, Datos$Tipo)
head(tabla_zona_tipo, n=10)
##               
##                Apartamento Casa
##   Zona Centro           21   98
##   Zona Norte          1198  722
##   Zona Oeste          1029  169
##   Zona Oriente          61  288
##   Zona Sur            2787 1939
chisq.test(tabla_zona_tipo)
## 
##  Pearson's Chi-squared test
## 
## data:  tabla_zona_tipo
## X-squared = 695.36, df = 4, p-value < 2.2e-16
library(FactoMineR)
library(factoextra)
library(gridExtra)
resultados_zona_tipo <- CA(tabla_zona_tipo)
library(factoextra)
eig.val <- get_eigenvalue(resultados_zona_tipo)
eig.val
##       eigenvalue variance.percent cumulative.variance.percent
## Dim.1 0.08365695              100                         100

3.5. Relación zona y estrato

library(FactoMineR)
tabla_zona_estrato <- table(Datos$Zona, Datos$Estrato)
head(tabla_zona_estrato, n=10)
##               
##                   3    4    5    6
##   Zona Centro   105   14    0    0
##   Zona Norte    572  407  769  172
##   Zona Oeste     54   84  290  770
##   Zona Oriente  340    8    1    0
##   Zona Sur      382 1616 1685 1043
chisq.test(tabla_zona_estrato)
## 
##  Pearson's Chi-squared test
## 
## data:  tabla_zona_estrato
## X-squared = 3867.7, df = 12, p-value < 2.2e-16
library(FactoMineR)
library(factoextra)
library(gridExtra)
resultados_zona_estrato <- CA(tabla_zona_estrato)

library(factoextra)
eig.val <- get_eigenvalue(resultados_zona_estrato)
eig.val
##       eigenvalue variance.percent cumulative.variance.percent
## Dim.1 0.32644555        70.155929                    70.15593
## Dim.2 0.12764789        27.432619                    97.58855
## Dim.3 0.01122083         2.411452                   100.00000
fviz_screeplot(resultados_zona_estrato, addlabels = TRUE, ylim = c(0, 30))+ggtitle("")+
  ylab("Porcentaje de varianza explicado") + xlab("Ejes")

3.6. Relación tipo y estrato

library(FactoMineR)
tabla_tipo_estrato <- table(Datos$Tipo, Datos$Estrato)
head(tabla_tipo_estrato, n=10)
##              
##                  3    4    5    6
##   Apartamento  639 1404 1763 1290
##   Casa         814  725  982  695
chisq.test(tabla_tipo_estrato)
## 
##  Pearson's Chi-squared test
## 
## data:  tabla_tipo_estrato
## X-squared = 224.45, df = 3, p-value < 2.2e-16
library(FactoMineR)
library(factoextra)
library(gridExtra)
resultados_tipo_estrato <- CA(tabla_tipo_estrato)
library(factoextra)
eig.val <- get_eigenvalue(resultados_tipo_estrato)
eig.val
##       eigenvalue variance.percent cumulative.variance.percent
## Dim.1  0.0270036              100                         100

4. Visualización de resultados:

Presentar gráficos, mapas y otros recursos visuales para comunicar los hallazgos de manera clara y efectiva a la dirección de la empresa.

4.1. Comportamiento de variables con clusters

require(cluster)
cluster <- pam.res$clustering

x <- data.frame(vivienda_limpia_con_cluster, cluster)

summary(x)
##      zona                piso           estrato         preciom      
##  Length:8312        Min.   : 1.000   Min.   :3.000   Min.   :  58.0  
##  Class :character   1st Qu.: 2.000   1st Qu.:4.000   1st Qu.: 220.0  
##  Mode  :character   Median : 3.000   Median :5.000   Median : 330.0  
##                     Mean   : 3.461   Mean   :4.633   Mean   : 433.8  
##                     3rd Qu.: 5.000   3rd Qu.:5.000   3rd Qu.: 540.0  
##                     Max.   :12.000   Max.   :6.000   Max.   :1999.0  
##    areaconst       parqueaderos        banios        habitaciones   
##  Min.   :  30.0   Min.   : 1.000   Min.   : 1.000   Min.   : 1.000  
##  1st Qu.:  80.0   1st Qu.: 1.000   1st Qu.: 2.000   1st Qu.: 3.000  
##  Median : 123.0   Median : 1.000   Median : 3.000   Median : 3.000  
##  Mean   : 174.9   Mean   : 1.713   Mean   : 3.122   Mean   : 3.629  
##  3rd Qu.: 229.0   3rd Qu.: 2.000   3rd Qu.: 4.000   3rd Qu.: 4.000  
##  Max.   :1745.0   Max.   :10.000   Max.   :10.000   Max.   :10.000  
##      tipo              barrio          cluster    cluster.1    
##  Length:8312        Length:8312        1:5683   Min.   :1.000  
##  Class :character   Class :character   2:2629   1st Qu.:1.000  
##  Mode  :character   Mode  :character            Median :1.000  
##                                                 Mean   :1.483  
##                                                 3rd Qu.:2.000  
##                                                 Max.   :2.000
boxplot(x$preciom ~ x$cluster, 
        main = "Distribución del precio de metro cuadrado por cluster",
        ylab = "Precio del metro cuadrado",
        xlab = "Cluster", las = 1,
        col = c("#0d3b66", "#f4d353"))

boxplot(x$areaconst ~ x$cluster, 
        main = "Distribución del área construída por cluster",
        ylab = "Precio del metro cuadrado",
        xlab = "Cluster", las = 1,
        col = c("#0d3b66", "#f4d353"))

boxplot(x$habitaciones ~ x$cluster, 
        main = "Distribución del número de habitaciones por cluster",
        ylab = "Precio del metro cuadrado",
        xlab = "Cluster", las = 1,
        col = c("#0d3b66", "#f4d353"))

boxplot(x$banios ~ x$cluster, 
        main = "Distribución de la cantidad de baños por cluster",
        ylab = "Precio del metro cuadrado",
        xlab = "Cluster", las = 1,
        col = c("#0d3b66", "#f4d353"))

boxplot(x$piso ~ x$cluster, 
        main = "Distribución del número de pisos por cluster",
        ylab = "Precio del metro cuadrado",
        xlab = "Cluster", las = 1,
        col = c("#0d3b66", "#f4d353"))

conteo <- table(x$cluster, x$zona)

barplot(conteo, main = "Tipo de viviendas por zona",
        xlab = "Categorias",
        col = c("#0d3b66", "#f4d353"), 
        legend = rownames(conteo))

conteo <- table(x$cluster, x$tipo)

barplot(conteo, main = "Tipo de vivienda (casa o apartamento)",
        xlab = "Categorias",
        col = c("#0d3b66", "#f4d353"), 
        legend = rownames(conteo))

# Sumar el total de casas
total_casas <- sum(x$tipo == "Casa", na.rm = TRUE)


# Sumar el total de apartamentos
total_apartamentos <- sum(x$tipo == "Apartamento", na.rm = TRUE)

cat("Total de Apartamentos:", total_apartamentos, "\n")
## Total de Apartamentos: 5096
cat("Total de Casas:", total_casas, "\n")
## Total de Casas: 3216

Como se ven en las gráficas anteriores, PC1 está afectado por viviendas cuyo precio es bajo, con poca area construída y pocos baños, también con gran número de pisos, sobre todo en viviendas tipo apartamento.

En cuanto al PC2, este se encuetnra afectado por viviendas con precio y tamaño alto, con alto número de baños y pocos pisos; de esta forma, resulta ser lo contrario al PC1.

4.2. Gráfico de líneas con línea de tendencia

# Gráfico de líneas con línea de tendencia
ggplot(vivienda_modificado_2, aes(x = areaconst, y = preciom)) +
  geom_line(color = "blue", size = 1) +  
  geom_smooth(method = "lm", color = "red", se = FALSE) +  
  labs(title = "Precio Medio en Función del Área Construida", x = "Área Construida", y = "Precio Medio") +
  theme_minimal()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## `geom_smooth()` using formula = 'y ~ x'

Se evidencia que el precio promedio está en función del tamaño de las viviendas, puesto que a mayor tamaño resultan ser más costosas.

# Gráfico de líneas con línea de tendencia
ggplot(vivienda_modificado_2, aes(x = estrato, y = preciom)) +
  geom_line(color = "blue", size = 1) +  
  geom_smooth(method = "lm", color = "red", se = FALSE) + 
  labs(title = "Precio Medio en función del Estrato", x = "Estrato", y = "Precio Medio") +
  theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'

Con el gráfico anterior, se evidencia el precio promedio por cada una de los estratos; la línea roja, representa la tendencia de los precios a través del método de regresión linea. De esta forma, se evidencia qeu que el incremento real del valor de las viviendas de los estratos 5 al 6, está en línea con la ergresión lineal, a diferencia del precio promedio de las viviendas de estrato 4 a 5.

# Gráfico de líneas con línea de tendencia

ggplot(vivienda_modificado_2, aes(x = areaconst, y = habitaciones)) +
  geom_line(color = "blue", size = 1) +  
  geom_smooth(method = "lm", color = "red", se = FALSE) +  
  labs(title = "Número de Habitaciones en Función del Área Construida", 
       x = "Área Construida (m²)", 
       y = "Número de Habitaciones") +
  theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'

# Gráfico de líneas con línea de tendencia

ggplot(vivienda_modificado_2, aes(x = banios, y = habitaciones)) +
  geom_line(color = "blue", size = 1) +  
  geom_smooth(method = "lm", color = "red", se = FALSE) +  
  labs(title = "Número de habitaciones en Función al número de baños", 
       x = "Número de baños", 
       y = "Número de Habitaciones") +
  theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'

# Gráfico de líneas con línea de tendencia

ggplot(vivienda_modificado_2, aes(x = areaconst, y = piso)) +
  geom_line(color = "blue", size = 1) +  
  geom_smooth(method = "lm", color = "red", se = FALSE) +  
  labs(title = "Número de Pisos en Función del Área Construida - total viviendas", x = "Área Construida (m²)", y = "Número de Pisos") +
  theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'

Con el gráfico anterior, se evidencia que entre menor área construída se cuenta con más pisos.

Teniendo en cuenta que esto podría generar un sesgo en la interpretación y toma de decisiones por parte de la empresa, se desagrega el gráfico a nivel de viviendas tipo casa y viviendas tipo apartamento.

# Filtrar los datos para tipo "Casa" y graficar
ggplot(data = subset(vivienda_modificado_2, tipo == "Casa"), aes(x = areaconst, y = piso)) +
  geom_line(color = "blue", size = 1) +  
  geom_smooth(method = "lm", color = "red", se = FALSE) +  
  labs(title = "Número de Pisos en Función del Área Construida - casas", x = "Área Construida (m²)", y = "Número de Pisos") +
  theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'

# Filtrar los datos para tipo "Casa" y graficar
ggplot(data = subset(vivienda_modificado_2, tipo == "Apartamento"), aes(x = areaconst, y = piso)) +
  geom_line(color = "blue", size = 1) +  
  geom_smooth(method = "lm", color = "red", se = FALSE) +  
  labs(title = "Número de Pisos en Función del Área Construida - apartamento", x = "Área Construida (m²)", y = "Número de Pisos") +
  theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'

Se evidencia que para las viviendas tipo casa, el area construída no afecta el número de pisos, ya que mantienen una media muy estable. En cuanto a las viviendas tipo apartamento, el área construída si genera un cambio en el número de pisos, mostrando una tendencia incremental leve.

4.2. Gráfico de proporción

# Contar el número de viviendas por estrato
estrato_count <- as.data.frame(table(vivienda_modificado_2$estrato))
colnames(estrato_count) <- c("estrato", "count")

# Calcular el porcentaje
estrato_count <- estrato_count %>%
  mutate(percentage = count / sum(count) * 100)

# Crear gráfico de pastel con porcentajes
ggplot(estrato_count, aes(x = "", y = count, fill = estrato)) +
  geom_bar(width = 1, stat = "identity") +
  coord_polar(theta = "y") +
  geom_text(aes(label = sprintf("%.1f%%", percentage)), position = position_stack(vjust = 0.5), color = "white") +
  scale_fill_manual(values = c("#1f77b4", "#aec7e8", "#d62728", "#ff7f0e", "#ffbb78")) +
  labs(title = "Proporción de Estratos", x = "", y = "") +
  theme_void()

# Contar el número de pisos
piso_count <- as.data.frame(table(vivienda_modificado_2$piso))
colnames(piso_count) <- c("piso", "count")

# Calcular el porcentaje
piso_count <- piso_count %>%
  mutate(percentage = count / sum(count) * 100)

num_colors <- length(unique(piso_count$piso))

# Crear gráfico de pastel con porcentajes
ggplot(piso_count, aes(x = "", y = count, fill = as.factor(piso))) +
  geom_bar(width = 1, stat = "identity") +
  coord_polar(theta = "y") +
  geom_text(aes(label = sprintf("%.1f%%", percentage)), 
            position = position_stack(vjust = 0.5), 
            color = "white") +
  scale_fill_manual(values = RColorBrewer::brewer.pal(num_colors, "Set3")) +
  labs(title = "Proporción de Propiedades por Número de Pisos", x = "", y = "") +
  theme_void()

# Contar el número de baños
banios_count <- as.data.frame(table(vivienda_modificado_2$banios))
colnames(banios_count) <- c("banios", "count")

# Calcular el porcentaje
banios_count <- banios_count %>%
  mutate(percentage = count / sum(count) * 100)

# Determinar el número de colores necesarios
num_colors <- length(unique(banios_count$banios))

# Crear gráfico de pastel con porcentajes
ggplot(banios_count, aes(x = "", y = count, fill = as.factor(banios))) +
  geom_bar(width = 1, stat = "identity") +
  coord_polar(theta = "y") +
  geom_text(aes(label = sprintf("%.1f%%", percentage)), 
            position = position_stack(vjust = 0.5), 
            color = "white") +
  scale_fill_manual(values = RColorBrewer::brewer.pal(num_colors, "Set3")) +
  labs(title = "Proporción de Propiedades por Número de Baños", x = "", y = "") +
  theme_void()

5. Informe final

“El informe final debe incluir análisis detallados de los resultados obtenidos, las conclusiones claves y las recomendaciones específicas para guiar las decisiones estratégicas de la empresa inmobiliaria. Se espera que este análisis de datos proporcione ventajas competitivas en el mercado, optimizando la inversión y maximizando los beneficios en un entorno altamente competitivo y en constante cambio”.

Una vez revisado y analizado la base de datos de las viviendas, se puede llegar a las siguientes conclusiones:

• Se cuentan con más viviendas tipo apartamento que casa, con una participación de 61.31% y 38.69% respectivamente.

• La zona sur tiene más viviendas que el resto de zonas, con una participación de 56.95%; seguido de la zona norte y zona oeste, con 23.1% y 14.4% respectivamente.

• Estrato 5 tiene mayor participación en el número de viviendas, con 33%; seguido por el estrato 4 con 25.6% y el estrato 6 con 23.9%.

• Las casas tienen un precio promedio más alto, con un valor de 540 vs 367 de los apartamentos.

• Las zonas con el precio más alto son: oeste, norte y sur, ambas muy parejas, con un valor de 1.999, 1.940 y 1.900 respectivamente. Sin embargo, como precio promedio alto, las viviendas se encuentran ubicadas en la zona oeste con un valor de 678; seguido por la zona sur y zona norte, con 427 y 346 respectivamente.

• En cuanto al análisis de variables, no se encuentran correlaciones fuertes entre ellas; sin embargo, se visualizan patrones entre: precio y número de parqueaderos; precio y área construida y precio y número de baños; por lo tanto, es posible afirmar que un precio alto depende de estas tres características de las viviendas: parqueadero, área construida y número de baños.

• Se evidencia que el precio de las viviendas cuenta con un crecimiento lineal en función del área construida y el número de habitaciones, sobre todo en las viviendas ubicadas en estrato 6. Por otro lado, es importante mencionar que el precio de las viviendas no está en función al número de pisos.

Teniendo en cuenta lo anterior, la compañía inmobiliaria puede generar sus estrategias hacia dos caminos totalmente diferentes, pero que, de conocer las características propias de cada tipo de vivienda, podría generar grandes beneficios:

  1. Viviendas de precio alto: si es interés de la empresa ofrecer viviendas con precios de venta alto, estas podrían ser construidas en las zonas oeste y deben ser de tipo casa, con un área construida alta, entre 125 y 270 metros cuadrados, de 2 pisos y preferiblemente en el estrato 6 o 5; con precios de 500 en adelante; también deberían considerar entre 4 a 5 baños y con 4, 3 o 5 habitaciones, dependiendo de igual forma del área de construcción. En cuanto al número de parqueaderos mínimo se debería ofrecer una vivienda con 2 parqueaderos.

  2. Viviendas de precio bajo: si la empresa quiere enfocar sus esfuerzos hacia viviendas de precio promedio bajo, estas deben ser de tipo apartamento, con área construida hasta 125 metros y precios hasta 250; el número de baños podría variar entre 2 y 3, dependiendo del tamaño del apartamento; así como debería tener 3 habitaciones. En cuanto al número de parqueaderos con 1 podría ser suficiente.