Introducción

De acuerdo con las cifras oficiales del DANE, el PIB de Colombia para 2023 muestra un crecimiento de 0.6% con respecto al del 2022, pero que al compararse con los últimos tres años, ha sido el de menor desempeño. Por otro lado, a partir de un desglose del crecimiento anual por sector se encuentra que el sector financiero es el de mayor contribución a la variación mientras que el sector inmobiliario tiene valores negativos.

En concordancia con lo anterior, es importante rescatar que el sector inmobiliario y de construcción ha contribuido históricamente al desarrollo del país y las regiones pero que por razones asociadas con costos de materiales e incrementos de tasas de interés se encuentra disminuido. Con el fin de reactivar el sector es importante proveer un análisis integral sobre su situación actual y a nivel regional o por ciudad permitiendo determinar las necesidades y oportunidades de inversión.

 

Objetivos

El objetivo de esta actividad es evaluar el contexto de bienes raíces de la ciudad de Cali a partir de los datos recolectados por la empresa Bines & Casas a través de técnicas de estadística descriptiva enfocadas en preguntas como: cuál es el precio zonal de viviendas, cuál es el tipo de vivienda más ofertada y cuáles son las características más relevantes de la oferta de vivienda.

 

Métodos

Para el desarrollo de esta actividad se llevará a cabo un análisis exploratorio de datos (EDA) para obtener información sobre el conjunto de datos vivienda_faltantes e identificar los patrones presentes en las variables. Este proceso incluirá las siguientes etapas:

  • Descripción general del dataset: se revisan medidas de tendencia central sobre el dataset original así como categorías más comunes por variable.

  • Análisis de valores faltantes: se analizarán los valores faltantes de cada variable así como la relación entre estos y el resto del conjunto de datos proponiendo algunas formas de imputación.

  • Limpieza de datos: se hará limpieza de datos mediante la normalización variables categóricas y revisión de valores atípicos.

  • Análisis univariado: se calcularán métricas de tendencia central y de dispersión para obtener información los valores centrales así como la variación de los datos. Adicionalmente, se examinará la distribución de cada variable usando histogramas y gráficos de densidad permitiendo identificar gráficamente simetría y puntos atípicos.

  • Análisis multivariado: se investigará las relaciones entre las variables, creando gráficos de dispersión y de correlación con el fin de indagar sobre la fuerza y dirección de estas relaciones.

 

Resultados

Descripción del dataset

El conjunto de datos original tiene 8330 filas con 13 columnas. Un conteo de valores únicos por variable del conjunto vivienda_faltantes nos muestra 8319 valores únicos para el identificador de bienes id por lo que deben revisarse duplicados para este campo. Por otro lado, la mayor variación del dataset se concentra en las variables numéricas de latitud, longitud, preciom y areaconst, respecto a las variables categóricas se tiene que barrio tiene 436 valores únicos por lo que sería ideal normalizar las categorías que se presentan.

suppressMessages(suppressWarnings(library(knitr)))
suppressMessages(suppressWarnings(library(rmarkdown)))
suppressMessages(suppressWarnings(library(paqueteMETODOS)))
suppressMessages(suppressWarnings(library(naniar)))
suppressMessages(suppressWarnings(library(mice)))
suppressMessages(suppressWarnings(library(ggmice)))
suppressMessages(suppressWarnings(library(pastecs)))
suppressMessages(suppressWarnings(library(stringr)))
suppressMessages(suppressWarnings(library(stringi)))
suppressMessages(suppressWarnings(library(dplyr)))
suppressMessages(suppressWarnings(library(tidyverse)))
suppressMessages(suppressWarnings(library(kableExtra)))
suppressMessages(suppressWarnings(library(reticulate)))
suppressMessages(suppressWarnings(library(lsr)))
suppressMessages(suppressWarnings(library(tibble)))
suppressMessages(suppressWarnings(library(patchwork)))
suppressMessages(suppressWarnings(library(sf)))
suppressMessages(suppressWarnings(library(osmdata)))
suppressMessages(suppressWarnings(library(mapview)))
suppressMessages(suppressWarnings(library(leaflet)))
suppressMessages(suppressWarnings(library(heatmaply)))
suppressMessages(suppressWarnings(library(ggplot2)))
suppressMessages(suppressWarnings(library(stats)))

