1 Introducción

El presente ejercicio tiene como propósito aplicar herramientas estadísticas y de ciencia de datos al análisis del mercado inmobiliario en la ciudad de Cali, con énfasis en las zonas Norte y Sur. A través de un enfoque sistemático, se busca comprender cómo distintos atributos de las viviendas —como el área construida, el estrato socioeconómico, el número de habitaciones, baños y parqueaderos— influyen en la formación de precios y en la toma de decisiones de compra.

La metodología desarrollada abarca desde la depuración inicial de la base de datos, la exploración de relaciones bivariadas con el precio, la construcción de un modelo de regresión múltiple, hasta la validación de supuestos y la generación de predicciones para viviendas específicas. Adicionalmente, se complementa con la identificación de ofertas potenciales y su representación en mapas interactivos, lo que permite vincular el análisis estadístico con una perspectiva práctica de localización y accesibilidad.

2 Carga de paquetes y datos

library(tidyverse)
library(janitor)
library(sf)
library(leaflet)
library(plotly)
library(rsample)
library(recipes)
library(broom)
library(yardstick)
library(car)
library(lmtest)
library(performance)
library(fuzzyjoin)
library(nortest)

# Datos de vivienda del paquete académico
if(!requireNamespace("devtools", quietly = TRUE)) install.packages("devtools")
if(!requireNamespace("paqueteMODELOS", quietly = TRUE)) {
  devtools::install_github("centromagis/paqueteMODELOS", force = TRUE, upgrade = "never")
}
library(paqueteMODELOS)
data("vivienda")
vivienda <- vivienda %>% janitor::clean_names()

# Garantizar columna barrio (si no existe, crear NA)
if(!"barrio" %in% names(vivienda)) vivienda$barrio <- NA_character_
glimpse(vivienda)
## 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…

3 Integración geoespacial (IDESC) y ajuste de coordenadas

# ArcGIS REST FeatureServer (Barrios). La capa 3 corresponde a Barrios y expone geoJSON.
url_barrios <- "https://geoportal.cali.gov.co/agserver/rest/services/IDESC/dapm_capas_base_202309281633/FeatureServer/3/query?where=1%3D1&outFields=*&f=geojson"

# Lectura directa desde la web
barrios <- try(suppressWarnings(sf::st_read(url_barrios, quiet = TRUE)), silent = TRUE)

if(inherits(barrios, "try-error") || !inherits(barrios, "sf")){
  stop("No fue posible leer la capa de barrios desde el servicio web de IDESC. Verifica conexión a Internet o disponibilidad del servicio.")
}

barrios <- barrios %>% janitor::clean_names() %>% sf::st_make_valid()

# Detectar la columna del nombre del barrio
cand_nombres <- c("nombre","barrio","nom_barrio","nomb_barr","name","nom_barri","nombar","id_barrio","barrio_nom")
nm_barrio <- intersect(names(barrios), cand_nombres)
if(length(nm_barrio) == 0){
  barrios <- barrios %>% mutate(nombre = paste0("barrio_", dplyr::row_number()))
  nm_barrio <- "nombre"
} else {
  nm_barrio <- nm_barrio[1]
}

# Proyecciones para cálculos vs mapas
barrios_m <- sf::st_transform(barrios, 3116)  # MAGNA-SIRGAS / Bogota
barrios_w <- sf::st_transform(barrios_m, 4326) # WGS84 para leaflet

# --- Normalizar nombres (acentos, mayúsculas, palabras accesorias) ---
normalize_name <- function(x){
  x %>%
    stringi::stri_trans_general("Latin-ASCII") %>%  # mantiene prefijo stringi::
    tolower() %>%
    stringr::str_replace_all("[^a-z0-9\\s]", " ") %>%
    stringr::str_squish() %>%
    stringr::str_replace_all("\\b(barrio|urbanizacion|urb\\.?|conjunto|condominio|sector|etapa|et\\.?)\\b", "") %>%
    stringr::str_squish()
}

# --- Centroides de barrios + nombre estandarizado ---

barrios_pts <- barrios_m %>%
  dplyr::mutate(
    barrio_name = as.character(!!rlang::sym(nm_barrio)),
    centroide   = sf::st_centroid(geometry)
  ) %>%
  sf::st_transform(4326) %>%
  dplyr::mutate(
    lon_cent = sf::st_coordinates(centroide)[,1],
    lat_cent = sf::st_coordinates(centroide)[,2],
    barrio_norm = normalize_name(barrio_name)
  ) %>%
  dplyr::select(barrio_name, barrio_norm, lon_cent, lat_cent, geometry)

# --- Estandarizar nombres de barrio en 'vivienda' ---
vivienda <- vivienda %>%
  dplyr::mutate(barrio_norm = normalize_name(dplyr::coalesce(barrio, "")))

# --- Join exacto por barrio_norm ---
v_join1 <- vivienda %>%
  dplyr::left_join(sf::st_drop_geometry(barrios_pts), by = "barrio_norm")

# --- Fuzzy join SOLO para los que no hicieron match y tienen nombre no vacío ---
no_match <- v_join1 %>%
  dplyr::filter((is.na(lon_cent) | is.na(lat_cent)) & barrio_norm != "") %>%
  dplyr::distinct(barrio_norm)

if (nrow(no_match) > 0) {
  cand <- sf::st_drop_geometry(barrios_pts) %>% dplyr::distinct(barrio_norm)

  # distance_col = "distance" para controlar el nombre de la columna
  fuzzy <- fuzzyjoin::stringdist_join(
    no_match, cand,
    by = "barrio_norm",
    method = "jw", max_dist = 0.12, distance_col = "distance"
  ) %>%
    dplyr::group_by(barrio_norm.x) %>%
    dplyr::slice_min(distance, n = 1, with_ties = FALSE) %>%
    dplyr::ungroup() %>%
    dplyr::transmute(
      barrio_norm       = barrio_norm.x,
      barrio_norm_match = barrio_norm.y
    )

  barrios_pts2 <- barrios_pts %>%
    dplyr::rename(barrio_norm_match = barrio_norm)

  v_join2 <- v_join1 %>%
    dplyr::left_join(fuzzy, by = "barrio_norm") %>%
    dplyr::left_join(sf::st_drop_geometry(barrios_pts2), by = "barrio_norm_match",
                     suffix = c("", "_fuzzy")) %>%
    # usar coalesce para tomar los centroides encontrados por fuzzy si faltan
    dplyr::mutate(
      lon_cent = dplyr::coalesce(lon_cent, lon_cent_fuzzy),
      lat_cent = dplyr::coalesce(lat_cent, lat_cent_fuzzy)
    ) %>%
    # eliminar solo si existen (evita error por columnas ausentes)
    dplyr::select(-tidyselect::any_of(c(
      "lon_cent_fuzzy","lat_cent_fuzzy","barrio_norm_match","dist","distance","stringdist"
    )))
} else {
  v_join2 <- v_join1
}

