Datos iniciales

Se inicia con la instalación y carga de paquetes, que contienen la base de datos “vivienda”. Se cuenta con un total de 8,322 registros y 13 variables.

  • id: Identificador único de la vivienda
  • zona: Zona de la ciudad (sur, norte, oriente, occidente)
  • piso: Piso en el que está ubicada la vivienda
  • estrato: Estrato socioeconómico
  • preciom: Precio en millones de pesos
  • areaconst: Área construida en metros cuadrados
  • parqueaderos: Número de parqueaderos
  • banios: Número de baños
  • habitaciones: Número de habitaciones
  • tipo: Tipo de vivienda (casa o apartamento)
  • barrio: Barrio
  • longitud: Coordenada de longitud
  • latitud: Coordenada de latitud
# Cargar los datos
data("vivienda")

# Visión general de los datos
glimpse(vivienda)
## Rows: 8,322
## Columns: 13
## $ id           <dbl> 1147, 1169, 1350, 5992, 1212, 1724, 2326, 4386, 1209, 159…
## $ zona         <chr> "Zona Oriente", "Zona Oriente", "Zona Oriente", "Zona Sur…
## $ piso         <chr> NA, NA, NA, "02", "01", "01", "01", "01", "02", "02", "02…
## $ estrato      <dbl> 3, 3, 3, 4, 5, 5, 4, 5, 5, 5, 6, 4, 5, 6, 4, 5, 5, 4, 5, …
## $ preciom      <dbl> 250, 320, 350, 400, 260, 240, 220, 310, 320, 780, 750, 62…
## $ areaconst    <dbl> 70, 120, 220, 280, 90, 87, 52, 137, 150, 380, 445, 355, 2…
## $ parqueaderos <dbl> 1, 1, 2, 3, 1, 1, 2, 2, 2, 2, NA, 3, 2, 2, 1, 4, 2, 2, 2,…
## $ banios       <dbl> 3, 2, 2, 5, 2, 3, 2, 3, 4, 3, 7, 5, 6, 2, 4, 4, 4, 3, 2, …
## $ habitaciones <dbl> 6, 3, 4, 3, 3, 3, 3, 4, 6, 3, 6, 5, 6, 2, 5, 5, 4, 3, 3, …
## $ tipo         <chr> "Casa", "Casa", "Casa", "Casa", "Apartamento", "Apartamen…
## $ barrio       <chr> "20 de julio", "20 de julio", "20 de julio", "3 de julio"…
## $ longitud     <dbl> -76.51168, -76.51237, -76.51537, -76.54000, -76.51350, -7…
## $ latitud      <dbl> 3.43382, 3.43369, 3.43566, 3.43500, 3.45891, 3.36971, 3.4…

Punto 1: Filtro y revisión de la base de datos

Se inicia realizando el filtro de la variable “tipo” por Apartamento

# Filtrar la variable "tipo" por Apartamento

data_apt <- vivienda %>%
  filter(tipo == "Apartamento")

t1<-table(data_apt$zona, data_apt$tipo)
df1<-as.data.frame.matrix(t1)
kable(df1, caption = "Distribución por tipo y zona", format = "markdown")
Distribución por tipo y zona
Apartamento
Zona Centro 24
Zona Norte 1198
Zona Oeste 1029
Zona Oriente 62
Zona Sur 2787

Se identifica por cada variable el total de datos faltantes

md.pattern(data_apt,rotate.names = TRUE)

##      id zona estrato preciom areaconst banios habitaciones tipo barrio longitud
## 3182  1    1       1       1         1      1            1    1      1        1
## 1049  1    1       1       1         1      1            1    1      1        1
## 537   1    1       1       1         1      1            1    1      1        1
## 332   1    1       1       1         1      1            1    1      1        1
##       0    0       0       0         0      0            0    0      0        0
##      latitud parqueaderos piso     
## 3182       1            1    1    0
## 1049       1            1    0    1
## 537        1            0    1    1
## 332        1            0    0    2
##            0          869 1381 2250

