1. Introducción

Durante los ultimos años, el mercado inmobiliario a nivel nacional ha venido aumentando, incrementando dramaticamente la compra y venta de inmuebles, especialmente en zonas turisticas como Cali, registrando ventas de miles millones de pesos para cada año, debido al flujo de capital en este mercado para los ultimos periodos, es de mucho interes realizar un análisis estadistico de los detalles y condiciones del mercado para identificar areas de oportunidad de inversión y crecimiento.

A continuación se va a proceder a evaluar los registros de viviendas en venta en Cali para sacar conclusiones que permitar tomar acciones para aprovechar los patrones existentes.

2. Objetivos

3. Metodos

Para el análisis de los datos se utilizaran:

Instalando paquetes y adquiriendo datos

library(tidyverse) #Paquete usado para el tratamiento de datos
library(stringi) #Paquete para tratamiento de acentuacion de caracteres
library(dplyr) #Paquete para sintaxis de codigo
library(magrittr) #Paquete para sintaxis de codigo
library(Amelia) #Paquete usado para el tratamiento de datos faltantes
library(summarytools)
devtools::install_github("kupietz/kableExtra")
devtools::install_github("dgonxalex80/paqueteMET")
library(kableExtra)
library(paqueteMET)
db<-vivienda_faltantes #base de datos a utilizar

Resumiendo el dataframe