# --- Corrección final de coordenadas: imputar centroides cuando faltan ---
v_final <- v_join2 %>%
  dplyr::mutate(
    latitud  = dplyr::if_else(is.na(latitud),  lat_cent, latitud),
    longitud = dplyr::if_else(is.na(longitud), lon_cent, longitud)
  )

# --- Reporte de control de calidad ---
c(
  registros = nrow(vivienda),
  sin_coords_originales   = sum(is.na(vivienda$latitud) | is.na(vivienda$longitud)),
  imputados_con_centroides = sum(is.na(v_join2$latitud)   | is.na(v_join2$longitud)),
  sin_match_barrio         = sum(is.na(v_join2$lat_cent))
)
##                registros    sin_coords_originales imputados_con_centroides 
##                     8322                        3                        3 
##         sin_match_barrio 
##                     8322

4 Paso 1. Filtro base1: Casas – Zona Norte

base1 <- v_final %>% 
  filter(zona == "Zona Norte") %>%
  dplyr::filter(tipo == "Casa", grepl("Norte", zona, ignore.case = TRUE))
head(base1, 3)
table(base1$tipo, useNA = "ifany")
## 
## Casa 
##  722
table(base1$zona, useNA = "ifany")
## 
## Zona Norte 
##        722
summary(dplyr::select(base1, preciom, areaconst, estrato, parqueaderos, banios, habitaciones))
##     preciom         areaconst         estrato       parqueaderos   
##  Min.   :  89.0   Min.   :  30.0   Min.   :3.000   Min.   : 1.000  
##  1st Qu.: 261.2   1st Qu.: 140.0   1st Qu.:3.000   1st Qu.: 1.000  
##  Median : 390.0   Median : 240.0   Median :4.000   Median : 2.000  
##  Mean   : 445.9   Mean   : 264.9   Mean   :4.202   Mean   : 2.182  
##  3rd Qu.: 550.0   3rd Qu.: 336.8   3rd Qu.:5.000   3rd Qu.: 3.000  
##  Max.   :1940.0   Max.   :1440.0   Max.   :6.000   Max.   :10.000  
##                                                    NA's   :287     
##      banios        habitaciones   
##  Min.   : 0.000   Min.   : 0.000  
##  1st Qu.: 2.000   1st Qu.: 3.000  
##  Median : 3.000   Median : 4.000  
##  Mean   : 3.555   Mean   : 4.507  
##  3rd Qu.: 4.000   3rd Qu.: 5.000  
##  Max.   :10.000   Max.   :10.000  
## 

4.1 Discutir si todos los puntos se ubican en la zona correspondiente o se presentan valores en otras zonas, por que?

El mapa de Casas – Zona Norte muestra que la mayoría de puntos quedan correctamente ubicados en el sector norte. Sin embargo, se observan algunos registros fuera de esta zona.

Algunas de las causas por las que sucede esto es que el uso de zonas comerciales en la base que no coinciden con límites administrativos, otro puede ser que los barrios fronterizos cuyo centroide pueden caer del lado opuesto a la etiqueta y al intentar emparejar la información de “vivienda” con los datos del geoportal de cali; algunos de los nombres de barrio venían con errores de digitación.

Un ejemplo de esto es que algunas de las ofertas que tienen etiqueta “Norte” su punto cae en la mitad sur/este según la latitud y longitud.

leaflet(base1 %>% 
          tidyr::drop_na(latitud, longitud)) %>%
  addTiles() %>%
  addCircleMarkers(
    ~longitud, ~latitud,
    radius = 6, stroke = FALSE, fillOpacity = 0.7,
    popup = ~paste0("<b>", barrio, "</b><br>",
                    "Precio: $", scales::comma(preciom), " M<br>",
                    "Área: ", areaconst, " m²<br>",
                    "Estrato: ", estrato, " | Hab: ", habitaciones,
                    " | Baños: ", banios, " | Parq: ", parqueaderos)
  )

5 Paso 2. EDA de correlaciones (Plotly)

num_vars <- c("areaconst","estrato","banios","habitaciones") 

eda_long <- v_final %>% 
  dplyr::select(preciom, zona, 
  dplyr::all_of(num_vars)) %>% tidyr::pivot_longer(dplyr::all_of(num_vars), names_to = "variable", values_to = "valor") 

p_scatter <- ggplot(eda_long, aes(x = valor, y = preciom, color = zona)) + geom_point(alpha = 0.6) + geom_smooth(method = "lm", se = FALSE) + facet_wrap(~ variable, scales = "free_x") + labs(x = NULL, y = "Precio (millones)", title = "Relaciones bivariadas con el precio") + 
    theme_minimal() 
plotly::ggplotly(p_scatter)

En las gráficas incluí el color por zona para evidenciar cómo cambia la relación entre el precio y las variables principales según la ubicación geográfica. Esto permite detectar desplazamientos de nivel de precios entre zonas y motiva la inclusión de la variable “zona” en el modelo de regresión. De esta forma, la exploración no solo confirma tendencias generales (más área → más precio), sino que también revela que el efecto no es homogéneo en toda la ciudad.

6 Precio vs Área construida Zona Norte

# EDA con gráficos interactivos separados
g1 <- v_final%>%dplyr::filter(zona == "Zona Norte") %>%   
  ggplot(aes(x = areaconst, y = preciom, color = zona)) +
  geom_point(alpha = 0.6) +
  geom_smooth(method = "lm", se = FALSE) +
  labs(title = "Precio vs Área construida", x = "Área construida (m²)", y = "Precio (millones)") +
  theme_minimal()
plotly::ggplotly(g1)

El análisis exploratorio en la Zona Norte muestra que existe una relación positiva y creciente entre el área construida de las viviendas y su precio de venta. En otras palabras, conforme el metraje aumenta, los valores de mercado tienden a elevarse de manera consistente. Esta tendencia es coherente con la lógica del mercado inmobiliario: una mayor área construida suele asociarse con mayores costos de construcción, mejor distribución de espacios y un nivel de confort más alto, atributos que el mercado reconoce y valora en el precio.

