Problema a resolver

Una empresa inmobiliaria líder en una gran ciudad está buscando comprender en profundidad el mercado de viviendas urbanas para tomar decisiones estratégicas más informadas. La empresa posee una base de datos extensa que contiene información detallada sobre diversas propiedades residenciales disponibles en el mercado. Se requiere realizar un análisis holístico de estos datos para identificar patrones, relaciones, y segmentaciones relevantes que permitan mejorar la toma de decisiones en cuanto a la compra, venta y valoración de propiedades.

Análisis exploratorio

Se realiza la carga de los datos, se muestra el resumen de la estructura, el tipo de datos en los atributos y finalmente la validación de vacíos.

data("vivienda")
datos <- vivienda
head(datos)
#Validamos cuantos datos hay vacíos

colSums(is.na(datos))
##           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
#Eliminamos los datos faltantes que en este caso son muy pocos respecto al universo (3 datos faltantes)
datos <- datos[!is.na(datos$id), ]

#Verificamos que lo datos faltantes hayan sido eliminados
nrow(datos)
## [1] 8319
colSums(is.na(datos))
##           id         zona         piso      estrato      preciom    areaconst 
##            0            0         2635            0            0            0 
## parqueaderos       banios habitaciones         tipo       barrio     longitud 
##         1602            0            0            0            0            0 
##      latitud 
##            0
str(datos)
## tibble [8,319 × 13] (S3: tbl_df/tbl/data.frame)
##  $ id          : num [1:8319] 1147 1169 1350 5992 1212 ...
##  $ zona        : chr [1:8319] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
##  $ piso        : chr [1:8319] NA NA NA "02" ...
##  $ estrato     : num [1:8319] 3 3 3 4 5 5 4 5 5 5 ...
##  $ preciom     : num [1:8319] 250 320 350 400 260 240 220 310 320 780 ...
##  $ areaconst   : num [1:8319] 70 120 220 280 90 87 52 137 150 380 ...
##  $ parqueaderos: num [1:8319] 1 1 2 3 1 1 2 2 2 2 ...
##  $ banios      : num [1:8319] 3 2 2 5 2 3 2 3 4 3 ...
##  $ habitaciones: num [1:8319] 6 3 4 3 3 3 3 4 6 3 ...
##  $ tipo        : chr [1:8319] "Casa" "Casa" "Casa" "Casa" ...
##  $ barrio      : chr [1:8319] "20 de julio" "20 de julio" "20 de julio" "3 de julio" ...
##  $ longitud    : num [1:8319] -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num [1:8319] 3.43 3.43 3.44 3.44 3.46 ...

Imputación de datos para los atributos piso y parqueaderos

Los faltantes para cada uno de los atributos es significativo, por lo que eliminar los datos faltantes no es una opción y pasamos a la imputación de datos, para el piso los hacemos con la moda y para parqueaderos utilizamos la media.

# función para calcular la moda

calcular_moda <- function (x){
  uniq_x <- unique(na.omit(x))
  uniq_x[which.max(tabulate(match(x, uniq_x)))]
  
}  

moda <- calcular_moda(datos$piso)

#Imputar los datos en la varible piso
datos$piso <- ifelse(is.na(datos$piso), moda, datos$piso)

print(colSums(is.na(datos)))
##           id         zona         piso      estrato      preciom    areaconst 
##            0            0            0            0            0            0 
## parqueaderos       banios habitaciones         tipo       barrio     longitud 
##         1602            0            0            0            0            0 
##      latitud 
##            0
# Calcular la media 

media <- mean(datos$parqueaderos, na.rm = TRUE)

# Redondear al mayor valor
media <- ceiling(media)

#Se imputan los datos de los parqueaderos con la media

datos$parqueaderos <- ifelse(is.na(datos$parqueaderos), media, datos$parqueaderos)
print(colSums(is.na(datos)))
##           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

Gráficas descriptivas de las viviendas urbanas

