install.packages(c("plotly", "nortest", "tseries", "lmtest", "car", "dplyr",
                    "ggplot2", "DT", "devtools"))
devtools::install_github("centro-magis/paqueteMODELOS", force = TRUE)

PARTE I — INFORME EJECUTIVO

1. Contexto y objetivo

María, agente inmobiliaria de C&A, recibió una solicitud de una compañía internacional para asesorar la compra de dos viviendas destinadas a dos de sus empleados. Este informe resume el análisis realizado, las recomendaciones para cada caso, y remite al lector a los Anexos técnicos para el detalle metodológico y estadístico completo.

Característica Vivienda 1 Vivienda 2
Tipo Casa Apartamento
Área construida (m²) 200 300
Parqueaderos 1 3
Baños 2 5
Habitaciones 4 5
Estrato 4 o 5 5 o 6
Zona Norte Sur
Crédito preaprobado $350 millones $850 millones

2. Metodología (resumen)

Se utilizó la base de ofertas inmobiliarias de los últimos tres meses en Cali (paqueteMODELOS::vivienda, 8319 registros). Para cada solicitud se filtró el subconjunto relevante por tipo de vivienda y zona, se depuraron los datos, y se estimaron y compararon modelos de regresión lineal múltiple para explicar el precio en función de las características de la vivienda. El detalle completo de la metodología para la obtención y depuración de los datos está en el Anexo 1, y los resultados técnicos completos (exploración, modelos comparados, validación de supuestos) están en el Anexo 2.

3. Resultados y recomendación — Vivienda 1 (Casa, Zona Norte)

Estrato Precio estimado (M$) IC 95% Cubre credito de $350M?
4 313 [275, 350] Si
5 378 [346, 409] No

Con un modelo que explica el 61% de la variación del precio de las casas en la zona norte (Anexo 2.A), el precio esperado de la Vivienda 1 es de $313 millones si es estrato 4 y de $378 millones si es estrato 5. Dado el crédito preaprobado de $350 millones, se recomienda priorizar la búsqueda en estrato 4, donde el precio esperado sí es cubierto por el crédito; en estrato 5 el precio esperado supera el presupuesto. Se identificaron 112 ofertas reales que cumplen el presupuesto y el estrato solicitado; las 5 más parecidas a los requerimientos del cliente son:

id barrio estrato areaconst habitaciones parqueaderos banios preciom longitud latitud
1270 el bosque 5 203 5 2 2 350 -77 3
1887 vipasa 5 203 4 2 3 340 -77 3
1352 la flora 5 190 3 1 3 350 -77 3
1163 la merced 5 216 4 2 2 350 -77 3
4267 el bosque 5 202 5 1 4 335 -77 3

4. Resultados y recomendación — Vivienda 2 (Apartamento, Zona Sur)

Estrato Precio estimado (M$) IC 95% Cubre credito de $850M?
5 719 [701, 737] Si
6 872 [856, 889] No

Este modelo explica el 78% de la variación del precio (Anexo 2.B). El precio esperado de la Vivienda 2 es de $719 millones en estrato 5 y de $872 millones en estrato 6. El crédito de $850 millones cubre cómodamente el estrato 5, y queda ajustado en el límite para estrato 6. Se identificaron 1439 ofertas reales dentro de presupuesto; las 5 más parecidas son:

id barrio estrato areaconst habitaciones parqueaderos banios preciom longitud latitud
7512 seminario 5 300 6 3 5 670 -77 3
8113 cuarto de legua 5 296 4 2 4 410 -77 3
5306 ciudadela pasoancho 5 275 5 2 5 650 -77 3
7742 cuarto de legua 5 288 4 1 5 490 -77 3
8036 seminario 5 256 5 3 5 530 -77 3

5. Conclusión general

