Preparación previa para informe detallado:

knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE)
set.seed(1234)
# Instalación y carga de paquetes
pkgs <- c(
  "devtools","paqueteMODELOS","tidyverse","janitor","skimr","DT","gt",
  "plotly","leaflet","sf","broom","GGally","rsample","yardstick","lmtest","car","stringr"
)
to_install <- pkgs[!pkgs %in% installed.packages()[,"Package"]]
if(length(to_install)){
  install.packages(setdiff(to_install, "paqueteMODELOS"))
  if(!"paqueteMODELOS" %in% installed.packages()[,"Package"]){
    devtools::install_github("centromagis/paqueteMODELOS", force = TRUE)
  }
}

library(paqueteMODELOS)
library(tidyverse)
library(janitor)
library(skimr)
library(DT)
library(gt)
library(plotly)
library(leaflet)
library(sf)
library(broom)
library(GGally)
library(rsample)
library(yardstick)
library(lmtest)
library(car)
library(stringr)

1. Resumen Ejecutivo

Objetivo. Atender dos solicitudes (Casa Norte con crédito $350M y Apartamento Sur con $850M) usando análisis de datos, modelo de regresión y mapas para recomendar opciones listas para visita.

Modelo y señales.
- Regresión lineal múltiple · R² = 0.72 (test) · MAE ≈ $113M · RMSE ≈ $175M.
- Palancas de valor: +10 m² ≈ +$8.2M · +1 baño ≈ +$62.1M · +1 parqueadero ≈ +$79.8M · +1 estrato ≈ +$98.4M.
- Habitaciones pesa menos a igual área/baños (efecto distribución).

Vivienda 1 — Casa en Zona Norte (200 m²)

  • Estimado Estrato 4: $245M. Estimado Estrato 5: $344M.
  • Recomendación: hay oferta suficiente 180–220 m² ≤ $350M; priorizar 2–3 baños y 1–2 parqueaderos.
  • Barrios foco: La Flora (valor de barrio), Salomia/Calima (mejor m²/$).
  • Acción: agendar 5 visitas y negociar cuando el observado esté cerca de $350M.

Vivienda 2 — Apartamento en Zona Sur (300 m²)

  • Estimado Estrato 5: $615M. Estimado Estrato 6: $714M.
  • Recomendación: presupuesto holgado; empezar por Seminario (300 m², 3 parq., 5 baños ≈ $670M) y Meléndez (oportunidad por precio); Cuarto de Legua como comodín; Pance como ancla premium para negociar.

Siguiente paso. Presentar las shortlists mapeadas y realizar visitas guiadas por las palancas de valor (baños, parqueaderos y área) para cerrar dentro del crédito.

INFORME DETALLADO

2. Carga y preparación de datos

data("vivienda")   # provisto por paqueteMODELOS
raw <- vivienda %>% clean_names()

glimpse(raw)
## 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…
skimr::skim(raw)
Data summary
Name raw
Number of rows 8322
Number of columns 13
_______________________
Column type frequency:
character 4
numeric 9
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
zona 3 1.00 8 12 0 5 0
piso 2638 0.68 2 2 0 12 0
tipo 3 1.00 4 11 0 2 0
barrio 3 1.00 4 29 0 436 0

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
id 3 1.00 4160.00 2401.63 1.00 2080.50 4160.00 6239.50 8319.00 ▇▇▇▇▇
estrato 3 1.00 4.63 1.03 3.00 4.00 5.00 5.00 6.00 ▅▆▁▇▆
preciom 2 1.00 433.89 328.65 58.00 220.00 330.00 540.00 1999.00 ▇▂▁▁▁
areaconst 3 1.00 174.93 142.96 30.00 80.00 123.00 229.00 1745.00 ▇▁▁▁▁
parqueaderos 1605 0.81 1.84 1.12 1.00 1.00 2.00 2.00 10.00 ▇▁▁▁▁
banios 3 1.00 3.11 1.43 0.00 2.00 3.00 4.00 10.00 ▇▇▃▁▁
habitaciones 3 1.00 3.61 1.46 0.00 3.00 3.00 4.00 10.00 ▂▇▂▁▁
longitud 3 1.00 -76.53 0.02 -76.59 -76.54 -76.53 -76.52 -76.46 ▁▅▇▂▁
latitud 3 1.00 3.42 0.04 3.33 3.38 3.42 3.45 3.50 ▃▇▅▇▅
base <- raw %>%
  mutate(
    estrato       = as.numeric(estrato),
    preciom       = as.numeric(preciom),      # millones COP
    areaconst     = as.numeric(areaconst),
    parqueaderos  = as.integer(parqueaderos),
    banios        = as.integer(banios),
    habitaciones  = as.integer(habitaciones),
    latitud       = as.numeric(latitud),
    longitud      = as.numeric(longitud),
    zona          = as.factor(zona),
    tipo          = as.factor(tipo),
    barrio        = as.factor(barrio)
  ) %>%
  drop_na(preciom, areaconst, estrato, parqueaderos, banios, habitaciones, latitud, longitud)

