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.
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.
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.
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')| 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')| 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')| 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')| 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')| 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:
tipo y barrio.longitud y latitud que se encuentran por fuera
del rango esperado para la ciudad de Cali.parquea (1606) y
piso (2641).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.
# 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.
| 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')| 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')| 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 |
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.
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), ]
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.
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:
preciom están
fuertemente asociados con la zona y
estrato.estrato parece estar relacionado a un nivel medio
(0.5-0.6) con zona, el tipo de inmueble y la
variable banios.tipo está asociada con el barrio, el número
de baños y habitaciones.areaconst aparece relacionada con el barrio
y la presencia de parqueadero en el inmueble.habitac está relacionada con el número de
baños y el barrio al que hace parte.
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.
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:
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')| 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, 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')| tipo | count |
|---|---|
| apartamento | 4726 |
| casa | 3028 |
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.
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))