María, propietaria de la agencia de bienes raíces C&A (Casas y Apartamentos) en Cali, ha recibido una solicitud de asesoría para la compra de dos viviendas por parte de una compañía internacional. Esta solicitud llega en un momento desafiante para el sector inmobiliario, donde las ventas han experimentado una disminución significativa debido a tensiones políticas y sociales.
El presente análisis tiene como objetivo proporcionar recomendaciones fundamentadas mediante técnicas de modelación estadística para atender ambas solicitudes de vivienda, considerando las características específicas requeridas y los límites de crédito preaprobado de 350 y 850 millones de pesos respectivamente.
La base de datos contiene información de ofertas inmobiliarias de los últimos tres meses con las siguientes variables:
#install.packages("gt", repos = "https://cran.rstudio.com/")
library(gt)
# Crear el dataframe
variables_df <- data.frame(
Variable = c("zona", "piso", "estrato", "preciom", "areaconst",
"parqueaderos", "banios", "habitaciones", "tipo",
"barrio", "longitud", "latitud"),
Tipo = c("Categórica", "Categórica", "Categórica", "Numérica",
"Numérica", "Numérica", "Numérica", "Numérica",
"Categórica", "Categórica", "Numérica", "Numérica"),
Descripción = c("Ubicación de la vivienda (Zona Centro, Zona Norte, etc.)",
"Piso que ocupa la vivienda (primer piso, segundo piso, etc.)",
"Estrato socio-económico (3, 4, 5, 6)",
"Precio de la vivienda en millones de pesos",
"Área construida en metros cuadrados",
"Número de parqueaderos",
"Número de baños",
"Número de habitaciones",
"Tipo de vivienda (Casa, Apartamento)",
"Barrio de ubicación (20 de Julio, álamos, etc.)",
"Coordenada geográfica (longitud)",
"Coordenada geográfica (latitud)")
)
variables_df %>%
gt() %>%
tab_header(
title = "Descripción de Variables"
) %>%
tab_style(
style = list(
cell_borders(
sides = c("top", "bottom"),
color = "black",
weight = px(1)
)
),
locations = cells_body()
) %>%
tab_style(
style = list(
cell_fill(color = "#f0f0f0"),
cell_text(weight = "bold")
),
locations = cells_column_labels()
)| Descripción de Variables | ||
| Variable | Tipo | Descripción |
|---|---|---|
| zona | Categórica | Ubicación de la vivienda (Zona Centro, Zona Norte, etc.) |
| piso | Categórica | Piso que ocupa la vivienda (primer piso, segundo piso, etc.) |
| estrato | Categórica | Estrato socio-económico (3, 4, 5, 6) |
| preciom | Numérica | Precio de la vivienda en millones de pesos |
| areaconst | Numérica | Área construida en metros cuadrados |
| parqueaderos | Numérica | Número de parqueaderos |
| banios | Numérica | Número de baños |
| habitaciones | Numérica | Número de habitaciones |
| tipo | Categórica | Tipo de vivienda (Casa, Apartamento) |
| barrio | Categórica | Barrio de ubicación (20 de Julio, álamos, etc.) |
| longitud | Numérica | Coordenada geográfica (longitud) |
| latitud | Numérica | Coordenada geográfica (latitud) |
Nota importante sobre el estrato: El estrato es una clasificación socioeconómica categórica ordinal. Aunque se representa con números (3, 4, 5, 6), NO debe tratarse como variable numérica. No es válido calcular promedios de estrato (ej. 3.5) ya que esto carece de significado socioeconómico. Debe tratarse como factor ordenado en el análisis estadístico.
#install.packages("gt", repos = "https://cran.rstudio.com/")
library(gt)
# Crear el dataframe
variables_df1 <- data.frame(
Características = c("Tipo","área construida","parqueaderos","baños","habitaciones","estrato","zona","crédito preaprobado"),
vivienda_1 = c("Casa","200","1","2","4","4 o 5","Norte","350 millones"),
vivienda_2 = c("Apartamento","300","3","3","5","5 o 6","Sur","850 millones")
)
variables_df1 %>%
gt() %>%
tab_header(
title = "Descripción de Variables"
) %>%
tab_style(
style = list(
cell_borders(
sides = c("top", "bottom"),
color = "black",
weight = px(1)
)
),
locations = cells_body()
) %>%
tab_style(
style = list(
cell_fill(color = "#f0f0f0"),
cell_text(weight = "bold")
),
locations = cells_column_labels()
)| Descripción de Variables | ||
| Características | vivienda_1 | vivienda_2 |
|---|---|---|
| Tipo | Casa | Apartamento |
| área construida | 200 | 300 |
| parqueaderos | 1 | 3 |
| baños | 2 | 3 |
| habitaciones | 4 | 5 |
| estrato | 4 o 5 | 5 o 6 |
| zona | Norte | Sur |
| crédito preaprobado | 350 millones | 850 millones |
# Cargar librerías necesarias
library(dplyr)
library(ggplot2)
library(plotly)
library(leaflet)
library(DT)
library(corrplot)
library(car)
library(broom)
library(knitr)
library(kableExtra)
library(geosphere) # Para distm() y distHaversine
library(cluster) # Para clustering PAM
library(dplyr)
library(kableExtra)
library(corrplot)
library(plotly)
library(car)
library(ggplot2)
library(lmtest)
library(caret)
# Configurar tema para gráficos
theme_set(theme_minimal())## Rows: 8,322
## Columns: 13
## $ id <dbl> 1147, 1169, 1350, 5992, 1212, 1724, 2326, 4386, 1209, 159…
## $ zona <chr> "Zona Oriente", "Zona Oriente", "Zona Oriente", "Zona Sur…
## $ piso <chr> NA, NA, NA, "02", "01", "01", "01", "01", "02", "02", "02…
## $ estrato <dbl> 3, 3, 3, 4, 5, 5, 4, 5, 5, 5, 6, 4, 5, 6, 4, 5, 5, 4, 5, …
## $ preciom <dbl> 250, 320, 350, 400, 260, 240, 220, 310, 320, 780, 750, 62…
## $ areaconst <dbl> 70, 120, 220, 280, 90, 87, 52, 137, 150, 380, 445, 355, 2…
## $ parqueaderos <dbl> 1, 1, 2, 3, 1, 1, 2, 2, 2, 2, NA, 3, 2, 2, 1, 4, 2, 2, 2,…
## $ banios <dbl> 3, 2, 2, 5, 2, 3, 2, 3, 4, 3, 7, 5, 6, 2, 4, 4, 4, 3, 2, …
## $ habitaciones <dbl> 6, 3, 4, 3, 3, 3, 3, 4, 6, 3, 6, 5, 6, 2, 5, 5, 4, 3, 3, …
## $ tipo <chr> "Casa", "Casa", "Casa", "Casa", "Apartamento", "Apartamen…
## $ barrio <chr> "20 de julio", "20 de julio", "20 de julio", "3 de julio"…
## $ longitud <dbl> -76.51168, -76.51237, -76.51537, -76.54000, -76.51350, -7…
## $ latitud <dbl> 3.43382, 3.43369, 3.43566, 3.43500, 3.45891, 3.36971, 3.4…
Antes de proceder con el análisis estadístico, es fundamental evaluar la calidad de los datos disponibles. Este proceso se realizará de manera sistemática evaluando diferentes aspectos de la integridad y consistencia de la información.
Se realiza una evaluación inicial para conocer las dimensiones del dataset y verificar la estructura general de los datos.
El dataset contiene 8,322 registros y 13 variables correspondientes a ofertas inmobiliarias de los últimos tres meses en Cali.
La verificación de la unicidad del identificador es crucial para asegurar que cada registro represente una propiedad única en el análisis.
library(dplyr)
# Buscar IDs duplicados y cuántas veces se repiten
duplicados_id <- datos %>%
count(id) %>%
filter(n > 1)
# Mostrar las filas originales que tienen esos IDs duplicados
datos %>%
filter(id %in% duplicados_id$id)## id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo
## 1 NA <NA> <NA> NA NA NA NA NA NA <NA>
## 2 NA <NA> <NA> NA NA NA NA NA NA <NA>
## 3 NA <NA> <NA> NA 330 NA NA NA NA <NA>
## barrio longitud latitud
## 1 <NA> NA NA
## 2 <NA> NA NA
## 3 <NA> NA NA
Eliminamos registros con id= NA
## [1] 8319
Se eliminan 3 registros de 8322.
La identificación de valores faltantes permite evaluar la completitud de la información y determinar estrategias de tratamiento apropiadas.
# Análisis de valores faltantes
valores_faltantes <- sapply(datos, function(x) sum(is.na(x)))
porcentaje_faltantes <- round(valores_faltantes / nrow(datos) * 100, 2)
tabla_faltantes <- data.frame(
Variable = names(valores_faltantes),
Faltantes = valores_faltantes,
Porcentaje = porcentaje_faltantes
) %>%
filter(Faltantes > 0) %>%
arrange(desc(Faltantes))
tiene_faltantes <- nrow(tabla_faltantes) > 0
if(tiene_faltantes) {
kable(tabla_faltantes,
caption = "Variables con valores faltantes",
row.names = FALSE,
col.names = c("Variable", "Casos Faltantes", "Porcentaje (%)")) %>%
kable_styling(bootstrap_options = c("striped", "hover"))
} else {
htmltools::p(style="color: green; font-weight: bold;",
"✓ No se encontraron valores faltantes en el dataset")
}| Variable | Casos Faltantes | Porcentaje (%) |
|---|---|---|
| piso | 2635 | 31.67 |
| parqueaderos | 1602 | 19.26 |
Los resultados muestran que únicamente 2 variables presentan valores faltantes significativos:
piso: 31.67% faltantes - Variable
no crítica para valoración de precios
parqueaderos: 19.26% faltantes -
Variable importante para las solicitudes específicas
La variable parqueaderos requiere tratamiento especial
dado que es una característica específicamente solicitada por el cliente
(1 parqueadero para vivienda 1, y 3 parqueaderos para vivienda 2).
Se procede a implementar la imputación de la variable
parqueaderos utilizando la moda por tipo de vivienda y
estrato, manteniendo la variable piso con sus valores
faltantes originales.
# Crear copia para imputación
datos_imputados <- datos
# Imputar parqueaderos usando moda por tipo y zona
datos_imputados <- datos_imputados %>%
group_by(tipo, estrato) %>%
mutate(
parqueaderos_moda = as.numeric(names(sort(table(parqueaderos), decreasing = TRUE))[1])
) %>%
mutate(
parking_orig = parqueaderos,
parqueaderos = ifelse(is.na(parqueaderos), parqueaderos_moda, parqueaderos)
) %>%
select(-parqueaderos_moda) %>%
ungroup()
# Verificar resultados de imputación
casos_imputados <- sum(is.na(datos$parqueaderos)) - sum(is.na(datos_imputados$parqueaderos))# Resumen de imputación
resumen_imputacion <- data.frame(
Variable = c("parqueaderos", "piso"),
Casos_Originales_Faltantes = c(sum(is.na(datos$parqueaderos)), sum(is.na(datos$piso))),
Casos_Finales_Faltantes = c(sum(is.na(datos_imputados$parqueaderos)), sum(is.na(datos_imputados$piso))),
Casos_Imputados = c(casos_imputados, 0),
Tratamiento = c("Imputación por moda", "Mantener faltantes")
)
kable(resumen_imputacion,
caption = "Resumen del tratamiento de valores faltantes",
col.names = c("Variable", "Faltantes Inicial", "Faltantes Final", "Imputados", "Tratamiento")) %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Variable | Faltantes Inicial | Faltantes Final | Imputados | Tratamiento |
|---|---|---|---|---|
| parqueaderos | 1602 | 0 | 1602 | Imputación por moda |
| piso | 2635 | 2635 | 0 | Mantener faltantes |
Dado el alto porcentaje de valores faltantes en piso
(31.67%), es fundamental analizar los patrones de esta variable antes de
tomar decisiones de tratamiento. Los valores faltantes podrían
representar diferentes situaciones arquitectónicas.
# Análisis de patrones de piso por tipo de vivienda
patron_piso_tipo <- datos %>%
group_by(tipo) %>%
summarise(
total = n(),
piso_faltante = sum(is.na(piso)),
piso_disponible = sum(!is.na(piso)),
porcentaje_faltante = round(piso_faltante/total*100, 2),
.groups = 'drop'
)
# Distribución de pisos disponibles por tipo
if(sum(!is.na(datos$piso)) > 0) {
dist_piso_disponible <- datos %>%
filter(!is.na(piso)) %>%
group_by(tipo, piso) %>%
summarise(n = n(), .groups = 'drop') %>%
arrange(tipo, piso)
}kable(patron_piso_tipo,
caption = "Análisis de valores faltantes en 'piso' por tipo de vivienda",
col.names = c("Tipo", "Total", "Faltantes", "Disponibles", "% Faltantes")) %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Tipo | Total | Faltantes | Disponibles | % Faltantes |
|---|---|---|---|---|
| Apartamento | 5100 | 1381 | 3719 | 27.08 |
| Casa | 3219 | 1254 | 1965 | 38.96 |
if(exists("dist_piso_disponible")) {
kable(dist_piso_disponible,
caption = "Distribución de valores disponibles en 'piso' por tipo",
col.names = c("Tipo", "Piso", "Frecuencia")) %>%
kable_styling(bootstrap_options = c("striped", "hover"))
}| Tipo | Piso | Frecuencia |
|---|---|---|
| Apartamento | 01 | 430 |
| Apartamento | 02 | 512 |
| Apartamento | 03 | 573 |
| Apartamento | 04 | 545 |
| Apartamento | 05 | 564 |
| Apartamento | 06 | 243 |
| Apartamento | 07 | 200 |
| Apartamento | 08 | 211 |
| Apartamento | 09 | 146 |
| Apartamento | 10 | 128 |
| Apartamento | 11 | 84 |
| Apartamento | 12 | 83 |
| Casa | 01 | 430 |
| Casa | 02 | 938 |
| Casa | 03 | 524 |
| Casa | 04 | 62 |
| Casa | 05 | 3 |
| Casa | 06 | 2 |
| Casa | 07 | 4 |
| Casa | 10 | 2 |
Consideraciones Críticas para el Tratamiento de piso:
Decisión de Tratamiento para piso:
Se excluirá completamente la variable piso del análisis por dos razones fundamentales:
Esta decisión asegura que el análisis se centre en las variables realmente relevantes para las solicitudes específicas, manteniendo la integridad metodológica del estudio.
# Resumen del proceso
# Crear copia de trabajo y eliminar duplicados completos
datos_limpios <- datos[!duplicated(datos), ]
registros_eliminados_dup <- n_registros - nrow(datos_limpios)
n_registros_final <- nrow(datos_limpios)
resumen_proceso <- data.frame(
Proceso = c("Registros duplicados eliminados", "Valores parqueaderos imputados",
"Variable piso", "Variables finales", "Registros finales"),
Resultado = c(
paste(registros_eliminados_dup, "registros"),
paste(casos_imputados, "valores"),
"EXCLUIDA del análisis",
paste(ncol(datos_limpios), "variables"),
format(n_registros_final, big.mark = ",")
)
)
kable(resumen_proceso,
caption = "Resumen del proceso de limpieza e imputación") %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Proceso | Resultado |
|---|---|
| Registros duplicados eliminados | 3 registros |
| Valores parqueaderos imputados | 1602 valores |
| Variable piso | EXCLUIDA del análisis |
| Variables finales | 13 variables |
| Registros finales | 8,319 |
La correcta codificación de variables categóricas es fundamental para el análisis estadístico, especialmente para el tratamiento del estrato como variable categórica ordinal.
# Tratamiento de variables categóricas
# Convertir estrato a factor ordenado (categórica ordinal)
if("estrato" %in% names(datos_limpios)) {
datos_limpios$estrato <- factor(datos_limpios$estrato,
levels = c(3, 4, 5, 6),
ordered = TRUE,
labels = c("Estrato 3", "Estrato 4", "Estrato 5", "Estrato 6"))
estrato_convertido <- TRUE
} else {
estrato_convertido <- FALSE
}
# Convertir otras variables categóricas a factores (excluyendo piso)
categoricas <- c("zona", "tipo", "barrio") # Removido "piso"
vars_convertidas <- c()
for(var in categoricas) {
if(var %in% names(datos_limpios)) {
datos_limpios[[var]] <- as.factor(datos_limpios[[var]])
vars_convertidas <- c(vars_convertidas, var)
}
}
paste(vars_convertidas, collapse = ", ")## [1] "zona, tipo, barrio"
## [1] "convertido exitosamente"
Variables convertidas correctamente a factores: zona, tipo, barrio. El estrato ha sido convertido exitosamente como factor ordenado.
Se verifican las distribuciones de las variables categóricas principales para asegurar la correcta representación de los datos.
# Distribución por zonas
if("zona" %in% names(datos_limpios)) {
tabla_zonas <- as.data.frame(table(datos_limpios$zona))
names(tabla_zonas) <- c("Zona", "Frecuencia")
tabla_zonas$Porcentaje <- round(tabla_zonas$Frecuencia/sum(tabla_zonas$Frecuencia)*100, 1)
}
# Distribución por tipo de vivienda
if("tipo" %in% names(datos_limpios)) {
tabla_tipos <- as.data.frame(table(datos_limpios$tipo))
names(tabla_tipos) <- c("Tipo", "Frecuencia")
tabla_tipos$Porcentaje <- round(tabla_tipos$Frecuencia/sum(tabla_tipos$Frecuencia)*100, 1)
}
# Distribución por estrato (como categórica ordinal)
if("estrato" %in% names(datos_limpios)) {
tabla_estratos <- as.data.frame(table(datos_limpios$estrato, useNA = "no"))
names(tabla_estratos) <- c("Estrato", "Frecuencia")
tabla_estratos$Porcentaje <- round(tabla_estratos$Frecuencia/sum(tabla_estratos$Frecuencia)*100, 1)
}# Mostrar distribución por zonas
if(exists("tabla_zonas")) {
kable(tabla_zonas,
caption = "Distribución de propiedades por zona") %>%
kable_styling(bootstrap_options = c("striped", "hover"))
}| Zona | Frecuencia | Porcentaje |
|---|---|---|
| Zona Centro | 124 | 1.5 |
| Zona Norte | 1920 | 23.1 |
| Zona Oeste | 1198 | 14.4 |
| Zona Oriente | 351 | 4.2 |
| Zona Sur | 4726 | 56.8 |
# Mostrar distribución por tipo
if(exists("tabla_tipos")) {
kable(tabla_tipos,
caption = "Distribución de propiedades por tipo de vivienda") %>%
kable_styling(bootstrap_options = c("striped", "hover"))
}| Tipo | Frecuencia | Porcentaje |
|---|---|---|
| Apartamento | 5100 | 61.3 |
| Casa | 3219 | 38.7 |
# Mostrar distribución por estrato (como categórica ordinal)
if(exists("tabla_estratos")) {
kable(tabla_estratos,
caption = "Distribución de propiedades por estrato (Categórica Ordinal)") %>%
kable_styling(bootstrap_options = c("striped", "hover"))
}| Estrato | Frecuencia | Porcentaje |
|---|---|---|
| Estrato 3 | 1453 | 17.5 |
| Estrato 4 | 2129 | 25.6 |
| Estrato 5 | 2750 | 33.1 |
| Estrato 6 | 1987 | 23.9 |
# Actualizar datos principales con datos limpios
datos <- datos_limpios
# Crear resumen final
resumen_final <- data.frame(
Aspecto = c("Registros originales", "Registros duplicados eliminados", "Valores parqueaderos imputados",
"Registros finales", "Variables categóricas convertidas", "Estrato como ordinal"),
Valor = c(
format(n_registros, big.mark = ","),
as.character(registros_eliminados_dup),
as.character(casos_imputados),
format(n_registros_final, big.mark = ","),
paste(length(vars_convertidas), "variables"),
ifelse(estrato_convertido, "✓ Convertido", "✗ No encontrado")
)
)
kable(resumen_final,
caption = "Resumen final del proceso de limpieza") %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Aspecto | Valor |
|---|---|
| Registros originales | 8,322 |
| Registros duplicados eliminados | 3 |
| Valores parqueaderos imputados | 1602 |
| Registros finales | 8,319 |
| Variables categóricas convertidas | 3 variables |
| Estrato como ordinal | ✓ Convertido |
El proceso de limpieza ha sido completado exitosamente. Los datos están ahora preparados para el análisis estadístico con variables categóricas correctamente codificadas, sin registros duplicados, y con la variable crítica parqueaderos completamente imputada.
Como validación final del proceso de limpieza, se realiza un análisis exhaustivo de valores faltantes y patrones de ausencia de datos en el dataset procesado, incluyendo visualizaciones que permiten identificar patrones sistemáticos.
# Cargar librerías adicionales para análisis de faltantes
library(VIM)
library(tidyr)
library(stringr)
# Eliminar variable de respaldo ya no necesaria
#if("parqueaderos_original" %in% names(datos)) {
# datos <- datos %>% select(-parqueaderos_original)
#}
# Análisis cuantitativo de valores faltantes
missing_analysis <- datos %>%
summarise_all(~sum(is.na(.))) %>%
gather(key = "Variable", value = "Valores_Faltantes") %>%
mutate(Porcentaje = round((Valores_Faltantes / nrow(datos)) * 100, 2)) %>%
arrange(desc(Valores_Faltantes)) %>%
filter(Valores_Faltantes > 0) # Solo mostrar variables con faltantes
# Verificar espacios en blanco en variables de texto
vars_texto <- datos %>% select_if(is.character)
if(ncol(vars_texto) > 0) {
espacios_analisis <- vars_texto %>%
summarise_all(~sum(str_trim(.) == "" | str_trim(.) == " ", na.rm = TRUE)) %>%
gather(key = "Variable", value = "Espacios_Vacios") %>%
mutate(Porcentaje = round((Espacios_Vacios / nrow(datos)) * 100, 2)) %>%
filter(Espacios_Vacios > 0)
} else {
espacios_analisis <- data.frame(Variable = character(0),
Espacios_Vacios = numeric(0),
Porcentaje = numeric(0))
}# Mostrar análisis de valores faltantes
if(nrow(missing_analysis) > 0) {
kable(missing_analysis,
caption = "Analisis de valores faltantes por variable (Post-limpieza)",
col.names = c("Variable", "Valores Faltantes", "Porcentaje (%)")) %>%
kable_styling(bootstrap_options = c("striped", "hover"))
} else {
htmltools::p(style="color: green; font-weight: bold;",
"✓ No se encontraron valores faltantes en el dataset procesado")
}| Variable | Valores Faltantes | Porcentaje (%) |
|---|---|---|
| parking_orig | 1602 | 19.26 |
# Mostrar análisis de espacios en blanco
if(nrow(espacios_analisis) > 0) {
kable(espacios_analisis,
caption = "Analisis de espacios en blanco en variables de texto",
col.names = c("Variable", "Espacios Vacios", "Porcentaje (%)")) %>%
kable_styling(bootstrap_options = c("striped", "hover"))
} else {
htmltools::p(style="color: green; font-weight: bold;",
"✓ No se encontraron espacios en blanco problematicos")
}✓ No se encontraron espacios en blanco problematicos
La variable parking_orig es una variable auxiliar que almacena la informacion del numero de parqueaderos sin imputacion.
## [1] "id" "zona" "estrato" "preciom" "areaconst"
## [6] "parqueaderos" "banios" "habitaciones" "tipo" "barrio"
## [11] "longitud" "latitud" "parking_orig"
# Visualizacion de valores faltantes usando VIM
if(sum(sapply(datos, function(x) sum(is.na(x)))) > 0) {
VIM::aggr(datos,
col = c('navyblue','red'),
numbers = TRUE,
sortVars = TRUE,
labels = names(datos),
cex.axis = 0.7,
gap = 3,
main = "Patron de Valores Faltantes - Dataset Procesado",
ylab = c("Proporcion de Faltantes", "Patrones de Combinacion"))
} else {
# Crear un grafico alternativo cuando no hay faltantes
plot(1, type="n", xlim=c(0,1), ylim=c(0,1),
xlab="", ylab="", main="Validacion Completada", axes=FALSE)
text(0.5, 0.5, "✓ Dataset sin valores faltantes\nTodas las variables estan completas",
cex=1.5, col="darkgreen", font=2)
}##
## Variables sorted by number of missings:
## Variable Count
## parking_orig 0.1925712
## id 0.0000000
## zona 0.0000000
## estrato 0.0000000
## preciom 0.0000000
## areaconst 0.0000000
## parqueaderos 0.0000000
## banios 0.0000000
## habitaciones 0.0000000
## tipo 0.0000000
## barrio 0.0000000
## longitud 0.0000000
## latitud 0.0000000
Debemos identificar casas ubicadas en la zona norte de Cali. El filtro se aplica considerando tanto el tipo de vivienda (Casa) como la ubicación geográfica (Zona Norte), lo que permitirá crear un subconjunto de datos relevante para el análisis específico de esta solicitud.
# Filtrar casas de la zona norte
base1 <- datos %>%
filter(tipo == "Casa" & zona == "Zona Norte")
# Información básica del filtro
n_total <- nrow(datos)
n_casas_norte <- nrow(base1)
porcentaje_filtro <- round(n_casas_norte/n_total*100, 2)
n_casas_norte## [1] 722
## [1] 8.68
## [1] "8,319"
El filtro resultó en 722 casas ubicadas en la zona norte, representando el 8.68 % del total de 8,319 registros en la base de datos.
# Mostrar los primeros 3 registros
kable(head(base1, 3),
caption = "Primeros 3 registros de casas en zona norte",
digits = 2) %>%
kable_styling(bootstrap_options = c("striped", "hover"),
full_width = FALSE) %>%
scroll_box(width = "100%")| id | zona | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | barrio | longitud | latitud | parking_orig |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1209 | Zona Norte | Estrato 5 | 320 | 150 | 2 | 4 | 6 | Casa | acopi | -76.51 | 3.48 | 2 |
| 1592 | Zona Norte | Estrato 5 | 780 | 380 | 2 | 3 | 3 | Casa | acopi | -76.52 | 3.49 | 2 |
| 4057 | Zona Norte | Estrato 6 | 750 | 445 | 2 | 7 | 6 | Casa | acopi | -76.53 | 3.39 | NA |
La verificación del filtro aplicado es fundamental para asegurar la correcta segmentación de los datos. Se realiza un análisis cruzado entre tipo de vivienda y zona geográfica para confirmar la distribución de las propiedades y validar que el filtro captura exactamente las casas de la zona norte requeridas.
# Tabla de verificación por tipo y zona
verificacion <- datos %>%
count(tipo, zona) %>%
pivot_wider(names_from = zona, values_from = n, values_fill = 0)
kable(verificacion,
caption = "Distribución de propiedades por tipo y zona") %>%
kable_styling(bootstrap_options = c("striped", "hover"))| tipo | Zona Centro | Zona Norte | Zona Oeste | Zona Oriente | Zona Sur |
|---|---|---|---|---|---|
| Apartamento | 24 | 1198 | 1029 | 62 | 2787 |
| Casa | 100 | 722 | 169 | 289 | 1939 |
# Tabla de verificación de estrato (tratado como categórica ordinal)
verificacion_estrato <- base1 %>%
count(estrato, .drop = FALSE) %>%
mutate(porcentaje = round(n/sum(n)*100, 1))
kable(verificacion_estrato,
caption = "Distribución de casas en zona norte por estrato",
col.names = c("Estrato", "Frecuencia", "Porcentaje (%)")) %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Estrato | Frecuencia | Porcentaje (%) |
|---|---|---|
| Estrato 3 | 235 | 32.5 |
| Estrato 4 | 161 | 22.3 |
| Estrato 5 | 271 | 37.5 |
| Estrato 6 | 55 | 7.6 |
# Estadísticas descriptivas de variables clave para casas zona norte
estadisticas_base1 <- base1 %>%
select(preciom, areaconst, parqueaderos, banios, habitaciones) %>%
summarise(
across(everything(), list(
Min = ~min(., na.rm = TRUE),
Q1 = ~quantile(., 0.25, na.rm = TRUE),
Mediana = ~median(., na.rm = TRUE),
Media = ~mean(., na.rm = TRUE),
Q3 = ~quantile(., 0.75, na.rm = TRUE),
Max = ~max(., na.rm = TRUE)
))
) %>%
pivot_longer(everything(), names_to = "Estadistica", values_to = "Valor") %>%
separate(Estadistica, into = c("Variable", "Medida"), sep = "_") %>%
pivot_wider(names_from = Medida, values_from = Valor)kable(estadisticas_base1,
caption = "Estadísticas descriptivas - Casas Zona Norte",
digits = 2) %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Variable | Min | Q1 | Mediana | Media | Q3 | Max |
|---|---|---|---|---|---|---|
| preciom | 89 | 261.25 | 390 | 445.91 | 550.00 | 1940 |
| areaconst | 30 | 140.00 | 240 | 264.85 | 336.75 | 1440 |
| parqueaderos | 1 | 1.00 | 1 | 1.82 | 2.00 | 10 |
| banios | 0 | 2.00 | 3 | 3.56 | 4.00 | 10 |
| habitaciones | 0 | 3.00 | 4 | 4.51 | 5.00 | 10 |
La visualización geográfica de las casas en zona norte permite verificar la consistencia espacial de los datos y identificar posibles patrones de concentración o dispersión de las propiedades. El mapa interactivo facilita la exploración individual de cada propiedad con sus características principales.
# Verificar si hay datos de coordenadas disponibles
coordenadas_disponibles <- sum(!is.na(base1$longitud) & !is.na(base1$latitud))
# Filtrar registros con coordenadas válidas
base1_coord <- base1 %>%
filter(!is.na(longitud) & !is.na(latitud))
# Crear mapa interactivo de las casas en zona norte
mapa_casas <- leaflet(base1_coord) %>%
addTiles() %>%
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
radius = ~sqrt(areaconst)/5, # Radio proporcional al área
popup = ~paste(
"<b>Casa en Zona Norte</b><br>",
"Precio: $", format(preciom, big.mark = ","), " millones<br>",
"Área: ", areaconst, " m²<br>",
"Habitaciones: ", habitaciones, "<br>",
"Baños: ", banios, "<br>",
"Estrato: ", estrato, "<br>",
"Barrio: ", barrio
),
color = ~colorFactor("viridis", estrato)(estrato),
fillOpacity = 0.7,
stroke = TRUE,
weight = 1
) %>%
addLegend("bottomright",
pal = colorFactor("viridis", base1_coord$estrato),
values = ~estrato,
title = "Estrato",
opacity = 1)
mapa_casasEl análisis preliminar del mapa reveló inconsistencias geográficas significativas que requieren corrección metodológica, implementada en la siguiente sección.
# Verificar disponibilidad de coordenadas para análisis posterior
base1_coord <- base1 %>%
filter(!is.na(longitud) & !is.na(latitud))
n_coord_disponibles <- nrow(base1_coord)
porcentaje_coord <- round(n_coord_disponibles/nrow(base1)*100, 1)
# Análisis de distribución de barrios más frecuentes
distribucion_barrios <- base1 %>%
count(barrio, sort = TRUE) %>%
mutate(porcentaje = round(n/sum(n)*100, 1)) %>%
head(10)
kable(distribucion_barrios,
caption = "Top 10 barrios con más casas en 'Zona Norte' (antes de corrección)",
col.names = c("Barrio", "Frecuencia", "Porcentaje (%)")) %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Barrio | Frecuencia | Porcentaje (%) |
|---|---|---|
| la flora | 99 | 13.7 |
| acopi | 70 | 9.7 |
| villa del prado | 40 | 5.5 |
| el bosque | 37 | 5.1 |
| prados del norte | 31 | 4.3 |
| san vicente | 31 | 4.3 |
| vipasa | 30 | 4.2 |
| la merced | 24 | 3.3 |
| urbanización la flora | 23 | 3.2 |
| brisas de los | 22 | 3.0 |
## [1] 722
## [1] 722
## [1] 100
De las 722 casas en zona norte, 722 registros 100% cuentan con coordenadas geográficas válidas para análisis espacial. La distribución de barrios muestra mezcla de ubicaciones que requieren validación geográfica mediante clustering espacial.
Dadas las inconsistencias identificadas en la clasificación original de zonas, se implementa una corrección metodológica usando clustering espacial basado en coordenadas geográficas reales.
Selección del Método de Distancia: Problema Identificado y Solución
El análisis de las coordenadas geográficas reveló que las propiedades clasificadas como “Zona Norte” se distribuyen por toda la extensión urbana de Cali, abarcando desde latitud 3.333 hasta 3.496. Esta dispersión geográfica requiere un método de clustering que calcule distancias reales entre ubicaciones.
Problema metodológico identificado: El uso de distancia euclidiana tradicional para coordenadas geográficas genera errores significativos porque: - Trata las coordenadas como puntos en un plano cartesiano bidimensional - Ignora la curvatura de la superficie terrestre - Produce distancias incorrectas que no corresponden a la realidad geográfica
Solución implementada: Se utiliza la
distancia Haversine mediante la librería
geosphere porque: - Calcula la distancia más corta entre
dos puntos sobre la superficie de una esfera - Considera la curvatura
terrestre para obtener distancias reales en metros - Es el estándar
metodológico para análisis geoespaciales urbanos - Permite agrupamiento
preciso basado en proximidad geográfica real
# Cargar librerías necesarias para clustering geográfico
library(geosphere)
library(cluster)
# Verificar registros con coordenadas válidas
base1_coord <- base1 %>%
filter(!is.na(longitud) & !is.na(latitud))
# Crear matriz de distancia Haversine (considera curvatura terrestre)
coordenadas <- base1_coord %>%
select(longitud, latitud)
matriz_distancia <- distm(coordenadas, fun = distHaversine)
# Clustering PAM con 5 grupos basado en proximidad geográfica
set.seed(123)
numero_clusters <- 5
pam_resultado <- pam(matriz_distancia, k = numero_clusters, diss = TRUE)
# Añadir cluster geográfico al dataset
base1_coord$cluster_geografico <- pam_resultado$clustering
# Crear paleta de colores para clusters
pal_cluster <- colorFactor("viridis", domain = base1_coord$cluster_geografico)Mapa Original vs Clustering Corregido
# Mapa con clustering geográfico corregido
mapa_corregido <- leaflet(base1_coord) %>%
addTiles() %>%
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
color = ~pal_cluster(cluster_geografico),
radius = 6,
stroke = FALSE,
fillOpacity = 0.8,
popup = ~paste(
"<b>Cluster Geográfico:</b>", cluster_geografico, "<br>",
"<b>Zona Original:</b>", zona, "<br>",
"<b>Precio:</b>", format(preciom, big.mark = ","), "M<br>",
"<b>Área:</b>", areaconst, "m²<br>",
"<b>Barrio:</b>", barrio
)
) %>%
addLegend(
"bottomright",
pal = pal_cluster,
values = ~cluster_geografico,
title = "Clusters Geográficos",
opacity = 1
) %>%
setView(lng = -76.5320, lat = 3.4516, zoom = 11)
mapa_corregidoAnálisis de Clusters Geográficos
# Analizar características de cada cluster
cluster_analisis <- base1_coord %>%
group_by(cluster_geografico) %>%
summarise(
n_propiedades = n(),
precio_mediano = median(preciom, na.rm = TRUE),
area_mediana = median(areaconst, na.rm = TRUE),
lat_centro = round(median(latitud), 4),
lon_centro = round(median(longitud), 4),
barrios_principales = paste(head(names(sort(table(barrio), decreasing = TRUE)), 3), collapse = ", "),
.groups = 'drop'
) %>%
arrange(desc(lat_centro)) # Ordenar por latitud (norte a sur)
kable(cluster_analisis %>% select(-lat_centro, -lon_centro),
caption = "Características de clusters geográficos (ordenados Norte a Sur)",
col.names = c("Cluster", "N° Propiedades", "Precio Mediano (M)",
"Área Mediana (m²)", "Barrios Principales")) %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Cluster | N° Propiedades | Precio Mediano (M) | Área Mediana (m²) | Barrios Principales |
|---|---|---|---|---|
| 1 | 251 | 395.0 | 250.0 | la flora, vipasa, el bosque |
| 4 | 186 | 215.0 | 129.0 | villa del prado, salomia, villa del sol |
| 5 | 98 | 592.5 | 318.5 | san vicente, santa monica, santa mónica residencial |
| 3 | 96 | 465.0 | 300.0 | acopi, juanamb√∫, la flora |
| 2 | 91 | 450.0 | 240.0 | acopi, Cali, la flora |
# Identificar cluster más al norte (mayor latitud)
cluster_norte_real <- cluster_analisis %>%
slice_max(lat_centro, n = 1) %>%
pull(cluster_geografico)
# Crear base1 corregida solo con cluster norte real
base1_norte_real <- base1_coord %>%
filter(cluster_geografico == cluster_norte_real)
# Estadísticas del cluster norte real
resumen_norte_real <- data.frame(
Aspecto = c("Cluster identificado como Norte", "Propiedades en norte real",
"Propiedades originales", "Efectividad del filtro geográfico"),
Valor = c(
as.character(cluster_norte_real),
nrow(base1_norte_real),
nrow(base1_coord),
paste0(round(nrow(base1_norte_real)/nrow(base1_coord)*100, 1), "%")
)
)
kable(resumen_norte_real,
caption = "Corrección geográfica: Cluster norte real vs dataset original") %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Aspecto | Valor |
|---|---|
| Cluster identificado como Norte | 1 |
| Propiedades en norte real | 251 |
| Propiedades originales | 722 |
| Efectividad del filtro geográfico | 34.8% |
## [1] 1
## [1] 251
Resultados de la Corrección Geográfica:
La implementación de clustering espacial usando distancia Haversine identifica el Cluster 1 como la zona geográficamente más al norte de Cali, conteniendo 251 propiedades realmente ubicadas en el norte de la ciudad.
Hallazgos Principales del Clustering:
El análisis reveló inconsistencias significativas en la clasificación original de zonas. De las 722 casas inicialmente marcadas como “Zona Norte”, únicamente 251 propiedades (34.8%) se encuentran efectivamente en la zona norte geográfica de la ciudad. Esto significa que aproximadamente dos de cada tres propiedades estaban incorrectamente clasificadas.
Validación Geográfica por Coordenadas:
Cluster 1 (Norte real): Latitud promedio 3.4840, con barrios como “la flora, vipasa, el bosque”
Clusters 2-5: Latitudes menores (3.3893-3.4680) confirman ubicación más al centro y sur
Cluster 4: Incluye “villa del prado”, confirmando clasificación errónea como zona norte
Características del Norte Real (Cluster 1):
251 propiedades geográficamente consistentes
Precio mediano: 395 millones de pesos
Área mediana: 250 m²
Barrios principales: La Flora, Vipasa, El Bosque - coherentes con la ubicación norte
Impacto Metodológico:
Esta corrección geográfica es fundamental para la validez del
análisis, ya que elimina el 65.2% de propiedades mal clasificadas que
habrían introducido sesgos significativos en las recomendaciones. El
dataset corregido (base1_norte_real) con 251 propiedades
garantiza que las sugerencias al cliente internacional correspondan
efectivamente a inmuebles ubicados en la zona norte solicitada.
La reducción drástica de registros, aunque significativa, mejora sustancialmente la calidad y confiabilidad de los datos para análisis posteriores, eliminando el ruido geográfico que comprometería las recomendaciones inmobiliarias.
Se realiza un análisis exploratorio enfocado en la correlación entre la variable respuesta (precio de la casa) y las variables explicativas clave: área construida, estrato, número de baños, número de habitaciones y zona. Se utilizan gráficos interactivos con plotly para facilitar la exploración y comprensión de las relaciones. Preparación de Datos para EDA
# Usar el dataset completo para análisis exploratorio general
datos_eda <- datos
# Crear subconjunto con variables de interés para correlaciones
variables_interes <- datos_eda %>%
select(preciom, areaconst, estrato, banios, habitaciones, zona, tipo, barrio) %>%
# Convertir estrato a numérico solo para análisis de correlación
mutate(estrato_num = as.numeric(gsub("Estrato ", "", estrato)))
# Estadísticas descriptivas de variables numéricas
summary_numericas <- variables_interes %>%
select(preciom, areaconst, banios, habitaciones, estrato_num) %>%
summary()
summary_numericas## preciom areaconst banios habitaciones
## Min. : 58.0 Min. : 30.0 Min. : 0.000 Min. : 0.000
## 1st Qu.: 220.0 1st Qu.: 80.0 1st Qu.: 2.000 1st Qu.: 3.000
## Median : 330.0 Median : 123.0 Median : 3.000 Median : 3.000
## Mean : 433.9 Mean : 174.9 Mean : 3.111 Mean : 3.605
## 3rd Qu.: 540.0 3rd Qu.: 229.0 3rd Qu.: 4.000 3rd Qu.: 4.000
## Max. :1999.0 Max. :1745.0 Max. :10.000 Max. :10.000
## estrato_num
## Min. :3.000
## 1st Qu.:4.000
## Median :5.000
## Mean :4.634
## 3rd Qu.:5.000
## Max. :6.000
Matriz de Correlación
# Calcular matriz de correlación para variables numéricas
matriz_corr <- variables_interes %>%
select(preciom, areaconst, banios, habitaciones, estrato_num) %>%
cor(use = "complete.obs")
# Visualización de matriz de correlación
library(corrplot)
corrplot(matriz_corr,
method = "color",
type = "upper",
order = "hclust",
tl.cex = 0.8,
tl.col = "black",
tl.srt = 45,
addCoef.col = "black",
number.cex = 0.7,
title = "Matriz de Correlación - Variables Numéricas")# Tabla de correlaciones con precio
corr_con_precio <- data.frame(
Variable = rownames(matriz_corr),
Correlacion_con_Precio = matriz_corr[, "preciom"]
) %>%
arrange(desc(abs(Correlacion_con_Precio))) %>%
filter(Variable != "preciom")
kable(corr_con_precio,
caption = "Correlaciones con el Precio (ordenadas por magnitud)",
col.names = c("Variable", "Correlación con Precio"),
digits = 3) %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Variable | Correlación con Precio | |
|---|---|---|
| areaconst | areaconst | 0.687 |
| banios | banios | 0.669 |
| estrato_num | estrato_num | 0.610 |
| habitaciones | habitaciones | 0.264 |
La matriz revela patrones claros en las relaciones entre variables numéricas del mercado inmobiliario. Las correlaciones más fuertes con el precio se observan con área construida (0.69) y número de baños (0.67), confirmando que estas son las variables más predictivas del valor inmobiliario.
El estrato socioeconómico (0.61) muestra una correlación moderada-alta con el precio, validando su importancia en la valoración. Interesantemente, el número de habitaciones presenta la correlación más débil (0.26), sugiriendo que la distribución eficiente del espacio puede compensar una menor cantidad de habitaciones.
Análisis de multicolinealidad: Se observa correlación moderada entre baños y habitaciones (0.59) y entre área construida y habitaciones (0.52), lo que es esperado dado que propiedades más grandes típicamente tienen más espacios. La correlación área-baños (0.65) es la más alta entre variables explicativas, indicando que ambas crecen conjuntamente pero mantienen información diferenciada.
El estrato muestra correlaciones moderadas con todas las variables físicas (0.27-0.42), confirmando que las propiedades en estratos superiores tienden a ser más grandes y tener más amenidades, pero sin crear problemas severos de multicolinealidad que comprometan el modelo.
Esta matriz valida la selección de variables para el modelo de regresión y sugiere que no hay multicolinealidad problemática (todas las correlaciones entre predictores < 0.7).
Gráficos Interactivos con Plotly
Relación Precio vs Área Construida
El análisis de la relación fundamental entre precio y área construida permite evaluar la consistencia del metro cuadrado como unidad de valoración y identificar segmentos de mercado con comportamientos diferenciales según el estrato socioeconómico.
# Gráfico interactivo precio vs área construida
p1 <- variables_interes %>%
plot_ly(x = ~areaconst, y = ~preciom, color = ~estrato,
hovertemplate = paste(
"<b>Área:</b> %{x} m²<br>",
"<b>Precio:</b> $%{y} millones<br>",
"<b>Estrato:</b> %{color}<br>",
"<extra></extra>"
)) %>%
add_markers(alpha = 0.6) %>%
layout(
title = "Relación entre Precio y Área Construida por Estrato",
xaxis = list(title = "Área Construida (m²)"),
yaxis = list(title = "Precio (millones)"),
hovermode = "closest"
)
p1La visualización confirma una relación positiva y lineal entre área construida y precio, con evidencia de segmentación clara por estratos. Los estratos 5 y 6 (puntos amarillos y verdes en la parte superior) muestran mayor dispersión de precios para áreas similares, indicando que factores adicionales como acabados, ubicación específica y amenidades influyen significativamente en la valoración de propiedades premium.
Se observa que la densidad de puntos se concentra en el rango de 50-400 m², donde la relación precio-área es más consistente. Los outliers evidentes en propiedades con áreas superiores a 1000 m² presentan variabilidad considerable en precios, sugiriendo que en el segmento de viviendas grandes, las características cualitativas cobran mayor importancia relativa que el área pura.
La diferenciación por estratos es clara: el Estrato 3 (puntos morados) se concentra en la zona inferior izquierda con precios y áreas menores, mientras que los estratos superiores se extienden hacia valores más altos, confirmando la segmentación socioeconómica del mercado inmobiliario caleño.
Relación Precio vs Número de Baños
El análisis revela un patrón incremental claro donde mayor número de baños correlaciona consistentemente con precios más altos. La concentración de propiedades en el rango de 2-4 baños sugiere que este es el segmento principal del mercado caleño.
# Gráfico interactivo precio vs baños
p2 <- variables_interes %>%
plot_ly(x = ~as.factor(banios), y = ~preciom, color = ~zona,
type = "box",
hovertemplate = paste(
"<b>Baños:</b> %{x}<br>",
"<b>Precio:</b> $%{y} millones<br>",
"<b>Zona:</b> %{color}<br>",
"<extra></extra>"
)) %>%
layout(
title = "Distribución de Precios por Número de Baños y Zona",
xaxis = list(title = "Número de Baños"),
yaxis = list(title = "Precio (millones)"),
boxmode = "group"
)
p2Las diferencias zonales son evidentes, con la Zona Norte mostrando precios medianos más contenidos comparada con otras zonas para el mismo número de baños, lo que confirma la corrección geográfica implementada y valida las diferencias de valoración entre sectores de la ciudad.
Relación Precio vs Número de Habitaciones
El análisis de habitaciones versus precio permite evaluar cómo la capacidad residencial impacta la valoración, considerando tanto aspectos funcionales como de inversión familiar.
# Gráfico interactivo precio vs habitaciones
p3 <- variables_interes %>%
filter(habitaciones <= 8) %>% # Filtrar valores extremos para mejor visualización
plot_ly(x = ~as.factor(habitaciones), y = ~preciom, color = ~estrato,
type = "violin",
box = list(visible = TRUE),
hovertemplate = paste(
"<b>Habitaciones:</b> %{x}<br>",
"<b>Precio:</b> $%{y} millones<br>",
"<b>Estrato:</b> %{color}<br>",
"<extra></extra>"
)) %>%
layout(
title = "Distribución de Precios por Número de Habitaciones y Estrato",
xaxis = list(title = "Número de Habitaciones"),
yaxis = list(title = "Precio (millones)"),
violinmode = "group"
)
p3## Warning: 'layout' objects don't have these attributes: 'violinmode'
## Valid attributes include:
## '_deprecated', 'activeshape', 'annotations', 'autosize', 'autotypenumbers', 'calendar', 'clickmode', 'coloraxis', 'colorscale', 'colorway', 'computed', 'datarevision', 'dragmode', 'editrevision', 'editType', 'font', 'geo', 'grid', 'height', 'hidesources', 'hoverdistance', 'hoverlabel', 'hovermode', 'images', 'legend', 'mapbox', 'margin', 'meta', 'metasrc', 'modebar', 'newshape', 'paper_bgcolor', 'plot_bgcolor', 'polar', 'scene', 'selectdirection', 'selectionrevision', 'separators', 'shapes', 'showlegend', 'sliders', 'smith', 'spikedistance', 'template', 'ternary', 'title', 'transition', 'uirevision', 'uniformtext', 'updatemenus', 'width', 'xaxis', 'yaxis', 'boxmode', 'barmode', 'bargap', 'mapType'
La distribución muestra concentración en viviendas de 3-4 habitaciones, que constituye el estándar del mercado familiar. La considerable variabilidad de precios para el mismo número de habitaciones indica que otros factores como área total, calidad de acabados y ubicación tienen peso significativo.
El Estrato 6 presenta mayor dispersión independientemente del número de habitaciones, confirmando que en segmentos premium la diferenciación va más allá de la cantidad de espacios hacia la calidad y exclusividad de los mismos.
Análisis por Zona Geográfica
La segmentación geográfica permite identificar diferencias estructurales en el mercado inmobiliario entre las distintas zonas de Cali, facilitando estrategias de búsqueda zonificadas para los clientes.
# Gráfico interactivo por zona
p4 <- variables_interes %>%
plot_ly(x = ~zona, y = ~preciom, color = ~estrato,
type = "box",
hovertemplate = paste(
"<b>Zona:</b> %{x}<br>",
"<b>Precio:</b> $%{y} millones<br>",
"<b>Estrato:</b> %{color}<br>",
"<extra></extra>"
)) %>%
layout(
title = "Distribución de Precios por Zona y Estrato",
xaxis = list(title = "Zona", tickangle = -45),
yaxis = list(title = "Precio (millones)"),
boxmode = "group"
)
p4El análisis zonal revela una jerarquía clara de valoración: Zona Oeste como premium ($677.58M promedio), seguida por Zona Sur ($426.52M) y Zona Norte ($345.61M) con precios más accesibles. Esta diferenciación confirma la correlación precio-área de 0.729 en Zona Norte, indicando una relación fuerte y predecible que beneficia la modelación.
Análisis Multivariado - Precio vs Área con Múltiples Dimensiones
La visualización tridimensional integra múltiples variables simultáneamente, permitiendo identificar patrones complejos y segmentaciones de mercado que no son evidentes en análisis bivariados.
# Gráfico scatter 3D interactivo
p5 <- variables_interes %>%
sample_n(min(1000, nrow(variables_interes))) %>% # Muestra para mejor performance
plot_ly(x = ~areaconst, y = ~banios, z = ~preciom,
color = ~estrato, size = ~habitaciones,
hovertemplate = paste(
"<b>Área:</b> %{x} m²<br>",
"<b>Baños:</b> %{y}<br>",
"<b>Precio:</b> $%{z} millones<br>",
"<b>Estrato:</b> %{color}<br>",
"<b>Habitaciones:</b> %{marker.size}<br>",
"<extra></extra>"
)) %>%
add_markers() %>%
layout(
title = "Análisis Multivariado: Precio vs Área, Baños, Habitaciones y Estrato",
scene = list(
xaxis = list(title = "Área Construida (m²)"),
yaxis = list(title = "Número de Baños"),
zaxis = list(title = "Precio (millones)")
)
)
p5## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
Interpretación de Resultados
La visualización 3D confirma la interacción compleja entre las variables explicativas. Se observa que propiedades con área y número de baños similares pueden tener precios significativamente diferentes según estrato, validando la necesidad de incluir variables categóricas en el modelo de regresión.
# Análisis estadístico adicional por zona
resumen_por_zona <- variables_interes %>%
group_by(zona) %>%
summarise(
n_propiedades = n(),
precio_promedio = round(mean(preciom, na.rm = TRUE), 2),
precio_mediano = round(median(preciom, na.rm = TRUE), 2),
area_promedio = round(mean(areaconst, na.rm = TRUE), 2),
correlacion_precio_area = round(cor(preciom, areaconst, use = "complete.obs"), 3),
.groups = 'drop'
) %>%
arrange(desc(precio_mediano))
kable(resumen_por_zona,
caption = "Análisis comparativo por zona geográfica",
col.names = c("Zona", "N° Propiedades", "Precio Promedio", "Precio Mediano",
"Área Promedio", "Correlación Precio-Área")) %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Zona | N° Propiedades | Precio Promedio | Precio Mediano | Área Promedio | Correlación Precio-Área |
|---|---|---|---|---|---|
| Zona Oeste | 1198 | 677.58 | 580 | 196.40 | 0.665 |
| Zona Sur | 4726 | 426.52 | 320 | 173.31 | 0.762 |
| Zona Norte | 1920 | 345.61 | 300 | 161.12 | 0.729 |
| Zona Centro | 124 | 309.69 | 297 | 194.04 | 0.609 |
| Zona Oriente | 351 | 228.53 | 210 | 192.33 | 0.428 |
# Análisis por estrato
resumen_por_estrato <- variables_interes %>%
group_by(estrato) %>%
summarise(
n_propiedades = n(),
precio_promedio = round(mean(preciom, na.rm = TRUE), 2),
precio_mediano = round(median(preciom, na.rm = TRUE), 2),
area_promedio = round(mean(areaconst, na.rm = TRUE), 2),
.groups = 'drop'
) %>%
arrange(desc(precio_mediano))
kable(resumen_por_estrato,
caption = "Análisis comparativo por estrato socioeconómico",
col.names = c("Estrato", "N° Propiedades", "Precio Promedio", "Precio Mediano", "Área Promedio")) %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Estrato | N° Propiedades | Precio Promedio | Precio Mediano | Área Promedio |
|---|---|---|---|---|
| Estrato 6 | 1987 | 800.29 | 700 | 248.41 |
| Estrato 5 | 2750 | 410.25 | 350 | 173.28 |
| Estrato 4 | 2129 | 275.10 | 235 | 131.19 |
| Estrato 3 | 1453 | 210.32 | 160 | 141.68 |
Síntesis del Análisis Exploratorio:
El EDA confirma la viabilidad del modelado estadístico con las variables identificadas. Área construida, número de baños y estrato emergen como predictores principales, mientras que la segmentación zonal revela diferencias estructurales importantes. La Zona Norte presenta características favorables para el análisis con correlaciones fuertes y comportamiento predecible, validando la decisión metodológica de enfocar el análisis en esta zona para la primera solicitud. Implicaciones para el Modelo de Regresión:
Los hallazgos del EDA proporcionan la base sólida para proceder con la construcción del modelo de regresión lineal múltiple en el Punto 3.
La construcción del modelo de regresión lineal múltiple se fundamenta en los hallazgos del análisis exploratorio, utilizando el dataset corregido geográficamente (base1_norte_real) para garantizar la precisión espacial de las recomendaciones. El modelo permitirá cuantificar el impacto individual de cada característica sobre el precio de las viviendas y generar predicciones confiables para las solicitudes específicas.
Preparación del Dataset para Modelación
# Usar el dataset corregido geográficamente para el modelo
datos_modelo <- base1_norte_real %>%
select(preciom, areaconst, estrato, habitaciones, parqueaderos, banios) %>%
# Convertir estrato a numérico para el modelo (manteniendo interpretabilidad ordinal)
mutate(estrato_num = as.numeric(gsub("Estrato ", "", estrato)))
# Estadísticas descriptivas del dataset de modelación
summary(datos_modelo)## preciom areaconst estrato habitaciones
## Min. : 120.0 Min. : 60.0 Estrato 3: 40 Min. : 0.000
## 1st Qu.: 325.0 1st Qu.: 160.0 Estrato 4: 66 1st Qu.: 4.000
## Median : 395.0 Median : 250.0 Estrato 5:142 Median : 4.000
## Mean : 428.8 Mean : 262.1 Estrato 6: 3 Mean : 4.554
## 3rd Qu.: 520.0 3rd Qu.: 338.0 3rd Qu.: 5.000
## Max. :1500.0 Max. :1188.0 Max. :10.000
## parqueaderos banios estrato_num
## Min. :1.00 Min. :0.000 Min. :3.00
## 1st Qu.:1.00 1st Qu.:3.000 1st Qu.:4.00
## Median :2.00 Median :3.000 Median :5.00
## Mean :2.08 Mean :3.602 Mean :4.43
## 3rd Qu.:2.00 3rd Qu.:4.000 3rd Qu.:5.00
## Max. :9.00 Max. :8.000 Max. :6.00
# Verificar completitud de datos
datos_completos <- sum(complete.cases(datos_modelo))
porcentaje_completo <- round(datos_completos/nrow(datos_modelo)*100, 2)
cat("Registros completos para modelación:", datos_completos, "de", nrow(datos_modelo),
"(", porcentaje_completo, "%)\n")## Registros completos para modelación: 251 de 251 ( 100 %)
El dataset corregido geográficamente presenta 251 propiedades con datos completos (100%), eliminando cualquier problema de valores faltantes para la modelación. Las estadísticas descriptivas revelan un mercado segmentado con características específicas:
Distribución de precios: Rango de 120-1,500 millones con mediana en 395M, indicando un mercado de casas en zona norte accesible comparado con otras zonas de Cali.
Características físicas:
Área construida: 60-1,188 m² (mediana 250 m²), mostrando diversidad desde casas compactas hasta propiedades amplias
Habitaciones: Concentración en 4 habitaciones (mediana), con rango 0-10 habitaciones
Bños: Distribución típica 0-8 baños (mediana 3), coherente con el segmento residencial
Segmentación por estrato: Predominio de Estrato 5 (142 propiedades) y Estrato 4 (66 propiedades), con presencia menor de Estratos 3 y 6, confirmando el perfil socioeconómico medio-alto de la zona norte.
Parqueaderos: Rango 1-9 con mediana 2, superando el requerimiento mínimo de la solicitud (1 parqueadero).
La completitud total de datos garantiza que el modelo de regresión tendrá información robusta para generar predicciones confiables sin sesgos por datos faltantes.
Especificación y Estimación del Modelo
# Modelo de regresión lineal múltiple
modelo_rlm <- lm(preciom ~ areaconst + estrato_num + habitaciones + parqueaderos + banios,
data = datos_modelo)
# Resumen del modelo
summary_modelo <- summary(modelo_rlm)
print(summary_modelo)##
## Call:
## lm(formula = preciom ~ areaconst + estrato_num + habitaciones +
## parqueaderos + banios, data = datos_modelo)
##
## Residuals:
## Min 1Q Median 3Q Max
## -470.69 -62.08 -12.02 35.36 971.89
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -81.99339 51.22975 -1.601 0.110776
## areaconst 0.61425 0.07082 8.673 5.90e-16 ***
## estrato_num 51.15129 11.72273 4.363 1.89e-05 ***
## habitaciones 0.05048 6.25536 0.008 0.993568
## parqueaderos 22.71167 6.47931 3.505 0.000542 ***
## banios 21.00632 8.45299 2.485 0.013621 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 122.4 on 245 degrees of freedom
## Multiple R-squared: 0.5556, Adjusted R-squared: 0.5465
## F-statistic: 61.26 on 5 and 245 DF, p-value: < 2.2e-16
# Intervalos de confianza para los coeficientes
intervalos_confianza <- confint(modelo_rlm, level = 0.95)
kable(intervalos_confianza,
caption = "Intervalos de Confianza (95%) para los Coeficientes",
digits = 3) %>%
kable_styling(bootstrap_options = c("striped", "hover"))| 2.5 % | 97.5 % | |
|---|---|---|
| (Intercept) | -182.900 | 18.914 |
| areaconst | 0.475 | 0.754 |
| estrato_num | 28.061 | 74.241 |
| habitaciones | -12.271 | 12.372 |
| parqueaderos | 9.949 | 35.474 |
| banios | 4.357 | 37.656 |
# Análisis de varianza
anova_resultado <- anova(modelo_rlm)
kable(anova_resultado,
caption = "Análisis de Varianza del Modelo",
digits = 4) %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Df | Sum Sq | Mean Sq | F value | Pr(>F) | |
|---|---|---|---|---|---|
| areaconst | 1 | 3631175.62 | 3631175.62 | 242.4467 | 0.0000 |
| estrato_num | 1 | 527324.78 | 527324.78 | 35.2085 | 0.0000 |
| habitaciones | 1 | 68329.14 | 68329.14 | 4.5622 | 0.0337 |
| parqueaderos | 1 | 268184.28 | 268184.28 | 17.9062 | 0.0000 |
| banios | 1 | 92493.24 | 92493.24 | 6.1756 | 0.0136 |
| Residuals | 245 | 3669417.62 | 14977.21 | NA | NA |
Interpretación de Coeficientes
# Crear tabla interpretativa de coeficientes
coeficientes <- data.frame(
Variable = names(coef(modelo_rlm)),
Coeficiente = round(coef(modelo_rlm), 3),
Error_Estandar = round(summary_modelo$coefficients[,2], 3),
t_valor = round(summary_modelo$coefficients[,3], 3),
p_valor = round(summary_modelo$coefficients[,4], 4),
Significativo = ifelse(summary_modelo$coefficients[,4] < 0.05, "Sí", "No")
)
kable(coeficientes,
caption = "Coeficientes del Modelo de Regresión Lineal Múltiple",
col.names = c("Variable", "Coeficiente", "Error Estándar", "t-valor", "p-valor", "Significativo (α=0.05)")) %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Variable | Coeficiente | Error Estándar | t-valor | p-valor | Significativo (α=0.05) | |
|---|---|---|---|---|---|---|
| (Intercept) | (Intercept) | -81.993 | 51.230 | -1.601 | 0.1108 | No |
| areaconst | areaconst | 0.614 | 0.071 | 8.673 | 0.0000 | Sí |
| estrato_num | estrato_num | 51.151 | 11.723 | 4.363 | 0.0000 | Sí |
| habitaciones | habitaciones | 0.050 | 6.255 | 0.008 | 0.9936 | No |
| parqueaderos | parqueaderos | 22.712 | 6.479 | 3.505 | 0.0005 | Sí |
| banios | banios | 21.006 | 8.453 | 2.485 | 0.0136 | Sí |
# Métricas de ajuste del modelo
r_cuadrado <- summary_modelo$r.squared
r_cuadrado_ajustado <- summary_modelo$adj.r.squared
f_estadistico <- summary_modelo$fstatistic[1]
p_modelo <- pf(f_estadistico, summary_modelo$fstatistic[2], summary_modelo$fstatistic[3], lower.tail = FALSE)
metricas_ajuste <- data.frame(
Metrica = c("R²", "R² Ajustado", "F-estadístico", "p-valor del modelo", "Error Estándar Residual"),
Valor = c(
round(r_cuadrado, 4),
round(r_cuadrado_ajustado, 4),
round(f_estadistico, 3),
format.pval(p_modelo),
round(summary_modelo$sigma, 3)
)
)
kable(metricas_ajuste,
caption = "Métricas de Ajuste del Modelo") %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Metrica | Valor |
|---|---|
| R² | 0.5556 |
| R² Ajustado | 0.5465 |
| F-estadístico | 61.26 |
| p-valor del modelo | < 2.22e-16 |
| Error Estándar Residual | 122.381 |
Interpretación Económica y Estadística:
Significancia Estadística y Coeficientes:
El modelo revela patrones claros en la valoración de casas en la zona norte de Cali:
Área Construida (β₁ = 0.614): Cada metro cuadrado adicional incrementa el precio en 0.614 millones de pesos (IC: 0.475-0.754M). Esta es la variable más significativa (p < 0.001) y económicamente la más importante, confirmando que el tamaño es el determinante fundamental del valor.
Estrato Socioeconómico (β₂ = 51.15): El incremento de un estrato aumenta el precio en 51.15 millones (IC: 28.06-74.24M), reflejando el premium sustancial por ubicación socioeconómica superior. Altamente significativo (p < 0.001).
Parqueaderos (β₄ = 22.71): Cada parqueadero adicional aporta 22.71 millones al valor (IC: 9.95-35.47M), significativo al 0.1% (p = 0.0005).
Número de Baños (β₅ = 21.01): Cada baño adicional incrementa el precio en 21.01 millones (IC: 4.36-37.66M), significativo al 5% (p = 0.014).
Habitaciones (β₃ = 0.050): NO significativo (p = 0.994), indicando que una vez controladas las otras variables, el número de habitaciones no aporta valor predictivo adicional.
Evaluación del Ajuste del Modelo:
R² = 0.5556: El modelo explica 55.56% de la variabilidad en precios, indicando poder predictivo sólido para un modelo inmobiliario
R² Ajustado = 0.5465: Confirma que las variables incluidas aportan valor explicativo real sin sobreajuste
F-estadístico = 61.26 (p < 2.2e-16): El modelo en conjunto es altamente significativo
Error Estándar Residual = 122.38M: Precisión promedio de ±122 millones en las predicciones
Implicaciones Prácticas:
El modelo identifica una ecuación de valoración robusta para la zona norte:
Precio = -82.0 + 0.614(Área) + 51.15(Estrato) + 22.71(Parqueaderos) + 21.01(Baños)
La variable habitaciones puede eliminarse del modelo final por su falta de significancia, simplificando la ecuación sin pérdida de poder predictivo. Esto sugiere que el área construida ya captura la información relevante sobre el espacio habitable.
Validación de Supuestos del Modelo
La validación de supuestos es crucial para confirmar la validez de las inferencias estadísticas del modelo de regresión lineal múltiple.
# Cargar librería para diagnósticos
# 1. Linealidad y Homocedasticidad
par(mfrow = c(2, 2))
plot(modelo_rlm)par(mfrow = c(1, 1))
# 2. Normalidad de residuos
# Test de Shapiro-Wilk (muestra reducida por limitación del test)
if(nrow(datos_modelo) <= 5000) {
shapiro_test <- shapiro.test(residuals(modelo_rlm))
cat("Test de Shapiro-Wilk para Normalidad de Residuos:\n")
cat("W =", round(shapiro_test$statistic, 4), ", p-valor =", format.pval(shapiro_test$p.value), "\n\n")
}## Test de Shapiro-Wilk para Normalidad de Residuos:
## W = 0.7895 , p-valor = < 2.22e-16
# 3. Test de Breusch-Pagan para Homocedasticidad
bp_test <- bptest(modelo_rlm)
cat("Test de Breusch-Pagan para Homocedasticidad:\n")## Test de Breusch-Pagan para Homocedasticidad:
## BP = 15.1083 , p-valor = 0.0099095
# 4. Multicolinealidad - Factor de Inflación de Varianza (VIF)
vif_valores <- vif(modelo_rlm)
vif_tabla <- data.frame(
Variable = names(vif_valores),
VIF = round(vif_valores, 3),
Interpretacion = ifelse(vif_valores > 10, "Multicolinealidad Alta",
ifelse(vif_valores > 5, "Multicolinealidad Moderada", "Aceptable"))
)
kable(vif_tabla,
caption = "Análisis de Multicolinealidad - Factor de Inflación de Varianza") %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Variable | VIF | Interpretacion | |
|---|---|---|---|
| areaconst | areaconst | 1.480 | Aceptable |
| estrato_num | estrato_num | 1.354 | Aceptable |
| habitaciones | habitaciones | 1.536 | Aceptable |
| parqueaderos | parqueaderos | 1.268 | Aceptable |
| banios | banios | 1.947 | Aceptable |
# 5. Test de Durbin-Watson para Autocorrelación
dw_test <- durbinWatsonTest(modelo_rlm)
cat("Test de Durbin-Watson para Autocorrelación:\n")## Test de Durbin-Watson para Autocorrelación:
## DW = 1.6793 , p-valor = 0.008
Interpretación de la Validación de Supuestos:
1. Análisis de Residuos (Gráficos de Diagnóstico):
Residuos vs Ajustados: Muestra dispersión razonablemente aleatoria alrededor de cero, confirmando linealidad. Se observan algunos outliers etiquetados (175, 176, 830) que requieren atención.
Q-Q Plot: Los residuos siguen aproximadamente la línea diagonal en la parte central, pero muestran desviaciones en las colas (especialmente 175o, 176o), indicando ligeras violaciones de normalidad en valores extremos.
Scale-Location: Detecta heterocedasticidad moderada con mayor variabilidad en predicciones altas, confirmado por el test Breusch-Pagan.
Residuos vs Leverage: Identifica observaciones influyentes, particularmente el punto 63 con alto leverage.
2. Tests Estadísticos:
Normalidad (Shapiro-Wilk): W = 0.7895, p < 0.001 - RECHAZA normalidad. Los residuos no siguen distribución normal perfecta, principalmente por outliers.
Homocedasticidad (Breusch-Pagan): BP = 15.11, p = 0.0099 - RECHAZA homocedasticidad. Existe heterocedasticidad significativa.
Multicolinealidad (VIF): Todos los valores < 2.0 - EXCELENTE. No hay problemas de multicolinealidad.
Autocorrelación (Durbin-Watson): DW = 1.68, p = 0.008 - DETECTA autocorrelación débil pero significativa.
3. Diagnóstico General:
Fortalezas del modelo:
Multicolinealidad bajo control (todos VIF < 2)
Relación lineal aceptable
R² = 0.556 con significancia alta
Violaciones detectadas:
Heterocedasticidad moderada
No normalidad de residuos (por outliers)
Autocorrelación espacial débil
Sugerencias de Mejora:
Para heterocedasticidad: Usar errores estándar robustos (HC) o transformación logarítmica del precio
Para outliers: Investigar observaciones 175, 176, 830 - verificar si son errores de datos o propiedades genuinamente atípicas
Para autocorrelación: Considerar variables espaciales adicionales o modelos espaciales
Modelo alternativo:
log(precio) ~ variables podría mejorar normalidad y
homocedasticidad
El modelo es funcionalmente válido para predicciones, pero las violaciones sugieren precaución en intervalos de confianza.
Diagnóstico de Residuos y Outliers
# Identificación de observaciones influyentes
# Distancia de Cook
cooksd <- cooks.distance(modelo_rlm)
n_observaciones <- nrow(datos_modelo)
umbral_cook <- 4/n_observaciones
outliers_cook <- which(cooksd > umbral_cook)
cat("Observaciones influyentes según Distancia de Cook (", length(outliers_cook), "):\n")## Observaciones influyentes según Distancia de Cook ( 12 ):
## 27 63 78 83 110 119 146 147 175 176 234 247
## 27 63 78 83 110 119 146 147 175 176 234 247
# Residuos estudentizados
residuos_student <- rstudent(modelo_rlm)
outliers_residuos <- which(abs(residuos_student) > 3)
cat("\nOutliers por residuos estudentizados (|t| > 3) (", length(outliers_residuos), "):\n")##
## Outliers por residuos estudentizados (|t| > 3) ( 4 ):
## 63 78 175 176
## 63 78 175 176
# Gráfico de residuos vs valores ajustados con identificación de outliers
datos_diagnostico <- data.frame(
ajustados = fitted(modelo_rlm),
residuos = residuals(modelo_rlm),
cook = cooksd,
outlier = 1:n_observaciones %in% c(outliers_cook, outliers_residuos)
)
ggplot(datos_diagnostico, aes(x = ajustados, y = residuos)) +
geom_point(aes(color = outlier), alpha = 0.6) +
geom_smooth(method = "loess", se = FALSE, color = "red") +
geom_hline(yintercept = 0, linetype = "dashed") +
scale_color_manual(values = c("black", "red"), name = "Outlier") +
labs(title = "Residuos vs Valores Ajustados",
x = "Valores Ajustados (Precio Predicho)",
y = "Residuos") +
theme_minimal()## `geom_smooth()` using formula = 'y ~ x'
Gráfico de Residuos vs Valores Ajustados: El gráfico con identificación de outliers (puntos rojos) confirma la heterocedasticidad observada anteriormente. La línea de tendencia (roja) muestra una curvatura descendente hacia valores altos, indicando que el modelo sobreestima propiedades de alto valor. Los 12 outliers identificados incluyen tanto propiedades subvaloradas como sobrevaloradas por el modelo.
Observaciones Influyentes:
12 propiedades con alta distancia de Cook: observaciones 27, 63, 78, 83, 110, 119, 146, 147, 175, 176, 234, 247
4 outliers extremos con residuos estudentizados |t| > 3: observaciones 63, 78, 175, 176
Predicciones para la Vivienda 1
El modelo predice con alto grado de confianza que una casa con las características solicitadas (200m², 4 habitaciones, 2 baños, 1 parqueadero, estrato 4-5) en la zona norte tendrá un precio cercano a 310 millones, dejando un margen confortable de 40 millones para negociación o características adicionales.
La amplitud del intervalo de predicción (485M) refleja la heterogeneidad del mercado, pero la estrechez del intervalo de confianza (52M) confirma que la estimación puntual es estadísticamente robusta.
Evaluación de las Predicciones para Vivienda 1:
Resultado Principal: ✅ DENTRO DEL PRESUPUESTO
Análisis Detallado:
Precio Predicho: 310.4 millones (margen de 39.6M bajo presupuesto)
Intervalo de Confianza (95%): 284.4 - 336.3 millones - rango estrecho indica alta precisión para la predicción media
Intervalo de Predicción (95%): 67.9 - 552.8 millones - rango amplio refleja la variabilidad natural del mercado
# Características de la vivienda solicitada
vivienda_1_specs <- data.frame(
areaconst = 200,
estrato_num = 4, # Promedio entre 4 y 5
habitaciones = 4,
parqueaderos = 1,
banios = 2
)
# Predicción puntual
precio_predicho <- predict(modelo_rlm, vivienda_1_specs)
# Intervalo de predicción
intervalo_pred <- predict(modelo_rlm, vivienda_1_specs, interval = "prediction", level = 0.95)
# Intervalo de confianza para la media
intervalo_conf <- predict(modelo_rlm, vivienda_1_specs, interval = "confidence", level = 0.95)
resultados_prediccion <- data.frame(
Tipo = c("Precio Predicho", "Intervalo Predicción (95%)", "Intervalo Confianza (95%)"),
Valor = c(
paste(round(precio_predicho, 1), "millones"),
paste(round(intervalo_pred[2], 1), "-", round(intervalo_pred[3], 1), "millones"),
paste(round(intervalo_conf[2], 1), "-", round(intervalo_conf[3], 1), "millones")
)
)
kable(resultados_prediccion,
caption = "Predicción de Precio para Vivienda 1 (Casa 200m², 4 hab, 2 baños, 1 parq, estrato 4-5, Zona Norte)") %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Tipo | Valor |
|---|---|
| Precio Predicho | 310.4 millones |
| Intervalo Predicción (95%) | 67.9 - 552.8 millones |
| Intervalo Confianza (95%) | 284.4 - 336.3 millones |
# Evaluación del presupuesto
presupuesto_disponible <- 350
diferencia_presupuesto <- presupuesto_disponible - precio_predicho
cat("\nEvaluación del Presupuesto:\n")##
## Evaluación del Presupuesto:
## Presupuesto disponible: 350 millones
## Precio predicho: 310.4 millones
## Diferencia: 39.6 millones
cat("Estado:", ifelse(diferencia_presupuesto >= 0, "DENTRO DEL PRESUPUESTO", "EXCEDE EL PRESUPUESTO"))## Estado: DENTRO DEL PRESUPUESTO
Supuestos del Modelo:
Linealidad: Los gráficos de residuos vs valores ajustados revelan si existe relación lineal adecuada
Homocedasticidad: La varianza constante de residuos es crucial para inferencias válidas
Normalidad: Los residuos deben seguir distribución normal para intervalos de confianza correctos
Independencia: No debe existir autocorrelación en los residuos.
Multicolinealidad:
VIF < 5: Aceptable
VIF 5-10: Moderada, requiere atención
VIF > 10: Alta, requiere corrección
Sugerencias de Mejora del Modelo
# Análisis de variables omitidas y transformaciones potenciales
cat("Sugerencias para Mejorar el Modelo:\n\n")## Sugerencias para Mejorar el Modelo:
## 1. TRANSFORMACIONES DE VARIABLES:
## - Considerar log(precio) si residuos muestran heterocedasticidad
## - Evaluar log(área) para relación no-lineal con precio
## - Términos cuadráticos para variables con efectos no-lineales
## 2. VARIABLES ADICIONALES:
## - Edad de la construcción
## - Estado de conservación
## - Proximidad a amenidades (colegios, centros comerciales)
## - Variables dummy para barrios específicos
## 3. TRATAMIENTO DE OUTLIERS:
## - Investigar observaciones con alta distancia de Cook
## - Considerar modelos robustos si hay muchos outliers
## - Validar datos de propiedades atípicas
## 4. VALIDACIÓN CRUZADA:
## - Implementar k-fold cross-validation
## - División hold-out para validación externa
## - Métricas de error predictivo (RMSE, MAE)
# Filtrar propiedades que cumplan criterios de la solicitud
candidatas_vivienda1 <- base1_norte_real %>%
filter(
preciom <= 350, # Dentro del presupuesto
habitaciones >= 3, # Mínimo 3 habitaciones (flexibilidad en la solicitud)
banios >= 2, # Mínimo 2 baños
parqueaderos >= 1, # Al menos 1 parqueadero
areaconst >= 150, # Área razonable para familia
estrato %in% c("Estrato 4", "Estrato 5") # Estratos solicitados
) %>%
arrange(desc(areaconst)) # Ordenar por área (mayor a menor)
cat("Propiedades candidatas encontradas:", nrow(candidatas_vivienda1), "\n")## Propiedades candidatas encontradas: 40
if(nrow(candidatas_vivienda1) > 0) {
# Mostrar top 10 candidatas
top_candidatas <- candidatas_vivienda1 %>%
head(10) %>%
select(id, preciom, areaconst, habitaciones, banios, parqueaderos, estrato, barrio) %>%
mutate(
valor_m2 = round(preciom / areaconst, 2),
score_criterios = habitaciones + banios + parqueaderos # Score simple basado en características
) %>%
arrange(desc(score_criterios), valor_m2)
kable(top_candidatas,
caption = "Top 10 Propiedades Candidatas para Vivienda 1",
col.names = c("ID", "Precio (M)", "Área (m²)", "Habitaciones", "Baños", "Parqueaderos", "Estrato", "Barrio", "Valor/m²", "Score")) %>%
kable_styling(bootstrap_options = c("striped", "hover")) %>%
scroll_box(width = "100%")
}| ID | Precio (M) | Área (m²) | Habitaciones | Baños | Parqueaderos | Estrato | Barrio | Valor/m² | Score |
|---|---|---|---|---|---|---|---|---|---|
| 4209 | 350 | 300.0 | 6 | 5 | 3 | Estrato 5 | el bosque | 1.17 | 14 |
| 4458 | 315 | 270.0 | 4 | 4 | 2 | Estrato 4 | el bosque | 1.17 | 10 |
| 952 | 330 | 275.0 | 5 | 3 | 2 | Estrato 4 | la merced | 1.20 | 10 |
| 3043 | 330 | 275.0 | 5 | 3 | 2 | Estrato 5 | la merced | 1.20 | 10 |
| 2544 | 340 | 264.5 | 4 | 4 | 2 | Estrato 4 | vipasa | 1.29 | 10 |
| 1822 | 340 | 295.0 | 4 | 2 | 2 | Estrato 4 | vipasa | 1.15 | 8 |
| 1108 | 330 | 260.0 | 4 | 3 | 1 | Estrato 4 | la merced | 1.27 | 8 |
| 1161 | 330 | 258.0 | 3 | 3 | 2 | Estrato 4 | la merced | 1.28 | 8 |
| 1943 | 350 | 346.0 | 4 | 2 | 1 | Estrato 5 | vipasa | 1.01 | 7 |
| 1924 | 320 | 264.0 | 3 | 2 | 1 | Estrato 4 | vipasa | 1.21 | 6 |
Para una validación robusta del modelo, se implementa una división estratificada del dataset que preserve las distribuciones de las variables categóricas importantes.
# División del dataset
set.seed(123) # Para reproducibilidad
# Verificar que la librería caret esté cargada
library(caret)
# Crear índices de división estratificada por estrato
indices_train <- createDataPartition(datos_modelo$estrato_num, p = 0.8, list = FALSE)
# Conjuntos de entrenamiento y prueba
train_data <- datos_modelo[indices_train, ]
test_data <- datos_modelo[-indices_train, ]
cat("Distribución de datos:\n")## Distribución de datos:
## Entrenamiento: 202 observaciones
## Prueba: 49 observaciones
# CORRECCIÓN: Verificar distribución de estratos con merge completo
train_estratos <- table(train_data$estrato_num)
test_estratos <- table(test_data$estrato_num)
# Crear data.frame con merge por nombres para asegurar misma longitud
train_df <- data.frame(Estrato = names(train_estratos),
Train_n = as.numeric(train_estratos),
stringsAsFactors = FALSE)
test_df <- data.frame(Estrato = names(test_estratos),
Test_n = as.numeric(test_estratos),
stringsAsFactors = FALSE)
# Merge completo para asegurar todas las categorías
distribucion_estratos <- merge(train_df, test_df, by = "Estrato", all = TRUE)
distribucion_estratos[is.na(distribucion_estratos)] <- 0
# Calcular porcentajes
distribucion_estratos$Train_pct <- round(distribucion_estratos$Train_n / sum(distribucion_estratos$Train_n) * 100, 1)
distribucion_estratos$Test_pct <- round(distribucion_estratos$Test_n / sum(distribucion_estratos$Test_n) * 100, 1)
kable(distribucion_estratos,
caption = "Distribución de Estratos en Conjuntos Train/Test") %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Estrato | Train_n | Test_n | Train_pct | Test_pct |
|---|---|---|---|---|
| 3 | 32 | 8 | 15.8 | 16.3 |
| 4 | 53 | 13 | 26.2 | 26.5 |
| 5 | 114 | 28 | 56.4 | 57.1 |
| 6 | 3 | 0 | 1.5 | 0.0 |
# Re-entrenar modelo con conjunto de entrenamiento
modelo_train <- lm(preciom ~ areaconst + estrato_num + habitaciones + parqueaderos + banios,
data = train_data)
# Predicciones en conjunto de prueba
predicciones_test <- predict(modelo_train, test_data)
# Métricas de evaluación
rmse_test <- sqrt(mean((test_data$preciom - predicciones_test)^2))
mae_test <- mean(abs(test_data$preciom - predicciones_test))
r2_test <- cor(test_data$preciom, predicciones_test)^2
metricas_validacion <- data.frame(
Metrica = c("RMSE", "MAE", "R² Test", "R² Train"),
Valor = c(
round(rmse_test, 2),
round(mae_test, 2),
round(r2_test, 4),
round(summary(modelo_train)$r.squared, 4)
),
Interpretacion = c(
"Error cuadrático medio (millones)",
"Error absoluto medio (millones)",
"Varianza explicada en test",
"Varianza explicada en train"
)
)
kable(metricas_validacion,
caption = "Métricas de Validación del Modelo") %>%
kable_styling(bootstrap_options = c("striped", "hover"))| Metrica | Valor | Interpretacion |
|---|---|---|
| RMSE | 124.4000 | Error cuadrático medio (millones) |
| MAE | 81.5400 | Error absoluto medio (millones) |
| R² Test | 0.6384 | Varianza explicada en test |
| R² Train | 0.5373 | Varianza explicada en train |
# Gráfico de predicciones vs valores reales
datos_evaluacion <- data.frame(
Real = test_data$preciom,
Predicho = predicciones_test,
Residuo = test_data$preciom - predicciones_test
)
# Scatter plot predicciones vs reales
ggplot(datos_evaluacion, aes(x = Real, y = Predicho)) +
geom_point(alpha = 0.6) +
geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
geom_smooth(method = "lm", se = TRUE, color = "blue") +
labs(title = "Predicciones vs Valores Reales",
x = "Precio Real (millones)",
y = "Precio Predicho (millones)",
subtitle = paste("R² =", round(r2_test, 3), "| RMSE =", round(rmse_test, 1), "millones")) +
theme_minimal()## `geom_smooth()` using formula = 'y ~ x'
# Distribución de residuos
ggplot(datos_evaluacion, aes(x = Residuo)) +
geom_histogram(bins = 20, fill = "lightblue", alpha = 0.7, color = "black") +
geom_vline(xintercept = 0, color = "red", linetype = "dashed") +
labs(title = "Distribución de Residuos",
x = "Residuo (Precio Real - Predicho)",
y = "Frecuencia") +
theme_minimal()Basándose en el modelo desarrollado y el análisis de propiedades disponibles, se presentan las siguientes recomendaciones para la solicitud de vivienda en la zona norte:
## RECOMENDACIONES PARA VIVIENDA 1 (ZONA NORTE)
## ==================================================
## MODELO PREDICTIVO:
## - Precisión del modelo: R² = 0.556
## - Error promedio: ± 122.4 millones
## - Precio predicho para especificaciones: 310.4 millones
## ESTADO DEL PRESUPUESTO:
if(precio_predicho <= 350) {
cat("✓ La vivienda solicitada SE AJUSTA al presupuesto de 350 millones\n")
cat("✓ Margen disponible:", round(350 - precio_predicho, 1), "millones\n")
} else {
cat("✗ La vivienda solicitada EXCEDE el presupuesto por:", round(precio_predicho - 350, 1), "millones\n")
cat("✓ Alternativas dentro del presupuesto disponibles\n")
}## ✓ La vivienda solicitada SE AJUSTA al presupuesto de 350 millones
## ✓ Margen disponible: 39.6 millones
##
## PROPIEDADES RECOMENDADAS:
if(nrow(candidatas_vivienda1) > 0) {
cat("✓", nrow(candidatas_vivienda1), "propiedades identificadas que cumplen criterios\n")
cat("✓ Rango de precios:", min(candidatas_vivienda1$preciom), "-", max(candidatas_vivienda1$preciom), "millones\n")
cat("✓ Áreas disponibles:", min(candidatas_vivienda1$areaconst), "-", max(candidatas_vivienda1$areaconst), "m²\n")
} else {
cat("⚠ Pocas opciones disponibles - considerar flexibilizar criterios\n")
}## ✓ 40 propiedades identificadas que cumplen criterios
## ✓ Rango de precios: 230 - 350 millones
## ✓ Áreas disponibles: 150 - 346 m²
##
## FACTORES CLAVE DEL PRECIO:
cat("1. Área construida: +", round(coef(modelo_rlm)["areaconst"], 2), "millones por m² adicional\n")## 1. Área construida: + 0.61 millones por m² adicional
## 2. Número de baños: + 21 millones por baño adicional
## 3. Estrato: + 51.2 millones por estrato superior
Siguiendo la metodología establecida para el primer tipo de vieinda, se procede al análisis integral de la segunda vivienda requerida: un apartamento de 300m² con 5 habitaciones, 3 baños, 3 parqueaderos, ubicado en estratos 5-6 de la zona sur de Cali, con un presupuesto preaprobado de 850 millones de pesos. El análisis aplicará la misma secuencia metodológica implementada anteriormente: filtrado geográfico por tipo de vivienda, corrección espacial mediante clustering Haversine, desarrollo de modelo de regresión específico para apartamentos, validación estadística, predicción para las características solicitadas, y identificación de propiedades candidatas dentro del rango presupuestal disponible.
# =============================================================================
# PUNTO 7: ANÁLISIS PARA VIVIENDA 2 (APARTAMENTOS ZONA SUR)
# =============================================================================
library(dplyr)
library(geosphere)
library(cluster)
# Filtrar apartamentos de la zona sur
base2 <- datos %>%
filter(tipo == "Apartamento" & zona == "Zona Sur")
cat("Total apartamentos Zona Sur encontrados:", nrow(base2), "\n")## Total apartamentos Zona Sur encontrados: 2787
# Verificar si hay suficientes datos
if(nrow(base2) < 20) {
cat("ADVERTENCIA: Pocos apartamentos encontrados en Zona Sur:", nrow(base2), "\n")
cat("El análisis continuará con los datos disponibles\n")
}
# Verificar coordenadas disponibles
base2_coord <- base2 %>%
filter(!is.na(longitud) & !is.na(latitud))
cat("Apartamentos con coordenadas válidas:", nrow(base2_coord), "\n")## Apartamentos con coordenadas válidas: 2787
# Corrección geográfica mediante clustering espacial (solo si hay suficientes datos)
if(nrow(base2_coord) >= 50) {
# Crear matriz de distancia Haversine
coordenadas_sur <- base2_coord %>%
select(longitud, latitud)
matriz_distancia_sur <- distm(coordenadas_sur, fun = distHaversine)
# Clustering PAM
set.seed(123)
numero_clusters_sur <- min(5, nrow(base2_coord) %/% 10) # Ajustar clusters según datos
pam_resultado_sur <- pam(matriz_distancia_sur, k = numero_clusters_sur, diss = TRUE)
# Añadir cluster geográfico
base2_coord$cluster_geografico <- pam_resultado_sur$clustering
# Analizar características de cada cluster
cluster_analisis_sur <- base2_coord %>%
group_by(cluster_geografico) %>%
summarise(
n_propiedades = n(),
precio_mediano = median(preciom, na.rm = TRUE),
area_mediana = median(areaconst, na.rm = TRUE),
lat_centro = round(median(latitud), 4),
lon_centro = round(median(longitud), 4),
.groups = 'drop'
) %>%
arrange(lat_centro) # Ordenar por latitud para zona sur
cat("\nAnálisis de Clusters Zona Sur:\n")
print(cluster_analisis_sur)
# Para zona sur, usar cluster con latitud más baja (más al sur)
cluster_sur_real <- cluster_analisis_sur %>%
slice_min(lat_centro, n = 1) %>%
pull(cluster_geografico)
base2_sur_real <- base2_coord %>%
filter(cluster_geografico == cluster_sur_real)
cat("Cluster identificado como Sur real:", cluster_sur_real, "\n")
} else {
# Si hay pocos datos, usar todos los disponibles
base2_sur_real <- base2_coord
cat("Usando todos los apartamentos disponibles (sin clustering por datos insuficientes)\n")
}##
## Análisis de Clusters Zona Sur:
## # A tibble: 5 × 6
## cluster_geografico n_propiedades precio_mediano area_mediana lat_centro
## <int> <int> <dbl> <dbl> <dbl>
## 1 5 348 590 136 3.35
## 2 2 933 229 73 3.37
## 3 3 643 260 86 3.39
## 4 4 558 220 85 3.41
## 5 1 305 240 80 3.45
## # ℹ 1 more variable: lon_centro <dbl>
## Cluster identificado como Sur real: 5
## Apartamentos en zona sur real (post-corrección): 348
# Verificar si hay suficientes datos para modelar
if(nrow(base2_sur_real) < 10) {
cat("ERROR: Insuficientes apartamentos para modelar:", nrow(base2_sur_real), "\n")
cat("Se requieren mínimo 10 observaciones. Usando dataset completo de apartamentos.\n")
base2_sur_real <- base2_coord
}
# Preparar dataset para modelación - Apartamentos
datos_modelo_apt <- base2_sur_real %>%
select(preciom, areaconst, estrato, habitaciones, parqueaderos, banios) %>%
mutate(estrato_num = as.numeric(gsub("Estrato ", "", estrato))) %>%
filter(complete.cases(.))
cat("Registros completos para modelación apartamentos:", nrow(datos_modelo_apt), "\n")## Registros completos para modelación apartamentos: 348
# Estadísticas descriptivas apartamentos
cat("\nEstadísticas descriptivas - Apartamentos Zona Sur:\n")##
## Estadísticas descriptivas - Apartamentos Zona Sur:
## preciom areaconst estrato habitaciones parqueaderos
## Min. : 150 Min. : 47.0 Estrato 3: 1 Min. :0.000 Min. :1.000
## 1st Qu.: 450 1st Qu.:120.0 Estrato 4: 10 1st Qu.:3.000 1st Qu.:2.000
## Median : 590 Median :136.0 Estrato 5: 36 Median :3.000 Median :2.000
## Mean : 610 Mean :149.3 Estrato 6:301 Mean :3.152 Mean :2.161
## 3rd Qu.: 700 3rd Qu.:168.2 3rd Qu.:4.000 3rd Qu.:2.000
## Max. :1750 Max. :464.0 Max. :6.000 Max. :4.000
## banios estrato_num
## Min. :1.000 Min. :3.00
## 1st Qu.:3.000 1st Qu.:6.00
## Median :4.000 Median :6.00
## Mean :3.664 Mean :5.83
## 3rd Qu.:5.000 3rd Qu.:6.00
## Max. :7.000 Max. :6.00
# Verificar variabilidad mínima para modelar
if(nrow(datos_modelo_apt) >= 10) {
# Modelo de regresión para apartamentos
modelo_apt <- lm(preciom ~ areaconst + estrato_num + habitaciones + parqueaderos + banios,
data = datos_modelo_apt)
# Resumen del modelo apartamentos
cat("\nRESUMEN DEL MODELO - APARTAMENTOS ZONA SUR:\n")
cat(strrep("=", 50), "\n")
print(summary(modelo_apt))
# Métricas de ajuste
r2_apt <- summary(modelo_apt)$r.squared
r2_adj_apt <- summary(modelo_apt)$adj.r.squared
cat("\nMétricas de Ajuste:\n")
cat("R² =", round(r2_apt, 4), "\n")
cat("R² Ajustado =", round(r2_adj_apt, 4), "\n")
cat("Error Estándar =", round(summary(modelo_apt)$sigma, 3), "millones\n")
# Predicciones para Vivienda 2
vivienda_2_specs <- data.frame(
areaconst = 300,
estrato_num = 5.5, # Promedio entre 5 y 6
habitaciones = 5,
parqueaderos = 3,
banios = 3
)
# Predicción apartamento
precio_predicho_apt <- predict(modelo_apt, vivienda_2_specs)
# Intervalos de predicción y confianza
intervalo_pred_apt <- predict(modelo_apt, vivienda_2_specs,
interval = "prediction", level = 0.95)
intervalo_conf_apt <- predict(modelo_apt, vivienda_2_specs,
interval = "confidence", level = 0.95)
cat("\nPREDICCIÓN PARA VIVIENDA 2:\n")
cat(strrep("=", 30), "\n")
cat("Apartamento 300m², 5 habitaciones, 3 baños, 3 parqueaderos, estrato 5-6\n")
cat("Precio Predicho:", round(as.numeric(precio_predicho_apt), 1), "millones\n")
cat("Intervalo Predicción (95%):", round(intervalo_pred_apt[2], 1), "-",
round(intervalo_pred_apt[3], 1), "millones\n")
cat("Intervalo Confianza (95%):", round(intervalo_conf_apt[2], 1), "-",
round(intervalo_conf_apt[3], 1), "millones\n")
# Evaluación del presupuesto apartamento
presupuesto_apt <- 850
diferencia_apt <- presupuesto_apt - as.numeric(precio_predicho_apt)
cat("\nEVALUACIÓN DEL PRESUPUESTO:\n")
cat("Presupuesto disponible:", presupuesto_apt, "millones\n")
cat("Precio predicho:", round(as.numeric(precio_predicho_apt), 1), "millones\n")
cat("Diferencia:", round(diferencia_apt, 1), "millones\n")
cat("Estado:", ifelse(diferencia_apt >= 0, "DENTRO DEL PRESUPUESTO", "EXCEDE EL PRESUPUESTO"), "\n")
# Identificar candidatos apartamentos
candidatos_apt <- base2_sur_real %>%
filter(
preciom <= 850,
habitaciones >= 4, # Flexibilidad
banios >= 3,
parqueaderos >= 2, # Flexibilidad
areaconst >= 250,
estrato %in% c("Estrato 5", "Estrato 6")
) %>%
arrange(desc(areaconst))
cat("\nPROPIEDADES CANDIDATAS:\n")
cat("Apartamentos candidatos encontrados:", nrow(candidatos_apt), "\n")
if(nrow(candidatos_apt) > 0) {
cat("Rango de precios:", min(candidatos_apt$preciom), "-",
max(candidatos_apt$preciom), "millones\n")
cat("Áreas disponibles:", min(candidatos_apt$areaconst), "-",
max(candidatos_apt$areaconst), "m²\n")
} else {
cat("No se encontraron apartamentos que cumplan todos los criterios.\n")
cat("Considerar flexibilizar algunos requisitos.\n")
}
} else {
cat("ERROR: Datos insuficientes para crear modelo de apartamentos.\n")
cat("Se encontraron solo", nrow(datos_modelo_apt), "registros completos.\n")
precio_predicho_apt <- NA
diferencia_apt <- NA
}##
## RESUMEN DEL MODELO - APARTAMENTOS ZONA SUR:
## ==================================================
##
## Call:
## lm(formula = preciom ~ areaconst + estrato_num + habitaciones +
## parqueaderos + banios, data = datos_modelo_apt)
##
## Residuals:
## Min 1Q Median 3Q Max
## -572.86 -56.07 -4.09 47.81 646.02
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -185.089 90.954 -2.035 0.04262 *
## areaconst 3.142 0.165 19.044 < 2e-16 ***
## estrato_num 29.184 16.341 1.786 0.07499 .
## habitaciones -22.874 12.206 -1.874 0.06178 .
## parqueaderos 35.536 12.935 2.747 0.00633 **
## banios 41.225 8.983 4.589 6.26e-06 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 127.5 on 342 degrees of freedom
## Multiple R-squared: 0.7599, Adjusted R-squared: 0.7564
## F-statistic: 216.5 on 5 and 342 DF, p-value: < 2.2e-16
##
##
## Métricas de Ajuste:
## R² = 0.7599
## R² Ajustado = 0.7564
## Error Estándar = 127.532 millones
##
## PREDICCIÓN PARA VIVIENDA 2:
## ==============================
## Apartamento 300m², 5 habitaciones, 3 baños, 3 parqueaderos, estrato 5-6
## Precio Predicho: 1033.8 millones
## Intervalo Predicción (95%): 773.4 - 1294.2 millones
## Intervalo Confianza (95%): 963.9 - 1103.8 millones
##
## EVALUACIÓN DEL PRESUPUESTO:
## Presupuesto disponible: 850 millones
## Precio predicho: 1033.8 millones
## Diferencia: -183.8 millones
## Estado: EXCEDE EL PRESUPUESTO
##
## PROPIEDADES CANDIDATAS:
## Apartamentos candidatos encontrados: 0
## No se encontraron apartamentos que cumplan todos los criterios.
## Considerar flexibilizar algunos requisitos.
##
## ============================================================
## RESUMEN COMPARATIVO FINAL
## ============================================================
## VIVIENDA 1 (Casa Norte):
## - Precio estimado: 310.4 millones
## - Presupuesto: 350 millones
## - Estado: DENTRO DEL PRESUPUESTO (margen: 39.6M)
## VIVIENDA 2 (Apartamento Sur):
if(!is.na(precio_predicho_apt)) {
cat("- Precio estimado:", round(as.numeric(precio_predicho_apt), 1), "millones\n")
cat("- Presupuesto: 850 millones\n")
cat("- Estado:", ifelse(diferencia_apt >= 0, "DENTRO DEL PRESUPUESTO", "EXCEDE PRESUPUESTO"), "\n")
if(diferencia_apt >= 0) {
cat("- Margen:", round(diferencia_apt, 1), "millones\n")
}
} else {
cat("- No se pudo generar predicción por datos insuficientes\n")
cat("- Recomendación: Ampliar búsqueda o revisar criterios\n")
}## - Precio estimado: 1033.8 millones
## - Presupuesto: 850 millones
## - Estado: EXCEDE PRESUPUESTO
Corrección Geográfica y Dataset:
El filtrado inicial identificó 2,787 apartamentos en la zona sur con coordenadas válidas completas. El clustering espacial reveló 5 conglomerados geográficos distintivos, siendo el Cluster 5 el más genuinamente sureño (latitud 3.35), conteniendo 348 apartamentos con precio mediano de 590 millones y área mediana de 136m². Esta corrección redujo significativamente el dataset pero garantiza precisión geográfica, eliminando apartamentos incorrectamente clasificados como “zona sur” que en realidad se ubicaban en otras áreas de la ciudad.
Características del Mercado de Apartamentos:
El perfil del mercado de apartamentos zona sur muestra un sesgo hacia segmentos premium: 301 propiedades (86.5%) corresponden a Estrato 6, con presencia menor de estratos 4 y 5. Esta concentración en el estrato más alto explica los precios elevados observados (rango 150-1,750 millones, mediana 590M). Las características físicas muestran apartamentos compactos pero bien equipados: área promedio 149m², 3-4 habitaciones típicamente, y excelente dotación de baños (promedio 3.7) y parqueaderos (promedio 2.2).
Desempeño del Modelo de Apartamentos:
El modelo de regresión para apartamentos exhibe rendimiento superior al modelo de casas:
R² = 0.7599 (75.99%) vs 55.56% de las casas, indicando mayor capacidad predictiva
Error estándar = 127.5 millones, ligeramente superior por el rango de precios más amplio
Significancia estadística robusta con F-statistic = 216.5 (p < 2.2e-16)
Los coeficientes revelan dinámicas específicas del mercado de apartamentos:
Área construida: 3.14 millones por m² (5x mayor que casas), reflejando la valorización premium del espacio en apartamentos
Baños: 41.2 millones por baño adicional (2x mayor que casas), indicando alta valoración de comodidades
Parqueaderos: 35.5 millones por plaza adicional, crítico en zonas urbanas densas
Habitaciones: Coeficiente negativo (-22.9M), sugiriendo que apartamentos con muchas habitaciones pequeñas valen menos que espacios amplios
Evaluación Crítica de la Solicitud:
La predicción para el apartamento solicitado arroja 1,033.8 millones, excediendo el presupuesto por 183.8 millones (21.6%). Esta sobrevaloración se explica por la conjunción de factores premium: 300m² (el doble del área mediana), 5 habitaciones (por encima del promedio), estrato 5.5, y ubicación sur valorizada.
Los intervalos estadísticos confirman la robustez de la predicción:
Intervalo de confianza: 963.9-1,103.8 millones (estrecho, alta precisión)
Intervalo de predicción: 773.4-1,294.2 millones (amplio, refleja variabilidad del mercado)
Disponibilidad de Propiedades y Recomendaciones:
La búsqueda de candidatos con criterios completos resultó en cero apartamentos disponibles dentro del presupuesto, revelando un desajuste fundamental entre expectativas y realidad del mercado. Esta situación requiere estrategias de flexibilización:
Ajuste presupuestal: Incrementar presupuesto a 1,050-1,100 millones
Flexibilización de área: Considerar apartamentos 250-280m²
Reducción de habitaciones: Aceptar 4 habitaciones en lugar de 5
Alternativas de estrato: Evaluar opciones en Estrato 5 puro
Diversificación geográfica: Explorar zonas oeste u otras áreas sur
Conclusión Estratégica:
El análisis revela que la solicitud de apartamento es técnicamente factible pero financieramente desafiante con el presupuesto actual. El mercado de apartamentos zona sur opera en segmentos de mayor valoración que las casas zona norte, requiriendo ajustes estratégicos para lograr compatibilidad entre expectativas del cliente y ofertas disponibles en el mercado caleño.
La zona sur representa el segmento premium del mercado inmobiliario caleño, mientras que la zona norte ofrece opciones más accesibles para familias de clase media-alta. Esta diferenciación explica por qué la vivienda 1 (norte) está dentro del presupuesto y la vivienda 2 (sur) lo excede significativamente.
Análisis de Sensibilidad con Datos Imputados:
La variable parking_orig conserva los valores originales
de parqueaderos antes de la imputación por moda, creando una oportunidad
de investigación para evaluar el impacto de los datos imputados en la
robustez del modelo. El trabajo futuro contempla:
is.na(parking_orig) (fueron imputados)listwise deletion)Incorporación de Variables Cualitativas: - Estado de conservación de la propiedad - Calidad de acabados y materiales - Proximidad a amenidades urbanas (colegios, centros comerciales, transporte) - Variables dummy específicas por barrio dentro de cada zona
Modelos Alternativos: - Transformaciones logarítmicas para manejo de heterocedasticidad - Modelos de regresión robusta para outliers - Técnicas de machine learning (Random Forest, Gradient Boosting) para comparación predictiva - Modelos espaciales que incorporen autocorrelación geográfica
Estabilidad del Modelo: - Evaluar consistencia de coeficientes con datos de diferentes períodos - Análisis de estacionalidad en precios inmobiliarios - Monitoreo continuo de precisión predictiva con nuevos datos
Esta línea de investigación permitiría robustecer las recomendaciones a C&A y establecer un sistema de modelado inmobiliario más confiable y adaptativo para el mercado caleño.