summary(base)
##        id                 zona          piso              estrato    
##  Min.   :   1   Zona Centro :  64   Length:6717        Min.   :3.00  
##  1st Qu.:2474   Zona Norte  :1287   Class :character   1st Qu.:4.00  
##  Median :4474   Zona Oeste  :1098   Mode  :character   Median :5.00  
##  Mean   :4413   Zona Oriente: 163                      Mean   :4.83  
##  3rd Qu.:6428   Zona Sur    :4105                      3rd Qu.:6.00  
##  Max.   :8319                                          Max.   :6.00  
##                                                                      
##     preciom         areaconst       parqueaderos        banios      
##  Min.   :  58.0   Min.   :  30.0   Min.   : 1.000   Min.   : 0.000  
##  1st Qu.: 248.0   1st Qu.:  86.0   1st Qu.: 1.000   1st Qu.: 2.000  
##  Median : 355.0   Median : 130.0   Median : 2.000   Median : 3.000  
##  Mean   : 468.9   Mean   : 181.1   Mean   : 1.835   Mean   : 3.255  
##  3rd Qu.: 580.0   3rd Qu.: 233.0   3rd Qu.: 2.000   3rd Qu.: 4.000  
##  Max.   :1999.0   Max.   :1745.0   Max.   :10.000   Max.   :10.000  
##                                                                     
##   habitaciones             tipo                 barrio        longitud     
##  Min.   : 0.000   Apartamento:4231   valle del lili: 837   Min.   :-76.59  
##  1st Qu.: 3.000   Casa       :2486   ciudad jardín : 494   1st Qu.:-76.54  
##  Median : 3.000                      pance         : 397   Median :-76.53  
##  Mean   : 3.611                      la flora      : 349   Mean   :-76.53  
##  3rd Qu.: 4.000                      santa teresita: 249   3rd Qu.:-76.52  
##  Max.   :10.000                      el ingenio    : 198   Max.   :-76.46  
##                                      (Other)       :4193                   
##     latitud     
##  Min.   :3.333  
##  1st Qu.:3.379  
##  Median :3.412  
##  Mean   :3.415  
##  3rd Qu.:3.451  
##  Max.   :3.498  
## 

🚀 Carga y preparación de datos (listo para vender decisiones)

📦 Fuente & tamaño del set

Trabajamos con la base vivienda del paquete paqueteMODELOS.

Tamaño original: 8.322 registros y 13 variables (4 categóricas, 9 numéricas).

Tras limpieza y control de calidad, el set analítico queda en 6.717 observaciones 👉 suficiente para conclusiones sólidas y accionables.

🧹 Calidad y transformación (lo que hicimos para que “brille”)

Estandarizamos nombres y tipos (factores/numéricos).

NAs detectados: zona (3), tipo (3), barrio (3), preciom (2), estrato (3), areaconst (3), parqueaderos (1.605), piso (2.638).

Estrategia: eliminamos filas con NAs en variables clave del modelo (precio, área, estrato, baños, habitaciones, parqueaderos y coordenadas).

🎯 Resultado: modelo limpio y confiable para pronóstico; a futuro podemos imputar parqueaderos y recodificar ceros en baños/habitaciones como “no informado” si se desea.

🗺️ Coherencia geográfica (Cali real, no ficción)

Coordenadas validadas: longitud ∈ [-76.59, -76.46] (media ≈ -76.53), latitud ∈ [3.33, 3.50] (media ≈ 3.42).

Con esto mapeamos ofertas y detectamos outliers para evitar sorpresas en terreno. ✅

💰📐 Variables clave (escala: millones COP y m²)

Precio (preciom): p25 220, mediana 330, p75 540, máx 1.999, media (limpio) ≈ 468,9.

Área (areaconst): min 30, mediana 130, p75 233, máx 1.745, media 181,1.

Estrato: 3–6 (media 4,63) → foco natural en 5.

Comodidades: baños ≈ 3,26, habitaciones ≈ 3,61, parqueaderos ≈ 1,84 (algunos faltantes, ya tratados).

📍 Dónde está la oferta (mix ideal para captar oportunidades)

Zonas (6.717 obs): Sur 61,1% (4.105), Norte 19,2% (1.287), Oeste 16,3% (1.098), Oriente 2,4% (163), Centro 1,0% (64).

Tipo de inmueble: Apartamento 63,0% (4.231) vs Casa 37,0% (2.486).

Barrios líderes: Valle del Lili 12,5%, Ciudad Jardín 7,4%, Pance 5,9%, La Flora 5,2%, Santa Teresita 3,7%, El Ingenio 2,9% → el resto 62% diversificado.

🧭 Traducción comercial: amplia canasta de opciones en Sur y sólida profundidad en Norte/Oeste para ajustar finamente a presupuesto.

📈 Implicaciones para el modelo (y para cerrar negocio)

El set es grande y representativo → predicciones estables para filtrar y priorizar.

Distribución de precios con colas altas → el RLM funciona bien para decisión; si buscamos máxima precisión, podemos probar log(precio) o modelos regularizados/boosting.

Recomendado para siguientes iteraciones “pro-plus”: incluir dummies de zona/barrio, interacciones área×estrato y splines para capturar no linealidades.

3. Paso 1 — Filtro: Casas en Zona Norte + verificación

base1 <- base %>%
  filter(tipo == "Casa", str_detect(str_to_lower(as.character(zona)), "norte"))

datatable(head(base1, 3), options = list(pageLength = 3),
          caption = "Primeros 3 registros: Casas en Zona Norte")