data(vivienda_faltantes)

unique_counts <- sapply(vivienda_faltantes, function(x) length(unique(x[!is.na(x) & x != "" & !is.nan(x)])))

unique_counts_df <- data.frame('Conteo' = unique_counts)

rownames(unique_counts_df) <- names(unique_counts)

paged_table(unique_counts_df, options = list(rows.print = 15)) %>%
  kable(caption = 'Valores únicos por Variable del Dataset Original')
Valores únicos por Variable del Dataset Original
Conteo
id 8319
zona 5
piso 12
estrato 4
preciom 539
areaconst 652
parquea 10
banios 11
habitac 11
tipo 6
barrio 436
longitud 2928
latitud 3679
df = subset(vivienda_faltantes, select = -c(id, zona, tipo, barrio))
options(scipen = 999)
summary_df <- stat.desc(df) 
summary_df <- round(summary_df, 2)

paged_table(summary_df, options = list(rows.print = 15)) %>%
  kable(caption = 'Variables Numéricas')
Variables Numéricas
piso estrato preciom areaconst parquea banios habitac longitud latitud
nbr.val 5689.00 8327.00 8328.00 8327.00 6724.00 8327.00 8327.00 8327.00 8327.00
nbr.null 0.00 0.00 0.00 0.00 0.00 45.00 66.00 0.00 0.00
nbr.na 2641.00 3.00 2.00 3.00 1606.00 3.00 3.00 3.00 3.00
min 1.00 3.00 58.00 30.00 1.00 0.00 0.00 -76576.00 3.33
max 12.00 6.00 1999.00 1745.00 10.00 10.00 10.00 -76.46 3497.00
range 11.00 3.00 1941.00 1715.00 9.00 10.00 10.00 76499.54 3493.67
sum 21461.00 38590.00 3616356.00 1457121.75 12345.00 25914.00 30020.00 -181904380.55 8080270.18
median 3.00 5.00 330.00 123.00 2.00 3.00 3.00 -76.54 3.45
mean 3.77 4.63 434.24 174.99 1.84 3.11 3.61 -21845.13 970.37
SE.mean 0.03 0.01 3.61 1.57 0.01 0.02 0.02 378.11 16.87
CI.mean.0.95 0.07 0.02 7.07 3.07 0.03 0.03 0.03 741.20 33.06
var 6.84 1.06 108251.33 20434.66 1.27 2.04 2.13 1190518886.77 2369001.37
std.dev 2.62 1.03 329.02 142.95 1.13 1.43 1.46 34503.90 1539.16
coef.var 0.69 0.22 0.76 0.82 0.61 0.46 0.40 -1.58 1.59

 

En la siguientes tablas, se muestran conteos para los valores de las variables categóricas zona, tipo y barrio. Para la variable zona, el 56.8% de los registros corresponden a la Zona Sur y el 23.1% a la Zona Norte, lo que puede explicarse por el área que cubren. Sin embargo, sería ideal expandir el catálogo de bienes inmuebles en otras zonas como la Zona Central o la Zona Oriente.

counts <- table(vivienda_faltantes$zona)

percentages <- prop.table(counts) * 100

mode_value <- names(which.max(counts))

result <- data.frame(Value = names(counts),
                     Mode = ifelse(names(counts) == mode_value, mode_value, NA),
                     Count = as.integer(counts),
                     Prc = round(percentages, 2))

result <-  subset(result, select = c(Value, Mode, Count, Prc.Freq))
paged_table(result, options = list(rows.print = 15)) %>%
  kable(caption = 'Variables Categóricas - Zona')
Variables Categóricas - Zona
Value Mode Count Prc.Freq
Zona Centro NA 124 1.49
Zona Norte NA 1922 23.08
Zona Oeste NA 1204 14.46
Zona Oriente NA 351 4.22
Zona Sur Zona Sur 4726 56.76

 

