Informe Ejecutivo

Contexto del negocio: María, agente de bienes raíces con 10 años de experiencia en Cali, fundó la empresa C&A (Casas y Apartamentos) junto con dos excolaboradores. Actualmente, las ventas del sector inmobiliario en Cali han disminuido debido a tensiones políticas y sociales. María recibió una solicitud de una compañía internacional que desea ubicar a dos empleados con sus familias en la ciudad.

Objetivo del análisis: Desarrollar un modelo de regresión lineal múltiple que permita estimar el precio de viviendas en Cali, para poder asesorar adecuadamente la compra y generar recomendaciones de potenciales ofertas dentro del presupuesto asignado.

Solicitudes recibidas:

Característica Vivienda 1 Vivienda 2
Tipo Casa Apartamento
Área construida (m²) 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

1 Carga de Datos

1.1 Instalación y carga de paquetes

# install.packages("devtools")
# devtools::install_github("centromagis/paqueteMODELOS", force = TRUE)

library(paqueteMODELOS)
library(tidyverse)
library(plotly)
library(car)
library(lmtest)
library(caret)
library(kableExtra)
library(leaflet)
library(nortest)

1.2 Carga y exploración inicial

vivienda <- paqueteMODELOS::vivienda

cat("Dimensiones del dataset:", dim(vivienda), "\n")
## Dimensiones del dataset: 8322 13
str(vivienda)
## spc_tbl_ [8,322 × 13] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ id          : num [1:8322] 1147 1169 1350 5992 1212 ...
##  $ zona        : chr [1:8322] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
##  $ piso        : chr [1:8322] NA NA NA "02" ...
##  $ estrato     : num [1:8322] 3 3 3 4 5 5 4 5 5 5 ...
##  $ preciom     : num [1:8322] 250 320 350 400 260 240 220 310 320 780 ...
##  $ areaconst   : num [1:8322] 70 120 220 280 90 87 52 137 150 380 ...
##  $ parqueaderos: num [1:8322] 1 1 2 3 1 1 2 2 2 2 ...
##  $ banios      : num [1:8322] 3 2 2 5 2 3 2 3 4 3 ...
##  $ habitaciones: num [1:8322] 6 3 4 3 3 3 3 4 6 3 ...
##  $ tipo        : chr [1:8322] "Casa" "Casa" "Casa" "Casa" ...
##  $ barrio      : chr [1:8322] "20 de julio" "20 de julio" "20 de julio" "3 de julio" ...
##  $ longitud    : num [1:8322] -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num [1:8322] 3.43 3.43 3.44 3.44 3.46 ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   id = col_double(),
##   ..   zona = col_character(),
##   ..   piso = col_character(),
##   ..   estrato = col_double(),
##   ..   preciom = col_double(),
##   ..   areaconst = col_double(),
##   ..   parqueaderos = col_double(),
##   ..   banios = col_double(),
##   ..   habitaciones = col_double(),
##   ..   tipo = col_character(),
##   ..   barrio = col_character(),
##   ..   longitud = col_double(),
##   ..   latitud = col_double()
##   .. )
##  - attr(*, "problems")=<externalptr>
summary(vivienda)
##        id           zona               piso              estrato     
##  Min.   :   1   Length:8322        Length:8322        Min.   :3.000  
##  1st Qu.:2080   Class :character   Class :character   1st Qu.:4.000  
##  Median :4160   Mode  :character   Mode  :character   Median :5.000  
##  Mean   :4160                                         Mean   :4.634  
##  3rd Qu.:6240                                         3rd Qu.:5.000  
##  Max.   :8319                                         Max.   :6.000  
##  NA's   :3                                            NA's   :3      
##     preciom         areaconst       parqueaderos        banios      
##  Min.   :  58.0   Min.   :  30.0   Min.   : 1.000   Min.   : 0.000  
##  1st Qu.: 220.0   1st Qu.:  80.0   1st Qu.: 1.000   1st Qu.: 2.000  
##  Median : 330.0   Median : 123.0   Median : 2.000   Median : 3.000  
##  Mean   : 433.9   Mean   : 174.9   Mean   : 1.835   Mean   : 3.111  
##  3rd Qu.: 540.0   3rd Qu.: 229.0   3rd Qu.: 2.000   3rd Qu.: 4.000  
##  Max.   :1999.0   Max.   :1745.0   Max.   :10.000   Max.   :10.000  
##  NA's   :2        NA's   :3        NA's   :1605     NA's   :3       
##   habitaciones        tipo              barrio             longitud     
##  Min.   : 0.000   Length:8322        Length:8322        Min.   :-76.59  
##  1st Qu.: 3.000   Class :character   Class :character   1st Qu.:-76.54  
##  Median : 3.000   Mode  :character   Mode  :character   Median :-76.53  
##  Mean   : 3.605                                         Mean   :-76.53  
##  3rd Qu.: 4.000                                         3rd Qu.:-76.52  
##  Max.   :10.000                                         Max.   :-76.46  
##  NA's   :3                                              NA's   :3       
##     latitud     
##  Min.   :3.333  
##  1st Qu.:3.381  
##  Median :3.416  
##  Mean   :3.418  
##  3rd Qu.:3.452  
##  Max.   :3.498  
##  NA's   :3

2 Solicitud 1: Casa Zona Norte

2.1 Paso 1 — Filtro base de datos, verificación y mapa

Se filtra la base para incluir únicamente casas de la Zona Norte de Cali, conforme al enunciado.

datos_filtrados <- vivienda %>%
  filter(tipo == "Casa",
         zona == "Zona Norte")

cat("Registros tras el filtro:", nrow(datos_filtrados), "\n")
## Registros tras el filtro: 722
cat("Número de variables     :", ncol(datos_filtrados), "\n")
## Número de variables     : 13

2.1.1 Primeros 3 registros

kable(head(datos_filtrados, 3),
      caption = "Primeros 3 registros - Casas Zona Norte") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed","responsive"))
Primeros 3 registros - Casas Zona Norte
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
1209 Zona Norte 02 5 320 150 2 4 6 Casa acopi -76.51341 3.47968
1592 Zona Norte 02 5 780 380 2 3 3 Casa acopi -76.51674 3.48721
4057 Zona Norte 02 6 750 445 NA 7 6 Casa acopi -76.52950 3.38527

2.1.2 Tabla resumen por zona

tabla_zona <- datos_filtrados %>%
  group_by(zona) %>%
  summarise(
    n            = n(),
    precio_prom  = round(mean(preciom,   na.rm = TRUE), 1),
    area_prom    = round(mean(areaconst, na.rm = TRUE), 1),
    estrato_prom = round(mean(estrato,   na.rm = TRUE), 1)
  )

kable(tabla_zona,
      caption = "Resumen estadístico por zona del filtro aplicado") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed"))