base1 %>% count(tipo, zona, name = "n") %>% 
  gt() %>% tab_header(title = "Conteo tipo–zona (tras el filtro)")
Conteo tipo–zona (tras el filtro)
tipo zona n
Casa Zona Norte 435
leaflet(base1) %>%
  addTiles() %>%
  addCircleMarkers(
    lng = ~longitud, lat = ~latitud, radius = 6, stroke = FALSE, fillOpacity = 0.7,
    popup = ~paste0("<b>", tipo, "</b><br>",
                    "Zona: ", zona,"<br>",
                    "Barrio: ", barrio,"<br>",
                    "Área: ", areaconst, " m²<br>",
                    "Precio: ", round(preciom,1), " M")
  ) %>% addMiniMap(toggleDisplay = TRUE)

🎯 Qué filtramos y cómo Aplicamos un filtro case–insensitive para quedarnos solo con inmuebles cuyo tipo == “Casa” y cuya zona contiene “Norte”. Además, excluimos registros con faltantes en las variables clave (precio, área, baños, habitaciones, parqueaderos y coordenadas) para garantizar consistencia en el análisis y en el mapa.

📌 Evidencia del filtro

La tabla de control arroja 435 registros: Casa – Zona Norte ✅

Los primeros 3 registros (tabla superior) muestran ejemplos representativos con variación en área, precio, baños y estrato — justo lo que necesitamos para comparar opciones reales.

🗺️ Verificación en mapa (coherencia espacial) El mapa interactivo muestra una alta concentración de puntos en el cordón norte de Cali (zonas residenciales consolidadas y de alta demanda). 👉 Excelente señal comercial: existe masa crítica de oferta para responder con rapidez a la solicitud de “Casa en Zona Norte”.

🔍 ¿Y los puntos que parecen “fuera de norte”? En la visualización pueden aparecer algunos marcadores en bordes o ligeramente desplazados. Esto suele deberse a:

✍️ Digitación de coordenadas con pequeños errores (lat/long invertidas, redondeos).

🧭 Criterios de “zona” comerciales (cómo lo reporta la inmobiliaria) que no siempre calzan con límites geográficos estrictos.

🧱 Barrios frontera o centroides aproximados (el punto se guarda como referencia del barrio y no del predio exacto).

✅ Qué hacemos al respecto (buenas prácticas)

Mantener el filtro por zona para cumplir el brief del cliente, y

Usar las coordenadas para validar in situ las ofertas preseleccionadas (evitamos sorpresas antes de la visita).

Si se requiere máxima precisión geográfica: cruzar con un shape oficial de comunas/zonas y restringir por polígono.

💼 Mensaje para María (C&A) Hay oferta suficiente (435 casas) en el Norte para construir una shortlist potente. El mapa confirma foco territorial y nos permite acelerar visitas en los corredores con mayor densidad de opciones.

Resultado: respuesta rápida, comparables sólidos y mejores probabilidades de cierre. 🏡✨

4.Paso 2 — EDA con Plotly (correlación con precio)

num_vars <- base %>% 
  select(preciom, areaconst, estrato, banios, habitaciones, parqueaderos)

p_pairs <- GGally::ggpairs(num_vars)
ggplotly(p_pairs)
g1 <- ggplot(base, aes(areaconst, preciom)) + geom_point(alpha=.5) + geom_smooth(se = FALSE) +
  labs(x="Área construida (m²)", y="Precio (M)", title="Precio vs Área")
g2 <- ggplot(base, aes(estrato, preciom)) + geom_jitter(alpha=.4, width=.2) + geom_smooth(se = FALSE) +
  labs(x="Estrato", y="Precio (M)", title="Precio vs Estrato")
g3 <- ggplot(base, aes(banios, preciom)) + geom_jitter(alpha=.4, width=.2) + geom_smooth(se = FALSE) +
  labs(x="Baños", y="Precio (M)", title="Precio vs Baños")
g4 <- ggplot(base, aes(habitaciones, preciom)) + geom_jitter(alpha=.4, width=.2) + geom_smooth(se = FALSE) +
  labs(x="Habitaciones", y="Precio (M)", title="Precio vs Habitaciones")
g5 <- ggplot(base, aes(parqueaderos, preciom)) + geom_jitter(alpha=.4, width=.2) + geom_smooth(se = FALSE) +
  labs(x="Parqueaderos", y="Precio (M)", title="Precio vs Parqueaderos")

subplot(ggplotly(g1), ggplotly(g2), ggplotly(g3), ggplotly(g4), ggplotly(g5),
        nrows = 3, margin = 0.04, titleX = TRUE, titleY = TRUE)

💡 Señales fuertes (correlación con preciom)

🅿️ Parqueaderos → r ≈ 0.69*: el salto de 1 → 2 → 3 parqueaderos incrementa sustancialmente el precio; después de 3 la ganancia se atenúa (efecto “plateau”).

📐 Área construida → r ≈ 0.68*: crecimiento sostenido del precio con el metraje, con rendimientos decrecientes a partir de ~900–1.200 m² (la curva suavizada empieza a “aplanarse”).

🚿 Baños → r ≈ 0.67*: de 2 a 4 baños el impacto es muy visible; más de 6 agrega valor, pero cada baño extra rinde menos.

🏷️ Estrato → r ≈ 0.59*: escalones de precio claros 3→4→5→6.

