Introducción

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.

Descripción de Variables

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("devtools") # solo la primera vez
#devtools::install_github("centromagis/paqueteMODELOS", force =TRUE)
library(paqueteMODELOS)
data("vivienda")

Solicitudes de Vivienda

#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

Metodologia

1. Data Collection

1.1 Carga de librerias

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

1.2 Selección de datos

## 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…

2. Data Preprocessing

2.1 Análisis de Calidad de Datos

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.

Información Básica del Dataset.

Se realiza una evaluación inicial para conocer las dimensiones del dataset y verificar la estructura general de los datos.

# Información básica del dataset
n_registros <- nrow(datos)
n_variables <- ncol(datos)

El dataset contiene 8,322 registros y 13 variables correspondientes a ofertas inmobiliarias de los últimos tres meses en Cali.

Verificación de Llave Única.

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

datos <- datos %>%
  filter(!is.na(id))


nrow(datos)
## [1] 8319

Se eliminan 3 registros de 8322.

Análisis de Valores Faltantes

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")
}
Variables con valores faltantes
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).

Imputación de Faltantes

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"))
Resumen del tratamiento de valores faltantes
Variable Faltantes Inicial Faltantes Final Imputados Tratamiento
parqueaderos 1602 0 1602 Imputación por moda
piso 2635 2635 0 Mantener faltantes
# Actualizar datos principales
datos <- datos_imputados

Análisis de la Variable Piso

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"))
Análisis de valores faltantes en ‘piso’ por tipo de vivienda
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"))
}
Distribución de valores disponibles en ‘piso’ por tipo
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:

  1. Irrelevancia para las solicitudes: La variable piso NO está incluida en ninguna de las dos solicitudes de vivienda del cliente internacional
  2. Variables críticas identificadas: Las solicitudes especifican únicamente: tipo, área construida, parqueaderos, baños, habitaciones, estrato y zona
  3. Alto porcentaje de faltantes: 32% de valores faltantes sin justificación clara
  4. Heterogeneidad arquitectónica: Las casas pueden tener múltiples configuraciones (planta baja, dos plantas, sótanos, entrepisos)
  5. Riesgo metodológico: Imputar sin fundamento sólido podría introducir sesgos

Decisión de Tratamiento para piso:

Se excluirá completamente la variable piso del análisis por dos razones fundamentales:

  1. Enfoque en solicitud del cliente: No es una característica requerida por el cliente internacional
  2. Calidad metodológica: El 32% de valores faltantes presenta riesgo de distorsión si se imputa incorrectamente

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.

# EXCLUIR variable piso del análisis posterior
datos <- datos %>%
  select(-piso)
# 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"))
Resumen del proceso de limpieza e imputación
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

Variables Categóricas

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"
ifelse(estrato_convertido, "convertido exitosamente", "no encontrado")
## [1] "convertido exitosamente"

Variables convertidas correctamente a factores: zona, tipo, barrio. El estrato ha sido convertido exitosamente como factor ordenado.

Verificación de Distribuciones

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"))
}
Distribución de propiedades por zona
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"))
}
Distribución de propiedades por tipo de vivienda
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"))
}
Distribución de propiedades por estrato (Categórica Ordinal)
Estrato Frecuencia Porcentaje
Estrato 3 1453 17.5
Estrato 4 2129 25.6
Estrato 5 2750 33.1
Estrato 6 1987 23.9

Finalización de Limpieza

# 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"))
Resumen final del proceso de limpieza
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.

Validacion de nulos y espaccios

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")
}
Analisis de valores faltantes por variable (Post-limpieza)
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

Proporcion de Faltantes

La variable parking_orig es una variable auxiliar que almacena la informacion del numero de parqueaderos sin imputacion.

names(datos)
##  [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

2.2. PREGUNTA 1

2.2.1 Filtro para Vivienda 1 (Casas en Zona Norte)

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
porcentaje_filtro
## [1] 8.68
format(n_total, big.mark = ",")
## [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%")
Primeros 3 registros de casas en zona norte
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

2.2.2 Verificación del Filtro

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"))
Distribución de propiedades por tipo y zona
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"))
Distribución de casas en zona norte por estrato
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"))
Estadísticas descriptivas - Casas Zona Norte
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

2.2.3 Mapa de Ubicaciones

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_casas

2.2.4 Análisis de Consistencia Geográfica

El 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"))
Top 10 barrios con más casas en ‘Zona Norte’ (antes de corrección)
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
nrow(base1)
## [1] 722
n_coord_disponibles  
## [1] 722
porcentaje_coord
## [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.

2.2.5 Correcció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_corregido

Aná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"))
Características de clusters geográficos (ordenados Norte a Sur)
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"))
Corrección geográfica: Cluster norte real vs dataset original
Aspecto Valor
Cluster identificado como Norte 1
Propiedades en norte real 251
Propiedades originales 722
Efectividad del filtro geográfico 34.8%
cluster_norte_real
## [1] 1
nrow(base1_norte_real)
## [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.

2.3 EDA

