En este informe se modela el precio de venta (en millones de pesos) de los apartamentos ofertados en el barrio Cristales de Cali, usando la base Datos_Vivienda. Se plantean modelos de regresión lineal simple y múltiple, se validan sus supuestos, se evalúa la pertinencia de transformaciones logarítmicas, se seleccionan variables con stepwise, se hace una predicción hipotética y se calcula el error absoluto medio (MAE).
# Instala automáticamente los paquetes que falten
paquetes <- c("readxl","dplyr","ggplot2","knitr","kableExtra","lmtest","car")
faltan <- paquetes[!paquetes %in% rownames(installed.packages())]
if (length(faltan) > 0) install.packages(faltan)
library(readxl)
library(dplyr)
library(ggplot2)
library(knitr)
library(kableExtra)
library(lmtest) # bptest, dwtest, resettest
library(car) # vif, boxCox, outlierTest
La base debe estar en la misma carpeta que este archivo
.Rmd (el código busca automáticamente cualquier archivo que
empiece por “Datos_Vivienda” y termine en .xlsx, por ejemplo
“Datos_Vivienda (1).xlsx”).
archivo <- list.files(pattern = "^Datos_Vivienda.*\\.xlsx$", ignore.case = TRUE)
if (length(archivo) == 0) stop("No encuentro Datos_Vivienda.xlsx en: ", getwd(),
". Pon el Excel en la misma carpeta que el .Rmd.")
datos <- read_excel(archivo[1])
datos <- data.frame(id = 1:nrow(datos), datos)
# Algunas columnas (piso, parqueaderos) vienen como texto con "NA": se pasan a número
datos <- datos %>%
mutate(across(c(piso, Estrato, precio_millon, Area_contruida, parqueaderos,
Banos, Habitaciones), ~ suppressWarnings(as.numeric(.x))))
str(datos)
## 'data.frame': 8319 obs. of 13 variables:
## $ id : int 1 2 3 4 5 6 7 8 9 10 ...
## $ Zona : chr "Zona Sur" "Zona Oeste" "Zona Sur" "Zona Sur" ...
## $ piso : num 2 2 3 NA 2 3 2 5 9 6 ...
## $ Estrato : num 6 4 5 6 6 6 6 5 4 6 ...
## $ precio_millon : num 880 1200 250 1280 1300 513 870 310 240 690 ...
## $ Area_contruida : num 237 800 86 346 600 160 490 82.5 80 150 ...
## $ parqueaderos : num 2 3 NA 4 4 2 3 1 1 2 ...
## $ Banos : num 5 6 2 6 7 4 6 2 2 5 ...
## $ Habitaciones : num 4 7 3 5 5 4 5 3 3 4 ...
## $ Tipo : chr "Casa" "Casa" "Apartamento" "Apartamento" ...
## $ Barrio : chr "pance" "miraflores" "multicentro" "ciudad jardin" ...
## $ cordenada_longitud: num -76.5 -76.5 -76.5 -76.5 -76.5 ...
## $ Cordenada_latitud : num 3.43 3.43 3.43 3.43 3.43 ...
Se elige un solo barrio (el que más registros tiene y supera ampliamente los 30 datos): “los cristales”. No se mezclan las otras variantes de escritura.
barrio_elegido <- "los cristales" # alternativa: "cristales" (72 apartamentos)
datos_sub <- datos %>%
filter(tolower(trimws(Barrio)) == barrio_elegido,
Tipo == "Apartamento")
cat("Apartamentos en", barrio_elegido, ":", nrow(datos_sub), "\n")
## Apartamentos en los cristales : 137
head(datos_sub) # primeras filas del barrio filtrado
| id | Zona | piso | Estrato | precio_millon | Area_contruida | parqueaderos | Banos | Habitaciones | Tipo | Barrio | cordenada_longitud | Cordenada_latitud |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1049 | Zona Oeste | 3 | 6 | 335 | 84 | 2 | 2 | 2 | Apartamento | los cristales | -76.50849 | 3.42043 |
| 3950 | Zona Oeste | NA | 5 | 530 | 123 | 1 | 3 | 3 | Apartamento | los cristales | -76.52900 | 3.41300 |
| 4331 | Zona Oeste | 2 | 6 | 530 | 123 | 2 | 4 | 3 | Apartamento | los cristales | -76.53098 | 3.43489 |
| 4381 | Zona Oeste | 3 | 6 | 335 | 85 | 2 | 2 | 3 | Apartamento | los cristales | -76.53102 | 3.42192 |
| 4858 | Zona Oeste | 3 | 6 | 385 | 103 | 2 | 2 | 3 | Apartamento | los cristales | -76.53340 | 3.42287 |
| 4859 | Zona Oeste | 5 | 6 | 330 | 85 | 2 | 2 | 2 | Apartamento | los cristales | -76.53340 | 3.42287 |
colSums(is.na(datos_sub))
## id Zona piso Estrato
## 0 0 28 0
## precio_millon Area_contruida parqueaderos Banos
## 0 0 2 0
## Habitaciones Tipo Barrio cordenada_longitud
## 0 0 0 0
## Cordenada_latitud
## 0
piso tiene muchos datos faltantes, por lo que no
se usa como predictor. En las demás variables se eliminan las
pocas filas con NA.
datos_sub <- datos_sub %>%
select(id, precio_millon, Area_contruida, Banos, Habitaciones, parqueaderos, Estrato) %>%
na.omit() %>%
filter(Area_contruida > 0, precio_millon > 0)
# Variable DUMMY: 1 si el apartamento es estrato 6, 0 si es estrato 4 o 5
datos_sub$estrato6 <- ifelse(datos_sub$Estrato == 6, 1, 0)
cat("Observaciones finales:", nrow(datos_sub))
## Observaciones finales: 135
summary(datos_sub[, -1])
## precio_millon Area_contruida Banos Habitaciones
## Min. : 210.0 Min. : 66.0 Min. :0.000 Min. :1.000
## 1st Qu.: 390.0 1st Qu.:103.0 1st Qu.:2.000 1st Qu.:3.000
## Median : 460.0 Median :123.0 Median :3.000 Median :3.000
## Mean : 547.6 Mean :141.8 Mean :3.156 Mean :3.015
## 3rd Qu.: 625.0 3rd Qu.:165.0 3rd Qu.:4.000 3rd Qu.:3.000
## Max. :1800.0 Max. :460.0 Max. :6.000 Max. :5.000
## parqueaderos Estrato estrato6
## Min. :1.000 Min. :4.000 Min. :0.0000
## 1st Qu.:2.000 1st Qu.:5.000 1st Qu.:0.0000
## Median :2.000 Median :6.000 Median :1.0000
## Mean :1.933 Mean :5.659 Mean :0.6815
## 3rd Qu.:2.000 3rd Qu.:6.000 3rd Qu.:1.0000
## Max. :4.000 Max. :6.000 Max. :1.0000
ggplot(datos_sub, aes(precio_millon)) +
geom_histogram(bins = 20, fill = "steelblue", color = "black", alpha = .7) +
labs(title = "Figura 1. Distribución del precio", x = "Precio (millones)", y = "Frecuencia") +
theme_minimal()
ggplot(datos_sub, aes(Area_contruida, precio_millon)) +
geom_point(color = "purple", alpha = .7) + geom_smooth(method = "loess", color = "grey40") +
labs(title = "Figura 2. Precio vs. área construida", x = "Área (m²)", y = "Precio (millones)") +
theme_minimal()
round(cor(datos_sub[, c("precio_millon","Area_contruida","Banos","Habitaciones","parqueaderos","estrato6")]), 2)
## precio_millon Area_contruida Banos Habitaciones parqueaderos
## precio_millon 1.00 0.84 0.67 0.36 0.61
## Area_contruida 0.84 1.00 0.64 0.43 0.61
## Banos 0.67 0.64 1.00 0.66 0.43
## Habitaciones 0.36 0.43 0.66 1.00 0.37
## parqueaderos 0.61 0.61 0.43 0.37 1.00
## estrato6 0.26 0.20 0.19 0.10 0.26
## estrato6
## precio_millon 0.26
## Area_contruida 0.20
## Banos 0.19
## Habitaciones 0.10
## parqueaderos 0.26
## estrato6 1.00
Interpretación: (escribe aquí qué ves: asimetría del precio, relación positiva con el área, qué variables correlacionan más con el precio).
Se plantean tres modelos de regresión simple con el precio como variable respuesta y como predictores el área construida, el número de baños y el número de parqueaderos.
# Diagnóstico de supuestos: gráficos y pruebas formales
diagnostico <- function(mod) {
par(mfrow = c(2, 2)); plot(mod); par(mfrow = c(1, 1))
data.frame(
Supuesto = c("Normalidad de residuos (Shapiro-Wilk)",
"Homocedasticidad (Breusch-Pagan)",
"Independencia (Durbin-Watson)",
"Linealidad / forma funcional (RESET)"),
`p-valor` = round(c(shapiro.test(residuals(mod))$p.value,
bptest(mod)$p.value,
dwtest(mod)$p.value,
resettest(mod)$p.value), 4),
check.names = FALSE)
}
# Tabla resumen para comparar un modelo
resumen_mod <- function(mod, nombre) {
data.frame(Modelo = nombre,
R2_ajustado = round(summary(mod)$adj.r.squared, 4),
Shapiro_p = round(shapiro.test(residuals(mod))$p.value, 4),
BreuschPagan_p = round(bptest(mod)$p.value, 4),
RESET_p = round(resettest(mod)$p.value, 4))
}
Criterio de decisión: con α = 0,05, no se rechaza el supuesto si el p-valor es mayor a 0,05.
m1 <- lm(precio_millon ~ Area_contruida, data = datos_sub)
summary(m1)
##
## Call:
## lm(formula = precio_millon ~ Area_contruida, data = datos_sub)
##
## Residuals:
## Min 1Q Median 3Q Max
## -443.02 -43.39 -14.96 46.64 530.62
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 62.6419 29.4356 2.128 0.0352 *
## Area_contruida 3.4205 0.1893 18.068 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 140.4 on 133 degrees of freedom
## Multiple R-squared: 0.7105, Adjusted R-squared: 0.7084
## F-statistic: 326.5 on 1 and 133 DF, p-value: < 2.2e-16
Interpretación: el intercepto es 62.64 y la pendiente es 3.421: por cada m² adicional de área construida, el precio esperado cambia en 3.421 millones de pesos. El R² es 0.711, es decir, el área explica aproximadamente el 71.1% de la variabilidad del precio. (Comenta significancia del coeficiente según el p-valor.)
Validación de supuestos (modelo lineal):
diagnostico(m1)
| Supuesto | p-valor |
|---|---|
| Normalidad de residuos (Shapiro-Wilk) | 0.0000 |
| Homocedasticidad (Breusch-Pagan) | 0.0000 |
| Independencia (Durbin-Watson) | 0.2924 |
| Linealidad / forma funcional (RESET) | 0.0982 |
Transformaciones logarítmicas: se compara con el modelo log-lineal (log del precio) y el log-log.
m1_ll <- lm(log(precio_millon) ~ Area_contruida, data = datos_sub) # log-lin
m1_gg <- lm(log(precio_millon) ~ log(Area_contruida), data = datos_sub) # log-log
car::boxCox(m1, lambda = seq(-1, 1, 0.1)) # lambda cercano a 0 sugiere log en la respuesta
rbind(resumen_mod(m1, "Lineal"), resumen_mod(m1_ll, "Log-lineal"), resumen_mod(m1_gg, "Log-log"))
| Modelo | R2_ajustado | Shapiro_p | BreuschPagan_p | RESET_p | |
|---|---|---|---|---|---|
| BP | Lineal | 0.7084 | 0.0000 | 0e+00 | 0.0982 |
| BP1 | Log-lineal | 0.6616 | 0.0143 | 1e-04 | 0.0000 |
| BP2 | Log-log | 0.7230 | 0.0000 | 9e-04 | 0.8383 |
diagnostico(m1_gg)
| Supuesto | p-valor |
|---|---|
| Normalidad de residuos (Shapiro-Wilk) | 0.0000 |
| Homocedasticidad (Breusch-Pagan) | 0.0009 |
| Independencia (Durbin-Watson) | 0.2243 |
| Linealidad / forma funcional (RESET) | 0.8383 |
summary(m1_gg)
##
## Call:
## lm(formula = log(precio_millon) ~ log(Area_contruida), data = datos_sub)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.95615 -0.08911 -0.00685 0.11066 0.57023
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1.85639 0.23369 7.944 7.33e-13 ***
## log(Area_contruida) 0.89478 0.04778 18.728 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.2074 on 133 degrees of freedom
## Multiple R-squared: 0.7251, Adjusted R-squared: 0.723
## F-statistic: 350.8 on 1 and 133 DF, p-value: < 2.2e-16
Conclusión modelo 1: (compara los p-valores de Shapiro y Breusch-Pagan entre las tres versiones; escoge la que mejor cumpla supuestos. En el log-log, el coeficiente es una elasticidad: un aumento de 1% en el área se asocia con un cambio de 0.89% en el precio).
m2 <- lm(precio_millon ~ Banos, data = datos_sub)
summary(m2)
##
## Call:
## lm(formula = precio_millon ~ Banos, data = datos_sub)
##
## Residuals:
## Min 1Q Median 3Q Max
## -323.50 -110.38 -12.67 64.62 796.09
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 41.42 52.03 0.796 0.427
## Banos 160.41 15.61 10.276 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 194.8 on 133 degrees of freedom
## Multiple R-squared: 0.4426, Adjusted R-squared: 0.4384
## F-statistic: 105.6 on 1 and 133 DF, p-value: < 2.2e-16
Interpretación: cada baño adicional se asocia con un cambio esperado de 160.41 millones en el precio; R² = 0.443.
diagnostico(m2)
| Supuesto | p-valor |
|---|---|
| Normalidad de residuos (Shapiro-Wilk) | 0.0000 |
| Homocedasticidad (Breusch-Pagan) | 0.0000 |
| Independencia (Durbin-Watson) | 0.2379 |
| Linealidad / forma funcional (RESET) | 0.0000 |
# Banos puede ser 0, por eso se usa log(Banos + 1) en el log-log
m2_ll <- lm(log(precio_millon) ~ Banos, data = datos_sub)
m2_gg <- lm(log(precio_millon) ~ log(Banos + 1), data = datos_sub)
car::boxCox(m2, lambda = seq(-1, 1, 0.1))
rbind(resumen_mod(m2, "Lineal"), resumen_mod(m2_ll, "Log-lineal"), resumen_mod(m2_gg, "Log-log"))
| Modelo | R2_ajustado | Shapiro_p | BreuschPagan_p | RESET_p | |
|---|---|---|---|---|---|
| BP | Lineal | 0.4384 | 0.0000 | 0.0000 | 0.0000 |
| BP1 | Log-lineal | 0.4953 | 0.0529 | 0.0091 | 0.0535 |
| BP2 | Log-log | 0.4351 | 0.0012 | 0.2672 | 0.0001 |
summary(m2_ll)
##
## Call:
## lm(formula = log(precio_millon) ~ Banos, data = datos_sub)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.83307 -0.14870 -0.04145 0.15627 0.80176
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 5.40559 0.07476 72.30 <2e-16 ***
## Banos 0.25820 0.02243 11.51 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.2799 on 133 degrees of freedom
## Multiple R-squared: 0.4991, Adjusted R-squared: 0.4953
## F-statistic: 132.5 on 1 and 133 DF, p-value: < 2.2e-16
diagnostico(m2_ll)
| Supuesto | p-valor |
|---|---|
| Normalidad de residuos (Shapiro-Wilk) | 0.0529 |
| Homocedasticidad (Breusch-Pagan) | 0.0091 |
| Independencia (Durbin-Watson) | 0.1721 |
| Linealidad / forma funcional (RESET) | 0.0535 |
Conclusión modelo 2: (en el log-lineal, cada baño adicional cambia el precio en aproximadamente 29.5%. Decide cuál versión es mejor).
m3 <- lm(precio_millon ~ parqueaderos, data = datos_sub)
summary(m3)
##
## Call:
## lm(formula = precio_millon ~ parqueaderos, data = datos_sub)
##
## Residuals:
## Min 1Q Median 3Q Max
## -358.42 -155.92 -43.42 107.55 816.58
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -55.45 70.57 -0.786 0.433
## parqueaderos 311.94 35.32 8.832 5.26e-15 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 207.2 on 133 degrees of freedom
## Multiple R-squared: 0.3697, Adjusted R-squared: 0.3649
## F-statistic: 78 on 1 and 133 DF, p-value: 5.262e-15
Interpretación: cada parqueadero adicional se asocia con un cambio esperado de 311.94 millones en el precio; R² = 0.37.
diagnostico(m3)
| Supuesto | p-valor |
|---|---|
| Normalidad de residuos (Shapiro-Wilk) | 0.0000 |
| Homocedasticidad (Breusch-Pagan) | 0.0056 |
| Independencia (Durbin-Watson) | 0.1260 |
| Linealidad / forma funcional (RESET) | 0.0000 |
m3_ll <- lm(log(precio_millon) ~ parqueaderos, data = datos_sub)
m3_gg <- lm(log(precio_millon) ~ log(parqueaderos), data = datos_sub)
car::boxCox(m3, lambda = seq(-1, 1, 0.1))
rbind(resumen_mod(m3, "Lineal"), resumen_mod(m3_ll, "Log-lineal"), resumen_mod(m3_gg, "Log-log"))
| Modelo | R2_ajustado | Shapiro_p | BreuschPagan_p | RESET_p | |
|---|---|---|---|---|---|
| BP | Lineal | 0.3649 | 0.0000 | 0.0056 | 0.0000 |
| BP1 | Log-lineal | 0.3343 | 0.0514 | 0.4933 | 0.0124 |
| BP2 | Log-log | 0.2652 | 0.0422 | 0.0519 | 0.0000 |
diagnostico(m3_gg)
| Supuesto | p-valor |
|---|---|
| Normalidad de residuos (Shapiro-Wilk) | 0.0422 |
| Homocedasticidad (Breusch-Pagan) | 0.0519 |
| Independencia (Durbin-Watson) | 0.1409 |
| Linealidad / forma funcional (RESET) | 0.0000 |
Conclusión modelo 3: (decide si la transformación mejora los supuestos).
rbind(resumen_mod(m1, "Precio ~ Área"), resumen_mod(m1_gg, "log(Precio) ~ log(Área)"),
resumen_mod(m2, "Precio ~ Baños"), resumen_mod(m2_ll, "log(Precio) ~ Baños"),
resumen_mod(m3, "Precio ~ Parqueaderos"), resumen_mod(m3_gg, "log(Precio) ~ log(Parq.)"))
| Modelo | R2_ajustado | Shapiro_p | BreuschPagan_p | RESET_p | |
|---|---|---|---|---|---|
| BP | Precio ~ Área | 0.7084 | 0.0000 | 0.0000 | 0.0982 |
| BP1 | log(Precio) ~ log(Área) | 0.7230 | 0.0000 | 0.0009 | 0.8383 |
| BP2 | Precio ~ Baños | 0.4384 | 0.0000 | 0.0000 | 0.0000 |
| BP3 | log(Precio) ~ Baños | 0.4953 | 0.0529 | 0.0091 | 0.0535 |
| BP4 | Precio ~ Parqueaderos | 0.3649 | 0.0000 | 0.0056 | 0.0000 |
| BP5 | log(Precio) ~ log(Parq.) | 0.2652 | 0.0422 | 0.0519 | 0.0000 |
Conclusión: (el área construida suele ser el mejor predictor individual; menciona que los residuos de los modelos en escala original suelen mostrar heterocedasticidad y asimetría, y que la transformación logarítmica ayuda a corregirlo).
Para calcular el MAE de forma honesta se separa el 80% de los datos para estimar los modelos y el 20% para evaluarlos.
set.seed(123)
idx <- sample(1:nrow(datos_sub), size = round(0.8 * nrow(datos_sub)))
train <- datos_sub[idx, ]
test <- datos_sub[-idx, ]
c(entrenamiento = nrow(train), prueba = nrow(test))
## entrenamiento prueba
## 108 27
Modelo completo con la variable dummy
estrato6 (1 = estrato 6, 0 = estratos 4 y 5):
full1 <- lm(precio_millon ~ Area_contruida + Banos + Habitaciones + parqueaderos + estrato6,
data = train)
summary(full1)
##
## Call:
## lm(formula = precio_millon ~ Area_contruida + Banos + Habitaciones +
## parqueaderos + estrato6, data = train)
##
## Residuals:
## Min 1Q Median 3Q Max
## -329.65 -68.03 -3.87 67.53 478.98
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -48.6699 70.9699 -0.686 0.49441
## Area_contruida 2.5102 0.2986 8.406 2.67e-13 ***
## Banos 80.8655 20.9557 3.859 0.00020 ***
## Habitaciones -69.5479 26.0975 -2.665 0.00895 **
## parqueaderos 88.7487 33.8189 2.624 0.01002 *
## estrato6 34.1047 28.5838 1.193 0.23558
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 134.6 on 102 degrees of freedom
## Multiple R-squared: 0.778, Adjusted R-squared: 0.7671
## F-statistic: 71.48 on 5 and 102 DF, p-value: < 2.2e-16
vif(full1) # valores > 5 sugieren multicolinealidad
## Area_contruida Banos Habitaciones parqueaderos estrato6
## 2.422825 2.832540 1.941352 1.698378 1.081800
mm1 <- step(full1, direction = "both", trace = 1)
## Start: AIC=1064.78
## precio_millon ~ Area_contruida + Banos + Habitaciones + parqueaderos +
## estrato6
##
## Df Sum of Sq RSS AIC
## - estrato6 1 25804 1874670 1064.3
## <none> 1848866 1064.8
## - parqueaderos 1 124828 1973693 1069.8
## - Habitaciones 1 128729 1977594 1070.0
## - Banos 1 269915 2118780 1077.5
## - Area_contruida 1 1280728 3129594 1119.6
##
## Step: AIC=1064.28
## precio_millon ~ Area_contruida + Banos + Habitaciones + parqueaderos
##
## Df Sum of Sq RSS AIC
## <none> 1874670 1064.3
## + estrato6 1 25804 1848866 1064.8
## - Habitaciones 1 126837 2001507 1069.3
## - parqueaderos 1 140602 2015272 1070.1
## - Banos 1 283211 2157881 1077.5
## - Area_contruida 1 1293788 3168458 1119.0
summary(mm1)
##
## Call:
## lm(formula = precio_millon ~ Area_contruida + Banos + Habitaciones +
## parqueaderos, data = train)
##
## Residuals:
## Min 1Q Median 3Q Max
## -319.52 -57.19 -11.19 53.69 485.99
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -44.2313 71.0179 -0.623 0.534780
## Area_contruida 2.5216 0.2991 8.431 2.21e-13 ***
## Banos 82.6274 20.9466 3.945 0.000146 ***
## Habitaciones -69.0252 26.1475 -2.640 0.009584 **
## parqueaderos 93.5268 33.6499 2.779 0.006474 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 134.9 on 103 degrees of freedom
## Multiple R-squared: 0.7749, Adjusted R-squared: 0.7661
## F-statistic: 88.63 on 4 and 103 DF, p-value: < 2.2e-16
Interpretación: (Interpreta cada coeficiente “manteniendo las demás variables constantes”. Para la dummy: un apartamento de estrato 6 tiene en promedio (la dummy fue eliminada por step) millones más/menos que uno de estrato 4–5, con el resto igual).
vif(mm1)
## Area_contruida Banos Habitaciones parqueaderos
## 2.420318 2.818475 1.940805 1.674563
diagnostico(mm1)
| Supuesto | p-valor |
|---|---|
| Normalidad de residuos (Shapiro-Wilk) | 0.0000 |
| Homocedasticidad (Breusch-Pagan) | 0.0000 |
| Independencia (Durbin-Watson) | 0.5589 |
| Linealidad / forma funcional (RESET) | 0.6623 |
Se evalúa si conviene transformar la respuesta:
car::boxCox(full1, lambda = seq(-1, 1, 0.1))
Si el intervalo de λ incluye valores cercanos a 0, el logaritmo del precio es pertinente. Se plantea el modelo log-log (el área en logaritmo, el resto en nivel porque son conteos o dummy):
full2 <- lm(log(precio_millon) ~ log(Area_contruida) + Banos + Habitaciones +
parqueaderos + estrato6, data = train)
summary(full2)
##
## Call:
## lm(formula = log(precio_millon) ~ log(Area_contruida) + Banos +
## Habitaciones + parqueaderos + estrato6, data = train)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.74796 -0.11665 -0.00754 0.13542 0.52701
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.61895 0.30417 8.610 9.54e-14 ***
## log(Area_contruida) 0.68428 0.07830 8.740 4.96e-14 ***
## Banos 0.12185 0.03097 3.934 0.000153 ***
## Habitaciones -0.13040 0.03768 -3.461 0.000788 ***
## parqueaderos 0.11571 0.04701 2.461 0.015519 *
## estrato6 0.06827 0.04143 1.648 0.102411
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.1951 on 102 degrees of freedom
## Multiple R-squared: 0.7867, Adjusted R-squared: 0.7762
## F-statistic: 75.22 on 5 and 102 DF, p-value: < 2.2e-16
mm2 <- step(full2, direction = "both", trace = 1,
scope = list(lower = ~ estrato6, upper = formula(full2)))
## Start: AIC=-347.12
## log(precio_millon) ~ log(Area_contruida) + Banos + Habitaciones +
## parqueaderos + estrato6
##
## Df Sum of Sq RSS AIC
## <none> 3.8845 -347.12
## - parqueaderos 1 0.23072 4.1152 -342.88
## - Habitaciones 1 0.45609 4.3405 -337.13
## - Banos 1 0.58944 4.4739 -333.86
## - log(Area_contruida) 1 2.90877 6.7932 -288.75
summary(mm2)
##
## Call:
## lm(formula = log(precio_millon) ~ log(Area_contruida) + Banos +
## Habitaciones + parqueaderos + estrato6, data = train)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.74796 -0.11665 -0.00754 0.13542 0.52701
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.61895 0.30417 8.610 9.54e-14 ***
## log(Area_contruida) 0.68428 0.07830 8.740 4.96e-14 ***
## Banos 0.12185 0.03097 3.934 0.000153 ***
## Habitaciones -0.13040 0.03768 -3.461 0.000788 ***
## parqueaderos 0.11571 0.04701 2.461 0.015519 *
## estrato6 0.06827 0.04143 1.648 0.102411
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.1951 on 102 degrees of freedom
## Multiple R-squared: 0.7867, Adjusted R-squared: 0.7762
## F-statistic: 75.22 on 5 and 102 DF, p-value: < 2.2e-16
Interpretación: (el coeficiente de log(Área) es una elasticidad. Para variables en nivel, el efecto porcentual es (exp(β) − 1) × 100. Para la dummy: 7.1% de diferencia en el precio de los estrato 6 frente a 4–5, con el resto constante).
vif(mm2)
## log(Area_contruida) Banos Habitaciones parqueaderos
## 2.588942 2.944782 1.926244 1.561912
## estrato6
## 1.081472
diagnostico(mm2)
| Supuesto | p-valor |
|---|---|
| Normalidad de residuos (Shapiro-Wilk) | 0.0356 |
| Homocedasticidad (Breusch-Pagan) | 0.0085 |
| Independencia (Durbin-Watson) | 0.5639 |
| Linealidad / forma funcional (RESET) | 0.2298 |
car::outlierTest(mm2)
## rstudent unadjusted p-value Bonferroni p
## 32 -4.306837 3.8455e-05 0.0041532
rbind(resumen_mod(mm1, "Múltiple 1 (precio)"), resumen_mod(mm2, "Múltiple 2 (log-precio)"))
| Modelo | R2_ajustado | Shapiro_p | BreuschPagan_p | RESET_p | |
|---|---|---|---|---|---|
| BP | Múltiple 1 (precio) | 0.7661 | 0.0000 | 0.0000 | 0.6623 |
| BP1 | Múltiple 2 (log-precio) | 0.7762 | 0.0356 | 0.0085 | 0.2298 |
Conclusión: (¿la transformación logarítmica mejoró normalidad y homocedasticidad? Los R² ajustados de modelos con distinta variable respuesta no son directamente comparables; por eso se usa el MAE en la escala original en la sección 5).
Se predice el precio de un apartamento hipotético de 150 m², 3 baños, 3 habitaciones, 2 parqueaderos y estrato 6 (puedes cambiar los valores).
nuevo <- data.frame(Area_contruida = 150, Banos = 3, Habitaciones = 3,
parqueaderos = 2, estrato6 = 1)
# Modelo 1 (escala original)
p1 <- predict(mm1, newdata = nuevo, interval = "prediction", level = 0.95)
# Modelo 2 (escala log): se devuelve a millones con exp()
p2 <- exp(predict(mm2, newdata = nuevo, interval = "prediction", level = 0.95))
data.frame(Modelo = c("Múltiple 1", "Múltiple 2 (log)"),
Prediccion_millones = round(c(p1[, "fit"], p2[, "fit"]), 1),
Limite_inferior = round(c(p1[, "lwr"], p2[, "lwr"]), 1),
Limite_superior = round(c(p1[, "upr"], p2[, "upr"]), 1))
| Modelo | Prediccion_millones | Limite_inferior | Limite_superior |
|---|---|---|---|
| Múltiple 1 | 561.9 | 292.9 | 830.8 |
| Múltiple 2 (log) | 556.5 | 376.5 | 822.5 |
Interpretación: (un apartamento con esas características se estima en X millones; con 95% de confianza el precio individual estaría entre los límites. Compara ambos modelos y comenta si el intervalo del modelo 1 tiene sentido, por ejemplo si el límite inferior es muy bajo).
El error absoluto medio se calcula sobre el conjunto de prueba, en millones de pesos:
\[MAE = \frac{1}{n}\sum_{i=1}^{n}|y_i - \hat{y}_i|\]
mae <- function(real, pred) mean(abs(real - pred))
pred1 <- predict(mm1, newdata = test)
pred2 <- exp(predict(mm2, newdata = test)) # regreso a la escala original
tabla_mae <- data.frame(
Modelo = c("Múltiple 1 (precio)", "Múltiple 2 (log-precio)"),
MAE_millones = round(c(mae(test$precio_millon, pred1),
mae(test$precio_millon, pred2)), 2),
Precio_promedio_prueba = round(mean(test$precio_millon), 1))
tabla_mae$Error_relativo_pct <- round(100 * tabla_mae$MAE_millones / tabla_mae$Precio_promedio_prueba, 1)
tabla_mae
| Modelo | MAE_millones | Precio_promedio_prueba | Error_relativo_pct |
|---|---|---|---|
| Múltiple 1 (precio) | 71.66 | 513.9 | 13.9 |
| Múltiple 2 (log-precio) | 55.30 | 513.9 | 10.8 |
Interpretación: (en promedio, el modelo se equivoca en X millones respecto al precio real, lo que equivale a Y% del precio promedio. El modelo con menor MAE predice mejor).