Se realiza una imputación basada en la mediana, donde se empleó la mediana calculada para cada tipo de vivienda, en este caso “Apartamentos”, para completar los valores faltantes en las variables correspondientes a ese tipo de vivienda.

# Calcular medianas por tipo de vivienda
data_apt$piso <- as.numeric(data_apt$piso)
medianas <- data_apt %>%
  group_by(tipo) %>%
  summarise(mediana_piso = median(piso, na.rm = TRUE),
            mediana_parqueaderos = median(parqueaderos, na.rm = TRUE))
medianas
# Completar valores faltantes usando la mediana correspondiente
bd_clean <- data_apt %>%
  left_join(medianas, by = "tipo") %>%  # Unir las medianas calculadas al dataframe original
  mutate(piso = if_else(is.na(piso), mediana_piso, piso),
         parqueaderos = if_else(is.na(parqueaderos), mediana_parqueaderos,
                                parqueaderos)) %>%
  select(-mediana_piso, -mediana_parqueaderos)  # Eliminar columnas auxiliares

colSums(is.na(bd_clean))
##           id         zona         piso      estrato      preciom    areaconst 
##            0            0            0            0            0            0 
## parqueaderos       banios habitaciones         tipo       barrio     longitud 
##            0            0            0            0            0            0 
##      latitud 
##            0

Obteniendo finalmente el conjunto de datos:

bd_clean %>%
  head() %>%
  sample_n(3) %>%
  kable("html", caption = " ") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"),
                full_width = FALSE, position = "center")
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
1724 Zona Norte 1 5 240 87 1 3 3 Apartamento acopi -76.51700 3.36971
4386 Zona Norte 1 5 310 137 2 3 4 Apartamento acopi -76.53105 3.38296
5424 Zona Norte 3 4 320 108 2 3 3 Apartamento acopi -76.53638 3.40770

Punto 2: Análisis Exploratorio

Se realiza un análisis exploratorio de los datos enfocado en la correlación entre la variable respuesta (precio de la casa) en función del área construida, estrato, número de baños, número de hábitaciones y zona donde se ubica la vivienda.

Se eliminan las variables que no se emplearan en el modelo de regresión multiple como son: “id”, “piso”, “barrio”, “longitud” y “latitud”.

bd_clean$estrato = as.factor(bd_clean$estrato)
bd_fin = subset(bd_clean, select = -c (id, piso, barrio, longitud, latitud))
bd_fin = bd_fin%>% mutate(across(where(is.character), as.factor))