Punto 2: Análisis de Correlaciones con Gráficos Interactivos

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"))
Correlaciones con el Precio (ordenadas por magnitud)
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"
  )

p1

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

p2

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

p4

El 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"))
Análisis comparativo por zona geográfica
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"))
Análisis comparativo por estrato socioeconómico
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:

  1. Variables clave confirmadas: Área construida, baños y estrato deben incluirse definitivamente
  2. Posible multicolinealidad: Entre área construida y número de habitaciones requiere evaluación
  3. Efectos zonales: Necesidad de variables dummy o análisis estratificado por zona
  4. Transformaciones: La dispersión observada sugiere evaluar transformaciones logarítmicas

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.

3. Model Building

3.1 Punto 3: Modelo de Regresión Lineal Múltiple

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"))
Intervalos de Confianza (95%) para los Coeficientes
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"))
Análisis de Varianza del Modelo
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"))
Coeficientes del Modelo de Regresión Lineal Múltiple
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
estrato_num estrato_num 51.151 11.723 4.363 0.0000
habitaciones habitaciones 0.050 6.255 0.008 0.9936 No
parqueaderos parqueaderos 22.712 6.479 3.505 0.0005
banios banios 21.006 8.453 2.485 0.0136
# 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"))
Métricas de Ajuste del Modelo
Metrica Valor
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:

  1. Á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.

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

  3. Parqueaderos (β₄ = 22.71): Cada parqueadero adicional aporta 22.71 millones al valor (IC: 9.95-35.47M), significativo al 0.1% (p = 0.0005).

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

  5. 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:
cat("BP =", round(bp_test$statistic, 4), ", p-valor =", format.pval(bp_test$p.value), "\n\n")
## 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"))
Análisis de Multicolinealidad - Factor de Inflación de Varianza
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:
cat("DW =", round(dw_test$dw, 4), ", p-valor =", format.pval(dw_test$p), "\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:

  1. Para heterocedasticidad: Usar errores estándar robustos (HC) o transformación logarítmica del precio

  2. Para outliers: Investigar observaciones 175, 176, 830 - verificar si son errores de datos o propiedades genuinamente atípicas

  3. Para autocorrelación: Considerar variables espaciales adicionales o modelos espaciales

  4. 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 ):
if(length(outliers_cook) > 0) {
  print(outliers_cook)
}
##  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 ):
if(length(outliers_residuos) > 0) {
  print(outliers_residuos)
}
##  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"))
Predicción de Precio para Vivienda 1 (Casa 200m², 4 hab, 2 baños, 1 parq, estrato 4-5, Zona Norte)
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:
cat("Presupuesto disponible:", presupuesto_disponible, "millones\n")
## Presupuesto disponible: 350 millones
cat("Precio predicho:", round(precio_predicho, 1), "millones\n")
## Precio predicho: 310.4 millones
cat("Diferencia:", round(diferencia_presupuesto, 1), "millones\n")
## Diferencia: 39.6 millones
cat("Estado:", ifelse(diferencia_presupuesto >= 0, "DENTRO DEL PRESUPUESTO", "EXCEDE EL PRESUPUESTO"))
## Estado: DENTRO DEL PRESUPUESTO

3.2 Análisis de Residuos y Mejoras del Modelo

Interpretación de Diagnósticos

Supuestos del Modelo:

  1. Linealidad: Los gráficos de residuos vs valores ajustados revelan si existe relación lineal adecuada

  2. Homocedasticidad: La varianza constante de residuos es crucial para inferencias válidas

  3. Normalidad: Los residuos deben seguir distribución normal para intervalos de confianza correctos

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

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:
cat("1. TRANSFORMACIONES DE VARIABLES:\n")
## 1. TRANSFORMACIONES DE VARIABLES:
cat("   - Considerar log(precio) si residuos muestran heterocedasticidad\n")
##    - Considerar log(precio) si residuos muestran heterocedasticidad
cat("   - Evaluar log(área) para relación no-lineal con precio\n")
##    - Evaluar log(área) para relación no-lineal con precio
cat("   - Términos cuadráticos para variables con efectos no-lineales\n\n")
##    - Términos cuadráticos para variables con efectos no-lineales
cat("2. VARIABLES ADICIONALES:\n")
## 2. VARIABLES ADICIONALES:
cat("   - Edad de la construcción\n")
##    - Edad de la construcción
cat("   - Estado de conservación\n")
##    - Estado de conservación
cat("   - Proximidad a amenidades (colegios, centros comerciales)\n")
##    - Proximidad a amenidades (colegios, centros comerciales)
cat("   - Variables dummy para barrios específicos\n\n")
##    - Variables dummy para barrios específicos
cat("3. TRATAMIENTO DE OUTLIERS:\n")
## 3. TRATAMIENTO DE OUTLIERS:
cat("   - Investigar observaciones con alta distancia de Cook\n")
##    - Investigar observaciones con alta distancia de Cook
cat("   - Considerar modelos robustos si hay muchos outliers\n")
##    - Considerar modelos robustos si hay muchos outliers
cat("   - Validar datos de propiedades atípicas\n\n")
##    - Validar datos de propiedades atípicas
cat("4. VALIDACIÓN CRUZADA:\n")
## 4. VALIDACIÓN CRUZADA:
cat("   - Implementar k-fold cross-validation\n")
##    - Implementar k-fold cross-validation
cat("   - División hold-out para validación externa\n")
##    - División hold-out para validación externa
cat("   - Métricas de error predictivo (RMSE, MAE)\n")
##    - Métricas de error predictivo (RMSE, MAE)