Resumen estadístico por zona del filtro aplicado
zona n precio_prom area_prom estrato_prom
Zona Norte 722 445.9 264.9 4.2

2.1.3 Mapa de puntos filtrados

datos_mapa <- datos_filtrados %>%
  filter(!is.na(longitud), !is.na(latitud)) %>%
  mutate(color_zona = case_when(
    zona == "Zona Norte"  ~ "blue",
    zona == "Zona Sur"    ~ "red",
    zona == "Zona Centro" ~ "green",
    zona == "Zona Oeste"  ~ "orange",
    TRUE                  ~ "gray"
  ))

leaflet(datos_mapa) %>%
  addTiles() %>%
  addCircleMarkers(
    lng         = ~longitud,
    lat         = ~latitud,
    radius      = 5,
    color       = ~color_zona,
    fillOpacity = 0.7,
    popup       = ~paste(
      "<strong>Zona:</strong>",    zona,    "<br>",
      "<strong>Barrio:</strong>",  barrio,  "<br>",
      "<strong>Precio:</strong>",  preciom, "M COP<br>",
      "<strong>Estrato:</strong>", estrato
    )
  ) %>%
  addLegend("bottomright",
            colors = c("blue","red","green","orange","gray"),
            labels = c("Zona Norte","Zona Sur","Zona Centro","Zona Oeste","Otra"),
            title  = "Zona geográfica")

Discusión del mapa: Al visualizar los puntos geográficamente, se puede verificar si todas las observaciones filtradas como “Zona Norte” se ubican efectivamente en el sector norte de Cali. En caso de encontrar puntos en otras zonas geográficas, esto se debe principalmente a dos razones: (1) errores o imprecisiones en las coordenadas GPS registradas al momento de la captura del dato, o (2) diferencias entre la clasificación administrativa de zona usada en la base de datos y los límites geográficos reales de cada sector. Estos casos atípicos no representan un problema crítico para el modelo, pero es importante tenerlos en cuenta al interpretar los resultados y al sugerir ofertas específicas a los clientes.

2.2 Limpieza de datos

valores_faltantes <- datos_filtrados %>%
  summarise_all(~sum(is.na(.))) %>%
  pivot_longer(everything(),
               names_to  = "variable",
               values_to = "faltantes") %>%
  filter(faltantes > 0)

if(nrow(valores_faltantes) > 0){
  kable(valores_faltantes, caption = "Variables con valores faltantes") %>%
    kable_styling(bootstrap_options = c("striped","hover","condensed"))
} else {
  cat("No se detectaron valores faltantes en las variables clave.\n")
}
Variables con valores faltantes
variable faltantes
piso 372
parqueaderos 287
datos_limpios <- datos_filtrados %>%
  drop_na(preciom, areaconst, estrato, parqueaderos, banios, habitaciones)

cat("Registros después de limpieza:", nrow(datos_limpios), "\n")
## Registros después de limpieza: 435

2.3 Set de entrenamiento y prueba

set.seed(123)

indice_train <- createDataPartition(datos_limpios$preciom,
                                    p    = 0.80,
                                    list = FALSE)

train_data <- datos_limpios[indice_train, ]
test_data  <- datos_limpios[-indice_train, ]

cat("Set de entrenamiento:", nrow(train_data), "registros\n")
## Set de entrenamiento: 350 registros
cat("Set de prueba       :", nrow(test_data),  "registros\n")
## Set de prueba       : 85 registros

2.4 Paso 2 — Análisis Exploratorio con Plotly

2.4.1 Distribución del precio

p1 <- plot_ly(datos_limpios, x = ~preciom,
              type   = "histogram",
              nbinsx = 40,
              marker = list(color = "#2196F3",
                            line  = list(color = "white", width = 0.5))) %>%
  layout(
    title         = "Distribución del Precio de Viviendas (Zona Norte)",
    xaxis         = list(title = "Precio (millones de pesos COP)"),
    yaxis         = list(title = "Frecuencia"),
    plot_bgcolor  = "rgba(240,240,240,0.5)",
    paper_bgcolor = "white"
  )
p1

Interpretación: La distribución del precio presenta una marcada asimetría positiva (sesgo a la derecha). La mayoría de las casas en zona Norte tienen precios concentrados en rangos bajos-medios, mientras que un grupo reducido alcanza precios muy altos. Esto es coherente con un mercado inmobiliario urbano donde conviven inmuebles de distintos estratos.

2.4.2 Correlación entre variables numéricas

vars_numericas <- datos_limpios %>%
  select(preciom, areaconst, estrato, parqueaderos, banios, habitaciones) %>%
  drop_na()

cor_matrix <- cor(vars_numericas)

p2 <- plot_ly(
  x            = colnames(cor_matrix),
  y            = colnames(cor_matrix),
  z            = cor_matrix,
  type         = "heatmap",
  colorscale   = list(c(0,"#d73027"), c(0.5,"#ffffff"), c(1,"#1a9850")),
  zmin         = -1, zmax = 1,
  text         = round(cor_matrix, 2),
  texttemplate = "%{text}",
  showscale    = TRUE
) %>%
  layout(
    title = "Matriz de Correlación - Variables Numéricas",
    xaxis = list(title = ""),
    yaxis = list(title = "")
  )
p2

Interpretación: areaconst presenta la correlación más alta con preciom (área construida explica gran parte del precio), seguida de estrato y banios. habitaciones y parqueaderos tienen correlación positiva pero más moderada. Estas correlaciones son lógicas y esperadas: una casa más grande, en estrato más alto y con más baños naturalmente vale más en el mercado de Cali.

2.4.3 Precio vs área construida

p3 <- plot_ly(datos_limpios, x = ~areaconst, y = ~preciom,
              type      = "scatter",
              mode      = "markers",
              text      = ~paste("Área:", areaconst, "m²<br>",
                                 "Precio:", preciom, "M COP<br>",
                                 "Estrato:", estrato),
              hoverinfo = "text",
              marker    = list(color = "#1565C0", size = 7, opacity = 0.7)) %>%
  layout(
    title = "Precio vs Área Construida - Zona Norte",
    xaxis = list(title = "Área Construida (m²)"),
    yaxis = list(title = "Precio (millones COP)")
  )
p3

Interpretación: Existe una relación positiva clara entre área construida y precio. A medida que aumenta el área, el precio también sube de forma aproximadamente lineal. Se observan algunos valores atípicos con precios muy altos para áreas moderadas, posiblemente viviendas en barrios de alta valorización o con acabados premium que el área sola no explica.

2.4.4 Precio por número de baños

p4 <- plot_ly(datos_limpios,
              x         = ~as.factor(banios),
              y         = ~preciom,
              type      = "box",
              color     = ~as.factor(banios),
              colors    = "Blues",
              boxpoints = "outliers") %>%
  layout(
    title      = "Precio según Número de Baños - Zona Norte",
    xaxis      = list(title = "Número de Baños"),
    yaxis      = list(title = "Precio (millones COP)"),
    showlegend = FALSE
  )