Ambos modelos son estadísticamente significativos y útiles para orientar la búsqueda, aunque ninguno cumple completamente los supuestos clásicos de normalidad de los residuos (ver Anexo 2), por lo que los intervalos de confianza deben tomarse como una guía y no como una cifra exacta. Se recomienda a María iniciar la gestión con las ofertas listadas arriba, priorizando estrato 4 para la Vivienda 1 y estrato 5 para la Vivienda 2 por ajustarse mejor al crédito preaprobado de cada cliente.


PARTE II — ANEXOS

Anexo 1: Plan de trabajo — metodología para la obtención y depuración de los datos

Fuente de datos. La información proviene del paquete del curso paqueteMODELOS (repositorio centro-magis/paqueteMODELOS en GitHub), objeto vivienda, que contiene 8322 ofertas inmobiliarias de Cali de los últimos tres meses, con las siguientes variables:

variable descripcion
id identificador unico de la oferta
zona ubicacion de la vivienda (Zona Centro, Norte, Sur, Oriente, Oeste)
piso piso que ocupa la vivienda
estrato estrato socioeconomico (3 a 6)
preciom precio de la vivienda en millones de pesos (variable respuesta)
areaconst area construida en m2
parqueaderos numero de parqueaderos
banios numero de banos
habitaciones numero de habitaciones
tipo tipo de vivienda (Casa o Apartamento)
barrio barrio de ubicacion
longitud coordenada geografica (longitud)
latitud coordenada geografica (latitud)

Plan de depuración general. (1) Se eliminan 3 filas completamente vacías presentes al final del archivo fuente (sin id). (2) Se convierten zona, tipo y barrio a factor, y estrato a factor ordenado, para que R los trate correctamente como variables categóricas en los modelos. (3) Se diagnostican valores faltantes por variable:

data.frame(
  variable = names(vivienda),
  n_na = sapply(vivienda, function(x) sum(is.na(x))),
  pct_na = round(100 * sapply(vivienda, function(x) sum(is.na(x))) / nrow(vivienda), 1)
) %>% filter(n_na > 0) %>% arrange(desc(n_na)) %>%
  DT::datatable(rownames = FALSE, options = list(dom = "t"))

Los faltantes en piso (~32%) y parqueaderos (~19%) aparecen tanto en casas como en apartamentos y en todas las zonas por igual, lo que indica que son datos genuinamente no diligenciados por el ofertante y no un problema estructural de una categoría en particular. Por esta razón no se imputan: se documentan y cada modelo trabaja con los casos completos de las variables que efectivamente utiliza (complete.cases), reportando el tamaño de muestra resultante en cada caso.

Estrategia de filtrado por solicitud. Cada solicitud del cliente especifica un tipo de vivienda y una zona puntual, por lo que la metodología adecuada es construir una submuestra específica para cada una, en vez de un único modelo general para toda la ciudad (que mezclaría segmentos de mercado muy distintos):

  • base1 = ofertas con tipo == "Casa" y zona == "Zona Norte" → usada para responder la Vivienda 1.
  • base2 = ofertas con tipo == "Apartamento" y zona == "Zona Sur" → usada para responder la Vivienda 2.

Variables seleccionadas para el modelo. Se modela preciom (precio, variable respuesta) en función de areaconst, estrato, habitaciones, parqueaderos y banios, por ser los atributos físicos de la vivienda que la literatura de valoración inmobiliaria y la exploración de datos (Anexo 2) señalan como más asociados al precio, y que además corresponden exactamente a las características que el cliente especifica en cada solicitud. La variable zona no se incluye como predictor dentro de cada submuestra porque, al ser el criterio de filtrado, resulta constante dentro de base1 y de base2 (no aporta variabilidad explicativa una vez fijada).

Anexo 2: Resultados de la modelación, validación y comparación de modelos

Anexo 2.A — Solicitud 1: Vivienda 1 (Casa, Zona Norte)

A.1 Filtro y verificación geográfica

lat_ref_sur <- quantile(vivienda$latitud[vivienda$zona == "Zona Sur"], 0.90, na.rm = TRUE)
base1_mapa <- base1 %>%
  mutate(chequeo = ifelse(latitud < lat_ref_sur, "Posible dato atipico (muy al sur)", "Coherente con zona norte"))

