install.packages(c("plotly", "nortest", "tseries", "lmtest", "car", "dplyr",
"ggplot2", "DT", "devtools"))
devtools::install_github("centro-magis/paqueteMODELOS", force = TRUE)
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 |
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.
| 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 |
| 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 |
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.
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).
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")
| 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"))
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")
| 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"))