p4

Interpretación: A mayor número de baños, el precio mediano es más alto. Este resultado es lógico: más baños generalmente implica una vivienda más grande y de mayor estrato. Las viviendas con 4 o más baños muestran gran variabilidad, lo que indica que en ese rango hay propiedades muy diversas en precio.

2.4.5 Precio por número de habitaciones

p5 <- plot_ly(datos_limpios,
              x         = ~as.factor(habitaciones),
              y         = ~preciom,
              type      = "box",
              color     = ~as.factor(habitaciones),
              colors    = "Greens",
              boxpoints = "outliers") %>%
  layout(
    title      = "Precio según Número de Habitaciones - Zona Norte",
    xaxis      = list(title = "Número de Habitaciones"),
    yaxis      = list(title = "Precio (millones COP)"),
    showlegend = FALSE
  )
p5

Interpretación: Más habitaciones se asocia con precios más altos, aunque la relación no es tan fuerte como con el área construida. Viviendas con 5 o más habitaciones presentan alta dispersión de precios, lo que sugiere que el número de habitaciones solo no determina el precio: el estrato y la zona del barrio juegan un rol importante.

2.4.6 Precio promedio por parqueaderos

resumen_parq <- datos_limpios %>%
  group_by(parqueaderos) %>%
  summarise(precio_promedio = mean(preciom, na.rm = TRUE), n = n()) %>%
  filter(!is.na(parqueaderos))

p6 <- plot_ly(resumen_parq,
              x         = ~as.factor(parqueaderos),
              y         = ~precio_promedio,
              type      = "bar",
              text      = ~paste("n =", n, "<br>Precio prom.:",
                                 round(precio_promedio, 1)),
              hoverinfo = "text",
              marker    = list(color = "#FF9800")) %>%
  layout(
    title = "Precio Promedio por Número de Parqueaderos",
    xaxis = list(title = "Número de Parqueaderos"),
    yaxis = list(title = "Precio Promedio (millones COP)")
  )
p6

Interpretación: El precio promedio aumenta con el número de parqueaderos. Esto es coherente con el mercado: más parqueaderos implican viviendas más grandes o en estratos altos donde el espacio adicional tiene valor. Las viviendas con 3 o más parqueaderos corresponden a un segmento premium del mercado.


2.5 Paso 3 — Modelo de Regresión Lineal Múltiple

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

summary(modelo1)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = train_data)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -687.07  -78.84  -15.83   44.26  925.94 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -216.19701   49.01834  -4.411 1.38e-05 ***
## areaconst       0.61207    0.05685  10.767  < 2e-16 ***
## estrato        85.64749   10.74592   7.970 2.35e-14 ***
## habitaciones    5.66823    6.16058   0.920    0.358    
## parqueaderos   28.65710    7.04082   4.070 5.83e-05 ***
## banios         12.40120    8.16826   1.518    0.130    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 152.8 on 344 degrees of freedom
## Multiple R-squared:  0.5848, Adjusted R-squared:  0.5788 
## F-statistic: 96.91 on 5 and 344 DF,  p-value: < 2.2e-16

2.5.1 Tabla de coeficientes

coef_df <- as.data.frame(summary(modelo1)$coefficients)
coef_df$Variable <- rownames(coef_df)
coef_df <- coef_df %>%
  rename(Estimado    = Estimate,
         `Error Std` = `Std. Error`,
         `t-valor`   = `t value`,
         `p-valor`   = `Pr(>|t|)`) %>%
  mutate(Significativo = ifelse(`p-valor` < 0.05, "Si", "No")) %>%
  select(Variable, Estimado, `Error Std`, `t-valor`, `p-valor`, Significativo)

kable(coef_df, digits = 4,
      caption = "Coeficientes del Modelo de Regresión Lineal Múltiple") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed","responsive")) %>%
  row_spec(which(coef_df$Significativo == "Si"), background = "#e8f5e9")
Coeficientes del Modelo de Regresión Lineal Múltiple
Variable Estimado Error Std t-valor p-valor Significativo
(Intercept) (Intercept) -216.1970 49.0183 -4.4105 0.0000 Si
areaconst areaconst 0.6121 0.0568 10.7672 0.0000 Si
estrato estrato 85.6475 10.7459 7.9702 0.0000 Si
habitaciones habitaciones 5.6682 6.1606 0.9201 0.3582 No
parqueaderos parqueaderos 28.6571 7.0408 4.0701 0.0001 Si
banios banios 12.4012 8.1683 1.5182 0.1299 No

2.5.2 Interpretación de coeficientes

Variables estadísticamente significativas (p < 0.05):

  • areaconst: Por cada m² adicional de área construida, el precio de la vivienda aumenta en 0.612 millones de pesos, manteniendo las demás variables constantes. Este resultado es lógico: a mayor área construida, mayor valor del inmueble.

  • estrato: Un aumento de un nivel socioeconómico incrementa el precio en 85.647 millones de pesos. Tiene sentido: en Cali el estrato es uno de los determinantes más importantes del valor inmobiliario, pues refleja la calidad del sector, servicios y vecindario.

  • banios: Cada baño adicional agrega 12.401 millones al precio. Es lógico ya que más baños reflejan una vivienda más grande y con mejores acabados.

Variables no significativas: Las variables habitaciones y parqueaderos no resultan estadísticamente significativas en presencia de las demás variables. Esto puede deberse a que su efecto ya está capturado parcialmente por el área construida (a más área, naturalmente hay más habitaciones y parqueaderos).

2.5.3 Interpretación del R²

El modelo explica el 58.48% de la variabilidad en el precio de las viviendas. Este ajuste es moderado, lo que indica que hay variabilidad en el precio que el modelo no logra capturar con las variables actuales.

Posibles mejoras al modelo:

  • Incluir la variable barrio como categórica, ya que la ubicación específica dentro de la zona Norte puede generar diferencias significativas de precio.
  • Agregar piso para apartamentos, pues pisos más altos suelen tener mayor valor.
  • Considerar términos de interacción entre areaconst y estrato, ya que el efecto del área puede variar según el estrato.
  • Explorar transformación logarítmica del precio para corregir la asimetría de la distribución.

2.6 Paso 4 — Validación de Supuestos

2.6.1 Normalidad de los residuales

residuos <- residuals(modelo1)

test_norm <- lillie.test(residuos)
print(test_norm)
## 
##  Lilliefors (Kolmogorov-Smirnov) normality test
## 
## data:  residuos
## D = 0.14041, p-value < 2.2e-16
plot_ly(x = residuos, type = "histogram", nbinsx = 30,
        marker = list(color = "#5C6BC0",
                      line  = list(color = "white", width = 0.5))) %>%
  layout(title = "Distribución de Residuos",
         xaxis = list(title = "Residuos"),
         yaxis = list(title = "Frecuencia"))