Por otro lado, un conteo para la variable tipo nos muestra la necesidad de la normalización de valores “apartamento” y “casa” para las categorías presentadas en el dataset original. Al sumar las categorías equivalentes vemos que los registros que pertenecen a “apartamento” corresponden con el 61.3% de todo el conjunto de datos, por lo que este tipo de bienes serían los más predominantes en comparación a los de tipo “casa”.

df <- data.table::copy(vivienda_faltantes)

counts <- table(df$tipo)


percentages <- prop.table(counts) * 100

mode_value <- names(which.max(counts))

result <- data.frame(Value = names(counts),
                     Mode = ifelse(names(counts) == mode_value, mode_value, NA),
                     Count = as.integer(counts),
                     Prc = round(percentages, 2))

result <-  subset(result, select = c(Value, Mode, Count, Prc.Freq))
paged_table(result, options = list(rows.print = 15)) %>%
  kable(caption = 'Variables Categóricas - Tipo')
Variables Categóricas - Tipo
Value Mode Count Prc.Freq
Apartamento Apartamento 5032 60.43
APARTAMENTO NA 61 0.73
apto NA 13 0.16
casa NA 14 0.17
Casa NA 3195 38.37
CASA NA 12 0.14

 

Finalmente, un conteo para el top 10 de los valores más comunes para la variable categórica barrio muestra que la categoría más común es el barrio Valle del Lili con 1008 registros o 29.2% lo que está en concordancia con lo encontrado en la variable zona descrita anteriormente.

counts <- table(df$barrio)

top_counts <- counts[order(-counts)][1:10]

percentages <- prop.table(top_counts) * 100

mode_value <- names(which.max(top_counts))

result <- data.frame(Value = names(top_counts),
                     Mode = ifelse(names(top_counts) == mode_value, mode_value, NA),
                     Count = as.integer(top_counts),
                     Prc = round(percentages, 2))

result <-  subset(result, select = c(Value, Mode, Count, Prc.Freq))
paged_table(result, options = list(rows.print = 15)) %>%
  kable(caption = 'Variables Categóricas - Top 10 Barrio')
Variables Categóricas - Top 10 Barrio
Value Mode Count Prc.Freq
valle del lili valle del lili 1008 29.20
ciudad jardín NA 516 14.95
pance NA 409 11.85
la flora NA 367 10.63
santa teresita NA 262 7.59
el caney NA 208 6.03
el ingenio NA 202 5.85
la hacienda NA 164 4.75
acopi NA 158 4.58
normandía NA 158 4.58

 

Al revisar más a fondo los valores de las variables numéricas y categóricas se encuentran los siguientes puntos a tratar:

  • Deben tratarse los tres registros para los que las variables se encuentran totalmente vacías.
  • Debe hacerse una limpieza de datos sobre las categorías de la variable tipo y barrio.
  • Debe hacerse un limpieza de datos sobre los puntos de longitud y latitud que se encuentran por fuera del rango esperado para la ciudad de Cali.
  • Debe proponerse una técnica de imputación para los valores vacíos que aparecen asociados a las variables parquea (1606) y piso (2641).
  • Deben revisarse los valores nulos presentes en las variables banios (45) y habitac (66).

Respecto a la importancia de las variables para responder las preguntas propuestas, las unidades de análisis podrían enfocarse en los valores de tipo, zona, estrato, preciom y areaconst.

 

Valores Faltantes

# Eliminar valores faltantes comunes a todas las columnas
vivienda_faltantes <- vivienda_faltantes %>% drop_na(tipo)

p1 <- vis_miss(vivienda_faltantes)  + 
  labs(title = 'Mapa de calor para valores faltantes') + 
  theme(plot.title = element_text(color = 'black', face = 'bold', size = 12, hjust = 0.5))
p2 <- plot_pattern(vivienda_faltantes, square = TRUE, rotate = TRUE) +
  theme(
    legend.position = "none",
    axis.title = element_blank(),
    axis.title.x.top = element_blank(),
    axis.title.y.right = element_blank()
  )+ 
  labs(title = 'Patrón de valores faltantes') + 
  theme(plot.title = element_text(color = 'black', face = 'bold', size = 12, hjust = 0.5))