#Pie para la distribución por zona
f <- table(datos$zona)
porcentajes <- prop.table(f) * 100
etiquetas <- paste0(names(f), ": ", round(porcentajes, 1), "%")
pie(f, labels = etiquetas, main = "Distribución de viviendas por zonas")

#Pie para la distribución por estrato
f1 <- table(datos$estrato)
p <- prop.table(f1) * 100
e <- paste0(names(f1), ": ", round(p, 1), "%")
pie(f1, labels = e, main = "Distribución de viviendas por estrato")

boxplot(preciom ~ zona, data = datos, main = "Diagrama de cajas y bigotes", xlab = "Categoria", ylab = "Valor", col = rainbow(5), border = "black")

De acuerdo con lo mostrado en las gráficas anteriores.

  1. La mayor cantidad de viviendas ofertadas se encuentran en la zona sur de la ciudad.
  2. El mayor número de viviendas se ofertan en estrato 5 con el 33,1% seguido por el estrato 4 con el 25,6%
  3. En el diagrama de cajas y bigotes el precio promedio más alto por cada zona es el de la zona oeste, siendo también esta zona la que tiene unos valores atípicos mucho más alejados de la media.
  4. Si bien la zona norte y sur tienen un comportamiento similar en oferta, la zona sur tiene un valor promedio por encima de la zona norte.

Mapa de distribución de viviendas por la ciudad

# Crear un mapa con ggplot2
ggplot(datos, aes(x = longitud, y = latitud)) +
  geom_point(color = "blue", size = 0.5) +
  labs(title = "Distribución de viviendas en Cali", x = "Longitud", y = "Latitud") +
  theme_minimal()

Análisis de componentes principales

El análisis de componentes principales busca principalmente disminuir la dimensionalidad de un conjunto de datos mientras se conserva la mayor cantidad posible de variabilidad en los datos originales. Se siguieron los siguientes pasos.

  1. Se hace una copia al dataframe que venía trabajando.
  2. Se eliminand todas las columnas categóricas y dejando solo las numéricas.
  3. Se realiza la validación que no se tienen datos faltantes.
  4. Se realiza la estandarización de los datos.
  5. Se aplican la función prcomp para aplicar el método PCA
## tibble [8,319 × 7] (S3: tbl_df/tbl/data.frame)
##  $ id          : num [1:8319] 1147 1169 1350 5992 1212 ...
##  $ estrato     : num [1:8319] 3 3 3 4 5 5 4 5 5 5 ...
##  $ preciom     : num [1:8319] 250 320 350 400 260 240 220 310 320 780 ...
##  $ areaconst   : num [1:8319] 70 120 220 280 90 87 52 137 150 380 ...
##  $ parqueaderos: num [1:8319] 1 1 2 3 1 1 2 2 2 2 ...
##  $ banios      : num [1:8319] 3 2 2 5 2 3 2 3 4 3 ...
##  $ habitaciones: num [1:8319] 6 3 4 3 3 3 3 4 6 3 ...
##  /\     /\
## {  `---'  }
## {  O   O  }
## ==>  V <==  No need for mice. This data set is completely observed.
##  \  \|/  /
##   `-----'

##      id estrato preciom areaconst parqueaderos banios habitaciones  
## 8319  1       1       1         1            1      1            1 0
##       0       0       0         0            0      0            0 0
## Standard deviations (1, .., p=6):
## [1] 1.8346512 1.0957956 0.7890282 0.6229765 0.4869034 0.4307517
## 
## Rotation (n x k) = (6 x 6):
##                    PC1        PC2         PC3        PC4        PC5        PC6
## estrato      0.3053339 -0.6314557  0.46567443  0.1939375  0.4568797 -0.2117342
## preciom      0.4805544 -0.2515172 -0.01744926 -0.2627551 -0.1791828  0.7774030
## areaconst    0.4567966  0.1902844 -0.11350866 -0.7257528  0.1839350 -0.4262574
## parqueaderos 0.3938199 -0.1258597 -0.77914893  0.4397503  0.1113150 -0.1273612
## banios       0.4705181  0.1517863  0.32361761  0.2846612 -0.6935003 -0.2981118
## habitaciones 0.3011546  0.6803785  0.24115808  0.3036753  0.4816521  0.2530344