p_base1 <- ggplot(base1_mapa, aes(x = longitud, y = latitud, color = chequeo)) +
  geom_point(alpha = 0.6, size = 1.3) +
  scale_color_manual(values = c("Coherente con zona norte" = "steelblue",
                                 "Posible dato atipico (muy al sur)" = "firebrick")) +
  labs(title = "base1: ubicacion geografica de casas etiquetadas Zona Norte",
       x = "Longitud", y = "Latitud", color = NULL) +
  theme_minimal(base_size = 11) + theme(legend.position = "bottom")
ggplotly(p_base1) %>% layout(legend = list(orientation = "h", y = -0.15))
n_atipicos1 <- sum(base1_mapa$chequeo != "Coherente con zona norte")
cat("Registros geograficamente atipicos en base1:", n_atipicos1, "de", nrow(base1),
    sprintf("(%.1f%%)", 100 * n_atipicos1 / nrow(base1)), "\n")
## Registros geograficamente atipicos en base1: 108 de 722 (15.0%)

Interpretación: cerca del 15% de las ofertas etiquetadas “Zona Norte” tiene coordenadas propias del sur de la ciudad, lo que sugiere que la variable zona se asigna por un criterio de negocio (barrio/comercial) y no siempre coincide con la geolocalización GPS. Se recomienda a C&A auditar esta variable con polígonos oficiales de comuna antes de futuros reportes geográficos.

A.2 Análisis exploratorio de datos

# El estrato es una variable ORDINAL: no se calcula su promedio/desviacion ni se
# incluye en una correlacion de Pearson (que asume escala numerica continua).
# Su asociacion con el precio se mide aparte con Spearman (basada en rangos).
num_vars1 <- base1 %>% select(preciom, areaconst, habitaciones, banios)
cor_mat1 <- cor(num_vars1, use = "pairwise.complete.obs")
round(cor_mat1, 2)
##              preciom areaconst habitaciones banios
## preciom         1.00      0.73         0.32   0.52
## areaconst       0.73      1.00         0.38   0.46
## habitaciones    0.32      0.38         1.00   0.58
## banios          0.52      0.46         0.58   1.00
cor_estrato1 <- cor(as.numeric(base1$estrato), base1$preciom, method = "spearman",
                     use = "pairwise.complete.obs")
cat("Correlacion de Spearman (precio vs. estrato, variable ordinal):", round(cor_estrato1, 2), "\n")
## Correlacion de Spearman (precio vs. estrato, variable ordinal): 0.71
plot_ly(base1, x = ~areaconst, y = ~preciom, color = ~estrato, type = "scatter", mode = "markers",
        text = ~paste("Barrio:", barrio, "<br>Banos:", banios, "<br>Habitaciones:", habitaciones),
        marker = list(size = 6, opacity = 0.6)) %>%
  layout(title = "Precio vs. Area construida (color = estrato) - base1",
         xaxis = list(title = "Area construida (m2)"), yaxis = list(title = "Precio (millones)"))
plot_ly(base1, x = ~estrato, y = ~preciom, type = "box", color = ~estrato) %>%
  layout(title = "Distribucion del precio por estrato - base1",
         xaxis = list(title = "Estrato"), yaxis = list(title = "Precio (millones)"))

Interpretación: el precio se correlaciona fuertemente con el área construida (r de Pearson = 0.73) y de forma más moderada con baños (r = 0.52) y habitaciones (r = 0.32). Como el estrato es una variable ordinal, no se calculan sobre ella medidas de promedio o dispersión ni se incluye en la correlación de Pearson; su asociación con el precio se mide con la correlación de Spearman (basada en rangos, no en promedios), que da 0.71 — una asociación positiva fuerte, consistente con lo que muestra el boxplot: a mayor estrato, mayor mediana y dispersión del precio.

A.3 Estimación y comparación de modelos candidatos

Se compara el modelo completo (con las 5 variables) contra un modelo seleccionado por procedimiento stepwise (selección automática por AIC, partiendo del modelo nulo):