🛏️ Habitaciones → r ≈ 0.27*: aporta, pero mucho más débil que área/baños/parqueaderos. La curva sugiere beneficio hasta 4–5 y luego poca ganancia adicional.

🔗 Relaciones entre explicativas (para el modelo)

Área se asocia con baños (r ≈ 0.67), habitaciones (r ≈ 0.53) y parqueaderos (r ≈ 0.59).

Baños con habitaciones (r ≈ 0.60) y parqueaderos (r ≈ 0.57).

📌 Implicación: hay colinealidad moderada; el RLM seguirá funcionando, pero más adelante conviene revisar VIF y considerar interacciones (ej. área×estrato) o transformaciones (splines/log).

🔎 Forma de las relaciones (según las curvas suavizadas)

Área: sube fuerte al principio y se desacelera en metrajes muy altos → “no paga por m² de sobra”.

Parqueaderos: gran salto hasta 3, luego meseta → “3 parqueaderos es el dulce”.

Baños: ganancia marcada 2→4, luego rendimientos decrecientes.

Estrato: escalones nítidos por nivel.

Habitaciones: suave; valor percibido depende más de baños y área que del número de cuartos en sí.

🎯 ¿Cómo aterriza esto en las dos solicitudes?

Vivienda 1 (Casa, Norte, 200 m², 1 parq., 2 baños, 4 hab., estrato 4–5, crédito 350 M)

✅ 200 m² es atractivo para el segmento;

⚠️ 1 parqueadero es justo: si encontramos 2, ganamos valor sin romper presupuesto;

✅ 2 baños funciona, pero 3 sería ideal (mejora precio/valor);

💡 En estrato 4 hay más chance de quedar dentro del crédito; estrato 5 requiere selección fina o negociación.

Vivienda 2 (Apto, Sur, 300 m², 3 parq., 3 baños, 5 hab., estrato 5–6, crédito 850 M)

✅ 3 parqueaderos está en el punto dulce;

✅ 3 baños correcto para 300 m²;

✅ 300 m² posiciona el inmueble en franja premium;

💰 Con 850 M se puede apuntar a estrato 5 e incluso 6 con buenas probabilidades de encaje.

🧠 Conclusión EDA

Para capturar valor y acelerar cierres, prioricemos baños y parqueaderos; el metraje suma, pero con cuidado de los rendimientos decrecientes.

Con esta lectura, en el siguiente paso el RLM tendrá variables con fuerza explicativa real y te permitirá filtrar las ofertas que sí encajan con el crédito de cada familia.

Resultado: visitas mejor calificadas, menos vueltas y mejores tasas de cierre. 🏡✨

5.Paso 3 — Modelo RLM y lectura de coeficientes

# Train/Test 70/30 estratificado por precio
split <- initial_split(base, prop = 0.7, strata = preciom)
train <- training(split)
test  <- testing(split)

modelo_lm <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = train)

# Coeficientes e IC
broom::tidy(modelo_lm, conf.int = TRUE) %>%
  mutate(across(where(is.numeric), ~round(.,4))) %>%
  datatable(caption = "Coeficientes del RLM (IC 95%)", options = list(pageLength = 10))
# Resumen del ajuste
broom::glance(modelo_lm) %>% 
  mutate(across(where(is.numeric), ~round(.,4))) %>%
  datatable(caption = "Ajuste global (R², R² ajustado, etc.)", options = list(dom='t'))

##Lectura.

𝛽area: variación en millones por m² adicional, ceteris paribus. 𝛽estrato: incremento medio por subir un nivel socioeconómico. 𝛽banos/habitaciones/parqueaderos :efectos marginales por unidad adicional.- R² y R² adj. indican capacidad explicativa para priorizar ofertas.

res <- resid(modelo_lm)
fit <- fitted(modelo_lm)

# Normalidad
p_hist <- ggplot(data.frame(res), aes(res)) + geom_histogram(bins=30, alpha=.7) + labs(title="Residuos: histograma")
p_qq   <- ggplot(data.frame(res), aes(sample=res)) + stat_qq() + stat_qq_line() + labs(title="QQ-plot")
subplot(ggplotly(p_hist), ggplotly(p_qq), nrows = 1)
# Homocedasticidad
bp <- lmtest::bptest(modelo_lm)

# Independencia
dw <- lmtest::dwtest(modelo_lm)

# Multicolinealidad
vifs <- car::vif(modelo_lm)

list(Breusch_Pagan_pvalue = bp$p.value, Durbin_Watson_pvalue = dw$p.value, VIF = round(vifs,3))
## $Breusch_Pagan_pvalue
##            BP 
## 5.746737e-188 
## 
## $Durbin_Watson_pvalue
## [1] 6.6619e-117
## 
## $VIF
##    areaconst      estrato habitaciones parqueaderos       banios 
##        2.131        1.546        2.037        1.817        2.848

Precio cop

𝛽 0 + 𝛽 1 A ˊ rea (m²) + 𝛽 2 Estrato + 𝛽 3 Habitaciones + 𝛽 4 Parqueaderos + 𝛽 5 Ba n ˜ os + 𝜀 Precio (M COP)=β 0 ​

+β 1 ​

A ˊ rea (m²)+β 2 ​

Estrato+β 3 ​

Habitaciones+β 4 ​