head(db, 10) #muestra de primeros 10 registros del dataframe
## # A tibble: 10 × 13
##       id zona        piso estrato preciom areaconst parquea banios habitac tipo 
##    <dbl> <chr>      <dbl>   <dbl>   <dbl>     <dbl>   <dbl>  <dbl>   <dbl> <chr>
##  1  8312 Zona Oeste     4       6    1300       318       2      4       2 Apar…
##  2  8311 Zona Oeste     1       6     480       300       1      4       4 Casa 
##  3  8307 Zona Oeste    NA       5    1200       800       4      7       5 Casa 
##  4  8296 Zona Sur       2       3     220       150       1      2       4 Casa 
##  5  8297 Zona Oeste    NA       5     330       112       2      4       3 Casa 
##  6  8298 Zona Sur      NA       5    1350       390       8     10      10 Casa 
##  7  8299 Zona Sur       2       6     305       125       2      3       3 Apar…
##  8  8300 Zona Oeste    NA       5     480       280       4      4       4 Apar…
##  9  8286 Zona Sur      NA       5     275        74       1      2       3 Apar…
## 10  8287 Zona Sur       2       5     285       120       2      4       3 Apar…
## # ℹ 3 more variables: barrio <chr>, longitud <dbl>, latitud <dbl>
dim(db) #dimensiones del dataframe
## [1] 8330   13
str(db) #formato de columnas y muestra de datos disponibles
## spc_tbl_ [8,330 × 13] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ id       : num [1:8330] 8312 8311 8307 8296 8297 ...
##  $ zona     : chr [1:8330] "Zona Oeste" "Zona Oeste" "Zona Oeste" "Zona Sur" ...
##  $ piso     : num [1:8330] 4 1 NA 2 NA NA 2 NA NA 2 ...
##  $ estrato  : num [1:8330] 6 6 5 3 5 5 6 5 5 5 ...
##  $ preciom  : num [1:8330] 1300 480 1200 220 330 1350 305 480 275 285 ...
##  $ areaconst: num [1:8330] 318 300 800 150 112 390 125 280 74 120 ...
##  $ parquea  : num [1:8330] 2 1 4 1 2 8 2 4 1 2 ...
##  $ banios   : num [1:8330] 4 4 7 2 4 10 3 4 2 4 ...
##  $ habitac  : num [1:8330] 2 4 5 4 3 10 3 4 3 3 ...
##  $ tipo     : chr [1:8330] "Apartamento" "Casa" "Casa" "Casa" ...
##  $ barrio   : chr [1:8330] "arboleda" "normandía" "miraflores" "el guabal" ...
##  $ longitud : num [1:8330] -76576 -76571 -76568 -76565 -76565 ...
##  $ latitud  : num [1:8330] 3454 3454 3455 3417 3408 ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   id = col_double(),
##   ..   zona = col_character(),
##   ..   piso = col_double(),
##   ..   estrato = col_double(),
##   ..   preciom = col_double(),
##   ..   areaconst = col_double(),
##   ..   parquea = col_double(),
##   ..   banios = col_double(),
##   ..   habitac = col_double(),
##   ..   tipo = col_character(),
##   ..   barrio = col_character(),
##   ..   longitud = col_double(),
##   ..   latitud = col_double()
##   .. )
##  - attr(*, "problems")=<externalptr>
summary(db) #Resumen estadistico de cada variable
##        id           zona                piso           estrato     
##  Min.   :   1   Length:8330        Min.   : 1.000   Min.   :3.000  
##  1st Qu.:2082   Class :character   1st Qu.: 2.000   1st Qu.:4.000  
##  Median :4164   Mode  :character   Median : 3.000   Median :5.000  
##  Mean   :4164                      Mean   : 3.772   Mean   :4.634  
##  3rd Qu.:6246                      3rd Qu.: 5.000   3rd Qu.:5.000  
##  Max.   :8319                      Max.   :12.000   Max.   :6.000  
##  NA's   :3                         NA's   :2641     NA's   :3      
##     preciom         areaconst       parquea           banios      
##  Min.   :  58.0   Min.   :  30   Min.   : 1.000   Min.   : 0.000  
##  1st Qu.: 220.0   1st Qu.:  80   1st Qu.: 1.000   1st Qu.: 2.000  
##  Median : 330.0   Median : 123   Median : 2.000   Median : 3.000  
##  Mean   : 434.2   Mean   : 175   Mean   : 1.836   Mean   : 3.112  
##  3rd Qu.: 540.0   3rd Qu.: 229   3rd Qu.: 2.000   3rd Qu.: 4.000  
##  Max.   :1999.0   Max.   :1745   Max.   :10.000   Max.   :10.000  
##  NA's   :2        NA's   :3      NA's   :1606     NA's   :3       
##     habitac           tipo              barrio             longitud        
##  Min.   : 0.000   Length:8330        Length:8330        Min.   :-76576.00  
##  1st Qu.: 3.000   Class :character   Class :character   1st Qu.:-76506.00  
##  Median : 3.000   Mode  :character   Mode  :character   Median :   -76.54  
##  Mean   : 3.605                                         Mean   :-21845.13  
##  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.390  
##  Median :   3.450  
##  Mean   : 970.370  
##  3rd Qu.:3367.000  
##  Max.   :3497.000  
##  NA's   :3

Con este vistazo general podemos definir que:

  • Nuestro dataframe esta compuesto por 8330 registros con 13 variables
  • De las 13 variables, contamos con 3 de tipo cualitativo (zona, tipo, barrio) y 9 cuantitativas (id, piso, estrato, preciom, areaconst, parquea, banios, habitac, longitud, latitud)

Se procede a eliminar las columnas que no son necesarias o no aportan ningun tipo de informacion accionable (id, longitud, latitud)

Se identifican columnas con errores tipograficos y no estandarizadas (barrio, tipo), se proceden a convertir las variables cualitativas a minusculas, se corrigen errores causados por acentos y se elimina acentuacion para garantizar estandarización.

Adicionalmente se identifica una cantidad variable de NA’s (Not Available) para diferentes columna, por lo que se realizara un analisis rapido para identificar que tanto impacto tienen estos valores.

Limpiando el dataframe