comp1 <- data.frame(
  Modelo = c("Completo (5 variables)", "Stepwise (seleccionado)"),
  Formula = c(paste(deparse(formula(modelo1_full)), collapse = " "),
              paste(deparse(formula(modelo1_step)), collapse = " ")),
  R2 = c(summary(modelo1_full)$r.squared, summary(modelo1_step)$r.squared),
  R2_ajustado = c(summary(modelo1_full)$adj.r.squared, summary(modelo1_step)$adj.r.squared),
  AIC = c(AIC(modelo1_full), AIC(modelo1_step)),
  BIC = c(BIC(modelo1_full), BIC(modelo1_step))
)
knitr::kable(comp1, digits = 4)
Modelo Formula R2 R2_ajustado AIC BIC
Completo (5 variables) preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios 0.6070 0.6006 5631.603 5668.281
Stepwise (seleccionado) preciom ~ areaconst + estrato + parqueaderos + banios 0.6056 0.6001 5631.217 5663.820

Interpretación de la comparación: el procedimiento stepwise retira la variable habitaciones (no significativa en el modelo completo, p ≈ 0.208), obteniendo un modelo más simple con prácticamente el mismo R² ajustado (0.6001 vs. 0.6006) y menor AIC/BIC — es decir, explica lo mismo con menos variables. Por parsimonia, se elige el modelo stepwise como modelo final para la Solicitud 1.

summary(modelo1)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + parqueaderos + banios, 
##     data = base1_cc)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -780.51  -77.28  -18.56   48.71 1001.78 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  149.76296   25.43054   5.889 7.86e-09 ***
## areaconst      0.69827    0.05105  13.678  < 2e-16 ***
## estrato.L    197.42773   25.09463   7.867 2.99e-14 ***
## estrato.Q     29.37963   19.36288   1.517 0.129925    
## estrato.C     17.25818   14.78708   1.167 0.243815    
## parqueaderos  24.33931    5.86973   4.147 4.07e-05 ***
## banios        23.12920    6.50058   3.558 0.000415 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 155 on 428 degrees of freedom
## Multiple R-squared:  0.6056, Adjusted R-squared:  0.6001 
## F-statistic: 109.5 on 6 and 428 DF,  p-value: < 2.2e-16
Estimate Std. Error t value Pr(>|t|)
(Intercept) 149.763 25.431 5.889 0.000
areaconst 0.698 0.051 13.678 0.000
estrato.L 197.428 25.095 7.867 0.000
estrato.Q 29.380 19.363 1.517 0.130
estrato.C 17.258 14.787 1.167 0.244
parqueaderos 24.339 5.870 4.147 0.000
banios 23.129 6.501 3.558 0.000

Interpretación de los coeficientes del modelo final: por cada m² adicional el precio esperado sube 0.7 millones (p < 0.001); el efecto lineal del estrato es positivo y muy significativo; cada parqueadero adicional suma 24.3 millones y cada baño adicional 23.1 millones, ambos significativos. Todos los signos son lógicos: más área, mejor estrato, más parqueaderos y más baños se asocian a mayor precio. El modelo explica el 61% de la variabilidad del precio (R² ajustado = 0.6), un ajuste razonable para datos reales de mercado donde factores no observados (acabados, antigüedad, vista) también influyen. Como mejora futura, se sugiere incorporar barrio como efecto fijo o probar log(preciom) para estabilizar la varianza (ver A.4).

A.4 Validación de los supuestos del modelo final

res1 <- residuals(modelo1)