glimpse(bd_fin) #visualización del nuevo dataset
## Rows: 5,100
## Columns: 8
## $ zona         <fct> Zona Norte, Zona Norte, Zona Norte, Zona Norte, Zona Nort…
## $ estrato      <fct> 5, 5, 4, 5, 6, 4, 5, 3, 3, 6, 6, 4, 5, 6, 5, 5, 3, 5, 5, …
## $ preciom      <dbl> 260, 240, 220, 310, 520, 320, 385, 100, 175, 820, 450, 29…
## $ areaconst    <dbl> 90, 87, 52, 137, 98, 108, 103, 49, 80, 377, 103, 96, 124,…
## $ parqueaderos <dbl> 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 3, 2, 1, 1, 1, 1, 1, …
## $ banios       <dbl> 2, 3, 2, 3, 2, 3, 2, 1, 2, 4, 3, 2, 3, 3, 1, 3, 1, 4, 2, …
## $ habitaciones <dbl> 3, 3, 3, 4, 2, 3, 3, 2, 3, 4, 4, 3, 3, 4, 2, 3, 5, 4, 2, …
## $ tipo         <fct> Apartamento, Apartamento, Apartamento, Apartamento, Apart…
summarytools::descr(bd_fin[,3:7])
## Descriptive Statistics  
## bd_fin  
## N: 5100  
## 
##                     areaconst    banios   habitaciones   parqueaderos   preciom
## ----------------- ----------- --------- -------------- -------------- ---------
##              Mean      112.78      2.62           2.97           1.47    366.94
##           Std.Dev       69.36      1.07           0.68           0.71    289.22
##               Min       35.00      0.00           0.00           1.00     58.00
##                Q1       68.00      2.00           3.00           1.00    175.00
##            Median       90.00      2.00           3.00           1.00    279.00
##                Q3      130.00      3.00           3.00           2.00    430.00
##               Max      932.00      8.00           9.00          10.00   1950.00
##               MAD       41.51      1.48           0.00           0.00    176.43
##               IQR       62.00      1.00           0.00           1.00    255.00
##                CV        0.61      0.41           0.23           0.48      0.79
##          Skewness        2.61      0.90           0.06           2.20      2.16
##       SE.Skewness        0.03      0.03           0.03           0.03      0.03
##          Kurtosis       11.17      0.71           3.82          11.13      5.43
##           N.Valid     5100.00   5100.00        5100.00        5100.00   5100.00
##         Pct.Valid      100.00    100.00         100.00         100.00    100.00
#Analisis de correlación
correlaciones<-round(cor(bd_fin[,3:7], method = "spearman"), 3)
correlaciones
##              preciom areaconst parqueaderos banios habitaciones
## preciom        1.000     0.890        0.736  0.772        0.326
## areaconst      0.890     1.000        0.706  0.800        0.468
## parqueaderos   0.736     0.706        1.000  0.647        0.302
## banios         0.772     0.800        0.647  1.000        0.489
## habitaciones   0.326     0.468        0.302  0.489        1.000
# Visualización de la matriz de correlación
plot_ly(z = correlaciones, 
        x = colnames(correlaciones), 
        y = rownames(correlaciones), 
        type = "heatmap", 
        colorscale = "Cividis") %>%
  layout(title = "Matriz de Correlación (Spearman)",
         xaxis = list(title = ""),
         yaxis = list(title = ""))

Se puede evidenciar una fuerte relación entre el precio y el area construida con un 0.89, como también el número de baños y el area construida con un 0.8.

Precio vs Área Construida

# Gráfico de dispersión Precio vs Área Construida
plot_ly(data = bd_clean, 
        x = ~areaconst, 
        y = ~preciom, 
        type = 'scatter', 
        mode = 'markers',
        marker = list(color = 'rgba(0, 128, 255, 0.6)',
                      size = 10,
                      line = list(color = 'rgba(0, 128, 255, 1.0)', width = 1))) %>%
  layout(title = "Precio vs Área Construida",
         xaxis = list(title = "Área Construida (m²)"),
         yaxis = list(title = "Precio (millones de COP)"))

En el análisis bivariado del precio (en millones de pesos COP) frente a la variable área construida, se evidencia que el precio de la vivienda tiende a aumentar a medida que aumenta el área construida. Este patrón sugiere que, en general, viviendas con una mayor área construida tienen precios más altos.

Precio vs Estrato

# Gráfico de cajas Precio vs Estrato
plot_ly(data = bd_fin, 
        x = ~as.factor(estrato), 
        y = ~preciom, 
        type = 'box',
        marker = list(color = 'rgba(255, 165, 0, 0.6)')) %>%
  layout(title = "Precio vs Estrato",
         xaxis = list(title = "Estrato"),
         yaxis = list(title = "Precio (millones de COP)"))

La relación entre el precio y la variable estrato, se observa que el precio de la vivienda varía según el estrato. Los estratos más altos presentan unos valores más elevados, mientras los más bajos están asociados con precios más bajos.

Precio vs Número de baños

# Gráfico de cajas interactivo para Precio vs Número de Baños
bd_fin$banios_cat <- cut(bd_fin$banios,
                         breaks = c(-Inf, 1, 3, 5, Inf),
                         labels = c("0 a 1", "2 a 3", "4 a 5", "6 o más"))
plot_ly(data = bd_fin, 
        x = ~as.factor(banios_cat), 
        y = ~preciom, 
        type = 'box',
        marker = list(color = 'rgba(34, 139, 34, 0.6)')) %>%
  layout(title = "Precio vs Número de Baños",
         xaxis = list(title = "Número de Baños"),
         yaxis = list(title = "Precio (millones de COP)"))