df = subset(db, select = -c(id, longitud, latitud)) #elimina columnas innecesarias
df$barrio = str_to_lower(df$barrio) #convierte a minusculas
df$tipo = str_to_lower(df$tipo) #convierte a minusculas
df[df == "  "] = "" #elimina espacios dobles del dataframe
df$tipo[df$tipo == "apartamento"] = "apto" #Estandarizar descripcion de tipo
df$barrio = gsub("é", "e", df$barrio) #reemplazar errores por acentos
df$barrio = gsub("√∫", "u", df$barrio) #reemplazar errores por acentos
df$barrio = stri_trans_general(str = df$barrio, id = "Latin-ASCII") #elimina acentos

Analizando datos faltantes

missmap(df,main = "Datos Faltantes",y.labels = ' ', y.at = 1 )

La mayoria de los datos faltantes se concentran en las variables piso y parqueadero (5% del total del dataframe), por la naturaleza de estas variables se podria argumentar que esta ausencia representa un “No Aplica” (no confundir con el significado original de NA (Not Available)) por ejemplo: para una casa de un solo piso, la variable piso podria entenderse como no aplica, lo mismo para una casa sin parqueadero. Pero al no tener una confirmación de la validez de esta hipotesis, en el siguiente analisis se ignoraran valores con NA cuando sea posible.

4. Resultados

En primer lugar realizaremos un analisis descriptivo usando las variables cualitativas, con esto queremos identificar la distribucion de viviendas de acuerdo a estas caracteristicas

4.1 Analisis por Zona y Tipo

freq(df$zona, report.nas = F, order = "freq")
## Frequencies  
## df$zona  
## Type: Character  
## 
##                      Freq        %   % Cum.
## ------------------ ------ -------- --------
##           Zona Sur   4726    56.76    56.76
##         Zona Norte   1922    23.08    79.84
##         Zona Oeste   1204    14.46    94.30
##       Zona Oriente    351     4.22    98.51
##        Zona Centro    124     1.49   100.00
##              Total   8327   100.00   100.00
tbl_zona.estrato = table(df$zona, df$estrato)
names(dimnames(tbl_zona.estrato)) <- c("Zona", "Estrato")
tbl_zona.estrato
##               Estrato
## Zona              3    4    5    6
##   Zona Centro   105   14    4    1
##   Zona Norte    572  408  770  172
##   Zona Oeste     54   85  290  775
##   Zona Oriente  340    8    2    1
##   Zona Sur      382 1616 1685 1043
plt_zona.estrato = table(df$estrato, df$zona)
barplot(plt_zona.estrato, legend.text = T, xlim = c(0,6.7), cex.names = 0.8,las=2, main = "Dristibucion de Estrato por Zona")

Como podemos ver, la mayor concentracion de viviendas se encuentra en la Zona Sur y la Zona Norte con un 56% y 23% correspondientemente, mientras que las zonas Centro y Oriente tienen la menor participacion con solo un 1.4% y 4.2% respectivamente. Hay algunos datos interesantes para destacar

  • Las zonas Sur y Norte corresponden a un 79.8% de los inmuebles en la base de datos
  • La Zona Sur tiene una participacion relativamente homogenea de estratos 4, 5 y 6. poca presencia ed estrato 3
  • La zona Norte tiene una distribucion relativamente uniforme entre estratos pero opuesta a la Zona Sur teniendo menor participacion para estratos 6.
  • La zona Oeste es la zona mas exclusiva de la ciudad, estando compuesta principalmente por estrato 6 y 5, participacion minima de estratos 3 y 4.
  • Las zonas Oriente y Centro se componen casi que exclusivamente por estrato 3, presencia de inmuebles de estratos 4 o superior inferior a 20 unidades para cada una