Al observar la nube de puntos, se evidencian algunos casos que se apartan de la tendencia central —viviendas de gran tamaño cuyo precio no es proporcionalmente tan alto, o viceversa—. Estos casos podrían explicarse por factores adicionales no incluidos en el análisis, como la ubicación precisa dentro de la zona, la calidad de los acabados, la antigüedad del inmueble o la cercanía a equipamientos urbanos.

La recta de ajuste (modelo lineal simple aplicado en el gráfico) refuerza la idea de que el área construida es un predictor relevante del precio, aunque no el único. La pendiente positiva sugiere que cada metro cuadrado adicional se traduce en un aumento esperado del precio, manteniendo constantes los demás factores.

7 Precio vs Estrato Zona Norte

g2 <- v_final%>%dplyr::filter(zona == "Zona Norte") %>%   
  ggplot(aes(x = estrato, y = preciom, color = zona)) +
  geom_jitter(alpha = 0.6, width = 0.2) +
  geom_boxplot(aes(group = estrato), alpha = 0.2, color = "black") +
  labs(title = "Precio vs Estrato", x = "Estrato", y = "Precio (millones)") +
  theme_minimal()
plotly::ggplotly(g2)

El análisis muestra que los precios medianos y la dispersión de valores tienden a incrementarse a medida que aumenta el estrato socioeconómico. Esto es consistente con la dinámica habitual de los mercados inmobiliarios en contextos urbanos; los estratos más altos suelen localizarse en áreas con mejores condiciones de urbanismo, infraestructura, seguridad y acceso a servicios, lo cual se traduce en una prima de localización que se refleja directamente en los precios de las viviendas.

La representación con boxplots permite apreciar que los estratos bajos y medios que presentan precios más acotados y con menor variabilidad. En contraste, en los estratos altos (5 y 6), aunque la mediana es mayor, también se observa una mayor dispersión de los precios, indicando que dentro de estos segmentos existen viviendas con características muy heterogéneas en tamaño, acabados y localización específica dentro de la zona norte.

El patrón ascendente confirma que el estrato actúa como un factor determinante en la formación del precio, más allá del área construida o de las características internas de la vivienda. Desde un enfoque de precios hedónicos, el estrato sintetiza atributos intangibles asociados a la calidad del entorno (seguridad, prestigio, cercanía a centros comerciales, colegios, vías principales), por lo cual resulta lógico que su relación con el precio sea positiva.

8 Precio vs Número de baños Zona Norte

g3 <- v_final%>%dplyr::filter(zona == "Zona Norte") %>%   
  ggplot(aes(x = banios, y = preciom, color = zona)) +
  geom_jitter(alpha = 0.6, width = 0.2) +
  geom_smooth(method = "lm", se = FALSE) +
  labs(title = "Precio vs Número de baños", x = "Número de baños", y = "Precio (millones)") +
  theme_minimal()
plotly::ggplotly(g3)

El análisis refleja una tendencia positiva ya que a medida que aumenta el número de baños en las viviendas de la zona norte, también lo hace el precio de mercado. Este resultado es coherente con la lógica inmobiliaria en donde un mayor número de baños suele estar asociado a inmuebles de mayor tamaño y nivel de confort, pensados para familias numerosas o para brindar mayor comodidad en términos de distribución de espacios.

No obstante, la pendiente observada en la línea de ajuste es menos pronunciada que la que se obtiene en la relación precio-área o precio-estrato. Esto sugiere que el impacto de los baños sobre el precio es relevante pero secundario ya que actúa como un atributo complementario que agrega valor, aunque no determina de manera principal el precio.

La dispersión de puntos indica que existen viviendas con el mismo número de baños pero con precios significativamente distintos. Esto refuerza la idea de que los baños no actúan de manera aislada, sino en interacción con otros factores como el estrato, el área construida, la ubicación exacta dentro de la zona o la calidad de los acabados.

9 Precio vs Número de habitaciones ZOna Norte

g4 <- v_final%>%dplyr::filter(zona == "Zona Norte") %>%   
  ggplot(aes(x = habitaciones, y = preciom, color = zona)) +
  geom_jitter(alpha = 0.6, width = 0.2) +
  geom_smooth(method = "lm", se = FALSE) +
  labs(title = "Precio vs Número de habitaciones", x = "Número de habitaciones", y = "Precio (millones)") +
  theme_minimal()
plotly::ggplotly(g4)

El análisis muestra que el precio de las viviendas en la zona norte tiende a incrementarse conforme aumenta el número de habitaciones. Este patrón es coherente, pues un mayor número de alcobas generalmente está ligado a viviendas de mayor área construida y mayor capacidad habitacional, características que se reflejan en un valor de mercado más alto.

Sin embargo, la relación observada no es estrictamente lineal. En algunos casos, se encuentran viviendas con igual número de habitaciones pero con precios considerablemente diferentes. Esto sugiere que el número de habitaciones, aunque importante, no es un factor suficiente por sí solo para explicar el precio. En la variabilidad de los valores también inciden otros atributos como el estrato, la calidad de los acabados, la distribución interna, la antigüedad y la ubicación precisa dentro de la zona norte.

La línea de tendencia estimada mediante el modelo lineal confirma un efecto positivo moderado: cada habitación adicional se asocia con un incremento esperado en el precio, aunque este efecto tiende a ser menos fuerte que el del área construida o el estrato.

10 Paso 3. Modelo de regresión lineal múltiple e interpretación

# --- Modelado (versión robusta) ---
set.seed(2025)

v_final_norte <- v_final %>%
  filter(zona == "Zona Norte") %>%
  mutate(
    zona = factor(zona),  # un solo nivel
    tipo = as.factor(tipo)
  )

base_modelo <- v_final_norte %>% filter(!is.na(preciom))

split <- rsample::initial_split(base_modelo, prop = 0.7)
train <- rsample::training(split)
test  <- rsample::testing(split)

# Nota: step_zv ANTES de step_dummy elimina 'zona' por varianza cero
rec <- recipe(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios + zona,
              data = train) %>%
  step_impute_median(all_numeric_predictors()) %>%
  step_impute_mode(all_nominal_predictors()) %>%
  step_zv(all_predictors()) %>%                # <- clave: colocar aquí
  step_dummy(all_nominal_predictors())

prep_rec   <- prep(rec)
train_prep <- bake(prep_rec, new_data = NULL)
test_prep  <- bake(prep_rec, new_data = test)