cat("== Normalidad de los residuos ==\n")
## == Normalidad de los residuos ==
shapiro.test(res1)
## 
##  Shapiro-Wilk normality test
## 
## data:  res1
## W = 0.84766, p-value < 2.2e-16
nortest::lillie.test(res1)
## 
##  Lilliefors (Kolmogorov-Smirnov) normality test
## 
## data:  res1
## D = 0.13563, p-value < 2.2e-16
tseries::jarque.bera.test(res1)
## 
##  Jarque Bera Test
## 
## data:  res1
## X-squared = 2002.1, df = 2, p-value < 2.2e-16
nortest::ad.test(res1)
## 
##  Anderson-Darling normality test
## 
## data:  res1
## A = 13.935, p-value < 2.2e-16
cat("\n== Media cero de los errores ==\n")
## 
## == Media cero de los errores ==
t.test(res1)
## 
##  One Sample t-test
## 
## data:  res1
## t = -1.7115e-16, df = 434, p-value = 1
## alternative hypothesis: true mean is not equal to 0
## 95 percent confidence interval:
##  -14.50554  14.50554
## sample estimates:
##     mean of x 
## -1.263166e-15
cat("\n== Homocedasticidad (Goldfeld-Quandt) ==\n")
## 
## == Homocedasticidad (Goldfeld-Quandt) ==
gqtest(modelo1)
## 
##  Goldfeld-Quandt test
## 
## data:  modelo1
## GQ = 2.23, df1 = 211, df2 = 210, p-value = 4.905e-09
## alternative hypothesis: variance increases from segment 1 to 2
cat("\n== No autocorrelacion (Durbin-Watson) ==\n")
## 
## == No autocorrelacion (Durbin-Watson) ==
dwtest(modelo1)
## 
##  Durbin-Watson test
## 
## data:  modelo1
## DW = 1.8015, p-value = 0.01547
## alternative hypothesis: true autocorrelation is greater than 0
par(mfrow = c(1, 2))
hist(res1, breaks = 30, main = "Histograma de residuos", xlab = "Residuo", col = "lightblue")
qqnorm(res1); qqline(res1, col = "red")

Interpretación: las cuatro pruebas de normalidad rechazan la normalidad de los residuos (p < 0.001), visible en la asimetría del histograma y las colas del QQ-plot. La media de los residuos es cero (por construcción de MCO). La prueba de Goldfeld-Quandt rechaza homocedasticidad (hay heterocedasticidad: la varianza crece con el precio) y Durbin-Watson detecta autocorrelación positiva leve. Sin corregir el modelo (como pide la actividad), estos hallazgos sugieren tomar los errores estándar y los intervalos de confianza con cautela; como mejora se sugiere modelar log(preciom) o usar errores estándar robustos a heterocedasticidad.

A.5 Estimación puntual — Vivienda 1

data.frame(estrato = c(4, 5), rbind(pred_e4, pred_e5)) %>%
  rename(precio_estimado = fit, IC_inferior = lwr, IC_superior = upr) %>%
  knitr::kable(digits = 1, caption = "Precio estimado (millones de $) para Vivienda 1")
Precio estimado (millones de $) para Vivienda 1
estrato precio_estimado IC_inferior IC_superior
X1 4 312.8 275.1 350.4
X1.1 5 377.9 346.5 409.3

Interpretación: para una casa de 200 m², 1 parqueadero y 2 baños, el precio esperado es de 313 millones en estrato 4 y 378 millones en estrato 5. Frente al crédito de $350 millones, solo el estrato 4 queda dentro de presupuesto.

A.6 Ofertas potenciales para la Vivienda 1

cat("Ofertas dentro del presupuesto (<=350M) y estrato 4-5 en base1:", nrow(ofertas1), "\n")
## Ofertas dentro del presupuesto (<=350M) y estrato 4-5 en base1: 112
DT::datatable(top5_ofertas1, rownames = FALSE, options = list(dom = "t"))
plot_ly() %>%
  add_markers(data = base1, x = ~longitud, y = ~latitud, name = "Otras ofertas base1",
              marker = list(color = "lightgray", size = 5, opacity = 0.5),
              text = ~paste("$", preciom, "M -", barrio)) %>%
  add_markers(data = top5_ofertas1, x = ~longitud, y = ~latitud, name = "Top 5 sugeridas",
              marker = list(color = "firebrick", size = 11, symbol = "star"),
              text = ~paste("$", preciom, "M -", barrio, "- Estrato", estrato)) %>%
  layout(title = "Ofertas potenciales sugeridas para Vivienda 1 (Casa, Zona Norte)",
         xaxis = list(title = "Longitud"), yaxis = list(title = "Latitud"))