tbl_zona.tipo = table(df$zona, df$tipo)
names(dimnames(tbl_zona.tipo)) <- c("Zona", "Tipo")
tbl_zona.tipo
##               Tipo
## Zona           apto casa
##   Zona Centro    24  100
##   Zona Norte   1199  723
##   Zona Oeste   1035  169
##   Zona Oriente   62  289
##   Zona Sur     2786 1940
round(prop.table(tbl_zona.tipo, 1)*100,2) #Distribucion porcentual para tipo de inmueble por zona
##               Tipo
## Zona            apto  casa
##   Zona Centro  19.35 80.65
##   Zona Norte   62.38 37.62
##   Zona Oeste   85.96 14.04
##   Zona Oriente 17.66 82.34
##   Zona Sur     58.95 41.05
tbl_tipo.zona = table(df$tipo, df$zona)
barplot(tbl_tipo.zona, legend = T, xlim = c(0,7.5), cex.names = 0.8, main = "Tipo de vivienda por zona", las=2)

Es importante mencionar que de la base de datos, los residentes de las zonas de estratos mas bajos del grupo muestral (Zona Centro y Oriente, principalmente viviendas estrato 3), tienen una preferencia por viviendas de tipo casa (mas del 80% para ambos casos).

4.2 Analisis por Estrato y Tipo

Las siguientes graficas muestran la distribucion e impacto del estrato, como se ha mencionado anteriormente, la base de datos se compone principalmente por viviendas de alto costo (estrato 5 y 4 siendo los mas frecuentes).

freq(df$estrato, report.nas = F)
## Frequencies  
## df$estrato  
## Type: Numeric  
## 
##               Freq        %   % Cum.
## ----------- ------ -------- --------
##           3   1453    17.45    17.45
##           4   2131    25.59    43.04
##           5   2751    33.04    76.08
##           6   1992    23.92   100.00
##       Total   8327   100.00   100.00
barplot(table(df$estrato), main = "Numero de viviendas por estrato")

freq(df$tipo, report.nas = F)
## Frequencies  
## df$tipo  
## Type: Character  
## 
##               Freq        %   % Cum.
## ----------- ------ -------- --------
##        apto   5106    61.32    61.32
##        casa   3221    38.68   100.00
##       Total   8327   100.00   100.00
barplot(table(df$tipo,df$estrato), beside = T, legend.text = T, xlim = c(0,14.5), main = "Tipo de vivienda por estrato")

De acuerdo a los resultados anteriores, la mayoria de los inmuebles son apartamentos con un 61% de los inmuebles de la ciudad vs un 38% para casas. Como se puede observar en la ultima grafica y tabla, si bien la cantidad de casas se mantiene relativamente estable entre estratos, la cantidad de apartamentos en venta incrementa junto con el estrato.

4.3 Analisis para Preciom

hist(df$preciom, main = "Viviendas por precio en millones de pesos", xlab = "Precio en millones", ylab = '', col = 'blue')

summarytools::descr(df$preciom)
## Descriptive Statistics  
## df$preciom  
## N: 8330  
## 
##                     preciom
## ----------------- ---------
##              Mean    434.24
##           Std.Dev    329.02
##               Min     58.00
##                Q1    220.00
##            Median    330.00
##                Q3    540.00
##               Max   1999.00
##               MAD    209.05
##               IQR    320.00
##                CV      0.76
##          Skewness      1.85
##       SE.Skewness      0.03
##          Kurtosis      3.66
##           N.Valid   8328.00
##         Pct.Valid     99.98

Se puede notar que el valor de las viviendas esta agrupado por debajo de los 500 millones de pesos, con una asimetria positiva y una mediana de 330M COP y una media de 434.2M COP, los precios se devian de la media 329M COP en promedio y hay un coeficiente de variacion de 70% lo que nos dice que tiene una variacion extremadamente alta.

4.4 Analisis para area

summarytools::descr(df$areaconst)
## Descriptive Statistics  
## df$areaconst  
## N: 8330  
## 
##                     areaconst
## ----------------- -----------
##              Mean      174.99
##           Std.Dev      142.95
##               Min       30.00
##                Q1       80.00
##            Median      123.00
##                Q3      229.00
##               Max     1745.00
##               MAD       84.51
##               IQR      149.00
##                CV        0.82
##          Skewness        2.69
##       SE.Skewness        0.03
##          Kurtosis       12.90
##           N.Valid     8327.00
##         Pct.Valid       99.96
hist(df$areaconst, main = "Viviendas por area m2", xlab = "Area m2", ylab = "", col="red", )