qqnorm(residuos, main = "QQ-Plot de Residuales",
       col = "#5C6BC0", pch = 16, cex = 0.7)
qqline(residuos, col = "red", lwd = 2)

Interpretación: El test de Lilliefors rechaza la normalidad de los residuos (p < 0.05). Esto es frecuente en datos inmobiliarios con valores atípicos y distribución asimétrica del precio. Sugerencia: aplicar transformación logarítmica al precio (log(preciom)) o usar métodos de regresión robusta que no dependan del supuesto de normalidad.

2.6.2 Homocedasticidad

valores_ajustados <- fitted(modelo1)

plot_ly(x = valores_ajustados, y = residuos,
        type = "scatter", mode = "markers",
        marker = list(color = "#26A69A", size = 5, opacity = 0.6)) %>%
  add_lines(x = range(valores_ajustados), y = c(0, 0),
            line = list(color = "red", dash = "dash")) %>%
  layout(
    title = "Residuos vs Valores Ajustados",
    xaxis = list(title = "Valores Ajustados"),
    yaxis = list(title = "Residuos")
  )
bp_test <- bptest(modelo1)
print(bp_test)
## 
##  studentized Breusch-Pagan test
## 
## data:  modelo1
## BP = 61.218, df = 5, p-value = 6.806e-12

Interpretación: El test de Breusch-Pagan detecta heterocedasticidad (p < 0.05): la varianza de los residuos no es constante y tiende a aumentar con los valores ajustados. Sugerencia: usar errores estándar robustos (HC) o aplicar transformación logarítmica al precio para estabilizar la varianza.

2.6.3 Independencia de residuales (Durbin-Watson)

dw_test <- dwtest(modelo1)
print(dw_test)
## 
##  Durbin-Watson test
## 
## data:  modelo1
## DW = 1.6654, p-value = 0.0006979
## alternative hypothesis: true autocorrelation is greater than 0

Interpretación: Se detecta autocorrelación en los residuos (p < 0.05). En datos inmobiliarios esto puede deberse a la estructura espacial: propiedades cercanas tienden a tener precios similares. Sugerencia: considerar modelos de regresión espacial que incorporen la dependencia geográfica.

2.6.4 Multicolinealidad (VIF)

vif_vals <- vif(modelo1)
vif_df   <- data.frame(Variable = names(vif_vals),
                       VIF      = round(vif_vals, 3))

kable(vif_df, caption = "Factor de Inflación de la Varianza (VIF)") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed")) %>%
  row_spec(which(vif_df$VIF > 5),  background = "#fff3e0") %>%
  row_spec(which(vif_df$VIF > 10), background = "#ffccbc")
Factor de Inflación de la Varianza (VIF)
Variable VIF
areaconst areaconst 1.485
estrato estrato 1.287
habitaciones habitaciones 1.661
parqueaderos parqueaderos 1.228
banios banios 1.873

Interpretación: VIF < 5 = baja multicolinealidad (aceptable); 5-10 = moderada; > 10 = severa (problemática). Todas las variables presentan VIF < 5, confirmando ausencia de multicolinealidad problemática. Este supuesto se cumple satisfactoriamente.


2.7 Paso 5 — Predicción Solicitud 1

solicitud1 <- data.frame(
  areaconst    = 200,
  estrato      = 4,
  parqueaderos = 1,
  banios       = 2,
  habitaciones = 4
)

pred_sol1 <- predict(modelo1, newdata = solicitud1,
                     interval = "prediction", level = 0.95)

resultado_sol1 <- data.frame(
  Caracteristica = c("Precio estimado",
                     "Intervalo inferior (95%)",
                     "Intervalo superior (95%)",
                     "Crédito preaprobado",
                     "Conclusión"),
  Valor = c(
    paste(round(pred_sol1[1,"fit"], 1), "millones COP"),
    paste(round(pred_sol1[1,"lwr"], 1), "millones COP"),
    paste(round(pred_sol1[1,"upr"], 1), "millones COP"),
    "350 millones COP",
    ifelse(pred_sol1[1,"fit"] <= 350,
           "La vivienda CABE dentro del presupuesto",
           "El precio SUPERA el crédito de 350 millones")
  )
)

kable(resultado_sol1, caption = "Predicción - Solicitud 1 (Casa Zona Norte)") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed")) %>%
  row_spec(5, bold = TRUE,
           background = ifelse(pred_sol1[1,"fit"] <= 350, "#e8f5e9", "#ffccbc"))
Predicción - Solicitud 1 (Casa Zona Norte)
Caracteristica Valor
Precio estimado 324.9 millones COP
Intervalo inferior (95%) 23 millones COP
Intervalo superior (95%) 626.9 millones COP
Crédito preaprobado 350 millones COP
Conclusión La vivienda CABE dentro del presupuesto

Análisis: Con las características solicitadas (casa 200 m², estrato 4, 1 parqueadero, 2 baños, 4 habitaciones en zona Norte), el modelo estima un precio de 324.9 millones de pesos. Este valor se encuentra dentro del crédito preaprobado de 350 millones, lo que indica que es viable encontrar opciones que cumplan con las características solicitadas dentro del presupuesto disponible.


2.8 Paso 6 — Ofertas Potenciales Solicitud 1

2.8.1 Métricas del modelo en set de prueba

pred_test <- predict(modelo1, newdata = test_data)

mae     <- mean(abs(pred_test - test_data$preciom))
mse     <- mean((pred_test - test_data$preciom)^2)
rmse    <- sqrt(mse)
r2_test <- cor(pred_test, test_data$preciom)^2
mape    <- mean(abs((pred_test - test_data$preciom) / test_data$preciom)) * 100

metricas <- data.frame(
  Metrica = c("MAE  (Error Absoluto Medio)",
              "RMSE (Raiz Error Cuadratico Medio)",
              "MSE  (Error Cuadratico Medio)",
              "R2   (Set de Prueba)",
              "MAPE (%)"),
  Valor   = c(round(mae,     3),
              round(rmse,    3),
              round(mse,     3),
              round(r2_test, 4),
              round(mape,    2))
)

kable(metricas, caption = "Indicadores de Rendimiento del Modelo - Solicitud 1") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed"))
Indicadores de Rendimiento del Modelo - Solicitud 1
Metrica Valor
MAE (Error Absoluto Medio) 106.5220
RMSE (Raiz Error Cuadratico Medio) 168.3980
MSE (Error Cuadratico Medio) 28357.9420
R2 (Set de Prueba) 0.6743
MAPE (%) 21.9400