Anexo 2.B — Solicitud 2: Vivienda 2 (Apartamento, Zona Sur)

B.1 Filtro y verificación geográfica

lat_ref_norte <- quantile(vivienda$latitud[vivienda$zona == "Zona Norte"], 0.10, na.rm = TRUE)
base2_mapa <- base2 %>%
  mutate(chequeo = ifelse(latitud > lat_ref_norte, "Posible dato atipico (muy al norte)", "Coherente con zona sur"))

p_base2 <- ggplot(base2_mapa, aes(x = longitud, y = latitud, color = chequeo)) +
  geom_point(alpha = 0.5, size = 1) +
  scale_color_manual(values = c("Coherente con zona sur" = "darkgreen",
                                 "Posible dato atipico (muy al norte)" = "firebrick")) +
  labs(title = "base2: ubicacion geografica de apartamentos etiquetados Zona Sur",
       x = "Longitud", y = "Latitud", color = NULL) +
  theme_minimal(base_size = 11) + theme(legend.position = "bottom")
ggplotly(p_base2) %>% layout(legend = list(orientation = "h", y = -0.15))
n_atipicos2 <- sum(base2_mapa$chequeo != "Coherente con zona sur")
cat("Registros geograficamente atipicos en base2:", n_atipicos2, "de", nrow(base2),
    sprintf("(%.1f%%)", 100 * n_atipicos2 / nrow(base2)), "\n")
## Registros geograficamente atipicos en base2: 529 de 2787 (19.0%)

Interpretación: de forma similar a base1, ~19% de los apartamentos etiquetados “Zona Sur” tiene coordenadas más propias del norte, reforzando la recomendación de auditar la variable zona.

B.2 Análisis exploratorio de datos

# El estrato es ordinal: se excluye de la correlacion de Pearson (asume escala
# numerica continua) y se mide aparte con Spearman (basada en rangos).
num_vars2 <- base2 %>% select(preciom, areaconst, habitaciones, banios)
cor_mat2 <- cor(num_vars2, use = "pairwise.complete.obs")
round(cor_mat2, 2)
##              preciom areaconst habitaciones banios
## preciom         1.00      0.76         0.33   0.72
## areaconst       0.76      1.00         0.43   0.66
## habitaciones    0.33      0.43         1.00   0.51
## banios          0.72      0.66         0.51   1.00
cor_estrato2 <- cor(as.numeric(base2$estrato), base2$preciom, method = "spearman",
                     use = "pairwise.complete.obs")
cat("Correlacion de Spearman (precio vs. estrato, variable ordinal):", round(cor_estrato2, 2), "\n")
## Correlacion de Spearman (precio vs. estrato, variable ordinal): 0.75
plot_ly(base2, x = ~areaconst, y = ~preciom, color = ~estrato, type = "scatter", mode = "markers",
        text = ~paste("Barrio:", barrio, "<br>Banos:", banios, "<br>Habitaciones:", habitaciones),
        marker = list(size = 5, opacity = 0.5)) %>%
  layout(title = "Precio vs. Area construida (color = estrato) - base2",
         xaxis = list(title = "Area construida (m2)"), yaxis = list(title = "Precio (millones)"))
plot_ly(base2, x = ~estrato, y = ~preciom, type = "box", color = ~estrato) %>%
  layout(title = "Distribucion del precio por estrato - base2",
         xaxis = list(title = "Estrato"), yaxis = list(title = "Precio (millones)"))

Interpretación: también aquí el precio se correlaciona fuertemente con el área (r de Pearson = 0.76) y positivamente con baños (r = 0.72). La asociación del precio con el estrato (ordinal) se mide con Spearman: 0.75, también positiva y fuerte.

B.3 Estimación y comparación de modelos candidatos