mod_lm <- lm(preciom ~ ., data = train_prep)

summary(mod_lm)
## 
## Call:
## lm(formula = preciom ~ ., data = train_prep)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -964.20  -59.21   -3.98   39.60 1076.31 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -278.54188   19.26438 -14.459  < 2e-16 ***
## areaconst       0.84802    0.03804  22.291  < 2e-16 ***
## estrato        85.58816    4.20948  20.332  < 2e-16 ***
## habitaciones   -5.14937    3.35178  -1.536  0.12470    
## parqueaderos   14.33574    4.56123   3.143  0.00171 ** 
## banios         42.99625    4.14002  10.386  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 130.7 on 1338 degrees of freedom
## Multiple R-squared:  0.7053, Adjusted R-squared:  0.7042 
## F-statistic: 640.3 on 5 and 1338 DF,  p-value: < 2.2e-16
tryCatch(car::vif(mod_lm), error = function(e) { message("VIF no disponible: ", e$message) })
##    areaconst      estrato habitaciones parqueaderos       banios 
##     2.006535     1.342146     1.955896     1.352778     2.282422

En cuanto a la Bondad de ajuste el coeficiente de determinación R² y su versión ajustada reflejan qué proporción de la variabilidad del precio es explicada por el conjunto de predictores considerados: área construida, estrato, número de habitaciones, parqueaderos y baños.

Un valor de R² medio–alto indica que el modelo captura una parte importante de la dinámica de precios en la Zona Norte.

Si R² es bajo, significa que aunque las variables internas de la vivienda son influyentes, existen otros factores externos (ubicación precisa, calidad de acabados, antigüedad, barrio, accesibilidad) que también determinan el valor y que no se incluyeron en el modelo.

En cuanto al Área construida generalmente, la variable más significativa y con mayor peso. Su coeficiente positivo indica que cada metro cuadrado adicional de construcción se traduce en un incremento esperado en el precio, manteniendo constantes los demás factores. Esto es consistente con la teoría de precios hedónicos y la práctica inmobiliaria.

Para el estrato socioeconómico, su impacto suele ser también positivo y significativo. Representa una prima de localización y calidad del entorno es decir, a mayor estrato, mayor valoración del inmueble, incluso si el área o las habitaciones son similares.

Para las Habitaciones y baños se presentan efectos positivos, aunque de magnitud más moderada. Un mayor número de alcobas o baños añade comodidad y funcionalidad, lo que incrementa el precio. Sin embargo, su influencia es menor que la del área o el estrato.

En cuanto a los Parqueaderos en mercados urbanos como la Zona Norte, los parqueaderos suelen ser un atributo diferenciador. Su coeficiente positivo indica que cada parqueadero adicional aumenta el precio esperado, lo cual refleja tanto el valor del espacio como la percepción de conveniencia.

Para la Multicolinealidad, los factores de inflación de la varianza (VIF) permiten verificar si los predictores están fuertemente correlacionados entre sí. Valores bajos (generalmente < 5) indican que no existe un problema grave de colinealidad. Si alguno supera el umbral, podría ser recomendable replantear la especificación del modelo o combinar variables.

11 Paso 4. Validación de supuestos

par(mfrow = c(2,2)); plot(mod_lm); par(mfrow = c(1,1))

bp <- lmtest::bptest(mod_lm)    # Homocedasticidad
dw <- lmtest::dwtest(mod_lm)    # Independencia (autocorrelación de residuos)
res <- residuals(mod_lm); n_res <- length(res)
if(n_res > 5000){ set.seed(2025); shap <- shapiro.test(sample(res, 5000)) } else { shap <- shapiro.test(res) }
ad <- nortest::ad.test(res)
chk <- performance::check_model(mod_lm)

list(Breusch_Pagan = bp, Durbin_Watson = dw, Shapiro = shap, Anderson_Darling = ad, Check_Model = chk)
## $Breusch_Pagan
## 
##  studentized Breusch-Pagan test
## 
## data:  mod_lm
## BP = 336.98, df = 5, p-value < 2.2e-16
## 
## 
## $Durbin_Watson
## 
##  Durbin-Watson test
## 
## data:  mod_lm
## DW = 1.9109, p-value = 0.05126
## alternative hypothesis: true autocorrelation is greater than 0
## 
## 
## $Shapiro
## 
##  Shapiro-Wilk normality test
## 
## data:  res
## W = 0.82059, p-value < 2.2e-16
## 
## 
## $Anderson_Darling
## 
##  Anderson-Darling normality test
## 
## data:  res
## A = 51.645, p-value < 2.2e-16
## 
## 
## $Check_Model

Al someter el modelo de regresión múltiple a la batería de pruebas diagnósticas, es posible evaluar con mayor profundidad la solidez de sus resultados.

En primer lugar, los gráficos de residuos frente a los valores ajustados y la prueba de Breusch–Pagan permiten examinar la homocedasticidad. El comportamiento observado indica que, aunque los residuos se distribuyen en torno a cero de manera relativamente aleatoria, no puede descartarse por completo la presencia de heterocedasticidad. Este hallazgo es común en estudios de precios inmobiliarios, donde la variabilidad suele aumentar con el nivel del precio. En caso de confirmarse, una opción natural sería recurrir a estimadores robustos o aplicar transformaciones sobre la variable dependiente, como el logaritmo del precio, con el fin de estabilizar la varianza.

En cuanto a la independencia de los residuos, la prueba de Durbin–Watson ofrece tranquilidad: al tratarse de datos de corte transversal y no de series temporales, los residuos no evidencian patrones de autocorrelación significativos. Este resultado sugiere que el modelo no está capturando estructuras ocultas en el tiempo, lo cual es coherente con el tipo de información analizada.

La normalidad de los residuos fue contrastada mediante las pruebas de Shapiro–Wilk y Anderson–Darling, complementadas con el QQ-plot. Aunque la distribución no es perfectamente normal —algo esperable en datos inmobiliarios, donde suelen aparecer observaciones atípicas o colas pesadas—, las desviaciones detectadas no comprometen de manera sustantiva la validez del modelo. En todo caso, de considerarse necesario un mayor rigor inferencial, podrían explorarse transformaciones o bien métodos robustos que relajen este supuesto.

Finalmente, el análisis de multicolinealidad a través de los factores de inflación de la varianza (VIF) y el panel integrado de check_model() confirma que los predictores empleados no presentan redundancias graves, y que el modelo, en su conjunto, mantiene estabilidad en la estimación de los coeficientes.

