Informe ejecutivo de mercado inmobiliario en Cali para la empresa B&C
El estudio del dinamismo del mercado inmobiliario en Cali es indispensable para el enfoque de las campañas de ventas y la definición de objetivos tanto de mercadeo como de los agentes de la inmobiliaria B&C.
Por está razón la compañía ha recolectado información de las caracteristicas de la venta de viviendas en la ciudad. En este ejercicio se revisará las variables, haciendo limpieza de datos en los casos que sea necesario para posteriormente realizar un analisis descriptivo.
Realizar un análisis descriptivo de los datos a partir de la información recolectada por B&C para identificar las tendencias y patrones, abarcando caracteriscticas de viviendas y precios por zonas.
A continuación se muestra la base con los datos recolectados por la empresa.
La información se compone de 8322 registros y 13 variables que se describen a continuación:
id: Identificador único de la vivienda
zona: Zona de la ciudad
piso: Piso en la que está ubicada la vivienda
estrato: Estrato
preciom: Precio en millones de pesos
areaconst: Área construida en metros cuadrados
parqueaderos: Número de parqueaderos
banios: Número de baños
habitaciones: Número de habitaciones
tipo: Tipo de vivienda
barrio: Barrio
longitud: Coordenada de longitud
latitud: Coordenada de latitud
Para facilitar el manejo de la información se transformaron los nombres de las variables e igualmente se realizaron los cambios en los tipos de variables que lo requerian.
colnames(base) <- c("ID", "Zona", "Piso", "Estrato",
"Precio", "Area_Construida",
"Parqueadero", "Baños", "Habitaciones",
"Tipo", "Barrio", "Longitud", "Latitud")
base$Estrato <- as.character(base$Estrato)
base$ID <- as.character(base$ID)
baseSe removieron de la base los registros que con NAs en ID por que no contenian información en las otras variables (3 casos).
Por otro lado se eliminaron 8 registros que se encontraban repetidos en ID y contenian los mismos datos en las demás variables.
Las otras variables con NAs se describen a continuacón.
Debido a que Piso y Parqueadero son las únicas variables con NAs y por
la cantidad de registros involucrados se abordara más adelante en la
descripción de cada variable.
Se identificaron numeros que no correspondia a las coordenadas de la ciudad de Cali, de está manera se pudo inferir que algunos valores requerian dividirse por 1000.
Se realizó un mapa con los puntos de las posiciones cardinales de la sección anterior donde se identifica por colores la variable Zona.
ggplot(base, aes(x = Longitud, y = Latitud, color = as.factor(Zona))) +
geom_point() +
labs(title = "Mapa de Cali - Colombia", x = "Longitud", y = "Latitud") +
theme_minimal() +
guides(color = guide_legend(title = "Zonas"))A pesar que se pueden observar algunos puntos mal etiquetados, se puede evidenciar una demarcación de las zonas y una validación visual entre estas dos variables.
Se realizó la creación de la variable Precio por metro cuadrado porque, debido a la relación entre estas variables, se puede obtener una mayor información a traves de este indicador.
plot(base$Area_Construida, base$Precio, main = "Gráfico de Dispersión Precio y Área Construida", xlab = "Área Construida mt2", ylab = "Precio en millones")A partir de este gráfico de puede inferir la relación positiva entre estas variables y observar una disperción de los datos, que debe ser tratada con el objetivo de centrar el analisis sobre las tendencias. A continuación se presenta la dispersión por zonas del precio por metro cuadrado.
ggplot(base, aes(x = Zona, y = Preciomt2)) +
geom_boxplot() +
labs(title = "Boxplot por Zona y Metro Cuadrado", x = "Zona", y = "Precio por Metro Cuadrado")Preciomt2_por_zona <- base %>%
group_by(Zona) %>%
summarize(
Promedio = mean(Preciomt2 ),
Mediana = median(Preciomt2),
DesviacionEstandar = sd(Preciomt2 )
)
Preciomt2_por_zonaCon el objetivo de encontrar patrones sobre la información se ha decidido eliminar estos outliers superiores atraves del calculo del rango intercuartílico, eliminando 104 registros. A continuación se muestra el resultado.
eliminar_outliers_por_zona <- function(datos_zona) {
q1 <- quantile(datos_zona$Preciomt2, 0.25)
q3 <- quantile(datos_zona$Preciomt2, 0.75)
iqr <- q3 - q1
umbral_superior <- q3 + 1.5 * iqr
datos_sin_outliers <- filter(datos_zona, Preciomt2 <= umbral_superior)
return(datos_sin_outliers)
}
base <- base %>%
group_split(Zona) %>%
map_df(~eliminar_outliers_por_zona(.))
base <- ungroup(base)
ggplot(base, aes(x = Zona, y = Preciomt2)) +
geom_boxplot() +
labs(title = "Boxplot por Zona", x = "Zona", y = "Precio por Metro Cuadrado")Las correciones en esta variable correspondieron a correciones en la escritura de las etiquetas.
base <- base %>%
mutate(Tipo = case_when(
Tipo == "casa"| Tipo == "CASA"~ "Casa",
Tipo == "APARTAMENTO" |Tipo == "apto" ~ "Apartamento",
TRUE ~ Tipo
))
Preciomt2_por_tipo <- base %>%
group_by(Tipo) %>%
summarize(
Viviendas = length(ID),
Promedio = mean(Preciomt2 ),
Mediana = median(Preciomt2),
DesviacionEstandar = sd(Preciomt2))
Preciomt2_por_tipoEstá variable no cuenta con informaicón de los estratos 1 y 2 que puede corresponder a preferencias de canal para publicación y gestión en la venta de vivienda, esto puede generar distorciones en los analisis que se generen con la base. Sobre está variable no se realizaron ajustes.
ggplot(base, aes(x = Estrato, y = Parqueadero)) +
geom_boxplot(na.rm = TRUE) +
labs(title = "Gráfico de Caja para Correlación Parqueadero y Estrato",
x = "Estrato",
y = "Parqueadero")ggplot(base, aes(x = Tipo, y = Parqueadero)) +
geom_boxplot(na.rm = TRUE) +
labs(title = "Gráfico de Caja para Correlación Parqueadero y Tipo",
x = "Tipo",
y = "Parqueadero")En la gráfica anterior se puede inferir que esta variable tiene correlación con las variables estrato y tipo debido a la diferencia entre las medianas. Por esta razón se decidió imputar la mediana en los datos no disponibles segun el grupo de Estrato y Tipo.
Por otro lado, tambien se evidencian valores altos en el numero de parqueaderos clasificados por Estrato, que son valores atipicos muy marcados, por lo que se decidio cambiar por la mediana de cada grupo.
base <- base %>%
group_by(Estrato, Tipo) %>%
mutate(
Parqueadero = ifelse(is.na (Parqueadero),
median(Parqueadero, na.rm = TRUE),
Parqueadero)
) %>%
ungroup()
base <- base %>%
mutate(Parqueadero = ceiling(Parqueadero))
reemplazar_outliers2 <- function(datos) {
datos %>%
group_by(Estrato) %>%
mutate(
Parqueadero = ifelse(
Parqueadero > mean(Parqueadero,
na.rm = TRUE) + 2 * sd(Parqueadero,
na.rm = TRUE),
mean(Parqueadero, na.rm = TRUE),
Parqueadero
)
)
}
base <- reemplazar_outliers2(base)
ggplot(base, aes(x = Estrato, y = Parqueadero)) +
geom_boxplot(na.rm = TRUE) +
labs(title = "Gráfico de Caja para Correlación Parqueadero y Estrato",
x = "Estrato",
y = "Parqueadero")habitaciones <- base %>%
group_by(Tipo, Estrato) %>%
summarize(
Mediana = median(Habitaciones),
Minimo = min(Habitaciones),
Maximo = max(Habitaciones)
)
habitacionesDebido a que no tiene sentido que no existan habitaciones en viviendas, se imputó la mediana de las habitaciones por tipo y estrato sin tener en cuenta los registros en cero.
Se evaluaron los registros con alto numero de habitaciones, sin embargo se decidió mantener estos registros.
base <- base %>%
group_by(Estrato,Tipo) %>%
mutate(
Habitaciones = ifelse(Habitaciones == 0,
median(Habitaciones[Habitaciones != 0],
na.rm = TRUE),
Habitaciones)
) %>%
ungroup()
habitaciones <- base %>%
group_by(Tipo, Estrato) %>%
summarize(
Mediana = median(Habitaciones ),
Minimo = min(Habitaciones),
Maximo = max(Habitaciones)
)
habitacionesPor tratarse de una caracteristica de las propiedades, se verifico contra las las variables Tipo y Estrato.
tabla_baños <- base %>%
group_by(Tipo, Estrato) %>%
summarize(
Viviendas = length(ID),
Mediana = median(Baños),
Minimo = min(Baños),
Maximo = max(Baños)
)
tabla_bañosggplot(base, aes(x = Estrato, y = Baños)) +
geom_boxplot() +
labs(title = "Boxplot Numero de Baños", x = "Estratos", y = "# Baños")Dado que una propiedad para vivienda tenga 0 baños no es adecuado, se imputo la mediana de cada grupo para los registros que tienen este valor.
base <- base %>%
group_by(Estrato,Tipo) %>%
mutate(
Baños = ifelse(Baños == 0,
median(Baños[Baños != 0],
na.rm = TRUE),
Baños)
) %>%
ungroup()
base <- base %>%
mutate(Baños = ceiling(Baños))
tabla_baños2 <- base %>%
group_by(Tipo, Estrato) %>%
summarize(
Viviendas = length(ID),
Mediana = median(Baños),
Minimo = min(Baños),
Maximo = max(Baños)
)
tabla_baños2 Esta variable corresponde al piso en la que está ubicada la vivienda y realizando la exploración se identificaron registros con tipo casa en pisos altos, lo cual genera la duda sobre el entendimiento del capo por parte de las personas que realizan el ingreso de datos.
La evaluación sobre el impacto de Piso sobre la variable Peso tampoco arroja cambios significativos en las medianas por numero de pisos. Por estas razones esta variable no será tenida en cuenta.
base$Piso<-factor(base$Piso)
datos_sin_nulos <- base[complete.cases(base$Piso, base$Precio), ]
ggplot(base, aes(x = Piso, y = Precio)) +
geom_boxplot() +
labs(title = "Gráfico de Caja para Piso y Precio",
x = "Piso",
y = "Precio en millones")Sin duda la variable con mayor dificultad para tratar fue Barrio, debido a que el registro no se encuentra estandarizado. Utilizando una base de datos del gobierno local, donde se catalogaban los barrios de Cali, se procedió a realizar una validación sobre los datos recolectados en esta variable.
Se transformaron ambas variables, cambiando a minuscula, sin tildes y sin caracteres especiales con el objetivo de homogenizar y abarcar alcanzar un mayor número de nombres de barrio validados. Del total de 8.215 registros con un total de 383 elementos de la base de B&C se validaron correctamente 171 (44.4%) equivalentes a 3.991 registros (48,6%), los restantes 212 nombres alguno no correspondían a nombres de barrio sino a sectores de la ciudad o nombres que popularmente se le asignan a espacios.
Debido al alto porcentaje de datos no validados se decidió no utlizar la variable Barrio en el analisis.
quitar_tildes <- function(texto) {
return(iconv(texto, to = "ASCII//TRANSLIT"))
}
remover_caracteres_especiales <- function(texto) {
return(gsub("[^a-zA-Z0-9 ]", "", texto))
}
barrios_cali <- read_excel("Barrios.xls")
barrios_cali$Barrio2<- tolower(barrios_cali$Barrio)
barrios_cali$Barrio2<-quitar_tildes(barrios_cali$Barrio2)
barrios_cali$Barrio2 <- remover_caracteres_especiales(barrios_cali$Barrio2)
base$Barrio2 <- tolower(base$Barrio)
base$Barrio2 <- quitar_tildes(base$Barrio2)
base$Barrio2 <- remover_caracteres_especiales(base$Barrio2)
barrios_coincidentes <- intersect(base$Barrio2, barrios_cali$Barrio2)
#length(barrios_coincidentes)
barrios_no_coincidentes <- setdiff(base$Barrio2, barrios_cali$Barrio2)
#length(barrios_no_coincidentes)
barrios_encontrados <- base$Barrio2 %in% barrios_cali$Barrio2
numero_elementos_no_encontrados <- sum(!barrios_encontrados)
#numero_elementos_no_encontrados
basefinal<- subset(base, select = - c(Barrio, Barrio2))Sobre las variables númericas se puede afirmar que la vivienda regular tiene 123 metros cuadrados, 3 habitaciones, 3 baños y 1 parqueadero con un precio promedio de $175 millones y $2.6 millones por metro cuadrado.
basefinal$Estrato<-as.factor(basefinal$Estrato)
basefinal$Zona<-as.factor(basefinal$Zona)
basefinal$Tipo<-as.factor(basefinal$Tipo)
desc_stats_numericas <- basefinal %>%
select_if(is.numeric) %>%
summary()
desc_stats_categoricas <- basefinal %>%
select_if(is.factor) %>%
summary()%>%
replace(is.na(.), " ")
kable(desc_stats_numericas, "html") %>%
kable_styling() | Precio | Area_Construida | Parqueadero | Baños | Habitaciones | Longitud | Latitud | Preciomt2 | |
|---|---|---|---|---|---|---|---|---|
| Min. : 58 | Min. : 40.0 | Min. :1.000 | Min. : 1.000 | Min. : 1.000 | Min. :-76.59 | Min. :3.333 | Min. :0.1461 | |
| 1st Qu.: 220 | 1st Qu.: 80.0 | 1st Qu.:1.000 | 1st Qu.: 2.000 | 1st Qu.: 3.000 | 1st Qu.:-76.54 | 1st Qu.:3.381 | 1st Qu.:1.9069 | |
| Median : 330 | Median : 123.0 | Median :1.000 | Median : 3.000 | Median : 3.000 | Median :-76.53 | Median :3.417 | Median :2.6250 | |
| Mean : 428 | Mean : 175.1 | Mean :1.572 | Mean : 3.122 | Mean : 3.637 | Mean :-76.53 | Mean :3.418 | Mean :2.6862 | |
| 3rd Qu.: 530 | 3rd Qu.: 229.0 | 3rd Qu.:2.000 | 3rd Qu.: 4.000 | 3rd Qu.: 4.000 | 3rd Qu.:-76.52 | 3rd Qu.:3.452 | 3rd Qu.:3.3548 | |
| Max. :1999 | Max. :1745.0 | Max. :5.000 | Max. :10.000 | Max. :10.000 | Max. :-76.46 | Max. :3.498 | Max. :6.6667 |
| Zona | Estrato | Tipo | |
|---|---|---|---|
| Zona Centro : 118 | 3:1439 | Apartamento:5037 | |
| Zona Norte :1907 | 4:2119 | Casa :3178 | |
| Zona Oeste :1188 | 5:2740 | ||
| Zona Oriente: 339 | 6:1917 | ||
| Zona Sur :4663 |
Por zonas, las viviendas en el sur representan el 57% de los registros, los estratos 4 y 5 el 59%, mientras que el tipo apartamento es el 61%, siendo las caracteristicas predominantes.
tabla_conteo <- basefinal %>%
group_by(Estrato, Zona) %>%
count() %>%
pivot_wider(names_from = Estrato, values_from = n, names_prefix = "Estrato_") %>%
replace(is.na(.), 0)
# Mostrar la tabla formateada
kable(tabla_conteo, "html") %>%
kable_styling() # Aplica estilos predeterminados| Zona | Estrato_3 | Estrato_4 | Estrato_5 | Estrato_6 |
|---|---|---|---|---|
| Zona Centro | 103 | 12 | 2 | 1 |
| Zona Norte | 570 | 407 | 765 | 165 |
| Zona Oeste | 54 | 83 | 289 | 762 |
| Zona Oriente | 331 | 6 | 2 | 0 |
| Zona Sur | 381 | 1611 | 1682 | 989 |
Desde este punto de vista general, las caracteristicas que abarcan una mayor cantidad de viviendas son: apartamentos que sean de estrato 4 o 5 , ubicadas en sur, con 3 habitaciones, 3 baños y 1 parqueadero.
Mode <- function(x) {
ux <- unique(x)
ux[which.max(tabulate(match(x, ux)))]
}
resultados2 <- basefinal %>%
group_by(Zona) %>%
summarise(
Conteo_Registros = length(Precio),
Promedio_Preciomt2 = mean(Preciomt2, na.rm = TRUE),
Moda_Precio = Mode(Precio),
Minimo = min(Preciomt2),
Maximo = max(Preciomt2)
)
kable(resultados2, format = "markdown")| Zona | Conteo_Registros | Promedio_Preciomt2 | Moda_Precio | Minimo | Maximo |
|---|---|---|---|---|---|
| Zona Centro | 118 | 1.619689 | 180 | 0.8133333 | 2.905405 |
| Zona Norte | 1907 | 2.423730 | 320 | 0.2569444 | 5.131579 |
| Zona Oeste | 1188 | 3.611341 | 450 | 0.7678571 | 6.666667 |
| Zona Oriente | 339 | 1.365056 | 150 | 0.1461318 | 2.777778 |
| Zona Sur | 4663 | 2.680811 | 250 | 0.2809917 | 5.014388 |
Se puede observar que las viviendas con menor valor en millones y por metro cuadrado se encuentran en el oriente, mientras que los de mayor valor en el oeste. La mayoria de propiedades se encuentran en el sur con una valor promedio de $2.7 millones y siendo $250 millones el valor que mas se repite. Adicionalmente se muestran los valores minimos y maximos para que la empresa B&C tenga como referencia para establecer precios de venta.
Como se mencionó anteriormente el tipo de vivienda apartamento tienen una mayor proporción en base de información.
frecuencia_tipo <- table(basefinal$Tipo)
datos_frecuencia <- data.frame(Tipo = names(frecuencia_tipo), Frecuencia = as.numeric(frecuencia_tipo))
ggplot(datos_frecuencia, aes(x = "", y = Frecuencia, fill = Tipo)) +
geom_bar(stat = "identity", width = 1) +
coord_polar(theta = "y") +
theme_minimal() +
labs(title = "Gráfico de Torta para la Variable Tipo")Analizando unicamente los registros de apartamentos encontramos que los estratos 4 y 5 de la zona sur tienen una proporcion del 42%, mostranto una fuerte concentración en este tipo de viviendas.
base_aptos <- filter(basefinal, Tipo == "Apartamento")
tabla_conteo2 <- base_aptos %>%
group_by(Estrato, Zona) %>%
count() %>%
pivot_wider(names_from = Estrato, values_from = n, names_prefix = "Estrato_") %>%
replace(is.na(.), 0)
kable(tabla_conteo2, caption = "Registros de Apartamentos", "html") %>%
kable_styling() | Zona | Estrato_3 | Estrato_4 | Estrato_5 | Estrato_6 |
|---|---|---|---|---|
| Zona Centro | 14 | 7 | 2 | 0 |
| Zona Norte | 337 | 246 | 495 | 110 |
| Zona Oeste | 29 | 58 | 231 | 704 |
| Zona Oriente | 56 | 0 | 1 | 0 |
| Zona Sur | 201 | 1088 | 1031 | 427 |
Para las propiedades tipo casa, son los estrato 5 y 6 de la misma zona sur las caracteristicas que más agrupan viviendas, representando un 38.2%
base_casa <- filter(basefinal, Tipo == "Casa")
tabla_conteo3 <- base_casa %>%
group_by(Estrato, Zona) %>%
count() %>%
pivot_wider(names_from = Estrato, values_from = n, names_prefix = "Estrato_") %>%
replace(is.na(.), 0)
kable(tabla_conteo3, caption = "Registros de Casas", "html") %>%
kable_styling() | Zona | Estrato_3 | Estrato_4 | Estrato_5 | Estrato_6 |
|---|---|---|---|---|
| Zona Centro | 89 | 5 | 0 | 1 |
| Zona Norte | 233 | 161 | 270 | 55 |
| Zona Oeste | 25 | 25 | 58 | 58 |
| Zona Oriente | 275 | 6 | 1 | 0 |
| Zona Sur | 180 | 523 | 651 | 562 |
A traves del estudio de la base de datos, se muestra la importancia de contar con información de alta calidad, con filtros para el ingreso adecuado de datos y validaciones de los mismos, para evitar tener realizar imputaciones o suposiciones sobre las variables, o como en este caso, descartar el uso de las variables.
Se debe pulir las captura de las variables de ubicación que son relavantes para los analisis de mercado.
Tambien sería valioso contar con la fecha de registro, si se vendió y en que fecha, con el fin de identificar viviendas con alta rotación, si esta ubicada en unidad residencial, amenities, cuota de administración, etc.
Otro dato relevante es el valor de la comisión de la inmobiliaria, sobretodo si esta es diferenciada por precio o zona, con el objetivo de buscar estrategias de venta que optimicen el ingreso.
Exisite un sesgo las publicaciones en donde los vendedores se ven tentados a cambiar el estrato, el lugar, el barrio, etc., buscando llamar la atención.
Sería de gran aporte la caracterización de los compradores para enfocar las campañas de mercadeo.
Desde el punto de vista de la captura de los datos, algunas variables pueden prestarse a interpretación como podría ocurrir con la variable Piso, donde alguien puede pensar que se trata del piso donde se encuentra y otra persona el numero de pisos del inmueble.
Por otro lado, la interpretación de la base de datos puede variar, porque un número grande de viviendas no necesariamente significa un aumento en las ventas, puede responder al reflejo de efectos economicos que acumulan inventarios para la venta, dificultades en la financiación, etc.
Vale la pena investigar las razones por las cuales los estratos 1 y 2 no tienen registros en la base, porque podrian ser un mercado que está siendo atendido por otros canales.
Existe una cantidad considerable de ofertas de viviendas para la ciudad de cali, muchas de las cuales reponden a un auge de construcción en el sur de la ciudad.
La compañia podría tener dos enfoques de mercado:
El primero buscando abarcar una cantidad importante de viviendas a traves de un mismo mensaje que podria responder al segmento de propiedades con 3 habitaciones, 3 baños y 1 parqueadero ubicados en estrato 4 y 5 del sector de sur de la ciudad. Este nicho de mercado debe estar acompañado de campañas agresivas de mercadeo, debido a que se espera una mayor competencia.
El segundo enfoque sería para viviendas de estrato 6 ubicados en el sector occidente y sur, como un segmento premium que tambien tienen oportunidades de negocio.
Ambos segmentos requiere identificación de potenciales clientes para hacer campañas diferenciadas.