p1 | p2

 

El conjunto de datos tiene un 3.95% de valores faltantes y al evaluar las filas con algún valor faltante se tiene un porcentaje de 42.23%. Con el objetivo de facilitar la exploración de valores faltantes se empleó un mapa de calor para valores faltantes donde se resaltan las variables piso (32%) y parquea (19%). Además, en la tabla de valores faltantes por variable se pueden identificar tres valores que son comunes para todas las variables por lo que se procede a su eliminación.

Valores faltantes por Variable
Column Count
id 0
zona 0
piso 2638
estrato 0
preciom 0
areaconst 0
parquea 1603
banios 0
habitac 0
tipo 0
barrio 0
longitud 0
latitud 0
p1 <- vivienda_faltantes %>%gg_miss_var(show_pct = TRUE, facet = tipo) + 
  labs(title = 'Valores faltantes por tipo') + 
  theme(plot.title = element_text(color = 'black', face = 'bold', size = 12, hjust = 0.5))

p2 <- vivienda_faltantes %>% gg_miss_var(show_pct = TRUE, facet = zona) + 
  labs(title = 'Valores faltantes por zona') + 
  theme(plot.title = element_text(color = 'black', face = 'bold', size = 12, hjust = 0.5))

p1 | p2

 

Se procede a verificar la distribución de los valores faltantes sobre algunas variables de importancia en el análisis, por ejemplo para la variable tipo sin normalizar se encuentra que un 70% de los valores vacíos de parquea están relacionados con la categoría apto referente a apartamento por lo que podría relacionarse con apartamentos sin acceso a parqueadero. Del mismo modo, al evaluar por la variable zona se encuentra que para las zonas Central y Oriental, los valores faltantes están entre 50% al 60%. Una tabla agregada, nos muestra que el valor más común para parqueaderos en apartamentos y casas de ambas zonas es 1. En el caso de la variable piso para casa en zonas Norte, Centro y Oeste es de uno a dos pisos mientras que para los apartamentos de la misma zona parecen indicar el piso en el que se encuentra ubicado el apartamento por lo que presenta valores de 4 y 5.

 

df <- data.table::copy(vivienda_faltantes)
df$tipo <- tolower(df$tipo)
df$tipo <- gsub("apto", "apartamento", df$tipo)
df_na_tipo <- df[is.na(df$tipo), ]

mean_result <- aggregate(df$parquea, by=list(Tipo=df$tipo, Zone=df$zona), FUN=mean, na.rm=TRUE)

median_result <- aggregate(df$parquea, by=list(Tipo=df$tipo, Zone=df$zona), FUN=median, na.rm=TRUE)

std_result <- aggregate(df$parquea, by=list(Tipo=df$tipo, Zone=df$zona), FUN=sd, na.rm=TRUE)

colnames(mean_result) <- c("Tipo", "Zona", "Mean")
colnames(median_result) <- c("Tipo", "Zona", "Median")
colnames(std_result) <- c("Tipo", "Zona", "StdDev")

final_result <- merge(mean_result, std_result, by=c("Tipo", "Zona"))
final_result <- merge(final_result, median_result, by=c("Tipo", "Zona"))

paged_table(final_result, options = list(rows.print = 5)) %>%
  kable(caption = 'Valores de parquea agrupados por Tipo y Zona')
Valores de parquea agrupados por Tipo y Zona
Tipo Zona Mean StdDev Median
apartamento Zona Centro 1.000000 0.0000000 1
apartamento Zona Norte 1.372802 0.5685921 1
apartamento Zona Oeste 2.132853 0.7824506 2
apartamento Zona Oriente 1.318182 0.6463350 1
apartamento Zona Sur 1.414532 0.6692079 1
casa Zona Centro 1.481481 1.1115303 1
casa Zona Norte 2.181193 1.4033505 2
casa Zona Oeste 2.310606 1.3429634 2
casa Zona Oriente 1.390071 0.8261187 1
casa Zona Sur 2.415313 1.5069265 2
mean_result <- aggregate(df$piso, by=list(Tipo=df$tipo, Zone=df$zona), FUN=mean, na.rm=TRUE)