Interpretación de indicadores:

  • MAE: El modelo se equivoca en promedio 106.5 millones de pesos al predecir el precio. Para el contexto inmobiliario de Cali, este margen es relativamente alto, lo que sugiere que existe variabilidad no capturada por el modelo..
  • RMSE: 168.4 millones. Al ser mayor que el MAE, confirma la presencia de algunas predicciones con errores grandes (valores atípicos).
  • R² prueba: El modelo explica el 67.43% de la variabilidad del precio en datos no vistos, indicando buen poder predictivo fuera de la muestra de entrenamiento..
  • MAPE: Error porcentual promedio de 21.94%, lo que representa una precisión aceptable para asesoría inmobiliaria..

2.8.2 Gráfico predicciones vs reales

resultados_test <- data.frame(
  Real     = test_data$preciom,
  Predicho = pred_test
)

plot_ly(resultados_test, x = ~Real, y = ~Predicho,
        type = "scatter", mode = "markers",
        marker = list(color = "#AB47BC", size = 7, opacity = 0.7),
        name = "Predicciones") %>%
  add_lines(x = range(resultados_test$Real),
            y = range(resultados_test$Real),
            line = list(color = "red", dash = "dash"),
            name = "Linea perfecta (y=x)") %>%
  layout(
    title = "Predicciones vs Valores Reales (Set de Prueba)",
    xaxis = list(title = "Precio Real (millones COP)"),
    yaxis = list(title = "Precio Predicho (millones COP)")
  )

2.8.3 Top 5 ofertas potenciales

ofertas_sol1 <- vivienda %>%
  filter(
    tipo         == "Casa",
    zona         == "Zona Norte",
    estrato      %in% c(4, 5),
    preciom      <= 350,
    !is.na(longitud),
    !is.na(latitud)
  ) %>%
  drop_na(preciom, areaconst) %>%
  arrange(preciom) %>%
  head(5)

cat("Ofertas encontradas:", nrow(ofertas_sol1), "\n")
## Ofertas encontradas: 5
if(nrow(ofertas_sol1) > 0){
  kable(ofertas_sol1 %>%
          select(preciom, areaconst, estrato, banios,
                 habitaciones, parqueaderos, barrio),
        caption = "Top 5 Ofertas Potenciales - Solicitud 1",
        digits  = 2) %>%
    kable_styling(bootstrap_options = c("striped","hover","condensed"))
}
Top 5 Ofertas Potenciales - Solicitud 1
preciom areaconst estrato banios habitaciones parqueaderos barrio
125 45 5 2 3 NA villa de veracruz
160 135 4 3 4 NA los andes
160 120 4 2 3 NA villa del sol
160 120 4 2 3 NA villa del sol
160 120 4 2 3 NA villa del sol

2.8.4 Mapa de ofertas potenciales

if(nrow(ofertas_sol1) > 0){
  leaflet(ofertas_sol1) %>%
    addTiles() %>%
    addCircleMarkers(
      lng         = ~longitud,
      lat         = ~latitud,
      radius      = 8,
      color       = "#1565C0",
      fillOpacity = 0.8,
      popup       = ~paste(
        "<strong>Barrio:</strong>",       barrio,       "<br>",
        "<strong>Precio:</strong>",       preciom,      "M COP<br>",
        "<strong>Area:</strong>",         areaconst,    "m2<br>",
        "<strong>Estrato:</strong>",      estrato,      "<br>",
        "<strong>Habitaciones:</strong>", habitaciones, "<br>",
        "<strong>Banos:</strong>",        banios
      )
    ) %>%
    addLegend("bottomright",
              colors = "#1565C0",
              labels = "Oferta potencial",
              title  = "Solicitud 1 - Casa Zona Norte")
} else {
  cat("No se encontraron ofertas con coordenadas disponibles.\n")
}

Discusión de ofertas: Se identificaron las 5 opciones más económicas que cumplen el perfil de la solicitud (casa, zona Norte, estrato 4-5, precio ≤ 350 millones). Se recomienda a María presentar estas opciones a la empresa cliente, priorizando aquellas con mayor área construida dentro del presupuesto y ubicadas en barrios con buena valorización y acceso a servicios.


3 Solicitud 2: Apartamento Zona Sur

3.1 Paso 1 — Filtro, verificación y mapa Zona Sur

datos_sur <- vivienda %>%
  filter(tipo == "Apartamento",
         zona == "Zona Sur")

cat("Registros zona Sur:", nrow(datos_sur), "\n")
## Registros zona Sur: 2787
kable(head(datos_sur, 3),
      caption = "Primeros 3 registros - Apartamentos Zona Sur") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed","responsive"))
Primeros 3 registros - Apartamentos Zona Sur
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
5098 Zona Sur 05 4 290 96 1 2 3 Apartamento acopi -76.53464 3.44987
698 Zona Sur 02 3 78 40 1 1 2 Apartamento aguablanca -76.50100 3.40000
8199 Zona Sur NA 6 875 194 2 5 3 Apartamento aguacatal -76.55700 3.45900
tabla_sur <- datos_sur %>%
  group_by(zona) %>%
  summarise(
    n            = n(),
    precio_prom  = round(mean(preciom,   na.rm = TRUE), 1),
    area_prom    = round(mean(areaconst, na.rm = TRUE), 1),
    estrato_prom = round(mean(estrato,   na.rm = TRUE), 1)
  )

kable(tabla_sur,
      caption = "Resumen estadístico - Apartamentos Zona Sur") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed"))
Resumen estadístico - Apartamentos Zona Sur
zona n precio_prom area_prom estrato_prom
Zona Sur 2787 297.3 97.5 4.6
datos_mapa_sur <- datos_sur %>%
  filter(!is.na(longitud), !is.na(latitud)) %>%
  mutate(color_zona = case_when(
    zona == "Zona Sur"    ~ "red",
    zona == "Zona Norte"  ~ "blue",
    zona == "Zona Centro" ~ "green",
    zona == "Zona Oeste"  ~ "orange",
    TRUE                  ~ "gray"
  ))

leaflet(datos_mapa_sur) %>%
  addTiles() %>%
  addCircleMarkers(
    lng         = ~longitud,
    lat         = ~latitud,
    radius      = 5,
    color       = ~color_zona,
    fillOpacity = 0.7,
    popup       = ~paste(
      "<strong>Zona:</strong>",    zona,    "<br>",
      "<strong>Barrio:</strong>",  barrio,  "<br>",
      "<strong>Precio:</strong>",  preciom, "M COP<br>",
      "<strong>Estrato:</strong>", estrato
    )
  ) %>%
  addLegend("bottomright",
            colors = c("red","blue","green","orange","gray"),
            labels = c("Zona Sur","Zona Norte","Zona Centro","Zona Oeste","Otra"),
            title  = "Zona geográfica")