Como podemos ver, el area se caracteriza por tener las siguientes caracteristicas:

  • Una mediana de 123 m2
  • Un promedio de 174 m2
  • Una desviacion estandar de 142 m2
  • El 75% de los valores se encuentran por debajo de los 229 m2

Pero tenemos un valor maximo de 1745 m2 lo que indica que al tener la mayoria de las viviendas concentradas por debajo de los 300m2, habra una cantidad considerable de valores atipicos, como se indica en el siguiente grafico de cajas

area_boxplot= boxplot(df$areaconst, horizontal = T, main="Distribucion de area m2" )

Podemos reducir/remover los datos atipicos para ver la distribucion de valores un poco mas claramente

df_area_no_otl = df[!(df$areaconst %in% area_boxplot$out),]
boxplot(df_area_no_otl$areaconst, horizontal = T, main="Distribucion de area m2")

Esto nos permite ver mas claramente que la mayoria de las viviendas se encuentra concentrada por debajo de los 220 m2

4.5 Area Vs Preciom

area_precio = select(df, areaconst, preciom, zona, tipo, estrato)
area_precio = na.omit(area_precio)

ggplot(area_precio, aes(x = preciom, y = areaconst, color = tipo)) + 
    geom_point() + 
    labs(x = "Precio en Millones", y = "Area en m2")+
    ggtitle("Precio en Millones vs Area en m2")+
    theme_light()+
    theme(plot.title = element_text(hjust = 0.5))

Algo evidente es que a medida que aumenta el area el precio incrementa, pero no es de manera lineal para ambos tipos de vivienda, los apartamentos se mantienen relativamente en areas similares aunque el precio siga aumentando (debido a la naturaleza intrinseca de un apartamento el area construida tiene limites mas bajos), por lo que es interesante analizar que tanto varia el precio entre casas y apartamentos para la misma area construida

area_h =(2 * IQR(df$areaconst, na.rm = TRUE)) / length(df$areaconst)^(1/3) #se utiliza Freedman-Diaconis para calcular el rango optimo para la amplitud de los segmentos
area_bins = ceiling((max(area_precio$areaconst, na.rm = T)-min(area_precio$areaconst, na.rm = T))/area_h)
segm_area = cut_width(area_precio$areaconst, width = area_bins) #segmenta la variable de area construida
area_precio$bin_area = segm_area #se agrega como una columna adicional a area_precio

precio_area = area_precio %>%   
    group_by(bin_area, tipo) %>% 
    summarize(preciom = mean(preciom), .groups = "drop") %>%
    pivot_wider(names_from = tipo, values_from = preciom) %>%
    kable() %>%
    kable_styling(full_width = FALSE) %>%
    add_header_above(c("Precio promedio en millones de pesos" = 3),italic = T, font_size = "small") %>%
    add_header_above(c("Analisis de precio: Area vs Tipo" = 3))
precio_area
Analisis de precio: Area vs Tipo
Precio promedio en millones de pesos
bin_area apto casa
[-58.5,58.5] 133.5927 147.6842
(58.5,176] 302.7157 295.7503
(176,292] 835.2519 469.6912
(292,410] 1025.1591 689.5367
(410,526] 1477.2222 869.6512
(526,644] 1133.3333 1058.2885
(644,760] 550.0000 1206.6667
(760,878] NA 1169.9600
(878,994] 299.0000 1220.7143
(994,1.11e+03] NA 1327.5000
(1.11e+03,1.23e+03] NA 1100.0000
(1.23e+03,1.35e+03] NA 1500.0000
(1.35e+03,1.46e+03] NA 285.0000
(1.46e+03,1.58e+03] NA 1575.0000
(1.58e+03,1.7e+03] NA 1700.0000
(1.7e+03,1.81e+03] NA 255.0000

