Introducción

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.

Datos de vivienda en El Ingenio

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))

Limpieza de los nombres de barrio

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:

  • Se excluye floor del análisis por sus datos faltantes y su interpretación diferente entre casas y apartamentos.
  • Se eliminan registros sin información en las variables que utilizan los modelos.
  • Se conservan valores positivos: precios y áreas, baños y habitaciones.
  • Se trata el estrato como variable categórica. El estrato 5 es la categoría de referencia porque tiene la mayor frecuencia en la muestra.
  • Se convierte el tipo de vivienda a factor. En los datos de análisis, 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².

Exploración

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.

Regresión Lineal Simple

Datos cuantitativos

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:

  • Residuos vs. ajustados: permite identificar curvas o patrones que el modelo no recoge.
  • Q-Q: permite revisar si los residuos se aproximan a una distribución normal.
  • Scale-Location: permite revisar si la dispersión de los residuos cambia con los valores ajustados.
  • Residuos vs. leverage: permite identificar observaciones que podrían influir mucho en el modelo.

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.

Modelo 1: Precio y área construida

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.

Modelo 2: Precio y parqueaderos

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.

Modelo 3: Precio y baños

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.

Resumen de los modelos con datos cuantitativos

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.

Variable dummy o cualitativa

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

  • El intercepto, 312.06 millones de pesos, es el precio promedio de los apartamentos de la muestra.
  • El coeficiente de 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().
  • El precio promedio estimado de las casas se obtiene sumando el intercepto y el coeficiente de la dummy: 647.75 millones de pesos.

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.

Cambio de base o referencia

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.

Estrato como variable categórica: referencia en el estrato 5

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

  • El intercepto, 409.49 millones de pesos, es el precio promedio de las viviendas de estrato 5.
  • 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.

Modelo de Regresión Lineal Múltiple

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.

Modelo 1: Datos cuantitativos y variable dummy

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:

  • Una vivienda de estrato 4 presenta una diferencia estimada de -5.41 millones de pesos respecto a una de estrato 5.
  • Una vivienda de estrato 6 presenta una diferencia estimada de 30.24 millones de pesos respecto a una de estrato 5.

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.

Selección de variables (Step by Step)

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:

  • Cada m² adicional se asocia con 0.83 millones más en el precio.
  • Cada parqueadero adicional se asocia con 26.59 millones más.
  • Cada baño adicional se asocia con 23.9 millones más.
  • Una casa tiene un precio estimado 70.1 millones mayor que un apartamento con las mismas características.
  • El estrato ya no aparece en la especificación seleccionada. Las comparaciones frente al estrato 5 corresponden al modelo completo anterior.

El R² ajustado es 0.825.

Validación de supuestos

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:

  • Un aumento del 1% en el área se asocia con un aumento aproximado del 0.43% en el precio.
  • Cada parqueadero adicional se asocia con un precio 5.2% mayor.
  • Una casa tiene un precio estimado 16.9% mayor que un apartamento con las mismas características.
  • Cada habitación adicional se asocia con una diferencia de 2.4% en el precio. Su p-valor es mayor que 0,05.
  • El estrato 4 presenta una diferencia estimada de -7.4% respecto al estrato 5; el estrato 6 presenta una diferencia de 4% respecto al 5. Ninguna de estas dos diferencias es significativa al 5%.

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

  1. Residuos vs. ajustados: los puntos se distribuyen alrededor de cero y la línea roja presenta variaciones pequeñas. No se observa una curvatura marcada.
  2. Q-Q: la mayor parte de los puntos está cerca de la recta, aunque algunas observaciones se apartan en las colas. Shapiro-Wilk tiene p = 0.672 y no rechaza la normalidad al 5%.
  3. Scale-Location: la línea roja es relativamente estable; el gráfico no muestra un aumento continuo y marcado de la dispersión. Esta es la revisión gráfica de homocedasticidad.
  4. Residuos vs. leverage: algunas observaciones tienen un leverage mayor que la mayoría. Conviene revisar su influencia junto con los demás 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.

Modelo 2: Efecto interacción entre cuantitativas y cualitativas

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.

Predicción

Predicción para una casa y un apartamento

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.

Evaluación del modelo

Error absoluto medio (MAE)

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.

Conclusiones

  • La limpieza de los nombres se realiza antes de seleccionar el barrio. Al incluir El Ingenio y sus etapas I, II y III, se reúnen 267 ofertas y se utilizan 261 viviendas válidas: 161 apartamentos y 100 casas.
  • El área construida presenta la mayor correlación con el precio entre las variables de los modelos simples. En el modelo lineal simple, cada m² adicional se asocia con 1.22 millones de pesos más.
  • El estrato se utiliza como variable categórica, con el estrato 5 como referencia. Sus dos dummies comparan el estrato 4 y el 6 con el 5, manteniendo constantes las demás características en el modelo múltiple completo.
  • Los modelos múltiples permiten interpretar cada característica manteniendo constantes las demás. En el modelo logarítmico seleccionado, el área, los parqueaderos y el tipo de vivienda son significativos al 5%. Las habitaciones y el estrato permanecen por AIC, aunque sus coeficientes no son significativos al 5%.
  • La selección por AIC elimina la interacción entre área y tipo. Por eso las versiones múltiples 1 y 2 terminan con la misma especificación, tanto en niveles como en logaritmos.
  • El modelo múltiple logarítmico presenta residuos más cercanos a la normalidad que el múltiple lineal. Sin embargo, el múltiple 1 lineal obtiene el menor MAE de prueba: 74.28 millones de pesos. Un mejor diagnóstico de normalidad no implica necesariamente un menor error predictivo.
  • Los resultados describen asociaciones en precios de oferta. Características que no están en la base, como antigüedad y acabados, también pueden relacionarse con el precio.