En este trabajo se utiliza la base de ofertas de vivienda en Cali para estimar modelos de regresión lineal simple y múltiple. Se escoge el barrio El Ingenio porque tiene más de 30 observaciones y cuenta con casas y apartamentos, lo que permite incluir el tipo de vivienda como variable dummy.
La variable que se quiere explicar es el precio de oferta de la vivienda en millones de pesos colombianos. Se estima primero la relación del precio con variables cuantitativas y con el tipo de vivienda. Después se combinan las características en modelos múltiples, se revisan los supuestos y se evalúan las predicciones.
library(readxl)
library(dplyr)
library(ggplot2)
housing_data <- read_excel(
"cali_housing_listings.xlsx",
na = c("", "NA"),
col_types = c("text", "text", "text", "numeric", "numeric", "numeric",
"numeric", "numeric", "text", "text", "numeric", "numeric")
)
housing_data$record_id <- seq_len(nrow(housing_data))
Antes de seleccionar el barrio, se conserva la etiqueta original y se crea una versión en minúsculas, sin tildes, con espacios normalizados y sin signos de puntuación. Después se unifican las denominaciones de El Ingenio y sus etapas I, II y III.
selected_labels <- c("el ingenio", "ingenio", "el ingenio i", "ingenio i",
"el ingenio ii", "ingenio ii", "el ingenio iii", "ingenio iii")
housing_data <- housing_data %>%
mutate(
neighborhood_original = neighborhood,
neighborhood_clean = tolower(trimws(neighborhood)),
neighborhood_clean = iconv(neighborhood_clean, to = "ASCII//TRANSLIT"),
neighborhood_clean = trimws(gsub("[^a-z0-9]+", " ", neighborhood_clean)),
neighborhood_group = ifelse(neighborhood_clean %in% selected_labels,
"el ingenio", neighborhood_clean)
)
neighborhood_data <- housing_data %>%
filter(neighborhood_group == "el ingenio")
# Show which original labels contribute observations to the selected group.
neighborhood_audit <- neighborhood_data %>%
count(neighborhood_original, neighborhood_clean, name = "observations")
knitr::kable(neighborhood_audit,
col.names = c("Etiqueta original", "Etiqueta limpia", "Ofertas"))
| Etiqueta original | Etiqueta limpia | Ofertas |
|---|---|---|
| El Ingenio | el ingenio | 1 |
| el ingenio | el ingenio | 202 |
| el ingenio i | el ingenio i | 19 |
| el ingenio ii | el ingenio ii | 21 |
| el ingenio iii | el ingenio iii | 21 |
| ingenio | ingenio | 1 |
| ingenio i | ingenio i | 1 |
| ingenio ii | ingenio ii | 1 |
nrow(neighborhood_data)
## [1] 267
table(neighborhood_data$property_type)
##
## Apartamento Casa
## 163 104
La selección reúne 267 ofertas: 104 casas y 163 apartamentos. Se incorporan 64 ofertas adicionales respecto al filtro que solo reconocía la etiqueta general «El Ingenio». La tabla permite verificar las etiquetas incluidas; «Palmas del Ingenio» se conserva como un nombre distinto.
Antes de modelar se realiza una limpieza básica:
floor del análisis por sus datos faltantes y
su interpretación diferente entre casas y apartamentos.Apartamento se representa como apartment y
Casa como house; la categoría base es
apartamento.analysis_data <- neighborhood_data %>%
mutate(
socioeconomic_stratum = factor(socioeconomic_stratum,
levels = c("5", "4", "6")),
property_type = factor(property_type,
levels = c("Apartamento", "Casa"),
labels = c("apartment", "house"))
) %>%
filter(
price_million_cop > 0, built_area_m2 > 0,
parking_spaces >= 0, bathrooms > 0, bedrooms > 0,
!is.na(socioeconomic_stratum), !is.na(property_type)
)
nrow(analysis_data)
## [1] 261
table(analysis_data$property_type)
##
## apartment house
## 161 100
table(analysis_data$socioeconomic_stratum)
##
## 5 4 6
## 196 13 52
summary(analysis_data[, c("price_million_cop", "built_area_m2",
"parking_spaces", "bathrooms", "bedrooms")])
## price_million_cop built_area_m2 parking_spaces bathrooms
## Min. : 175.0 Min. : 56.0 Min. : 1.000 Min. :1.000
## 1st Qu.: 290.0 1st Qu.: 97.0 1st Qu.: 1.000 1st Qu.:2.000
## Median : 350.0 Median : 136.0 Median : 2.000 Median :3.000
## Mean : 440.7 Mean : 204.2 Mean : 1.912 Mean :3.536
## 3rd Qu.: 550.0 3rd Qu.: 250.0 3rd Qu.: 2.000 3rd Qu.:4.000
## Max. :1450.0 Max. :1200.0 Max. :10.000 Max. :9.000
## bedrooms
## Min. :2.00
## 1st Qu.:3.00
## Median :4.00
## Mean :3.82
## 3rd Qu.:4.00
## Max. :9.00
Después de la limpieza quedan 261 viviendas: 100 casas y 161 apartamentos. Se excluyen 6 registros por los criterios anteriores. El precio va de 175 a 1.450 millones de pesos y el área de 56 a 1.200 m². La mediana del área es 136 m².
cor(analysis_data[, c("price_million_cop", "built_area_m2", "parking_spaces",
"bathrooms", "bedrooms")])
## price_million_cop built_area_m2 parking_spaces bathrooms
## price_million_cop 1.0000000 0.8808424 0.5971010 0.7560494
## built_area_m2 0.8808424 1.0000000 0.5335214 0.7307955
## parking_spaces 0.5971010 0.5335214 1.0000000 0.5143647
## bathrooms 0.7560494 0.7307955 0.5143647 1.0000000
## bedrooms 0.7269309 0.7117438 0.5257922 0.7861861
## bedrooms
## price_million_cop 0.7269309
## built_area_m2 0.7117438
## parking_spaces 0.5257922
## bathrooms 0.7861861
## bedrooms 1.0000000
La correlación del precio con el área construida es 0.88; con parqueaderos, 0.6; y con baños, 0.76. Se utilizan estas tres variables en los modelos simples.
El estrato y el tipo de vivienda se analizan como variables categóricas y se incorporan a las regresiones mediante dummies.
ggplot(analysis_data, aes(x = built_area_m2, y = price_million_cop,
color = property_type)) +
geom_point(alpha = 0.6) +
geom_smooth(method = "lm", formula = y ~ x, se = FALSE) +
scale_color_manual(values = c("apartment" = "#2878A5", "house" = "#C16C2A"),
labels = c("Apartamento", "Casa")) +
labs(title = "Precio y área construida en El Ingenio",
x = "Área construida (m²)", y = "Precio (millones de pesos)",
color = "Tipo de vivienda") +
theme_minimal()
En el gráfico se observa una relación positiva entre área y precio. Las casas tienen un rango de áreas más amplio y presentan ofertas de mayor precio. Las líneas por tipo de vivienda motivan revisar si el efecto del área cambia entre casas y apartamentos mediante una interacción.
Se estima el precio en función de una característica a la vez: área construida, parqueaderos o baños. En cada caso se interpreta la pendiente, se revisan los residuos y se prueba una transformación logarítmica.
Para revisar los supuestos se utilizan los cuatro gráficos de residuos:
También se aplica Shapiro-Wilk, cuya hipótesis nula es la normalidad de los residuos. Si el p-valor es menor que 0,05, se rechaza esa hipótesis; si es mayor, no se rechaza. La homocedasticidad se revisa mediante los gráficos.
La independencia de las observaciones depende de cómo se recolectaron los anuncios. La base no permite comprobar si un mismo inmueble aparece más de una vez, de modo que estos gráficos no garantizan ese supuesto.
area_model <- lm(price_million_cop ~ built_area_m2, data = analysis_data)
summary(area_model)
##
## Call:
## lm(formula = price_million_cop ~ built_area_m2, data = analysis_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -293.92 -59.38 -13.01 47.66 477.76
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 192.14382 10.67257 18.00 <2e-16 ***
## built_area_m2 1.21683 0.04064 29.94 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 108.4 on 259 degrees of freedom
## Multiple R-squared: 0.7759, Adjusted R-squared: 0.775
## F-statistic: 896.6 on 1 and 259 DF, p-value: < 2.2e-16
Interpretación
La ecuación estimada es:
\[ \widehat{precio} = 192.14 + 1.22\, área. \]
Por cada m² adicional, el precio aumenta en promedio 1.22 millones de pesos en esta muestra. El R² es 0.776: el modelo explica aproximadamente el 77.6% de la variación del precio. El intercepto corresponde a una vivienda de área cero y no tiene una interpretación práctica aquí.
Validación de supuestos
par(mfrow = c(2, 2), ann = TRUE)
plot(area_model)
shapiro.test(residuals(area_model))
##
## Shapiro-Wilk normality test
##
## data: residuals(area_model)
## W = 0.9336, p-value = 1.928e-09
Shapiro-Wilk tiene un p-valor menor que 0,05, por lo que se rechaza la normalidad de los residuos. Los gráficos permiten revisar la dispersión y el efecto de las viviendas de mayor área. Se prueba una transformación logarítmica para buscar un ajuste más adecuado.
En Residuos vs. ajustados, la línea roja se curva y desciende para las viviendas de mayor área. En Scale-Location, la dispersión aumenta con el valor ajustado, lo que sugiere heterocedasticidad. El Q-Q se aparta de la recta en las colas y aparecen algunas viviendas con leverage elevado. Estos resultados justifican probar logaritmos del precio y del área.
Transformación del modelo
area_log_model <- lm(log(price_million_cop) ~ log(built_area_m2),
data = analysis_data)
summary(area_log_model)
##
## Call:
## lm(formula = log(price_million_cop) ~ log(built_area_m2), data = analysis_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.54638 -0.14437 -0.01631 0.16369 0.63191
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.90257 0.09982 29.08 <2e-16 ***
## log(built_area_m2) 0.60589 0.01948 31.10 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.2031 on 259 degrees of freedom
## Multiple R-squared: 0.7888, Adjusted R-squared: 0.788
## F-statistic: 967.3 on 1 and 259 DF, p-value: < 2.2e-16
Interpretación
En un modelo log-log, el coeficiente del área es una elasticidad. Si el área aumenta 1%, el precio aumenta aproximadamente 0.61%. El R² es 0.789 y corresponde al logaritmo del precio, por lo que no se compara directamente con el R² del modelo en niveles.
Validación de supuestos
par(mfrow = c(2, 2), ann = TRUE)
plot(area_log_model)
shapiro.test(residuals(area_log_model))
##
## Shapiro-Wilk normality test
##
## data: residuals(area_log_model)
## W = 0.99093, p-value = 0.106
El p-valor de Shapiro-Wilk es aproximadamente 0.106. Es mayor que 0,05, por lo que no se rechaza la normalidad de los residuos al 5%. La transformación mejora este diagnóstico respecto al modelo lineal.
La línea de Residuos vs. ajustados presenta menos curvatura, el Q-Q se aproxima más a la recta y Scale-Location es más estable. Persisten algunos residuos extremos, pero las viviendas grandes ya no presentan un leverage tan separado del resto. La transformación log-log resulta pertinente para este modelo por la mejora de los diagnósticos.
parking_model <- lm(price_million_cop ~ parking_spaces, data = analysis_data)
summary(parking_model)
##
## Call:
## lm(formula = price_million_cop ~ parking_spaces, data = analysis_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -293.4 -101.6 -51.6 33.4 998.4
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 203.65 22.82 8.924 <2e-16 ***
## parking_spaces 123.97 10.35 11.979 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 183.7 on 259 degrees of freedom
## Multiple R-squared: 0.3565, Adjusted R-squared: 0.354
## F-statistic: 143.5 on 1 and 259 DF, p-value: < 2.2e-16
Interpretación
Cada parqueadero adicional se asocia con 123.97 millones de pesos más en el precio promedio. El R² es 0.357. Como este modelo no controla por área ni por otras características, el coeficiente también puede recoger diferencias entre viviendas grandes y pequeñas.
Validación de supuestos
par(mfrow = c(2, 2), ann = TRUE)
plot(parking_model)
shapiro.test(residuals(parking_model))
##
## Shapiro-Wilk normality test
##
## data: residuals(parking_model)
## W = 0.79366, p-value < 2.2e-16
Se rechaza la normalidad de los residuos, porque el p-valor de Shapiro-Wilk es menor que 0,05. Los puntos aparecen agrupados por el número de parqueaderos, que es una variable de conteo.
En el Q-Q hay una separación marcada de la cola superior respecto a la recta. En Scale-Location la dispersión cambia entre los grupos de parqueaderos; además, hay observaciones con residuos o leverage elevados. La relación lineal con esta única variable deja diferencias de precio importantes sin explicar, por lo que se revisa una transformación del precio.
Transformación del modelo
Se aplica logaritmo al precio y se conserva el número de parqueaderos en niveles. Así se pueden incluir viviendas con cero parqueaderos.
parking_log_model <- lm(log(price_million_cop) ~ parking_spaces,
data = analysis_data)
summary(parking_log_model)
##
## Call:
## lm(formula = log(price_million_cop) ~ parking_spaces, data = analysis_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.85948 -0.23497 -0.06312 0.14086 1.36558
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 5.52736 0.04418 125.11 <2e-16 ***
## parking_spaces 0.23796 0.02004 11.88 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.3556 on 259 degrees of freedom
## Multiple R-squared: 0.3526, Adjusted R-squared: 0.3501
## F-statistic: 141 on 1 and 259 DF, p-value: < 2.2e-16
Interpretación
Cada parqueadero adicional se asocia con un precio 26.9% mayor, calculado como \(100[\exp(\hat\beta)-1]\). El R² para el logaritmo del precio es 0.353.
Validación de supuestos
par(mfrow = c(2, 2), ann = TRUE)
plot(parking_log_model)
shapiro.test(residuals(parking_log_model))
##
## Shapiro-Wilk normality test
##
## data: residuals(parking_log_model)
## W = 0.93207, p-value = 1.395e-09
La transformación no elimina el problema de normalidad: el p-valor sigue siendo menor que 0,05.
La línea de Residuos vs. ajustados conserva curvatura, Scale-Location no es plana y el Q-Q mantiene desviaciones en las colas. También persisten observaciones influyentes que conviene revisar. La transformación se evaluó, pero no corrige suficientemente los problemas de este modelo simple; no se considera superior solo por haber aplicado logaritmos.
bathroom_model <- lm(price_million_cop ~ bathrooms, data = analysis_data)
summary(bathroom_model)
##
## Call:
## lm(formula = price_million_cop ~ bathrooms, data = analysis_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -386.64 -86.73 -11.73 65.09 711.45
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 13.089 24.801 0.528 0.598
## bathrooms 120.910 6.504 18.590 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 149.9 on 259 degrees of freedom
## Multiple R-squared: 0.5716, Adjusted R-squared: 0.57
## F-statistic: 345.6 on 1 and 259 DF, p-value: < 2.2e-16
Interpretación
Cada baño adicional se asocia con 120.91 millones de pesos más en el precio promedio. El R² es 0.572. La interpretación corresponde a un modelo simple que no mantiene constantes las demás características.
Validación de supuestos
par(mfrow = c(2, 2), ann = TRUE)
plot(bathroom_model)
shapiro.test(residuals(bathroom_model))
##
## Shapiro-Wilk normality test
##
## data: residuals(bathroom_model)
## W = 0.94998, p-value = 8.482e-08
El p-valor es menor que 0,05 y se rechaza la normalidad de los residuos.
En Residuos vs. ajustados se observa curvatura y en Scale-Location la dispersión aumenta con los valores ajustados. El Q-Q presenta desviaciones en las colas y algunas observaciones se separan del resto en el gráfico de leverage. Por esos indicios de falta de ajuste, heterocedasticidad y no normalidad, se prueba el logaritmo del precio.
Transformación del modelo
bathroom_log_model <- lm(log(price_million_cop) ~ bathrooms,
data = analysis_data)
summary(bathroom_log_model)
##
## Call:
## lm(formula = log(price_million_cop) ~ bathrooms, data = analysis_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.88105 -0.18741 0.01059 0.17489 0.82732
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 5.15802 0.04795 107.57 <2e-16 ***
## bathrooms 0.23309 0.01258 18.54 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.2897 on 259 degrees of freedom
## Multiple R-squared: 0.5702, Adjusted R-squared: 0.5685
## F-statistic: 343.6 on 1 and 259 DF, p-value: < 2.2e-16
Interpretación
Un baño adicional se asocia con un precio 26.2% mayor. El R² para el logaritmo del precio es 0.57.
Validación de supuestos
par(mfrow = c(2, 2), ann = TRUE)
plot(bathroom_log_model)
shapiro.test(residuals(bathroom_log_model))
##
## Shapiro-Wilk normality test
##
## data: residuals(bathroom_log_model)
## W = 0.9959, p-value = 0.7266
En esta especificación, Shapiro-Wilk tiene un p-valor de 0.727. No se rechaza la normalidad al 5%. Esto debe leerse junto con los gráficos, que también permiten revisar la forma de la relación y la dispersión.
El Q-Q queda más cerca de la recta. La línea de Scale-Location es más estable que en niveles, aunque todavía varía, y Residuos vs. ajustados conserva cierta curvatura entre los grupos de baños. Hay algunas observaciones con leverage mayor que la mayoría. El logaritmo del precio resulta pertinente como mejora del modelo, especialmente para la normalidad, pero no garantiza que todos los supuestos se cumplan.
| Modelo | R² | Normalidad: Shapiro al 5% |
|---|---|---|
| Precio ~ área | 0.776 | Se rechaza |
| log(precio) ~ log(área) | 0.789 | No se rechaza |
| Precio ~ parqueaderos | 0.357 | Se rechaza |
| log(precio) ~ parqueaderos | 0.353 | Se rechaza |
| Precio ~ baños | 0.572 | Se rechaza |
| log(precio) ~ baños | 0.57 | No se rechaza |
El área tiene la asociación lineal más fuerte con el precio entre las tres variables estudiadas. Las transformaciones no resuelven por igual los problemas de todos los modelos. La capacidad predictiva se comparará con MAE, expresado en millones de pesos.
Ahora se utiliza una variable cualitativa: el tipo de vivienda. En El Ingenio hay dos categorías, casas y apartamentos.
table(analysis_data$property_type)
##
## apartment house
## 161 100
Modelo con variable dummy
El modelo compara el precio promedio de las casas con el de los
apartamentos. La categoría base es apartment, que se
definió primero al crear el factor.
type_model <- lm(price_million_cop ~ property_type, data = analysis_data)
summary(type_model)
##
## Call:
## lm(formula = price_million_cop ~ property_type, data = analysis_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -377.75 -72.06 -12.06 42.94 802.25
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 312.06 12.61 24.75 <2e-16 ***
## property_typehouse 335.69 20.37 16.48 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 160 on 259 degrees of freedom
## Multiple R-squared: 0.512, Adjusted R-squared: 0.5101
## F-statistic: 271.7 on 1 and 259 DF, p-value: < 2.2e-16
Interpretación
property_typehouse,
335.69 millones, es la diferencia del precio promedio de las casas
respecto al de los apartamentos. Su p-valor se puede revisar en la
salida de summary().Evaluación del modelo
El R² es 0.512: el tipo de vivienda explica aproximadamente el 51.2% de la variación del precio. Esta comparación todavía no controla por área, parqueaderos ni otras características. En los modelos múltiples se revisará la diferencia entre tipos manteniendo constantes esas características.
Se cambia la categoría base a casa, siguiendo el procedimiento de la actividad de clase. Para conservar la referencia de los demás modelos, se trabaja con una copia de los datos.
house_reference_data <- analysis_data
house_reference_data$property_type <- factor(
house_reference_data$property_type,
levels = c("house", "apartment")
)
type_house_base_model <- lm(price_million_cop ~ property_type,
data = house_reference_data)
summary(type_house_base_model)
##
## Call:
## lm(formula = price_million_cop ~ property_type, data = house_reference_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -377.75 -72.06 -12.06 42.94 802.25
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 647.75 16.00 40.50 <2e-16 ***
## property_typeapartment -335.69 20.37 -16.48 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 160 on 259 degrees of freedom
## Multiple R-squared: 0.512, Adjusted R-squared: 0.5101
## F-statistic: 271.7 on 1 and 259 DF, p-value: < 2.2e-16
Interpretación
Ahora el intercepto es el precio promedio de las casas:
647.75 millones. El coeficiente de
property_typeapartment indica la diferencia de los
apartamentos respecto a las casas: -335.69
millones.
El ajuste y el R² son los mismos. Cambiar la categoría base modifica cómo se leen el intercepto y los coeficientes.
En la muestra hay 196 viviendas de estrato 5, 13 de estrato 4 y 52 de estrato 6. Se toma el estrato 5 como referencia, por ser la categoría más frecuente.
Como existen tres categorías, R genera dos variables dummy. La codificación es:
| Estrato | Viviendas | Dummy estrato 4 | Dummy estrato 6 |
|---|---|---|---|
| 5: referencia | 196 | 0 | 0 |
| 4 | 13 | 1 | 0 |
| 6 | 52 | 0 | 1 |
Cada dummy es dicotómica. Esta codificación conserva los tres estratos y permite comparar el 4 y el 6 con el 5.
stratum_model <- lm(price_million_cop ~ socioeconomic_stratum,
data = analysis_data)
summary(stratum_model)
##
## Call:
## lm(formula = price_million_cop ~ socioeconomic_stratum, data = analysis_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -346.98 -139.49 -66.98 83.02 1040.51
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 409.495 15.749 26.002 < 2e-16 ***
## socioeconomic_stratum4 -3.956 63.146 -0.063 0.95
## socioeconomic_stratum6 157.486 34.393 4.579 7.27e-06 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 220.5 on 258 degrees of freedom
## Multiple R-squared: 0.07631, Adjusted R-squared: 0.06915
## F-statistic: 10.66 on 2 and 258 DF, p-value: 3.571e-05
Interpretación
socioeconomic_stratum4, -3.96
millones, es la diferencia promedio del estrato 4 respecto al 5. Su
p-valor es 0.95; no es significativa al 5%.socioeconomic_stratum6, 157.49
millones, es la diferencia promedio del estrato 6 respecto al 5. Su
p-valor es menor que 0,05 y la diferencia es significativa a ese
nivel.Este modelo compara promedios por estrato sin controlar por área ni otras características. Los modelos múltiples permiten hacer la comparación manteniendo constantes las demás variables.
Se plantean dos especificaciones: una con los efectos de las características y las variables dummy, y otra que además incorpora la interacción entre área y tipo de vivienda. Luego se aplica selección de variables a cada una para determinar qué términos conserva.
Se incluyen área, parqueaderos, baños, habitaciones, estrato y tipo
de vivienda. Las referencias son estrato 5 y
apartamento. R crea socioeconomic_stratum4
y socioeconomic_stratum6 para comparar cada estrato con el
5, y property_typehouse para comparar casas con
apartamentos.
multiple_model <- lm(
price_million_cop ~ built_area_m2 + parking_spaces + bathrooms +
bedrooms + socioeconomic_stratum + property_type,
data = analysis_data
)
summary(multiple_model)
##
## Call:
## lm(formula = price_million_cop ~ built_area_m2 + parking_spaces +
## bathrooms + bedrooms + socioeconomic_stratum + property_type,
## data = analysis_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -285.63 -47.65 -6.87 53.33 438.46
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 92.08123 21.90832 4.203 3.66e-05 ***
## built_area_m2 0.80790 0.05922 13.642 < 2e-16 ***
## parking_spaces 24.25201 6.65944 3.642 0.000328 ***
## bathrooms 19.35923 7.52686 2.572 0.010682 *
## bedrooms 10.03774 7.95153 1.262 0.207979
## socioeconomic_stratum4 -5.40727 27.55803 -0.196 0.844600
## socioeconomic_stratum6 30.24191 15.42435 1.961 0.051016 .
## property_typehouse 64.34953 17.94501 3.586 0.000403 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 95.26 on 253 degrees of freedom
## Multiple R-squared: 0.8309, Adjusted R-squared: 0.8262
## F-statistic: 177.6 on 7 and 253 DF, p-value: < 2.2e-16
Interpretación del estrato en el modelo completo
Manteniendo constantes el área, los parqueaderos, los baños, las habitaciones y el tipo de vivienda:
Los p-valores de ambas comparaciones se revisan en la salida de
summary(). Cada coeficiente compara categorías; no
representa un incremento por pasar de un nivel de estrato al
siguiente.
multiple_step_model <- step(multiple_model, direction = "both", trace = 0)
summary(multiple_step_model)
##
## Call:
## lm(formula = price_million_cop ~ built_area_m2 + parking_spaces +
## bathrooms + property_type, data = analysis_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -261.16 -48.84 -5.45 51.37 424.32
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 108.3777 18.3248 5.914 1.06e-08 ***
## built_area_m2 0.8328 0.0577 14.432 < 2e-16 ***
## parking_spaces 26.5915 6.5719 4.046 6.90e-05 ***
## bathrooms 23.8976 6.5972 3.622 0.000352 ***
## property_typehouse 70.1016 17.7242 3.955 9.91e-05 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 95.69 on 256 degrees of freedom
## Multiple R-squared: 0.8274, Adjusted R-squared: 0.8247
## F-statistic: 306.7 on 4 and 256 DF, p-value: < 2.2e-16
Interpretación
La selección por AIC elimina las habitaciones y el estrato. Mantiene área, parqueaderos, baños y tipo de vivienda. Dejando las demás variables constantes:
El R² ajustado es 0.825.
par(mfrow = c(2, 2), ann = TRUE)
plot(multiple_step_model)
shapiro.test(residuals(multiple_step_model))
##
## Shapiro-Wilk normality test
##
## data: residuals(multiple_step_model)
## W = 0.93742, p-value = 4.404e-09
Shapiro-Wilk rechaza la normalidad. Los gráficos ayudan a revisar si persisten cambios en la dispersión y observaciones que se apartan del patrón general. Se prueba un modelo con logaritmos.
La línea de Residuos vs. ajustados es relativamente cercana a cero, pero la amplitud de los residuos crece para las viviendas de mayor precio. Scale-Location muestra una tendencia ascendente y el Q-Q se aparta de la recta en las colas. Algunas observaciones presentan residuos o leverage elevados. Estos resultados justifican evaluar una especificación logarítmica.
Transformación del modelo
multiple_log_model <- lm(
log(price_million_cop) ~ log(built_area_m2) + parking_spaces + bathrooms +
bedrooms + socioeconomic_stratum + property_type,
data = analysis_data
)
multiple_log_step_model <- step(multiple_log_model, direction = "both", trace = 0)
summary(multiple_log_step_model)
##
## Call:
## lm(formula = log(price_million_cop) ~ log(built_area_m2) + parking_spaces +
## bedrooms + socioeconomic_stratum + property_type, data = analysis_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.51327 -0.12122 0.00074 0.13915 0.55081
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 3.56356 0.13985 25.481 < 2e-16 ***
## log(built_area_m2) 0.42624 0.03365 12.666 < 2e-16 ***
## parking_spaces 0.05099 0.01299 3.927 0.000111 ***
## bedrooms 0.02379 0.01382 1.722 0.086328 .
## socioeconomic_stratum4 -0.07693 0.05390 -1.427 0.154749
## socioeconomic_stratum6 0.03880 0.03016 1.286 0.199487
## property_typehouse 0.15624 0.03834 4.075 6.16e-05 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.1867 on 254 degrees of freedom
## Multiple R-squared: 0.825, Adjusted R-squared: 0.8209
## F-statistic: 199.6 on 6 and 254 DF, p-value: < 2.2e-16
Interpretación
La selección elimina los baños. Mantiene el logaritmo del área, los parqueaderos, las habitaciones, el estrato y el tipo de vivienda. Dejando las demás variables constantes:
Que AIC conserve una variable no significa que todos sus coeficientes sean significativos al 5%.
El R² ajustado para el logaritmo del precio es 0.821.
Validación de supuestos
par(mfrow = c(2, 2), ann = TRUE)
plot(multiple_log_step_model)
shapiro.test(residuals(multiple_log_step_model))
##
## Shapiro-Wilk normality test
##
## data: residuals(multiple_log_step_model)
## W = 0.99561, p-value = 0.6718
Interpretación de los gráficos
Conclusión del modelo
La distribución de los residuos se aproxima más a la normal que en el modelo múltiple lineal. La comparación de la capacidad predictiva se realizará con los datos de prueba y el MAE.
Se permite que el efecto del área sea diferente entre casas y
apartamentos. La expresión built_area_m2 * property_type
incluye el área, el tipo de vivienda y la interacción entre ambos.
interaction_model <- lm(
price_million_cop ~ built_area_m2 * property_type + parking_spaces +
bathrooms + bedrooms + socioeconomic_stratum,
data = analysis_data
)
summary(interaction_model)
##
## Call:
## lm(formula = price_million_cop ~ built_area_m2 * property_type +
## parking_spaces + bathrooms + bedrooms + socioeconomic_stratum,
## data = analysis_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -290.29 -48.25 -5.82 50.91 433.15
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 100.9477 24.8511 4.062 6.50e-05 ***
## built_area_m2 0.7154 0.1357 5.273 2.89e-07 ***
## property_typehouse 47.9356 28.1312 1.704 0.089614 .
## parking_spaces 23.8969 6.6815 3.577 0.000417 ***
## bathrooms 20.2223 7.6187 2.654 0.008453 **
## bedrooms 10.0506 7.9582 1.263 0.207785
## socioeconomic_stratum4 -3.5756 27.6868 -0.129 0.897345
## socioeconomic_stratum6 29.5614 15.4634 1.912 0.057049 .
## built_area_m2:property_typehouse 0.1054 0.1391 0.758 0.449106
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 95.34 on 252 degrees of freedom
## Multiple R-squared: 0.8313, Adjusted R-squared: 0.8259
## F-statistic: 155.2 on 8 and 252 DF, p-value: < 2.2e-16
Interpretación del modelo completo con interacción
Manteniendo las demás características constantes, la pendiente del área es 0.72 millones de pesos por m² para apartamentos. Para casas se suman el coeficiente del área y el de la interacción: 0.82 millones por m².
El p-valor de la interacción es 0.449. No se detecta una diferencia de pendientes al 5%. A continuación se revisa qué variables conserva AIC.
interaction_step_model <- step(interaction_model, direction = "both", trace = 0)
summary(interaction_step_model)
##
## Call:
## lm(formula = price_million_cop ~ built_area_m2 + property_type +
## parking_spaces + bathrooms, data = analysis_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -261.16 -48.84 -5.45 51.37 424.32
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 108.3777 18.3248 5.914 1.06e-08 ***
## built_area_m2 0.8328 0.0577 14.432 < 2e-16 ***
## property_typehouse 70.1016 17.7242 3.955 9.91e-05 ***
## parking_spaces 26.5915 6.5719 4.046 6.90e-05 ***
## bathrooms 23.8976 6.5972 3.622 0.000352 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 95.69 on 256 degrees of freedom
## Multiple R-squared: 0.8274, Adjusted R-squared: 0.8247
## F-statistic: 306.7 on 4 and 256 DF, p-value: < 2.2e-16
Interpretación
La selección por AIC elimina la interacción, las habitaciones y el estrato. El modelo final utiliza las mismas variables que el múltiple 1 lineal. Por eso tiene una pendiente del área común para casas y apartamentos, de 0.83 millones de pesos por m². La diferencia de precio entre tipos se recoge en la variable dummy.
Validación de supuestos
par(mfrow = c(2, 2), ann = TRUE)
plot(interaction_step_model)
shapiro.test(residuals(interaction_step_model))
##
## Shapiro-Wilk normality test
##
## data: residuals(interaction_step_model)
## W = 0.93742, p-value = 4.404e-09
Como la especificación final coincide con la del múltiple 1 lineal, también coinciden sus resultados y diagnósticos. Se rechaza la normalidad de los residuos.
Transformación del modelo
interaction_log_model <- lm(
log(price_million_cop) ~ log(built_area_m2) * property_type + parking_spaces +
bathrooms + bedrooms + socioeconomic_stratum,
data = analysis_data
)
summary(interaction_log_model)
##
## Call:
## lm(formula = log(price_million_cop) ~ log(built_area_m2) * property_type +
## parking_spaces + bathrooms + bedrooms + socioeconomic_stratum,
## data = analysis_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.51427 -0.12409 0.00135 0.13455 0.51887
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 3.71152 0.22011 16.862 < 2e-16 ***
## log(built_area_m2) 0.39202 0.05030 7.794 1.71e-13 ***
## property_typehouse -0.05384 0.30166 -0.178 0.858482
## parking_spaces 0.04959 0.01310 3.785 0.000192 ***
## bathrooms 0.01148 0.01563 0.734 0.463472
## bedrooms 0.01835 0.01550 1.184 0.237617
## socioeconomic_stratum4 -0.07375 0.05460 -1.351 0.177983
## socioeconomic_stratum6 0.03665 0.03031 1.209 0.227648
## log(built_area_m2):property_typehouse 0.04093 0.05875 0.697 0.486648
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.1871 on 252 degrees of freedom
## Multiple R-squared: 0.8257, Adjusted R-squared: 0.8201
## F-statistic: 149.2 on 8 and 252 DF, p-value: < 2.2e-16
Interpretación del modelo completo transformado
La elasticidad del precio respecto al área es 0.39 en apartamentos y 0.43 en casas. Un aumento del 1% en el área se asocia aproximadamente con esos porcentajes de aumento en el precio, manteniendo las demás características constantes.
El p-valor de la interacción es 0.487, por lo que tampoco se detecta una diferencia de elasticidades al 5% en esta especificación completa.
interaction_log_step_model <- step(interaction_log_model, direction = "both", trace = 0)
summary(interaction_log_step_model)
##
## Call:
## lm(formula = log(price_million_cop) ~ log(built_area_m2) + property_type +
## parking_spaces + bedrooms + socioeconomic_stratum, data = analysis_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.51327 -0.12122 0.00074 0.13915 0.55081
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 3.56356 0.13985 25.481 < 2e-16 ***
## log(built_area_m2) 0.42624 0.03365 12.666 < 2e-16 ***
## property_typehouse 0.15624 0.03834 4.075 6.16e-05 ***
## parking_spaces 0.05099 0.01299 3.927 0.000111 ***
## bedrooms 0.02379 0.01382 1.722 0.086328 .
## socioeconomic_stratum4 -0.07693 0.05390 -1.427 0.154749
## socioeconomic_stratum6 0.03880 0.03016 1.286 0.199487
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.1867 on 254 degrees of freedom
## Multiple R-squared: 0.825, Adjusted R-squared: 0.8209
## F-statistic: 199.6 on 6 and 254 DF, p-value: < 2.2e-16
Interpretación
En la versión logarítmica, AIC elimina la interacción y los baños. El modelo final coincide con el múltiple 1 logarítmico: utiliza logaritmo del área, parqueaderos, habitaciones, estrato y tipo de vivienda. La elasticidad del área es 0.43 para ambos tipos de vivienda.
Validación de supuestos
par(mfrow = c(2, 2), ann = TRUE)
plot(interaction_log_step_model)
shapiro.test(residuals(interaction_log_step_model))
##
## Shapiro-Wilk normality test
##
## data: residuals(interaction_log_step_model)
## W = 0.99561, p-value = 0.6718
Se obtiene el mismo diagnóstico que en el múltiple 1 logarítmico. Shapiro-Wilk no rechaza la normalidad al 5%.
Pertinencia de las transformaciones en los modelos múltiples
En las especificaciones seleccionadas, el logaritmo del precio y del área mejora el Q-Q y estabiliza el gráfico Scale-Location respecto a los modelos en niveles. Por ello, la transformación es pertinente para mejorar los diagnósticos, aunque todavía se revisan las observaciones de mayor leverage. La comparación de MAE determinará cuál obtiene menor error en los datos de prueba; mejorar los supuestos no asegura ganar esa comparación.
Se estima el precio de una casa y de un apartamento de 200 m², 2 parqueaderos, 3 baños, 3 habitaciones y estrato 5. Se utiliza el estrato 5 porque es el más frecuente en la muestra de El Ingenio.
new_housing <- data.frame(
built_area_m2 = c(200, 200), parking_spaces = c(2, 2),
bathrooms = c(3, 3), bedrooms = c(3, 3),
socioeconomic_stratum = factor(c("5", "5"),
levels = levels(analysis_data$socioeconomic_stratum)),
property_type = factor(c("house", "apartment"),
levels = c("apartment", "house"))
)
En los resultados, la primera fila corresponde a la casa y la segunda
al apartamento. Los valores se expresan en millones de pesos. Para los
modelos logarítmicos se utiliza exp() para volver a la
escala del precio.
# Multiple model 1 in levels.
linear_predictions <- predict(multiple_step_model, new_housing,
interval = "prediction")
linear_predictions
## fit lwr upr
## 1 469.9068 279.4827 660.3309
## 2 399.8052 210.5568 589.0536
# Multiple model 1 with logarithms.
log_predictions <- exp(predict(multiple_log_step_model, new_housing,
interval = "prediction"))
log_predictions
## fit lwr upr
## 1 469.4373 323.4899 681.2311
## 2 401.5344 276.9295 582.2055
# Multiple model 2 with logarithms.
interaction_log_predictions <- exp(predict(interaction_log_step_model, new_housing,
interval = "prediction"))
interaction_log_predictions
## fit lwr upr
## 1 469.4373 323.4899 681.2311
## 2 401.5344 276.9295 582.2055
Interpretación
El modelo múltiple lineal estima 469.9 millones para la casa y 399.8 millones para el apartamento. Los modelos logarítmicos estiman 469.4 millones para la casa y 401.5 millones para el apartamento.
Las dos versiones logarítmicas dan las mismas predicciones porque
terminan con la misma especificación. Las columnas lwr y
upr muestran los límites del intervalo de predicción del
95%, que refleja la incertidumbre de estimar el precio de una vivienda
individual.
El modelo lineal seleccionado excluye el estrato. Los modelos logarítmicos lo conservan: las predicciones anteriores utilizan el estrato 5 como referencia y cambiarían si se indicara estrato 4 o 6, manteniendo constantes las demás características.
El MAE indica cuántos millones de pesos se equivoca el modelo, en promedio, al predecir. Un valor menor indica menos error absoluto en los datos de prueba.
Se usa el 80% de las observaciones para entrenar y el 20% para evaluar. La selección de variables se realiza con el conjunto de entrenamiento.
set.seed(123)
training_rows <- sample(seq_len(nrow(analysis_data)),
size = round(0.8 * nrow(analysis_data)))
training_data <- analysis_data[training_rows, ]
test_data <- analysis_data[-training_rows, ]
Se utilizan 209 viviendas para entrenar y 52 para evaluar.
training_area_model <- lm(price_million_cop ~ built_area_m2,
data = training_data)
training_area_log_model <- lm(log(price_million_cop) ~ log(built_area_m2),
data = training_data)
training_multiple_model <- lm(formula(multiple_model), data = training_data)
training_multiple_step_model <- step(training_multiple_model,
direction = "both", trace = 0)
training_multiple_log_model <- lm(formula(multiple_log_model), data = training_data)
training_multiple_log_step_model <- step(training_multiple_log_model,
direction = "both", trace = 0)
training_interaction_log_model <- lm(formula(interaction_log_model), data = training_data)
training_interaction_log_step_model <- step(training_interaction_log_model,
direction = "both", trace = 0)
observed_prices <- test_data$price_million_cop
test_area_predictions <- predict(training_area_model, test_data)
test_area_log_predictions <- exp(predict(training_area_log_model, test_data))
test_multiple_predictions <- predict(training_multiple_step_model, test_data)
test_multiple_log_predictions <- exp(predict(training_multiple_log_step_model, test_data))
test_interaction_log_predictions <- exp(predict(training_interaction_log_step_model, test_data))
mae_table <- data.frame(
model = c("Simple lineal (área)", "Simple log-log (área)", "Múltiple 1 lineal",
"Múltiple 1 logarítmico", "Múltiple 2 logarítmico"),
mae_million_cop = c(
mean(abs(observed_prices - test_area_predictions)),
mean(abs(observed_prices - test_area_log_predictions)),
mean(abs(observed_prices - test_multiple_predictions)),
mean(abs(observed_prices - test_multiple_log_predictions)),
mean(abs(observed_prices - test_interaction_log_predictions))
)
)
knitr::kable(mae_table, digits = 2,
col.names = c("Modelo", "MAE (millones de pesos)"))
| Modelo | MAE (millones de pesos) |
|---|---|
| Simple lineal (área) | 82.38 |
| Simple log-log (área) | 77.43 |
| Múltiple 1 lineal | 74.28 |
| Múltiple 1 logarítmico | 74.58 |
| Múltiple 2 logarítmico | 80.86 |
Interpretación
El modelo simple lineal tiene un MAE de 82.38 millones. Su versión log-log obtiene 77.43 millones y el múltiple lineal obtiene 74.28 millones.
El múltiple 1 lineal obtiene el menor MAE: 74.28 millones de pesos. El múltiple 1 logarítmico obtiene 74.58 millones, mientras que el múltiple 2 logarítmico alcanza 80.86 millones. Los resultados corresponden a esta división de entrenamiento y prueba.
En el entrenamiento, AIC excluye el estrato de los modelos múltiples evaluados y conserva las habitaciones en los logarítmicos. Además, mantiene la interacción en el múltiple 2 logarítmico. Por eso las dos variantes logarítmicas tienen errores de prueba distintos, aunque al ajustarlas con toda la muestra sus especificaciones seleccionadas coincidan. La selección puede variar entre la muestra completa y el conjunto de entrenamiento.