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.
Para el análisis de los datos se utilizaran:
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
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:
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.
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
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.
En primer lugar realizaremos un analisis descriptivo usando las variables cualitativas, con esto queremos identificar la distribucion de viviendas de acuerdo a estas caracteristicas
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
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).
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.
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.
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:
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
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
| 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.
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
| 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
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
| 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
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
| 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