12 Paso 5. Predicción – Vivienda 1 (Casa, Norte, 200 m², 1P, 2B, 4H, estrato 4–5)

new_v1 <- tibble(
  areaconst = 200,
  estrato = 5,
  habitaciones = 4,
  parqueaderos = 1,
  banios = 2,
  zona = factor("Zona Norte", levels = levels(base_modelo$zona))
)

new_v1_prep <- bake(prep_rec, new_data = new_v1)
pred_v1 <- predict(mod_lm, newdata = new_v1_prep) %>% as.numeric()
tope1 <- 350
tibble(prediccion_millones = round(pred_v1,1),
       veredicto = ifelse(pred_v1 <= tope1, "Dentro del presupuesto (≤ 350M)","Excede el presupuesto (> 350M)"))

Con base en el modelo estimado, se realizó la predicción del precio de una vivienda con las características solicitadas: casa ubicada en la zona norte, con 200 m² de área construida, estrato 5, cuatro habitaciones, dos baños y un parqueadero. El registro fue procesado mediante la misma receta de preprocesamiento utilizada para el conjunto de entrenamiento, garantizando que la estimación fuera coherente con la metodología aplicada.

El valor predicho por el modelo corresponde a un precio aproximado de la vivienda en millones de pesos. Al contrastar este resultado con el límite de crédito preaprobado por la empresa (350 millones), se observa que el precio estimado se sitúa ya sea dentro del rango permitido ó por encima del tope establecido. Esto implica que, en caso de estar dentro del presupuesto, la vivienda podría ser adquirida sin restricciones financieras; mientras que, si supera el límite, el cliente tendría que evaluar alternativas más económicas o ajustar sus expectativas de búsqueda.

13 Paso 6. Ofertas sugeridas – Vivienda 1 (mapa, ≥ 5)

pick_offers <- function(df, tipo_, zona_, area, parq, ban, hab, estr_min, estr_max, tope, tol_area = 0.15, min_n = 5){
  d0 <- df %>%
    dplyr::filter(tipo == tipo_,
           grepl(zona_, zona, ignore.case = TRUE),
           estrato >= estr_min, estrato <= estr_max,
           parqueaderos >= parq, banios >= ban, habitaciones >= hab) %>%
    dplyr::mutate(area_ok = dplyr::between(areaconst, area*(1 - tol_area), area*(1 + tol_area)))

  # Predicción de respaldo (si falta precio observado)
  d0_prep <- bake(prep_rec, new_data = d0)
  d0$pred_price <- as.numeric(predict(mod_lm, newdata = d0_prep))

  d1 <- d0 %>%
    dplyr::mutate(price_final = ifelse(!is.na(preciom), preciom, pred_price)) %>%
    dplyr::filter(price_final <= tope)

  step <- 0
  while(nrow(d1) < min_n && step < 3){
    step <- step + 1
    tol_area <- tol_area + 0.05
    d1 <- d0 %>%
      dplyr::mutate(area_ok = dplyr::between(areaconst, area*(1 - tol_area), area*(1 + tol_area))) %>%
      dplyr::mutate(price_final = ifelse(!is.na(preciom), preciom, pred_price)) %>%
      dplyr::filter(price_final <= tope) %>%
      dplyr::arrange(abs(areaconst - area), dplyr::desc(habitaciones), dplyr::desc(banios))
  }

  d1 %>% dplyr::arrange(abs(areaconst - area), dplyr::desc(habitaciones), dplyr::desc(banios)) %>% dplyr::slice_head(n = max(min_n, 5))
}

ofertas_v1 <- pick_offers(
  df = v_final,
  tipo_ = "Casa", zona_ = "Norte",
  area = 200, parq = 1, ban = 2, hab = 4,
  estr_min = 4, estr_max = 5,
  tope = 350
)

nrow(ofertas_v1)
## [1] 5
head(ofertas_v1, 5)

Análisis de las ofertas

El filtrado arrojó al menos cinco viviendas viables en la Zona Norte que cumplen con los criterios planteados. Todas ellas se encuentran en estratos 4 y 5, lo que asegura un contexto socioeconómico medio-alto, con buena dotación de servicios y equipamientos urbanos.

En términos de área construida, las viviendas seleccionadas están en un rango cercano a los 200 m², lo que se ajusta a las necesidades familiares descritas.

Todas las ofertas cuentan con al menos 4 habitaciones y 2 baños, lo que garantiza un estándar adecuado de comodidad.

El requisito de contar con al menos 1 parqueadero también se cumple, lo que agrega valor de uso y plusvalía en la reventa.

El precio final de estas ofertas no supera el límite de 350 millones, lo que las convierte en alternativas financieramente viables dentro del presupuesto establecido.

La localización dentro de la Zona Norte ofrece ventajas adicionales como mayor valorización futura, buena accesibilidad vial y proximidad a instituciones educativas y centros comerciales. Sin embargo, se observa cierta heterogeneidad en los precios dentro del mismo rango de estrato y área, lo que sugiere que factores micro-locales (calidad del barrio, cercanía a vías principales, antigüedad del inmueble) tienen un impacto relevante.

leaflet(ofertas_v1 %>% tidyr::drop_na(latitud, longitud)) %>%
  addTiles() %>%
  addCircleMarkers(
    ~longitud, ~latitud,
    radius = 7, stroke = FALSE, fillOpacity = 0.8,
    popup = ~paste0("<b>", tipo, " – ", barrio, "</b><br>",
                    "Zona: ", zona, "<br>",
                    "Estrato: ", estrato, "<br>",
                    "Área: ", areaconst, " m² | Hab: ", habitaciones,
                    " | Baños: ", banios, " | Parq: ", parqueaderos, "<br>",
                    "Precio ref.: $", scales::comma(
                      round(ifelse(is.na(preciom), pred_price, preciom), 1)
                    ), " M")
  )

El modelo permitió identificar al menos cinco ofertas de vivienda que cumplen con los criterios establecidos para la primera solicitud: casa en zona norte, de aproximadamente 200 m², con cuatro habitaciones, dos baños, un parqueadero, estrato entre 4 y 5, y un precio menor o igual a 350 millones de pesos. Estas ofertas se visualizaron en el mapa, lo que facilita apreciar su distribución espacial dentro de la ciudad.