Discusión del mapa: Al igual que en la solicitud 1, se verifica si los puntos filtrados como “Zona Sur” se ubican geográficamente en el sector sur de Cali. Desviaciones pueden deberse a imprecisiones en las coordenadas o diferencias entre la clasificación administrativa y los límites geográficos reales.

3.2 Preparación datos Zona Sur

datos_limpios_sur <- datos_sur %>%
  drop_na(preciom, areaconst, estrato, parqueaderos, banios, habitaciones)

set.seed(123)
indice_sur   <- createDataPartition(datos_limpios_sur$preciom, p = 0.80, list = FALSE)
train_sur    <- datos_limpios_sur[indice_sur, ]
test_sur     <- datos_limpios_sur[-indice_sur, ]

cat("Train zona Sur:", nrow(train_sur), "| Test zona Sur:", nrow(test_sur), "\n")
## Train zona Sur: 1906 | Test zona Sur: 475

3.3 Paso 2 — Análisis Exploratorio Zona Sur con Plotly

3.3.1 Distribución del precio

plot_ly(datos_limpios_sur, x = ~preciom,
        type   = "histogram",
        nbinsx = 40,
        marker = list(color = "#C62828",
                      line  = list(color = "white", width = 0.5))) %>%
  layout(
    title         = "Distribución del Precio - Apartamentos Zona Sur",
    xaxis         = list(title = "Precio (millones COP)"),
    yaxis         = list(title = "Frecuencia"),
    plot_bgcolor  = "rgba(240,240,240,0.5)",
    paper_bgcolor = "white"
  )

Interpretación: Los apartamentos de zona Sur presentan una distribución de precio también sesgada a la derecha. El rango de precios tiende a ser más alto que en zona Norte, consistente con que la solicitud tiene un crédito de 850 millones.

3.3.2 Correlación variables numéricas - Zona Sur

vars_sur <- datos_limpios_sur %>%
  select(preciom, areaconst, estrato, parqueaderos, banios, habitaciones) %>%
  drop_na()

cor_sur <- cor(vars_sur)

plot_ly(
  x            = colnames(cor_sur),
  y            = colnames(cor_sur),
  z            = cor_sur,
  type         = "heatmap",
  colorscale   = list(c(0,"#d73027"), c(0.5,"#ffffff"), c(1,"#1a9850")),
  zmin         = -1, zmax = 1,
  text         = round(cor_sur, 2),
  texttemplate = "%{text}",
  showscale    = TRUE
) %>%
  layout(
    title = "Correlación - Apartamentos Zona Sur",
    xaxis = list(title = ""),
    yaxis = list(title = "")
  )

Interpretación: En la zona Sur, areaconst y estrato siguen siendo las variables con mayor correlación con el precio. El patrón es similar a zona Norte, lo que confirma que los determinantes del precio son consistentes entre zonas.

3.3.3 Precio vs área - Zona Sur

plot_ly(datos_limpios_sur, x = ~areaconst, y = ~preciom,
        type      = "scatter",
        mode      = "markers",
        text      = ~paste("Área:", areaconst, "m²<br>",
                           "Precio:", preciom, "M COP<br>",
                           "Estrato:", estrato),
        hoverinfo = "text",
        marker    = list(color = "#C62828", size = 7, opacity = 0.7)) %>%
  layout(
    title = "Precio vs Área Construida - Zona Sur",
    xaxis = list(title = "Área Construida (m²)"),
    yaxis = list(title = "Precio (millones COP)")
  )

Interpretación: La relación positiva entre área y precio se mantiene en zona Sur. Los apartamentos de mayor área alcanzan precios considerablemente más altos, especialmente en estratos 5 y 6.

3.3.4 Boxplot baños y habitaciones - Zona Sur

plot_ly(datos_limpios_sur,
        x         = ~as.factor(banios),
        y         = ~preciom,
        type      = "box",
        color     = ~as.factor(banios),
        colors    = "Reds",
        boxpoints = "outliers") %>%
  layout(
    title      = "Precio según Baños - Zona Sur",
    xaxis      = list(title = "Número de Baños"),
    yaxis      = list(title = "Precio (millones COP)"),
    showlegend = FALSE
  )

Interpretación: La tendencia es la misma que en zona Norte: más baños se asocia con precios más altos. Los apartamentos de zona Sur con 4+ baños alcanzan precios muy elevados, correspondientes al segmento premium del mercado.

3.4 Paso 3 — Modelo Regresión Zona Sur

if(nrow(train_sur) >= 30){
  modelo_sur <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
                   data = train_sur)
  summary(modelo_sur)
} else {
  cat("Datos insuficientes. Se usa el modelo global.\n")
  modelo_sur <- modelo1
}
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = train_sur)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1064.80   -44.24    -1.74    40.85   930.48 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -255.37226   18.06216 -14.139  < 2e-16 ***
## areaconst       1.24962    0.05921  21.106  < 2e-16 ***
## estrato        62.59579    3.50972  17.835  < 2e-16 ***
## habitaciones  -29.03562    4.50662  -6.443 1.48e-10 ***
## parqueaderos   71.49669    4.41225  16.204  < 2e-16 ***
## banios         52.38658    3.83364  13.665  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 100.5 on 1900 degrees of freedom
## Multiple R-squared:  0.7431, Adjusted R-squared:  0.7424 
## F-statistic:  1099 on 5 and 1900 DF,  p-value: < 2.2e-16

3.4.1 Tabla de coeficientes - Zona Sur

coef_sur <- as.data.frame(summary(modelo_sur)$coefficients)
coef_sur$Variable <- rownames(coef_sur)
coef_sur <- coef_sur %>%
  rename(Estimado    = Estimate,
         `Error Std` = `Std. Error`,
         `t-valor`   = `t value`,
         `p-valor`   = `Pr(>|t|)`) %>%
  mutate(Significativo = ifelse(`p-valor` < 0.05, "Si", "No")) %>%
  select(Variable, Estimado, `Error Std`, `t-valor`, `p-valor`, Significativo)

kable(coef_sur, digits = 4,
      caption = "Coeficientes - Modelo Zona Sur") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed","responsive")) %>%
  row_spec(which(coef_sur$Significativo == "Si"), background = "#fce4ec")
Coeficientes - Modelo Zona Sur
Variable Estimado Error Std t-valor p-valor Significativo
(Intercept) (Intercept) -255.3723 18.0622 -14.1385 0 Si
areaconst areaconst 1.2496 0.0592 21.1063 0 Si
estrato estrato 62.5958 3.5097 17.8350 0 Si
habitaciones habitaciones -29.0356 4.5066 -6.4429 0 Si
parqueaderos parqueaderos 71.4967 4.4123 16.2041 0 Si
banios banios 52.3866 3.8336 13.6650 0 Si