Aqui podemos notar que de manera general, en casi todos los escenarios las casas tienen un costo menor que los apartamentos, en algunos casos alcanzando un precio hasta un 40% mas bajo.

4.6 Amenities vs Precio

4.6.1 Parqueaderos

parq_precio = df %>% 
    mutate(parquea = ifelse(is.na(parquea), 0, parquea)) %>%
    na.omit(tipo)

precio_parq = parq_precio %>%
    group_by(parquea, tipo) %>% 
    summarize(preciom = mean(preciom), .groups = "drop") %>%
    pivot_wider(names_from = tipo, values_from = preciom) %>%
    kable() %>%
    kable_styling(full_width = FALSE) %>%
    add_header_above(c("Precio promedio en millones de pesos" = 3),italic = T, font_size = "small") %>%
    add_header_above(c("Analisis de precio: Parqueadero vs Tipo" = 3))
precio_parq
Analisis de precio: Parqueadero vs Tipo
Precio promedio en millones de pesos
parquea apto casa
0 165.8827 355.9441
1 233.7536 358.9176
2 491.1334 534.1119
3 996.5756 695.9017
4 1125.5667 941.0667
5 1410.0000 1003.7674
6 1250.0000 1202.2391
7 1500.0000 1031.4286
8 NA 1278.0000
9 NA 1210.0000
10 NA 1075.0000

Para este ejercicio se asume que los NA, representan viviendas con 0 parqueaderos. No se encuentra un factor que indique si es mejor comprar casa o apartamento si el numero de parqueaderos es un factor decisivo

4.6.2 Baños

banio_precio = df %>% 
    na.omit(tipo)

precio_banios = parq_precio %>%
    group_by(banios, tipo) %>% 
    summarize(preciom = mean(preciom), .groups = "drop") %>%
    pivot_wider(names_from = tipo, values_from = preciom) %>%
    kable() %>%
    kable_styling(full_width = FALSE) %>%
    add_header_above(c("Precio promedio en millones de pesos" = 3),italic = T, font_size = "small") %>%
    add_header_above(c("Analisis de precio: Baños vs Tipo" = 3))
precio_banios
Analisis de precio: Baños vs Tipo
Precio promedio en millones de pesos
banios apto casa
0 478.5714 718.4286
1 128.0761 185.5283
2 229.6819 274.8694
3 380.2904 383.6899
4 667.1391 546.2703
5 865.4312 694.0133
6 1214.8667 908.1200
7 1330.0000 1033.4062
8 NA 903.8333
9 NA 1011.8182
10 NA 462.5000

No se encuentra un factor que indique si es mejor decidirse por casa o apartamento si el numero de baños es un factor decisivo

4.6.1 Habitaciones

hab_precio = df %>% 
    na.omit(habitac) # se omiten registros con NA para habitaciones

precio_hab = hab_precio %>%
    group_by(habitac, tipo) %>% 
    summarize(preciom = mean(preciom), .groups = "drop") %>%
    pivot_wider(names_from = tipo, values_from = preciom) %>%
    kable() %>%
    kable_styling(full_width = FALSE) %>%
    add_header_above(c("Precio promedio en millones de pesos" = 3),italic = T, font_size = "small") %>%
    add_header_above(c("Analisis de precio: Habitaciones vs Tipo" = 3))
precio_hab
Analisis de precio: Habitaciones vs Tipo
Precio promedio en millones de pesos
habitac apto casa
0 370.2222 499.7500
1 279.0000 366.3333
2 275.0374 296.4091
3 370.5568 482.9529
4 540.9771 610.3968
5 825.3333 662.3514
6 956.2500 643.9560
7 1500.0000 590.5250
8 NA 567.0889
9 NA 558.7045
10 NA 599.8000

Si el numero de habitaciones requeridas es menor o igual a 4, es mas economico comprar un apartamento, despues de este valor, las casas son mas economicas en un minimo de 200 Millones de pesos

5. Conclusiones