median_result <- aggregate(df$piso, by=list(Tipo=df$tipo, Zone=df$zona), FUN=median, na.rm=TRUE)

std_result <- aggregate(df$piso, by=list(Tipo=df$tipo, Zone=df$zona), FUN=sd, na.rm=TRUE)

colnames(mean_result) <- c("Tipo", "Zona", "Mean")
colnames(median_result) <- c("Tipo", "Zona", "Median")
colnames(std_result) <- c("Tipo", "Zona", "StdDev")

final_result <- merge(mean_result, std_result, by=c("Tipo", "Zona"))
final_result <- merge(final_result, median_result, by=c("Tipo", "Zona"))

paged_table(final_result, options = list(rows.print = 5)) %>%
  kable(caption = 'Valores de piso agrupados por Tipo y Zona')
Valores de piso agrupados por Tipo y Zona
Tipo Zona Mean StdDev Median
apartamento Zona Centro 4.733333 2.8401878 4
apartamento Zona Norte 4.868354 3.1215975 4
apartamento Zona Oeste 5.012730 2.8431333 5
apartamento Zona Oriente 2.617021 1.4073285 2
apartamento Zona Sur 4.469039 2.6709621 4
casa Zona Centro 1.574074 0.7915080 1
casa Zona Norte 1.994286 0.7531949 2
casa Zona Oeste 2.529412 1.2324481 2
casa Zona Oriente 1.993789 1.0810695 2
casa Zona Sur 2.189376 0.8051325 2

 

Imputación de datos

Para la propuesta de imputación de datos, es posible seguir el procedimiento descrito por van Buuren, S. (2018) en “Flexible Imputation of Missing Data”. El conjunto vivienda_faltantes se le puede aplicar modelos de imputación multivariada, por lo que se recomienda usar Joint Modelling o Fully Conditional Specification siendo el último el mejor modelo reportado para distintas condiciones de imputación.

 

Limpieza de Datos

Para el proceso de limpieza de datos se llevaron a cabo los siguientes pasos: - Normalizar valores de la columna tipo obteniendo dos categorías: apartamento y casa. - Eliminar valores faltantes donde la variable tipo es na. - Normalizar valores para columna barrio convirtiéndolos a formato UTF-8. - Normalizar valores más comunes para barrio manualmente. - Corregir valores de latitud y longitud para datos fuera del intervalo de trabajo. - Eliminar valores nulos de banios y habitac

# Normalizar valores de la columna "tipo".
vivienda_faltantes$tipo <- tolower(vivienda_faltantes$tipo)
vivienda_faltantes$tipo <- gsub("apto", "apartamento", vivienda_faltantes$tipo)

# Eliminar valores vacíos donde variable "tipo" es na. 
vivienda_faltantes_na_tipo <- vivienda_faltantes[is.na(vivienda_faltantes$tipo), ]

# Normalizar valores para barrio
vivienda_faltantes$barrio <- tolower(vivienda_faltantes$barrio)
vivienda_faltantes$barrio <- iconv(vivienda_faltantes$barrio, "UTF-8", "ASCII//TRANSLIT")
vivienda_faltantes$barrio[vivienda_faltantes$barrio == 'urbanizacion la flora'] <- 'la flora'
vivienda_faltantes$barrio[vivienda_faltantes$barrio == 'caney'] <- 'el caney'
vivienda_faltantes$barrio[vivienda_faltantes$barrio == 'cristales'] <- 'los cristales'
vivienda_faltantes$barrio[vivienda_faltantes$barrio == 'alf?crez real'] <- 'alferez real'
vivienda_faltantes$barrio[vivienda_faltantes$barrio == 'parcelaciones pance'] <- 'pance'
vivienda_faltantes$barrio[vivienda_faltantes$barrio == 'juanamb??'] <- 'juanambu'
vivienda_faltantes$barrio[vivienda_faltantes$barrio == 'santa monica residencial'] <- 'santa monica'
vivienda_faltantes$barrio[vivienda_faltantes$barrio == 'mel?cndez'] <- 'melendez'
vivienda_faltantes$barrio[vivienda_faltantes$barrio == 'el aguacatal'] <- 'aguacatal'
vivienda_faltantes$barrio[vivienda_faltantes$barrio == 'bajo aguacatal'] <- 'aguacatal'
vivienda_faltantes$barrio[vivienda_faltantes$barrio == 'miradol del aguacatal'] <- 'aguacatal'
vivienda_faltantes$barrio[vivienda_faltantes$barrio == 'sector aguacatal'] <- 'aguacatal'
vivienda_faltantes$barrio[vivienda_faltantes$barrio == 'arboleda campestre candelaria'] <- 'arboledas'
vivienda_faltantes$barrio[vivienda_faltantes$barrio == 'arboleda'] <- 'arboledas'