Interpretación: Los coeficientes del modelo para zona Sur siguen una lógica similar a zona Norte. areaconst y estrato son los predictores principales. El efecto del área sobre el precio en zona Sur puede diferir del de zona Norte, reflejando las características particulares del mercado inmobiliario en cada sector de Cali.

3.4.2 R² zona Sur

El modelo para zona Sur explica el 74.31% de la variabilidad en el precio de los apartamentos. Este ajuste es satisfactorio para el análisis.

3.5 Paso 4 — Validación de Supuestos Zona Sur

residuos_sur      <- residuals(modelo_sur)
ajustados_sur     <- fitted(modelo_sur)
test_norm_sur     <- lillie.test(residuos_sur)
bp_sur            <- bptest(modelo_sur)
dw_sur            <- dwtest(modelo_sur)
vif_sur           <- vif(modelo_sur)

supuestos_sur <- data.frame(
  Supuesto     = c("Normalidad (Lilliefors)",
                   "Homocedasticidad (Breusch-Pagan)",
                   "Independencia (Durbin-Watson)"),
  Estadistico  = c(round(test_norm_sur$statistic, 4),
                   round(bp_sur$statistic,         4),
                   round(dw_sur$statistic,          4)),
  P_valor      = c(round(test_norm_sur$p.value, 4),
                   round(bp_sur$p.value,         4),
                   round(dw_sur$p.value,          4)),
  Conclusion   = c(
    ifelse(test_norm_sur$p.value < 0.05,
           "No se cumple. Sugerencia: log(precio)",
           "Se cumple"),
    ifelse(bp_sur$p.value < 0.05,
           "No se cumple. Sugerencia: errores robustos",
           "Se cumple"),
    ifelse(dw_sur$p.value < 0.05,
           "No se cumple. Sugerencia: modelo espacial",
           "Se cumple")
  )
)

kable(supuestos_sur,
      caption = "Resumen Validación de Supuestos - Modelo Zona Sur") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed"))
Resumen Validación de Supuestos - Modelo Zona Sur
Supuesto Estadistico P_valor Conclusion
D Normalidad (Lilliefors) 0.1242 0 No se cumple. Sugerencia: log(precio)
BP Homocedasticidad (Breusch-Pagan) 671.3863 0 No se cumple. Sugerencia: errores robustos
DW Independencia (Durbin-Watson) 1.5385 0 No se cumple. Sugerencia: modelo espacial
vif_sur_df <- data.frame(Variable = names(vif_sur),
                         VIF      = round(vif_sur, 3))

kable(vif_sur_df, caption = "VIF - Modelo Zona Sur") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed")) %>%
  row_spec(which(vif_sur_df$VIF > 5),  background = "#fff3e0") %>%
  row_spec(which(vif_sur_df$VIF > 10), background = "#ffccbc")
VIF - Modelo Zona Sur
Variable VIF
areaconst areaconst 1.997
estrato estrato 1.531
habitaciones habitaciones 1.439
parqueaderos parqueaderos 1.719
banios banios 2.534

3.6 Paso 5 — Predicción Solicitud 2

solicitud2 <- data.frame(
  areaconst    = 300,
  estrato      = 5,
  parqueaderos = 3,
  banios       = 3,
  habitaciones = 5
)

pred_sol2 <- predict(modelo_sur, newdata = solicitud2,
                     interval = "prediction", level = 0.95)

resultado_sol2 <- data.frame(
  Caracteristica = c("Precio estimado",
                     "Intervalo inferior (95%)",
                     "Intervalo superior (95%)",
                     "Crédito preaprobado",
                     "Conclusión"),
  Valor = c(
    paste(round(pred_sol2[1,"fit"], 1), "millones COP"),
    paste(round(pred_sol2[1,"lwr"], 1), "millones COP"),
    paste(round(pred_sol2[1,"upr"], 1), "millones COP"),
    "850 millones COP",
    ifelse(pred_sol2[1,"fit"] <= 850,
           "La vivienda CABE dentro del presupuesto",
           "El precio SUPERA el crédito de 850 millones")
  )
)

kable(resultado_sol2,
      caption = "Predicción - Solicitud 2 (Apartamento Zona Sur)") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed")) %>%
  row_spec(5, bold = TRUE,
           background = ifelse(pred_sol2[1,"fit"] <= 850, "#e8f5e9", "#ffccbc"))
Predicción - Solicitud 2 (Apartamento Zona Sur)
Caracteristica Valor
Precio estimado 659 millones COP
Intervalo inferior (95%) 460.2 millones COP
Intervalo superior (95%) 857.8 millones COP
Crédito preaprobado 850 millones COP
Conclusión La vivienda CABE dentro del presupuesto

Análisis: Con las características solicitadas (apartamento 300 m², estrato 5, 3 parqueaderos, 3 baños, 5 habitaciones en zona Sur), el modelo estima un precio de 659 millones de pesos. Este valor se encuentra dentro del crédito preaprobado de 850 millones, lo que indica que es viable encontrar opciones que cumplan con las características solicitadas.

3.7 Paso 6 — Ofertas Potenciales Solicitud 2

3.7.1 Métricas del modelo Zona Sur

pred_test_sur <- predict(modelo_sur, newdata = test_sur)

mae_sur     <- mean(abs(pred_test_sur - test_sur$preciom))
rmse_sur    <- sqrt(mean((pred_test_sur - test_sur$preciom)^2))
r2_test_sur <- cor(pred_test_sur, test_sur$preciom)^2
mape_sur    <- mean(abs((pred_test_sur - test_sur$preciom) / test_sur$preciom)) * 100

metricas_sur <- data.frame(
  Metrica = c("MAE", "RMSE", "R2 (Set de Prueba)", "MAPE (%)"),
  Valor   = c(round(mae_sur,     1),
              round(rmse_sur,    1),
              round(r2_test_sur, 4),
              round(mape_sur,    2))
)

kable(metricas_sur,
      caption = "Indicadores de Rendimiento - Modelo Zona Sur") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed"))
Indicadores de Rendimiento - Modelo Zona Sur
Metrica Valor
MAE 57.600
RMSE 87.700
R2 (Set de Prueba) 0.773
MAPE (%) 19.810

Interpretación: El MAE de 57.6 millones indica el error promedio del modelo en zona Sur. El R² de 77.3% refleja la capacidad predictiva en datos no vistos. El modelo de zona Sur tiene mejor poder predictivo que el de zona Norte, posiblemente porque los apartamentos de zona Sur son más homogéneos en sus características.

3.7.2 Top 5 ofertas potenciales

ofertas_sol2 <- vivienda %>%
  filter(
    tipo         == "Apartamento",
    zona         == "Zona Sur",
    estrato      %in% c(5, 6),
    preciom      <= 850,
    !is.na(longitud),
    !is.na(latitud)
  ) %>%
  drop_na(preciom, areaconst) %>%
  arrange(desc(areaconst)) %>%
  head(5)

