Estimados señores de B&C:
En el competitivo mercado inmobiliario de la ciudad de Cali, contar con información precisa y actualizada sobre las tendencias del sector es crucial para tomar decisiones estratégicas fundamentadas. Este informe presenta un análisis detallado de los datos estadísticos recopilados sobre las viviendas en Cali, con el objetivo de proporcionar a B&C una visión completa y perspicaz del panorama actual del mercado
Este análisis se basa en datos recopilados de diversas fuentes confiables, y tiene como fin equipar a la empresa para identificar oportunidades de inversión, desarrollar estrategias de comercialización efectivas y ofrecer a sus clientes servicios inmobiliarios de alta calidad que se ajusten a las necesidades y expectativas del mercado.
De acuerdo a la información obtenida se tienen los siguientes datos:
ggplot(vivienda_sinNA, aes(x = tipo)) +
geom_bar(width=0.5, colour="black", fill="lavender") +
labs(x="Tipo de vivienda",y= "Cantidad") +
ylim(c(0, 4000)) +
theme_bw(base_size = 12) +
geom_text(aes(label=after_stat(count)), stat='count',
position=position_dodge(0.9),
vjust=-0.5,
size=5.0
) +
facet_wrap(~"Viviendas por Tipo")
En este gráfica se puede observar que la mayor cantidad de ofertas que
hay en la ciudad se enfocan en los apartamentos. Ahora veamos las
ofertas por estratos.
ggplot(vivienda_sinNA, aes(x = estrato)) +
geom_bar(width=0.5, colour="black", fill="skyblue") +
labs(x="Estrato",y= "Cantidad") +
ylim(c(0, 2300)) +
theme_bw(base_size = 12) +
geom_text(aes(label=after_stat(count)), stat='count',
position=position_dodge(0.9),
vjust=-0.5,
size=5.0
) +
facet_wrap(~"Viviendas por Estrato")
Como se observa en la gráfica anterior, se identifica que la mayor parte
de la oferta de viviendas disponibles en Cali, están en los estratos 4,
5 y 6.
ggplot(vivienda_sinNA, aes(x = zona)) +
geom_bar(width=0.5, colour="black", fill="pink") +
labs(x="Zona",y= "Cantidad") +
ylim(c(0, 4000)) +
theme_bw(base_size = 12) +
geom_text(aes(label=after_stat(count)), stat='count',
position=position_dodge(0.9),
vjust=-0.5,
size=5.0
) +
facet_wrap(~"Viviendas por Zona")
Por zona podemos ver claramente que la mayor parte de la oferta se
encuentra en la zona Sur de la ciudad, que es la zona con mayor
crecimiento actualmente en cuanto a construcción de nuevas viviendas y
que se está valorizando con gran fuerza.
tabla_zona_estrato <- table(vivienda_sinNA$zona, vivienda_sinNA$estrato)
detail2 <- prop.table(tabla_zona_estrato,2) * 100
knitr::kable(detail2, caption = "Proporción de viviendas por zona y estrato", align = "c")
| 3 | 4 | 5 | 6 | |
|---|---|---|---|---|
| Zona Centro | 7.963875 | 0.5717553 | 0.0448029 | 0.0856164 |
| Zona Norte | 36.288998 | 16.9811321 | 27.2849462 | 8.6472603 |
| Zona Oeste | 3.448276 | 3.9451115 | 8.9157706 | 31.8493151 |
| Zona Oriente | 25.779967 | 0.4002287 | 0.0896057 | 0.0000000 |
| Zona Sur | 26.518883 | 78.1017724 | 63.6648746 | 59.4178082 |
Ahora si analizamos en la gráfica anterior la proporción por estrato es claro que para un cliente que esté buscando una casa en estrato 3 encontrará una mayor cantidad de oferta en el norte de la ciudad, para el estrato 4, 5 y 6 la mayor cantidad se encuentra en el sur, siendo esto consistente con la gráfica anterior de viviendas por estrato.
detail <- prop.table(table(vivienda_sinNA$tipo, vivienda_sinNA$zona),2) * 100
knitr::kable(detail, caption = "Proporción de viviendas por zona y tipo")
| Zona Centro | Zona Norte | Zona Oeste | Zona Oriente | Zona Sur | |
|---|---|---|---|---|---|
| apartamento | 13.76147 | 52.93306 | 80.35191 | 14.24149 | 55.41535 |
| casa | 86.23853 | 47.06694 | 19.64809 | 85.75851 | 44.58465 |
En la tabla anterior podemos ver la proporción por tipo de vivienda y zona, viendo que en el sur el 55% de la oferta es para apartamentos, frente a un 45% para casas. En las zona del centro vemos que la mayoría de ofertas son para casas con un 86% , dado que en la zona por ser la de mayor antiguedad de la ciudad no presenta tantas ofertas de apartamentos que son sólo el 14% restantes, un caso similar lo vemos en el Oriente, aunque la hipótesis en este caso es que pueda ser debido a que la mayor parte de las viviendas en el oriente son casas debido a la capacidad adquisitiva de la población que la habita. Lo contrario se puede ver en la zona Oeste, donde el 80% son apartamentos y el 20% de casas. El comportamiento en el norte es muy parecido al del Sur, con un 53% para apartamentos y un 47 para casas
histograma <- ggplot(vivienda_sinNA, aes(x=areaconst)) +
ggtitle("Histograma de área construida en viviendas") +
theme_fivethirtyeight() +
geom_histogram(color="#28324a", fill="#C06BF7", bins=30)
histograma
En el anterior histograma, vemos que la gran mayoría de la oferta se encuentra en viviendas de menos de 250 metros cuadrados, con un segundo lugar para las areas entre 250 y 500, y ya muy pocos datos en viviendas grandes de mas de 500 metros cuadrados
histograma2 <- ggplot(vivienda_sinNA, aes(x=preciom)) +
ggtitle("Histograma de precio de las viviendas") +
theme_fivethirtyeight() +
geom_histogram(color="#28324a", fill="#3c78d8", bins=30)
histograma2
El precio de las viviendas se observa que la gran mayoría se encuentra
en precios de menos de 500 millones y en segundo lugar entre 500 y 1000
millones, y ya en muy pocos datos hay precios mas altos de 1000.
promedio_precio <- vivienda_sinNA %>% group_by(zona) %>% summarise(promedio_precio = round(mean(preciom), digits=1))
knitr::kable(promedio_precio, caption = "Promedio de precios x zona")
| zona | promedio_precio |
|---|---|
| Zona Centro | 306.3 |
| Zona Norte | 345.3 |
| Zona Oeste | 530.0 |
| Zona Oriente | 226.8 |
| Zona Sur | 388.0 |
promedio_precioxmetro_zona <- vivienda_sinNA %>% group_by(zona) %>% summarise(promedio_preciometro = round(mean(precioxmetro), digits=1))
knitr::kable(promedio_precioxmetro_zona, caption = "Promedio de precios por metro cuadrado y zona")
| zona | promedio_preciometro |
|---|---|
| Zona Centro | 1.7 |
| Zona Norte | 2.3 |
| Zona Oeste | 3.3 |
| Zona Oriente | 1.4 |
| Zona Sur | 2.6 |
promedio_precio_estrato <- vivienda_sinNA %>% group_by(estrato) %>% summarise(promedio_precio = round(mean(preciom), digits=1))
knitr::kable(promedio_precio_estrato, caption = "Promedio de precios por estrato")
| estrato | promedio_precio |
|---|---|
| 3 | 218.4 |
| 4 | 280.7 |
| 5 | 404.0 |
| 6 | 672.5 |
means <- aggregate (vivienda_sinNA$preciom ~ vivienda_sinNA$tipo + vivienda_sinNA$zona, data = vivienda_sinNA, FUN = function(x) c(round(as.numeric(mean(x)),2)))
means <- data.frame(as.matrix(means))
colnames(means) <- c("Tipo","Zona","Promedio_Precio")
ggplot(means, aes(x = Tipo, y = Promedio_Precio, fill = Tipo, width = 0.7)) +
geom_bar(stat = "identity", show.legend = TRUE) +
labs(x = "Tipo de vivienda", y = "Promedio precio") +
theme(text = element_text(size = 10),
plot.margin = unit(c(0.5,0.5,0.5,0.5), "cm")) +
facet_wrap(~Zona ) +
geom_text(aes(label = Promedio_Precio),size = 3.5, vjust = -0.2) +
geom_col(colour = "gray") +
scale_fill_manual(values = c("#31AAFD", "#A789FF"))
En cuanto a precio de viviendas, con las tablas y gráficas anteriores podemos ver que las viviendas mas costosas se encuentran en la zona Oeste, pues esta zona es de estratos altos, generalmente entre 5 y 6 que como vemos el promedio también bastante alto, y con viviendas que tienen areas de construcción grandes, lo cual es consistente con las características de la zona. En el sur es la segunda mas alta ya que hay gran variedad de estratos, y el costo es también alto debido a, como ya mencionamos anteriormente, la gran valorización que tiene esta zona.
promedio_banios <- vivienda_sinNA %>% group_by(zona) %>% summarise(promedio_banios = round(mean(banios), digits=1))
knitr::kable(promedio_banios, caption = "Promedio de baños por zona")
| zona | promedio_banios |
|---|---|
| Zona Centro | 3.0 |
| Zona Norte | 2.9 |
| Zona Oeste | 3.2 |
| Zona Oriente | 2.8 |
| Zona Sur | 3.1 |
promedio_parqueaderos <- vivienda_sinNA %>% group_by(zona) %>% summarise(promedio_parquea = round(mean(parquea), digits=1))
knitr::kable(promedio_parqueaderos, caption = "Promedio de parqueaderos por zona")
| zona | promedio_parquea |
|---|---|
| Zona Centro | 0.8 |
| Zona Norte | 1.2 |
| Zona Oeste | 1.7 |
| Zona Oriente | 0.7 |
| Zona Sur | 1.5 |
promedio_habitaciones <- vivienda_sinNA %>% group_by(zona) %>% summarise(promedio_habitaciones = round(mean(habitac), digits=1))
knitr::kable(promedio_habitaciones, caption = "Promedio de habitaciones por zona")
| zona | promedio_habitaciones |
|---|---|
| Zona Centro | 5.0 |
| Zona Norte | 3.7 |
| Zona Oeste | 3.4 |
| Zona Oriente | 5.1 |
| Zona Sur | 3.7 |
En cuanto a características de las viviendas, se ofrecen en general muy parecidos los datos para cantidad de baños para todas las zonas, para parqueaderos el oriente y el sur ofrecen mejores opciones para los que necesiten de viviendas con estas opciones. Para las viviendas con mayor cantidad de habitaciones, êstas se encuentran ubicadas en el centro y el oriente.
En esta sección procederemos a explicar el proceso que se realizó para la limpieza de los datos de la base de datos original.
Para realizar el análisis, se trabajo con una base de datos de viviendas de Cali con 8830 registros originalmente, que cuentan con variables como zona, piso, estrato, area construida, tipo, barrio entre otras, lo que permite visualizar la oferta por cada una de estas variables. Esta base de datos presentaba algunos datos faltantes para las variables de piso y parqueadero, a continuación se procede a explicar los métodos aplicados para la limpieza de los datos.
Inicialmente limpiamos la base de datos para normalizar y eliminar valores faltantes y atípicos. Inicialmente se observan que la variable tipo tiene un problema de escritura pues en algunas ocasiones está en mayúscula y minúscula o lo escriben de forma diferente. Por lo tanto se procede a normalizar colocándolas todas en minúsculas, y además unificando los valores.data("vivienda_faltantes")
vivienda_modificada <- vivienda_faltantes
vivienda_modificada$tipo <- tolower(vivienda_modificada$tipo)
vivienda_modificada$tipo <- ifelse(vivienda_modificada$tipo == "apto", "apartamento", vivienda_modificada$tipo)
Luego de esto se observan datos con valores faltantes en la mayoría de los datos, comenzando por el id que es un dato que normalmente es obligatorio, por lo tanto se asumen como registros erróneos y se procede a eliminarlos.
vivienda_modificada <- subset(vivienda_modificada, !is.na(vivienda_modificada$id))
Después de esto se identifica que las variables con valores faltantes son los pisos y parqueaderos. En ese caso se procede a realizar un análisis para identificar la mejor forma de limpiar o imputar estos datos. La variable parqueadero se compara contra los datos de el area construida, el estrato, la zona y el precio y el tipo:
parquea <- table(vivienda_modificada$parquea, vivienda_modificada$estrato, vivienda_modificada$tipo)
parquea
## , , = apartamento
##
##
## 3 4 5 6
## 1 280 921 972 126
## 2 14 120 640 812
## 3 2 3 30 218
## 4 0 2 16 72
## 5 0 0 0 4
## 6 0 0 0 2
## 7 0 0 1 0
## 8 0 0 0 0
## 9 0 0 0 0
## 10 0 1 1 0
##
## , , = casa
##
##
## 3 4 5 6
## 1 277 317 235 28
## 2 79 198 390 225
## 3 17 47 106 98
## 4 10 21 84 181
## 5 1 4 27 32
## 6 3 4 13 46
## 7 0 2 4 11
## 8 0 0 3 14
## 9 0 1 1 2
## 10 1 1 0 4
Como vemos en los datos, son muy pocos registros en el caso de apartamentos que tiene más de 4 parqueaderos, por lo tanto se asume que estos datos están mal y se normalizan a 4. Para las casas hay más datos con parqueaderos mayores a 4, pero también es posible que se estén refiriendo a parqueaderos externos o en la entrada de condominios. Se asumen entonces que debido a la cantidad de registros que hay por parqueaderos mayor a 6 en las casas, estos valores mayores se normalizaran a 6, y en ambos tipos de vivienda para las que tienen NA se asume que esto es debido a que no tienen parqueaderos por lo tanto se colocarán como 0.
vivienda_modificada$parquea <- ifelse(vivienda_modificada$parquea > 4 & vivienda_modificada$tipo == "apartamento", 4, vivienda_modificada$parquea)
vivienda_modificada$parquea <- ifelse(vivienda_modificada$parquea > 6 & vivienda_modificada$tipo == "casa", 6, vivienda_modificada$parquea)
vivienda_modificada$parquea <- ifelse(is.na(vivienda_modificada$parquea), 0, vivienda_modificada$parquea)
parquea <- table(vivienda_modificada$parquea, vivienda_modificada$estrato, vivienda_modificada$tipo)
parquea
## , , = apartamento
##
##
## 3 4 5 6
## 0 343 357 107 62
## 1 280 921 972 126
## 2 14 120 640 812
## 3 2 3 30 218
## 4 0 3 18 78
## 5 0 0 0 0
## 6 0 0 0 0
##
## , , = casa
##
##
## 3 4 5 6
## 0 426 132 121 55
## 1 277 317 235 28
## 2 79 198 390 225
## 3 17 47 106 98
## 4 10 21 84 181
## 5 1 4 27 32
## 6 4 8 21 77
sum(is.na(vivienda_modificada$parquea))
## [1] 0
Con esto los datos de parqueadero quedan listos para ser utilizados en el ańalisis. El siguiente dato con valores faltantes es el numero de pisos, que es un poco más confuso, pues aunque según la identificación de la columna se entiende como cantidad de pisos en el inmueble, esto sólo tendría sentido para las casas, y los valores sólo podrían ser máximo 3 pisos. Para los apartamentos el piso sería la ubicación del apartamento entonces la variable piso sería una variable mal definida pues se usa para dos características diferentes. Miremos como se ve la variable piso segun el tipo de vivienda:
piso <- table(vivienda_modificada$piso, vivienda_modificada$tipo)
piso
##
## apartamento casa
## 1 431 430
## 2 512 938
## 3 573 524
## 4 545 62
## 5 564 4
## 6 243 2
## 7 203 4
## 8 211 0
## 9 146 0
## 10 128 2
## 11 84 0
## 12 83 0
En este caso vemos que la mayoría de los datos en apartamentos estaría bien definido pues un apartamento en el piso 12 es algo que puede ocurrir. Se asumirá que las casas están todas ubicadas en el piso 1 y los apartamentos se dejan como están.
vivienda_modificada$piso <- ifelse(vivienda_modificada$tipo == "casa", 1, vivienda_modificada$piso)
piso <- table(vivienda_modificada$piso, vivienda_modificada$tipo)
piso
##
## apartamento casa
## 1 431 3221
## 2 512 0
## 3 573 0
## 4 545 0
## 5 564 0
## 6 243 0
## 7 203 0
## 8 211 0
## 9 146 0
## 10 128 0
## 11 84 0
## 12 83 0
sum(is.na(vivienda_modificada$piso))
## [1] 1383
vivienda_modificada <- filter(vivienda_modificada, vivienda_modificada$areaconst!=0)
vivienda_modificada <- filter(vivienda_modificada, vivienda_modificada$habitac!=0)
vivienda_modificada <- filter(vivienda_modificada, vivienda_modificada$preciom!=0)
vivienda_modificada <- filter(vivienda_modificada, vivienda_modificada$banios!=0)
na_piso <- subset(vivienda_modificada, is.na(vivienda_modificada$piso))
table(na_piso$tipo)
##
## apartamento
## 1376
Se observa que hay bastantes registros en apartamento sin ubicación de piso. En este caso, y dado que se asumió que variable piso hace referencia a la ubicación del apartamento en un edificio, se decide que el dato no es correcto y por lo tanto se elimina de la base de datos.
vivienda_modificada <- filter(vivienda_modificada, !is.na(vivienda_modificada$piso))
sum(is.na(vivienda_modificada$piso))
## [1] 0
vivienda_sinNA <- vivienda_modificada
Una vez eliminados los datos faltantes, se proceden a revisar los datos de las otras variable para identificar valores atípicos que pueda afectar el informe. Comenzando por las variables latitud y longitud que están en formatos diferentes y debido a que se tiene el nombre del barrio y la zona no se tendrán en cuenta pues no aportan datos relevantes para este informe
Ahora bien, al revisar la información del area construida y el precio, vemos que no es consistente en algunos datos de viviendas muy grandes con precios bajos, vamos a verlo gráficamente:
plot(vivienda_sinNA$areaconst, vivienda_sinNA$preciom, xlab="Area construida", ylab="precio de la vivienda", main="Gráfico de area x precio", bg = "red", col="skyblue", cex=3, lwd=3)
Como se observa en la gráfica, hay muchos datos atípicos con las variables area y precio, pues se espera que entre mayor área construida el precio sea mas alto. En ese caso vamos a sacar una nueva variable que se llame precio x metro cuadrado que nos permita ver mejor estos datos atípicos:
vivienda_sinNA$precioxmetro <- vivienda_sinNA$preciom / vivienda_sinNA$areaconst
vivienda_sinNA$precioxmetro <- round(vivienda_sinNA$precioxmetro, digits=2)
boxplot(vivienda_sinNA$precioxmetro, main= "Diagrama de cajas y bigotes para la nueva variable precio x metro")
En el diagrama de cajas y bigotes sobre la nueva variable nos confirma
que definitivamente hay muchos datos atípicos en el precio x metro de
las viviendas. Se retiran los registros que corresponden a areas de más
de 1000 y con precios menores de 500M y viceversa.
vivienda_sinNA <- filter(vivienda_sinNA, !(vivienda_sinNA$areaconst > 1000 & vivienda_sinNA$preciom < 500))
vivienda_sinNA <- filter(vivienda_sinNA, !(vivienda_sinNA$areaconst < 500 & vivienda_sinNA$preciom > 1000))
Luego procedemos a hacer el mismo ejercicio pero esta vez con las variables baños y habitaciones.
plot(vivienda_sinNA$banios, vivienda_sinNA$habitac, xlab="Cantidad de baños en la vivienda", ylab="Cantidad de habitaciones en la vivienda", main="Gráfico de baños por habitaciones en vivienda")
Vemos que hay casos en que las habitaciones son menores que la cantidad
de baños, lo cual no tiene sentido pues lo normal es que los baños sean
menores o iguales a la cantidad de habitaciones, por lo tanto se asumen
que son datos atípicos y se retiran, generando un nuevo gráfico para ver
el cambio más fácil.
vivienda_sinNA <- filter(vivienda_sinNA, !(vivienda_sinNA$banios > 4 & vivienda_sinNA$habitac < 4))
vivienda_sinNA <- filter(vivienda_sinNA, !(vivienda_sinNA$banios > 6 & vivienda_sinNA$habitac < 6))
vivienda_sinNA <- filter(vivienda_sinNA, !(vivienda_sinNA$banios < 2 & vivienda_sinNA$habitac > 4))
plot(vivienda_sinNA$banios, vivienda_sinNA$habitac, xlab="Cantidad de baños en la vivienda", ylab="Cantidad de habitaciones en la vivienda", main="Gráfico de baños por habitaciones en vivienda")
Con esto finalizamos el proceso de análisis, es posible que hayan otras
relaciones que puedan estar faltando sin embargo dado que hay que hacer
muchos supuestos la incertidumbre es bastante alta con la información
que se pueda obtener.
Durante la realización de este informe, se trabajaron con varios supuestos, ya que los datos están algo ambiguos en cierta información y poco homogeneizados, lo cual dificulta el trabajo que implica analizarlos. Por otro lado sería bueno tener más información de la venta de viviendas pues es posible que se hayan acumulado por problemas durante los procesos de adquisición, también identificar cuanto tiempo llevan las viviendas siendo ofertadas, pues si hay ofertas muy viejas es posible que las viviendas tengan algún problema por el cual se hace difícil venderlas.
Algunos de los puntos en los que se encontraron problemas fueron mencionados en la sección de la limpieza de datos sin embargo se vuelven a mencionar aca:
El tema de la variable piso, que puede tener dos interpretaciones diferentes, una para el piso en el que se ubica la vivienda o el otro la cantidad de pisos que tiene. Es preferible tener estos datos separados pues son características diferentes dependiendo del tipo.
Los datos faltantes en la variable parqueadero, estos son más sencillos pues es posible que la vivienda no tenga parqueaderos, y por lo tanto las personas llenaron el campo con el valor NA. Debido a que también la proporción de datos afectados no era tanta fue posible imputarlos a 0.
Este informe sirvió para identificar la importancia del proceso de limpieza de datos, pues cualquier dato que quede mal imputado o que sea eliminado implicará que el resultado se vea afectado drásticamente, ya que en un inicio este informe se desarrolló imputando las variables faltantes con valores promedio de viviendas con area, estrato y zona similares que si tuvieran información. Este proceso fue dispendioso y se detectó que no era necesario hacerlo pues habian formas más sencillas de lograr resultados similares. Sin embargo es posible que con otro tipo de bases de datos sea necesario que se apliquen este tipo de métodos, por lo cual esperamos poder afianzar el conocimiento necesario para poder identificar cuándo es bueno aplicarlos.
Es interesante como gráficamente se pueden observar de forma rápida los comportamientos y patrones de los datos que pueden ayudar a tomar decisiones, aunque este ejercicio no es tan complejo sirve para descubrir el potencial de la ciencia de datos y como abrebocas a todo lo que se puede hacer con un bueno análisis y con buenas técnicas aplicadas.