Se toman los dos primeros componentes principales ya que con solo dos logramos explicar el 76% de la varianza de las variables.

##                  Variable     PCA_1      PCA_2
## estrato           estrato 0.3053339 -0.6314557
## preciom           preciom 0.4805544 -0.2515172
## areaconst       areaconst 0.4567966  0.1902844
## parqueaderos parqueaderos 0.3938199 -0.1258597
## banios             banios 0.4705181  0.1517863
## habitaciones habitaciones 0.3011546  0.6803785

Podemos indentificar en este análisis de variables que la Dimensión 1 explica el 56,1%, mientras que la Dimensión 2 explica el 20%.

Por otro lado las variable preciom está fuertemente relacionada con la dimensión 1, como también lo están las variables área contruida y baños, sin embargo estas están mucho más cerca en su correlación. Si bien parqueadero tiene una corelación mayor con la dimensión 1 que con la dimensión 2 esta es menor que las mencionadas anteriormente. Por otro lado el estrato y las habitaciones están más fuertemente corelacionadas con la dimensión 2.

Análisis de conglomerados

Se pueden observar algunas agrupaciones que están relacionadas con el precio y el número de baños con el que cuenta la vivienda, se puede determinar que las viviendas con menor precio pueden llegar a tener un número alto de baños peor cuando hacemos otra relación entre el precio de la vivienda y el estrato logramos identificar que estas son de un estrato más bajo.

Análisis de correspondencia

##               
##                  01   02   03   04   05   06   07   08   09   10   11   12
##   Zona Centro    33   71    8    5    5    0    0    0    0    0    2    0
##   Zona Norte    177 1074  189  129  117   33   33   39   32   27   33   37
##   Zona Oeste     86  514  134   99   84   80   57   43   41   28   20   12
##   Zona Oriente   74  206   54    8    8    0    0    0    0    1    0    0
##   Zona Sur      490 2220  712  366  353  132  114  129   73   74   29   34
##  /\     /\
## {  `---'  }
## {  O   O  }
## ==>  V <==  No need for mice. This data set is completely observed.
##  \  \|/  /
##   `-----'

## 
## Principal inertias (eigenvalues):
## 
##  dim    value      %   cum%   scree plot               
##  1      0.033106  64.0  64.0  ****************         
##  2      0.012289  23.8  87.8  ******                   
##  3      0.005418  10.5  98.3  ***                      
##  4      0.000896   1.7 100.0                           
##         -------- -----                                 
##  Total: 0.051709 100.0                                 
## 
## 
## Rows:
##     name   mass  qlt  inr    k=1 cor ctr    k=2 cor ctr  
## 1 | ZnCn |   15  707  132 | -566 702 144 |   44   4   2 |
## 2 | ZnNr |  231  968  190 |  -75 132  39 |  189 836 668 |
## 3 | ZnOs |  144  883  332 |  323 874 453 |   31   8  11 |
## 4 | ZnOr |   42  906  266 | -534 873 363 | -103  33  36 |
## 5 | ZnSr |  568  833   80 |    3   1   0 |  -78 832 282 |
## 
## Columns:
##      name   mass  qlt  inr    k=1 cor ctr    k=2 cor ctr  
## 1  |   01 |  103  813  197 | -269 736 227 |  -87  77  64 |
## 2  |   02 |  491  982   94 |  -78 614  90 |   60 368 145 |
## 3  |   03 |  132  857   89 |  -10   3   0 | -172 854 318 |
## 4  |   04 |   73  775   41 |  148 752  48 |  -26  23   4 |
## 5  |   05 |   68  596   38 |  120 492  29 |  -55 104  17 |
## 6  |   06 |   29  871  188 |  533 861 253 |  -58  10   8 |
## 7  |   07 |   25  945   97 |  439 938 143 |  -40   8   3 |
## 8  |   08 |   25  964   46 |  296 928  67 |  -59  36   7 |
## 9  |   09 |   18  937   66 |  417 886  92 |  100  51  14 |
## 10 |   10 |   16  997   24 |  284 996  38 |    6   0   0 |
## 11 |   11 |   10  932   61 |  193 120  11 |  502 812 207 |
## 12 |   12 |   10  886   58 |   80  21   2 |  511 865 212 |