Parqueaderos+β 5 ​

Ba n ˜ os+ε

Ajuste global: R² = 0.720 (R² adj. 0.720), n = 4.700. 👉 El modelo explica aprox. 72% de la variación del precio: bueno para priorizar y filtrar ofertas. Error típico (sigma): ~178 M → el precio de mercado tiene dispersión alta; por eso reportamos intervalos de predicción en las recomendaciones.

📌 Lectura de coeficientes (IC 95% entre paréntesis)

📐 Área construida: +0.819 M por cada m² (0.767 – 0.870).

Regla de bolsillo: +10 m² ≈ +8,2 M; +50 m² ≈ +41 M.

🏷️ Estrato: +98,4 M por cada nivel (+91,7 – +105,0).

Subir de 4→5 agrega ~+98 M; de 5→6, otro +98 M.

🚗 Parqueaderos: +79,8 M por unidad (+73,6 – +86,0).

Pasar de 1 a 2 parqueaderos suma ≈ +80 M; de 2 a 3, +80 M extra.

🚿 Baños: +62,1 M por unidad (+55,9 – +68,4).

De 2 a 3 baños, ≈ +62 M; muy valorizado para familias.

🛏️ Habitaciones: −32,0 M por unidad (−37,3 – −26,7).

👀 “Negativo” no significa que más cuartos bajen el precio per se. Significa que, a área fija y mismos baños/parqueaderos, agregar cuartos “divide” el espacio (menos áreas sociales/terminaciones), por lo que el mercado no paga tanto por una distribución más fraccionada. En la práctica: el “valor” de los cuartos ya lo captura Área y Baños; por eso Habitaciones sale con signo inverso.

Todos los coeficientes son estadísticamente significativos (p < 0.001), con intervalos estrechos → señales robustas.

🔎 Diagnósticos del modelo (qué miramos y qué significa)

📉 Normalidad de residuos (QQ-plot): colas pesadas → hay outliers en precios altos (mercado premium).

📈 Heterocedasticidad (Breusch–Pagan p ≈ 5.7e-188): varianza no constante; típico en precios inmobiliarios. Sugerencia: usar errores robustos (HC3) o modelar log(precio) en una versión “pro”.

🔁 Durbin–Watson (p ≈ 6.7e-117): señal de dependencia (clustering espacial y por segmento). En corte transversal no es crítico para predicción, pero conviene considerar efectos por barrio/zona.

🧩 Multicolinealidad (VIFs): Área 2.13, Estrato 1.55, Habitaciones 2.04, Parqueaderos 1.82, Baños 2.85 → todos < 5, sin riesgo.

🎯 Traducción comercial (para tomar decisiones YA)

Palancas que más pagan: Área, Parqueaderos y Baños (en ese orden práctico).

Estrato define el “escalón” de precio: al movernos de 4 a 5 o de 5 a 6, el presupuesto debe crecer ~100 M por escalón.

Habitaciones: priorizar mejor distribución (4–5 cuartos con 2–3 baños) sobre “muchos cuartos chicos”.

6.Paso 5 — Predicciones para las solicitudes

# Vivienda 1
new_v1_e4 <- data.frame(areaconst=200, estrato=4, habitaciones=4, parqueaderos=1, banios=2)
new_v1_e5 <- data.frame(areaconst=200, estrato=5, habitaciones=4, parqueaderos=1, banios=2)

# Vivienda 2
new_v2_e5 <- data.frame(areaconst=300, estrato=5, habitaciones=5, parqueaderos=3, banios=3)
new_v2_e6 <- data.frame(areaconst=300, estrato=6, habitaciones=5, parqueaderos=3, banios=3)

pred_tbl <- bind_rows(
  tibble(Caso="Vivienda 1", Escenario="Estrato 4",  as_tibble(predict(modelo_lm, new_v1_e4, interval="prediction", level=.95))),
  tibble(Caso="Vivienda 1", Escenario="Estrato 5",  as_tibble(predict(modelo_lm, new_v1_e5, interval="prediction", level=.95))),
  tibble(Caso="Vivienda 2", Escenario="Estrato 5",  as_tibble(predict(modelo_lm, new_v2_e5, interval="prediction", level=.95))),
  tibble(Caso="Vivienda 2", Escenario="Estrato 6",  as_tibble(predict(modelo_lm, new_v2_e6, interval="prediction", level=.95)))
) %>% 
  rename(Precio_Estimado=fit, LI_95=lwr, LS_95=upr) %>%
  mutate(across(where(is.numeric), ~round(.,1)))

datatable(pred_tbl, caption="Predicciones con intervalos (millones)")

🔮 Paso 5 — Predicciones para las dos solicitudes (con lectura comercial)

Tabla base (millones COP):

🏠 Vivienda 1 (Casa, Norte, 200 m², 1 parq., 2 baños, 4 hab.)

Estrato 4 → $245.3 M (IC95%: −104.1 a 594.7)

Estrato 5 → $343.7 M (IC95%: −5.8 a 693.1)

🏢 Vivienda 2 (Apto, Sur, 300 m², 3 parq., 3 baños, 5 hab.)

Estrato 5 → $615.2 M (IC95%: 265.7 a 964.7)

Estrato 6 → $713.6 M (IC95%: 364.0 a 1,063.2)