# Corregir valores para latitud y longitud
vivienda_faltantes$longitud <- ifelse(vivienda_faltantes$longitud < -100, vivienda_faltantes$longitud / 10000,
                               ifelse(vivienda_faltantes$longitud < -10,  vivienda_faltantes$longitud / 10, 
                               vivienda_faltantes$longitud))
vivienda_faltantes$latitud <- ifelse(vivienda_faltantes$latitud > 100, vivienda_faltantes$latitud / 1000,
                              vivienda_faltantes$latitud)

# Eliminar valores de 0 para las variables banios (45) y habitac (66).
vivienda_faltantes <- subset(vivienda_faltantes, banios != 0)
vivienda_faltantes <- subset(vivienda_faltantes, habitac != 0)

# Eliminar duplicados de id
vivienda_faltantes <- vivienda_faltantes[!duplicated(vivienda_faltantes$id), ]

 

Análisis Univariado

A continuación se muestran gráficos de barras para variables categóricas e histogramas para variables numéricas de interés. La variable piso no aporta información al análisis por lo que se ha decidido no tenerla en cuenta para el análisis y se han eliminado las filas para las cuales parquea presentaba valores vacíos.

Las características de los inmuebles desde las variables categóricas los ubican como apartamentos de estrato 5 de la zona Sur, con 3 habitaciones y 2 baños con entre uno y dos parqueaderos. Para las variables numéricas se encuentran que las distribuciones para las variables preciom y areaconst poseen asimetría positiva situando la mayor parte de registros entre 58 a 500 millones y áreas construidas entre 30 a 300 metros cuadrados.

plot_df <- subset(vivienda_faltantes, select = -c(id, piso))
plot_df$precio_metro <- plot_df$preciom/plot_df$areaconst
plot_df <- subset(plot_df, !is.na(parquea))
categorical_df <- subset(plot_df, select = c(zona, tipo, estrato, barrio, parquea, banios, habitac))
numerical_df <- subset(plot_df, select = c(preciom, areaconst, precio_metro))

for (i in names(categorical_df)) {
  cat('\n\n **Resumen de la variable** `', i, '`\n\n')
  x <- categorical_df[, i]
  cat('`', i, '` es una variable categórica.\n\n')
  
  if (i == 'barrio') {
    counts <- table(x)
    top_counts <- counts[order(-counts)][1:5]
    barplot(top_counts, xlab = 'barrio', ylab = 'Frequencia', main = paste('Barplot', i), lwd = 2, col=c("#003049","#d62828", "#f77f00", "#fcbf49", "#eae2b7"))
  } else {
    barplot(table(x), xlab = i, ylab = 'Frequencia', main = paste('Barplot', i), lwd = 2, col=c("#003049", "#6B2C39","#d62828", "#E75414", "#F77F00", "#FA9F25", "#FCBF49", "#F8C865", "#F3D180"))
  }
}

Resumen de la variable zona

zona es una variable categórica.

Resumen de la variable tipo

tipo es una variable categórica.

Resumen de la variable estrato

estrato es una variable categórica.

Resumen de la variable barrio

barrio es una variable categórica.

Resumen de la variable parquea

parquea es una variable categórica.

Resumen de la variable banios

banios es una variable categórica.

Resumen de la variable habitac

habitac es una variable categórica.

