Informe ejecutivo de mercado inmobiliario en Cali para la empresa B&C

1. Introducción

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.

2. Objetivos

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.

3. Métodos

3.1 Información Inicial

A continuación se muestra la base con los datos recolectados por la empresa.

base<-vivienda_faltantes
base

3.2 Exploración y Modificación de Datos

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)

base

3.4 Registros no disponibles - NAs y repetidos

Se 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.

base <- base[complete.cases(base$ID), ]
base <- base[!duplicated(base$ID), ]
base

Las otras variables con NAs se describen a continuacón.

gg_miss_var(base)

VIM::aggr(base, cex.axis = 0.4, cex.lab= 0.8)

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.

3.5 Tratamiento por variable

3.5.1 Latitud y Longitud

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.

base$Longitud <- ifelse(base$Longitud < -100, 
                         base$Longitud/1000,
                         base$Longitud)
base$Latitud <- ifelse(base$Latitud < 100, 
                        base$Latitud,
                        base$Latitud/1000)

3.5.2 Zona

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.

3.5.3 Precio y área construida

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")

base <- mutate(base, Preciomt2 = (base$Precio / base$Area_Construida))

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_zona

Con 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")

3.5.4 Tipo

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_tipo

3.5.5 Estrato

Está 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.

3.5.6 Parqueadero

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")

3.5.7 Habitaciones

habitaciones <- base %>%
  group_by(Tipo, Estrato) %>%
  summarize(
    Mediana = median(Habitaciones),
    Minimo = min(Habitaciones),
    Maximo = max(Habitaciones)
  )

habitaciones

Debido 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)
  )

habitaciones

3.5.8 Baños

Por 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ños
ggplot(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 

3.5.9 Piso

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")

base <- subset(base, select = -Piso)

base

3.5.10 Barrios

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))

3.5.11 Base final

A continuación se muestra la base con las modificaciones descritas en las secciones anteriores, donde finalmente quedaron 8.215 registros y 12 variables la cual será analizada para identificar patrones.

basefinal

4. Resultados

4.1 Caracteristicas más relevantes

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
kable(desc_stats_categoricas, "html") %>%
  kable_styling()  
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.

4.2 Precios por Zonas

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.

4.3 Tipos de Vivienda

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")

4.3.1 Apartamentos

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()  
Registros de Apartamentos
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

4.3.2 Casas

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() 
Registros de Casas
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

5. Discusión

5.1 Calidad de Datos

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.

5.2 Interpretación de datos

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.

6. Conclusiones

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.