Nota: El modelo entrega punto estimado y intervalo de predicción (IC95%). El intervalo es amplio porque los precios tienen dispersión alta en el mercado (sigma ≈ 178 M). Para decisión práctica nos guiamos por el punto y contrastamos con el crédito.

🎯 Decisión para Vivienda 1 — crédito $350 M

Estrato 4: Estimado $245 M → ✅ cómodamente dentro del crédito. Acción: priorizar casas en Norte ~200 m² con 2–3 baños y ≥1–2 parqueaderos.

Estrato 5: Estimado $344 M → ✅ dentro del crédito, pero al límite. Acción: short-list en $320–350 M y negociar. Evitar extras costosas (cada parqueadero +$80 M, cada baño +$62 M aprox.).

Tip de ajuste rápido (según el RLM):

+10 m² ≈ +$8,2 M

+1 parqueadero ≈ +$79,8 M

+1 baño ≈ +$62,1 M

+1 nivel de estrato ≈ +$98,4 M

🧭 Recomendación para María: arrancar visitas con 5–7 opciones estrato 4 (colchón amplio) y 2–3 en estrato 5 bien ubicadas para tantear negociación.

💼 Decisión para Vivienda 2 — crédito $850 M

Estrato 5: Estimado $615 M → ✅ holgura de ~$235 M. Acción: elegir ubicaciones premium en Sur con 3 parqueaderos + 3 baños, priorizando amenidades y estado del edificio.

Estrato 6: Estimado $714 M → ✅ dentro con holgura de ~$136 M. Acción: foco en edificios de alta especificación; el IC superior supera 850, pero el punto está cómodo → filtrar en $680–820 M y negociar si aparece algo cerca de 850.

📌 Mensaje para la empresa: para el traslado ejecutivo, Estrato 5 ofrece mejor relación área/amenidades/precio; Estrato 6 es viable manteniendo controles de precio.

🛡️ Cómo usar los intervalos (sin asustarnos)

Los IC95% son amplios porque el mercado es heterogéneo (barrios, acabados). No significan “incertidumbre total”, sino que conviene confirmar con comparables y visitar.

En la práctica: selecciona por el punto y valida en terreno; usa el IC para fijar rangos de negociación.

✅ Siguiente paso

Pasamos a Paso 6 y 7: armar mapas y short-lists de ≥5 ofertas para cada caso:

Vivienda 1: Casas Norte 180–220 m², ≤ $350 M (predicho).

Vivienda 2: Aptos Sur 270–330 m², ≤ $850 M (predicho).

Con esto, María podrá mostrar rápido lo que sí cierra, con un discurso claro de valor vs. presupuesto. 🏡

7.Paso 6 — Ofertas para Vivienda 1 (mapa + tabla)

Criterios: Casa, Zona “Norte”, área 180–220 m² (±10% de 200), precio predicho ≤ 350 M.

ofertas_v1 <- base %>%
  filter(tipo == "Casa", str_detect(str_to_lower(as.character(zona)), "norte")) %>%
  filter(between(areaconst, 180, 220)) %>%
  mutate(precio_pred = predict(modelo_lm, newdata = .)) %>%
  filter(precio_pred <= 350) %>%
  arrange(abs(areaconst - 200), desc(precio_pred)) %>%
  mutate(label = paste0(
    "<b>", as.character(tipo), " - ", zona, "</b><br>",
    "Barrio: ", barrio, "<br>",
    "Área: ", areaconst, " m²<br>",
    "Baños: ", banios, " | Hab: ", habitaciones, " | Parq: ", parqueaderos, "<br>",
    "Precio observado: ", round(preciom,1), " M<br>",
    "<b>Precio predicho: ", round(precio_pred,1), " M</b>"
  ))

ofertas_v1_top <- head(ofertas_v1, 5)

datatable(ofertas_v1_top %>%
            select(zona, barrio, areaconst, banios, habitaciones, parqueaderos, preciom, precio_pred) %>%
            mutate(across(where(is.numeric), ~round(.,1))),
          caption = "Vivienda 1 — Ofertas recomendadas (Top 5)")
leaflet(ofertas_v1_top) %>%
  addTiles() %>%
  addCircleMarkers(lng=~longitud, lat=~latitud, popup=~label, radius = 8, fillOpacity = .85) %>%
  addMiniMap(toggleDisplay = TRUE)

Criterios aplicados: Casa · Zona Norte · 180–220 m² (±10% de 200) · precio predicho ≤ 350 M. Resultado: Top 5 opciones con buena pinta para visita (ver mapa).

🏆 Mi “lista corta” (por qué valen la pena)

Salomia · 200 m² · 2 baños · 1 parqueadero — $350 M

✅ Metraje exacto a la necesidad.

✅ Dentro del crédito; margen de negociación ligero.

🎯 Ideal para arrancar visitas (match casi perfecto con el brief).

Calima · 196 m² · 2 baños · 1 parqueadero — $270 M

✅ Precio muy por debajo del crédito (colchón para mejoras).

🔧 Si hace falta, podemos invertir en un baño adicional (+~$62 M estimados por el modelo) y seguir holgados.

Salomia · 195 m² · 4 baños · 2 parqueaderos — $300 M (pred ≈ 347 M)

💎 Configuración premium para V1: más baños y 2 parqueaderos.

💬 Excelente candidata para cerrar rápido: está bien equipada sin pasarse del tope.