for (i in names(numerical_df)) {
  cat('\n\n **Resumen de la variable** `', i, '`\n\n')

  x <- as.numeric(numerical_df[[i]])
  
  if (any(is.na(x))) {
    cat('`', i, '` contiene valores faltantes.\n\n')
  } else {
    cat('`', i, '` es una variable numérica.\n\n')
    hist(x, freq = TRUE,  main = paste('Histograma', i), xlab = i, ylab="Frecuencia", col = 'lightblue')#, xlim = c(0, max(x)))
  
  }
}

Resumen de la variable preciom

preciom es una variable numérica.

Resumen de la variable areaconst

areaconst es una variable numérica.

Resumen de la variable precio_metro

precio_metro es una variable numérica.

 

Análisis Multivariado

Correlación entre variables

Se ha evaluado la correlación entre variables usando reticulate para acceder a los cálculos del paquete dython en Python. Esta herramienta permite incluir variables categóricas en la matriz de correlación con el objetivo de evaluar las relaciones entre todas las variables desde un mismo gráfico.

A partir de esta matriz se encuentra que:

  • Como es esperado, que los valores de preciom están fuertemente asociados con la zona y estrato.
  • El estrato parece estar relacionado a un nivel medio (0.5-0.6) con zona, el tipo de inmueble y la variable banios.
  • La variable tipo está asociada con el barrio, el número de baños y habitaciones.
  • La variable areaconst aparece relacionada con el barrio y la presencia de parqueadero en el inmueble.
  • La variable habitac está relacionada con el número de baños y el barrio al que hace parte.

 

 

Precio metro cuadrado vs Variables

Como se puede apreciar en la siguiente figura el precio por metro cuadrado para apartamentos está agrupado entre áreas de 50 a 250 \(m^{2}\) para los cuales el precio crece considerablemente a medida que el estrato aumenta. En el caso de los bienes de tipo casa, se tiene un menor crecimiento del precio \(m^{2}\) con respecto al área construida y se tiene un aumento gradual del precio al aumentar el estrato. En el caso de las zonas, vemos que el precio \(m^2\) de la zona Oeste es el más alto de todas las zonas incluyendo inmuebles de tipo casa cuando por el contrario, la zona Oriente representa el valor más bajo por lo que podría convertirse en una buena oportunidad de inversión para compradores interesados.

 

 

Discusión

A partir de las diferentes visualizaciones se ha podido describir las relaciones entre las variables presentes en el dataset vivienda_faltantes encontrando relaciones de utilidad para la resolución de las preguntas de negocio:

  • Precio zonal de viviendas: Al eliminar todos los valores fuera del rango intercuartílico se encuentra que los apartamentos y casas de la zona oeste son los de mayor valor, sin embargo debe rescatarse que el conjunto de datos se encuentra principalmente compuesto de bienes de la zona sur por lo que no se tienen mapeadas correctamente las características de otras zonas, en especial la zona centro y oriental.
vivienda_faltantes %>%
  group_by(tipo, zona) %>%
  mutate(Q1 = quantile(preciom, 0.25),
         Q3 = quantile(preciom, 0.75),
         IQR = Q3 - Q1,
         lower_bound = Q1 - 1.5 * IQR,
         upper_bound = Q3 + 1.5 * IQR) %>%
  filter(preciom >= lower_bound, preciom <= upper_bound) -> df_filtered


results <- df_filtered %>%
  group_by(tipo, zona) %>%
  summarise(mean = mean(preciom),
            median = median(preciom),
            std_dev = sd(preciom), .groups = 'drop')

paged_table(results, options = list(rows.print = 10)) %>%
  kable(caption = 'Valores de preciom agrupados por Tipo y Zona')