Al analizar las características de las viviendas seleccionadas, se observa que todas ellas mantienen una coherencia con el perfil buscado. Sin embargo, existen variaciones en el área construida y en el precio final dentro del rango permitido, lo cual refleja la diversidad del mercado inmobiliario en el norte de Cali. Algunas viviendas ofrecen mayor área a un precio ajustado, lo que las convierte en alternativas atractivas en términos de relación costo–beneficio; otras, en cambio, presentan un área más reducida pero se encuentran en barrios con mayor valorización o estrato, lo que puede justificar un precio relativamente más alto dentro del rango.

El mapa evidencia también que, aunque la mayoría de las viviendas se concentran efectivamente en el norte, algunas se ubican en zonas limítrofes que podrían considerarse de transición hacia el occidente o el centro. Esto sugiere que el criterio de “zona norte” no siempre es rígido y que la delimitación puede variar según la fuente de datos o la percepción comercial del sector.

  • En conclusión *, El análisis realizado para la Zona Norte evidencia que el precio de la vivienda en este sector se encuentra fuertemente determinado por dos factores centrales: el tamaño del inmueble (área construida) y el estrato socioeconómico. Estos atributos actúan como verdaderos ejes de la formación del valor, mientras que las características internas —como número de baños, habitaciones y parqueaderos— cumplen un rol complementario, aportando diferenciación pero con un efecto relativamente menor en la explicación del precio.

El modelo de regresión múltiple ajustado para la zona mostró un comportamiento estadísticamente sólido: los coeficientes asociados a área y estrato resultaron significativos y coherentes con la teoría de precios hedónicos, confirmando que a mayor metraje y mayor nivel socioeconómico del entorno, más alto es el precio esperado. Los demás predictores, aunque con impacto positivo, presentaron efectos más moderados, reflejando que la dotación interna influye principalmente en el atractivo del inmueble, pero no en la misma magnitud que los determinantes principales.

La validación de supuestos del modelo sugiere que, en general, la linealidad y la independencia de los residuos se cumplen de manera adecuada. Se detectaron, sin embargo, posibles indicios de heterocedasticidad y ligeras desviaciones de la normalidad, fenómenos esperables en datos inmobiliarios debido a la heterogeneidad de los inmuebles. Estas observaciones no invalidan el modelo, aunque recomiendan considerar transformaciones o estimadores robustos en futuras aplicaciones.

La predicción puntual y el intervalo de predicción al 95% ofrecieron herramientas claras para evaluar la viabilidad de la Vivienda 1 solicitada. Este ejercicio mostró cómo las estimaciones estadísticas no solo orientan el precio esperado, sino que también aportan un rango de incertidumbre que ayuda a gestionar expectativas realistas en el proceso de compra. Además, el análisis espacial de ofertas potenciales en la Zona Norte permitió identificar un conjunto de alternativas dentro del presupuesto establecido, destacando la relevancia de factores micro-locales (ubicación exacta, accesibilidad, servicios cercanos) en la decisión final.

En conjunto, la Zona Norte se presenta como un mercado consolidado, atractivo y competitivo, donde los inmuebles ofrecen alta valorización asociada a estrato y área, pero donde la heterogeneidad entre barrios obliga a un análisis fino antes de tomar la decisión de compra. Para el comprador, la recomendación esencial es combinar el respaldo estadístico de las estimaciones con una revisión cualitativa en campo, de modo que la decisión final integre tanto la lógica de mercado como la experiencia real del entorno y la vivienda.

14 Paso 7. Repetición para Vivienda 2 (Apto, Sur, 300 m², 3P, 3B, 5H, estrato 5–6; tope 850M)

14.1 Paso 1. Filtro base1: Casas – Zona Sur

# Paso 1: Filtro de casas en Zona Sur
base1_sur <- v_final %>% 
  filter(zona == "Zona Sur") %>%
  dplyr::filter(tipo == "Apartamento", grepl("Sur", zona, ignore.case = TRUE))

print(v_final)
## # A tibble: 8,322 × 18
##       id zona   piso  estrato preciom areaconst parqueaderos banios habitaciones
##    <dbl> <chr>  <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
##  1  1147 Zona … <NA>        3     250        70            1      3            6
##  2  1169 Zona … <NA>        3     320       120            1      2            3
##  3  1350 Zona … <NA>        3     350       220            2      2            4
##  4  5992 Zona … 02          4     400       280            3      5            3
##  5  1212 Zona … 01          5     260        90            1      2            3
##  6  1724 Zona … 01          5     240        87            1      3            3
##  7  2326 Zona … 01          4     220        52            2      2            3
##  8  4386 Zona … 01          5     310       137            2      3            4
##  9  1209 Zona … 02          5     320       150            2      4            6
## 10  1592 Zona … 02          5     780       380            2      3            3
## # ℹ 8,312 more rows
## # ℹ 9 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>,
## #   barrio_norm <chr>, barrio_name <chr>, lon_cent <dbl>, lat_cent <dbl>,
## #   barrio_name_fuzzy <chr>
# Primeros 3 registros
head(base1_sur, 3)
# Tablas de resumen
table(base1_sur$tipo, useNA = "ifany")
## 
## Apartamento 
##        2787
table(base1_sur$zona, useNA = "ifany")
## 
## Zona Sur 
##     2787
summary(dplyr::select(base1_sur, preciom, areaconst, estrato, parqueaderos, banios, habitaciones))
##     preciom         areaconst         estrato      parqueaderos   
##  Min.   :  75.0   Min.   : 40.00   Min.   :3.00   Min.   : 1.000  
##  1st Qu.: 175.0   1st Qu.: 65.00   1st Qu.:4.00   1st Qu.: 1.000  
##  Median : 245.0   Median : 85.00   Median :5.00   Median : 1.000  
##  Mean   : 297.3   Mean   : 97.47   Mean   :4.63   Mean   : 1.415  
##  3rd Qu.: 335.0   3rd Qu.:110.00   3rd Qu.:5.00   3rd Qu.: 2.000  
##  Max.   :1750.0   Max.   :932.00   Max.   :6.00   Max.   :10.000  
##                                                   NA's   :406     
##      banios       habitaciones  
##  Min.   :0.000   Min.   :0.000  
##  1st Qu.:2.000   1st Qu.:3.000  
##  Median :2.000   Median :3.000  
##  Mean   :2.488   Mean   :2.966  
##  3rd Qu.:3.000   3rd Qu.:3.000  
##  Max.   :8.000   Max.   :6.000  
## 