La Flora · 190 m² · 3 baños · 2 parqueaderos — $355 M (pred ≈ 347 M)

📍 Ubicación top del Norte.

⚖️ Observado levemente sobre 350, pero el precio predicho sugiere que hay espacio a negociar.

📣 Muy recomendable llevarla a visita.

Rozo La Torre · 190 m² · 2 baños · 1 parqueadero — $200 M

💰 Precio de oportunidad.

🧱 Con bajo costo podría mejorarse (p.ej. un baño extra) y aún quedar muy por debajo del crédito.

📝 Nota técnica: las predicciones son del modelo RLM; el precio observado puede diferir por acabados, micro–ubicación o estado. Si el observado está cerca de 350 M y el predicho también, prioricemos visita + negociación.

🧭 Lectura del mapa (¿dónde están?)

Vemos concentración en el corredor norte y ejes viales con buena conectividad (ideal para familias que se mueven entre estudio/trabajo).

La Flora destaca por su valor de barrio (servicios, parques, seguridad), mientras Salomia/Calima ofrecen mejor relación m²/precio.

🎯 Estrategia de cierre para María (C&A)

Ruta de visitas (orden sugerido):

La Flora (valor de barrio + 3 baños + 2 parq.) → ir con argumento de precio de mercado (pred ≈ 347) para llevarlo ≤ 350.

Salomia 195 m² (4 baños + 2 parq.) → gran “combo” funcional dentro de presupuesto.

Salomia 200 m² y Calima 196 m² → opción exacta y opción económica con margen para upgrades.

Rozo La Torre → carta de alto ahorro para inversionistas o familias que prefieran remodelar.

Reglas rápidas del modelo (para calibrar expectativas durante la visita):

+10 m² ≈ +$8,2 M

+1 parqueadero ≈ +$79,8 M

+1 baño ≈ +$62,1 M

+1 nivel de estrato ≈ +$98,4 M

💬 Mensaje al cliente: “Con estas 5 opciones en Norte, entramos cómodamente en $350 M, maximizando baños y parqueaderos, y eligiendo entre valor de barrio premium (La Flora) o precio oportunidad (Calima/Salomia).”

8. Paso 7 — Ofertas para Vivienda 2 (mapa + tabla)

Criterios: Apartamento, Zona “Sur”, área 270–330 m² (±10% de 300), precio predicho ≤ 850 M.

ofertas_v2 <- base %>%
  filter(tipo == "Apartamento", str_detect(str_to_lower(as.character(zona)), "sur")) %>%
  filter(between(areaconst, 270, 330)) %>%
  mutate(precio_pred = predict(modelo_lm, newdata = .)) %>%
  filter(precio_pred <= 850) %>%
  arrange(abs(areaconst - 300), desc(precio_pred)) %>%
  mutate(label = paste0(
    "<b>", as.character(tipo), " - ", zona, "</b><br>",
    "Barrio: ", barrio, "<br>",
    "Área: ", areaconst, " m²<br>",
    "Baños: ", banios, " | Hab: ", habitaciones, " | Parq: ", parqueaderos, "<br>",
    "Precio observado: ", round(preciom,1), " M<br>",
    "<b>Precio predicho: ", round(precio_pred,1), " M</b>"
  ))

ofertas_v2_top <- head(ofertas_v2, 5)

datatable(ofertas_v2_top %>%
            select(zona, barrio, areaconst, banios, habitaciones, parqueaderos, preciom, precio_pred) %>%
            mutate(across(where(is.numeric), ~round(.,1))),
          caption = "Vivienda 2 — Ofertas recomendadas (Top 5)")
leaflet(ofertas_v2_top) %>%
  addTiles() %>%
  addCircleMarkers(lng=~longitud, lat=~latitud, popup=~label, radius = 8, fillOpacity = .85) %>%
  addMiniMap(toggleDisplay = TRUE)

Criterios aplicados: Apartamento · Zona Sur · 270–330 m² (±10% de 300) · precio predicho ≤ $850 M · foco en 3 parqueaderos y ≥3 baños. Resultado: Top 5 opciones que encajan con el crédito (ver mapa).

🏆 Mi “lista corta” (por qué valen la pena)

Seminario · 300 m² · 5 baños · 6 hab. · 3 parq. — $670 M (pred ≈ $707 M)

✅ Match perfecto con 300 m² y 3 parqueaderos.

✅ Muy por debajo del crédito; baños/espacio ideales para familia ejecutiva.

🎯 Candidata #1 para visita inmediata.

Meléndez · 300 m² · 6 baños · 5 hab. · 3 parq. — $370 M (pred ≈ $605 M)

💰 Bargain: observado muy bajo vs predicho; revisar estado/terminaciones.

🧪 Si calza en estrato 5–6, es una oportunidad de oro.

Cuarto de Legua · 296 m² · 4 baños · 4 hab. · 2 parq. — $410 M (pred ≈ $626 M)

✅ Área y baños ok; 2 parqueaderos (se puede negociar alquiler del 3ro en el edificio).

🔎 Muy competitivo en precio observado; gran relación valor/ubicación.

San Joaquín · 300 m² · 4 baños · 5 hab. · 1 parq. — $260 M (pred ≈ $321 M)

💵 Precio de entrada muy bajo.