Valores de preciom agrupados por Tipo y Zona
tipo zona mean median std_dev
apartamento Zona Centro 169.4545 150.0 72.68505
apartamento Zona Norte 252.8307 235.0 134.60304
apartamento Zona Oeste 641.1260 560.0 351.79394
apartamento Zona Oriente 115.6667 113.0 35.06221
apartamento Zona Sur 248.5044 235.0 98.77510
casa Zona Centro 302.7111 299.5 98.07853
casa Zona Norte 407.3997 380.0 197.48681
casa Zona Oeste 707.8188 665.0 356.82464
casa Zona Oriente 238.7908 231.5 92.97722
casa Zona Sur 553.9660 460.0 292.99412
  • Tipo de vivienda más ofertada: Como se puede ver en la siguiente tabla y en el gráfico de barras para la variable categórica tipo, el tipo de vivienda más ofertado es apartamento con 4726 registros que se encuentran dentro del rango intercuartílico para valores de preciom.
tipo_counts <- df_filtered %>%
  group_by(tipo) %>%
  summarise(count = n(), .groups = 'drop')

paged_table(tipo_counts, options = list(rows.print = 10)) %>%
  kable(caption = 'Conteo de registros por tipo')
Conteo de registros por tipo
tipo count
apartamento 4726
casa 3028
  • Características más relevantes de la oferta de vivienda: Como se mencionó inicialmente para las características de los inmuebles desde las variables categóricas los ubican como apartamentos de estrato 5 de la zona Sur, con 3 habitaciones y 2 baños con entre uno y dos parqueaderos para precios por metro cuadrado de 3.5 millones de pesos.

 

Conclusiones

En conclusión, el análisis estadístico ha permitido encontrar información importante del mercado de bienes raices de la ciduad de Cali. Se ha identificado como variables clave a las variables originales tipo, zona, estrato, preciom y areaconst así como también a la variable derivada precio_metro que ha permitido encontrar las zonas con menores precios por metro cuadrado. En adición a estos, la limpieza de datos nos ha permitido tener una imagen más clara de los datos así como la remoción de valores atípicos para responder las preguntas de negocio de Bines & Casas. Finalmente, se podría profundizar en las variables de latitud y longitud con el fin de mejorar el análisis y elevarlo al nivel geoespacial lo que podría proveer aún más información para las partes interesadas.

 

Anexos

Valores faltantes: Influx y Outflux

De acuerdo con van Buuren, S. (2018) existen dos medidas que a nivel general miden cómo una variable se conecta con otra, estas medidas son: flujo de entrada (influx) y flujo de salida (outflux). El coeficiente de flujo de entrada depende de la proporción de valores faltantes de la variable. Este coeficiente para una variable totalmente completa es igual a 0 mientras que para una variable con un porcentaje de valores vacíos del 100% es igual a 1. Para dos variables con la misma proporción de datos vacíos, la variable con mayor flujo de entrada estará más conectada a los datos observados por lo que será más fácilmente imputable.

Por otro lado, el coeficiente de flujo de salida, es un indicador de la utilidad potencial de la variable. Este coeficiente depende de la proporción de datos faltantes de la variable, por lo que una variable sin valores vacíos tiene un coeficiente de flujo de salida de 1 y en caso de un 100% de valores vacíos tiene un valor de 0. Para dos variables con la misma proporción de valores vacíos, un mayor coeficiente de flujo de salida representa una mejor conexión a los valores faltantes y por tanto es más útil para imputar otras variables.

El gráfico de flujos o fluxplot de la librería mice puede usarse para determinar variables no útiles para imputación. Como regla general, las variables que están ubicadas en regiones bajas y especialmente cerca de la esquina abajo-izquierda deben eliminarse antes de la imputación.

fluxes <- flux(vivienda_faltantes)[,1:3]
g <- ggplot(data = fluxes, aes(y = outflux, x = influx))
g + geom_point(aes()) +
  coord_cartesian(xlim = c(0, 1), ylim = c(0,1), expand = FALSE) +
  #coord_fixed(ratio = 1) +
  geom_segment(aes(x = 0, y = 1, xend = 1, yend = 0), linetype="dashed") +
  annotate("text", x = 0.1779637, y = 0.4908371, label = "parquea") +
  annotate("text", x = 0.2973753, y = 0.2467909, label = "piso") + 
  labs(title = 'Influx-outflux pattern vivienda_faltantes') + 
  theme(plot.title = element_text(color = 'black', face = 'bold', size = 12, hjust = 0.5))