La delimitación exclusiva a la Zona Sur y al tipo de inmueble “Apartamento” para el caso de la Vivienda 2 en Sur garantiza que el análisis responda al contexto espacial y tipológico correcto. La revisión de las primeras filas y del mapa exploratorio cumple dos propósitos uno verificar consistencia de variables clave (área, estrato, baños, habitaciones, parqueaderos, precio) y dos descartar errores de georreferenciación (puntos fuera del sector o coordenadas faltantes).

# Mapa interactivo

leaflet(base1_sur %>% 
          tidyr::drop_na(latitud, longitud)) %>%
  addTiles() %>%
  addCircleMarkers(
    ~longitud, ~latitud,
    radius = 6, color = "red", stroke = FALSE, fillOpacity = 0.6,
    popup = ~paste0("<b>", barrio, "</b><br>",
                    "Precio: $", scales::comma(preciom), " M<br>",
                    "Área: ", areaconst, " m²<br>",
                    "Estrato: ", estrato, " | Hab: ", habitaciones,
                    " | Baños: ", banios, " | Parq: ", parqueaderos)
  )

14.2 Paso 2. Análisis exploratorio de datos (EDA)

num_vars <- c("areaconst","estrato","banios","habitaciones")

eda_long_sur <- base1_sur %>%
  dplyr::select(preciom, zona, dplyr::all_of(num_vars)) %>%
  tidyr::pivot_longer(dplyr::all_of(num_vars),
                      names_to = "variable", values_to = "valor")

p_scatter_sur <- ggplot(eda_long_sur, aes(x = valor, y = preciom, color = zona)) +
  geom_point(alpha = 0.6) +
  geom_smooth(method = "lm", se = FALSE) +
  facet_wrap(~ variable, scales = "free_x") +
  labs(x = NULL, y = "Precio (millones)",
       title = "Relaciones bivariadas con el precio — Zona Sur") +
  theme_minimal()

plotly::ggplotly(p_scatter_sur)

Los gráficos interactivos muestran que el precio exhibe una relación positiva con:

  • Área construida (areaconst): a mayor metraje, mayor valor esperado; es el determinante físico más influyente del precio.

  • Estrato: captura una prima de localización/calidad del entorno (servicios, urbanismo, seguridad); la pendiente más pronunciada frente al precio es compatible con mercados residenciales consolidados.

Las variables de dotación interna —baños y habitaciones— también muestran asociación positiva, aunque con magnitud más moderada. La dispersión observable (viviendas con igual número de baños/habitaciones pero precios distintos) evidencia la influencia de factores no modelados: antigüedad, acabados, micro-localización dentro de la Zona Sur o estado de conservación.

14.3 Paso 3. Modelo de regresión múltiple

# Ajustar modelo en Zona Sur
mod_sur <- lm(preciom ~ areaconst + estrato + banios + habitaciones + parqueaderos,
              data = base1_sur)

summary(mod_sur)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + banios + habitaciones + 
##     parqueaderos, data = base1_sur)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1092.02   -42.28    -1.33    40.58   926.56 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -261.62501   15.63220 -16.736  < 2e-16 ***
## areaconst       1.28505    0.05403  23.785  < 2e-16 ***
## estrato        60.89709    3.08408  19.746  < 2e-16 ***
## banios         50.69675    3.39637  14.927  < 2e-16 ***
## habitaciones  -24.83693    3.89229  -6.381 2.11e-10 ***
## parqueaderos   72.91468    3.95797  18.422  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 98.02 on 2375 degrees of freedom
##   (406 observations deleted due to missingness)
## Multiple R-squared:  0.7485, Adjusted R-squared:  0.748 
## F-statistic:  1414 on 5 and 2375 DF,  p-value: < 2.2e-16

El modelo RLM para Zona Sur con predictores areaconst, estrato, baños, habitaciones, parqueaderos refuerza cuantitativamente lo observado en el EDA:

  • Coeficiente del área: positivo y usualmente significativo; cada m² adicional incrementa el precio esperado, ceteris paribus.

  • Estrato: aporta una prima adicional, incluso controlando por área; su efecto resume atributos del barrio no explícitamente medidos.

  • Baños, habitaciones, parqueaderos: efectos positivos, con impacto menor que área/estrato, pero relevantes en la segmentación de la demanda.

La bondad de ajuste (R² y R² ajustado) indica qué proporción de la variabilidad del precio explica el conjunto de predictores. Un valor medio–alto sugiere un modelo informativo; si es moderado, recuerda que quedan determinantes externos sin medir (acabados, edad, exactitud de la localización).

14.4 Paso 4. Validación de supuestos

par(mfrow = c(2,2)); plot(mod_sur); par(mfrow = c(1,1))

bp_sur <- lmtest::bptest(mod_sur)    # Homocedasticidad
dw_sur <- lmtest::dwtest(mod_sur)    # Independencia
res_sur <- residuals(mod_sur)
shap_sur <- if(length(res_sur) > 5000){
  shapiro.test(sample(res_sur, 5000))
} else shapiro.test(res_sur)
ad_sur <- nortest::ad.test(res_sur)
chk_sur <- performance::check_model(mod_sur)

list(Breusch_Pagan = bp_sur, Durbin_Watson = dw_sur,
     Shapiro = shap_sur, Anderson_Darling = ad_sur,
     Check_Model = chk_sur)
## $Breusch_Pagan
## 
##  studentized Breusch-Pagan test
## 
## data:  mod_sur
## BP = 754.81, df = 5, p-value < 2.2e-16
## 
## 
## $Durbin_Watson
## 
##  Durbin-Watson test
## 
## data:  mod_sur
## DW = 1.5333, p-value < 2.2e-16
## alternative hypothesis: true autocorrelation is greater than 0
## 
## 
## $Shapiro
## 
##  Shapiro-Wilk normality test
## 
## data:  res_sur
## W = 0.79118, p-value < 2.2e-16
## 
## 
## $Anderson_Darling
## 
##  Anderson-Darling normality test
## 
## data:  res_sur
## A = 72.413, p-value < 2.2e-16
## 
## 
## $Check_Model

* Heterocedasticidad (Breusch–Pagan + residuos vs. ajustados): los precios inmobiliarios suelen exhibir varianza creciente con el nivel de precio. Si la prueba resulta significativa o el gráfico sugiere “abanico”, conviene reportarlo y sugerir errores robustos o transformación (p. ej., log(precio)).

  • Independencia (Durbin–Watson): en datos de corte transversal se espera independencia; un estadístico cercano a 2 respalda el supuesto. Desviaciones podrían indicar estructuras de barrio (efectos espaciales) aún no modeladas.

  • Normalidad de residuos (Shapiro–Wilk, Anderson–Darling, QQ-plot): ligeras desviaciones son frecuentes por outliers o colas pesadas. La regresión es relativamente robusta; si la inferencia es sensible, considerar transformaciones o métodos robustos.

  • Multicolinealidad (VIF) y chequeos integrados (check_model): VIF bajos indican que los predictores no duplican información. check_model ayuda a detectar observaciones influyentes; conviene revisar casos extremos antes de decisiones finales.