🅿️ 1 parqueadero no cumple la ficha; útil si la empresa acepta parqueadero adicional arriendo o hay cupo disponible.

Pance · 310 m² · 4 baños · 4 hab. · 3 parq. — $1.590 M (pred ≈ $848 M)

💎 Premium en zona top del Sur.

⚠️ Observado muy por encima del crédito; el predicho sugiere que el inmueble puede tener acabados/amenities de lujo o información atípica.

📌 Mantener como referencia de tope alto; solo considerar si hay flexibilidad de presupuesto.

📝 Nota técnica: las cifras “precio_pred” provienen del RLM; la diferencia con “preciom” refleja acabados, amenities, micro–ubicación y estrato. Usamos el punto para filtrar y el observado para negociar.

🧭 Lectura del mapa (¿dónde conviene?)

Puntos ubicados en corredores residenciales del Sur con acceso a vías principales y cercanía a servicios (colegios, comercio, clubes).

Pance es premium (verde, amenidades); Seminario/Meléndez balancean acceso y valor; Cuarto de Legua es muy competitivo en precio.

🎯 Estrategia de cierre para María (C&A)

Ruta de visitas sugerida:

Seminario → cumple todo (3 parq., 5 baños) y queda muy por debajo de $850 M.

Meléndez → oportunidad de precio; confirmar estado, estrato y administración.

Cuarto de Legua → relación m²/amenidades/precio sobresaliente; gestionar 3er parqueadero.

San Joaquín → plan B si la empresa admite parqueadero adicional en arriendo.

Pance → referencia premium “stretch”; usarla como ancla de negociación.

Reglas rápidas del modelo (para negociar on-site):

+10 m² ≈ +$8,2 M

+1 parqueadero ≈ +$79,8 M

+1 baño ≈ +$62,1 M

+1 nivel de estrato ≈ +$98,4 M

💬 Mensaje al cliente: “En el Sur tenemos apartamentos amplios con 3 parqueaderos y ≥3 baños por debajo de $850 M. Recomiendo iniciar con Seminario y Meléndez (mejor relación valor/precio), dejando Cuarto de Legua como comodín de cierre.” 🏢💜

9. Predicciones en set de prueba y métricas

# Predicciones en test
pred_test <- predict(modelo_lm, newdata = test)

# Data frame de evaluación con ambas columnas
eval_df <- test %>%
  mutate(
    .pred   = as.numeric(pred_test),   # estimaciones (columna dentro del df)
    preciom = as.numeric(preciom)      # asegurar tipo numérico
  )

# Métricas
metrics_tbl <- yardstick::metric_set(rmse, mae, rsq)
res_metricas <- metrics_tbl(eval_df, truth = preciom, estimate = .pred)

datatable(res_metricas %>% mutate(.estimate = round(.estimate, 3)),
          caption = "Rendimiento del modelo en set de prueba")
df_pred <- tibble(real = test$preciom, pred = pred_test)
g <- ggplot(df_pred, aes(real, pred)) +
  geom_point(alpha=.6) + geom_abline(slope=1, intercept=0, linetype="dashed") +
  labs(title="Predicho vs Observado (Test)", x="Observado (M)", y="Predicho (M)")
ggplotly(g)

📈 Predicciones en set de prueba y métricas (qué tan bien acierta el modelo)

Resultados (test):

RMSE: 175,1 M

MAE: 113,5 M

R²: 0,722

Lectura rápida para negocio:

🔍 R² = 0,72 → el modelo explica ~72% de la variación de precios fuera de muestra. Sólido para pre-filtrar inventario y priorizar visitas.

🔧 MAE ≈ 113 M → error absoluto típico. En inmuebles con media ~470 M, esto equivale a un error relativo promedio cercano al 20–25% (orientativo).

📉 RMSE 175 M > MAE 113 M → hay colas altas/outliers (segmento premium) que “empujan” el error; lo mismo se aprecia en el predicho vs observado (los puntos caros tienden a sub-estimarse).

Qué se ve en el scatter “Predicho vs Observado”:

☑️ Buena alineación en la franja 200–800 M (donde están la mayoría de casos y ambos créditos).

⚠️ En el extremo >1.2–1.5 B el modelo sub-predice (línea punteada queda por encima de los puntos): típico por no linealidad y atributos premium no modelados (acabados, amenities, vistas, club).

Implicación práctica (cómo usarlo para cerrar):

👍 Usamos las predicciones para filtrar rápido lo que sí cabe en el crédito (350 M y 850 M), no para fijar el precio final.

🧭 En shortlist y visitas, decidimos con precio observado, comparables y negociación; la predicción sirve de ancla y para detectar sobre/sub-valoraciones.

##10. Anexos técnicos

base %>% 
  group_by(zona, tipo) %>%
  summarize(n = n(), mediana_precio = median(preciom), mediana_area = median(areaconst), .groups="drop") %>%
  arrange(desc(mediana_precio)) %>%
  mutate(across(where(is.numeric), ~round(.,1))) %>%
  datatable(caption = "Estadísticos por zona y tipo")
cooks <- cooks.distance(modelo_lm)
tibble(idx = seq_along(cooks), cooks = cooks) %>%
  arrange(desc(cooks)) %>% head(10) %>%
  datatable(caption = "Top-10 observaciones con mayor influencia (Cook's D)")