Las viviendas con mayor número de baños son las de precios más elevados. Se presenta una gran concentración en apartamentos con 2 a 3 baños.

Precio vs Número de Habitaciones

# Gráfico de cajas Precio vs Número de Habitaciones
bd_fin$habitaciones_cat <- cut(bd_fin$habitaciones,
                               breaks = c(-Inf, 1, 3, 5, Inf),
                               labels = c("0 a 1", "2 a 3", "4 a 5", "6 o más"))
plot_ly(data = bd_fin, 
        x = ~as.factor(habitaciones_cat), 
        y = ~preciom, 
        type = 'box',
        marker = list(color = 'rgba(220, 20, 60, 0.6)')) %>%
  layout(title = "Precio vs Número de Habitaciones",
         xaxis = list(title = "Número de Habitaciones"),
         yaxis = list(title = "Precio (millones de COP)"))

Se presenta una mayor concentración en apartamentos con 2 a 3 habitaciones.

Precio vs Zona

# Gráfico de cajas Precio vs Zona
plot_ly(data = bd_fin, 
        x = ~zona, 
        y = ~preciom, 
        type = 'box',
        marker = list(color = 'rgba(255, 215, 0, 0.6)')) %>%
  layout(title = "Precio vs Zona",
         xaxis = list(title = "Zona"),
         yaxis = list(title = "Precio (millones de COP)"))

Se evidencia que en la zona oeste se encuentran los apartamentos con los precios más altos, mientras que en el oriente se concentran los de precios más bajos.

Punto 3: Regresión Lineal Múltiple

Se realiza la estimación de un modelo de regresión lineal múltiple con las variables del punto anterior (precio = f(área construida, estrato, número de cuartos, número de parqueaderos, número de baños ) ).

modelo <- lm(preciom ~ areaconst + estrato + habitaciones + 
               parqueaderos + banios, data = bd_fin)
summary(modelo)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = bd_fin)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1619.72   -47.00     0.33    41.79   994.91 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -66.22217    9.61351  -6.888 6.32e-12 ***
## areaconst      1.94971    0.04133  47.174  < 2e-16 ***
## estrato4      23.70568    6.24480   3.796 0.000149 ***
## estrato5      46.25870    6.32620   7.312 3.04e-13 ***
## estrato6     182.90504    7.98194  22.915  < 2e-16 ***
## habitaciones -32.82124    3.14638 -10.431  < 2e-16 ***
## parqueaderos  83.55719    3.63753  22.971  < 2e-16 ***
## banios        45.48708    2.89468  15.714  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 127.1 on 5092 degrees of freedom
## Multiple R-squared:  0.8072, Adjusted R-squared:  0.807 
## F-statistic:  3046 on 7 and 5092 DF,  p-value: < 2.2e-16