comp2 <- data.frame(
  Modelo = c("Completo (5 variables)", "Stepwise (seleccionado)"),
  Formula = c(paste(deparse(formula(modelo2_full)), collapse = " "),
              paste(deparse(formula(modelo2_step)), collapse = " ")),
  R2 = c(summary(modelo2_full)$r.squared, summary(modelo2_step)$r.squared),
  R2_ajustado = c(summary(modelo2_full)$adj.r.squared, summary(modelo2_step)$adj.r.squared),
  AIC = c(AIC(modelo2_full), AIC(modelo2_step)),
  BIC = c(BIC(modelo2_full), BIC(modelo2_step))
)
knitr::kable(comp2, digits = 4)
Modelo Formula R2 R2_ajustado AIC BIC
Completo (5 variables) preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios 0.7762 0.7755 28326.39 28378.36
Stepwise (seleccionado) preciom ~ areaconst + estrato + parqueaderos + banios + habitaciones 0.7762 0.7755 28326.39 28378.36

Interpretación de la comparación: a diferencia de base1, aquí el procedimiento stepwise no elimina ninguna variable — el modelo completo ya es el mejor según AIC/BIC, porque las 5 variables son significativas individualmente (ver más abajo). Se confirma el modelo completo como modelo final para la Solicitud 2.

summary(modelo2)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + parqueaderos + banios + 
##     habitaciones, data = base2_cc)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1058.69   -39.21     0.38    36.96   898.14 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   43.03878   10.31910   4.171 3.14e-05 ***
## areaconst      1.28595    0.05105  25.189  < 2e-16 ***
## estrato.L    141.70006    7.66829  18.479  < 2e-16 ***
## estrato.Q     61.55764    5.57298  11.046  < 2e-16 ***
## estrato.C     31.96203    3.70554   8.625  < 2e-16 ***
## parqueaderos  62.13696    3.79229  16.385  < 2e-16 ***
## banios        41.95467    3.24893  12.913  < 2e-16 ***
## habitaciones -17.10675    3.70357  -4.619 4.06e-06 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 92.52 on 2373 degrees of freedom
## Multiple R-squared:  0.7762, Adjusted R-squared:  0.7755 
## F-statistic:  1175 on 7 and 2373 DF,  p-value: < 2.2e-16
Estimate Std. Error t value Pr(>|t|)
(Intercept) 43.039 10.319 4.171 0
areaconst 1.286 0.051 25.189 0
estrato.L 141.700 7.668 18.479 0
estrato.Q 61.558 5.573 11.046 0
estrato.C 31.962 3.706 8.625 0
parqueaderos 62.137 3.792 16.385 0
banios 41.955 3.249 12.913 0
habitaciones -17.107 3.704 -4.619 0

Interpretación de los coeficientes: el área construida tiene efecto positivo muy significativo (1.29 millones por m²). El estrato tiene efecto positivo y no lineal (términos lineal, cuadrático y cúbico todos significativos). Habitaciones tiene coeficiente negativo (-17.11): para un área fija, más habitaciones implica cuartos más pequeños, lo que el mercado valora menos — es un efecto lógico una vez se entiende como ceteris paribus. Parqueaderos y baños son positivos y muy significativos. El modelo explica el 78% de la variabilidad del precio (R² ajustado = 0.775), un ajuste notablemente mejor que en base1, probablemente por el mayor tamaño de muestra (2381 vs. 435 observaciones) y un segmento más homogéneo.

B.4 Validación de los supuestos del modelo final

res2 <- residuals(modelo2)