4. Model Training

4.1 Identificación de Propiedades Candidatas

# 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%")
}
Top 10 Propiedades Candidatas para Vivienda 1
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

4.2 División train y test

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:
cat("Entrenamiento:", nrow(train_data), "observaciones\n")
## Entrenamiento: 202 observaciones
cat("Prueba:", nrow(test_data), "observaciones\n\n")
## 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"))
Distribución de Estratos en Conjuntos Train/Test
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

4.3 Re-entrenamiento y Validación

# 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"))
Métricas de Validación del Modelo
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

5. Model Evaluation

5.1 Evaluación Integral del Modelo

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

6. Recomendaciones y Conclusiones

6.1 Recomendaciones para Vivienda 1

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:

# Resumen final de recomendaciones
cat("RECOMENDACIONES PARA VIVIENDA 1 (ZONA NORTE)\n")
## RECOMENDACIONES PARA VIVIENDA 1 (ZONA NORTE)
cat(strrep("=", 50), "\n\n")
## ==================================================
cat("MODELO PREDICTIVO:\n")
## MODELO PREDICTIVO:
cat("- Precisión del modelo: R² =", round(summary(modelo_rlm)$r.squared, 3), "\n")
## - Precisión del modelo: R² = 0.556
cat("- Error promedio: ±", round(summary(modelo_rlm)$sigma, 1), "millones\n")
## - Error promedio: ± 122.4 millones
cat("- Precio predicho para especificaciones:", round(precio_predicho, 1), "millones\n\n")
## - Precio predicho para especificaciones: 310.4 millones
cat("ESTADO DEL PRESUPUESTO:\n")
## 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
cat("\nPROPIEDADES RECOMENDADAS:\n")
## 
## 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²
cat("\nFACTORES CLAVE DEL PRECIO:\n")
## 
## 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
cat("2. Número de baños: +", round(coef(modelo_rlm)["banios"], 1), "millones por baño adicional\n") 
## 2. Número de baños: + 21 millones por baño adicional
cat("3. Estrato: +", round(coef(modelo_rlm)["estrato_num"], 1), "millones por estrato superior\n")
## 3. Estrato: + 51.2 millones por estrato superior

PREGUNTA 7

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
cat("Apartamentos en zona sur real (post-corrección):", nrow(base2_sur_real), "\n")
## 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:
print(summary(datos_modelo_apt))
##     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.
# Conclusiones finales
cat("\n", strrep("=", 60), "\n")
## 
##  ============================================================
cat("RESUMEN COMPARATIVO FINAL\n")
## RESUMEN COMPARATIVO FINAL
cat(strrep("=", 60), "\n")
## ============================================================
cat("VIVIENDA 1 (Casa Norte):\n")
## VIVIENDA 1 (Casa Norte):
cat("- Precio estimado: 310.4 millones\n")
## - Precio estimado: 310.4 millones
cat("- Presupuesto: 350 millones\n")
## - Presupuesto: 350 millones
cat("- Estado: DENTRO DEL PRESUPUESTO (margen: 39.6M)\n\n")
## - Estado: DENTRO DEL PRESUPUESTO (margen: 39.6M)
cat("VIVIENDA 2 (Apartamento Sur):\n")
## 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:

  1. Ajuste presupuestal: Incrementar presupuesto a 1,050-1,100 millones

  2. Flexibilización de área: Considerar apartamentos 250-280m²

  3. Reducción de habitaciones: Aceptar 4 habitaciones en lugar de 5

  4. Alternativas de estrato: Evaluar opciones en Estrato 5 puro

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

Trabajo Futuro

Mejoras Metodológicas Identificadas

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:

  1. Identificación de observaciones problemáticas: Cruzar registros que cumplan simultáneamente dos condiciones:
    • is.na(parking_orig) (fueron imputados)
    • Alta distancia de Cook (observaciones influyentes identificadas: 27, 63, 78, 83, 110, 119, 146, 147, 175, 176, 234, 247)
  2. Análisis de sensibilidad: Desarrollar modelos alternativos excluyendo estas observaciones para evaluar si:
    • Mejoran las métricas de ajuste (R², AIC, BIC)
    • Se reducen las violaciones de supuestos (heterocedasticidad, normalidad)
    • Aumenta la estabilidad de los coeficientes
    • Se incrementa la precisión predictiva en validación cruzada
  3. Comparación de estrategias de imputación: Contrastar la imputación por moda actual con métodos alternativos:
    • Imputación por regresión múltiple
    • Métodos de imputación múltiple (MICE)
    • Eliminación completa de casos (listwise deletion)

Extensiones del Modelo

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

Validación Temporal

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.