Qué hace este bloque: Configura cómo se mostrarán el código, las advertencias y las tablas; no estima ningún modelo. Este bloque se ejecuta, pero no se muestra en el HTML.
Pregunta: ¿cómo se relaciona el precio de oferta con el área, la zona y el tipo de vivienda? Los datos provienen de anuncios de OLX recolectados mediante web scraping. Son precios anunciados, no precios finales de venta. Las relaciones estimadas son asociaciones: no prueban cuánto valdría una remodelación.
Interpretamos precio en millones de pesos y área en m²; Coloque el
Excel junto al .Rmd. Si tiene otro nombre o ubicación,
modifique archivo en el encabezado. No reinstale paquetes
que ya funcionan.
Qué hace este bloque: Lee el Excel, revisa las columnas necesarias, conserva una muestra común y fija las referencias de las categorías. La primera tabla permite saber si se excluyeron filas.
archivo <- params$archivo
if (!file.exists(archivo)) stop(
"No se encuentra: ", archivo,
". Coloque el Excel junto al Rmd o cambie params: archivo en el encabezado.")
Datos_Vivienda <- as.data.frame(readxl::read_excel(archivo, sheet = 1))
variables <- c("precio_millon", "Area_contruida", "Zona", "Tipo",
"Banos", "Habitaciones", "Estrato")
stopifnot(all(variables %in% names(Datos_Vivienda)))
for (v in c("Zona", "Tipo")) {
Datos_Vivienda[[v]] <- trimws(gsub("[[:space:]]+", " ", Datos_Vivienda[[v]]))
}
# Usamos las mismas filas en todos los modelos.
validas <- complete.cases(Datos_Vivienda[variables]) &
is.finite(Datos_Vivienda$precio_millon) &
is.finite(Datos_Vivienda$Area_contruida) &
Datos_Vivienda$precio_millon > 0 & Datos_Vivienda$Area_contruida > 0
tabla(data.frame(Filas = nrow(Datos_Vivienda), Excluidas = sum(!validas),
Repeticiones_exactas = sum(duplicated(Datos_Vivienda))))
| Filas | Excluidas | Repeticiones_exactas |
|---|---|---|
| 8319 | 0 | 57 |
Datos_Vivienda <- Datos_Vivienda[validas, ]
Datos_Vivienda$Zona <- relevel(factor(Datos_Vivienda$Zona), ref = "Zona Centro")
Datos_Vivienda$Tipo <- relevel(factor(Datos_Vivienda$Tipo), ref = "Apartamento")
Datos_Vivienda$Estrato <- relevel(factor(Datos_Vivienda$Estrato), ref = "3")
Datos_Vivienda$area_c <- Datos_Vivienda$Area_contruida - 100
tabla(summary(Datos_Vivienda[c("precio_millon", "Area_contruida")]))
| precio_millon | Area_contruida | |
|---|---|---|
| Min. : 58 | Min. : 30 | |
| 1st Qu.: 220 | 1st Qu.: 80 | |
| Median : 330 | Median : 123 | |
| Mean : 434 | Mean : 175 | |
| 3rd Qu.: 540 | 3rd Qu.: 229 | |
| Max. :1999 | Max. :1745 |
Se conservan las repeticiones para mantener la muestra del ejemplo
original. Conviene verificar si son anuncios duplicados antes de un
análisis definitivo. No convertimos los faltantes en cero.
area_c es el área menos 100: conserva la unidad m² y
facilita interpretar el intercepto para una vivienda de 100 m².
\[ Precio_i=\beta_0+\beta_1 Area_i+\varepsilon_i. \]
Qué hace este bloque: Ajusta una recta entre precio
y área, muestra su resumen y dibuja esa recta sobre los datos. Empiece
por la pendiente de Area_contruida.
mod1 <- lm(precio_millon ~ Area_contruida, data = Datos_Vivienda)
summary(mod1)
##
## Call:
## lm(formula = precio_millon ~ Area_contruida, data = Datos_Vivienda)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2659.9 -120.8 -47.5 67.3 1330.1
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 157.4764 4.1364 38.1 <2e-16 ***
## Area_contruida 1.5802 0.0183 86.3 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 239 on 8317 degrees of freedom
## Multiple R-squared: 0.472, Adjusted R-squared: 0.472
## F-statistic: 7.45e+03 on 1 and 8317 DF, p-value: <2e-16
plot(precio_millon ~ Area_contruida, data = Datos_Vivienda,
pch = 16, col = adjustcolor("steelblue", .2),
xlab = "Área construida (m²)", ylab = "Precio de oferta (millones)")
abline(mod1, col = "firebrick", lwd = 2)
Interpretación: un m² adicional se asocia con 1.58 millones adicionales en el precio medio anunciado. Por 10 m², la diferencia estimada es diez veces esa cantidad. Todavía no controlamos ubicación, tipo ni otras características.
El intercepto predice el precio con área cero: no tiene una interpretación inmobiliaria útil aquí. Un residuo es precio observado menos predicho. Un residuo positivo no demuestra que una vivienda esté sobrevalorada.
R² = 0.472: proporción de variación del precio explicada dentro de esta muestra. No es el porcentaje de predicciones correctas. El valor p de la pendiente contrasta que sea cero; no mide su importancia práctica.
summary()?Ejemplo de lectura completa: «Por un m² adicional, el precio anunciado difiere en 1.58 millones en promedio. Su error estándar es 0.018. El modelo explica 47.2% de la variación muestral».
Antes de avanzar: mire la nube de puntos. Si área por sí sola deja diferencias grandes, piense qué otra característica de la vivienda podría explicar parte de ellas. No agregue variables solamente para aumentar R².
Qué hace este bloque: Cuenta las ofertas por zona y estima un modelo de medias por grupo. Compare el intercepto y las dummies con la tabla de promedios.
table(Datos_Vivienda$Zona)
##
## Zona Centro Zona Norte Zona Oeste Zona Oriente Zona Sur
## 124 1920 1198 351 4726
mod2 <- lm(precio_millon ~ Zona, data = Datos_Vivienda)
summary(mod2)
##
## Call:
## lm(formula = precio_millon ~ Zona, data = Datos_Vivienda)
##
## Residuals:
## Min 1Q Median 3Q Max
## -592.6 -200.6 -85.6 93.5 1594.4
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 309.7 27.8 11.15 < 2e-16 ***
## ZonaZona Norte 35.9 28.7 1.25 0.210
## ZonaZona Oeste 367.9 29.2 12.61 < 2e-16 ***
## ZonaZona Oriente -81.2 32.3 -2.51 0.012 *
## ZonaZona Sur 116.8 28.1 4.15 3.3e-05 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 309 on 8314 degrees of freedom
## Multiple R-squared: 0.115, Adjusted R-squared: 0.114
## F-statistic: 269 on 4 and 8314 DF, p-value: <2e-16
tabla(aggregate(precio_millon ~ Zona, data = Datos_Vivienda, FUN = mean))
| Zona | precio_millon |
|---|---|
| Zona Centro | 309.7 |
| Zona Norte | 345.6 |
| Zona Oeste | 677.6 |
| Zona Oriente | 228.5 |
| Zona Sur | 426.5 |
Referencia: Zona Centro. El intercepto es su precio medio, y cada coeficiente es la diferencia entre la zona correspondiente y Centro. Por ejemplo, un coeficiente de −80 significaría 80 millones menos en promedio; es un ejemplo de lectura, no una cifra que deba copiarse como resultado.
Con cinco zonas aparecen cuatro dummies más el intercepto. La zona base no desaparece de los datos: está representada por el intercepto. Estas diferencias todavía mezclan tamaños y tipos de vivienda distintos. Un valor p mayor que 0.05 no demuestra igualdad entre zonas.
Qué hace este bloque: Cambia únicamente la zona de referencia a Sur, vuelve a estimar y comprueba que las predicciones siguen siendo iguales.
Datos_Vivienda$Zona_sur <- relevel(Datos_Vivienda$Zona, ref = "Zona Sur")
mod3 <- lm(precio_millon ~ Zona_sur, data = Datos_Vivienda)
summary(mod3)
##
## Call:
## lm(formula = precio_millon ~ Zona_sur, data = Datos_Vivienda)
##
## Residuals:
## Min 1Q Median 3Q Max
## -592.6 -200.6 -85.6 93.5 1594.4
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 426.52 4.50 94.80 < 2e-16 ***
## Zona_surZona Centro -116.82 28.14 -4.15 3.3e-05 ***
## Zona_surZona Norte -80.91 8.37 -9.67 < 2e-16 ***
## Zona_surZona Oeste 251.06 10.01 25.09 < 2e-16 ***
## Zona_surZona Oriente -197.99 17.11 -11.57 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 309 on 8314 degrees of freedom
## Multiple R-squared: 0.115, Adjusted R-squared: 0.114
## F-statistic: 269 on 4 and 8314 DF, p-value: <2e-16
max(abs(fitted(mod2) - fitted(mod3)))
## [1] 1.24e-09
Ahora el intercepto es la media de Zona Sur y las diferencias se leen respecto a Sur. Las predicciones y el R² no cambian, solo la comparación de referencia. La diferencia máxima calculada arriba debe ser cero o prácticamente cero.
La idea: agregar grupos de variables con una razón concreta. Así podemos ver qué cambia al comparar viviendas más parecidas. Los modelos siguientes usan las mismas filas, seleccionadas al importar.
Qué hace este bloque: Agrega zona a la explicación del precio y centra el área en 100 m². Lea ahora la pendiente del área como una asociación ajustada por zona.
mod_area_zona <- lm(precio_millon ~ area_c + Zona, data = Datos_Vivienda)
summary(mod_area_zona)
##
## Call:
## lm(formula = precio_millon ~ area_c + Zona, data = Datos_Vivienda)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2377.3 -102.7 -37.5 63.3 1246.4
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 164.0994 19.5277 8.40 < 2e-16 ***
## area_c 1.5482 0.0167 92.84 < 2e-16 ***
## ZonaZona Norte 86.8810 20.0908 4.32 1.5e-05 ***
## ZonaZona Oeste 364.2379 20.4472 17.81 < 2e-16 ***
## ZonaZona Oriente -78.5136 22.6433 -3.47 0.00053 ***
## ZonaZona Sur 148.9189 19.7213 7.55 4.8e-14 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 217 on 8313 degrees of freedom
## Multiple R-squared: 0.565, Adjusted R-squared: 0.565
## F-statistic: 2.16e+03 on 5 and 8313 DF, p-value: <2e-16
¿Por qué agregar zona? Dos viviendas con la misma área pueden estar en mercados distintos. La pendiente del área ahora se interpreta manteniendo fija la zona. Cada dummy compara zonas a igual área. El modelo todavía supone una pendiente común para todas ellas.
Qué hace este bloque: Extrae la pendiente del área de dos modelos para observar cuánto cambia al controlar por ubicación. No es una prueba estadística de esa diferencia.
c(Simple = coef(mod1)["Area_contruida"],
Ajustando_por_zona = coef(mod_area_zona)["area_c"])
## Simple.Area_contruida Ajustando_por_zona.area_c
## 1.580 1.548
Pregunta: ¿cambió la pendiente? Un cambio no es una contradicción: cambió la comparación. Centrar el área en 100 solo cambia el intercepto; las diferencias de pendiente entre estos modelos provienen del ajuste por zona.
Qué hace este bloque: Usa update() para
añadir tipo sin reescribir toda la fórmula. Busque TipoCasa
y observe si cambia la pendiente del área.
mod_area_zona_tipo <- update(mod_area_zona, . ~ . + Tipo)
summary(mod_area_zona_tipo)
##
## Call:
## lm(formula = precio_millon ~ area_c + Zona + Tipo, data = Datos_Vivienda)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2479.3 -103.9 -38.8 65.4 1257.9
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 187.3254 19.8773 9.42 < 2e-16 ***
## area_c 1.6181 0.0204 79.34 < 2e-16 ***
## ZonaZona Norte 73.2780 20.1804 3.63 0.00028 ***
## ZonaZona Oeste 339.4868 20.8278 16.30 < 2e-16 ***
## ZonaZona Oriente -77.7691 22.5972 -3.44 0.00058 ***
## ZonaZona Sur 135.7291 19.8062 6.85 7.8e-12 ***
## TipoCasa -36.9507 6.2309 -5.93 3.1e-09 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 216 on 8312 degrees of freedom
## Multiple R-squared: 0.567, Adjusted R-squared: 0.567
## F-statistic: 1.82e+03 on 6 and 8312 DF, p-value: <2e-16
update() conserva el modelo y agrega el término
indicado. TipoCasa compara casa−apartamento con igual
área y zona. El coeficiente estimado es -36.95
millones: si es negativo, las casas tienen menor precio
ajustado; si es positivo, mayor. Esto puede diferir de comparar los
promedios sin ajustar.
Qué hace este bloque: Agrega baños, habitaciones y
estrato. summary() muestra las estimaciones;
confint() agrega intervalos clásicos al 95% para apreciar
su incertidumbre.
mod_multiple <- update(mod_area_zona_tipo, . ~ . + Banos + Habitaciones + Estrato)
summary(mod_multiple)
##
## Call:
## lm(formula = precio_millon ~ area_c + Zona + Tipo + Banos + Habitaciones +
## Estrato, data = Datos_Vivienda)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1664.5 -71.6 -9.1 50.0 1170.3
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 137.2702 16.7302 8.20 2.7e-16 ***
## area_c 1.0369 0.0191 54.28 < 2e-16 ***
## ZonaZona Norte -16.6274 16.1301 -1.03 0.30265
## ZonaZona Oeste 70.1350 17.0482 4.11 3.9e-05 ***
## ZonaZona Oriente -61.8704 17.7163 -3.49 0.00048 ***
## ZonaZona Sur -12.3725 16.1334 -0.77 0.44317
## TipoCasa -2.9091 5.2547 -0.55 0.57985
## Banos 62.5271 2.1644 28.89 < 2e-16 ***
## Habitaciones -23.1554 1.8757 -12.34 < 2e-16 ***
## Estrato4 28.3694 6.8079 4.17 3.1e-05 ***
## Estrato5 88.9216 6.7238 13.22 < 2e-16 ***
## Estrato6 317.0343 8.2783 38.30 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 169 on 8307 degrees of freedom
## Multiple R-squared: 0.735, Adjusted R-squared: 0.734
## F-statistic: 2.09e+03 on 11 and 8307 DF, p-value: <2e-16
confint(mod_multiple)
## 2.5 % 97.5 %
## (Intercept) 104.4747 170.066
## area_c 0.9995 1.074
## ZonaZona Norte -48.2464 14.992
## ZonaZona Oeste 36.7164 103.554
## ZonaZona Oriente -96.5986 -27.142
## ZonaZona Sur -43.9980 19.253
## TipoCasa -13.2097 7.391
## Banos 58.2844 66.770
## Habitaciones -26.8323 -19.479
## Estrato4 15.0242 41.715
## Estrato5 75.7414 102.102
## Estrato6 300.8069 333.262
¿Por qué incluirlas? Baños y habitaciones describen la distribución interna; estrato permite diferencias entre categorías que la zona amplia no necesariamente captura. Se trata estrato como factor: no se impone el mismo salto de 3 a 4 que de 5 a 6. La categoría base es 3.
Lectura numérica: en el modelo completo, la diferencia por un m² es 1.037 millones; por un baño adicional, 62.527 millones. Son asociaciones parciales. Si un coeficiente sorprende, revise la comparación y la redundancia entre características antes de declararlo un error.
Qué hace este bloque: Compara el aporte conjunto de los términos añadidos en cada etapa mediante pruebas F para modelos anidados.
anova(mod1, mod_area_zona, mod_area_zona_tipo, mod_multiple)
| Res.Df | RSS | Df | Sum of Sq | F | Pr(>F) |
|---|---|---|---|---|---|
| 8317 | 474009760 | NA | NA | NA | NA |
| 8313 | 390544380 | 4 | 83465380 | 727.16 | 0 |
| 8312 | 388898979 | 1 | 1645401 | 57.34 | 0 |
| 8307 | 238376382 | 5 | 150522597 | 1049.09 | 0 |
Cada fila después de la primera pregunta si los términos añadidos
mejoran el ajuste frente al modelo anterior. La prueba F clásica supone
varianza constante. mod1 y la versión centrada describen la
misma recta, por eso el cambio de escala del intercepto no impide la
comparación. No use esta prueba si cambian las filas, la respuesta o si
los modelos no son anidados.
Decisión docente: no eliminar una variable solo porque su p sea mayor que 0.05. Considere su papel, su incertidumbre y el aporte conjunto de categorías. Área, baños y habitaciones pueden compartir información y producir coeficientes imprecisos.
Qué hace este bloque: Agrega los productos área×zona al modelo completo y lo compara con el modelo aditivo. Ahora las pendientes pueden variar entre zonas.
mod4 <- update(mod_multiple, . ~ . + area_c:Zona)
summary(mod4)
##
## Call:
## lm(formula = precio_millon ~ area_c + Zona + Tipo + Banos + Habitaciones +
## Estrato + area_c:Zona, data = Datos_Vivienda)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1049.1 -66.3 -10.0 48.2 1167.9
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 167.777 20.390 8.23 < 2e-16 ***
## area_c 0.666 0.136 4.91 9.3e-07 ***
## ZonaZona Norte -42.451 20.135 -2.11 0.0350 *
## ZonaZona Oeste -27.421 21.099 -1.30 0.1938
## ZonaZona Oriente -20.493 21.954 -0.93 0.3506
## ZonaZona Sur -52.350 20.151 -2.60 0.0094 **
## TipoCasa -5.044 5.146 -0.98 0.3270
## Banos 58.259 2.106 27.67 < 2e-16 ***
## Habitaciones -19.579 1.827 -10.72 < 2e-16 ***
## Estrato4 37.437 6.684 5.60 2.2e-08 ***
## Estrato5 98.310 6.606 14.88 < 2e-16 ***
## Estrato6 323.970 8.080 40.09 < 2e-16 ***
## area_c:ZonaZona Norte 0.193 0.138 1.40 0.1605
## area_c:ZonaZona Oeste 1.026 0.140 7.31 3.0e-13 ***
## area_c:ZonaZona Oriente -0.456 0.146 -3.12 0.0018 **
## area_c:ZonaZona Sur 0.413 0.136 3.03 0.0024 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 164 on 8303 degrees of freedom
## Multiple R-squared: 0.751, Adjusted R-squared: 0.751
## F-statistic: 1.67e+03 on 15 and 8303 DF, p-value: <2e-16
anova(mod_multiple, mod4)
| Res.Df | RSS | Df | Sum of Sq | F | Pr(>F) |
|---|---|---|---|---|---|
| 8307 | 238376382 | NA | NA | NA | NA |
| 8303 | 223452551 | 4 | 14923831 | 138.6 | 0 |
La hipótesis es que un m² adicional no tiene la misma asociación con
el precio en todas las zonas. area_c * Zona incluye
área + zona + productos área×zona. La prueba F compara
el modelo de pendiente común con el de pendientes distintas, bajo los
supuestos clásicos, incluida varianza constante.
area_c es la pendiente en Centro.area_c:Zona... es cuánto cambia esa pendiente respecto
a Centro. Para obtener la pendiente de otra zona hay que
sumar ambos coeficientes.area_c = 0.Corrección del ejemplo inicial:
Zona:Area_contruida sin los efectos principales permite
pendientes por zona, pero impone un intercepto común.
Ese intercepto sí se interpreta: es el precio predicho a área cero para
todas las zonas, aunque no resulte útil en la práctica. No es
equivalente a Zona * Area.
Qué hace este bloque: Predice para perfiles que difieren solo en un m². La resta permite leer directamente la pendiente de cada zona sin sumar coeficientes a mano.
# La diferencia entre 101 y 100 m² da la pendiente por m² de cada zona.
perfil <- data.frame(area_c = 0,
Zona = factor(levels(Datos_Vivienda$Zona), levels = levels(Datos_Vivienda$Zona)),
Tipo = factor("Apartamento", levels = levels(Datos_Vivienda$Tipo)),
Banos = 2, Habitaciones = 3,
Estrato = factor("3", levels = levels(Datos_Vivienda$Estrato)))
perfil_101 <- perfil
perfil_101$area_c <- 1
tabla(data.frame(Zona = perfil$Zona,
Precio_a_100m2 = predict(mod4, perfil),
Pendiente_por_m2 = predict(mod4, perfil_101) - predict(mod4, perfil)))
| Zona | Precio_a_100m2 | Pendiente_por_m2 |
|---|---|---|
| Zona Centro | 225.6 | 0.666 |
| Zona Norte | 183.1 | 0.859 |
| Zona Oeste | 198.1 | 1.693 |
| Zona Oriente | 205.1 | 0.211 |
| Zona Sur | 173.2 | 1.079 |
Pregunta de clase: si una zona tiene mayor pendiente, ¿podemos afirmar que ampliar allí una vivienda causa más valorización? No: observamos viviendas distintas, con posibles diferencias de calidad, antigüedad y otras variables omitidas. Además, el perfil ilustrativo tiene 100 m², dos baños, tres habitaciones y estrato 3: compruebe que sea plausible en cada zona. La pendiente estimada es constante dentro de cada zona en esta especificación.
Antes de transformar, observe el modelo aditivo. Una transformación responde a una hipótesis sobre la forma de la relación, no a la búsqueda de estrellas.
Qué hace este bloque: Dibuja residuos frente a predicciones del modelo aditivo. Busque curvatura o dispersión creciente para motivar qué alternativa explorar.
plot(mod_multiple, which = 1)
| Lo que sospechamos | Candidato | Qué cambia |
|---|---|---|
| La diferencia por m² disminuye a medida que crece la vivienda | Lin–log | El área entra en log; precio sigue en millones |
| La dispersión crece con el precio y tiene sentido pensar en proporciones | Log–lin | Precio en log, área en niveles |
| Cambios porcentuales de área se relacionan con cambios porcentuales de precio | Log–log | Precio y área en log |
| Hay curvatura, pero interesa mantener ambos en niveles | Cuadrático | Se agrega el área al cuadrado |
Estas son motivaciones, no diagnósticos automáticos. Conserve los
mismos controles para aislar qué cambia al transformar el área o el
precio. Usamos mod_multiple como base; la interacción por
zona se evaluó por separado y se puede combinar después si hay una razón
para hacerlo.
Qué hace este bloque: Sustituye el área centrada por su logaritmo y mantiene los demás controles. La respuesta continúa en millones.
mod5 <- update(mod_multiple, . ~ . - area_c + log(Area_contruida))
summary(mod5)
##
## Call:
## lm(formula = precio_millon ~ Zona + Tipo + Banos + Habitaciones +
## Estrato + log(Area_contruida), data = Datos_Vivienda)
##
## Residuals:
## Min 1Q Median 3Q Max
## -752.4 -94.1 2.0 57.6 1140.7
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -1120.38 25.98 -43.13 < 2e-16 ***
## ZonaZona Norte 30.21 16.12 1.87 0.0609 .
## ZonaZona Oeste 89.43 17.02 5.26 1.5e-07 ***
## ZonaZona Oriente -41.08 17.69 -2.32 0.0202 *
## ZonaZona Sur 43.19 16.15 2.67 0.0075 **
## TipoCasa -63.34 5.75 -11.02 < 2e-16 ***
## Banos 52.05 2.22 23.42 < 2e-16 ***
## Habitaciones -29.13 1.89 -15.40 < 2e-16 ***
## Estrato4 5.24 6.83 0.77 0.4431
## Estrato5 34.95 6.88 5.08 3.9e-07 ***
## Estrato6 245.91 8.59 28.62 < 2e-16 ***
## log(Area_contruida) 285.93 5.23 54.64 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 169 on 8307 degrees of freedom
## Multiple R-squared: 0.736, Adjusted R-squared: 0.735
## F-statistic: 2.1e+03 on 11 and 8307 DF, p-value: <2e-16
Un aumento del 1% de área se asocia aproximadamente con
beta/100 millones. La pendiente por m² es
beta/área: si beta es positivo, el mismo m² adicional tiene
una asociación menor cuanto mayor sea la vivienda.
En estos datos: 10% más de área se asocia con
27.25 millones de diferencia, manteniendo los controles
fijos. Se usa beta * log(1.10), no
beta * 0.10. Duplicar área implica sumar
beta * log(2), no duplicar precio.
Qué hace este bloque: Transforma solo la respuesta a log(precio), conservando el área en niveles y los mismos controles.
mod6 <- update(mod_multiple, log(precio_millon) ~ .)
summary(mod6)
##
## Call:
## lm(formula = log(precio_millon) ~ area_c + Zona + Tipo + Banos +
## Habitaciones + Estrato, data = Datos_Vivienda)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2.0229 -0.1960 -0.0121 0.1810 1.7730
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 4.968549 0.028896 171.95 < 2e-16 ***
## area_c 0.001411 0.000033 42.76 < 2e-16 ***
## ZonaZona Norte -0.190606 0.027859 -6.84 8.4e-12 ***
## ZonaZona Oeste 0.013010 0.029445 0.44 0.66
## ZonaZona Oriente -0.246463 0.030599 -8.05 9.1e-16 ***
## ZonaZona Sur -0.206131 0.027865 -7.40 1.5e-13 ***
## TipoCasa 0.155393 0.009076 17.12 < 2e-16 ***
## Banos 0.125165 0.003738 33.48 < 2e-16 ***
## Habitaciones -0.004783 0.003240 -1.48 0.14
## Estrato4 0.311549 0.011758 26.50 < 2e-16 ***
## Estrato5 0.584345 0.011613 50.32 < 2e-16 ***
## Estrato6 0.965367 0.014298 67.52 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.293 on 8307 degrees of freedom
## Multiple R-squared: 0.81, Adjusted R-squared: 0.809
## F-statistic: 3.21e+03 on 11 and 8307 DF, p-value: <2e-16
Un m² adicional se asocia con un cambio exacto de
100[exp(beta)−1]%. Por 10 m² use
100 * (exp(10 * beta) - 1), que aquí da
1.42%.
Por qué probarlo: un error de 50 millones puede ser muy distinto en una oferta de 100 y en una de 1.000 millones. Modelar log(precio) enfatiza diferencias relativas y comprime los valores altos. Puede estabilizar la dispersión, pero hay que comprobarlo en los residuos del nuevo modelo.
Qué hace este bloque: Transforma también la respuesta del modelo lin–log. Ahora el coeficiente de log(área) se interpreta como elasticidad.
mod7 <- update(mod5, log(precio_millon) ~ .)
summary(mod7)
##
## Call:
## lm(formula = log(precio_millon) ~ Zona + Tipo + Banos + Habitaciones +
## Estrato + log(Area_contruida), data = Datos_Vivienda)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1.5445 -0.1590 -0.0088 0.1563 1.4556
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.57860 0.03893 66.23 < 2e-16 ***
## ZonaZona Norte -0.10376 0.02415 -4.30 1.8e-05 ***
## ZonaZona Oeste 0.04329 0.02550 1.70 0.08966 .
## ZonaZona Oriente -0.20494 0.02651 -7.73 1.2e-14 ***
## ZonaZona Sur -0.09120 0.02420 -3.77 0.00017 ***
## TipoCasa -0.03011 0.00861 -3.50 0.00047 ***
## Banos 0.08337 0.00333 25.03 < 2e-16 ***
## Habitaciones -0.02572 0.00283 -9.08 < 2e-16 ***
## Estrato4 0.25452 0.01024 24.87 < 2e-16 ***
## Estrato5 0.45448 0.01031 44.06 < 2e-16 ***
## Estrato6 0.77456 0.01288 60.15 < 2e-16 ***
## log(Area_contruida) 0.56565 0.00784 72.13 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.253 on 8307 degrees of freedom
## Multiple R-squared: 0.857, Adjusted R-squared: 0.857
## F-statistic: 4.53e+03 on 11 and 8307 DF, p-value: <2e-16
La pendiente del área es una elasticidad. Un aumento
del 1% del área se asocia aproximadamente con 0.566% de
cambio del precio, manteniendo las demás variables constantes. El cambio
exacto ante 10% más de área es 100 * (1.10^beta - 1).
Dummies en modelos logarítmicos:
TipoCasa se interpreta como
100 * (exp(coeficiente) - 1)% respecto al apartamento. En
mod7 la diferencia ajustada es -2.97%. Una
dummy pasa de 0 a 1: no aumenta «1%». Un coeficiente hipotético de 0.20
equivale a 22.14%.
Estas interpretaciones de log(precio) describen la media geométrica condicional; también la mediana si el error logarítmico tiene mediana cero. Para la media aritmética en millones se necesita tratar la retransformación. No aplique log a zona, tipo o códigos de estrato. Baños y habitaciones se dejan en niveles porque pueden contener ceros; sumar uno cambiaría la interpretación.
Qué hace este bloque: Añade el cuadrado del área centrada para permitir curvatura y contrasta si ese término aporta al modelo aditivo.
mod8 <- update(mod_multiple, . ~ . + I(area_c^2))
summary(mod8)
##
## Call:
## lm(formula = precio_millon ~ area_c + Zona + Tipo + Banos + Habitaciones +
## Estrato + I(area_c^2), data = Datos_Vivienda)
##
## Residuals:
## Min 1Q Median 3Q Max
## -836.9 -73.9 -3.3 52.2 1107.3
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1.85e+02 1.63e+01 11.36 < 2e-16 ***
## area_c 1.73e+00 3.41e-02 50.78 < 2e-16 ***
## ZonaZona Norte -6.45e+00 1.56e+01 -0.41 0.6792
## ZonaZona Oeste 6.78e+01 1.65e+01 4.12 3.9e-05 ***
## ZonaZona Oriente -5.01e+01 1.71e+01 -2.93 0.0034 **
## ZonaZona Sur 5.05e+00 1.56e+01 0.32 0.7461
## TipoCasa -4.65e+01 5.39e+00 -8.63 < 2e-16 ***
## Banos 5.13e+01 2.14e+00 23.97 < 2e-16 ***
## Habitaciones -2.85e+01 1.83e+00 -15.61 < 2e-16 ***
## Estrato4 1.79e+01 6.59e+00 2.71 0.0067 **
## Estrato5 6.81e+01 6.55e+00 10.39 < 2e-16 ***
## Estrato6 2.84e+02 8.12e+00 34.92 < 2e-16 ***
## I(area_c^2) -9.13e-04 3.77e-05 -24.23 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 164 on 8306 degrees of freedom
## Multiple R-squared: 0.752, Adjusted R-squared: 0.752
## F-statistic: 2.1e+03 on 12 and 8306 DF, p-value: <2e-16
anova(mod_multiple, mod8)
| Res.Df | RSS | Df | Sum of Sq | F | Pr(>F) |
|---|---|---|---|---|---|
| 8307 | 238376382 | NA | NA | NA | NA |
| 8306 | 222634658 | 1 | 15741724 | 587.3 | 0 |
I() pide el cuadrado aritmético. La pendiente por m² es
beta_area + 2 * beta_cuadrado * (área - 100). A 100 m² vale
beta_area; a 200 m² vale
beta_area + 200 * beta_cuadrado. Un coeficiente cuadrático
negativo indica pendiente decreciente, no necesariamente precio
decreciente en todo el rango. Evite extrapolar esa curva a tamaños no
observados.
Qué hace este bloque: Resume R² ajustado y AIC en
dos tablas separadas según la respuesta. Las salidas completas de cada
modelo siguen disponibles en sus summary().
comparar <- function(lista) {
data.frame(Modelo = names(lista),
R2_ajustado = sapply(lista, function(m) summary(m)$adj.r.squared),
AIC = sapply(lista, AIC), row.names = NULL)
}
tabla(comparar(list(Aditivo = mod_multiple, Interaccion = mod4,
Lin_log = mod5, Cuadratico = mod8)))
| Modelo | R2_ajustado | AIC |
|---|---|---|
| Aditivo | 0.734 | 109013 |
| Interaccion | 0.751 | 108483 |
| Lin_log | 0.735 | 108984 |
| Cuadratico | 0.752 | 108446 |
tabla(comparar(list(Log_lin = mod6, Log_log = mod7)))
| Modelo | R2_ajustado | AIC |
|---|---|---|
| Log_lin | 0.809 | 3173.8 |
| Log_log | 0.857 | 782.9 |
Compare dentro de cada tabla: misma respuesta y mismas filas. Mayor R² ajustado y menor AIC pueden orientar, pero no bastan. No compare directamente R² ni AIC de precio con los de log(precio); para AIC entre escalas haría falta corregir la transformación de la verosimilitud.
Para elegir, siga tres preguntas: ¿la interpretación tiene sentido?, ¿mejoraron los patrones de residuos?, ¿predice mejor en datos reservados? Esta comparación interna no sustituye la tercera pregunta. No elija automáticamente el modelo más complejo ni afirme que la transformación «arregló» los supuestos.
Qué hace este bloque: Organiza cuatro gráficos para revisar el modelo aditivo en niveles y luego restaura la disposición gráfica original.
op <- par(mfrow = c(2, 2))
plot(mod_multiple, which = c(1, 2, 3, 5))
par(op)
Qué hace este bloque: Repite los mismos cuatro gráficos para el modelo log–log. Compare la forma de los patrones con los del bloque anterior.
op <- par(mfrow = c(2, 2))
plot(mod7, which = c(1, 2, 3, 5))
par(op)
Compare los patrones del modelo aditivo en niveles y del log–log. Los residuos están en unidades diferentes: interesa su estructura, no cuál eje tiene números más pequeños.
| Gráfico | Qué revisar | Qué sugeriría un problema |
|---|---|---|
| Residuos vs ajustados | Errores alrededor de cero | Curvatura: forma funcional insuficiente |
| Q–Q | Cercanía a la recta, especialmente en colas | Colas alejadas: normalidad dudosa |
| Scale–Location | Dispersión aproximadamente estable | Embudo: posible heterocedasticidad |
| Residuos vs leverage | Perfiles extremos y distancia de Cook | Casos que pueden influir mucho en los coeficientes |
Describa lo que vea al ejecutar, sin asumir de antemano que se cumplen los supuestos. Cook menor que 1 no garantiza ausencia de influencia. Revise casos marcados y su contexto antes de eliminarlos. Si prefiere otro candidato, repita el diagnóstico para ese modelo.
Qué hace este bloque: Si están disponibles los paquetes, evalúa heterocedasticidad, muestra inferencia robusta HC3 del log–log y contrasta las interacciones con HC3.
if (requireNamespace("lmtest", quietly = TRUE) &&
requireNamespace("sandwich", quietly = TRUE)) {
print(lmtest::bptest(mod_multiple))
print(lmtest::bptest(mod7))
print(lmtest::coeftest(mod7, vcov. = sandwich::vcovHC(mod7, type = "HC3")))
print(lmtest::waldtest(mod_multiple, mod4,
vcov = function(m) sandwich::vcovHC(m, type = "HC3"), test = "F"))
} else {
cat("Para las pruebas opcionales se requieren lmtest y sandwich. Los gráficos sí están disponibles.")
}
##
## studentized Breusch-Pagan test
##
## data: mod_multiple
## BP = 1768, df = 11, p-value <2e-16
##
##
## studentized Breusch-Pagan test
##
## data: mod7
## BP = 477, df = 11, p-value <2e-16
##
##
## t test of coefficients:
##
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.57860 0.05157 50.00 < 2e-16 ***
## ZonaZona Norte -0.10376 0.02710 -3.83 0.00013 ***
## ZonaZona Oeste 0.04329 0.02914 1.49 0.13751
## ZonaZona Oriente -0.20494 0.03051 -6.72 2.0e-11 ***
## ZonaZona Sur -0.09120 0.02743 -3.32 0.00089 ***
## TipoCasa -0.03011 0.00996 -3.02 0.00250 **
## Banos 0.08337 0.00414 20.12 < 2e-16 ***
## Habitaciones -0.02572 0.00374 -6.87 6.8e-12 ***
## Estrato4 0.25452 0.01059 24.02 < 2e-16 ***
## Estrato5 0.45448 0.01086 41.84 < 2e-16 ***
## Estrato6 0.77456 0.01480 52.33 < 2e-16 ***
## log(Area_contruida) 0.56565 0.01182 47.86 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Wald test
##
## Model 1: precio_millon ~ area_c + Zona + Tipo + Banos + Habitaciones +
## Estrato
## Model 2: precio_millon ~ area_c + Zona + Tipo + Banos + Habitaciones +
## Estrato + area_c:Zona
## Res.Df Df F Pr(>F)
## 1 8307
## 2 8303 4 28.8 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Breusch–Pagan contrasta varianza constante. HC3 cambia los errores estándar, no los coeficientes; la última prueba evalúa conjuntamente las interacciones con esa corrección. Son herramientas de inferencia aproximada, no una reparación de variables omitidas ni de dependencia entre viviendas del mismo barrio.
Tres límites: un valor p grande no demuestra un supuesto; un R² de 0.59 no basta para llamar «bueno» al modelo; precios o áreas no necesitan ser normales para estimar una regresión. La independencia y la representatividad también dependen de cómo se obtuvieron los anuncios.
No. Lo anterior fue una construcción gradual guiada
por preguntas. step() automatiza una búsqueda según AIC.
Puede servir para explorar, pero puede elegir resultados inestables y no
sabe qué variables necesita la pregunta.
Qué hace este bloque: Ejecuta, solo si usted lo activa, una búsqueda hacia atrás por AIC dentro de la escala log. Conserva el área y muestra el resumen del modelo seleccionado.
# Ejecutar solo como extensión: selección hacia atrás en la misma escala log.
# Conservamos el área por ser la variable central de la pregunta.
mod_step <- step(mod7, direction = "backward",
scope = list(lower = ~ log(Area_contruida), upper = formula(mod7)), trace = 1)
summary(mod_step)
La salida muestra qué términos se retiran y cómo cambia AIC. No busque con este procedimiento entre precio y log(precio), ni interprete sus valores p como si el modelo se hubiera definido antes de ver los datos. Evalúe el resultado con nuevos datos y compruebe que siga respondiendo la pregunta.
Qué hace este bloque: Toma un perfil observado, calcula una predicción con intervalo en niveles y una estimación de la media en millones desde el modelo log–log.
# Perfil observado, usado aquí para ilustrar predict(), no para medir desempeño.
nueva <- Datos_Vivienda[1, , drop = FALSE]
tabla(nueva[c("Area_contruida", "Zona", "Tipo")])
| Area_contruida | Zona | Tipo |
|---|---|---|
| 237 | Zona Sur | Casa |
predict(mod_multiple, newdata = nueva, interval = "prediction")
## fit lwr upr
## 1 801.1 468.9 1133
# Media en millones desde el modelo log–log: corrección de smearing.
eta <- predict(mod7, newdata = nueva)
exp(eta) * mean(exp(resid(mod7)))
## 1
## 789.6
El intervalo de predicción contempla la variación de una oferta individual. Un intervalo de confianza para el precio medio sería más estrecho. Estos intervalos clásicos dependen de independencia, normalidad y homocedasticidad de los errores; HC3 no los vuelve robustos automáticamente. Para evaluar capacidad predictiva, reserve datos que no se usen al ajustar y compare errores en millones. La última línea aproxima la media desde log(precio), usando smearing; un factor global puede fallar si la distribución del error logarítmico cambia con el perfil.
Guion de cierre: «Empecé con área; agregué variables para comparar perfiles más parecidos; probé si la pendiente variaba por zona; exploré transformaciones por la forma y dispersión de la relación. Prefiero ___ porque , aunque queda la limitación . El R² por sí solo no responde esas preguntas».
Tarea breve: filtre por Valle del Lili, compare
precio ~ área con
log(precio) ~ log(área) + Tipo y agregue una interacción
área×tipo. Interprete una pendiente, una dummy y la interacción. Revise
residuos y explique cuál modelo elegiría. Verifique antes que ambos
tipos tengan observaciones suficientes; no incluya barrio ni otras
categorías que queden constantes tras filtrar.
Idea para recordar: simple = una asociación; múltiple = una asociación ajustada; interacción = una pendiente que cambia; logaritmos = otra escala para interpretar. Ninguno convierte automáticamente una asociación en causalidad. ¿