cat("== Normalidad de los residuos ==\n")
## == Normalidad de los residuos ==
shapiro.test(res2)
## 
##  Shapiro-Wilk normality test
## 
## data:  res2
## W = 0.77823, p-value < 2.2e-16
nortest::lillie.test(res2)
## 
##  Lilliefors (Kolmogorov-Smirnov) normality test
## 
## data:  res2
## D = 0.12693, p-value < 2.2e-16
tseries::jarque.bera.test(res2)
## 
##  Jarque Bera Test
## 
## data:  res2
## X-squared = 77070, df = 2, p-value < 2.2e-16
nortest::ad.test(res2)
## 
##  Anderson-Darling normality test
## 
## data:  res2
## A = 76.332, p-value < 2.2e-16
cat("\n== Media cero de los errores ==\n")
## 
## == Media cero de los errores ==
t.test(res2)
## 
##  One Sample t-test
## 
## data:  res2
## t = -5.8668e-15, df = 2380, p-value = 1
## alternative hypothesis: true mean is not equal to 0
## 95 percent confidence interval:
##  -3.712519  3.712519
## sample estimates:
##     mean of x 
## -1.110709e-14
cat("\n== Homocedasticidad (Goldfeld-Quandt) ==\n")
## 
## == Homocedasticidad (Goldfeld-Quandt) ==
gqtest(modelo2)
## 
##  Goldfeld-Quandt test
## 
## data:  modelo2
## GQ = 0.90861, df1 = 1183, df2 = 1182, p-value = 0.9502
## alternative hypothesis: variance increases from segment 1 to 2
cat("\n== No autocorrelacion (Durbin-Watson) ==\n")
## 
## == No autocorrelacion (Durbin-Watson) ==
dwtest(modelo2)
## 
##  Durbin-Watson test
## 
## data:  modelo2
## DW = 1.6866, p-value = 7.31e-15
## alternative hypothesis: true autocorrelation is greater than 0
par(mfrow = c(1, 2))
hist(res2, breaks = 30, main = "Histograma de residuos", xlab = "Residuo", col = "lightgreen")
qqnorm(res2); qqline(res2, col = "red")

Interpretación: los residuos tampoco son normales aquí (todas las pruebas rechazan H0). A diferencia de base1, Goldfeld-Quandt no rechaza homocedasticidad (p ≈ 0.95): la varianza del error es razonablemente constante en este segmento. Durbin-Watson sí detecta autocorrelación positiva. En conjunto, este modelo cumple mejor los supuestos que el de base1, aunque persiste el problema de normalidad.

B.5 Estimación puntual — Vivienda 2

data.frame(estrato = c(5, 6), rbind(pred_e5b, pred_e6b)) %>%
  rename(precio_estimado = fit, IC_inferior = lwr, IC_superior = upr) %>%
  knitr::kable(digits = 1, caption = "Precio estimado (millones de $) para Vivienda 2")
Precio estimado (millones de $) para Vivienda 2
estrato precio_estimado IC_inferior IC_superior
X1 5 718.9 700.6 737.3
X1.1 6 872.5 855.8 889.1

Interpretación: para un apartamento de 300 m², 3 parqueaderos, 5 baños y 5 habitaciones, el precio esperado es de 719 millones en estrato 5 y 872 millones en estrato 6. El crédito de $850 millones cubre cómodamente el estrato 5.

B.6 Ofertas potenciales para la Vivienda 2

cat("Ofertas dentro del presupuesto (<=850M) y estrato 5-6 en base2:", nrow(ofertas2), "\n")
## Ofertas dentro del presupuesto (<=850M) y estrato 5-6 en base2: 1439
DT::datatable(top5_ofertas2, rownames = FALSE, options = list(dom = "t"))
plot_ly() %>%
  add_markers(data = base2, x = ~longitud, y = ~latitud, name = "Otras ofertas base2",
              marker = list(color = "lightgray", size = 4, opacity = 0.4),
              text = ~paste("$", preciom, "M -", barrio)) %>%
  add_markers(data = top5_ofertas2, x = ~longitud, y = ~latitud, name = "Top 5 sugeridas",
              marker = list(color = "darkgreen", size = 11, symbol = "star"),
              text = ~paste("$", preciom, "M -", barrio, "- Estrato", estrato)) %>%
  layout(title = "Ofertas potenciales sugeridas para Vivienda 2 (Apartamento, Zona Sur)",
         xaxis = list(title = "Longitud"), yaxis = list(title = "Latitud"))