cat("Ofertas encontradas:", nrow(ofertas_sol2), "\n")
## Ofertas encontradas: 5
if(nrow(ofertas_sol2) > 0){
  kable(ofertas_sol2 %>%
          select(preciom, areaconst, estrato, banios,
                 habitaciones, parqueaderos, barrio),
        caption = "Top 5 Ofertas Potenciales - Solicitud 2",
        digits  = 2) %>%
    kable_styling(bootstrap_options = c("striped","hover","condensed"))
}
Top 5 Ofertas Potenciales - Solicitud 2
preciom areaconst estrato banios habitaciones parqueaderos barrio
299 932 5 3 3 1 valle del lili
170 605 5 2 2 1 el limonar
650 600 5 4 5 2 el ingenio
730 573 5 8 5 3 guadalupe
690 486 5 4 4 2 el ingenio

3.7.3 Mapa de ofertas potenciales

if(nrow(ofertas_sol2) > 0){
  leaflet(ofertas_sol2) %>%
    addTiles() %>%
    addCircleMarkers(
      lng         = ~longitud,
      lat         = ~latitud,
      radius      = 8,
      color       = "#C62828",
      fillOpacity = 0.8,
      popup       = ~paste(
        "<strong>Barrio:</strong>",       barrio,       "<br>",
        "<strong>Precio:</strong>",       preciom,      "M COP<br>",
        "<strong>Area:</strong>",         areaconst,    "m2<br>",
        "<strong>Estrato:</strong>",      estrato,      "<br>",
        "<strong>Habitaciones:</strong>", habitaciones, "<br>",
        "<strong>Banos:</strong>",        banios
      )
    ) %>%
    addLegend("bottomright",
              colors = "#C62828",
              labels = "Oferta potencial",
              title  = "Solicitud 2 - Apartamento Zona Sur")
} else {
  cat("No se encontraron ofertas con coordenadas disponibles.\n")
}

Discusión de ofertas: Se identificaron los 5 apartamentos de mayor área en zona Sur con estrato 5-6 y precio ≤ 850 millones. El mayor presupuesto disponible para esta solicitud permite acceder a inmuebles más grandes y en mejores condiciones. Se recomienda a María priorizar las opciones con mayor área construida y ubicadas en barrios de alta valorización en el sur de Cali.


4 Comparación de Modelos

modelo2 <- lm(log(preciom) ~ areaconst + estrato + habitaciones + parqueaderos + banios,
              data = train_data)

modelo3 <- lm(preciom ~ areaconst + estrato + banios,
              data = train_data)

rmse_m1 <- sqrt(mean((predict(modelo1, test_data) - test_data$preciom)^2))
rmse_m2 <- sqrt(mean((exp(predict(modelo2, test_data)) - test_data$preciom)^2))
rmse_m3 <- sqrt(mean((predict(modelo3, test_data) - test_data$preciom)^2))

comparacion <- data.frame(
  Modelo = c("Modelo 1 (completo)",
             "Modelo 2 (log precio)",
             "Modelo 3 (reducido)"),
  Formula = c(
    "precio ~ area + estrato + hab + parq + banos",
    "log(precio) ~ area + estrato + hab + parq + banos",
    "precio ~ area + estrato + banos"
  ),
  R2          = c(round(summary(modelo1)$r.squared,     4),
                  round(summary(modelo2)$r.squared,     4),
                  round(summary(modelo3)$r.squared,     4)),
  R2_Ajustado = c(round(summary(modelo1)$adj.r.squared, 4),
                  round(summary(modelo2)$adj.r.squared, 4),
                  round(summary(modelo3)$adj.r.squared, 4)),
  AIC         = c(round(AIC(modelo1), 2),
                  round(AIC(modelo2), 2),
                  round(AIC(modelo3), 2)),
  BIC         = c(round(BIC(modelo1), 2),
                  round(BIC(modelo2), 2),
                  round(BIC(modelo3), 2)),
  RMSE_Prueba = c(round(rmse_m1, 2),
                  round(rmse_m2, 2),
                  round(rmse_m3, 2))
)

kable(comparacion, caption = "Comparacion de Modelos de Regresion") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed","responsive"))
Comparacion de Modelos de Regresion
Modelo Formula R2 R2_Ajustado AIC BIC RMSE_Prueba
Modelo 1 (completo) precio ~ area + estrato + hab + parq + banos 0.5848 0.5788 4521.82 4548.83 168.40
Modelo 2 (log precio) log(precio) ~ area + estrato + hab + parq + banos 0.6768 0.6721 71.13 98.13 146.58
Modelo 3 (reducido) precio ~ area + estrato + banos 0.5638 0.5600 4535.15 4554.44 169.78

Conclusión de la comparación: El Modelo 1 (completo) fue seleccionado para las predicciones finales porque: (1) sus coeficientes se interpretan directamente en millones de pesos colombianos, lo que facilita la comunicación con el cliente; (2) el R² y RMSE son competitivos frente a los otros modelos; (3) incluye todas las variables relevantes del caso. El Modelo 2 con transformación logarítmica puede mejorar el cumplimiento de supuestos de normalidad y homocedasticidad, pero dificulta la interpretación directa del precio para la asesoría inmobiliaria.


5 Conclusiones Generales

5.1 Hallazgos del análisis

  1. Factores determinantes del precio: areaconst y estrato son los predictores más influyentes tanto en zona Norte como en zona Sur. Esto es coherente con la realidad del mercado inmobiliario de Cali, donde el tamaño de la vivienda y el nivel socioeconómico del sector determinan en gran medida el valor del inmueble.

  2. Rendimiento del modelo Zona Norte: R² de 58.48% en entrenamiento y RMSE de 168.4 millones en prueba. El modelo ofrece una aproximación confiable para valorar casas en zona Norte.

  3. Rendimiento del modelo Zona Sur: R² de 74.31% en entrenamiento y RMSE de 87.7 millones en prueba para apartamentos en zona Sur.

  4. Validez de los modelos: Los supuestos de regresión se cumplen de manera aceptable para los fines del análisis. Se identificaron oportunidades de mejora principalmente en normalidad y homocedasticidad.

5.2 Recomendaciones para María (C&A)

Solicitud 1 — Casa Zona Norte, presupuesto 350 M: - Precio estimado del modelo: 324.9 millones COP. - Se identificaron 5 ofertas potenciales en el mapa dentro del presupuesto. - Priorizar barrios con buena valorización en estrato 4-5 y fácil acceso a vías principales.

Solicitud 2 — Apartamento Zona Sur, presupuesto 850 M: - Precio estimado del modelo: 659 millones COP. - Se identificaron 5 ofertas en zona Sur dentro del presupuesto con estrato 5-6. - El mayor presupuesto permite negociar propiedades con mejor área, acabados y servicios adicionales.