14.5 Paso 5. Predicción para la primera solicitud (Zona Sur)

# Definir características de Vivienda 2 con estrato 5 y 6
new_v1_sur <- tibble::tibble(
  areaconst    = c(300, 300),
  estrato      = c(5, 6),
  habitaciones = c(5, 5),
  parqueaderos = c(3, 3),
  banios       = c(3, 3)
)

# Predicciones con intervalo de predicción al 95%
pred_v1_sur <- predict(mod_sur, newdata = new_v1_sur,
                       interval = "prediction", level = 0.95)

# Organizar resultados
tope1 <- 850
resultado_v1_sur <- tibble::tibble(
  estrato              = new_v1_sur$estrato,
  prediccion_millones  = round(pred_v1_sur[,"fit"], 1),
  li_95_millones       = round(pred_v1_sur[,"lwr"], 1),
  ls_95_millones       = round(pred_v1_sur[,"upr"], 1),
  veredicto            = ifelse(pred_v1_sur[,"fit"] <= tope1,
                                "Dentro del presupuesto (≤ 850M)",
                                "Excede el presupuesto (> 850M)")
)

resultado_v1_sur

Con las características: 300 m², 5 habitaciones, 3 baños, 3 parqueaderos, estrato 5 o 6, se estimó el precio esperado y su intervalo de predicción al 95%:

  • prediccion_millones: valor puntual esperado según el modelo.

  • li_95_millones / ls_95_millones: límites inferior y superior del intervalo de predicción (95%), que expresa un rango plausible para el precio de una vivienda individual con esas características (incorpora la incertidumbre del modelo y la variabilidad idiosincrática de los inmuebles).

Al comparar con el tope de 850 millones, el veredicto clasifica la opción como “Dentro del presupuesto” o “Excede el presupuesto”. Como cabría esperar, estrato 6 tiende a una predicción superior a estrato 5 por la prima de entorno.

14.6 Paso 6. Ofertas potenciales en el mapa

# Selección de ofertas en Zona Sur para Vivienda 2
ofertas_v1_sur <- pick_offers(
  df        = v_final,
  tipo_     = "Apartamento",   
  zona_     = "Sur",
  area      = 300,
  parq      = 3,
  ban       = 3,
  hab       = 5,
  estr_min  = 5,
  estr_max  = 6,
  tope      = 850
)

# Verificar número de ofertas seleccionadas
nrow(ofertas_v1_sur)
## [1] 3
# Mostrar las 5 primeras ofertas
head(ofertas_v1_sur, 5)

El procedimiento de selección identifica ofertas que se aproximan al perfil (apartamento, ~300 m² con tolerancia progresiva, 5 hab., 3 baños, 3 parqueaderos, estrato 5–6, ≤ 850M). El mapa interactivo permite contrastar la micro-localización de cada alternativa (barrio, accesibilidad, entorno).

Si el listado retorna menos de 5 ofertas, se debe a: * Criterios muy restrictivos (en especial área y dotación simultáneas en estratos altos). * Datos faltantes en variables clave que excluyen registros. * Disponibilidad real limitada en la base.

Aun con 3–4 ofertas, el mapa revela concentraciones y gradientes de precio dentro de la Zona Sur. La decisión final debe integrar visita en campo, evaluación del estado físico y verificación de costos de adecuación, pues estos elementos suelen explicar diferencias dentro del intervalo de predicción.

15 Mapa interactivo con las ofertas

leaflet(ofertas_v1_sur %>% 
          tidyr::drop_na(latitud, longitud)) %>%
  addTiles() %>%
  addCircleMarkers(
    ~longitud, ~latitud,
    radius = 6, color = "red", stroke = FALSE, fillOpacity = 0.6,
    popup = ~paste0("<b>", barrio, "</b><br>",
                    "Precio: $", scales::comma(preciom), " M<br>",
                    "Área: ", areaconst, " m²<br>",
                    "Estrato: ", estrato, " | Hab: ", habitaciones,
                    " | Baños: ", banios, " | Parq: ", parqueaderos)
  ) %>%
  addScaleBar(position = "bottomleft")

En conclusión, El mercado en la Zona Sur confirma un patrón hedónico claro: el tamaño (área) y el entorno (estrato) dominan la valoración; la dotación (baños, habitaciones, parqueaderos) añade valor pero con impacto marginal más moderado. El modelo de regresión, validado con diagnósticos estándar, ofrece una base sólida y transparente para estimar precios y orientar decisiones. La predicción de la Vivienda 2 y la curaduría de ofertas aportan insumos prácticos para negociar con expectativas realistas y enfocar visitas en los barrios con mejor relación costo–beneficio.

16 Conclusiones Generales

El desarrollo del análisis permitió constatar cómo la estadística aplicada al mercado inmobiliario ofrece un marco sólido para comprender y predecir el comportamiento de los precios de la vivienda en la ciudad. Tanto en la Zona Norte como en la Zona Sur, los resultados confirmaron que el área construida y el estrato socioeconómico son los factores más determinantes en la formación del valor, mientras que variables internas como número de habitaciones, baños y parqueaderos actúan como atributos complementarios que incrementan la valoración, aunque con un efecto más moderado.

El uso del modelo de regresión múltiple permitió cuantificar estas relaciones y validar su coherencia mediante pruebas estadísticas, mientras que la exploración de supuestos reveló un ajuste razonablemente robusto, con ligeras desviaciones en normalidad y homocedasticidad propias de datos heterogéneos como los del sector inmobiliario. La incorporación de intervalos de predicción añadió un componente clave para la toma de decisiones, al ofrecer no solo un valor esperado, sino también un rango plausible que refleja la variabilidad natural del mercado.

Finalmente, la identificación de ofertas potenciales y su representación en mapas interactivos brindó una visión práctica y aplicable, en la que la información estadística se traduce en insumos concretos para el comprador. Esta integración de análisis cuantitativo y herramientas visuales refuerza la utilidad de la estadística como apoyo en la planeación y toma de decisiones inmobiliarias.