## List of 16
##  $ sv        : num [1:4] 0.182 0.1109 0.0736 0.0299
##  $ nd        : logi NA
##  $ rownames  : chr [1:5] "Zona Centro" "Zona Norte" "Zona Oeste" "Zona Oriente" ...
##  $ rowmass   : num [1:5] 0.0149 0.2308 0.144 0.0422 0.5681
##  $ rowdist   : num [1:5] 0.6754 0.2063 0.3453 0.5711 0.0855
##  $ rowinertia: num [1:5] 0.0068 0.00982 0.01717 0.01376 0.00416
##  $ rowcoord  : num [1:5, 1:4] -3.111 -0.4115 1.7745 -2.9333 0.0169 ...
##   ..- attr(*, "dimnames")=List of 2
##   .. ..$ : chr [1:5] "Zona Centro" "Zona Norte" "Zona Oeste" "Zona Oriente" ...
##   .. ..$ : chr [1:4] "Dim1" "Dim2" "Dim3" "Dim4"
##  $ rowsup    : logi(0) 
##  $ colnames  : chr [1:12] "01" "02" "03" "04" ...
##  $ colmass   : num [1:12] 0.1034 0.491 0.1319 0.073 0.0682 ...
##  $ coldist   : num [1:12] 0.3141 0.0993 0.1864 0.1704 0.1706 ...
##  $ colinertia: num [1:12] 0.0102 0.00484 0.00458 0.00212 0.00198 ...
##  $ colcoord  : num [1:12, 1:4] -1.4807 -0.4273 -0.0565 0.8125 0.6575 ...
##   ..- attr(*, "dimnames")=List of 2
##   .. ..$ : chr [1:12] "01" "02" "03" "04" ...
##   .. ..$ : chr [1:4] "Dim1" "Dim2" "Dim3" "Dim4"
##  $ colsup    : logi(0) 
##  $ N         : int [1:5, 1:12] 33 177 86 74 490 71 1074 514 206 2220 ...
##  $ call      : language ca.matrix(obj = obj)
##  - attr(*, "class")= chr "ca"

El gráfico nos pemite establecer las relaciones y validardas de acuerdo con la distancia que hay entre cada una de las variables.

Los dos primeros componentes resumen el 87,8% de los datos.

Las zonas sur y oeste son las que tienen viviendas con un mayor número de pisos.

La zona centro y oriente seguramente por la antiguedad no son muy altas, estas se ubican usualmente en el centro de la ciudad

Conclusiones

  1. La zonas en donde la inmobiliaria podría enfocar sus esfuerzos de venta para personas que quieran vivir en pisos altos está en la zona su y oeste de la ciudad, esta zona seguramente está llena de nuevas construcciones y desarrollos.

  2. Uno de los estratos que podría ser de importante desarrollo para la inmobiliaria son las viviendas que quedan en el estrato 4 donde al valor de la vivienda es mayor y el número de baños y es menor que en algunas viviendas en otras zonas.

  3. Hay un número importante de viviendas que están por debajo 1,000 millones de pesos pero en la que las construcciones tienen en promedio 3 baños, sin embargo, las que pueden generar mayor valor para la compañía son las que están en los estratos 3 y 4.