\[ \text{Precio} = \beta_0 + \beta_1 \cdot \text{areaconst} + \beta_2 \cdot \text{estrato4} + \beta_3 \cdot \text{estrato5} + \beta_4 \cdot \text{estrato6} + \beta_5 \cdot \text{habitaciones} + \beta_6 \cdot \text{parqueaderos} + \beta_7 \cdot \text{banios} \]

  • Intercepto:El valor estimado del intercepto es -66.22, lo que significa que cuando todas las variables predictoras tienen un valor de 0, el precio de la vivienda sería -66.22 millones de COP. Este valor negativo del intercepto sugiere que el modelo puede no ser apropiado para extrapolar a valores extremos o fuera del rango de las variables observadas en los datos.

  • Área construida (`areaconst): El coeficiente es 1.95, lo que significa que, en promedio, por cada metro cuadrado adicional de área construida, el precio de la vivienda aumenta en 1.95 millones de COP, manteniendo constantes las demás variables. Este coeficiente es altamente significativo (p-valor < 2e-16).

  • Estrato:

    • Estrato 4: Las viviendas en estrato 4 tienen un precio promedio 23.71 millones de COP más alto que las de estrato 3 (el nivel base).
    • Estrato 5: Las viviendas en estrato 5 tienen un precio promedio 46.26 millones de COP más alto que las de estrato 3.
    • Estrato 6: Las viviendas en estrato 6 tienen un precio promedio 182.91 millones de COP más alto que las de estrato 3. Todos los estratos son estadísticamente significativos con p-valores muy bajos.
  • Número de habitaciones (habitaciones): El coeficiente es negativo (-32.82), lo que sugiere que, manteniendo constantes las demás variables, un aumento en el número de habitaciones se asocia con una disminución en el precio de la vivienda en 32.82 millones de COP.

  • Número de parqueaderos (parqueaderos): Un parqueadero adicional se asocia con un aumento de 83.56 millones de COP en el precio, manteniendo todo lo demás constante.

  • Número de baños (banios): Cada baño adicional se asocia con un aumento de 45.49 millones de COP en el precio, lo que también es un efecto importante y significativo.

Significancia estadística:

Todos los coeficientes tienen p-valores muy bajos, lo que indica que son altamente significativos y que existe suficiente evidencia para concluir que estas variables tienen un impacto real sobre el precio de las viviendas.

\(R^2 = 0.8072\):

Esto significa que el 80.72% de la variabilidad en los precios de las viviendas puede explicarse por las variables incluidas en el modelo (área construida, estrato, número de habitaciones, parqueaderos y baños).

\(R^2\) ajustado = 0.807:

El valor ajustado es casi el mismo, lo que sugiere que el número de variables en el modelo no está influyendo de manera negativa en el ajuste del modelo.

Posibles mejoras del modelo:

  • Probar con transformaciones de variables, como el logaritmo del precio, para ver si el ajuste mejora.
  • Podría ser útil incluir interacciones entre variables como el área construida y el número de habitaciones o estrato, para ver si las relaciones entre estas variables afectan de manera conjunta al precio.

Punto 4: Validación de supuestos

Para validar los supuestos del modelo de regresión lineal multiple, se evalúan los aspectos como linealidad, homocedasticidad, normalidad de los residuos, independencia de los errores y multicolinealidad.

par(mfrow = c(2, 2))
plot(modelo, which = 1) #Linealidad y Homocedasticidad
plot(modelo, which = 2) #Normalidad de los residuos
hist(residuals(modelo),main = "Histograma de los residuos", xlab = "Residuos", breaks = 30) #Histograma de residuos
plot(modelo, which = 5) #Detección de puntos influyentes

ad.test(residuals(modelo)) #Anderson-Darling Normalidad
## 
##  Anderson-Darling normality test
## 
## data:  residuals(modelo)
## A = 211.74, p-value < 2.2e-16

El resultado sugiere que los residuos del modelo no se distribuyen normalmente, lo que puede afectar la validez de los intervalos de confianza y las pruebas de hipótesis que dependen del supuesto de normalidad de los errores.

Se puede intentar transformar la variable dependiente o las variables independientes para mejorar la normalidad de los residuos. Usando por ejemplo, transformaciones logarítmicas, cuadráticas o de Box-Cox.

vif(modelo) #Test de Multicolinealidad con VIF
##                  GVIF Df GVIF^(1/(2*Df))
## areaconst    2.595056  1        1.610918
## estrato      2.031266  3        1.125368
## habitaciones 1.428472  1        1.195187
## parqueaderos 2.101723  1        1.449732
## banios       3.021915  1        1.738366

Los valores de VIF indican que no hay multicolinealidad grave en las variables del modelo. Aunque el valor de VIF más alto es para el número de baños (1.74), sigue siendo bastante bajo. Esto implica que los coeficientes estimados del modelo son estables, y no hay una necesidad urgente de eliminar o combinar variables.

# R cuadrado y estadísticas relacionadas
rsq <- summary(modelo)$r.squared
adj_rsq <- summary(modelo)$adj.r.squared
mse <- summary(modelo)$sigma
estadisticas <- data.frame(
  "R2" = rsq,
  "R2_ajustado" = adj_rsq,
  "Error estándar de la estimación" = mse)
kable(estadisticas, format = "markdown")
R2 R2_ajustado Error.estándar.de.la.estimación
0.8072373 0.8069723 127.0683

El modelo tiene un buen ajuste con un \(R^2\) y un \(R^2\) ajustado cercanos a 0.81, lo que sugiere que el modelo explica bien los datos. Sin embargo, el Error estándar es de 127.07 lo que significa que, en promedio, las predicciones del modelo difieren de los valores reales en aproximadamente 127 unidades, puede ser alto dependiendo de la escala de los precios de las casas.

Punto 5: Entrenamiento y prueba

Se realiza una partición en los datos de forma aleatoria donde el 70% es un set para entrenar el modelo y 30% para prueba. Se estima el modelo con la muestra del 70%.

set.seed(123)

# Partición 70% para entrenamiento y 30% para prueba
particion <- createDataPartition(bd_fin$preciom, p = 0.70,list = FALSE,times = 1)
bdtrain <- bd_fin[particion, ]
bdtest <- bd_fin[-particion, ]

modelo_ajus <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
                    banios, data = bdtrain)
summary(modelo_ajus)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = bdtrain)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1582.76   -46.11     0.13    39.56  1005.01 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -62.77851   11.43968  -5.488 4.35e-08 ***
## areaconst      1.91049    0.04911  38.900  < 2e-16 ***
## estrato4      26.59849    7.46417   3.563 0.000371 ***
## estrato5      50.04263    7.57955   6.602 4.65e-11 ***
## estrato6     196.43085    9.55759  20.552  < 2e-16 ***
## habitaciones -30.15853    3.72182  -8.103 7.29e-16 ***
## parqueaderos  82.70181    4.42541  18.688  < 2e-16 ***
## banios        40.56418    3.46532  11.706  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 127.4 on 3564 degrees of freedom
## Multiple R-squared:  0.7996, Adjusted R-squared:  0.7992 
## F-statistic:  2032 on 7 and 3564 DF,  p-value: < 2.2e-16

Punto 6: Predicciones

Usando los datos de prueba (30%) se realizan predicciones con el modelo anterior

prediccion = predict(modelo_ajus, newdata = bdtest)

comparacion = data.frame(Real = bdtest$preciom, Predicción = prediccion)
head(comparacion)

Punto 7: Errores y métricas

Se hace el calculo el error cuadrático medio, el error absoluto medio y el \(R^2\)

metricas <- postResample(pred = prediccion, obs = bdtest$preciom)
metricas
##        RMSE    Rsquared         MAE 
## 126.6924954   0.8232781  76.2202024

RMSE (Root Mean Square Error): Un valor de 126.69 indica que, en promedio, el modelo se equivoca por aproximadamente 126.69 unidades en el precio predicho.

\(R^2\) (Coeficiente de Determinación): Un valor de 0.82 significa que el modelo explica el 82% de la variabilidad en el precio de las casas. Esto sugiere un buen ajuste del modelo a los datos.

MAE (Mean Absolute Error): 76.22 Un MAE de 76.22 significa que, en promedio, las predicciones del modelo se desvían en 76.22 unidades del valor real. Es una métrica más robusta frente a los errores atípicos en comparación con el RMSE.

Estos resultados indican que el modelo tiene un rendimiento relativamente bueno en términos de precisión y capacidad de explicación.

Conclusiones

Ajuste del Modelo: El modelo tiene un buen ajuste general, con un \(R^2\) alto y un \(R^2\) ajustado similar, sugiriendo que el modelo captura bien la variabilidad en los datos.

Precisión de las Predicciones: Los errores de predicción (MSE y MAE) proporcionarán información adicional sobre la precisión del modelo. Los valores específicos de MSE y MAE ayudarán a evaluar la precisión de las predicciones.

Problemas Potenciales: La falta de normalidad en los residuos y la autocorrelación positiva sugieren que el modelo puede no estar cumpliendo todos los supuestos ideales. Esto podría requerir ajustes en el modelo, como transformaciones de variables, inclusión de variables adicionales o el uso de modelos alternativos que no asuman normalidad de residuos.