Brayan Hernandez Cardona

knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE, collapse = FALSE, comment = "#>", render = TRUE)
#install.packages("devtools") # solo la primera vez
#devtools::install_github("centromagis/paqueteMODELOS", force =TRUE)
#install.packages("plotly")
#install.packages("dplyr")
#install.packages("corrplot")
#install.packages("leaflet")
#install.packages("ggplot2")
#install.packages("lmtest")
#install.packages("Metrics")
library(htmlwidgets)
library(dplyr)
library(ggplot2)
library(plotly)
library(lmtest)
library(corrplot)
library(Metrics)
library(leaflet)
library(paqueteMODELOS)
#Funciones necesarias para la actividad

histogram <- function(df, variable) {
  grafico <- plot_ly(x = df[[variable]], type = "histogram") %>%
    layout(
    xaxis = list(title = variable),
    yaxis = list(title = "Frecuencia")
  )
  return(grafico)
}


histograms <- function(df, variables) {
  graficos <- list()
  for(var in variables) {
    graficos[[var]] <- histogram(df, var)
  }
  
  return(graficos)
}




dispersion <- function(df, variable1, variable2) {
  plot_ly(x = ~df[[variable1]], y = ~df[[variable2]], type = 'scatter', mode = 'markers') %>%
    layout(
    xaxis = list(title = variable1),
    yaxis = list(title = variable2)
  )
}

Exploración de los datos:

En esta sección se explorará nuestro dataset para conocer su estructura, funcionamiento y comportamiento.

data("vivienda")

datos <- vivienda

head(datos)
#> # A tibble: 6 × 13
#>      id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
#>   <dbl> <chr>   <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
#> 1  1147 Zona O… <NA>        3     250        70            1      3            6
#> 2  1169 Zona O… <NA>        3     320       120            1      2            3
#> 3  1350 Zona O… <NA>        3     350       220            2      2            4
#> 4  5992 Zona S… 02          4     400       280            3      5            3
#> 5  1212 Zona N… 01          5     260        90            1      2            3
#> 6  1724 Zona N… 01          5     240        87            1      3            3
#> # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>

Podemos identificar que nuestro dataset completo se compone de 8322 filas y 14 columnas, lo que podemos considerar como una base de datos considerable.

#Revisamos la estructura del dataset
cat("Numero de filas:", nrow(datos), "\n")
#> Numero de filas: 8322
cat("Numero de columnas:", ncol(datos), "\n\n\n\n")
#> Numero de columnas: 13
#Tipos de datos de las variables
str(datos)
#> spc_tbl_ [8,322 × 13] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
#>  $ id          : num [1:8322] 1147 1169 1350 5992 1212 ...
#>  $ zona        : chr [1:8322] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
#>  $ piso        : chr [1:8322] NA NA NA "02" ...
#>  $ estrato     : num [1:8322] 3 3 3 4 5 5 4 5 5 5 ...
#>  $ preciom     : num [1:8322] 250 320 350 400 260 240 220 310 320 780 ...
#>  $ areaconst   : num [1:8322] 70 120 220 280 90 87 52 137 150 380 ...
#>  $ parqueaderos: num [1:8322] 1 1 2 3 1 1 2 2 2 2 ...
#>  $ banios      : num [1:8322] 3 2 2 5 2 3 2 3 4 3 ...
#>  $ habitaciones: num [1:8322] 6 3 4 3 3 3 3 4 6 3 ...
#>  $ tipo        : chr [1:8322] "Casa" "Casa" "Casa" "Casa" ...
#>  $ barrio      : chr [1:8322] "20 de julio" "20 de julio" "20 de julio" "3 de julio" ...
#>  $ longitud    : num [1:8322] -76.5 -76.5 -76.5 -76.5 -76.5 ...
#>  $ latitud     : num [1:8322] 3.43 3.43 3.44 3.44 3.46 ...
#>  - attr(*, "spec")=List of 3
#>   ..$ cols   :List of 13
#>   .. ..$ id          : list()
#>   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
#>   .. ..$ zona        : list()
#>   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
#>   .. ..$ piso        : list()
#>   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
#>   .. ..$ estrato     : list()
#>   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
#>   .. ..$ preciom     : list()
#>   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
#>   .. ..$ areaconst   : list()
#>   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
#>   .. ..$ parqueaderos: list()
#>   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
#>   .. ..$ banios      : list()
#>   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
#>   .. ..$ habitaciones: list()
#>   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
#>   .. ..$ tipo        : list()
#>   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
#>   .. ..$ barrio      : list()
#>   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
#>   .. ..$ longitud    : list()
#>   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
#>   .. ..$ latitud     : list()
#>   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
#>   ..$ default: list()
#>   .. ..- attr(*, "class")= chr [1:2] "collector_guess" "collector"
#>   ..$ delim  : chr ";"
#>   ..- attr(*, "class")= chr "col_spec"
#>  - attr(*, "problems")=<externalptr>

Para motivos del analisis filtraremos solo las viviendas de tipo “Casa” en la “Zona Norte” para enfocar nuestro analisis en esta selección.

# Filtrar los datos para la Zona Norte y tipo Casa
base1 <- datos %>%
  filter(zona == "Zona Norte", tipo == "Casa")

# Mostrar las primeras 3 filas
head(base1, 3)
#> # A tibble: 3 × 13
#>      id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
#>   <dbl> <chr>   <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
#> 1  1209 Zona N… 02          5     320       150            2      4            6
#> 2  1592 Zona N… 02          5     780       380            2      3            3
#> 3  4057 Zona N… 02          6     750       445           NA      7            6
#> # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>

Con este filtro nuestro dataframe se reduce a 722 filas, lo cual disminuye en gran cantidad la cantidad de registros disponibles pero nos permite realizar un analisis más certero en estos tipos de viviendas.

#Verificamos el filtro aplicado
table(base1$zona)
#> 
#> Zona Norte 
#>        722
table(base1$tipo)
#> 
#> Casa 
#>  722

A continuación se presentan las diferentes medidas de centralidad de nuestros datos.

# Resumen estadístico de las variables relevantes
summary(base1[, c("preciom", "areaconst", "estrato", "banios", "habitaciones", "parqueaderos")])
#>     preciom         areaconst         estrato          banios      
#>  Min.   :  89.0   Min.   :  30.0   Min.   :3.000   Min.   : 0.000  
#>  1st Qu.: 261.2   1st Qu.: 140.0   1st Qu.:3.000   1st Qu.: 2.000  
#>  Median : 390.0   Median : 240.0   Median :4.000   Median : 3.000  
#>  Mean   : 445.9   Mean   : 264.9   Mean   :4.202   Mean   : 3.555  
#>  3rd Qu.: 550.0   3rd Qu.: 336.8   3rd Qu.:5.000   3rd Qu.: 4.000  
#>  Max.   :1940.0   Max.   :1440.0   Max.   :6.000   Max.   :10.000  
#>                                                                    
#>   habitaciones     parqueaderos   
#>  Min.   : 0.000   Min.   : 1.000  
#>  1st Qu.: 3.000   1st Qu.: 1.000  
#>  Median : 4.000   Median : 2.000  
#>  Mean   : 4.507   Mean   : 2.182  
#>  3rd Qu.: 5.000   3rd Qu.: 3.000  
#>  Max.   :10.000   Max.   :10.000  
#>                   NA's   :287

En nuestro modelo emplearemos las variables de precio, area, baños, habitaciones y estrato para predecir o estimar el valor de una vivienda, por lo que resulta de amplia importancia entender el comportamiento de dichas variables y su relación entre ellas.

La siguiente grafica muestra el coeficiente de correlación para cada par de variables, podemos ver que el precio y el area se encuentran altamente correlacionados con un indice de 0,73. Lo que indica una amplia correlación entre ambas variables y que el area es significativa para la estimación del precio.

Otra correlación importantes se encuentra entre el precio y el estrato, con un indice de 0,61. Es posible intuir que el precio está relacionado al estrato de la vivienda y también será un factor fundamental para la estimación del precio.

Por otro lado, la variable menos correlacionado con el precio es numero de habitaciones, con un indice de 0.32 indica una baja correlación, podemos inferir que las habitaciones si bien influyen en el precio, no es un factor tan determinante.

#Analisis de Correlaciones
# Calcular la matriz de correlación
matriz_corr <- cor(base1[, c("preciom", "areaconst", "banios", "habitaciones", "estrato")])

# Crear el mapa de correlaciones
corrplot(matriz_corr, method = "color", tl.srt = 45, type = "upper", addCoef.col = TRUE)

Realizaremos un analisis univariable mediante histogramas para comprender un poco más cada variable:

En el caso del precio, podemos ver que la grafica se encuentra sobrecargada a la izquierda, indicando que la mayoria de viviendas está generalmente ubicada en la sección de precios bajos.

El area también muestra el mismo comportamiento que el precio, sobrecargandose a la izquierda.

graficos <- histograms(base1, c("preciom", "areaconst", "estrato", "banios", "habitaciones"))

graficos[["preciom"]]
graficos[["areaconst"]]
graficos[["estrato"]]
graficos[["banios"]]
graficos[["habitaciones"]]

Una vez conocemos un poco más sobre cada variable podemos empezar a visualizar el comportamiento entre ellas, para esto nos valdremos de los graficos de dispersión para observar la relación entre las variables.

Para el precio y el area, podemos visualizar una clara tendencia positiva, a medida que una vivienda tiene un mayor area de contrucción suele tener mayores precios, mostrando una fuerte correlación positiva entre ambas variables.

Cuando miramos la variable precio contra las demás variables seleccionadas, podemos ver que a medida que una vivienda tiene mayor estrato, mayor numero de baños o habitaciones, usualmente el precio también tiende a aumentar.

dispersion(base1, "areaconst", "preciom")
dispersion(base1, "estrato", "preciom")
dispersion(base1, "banios", "preciom")
dispersion(base1, "habitaciones", "preciom")
# Visualizar la ubicación de las casas en un mapa

leaflet(base1) %>%
  addTiles() %>%
  addMarkers(~longitud, ~latitud, popup = ~paste("Precio:", preciom, "<br>",
                                                 "Área:", areaconst, "<br>",
                                                 "Estrato:", estrato, "<br>",
                                                 "Baños:", banios, "<br>",
                                                 "Habitaciones:", habitaciones))

Ahora resulta importante revisar si nuestro dataframe contiene datos faltantes que puedan afectar nuestros modelos.

Puede verse que en todo el dataframe filtrado tenemos un total de 287 datos faltantes y todos en la variable parqueaderos.

#Revisión de datos faltantes
cat("Cantidad de datos faltantes:", sum(is.na(base1)), "\n")
#> Cantidad de datos faltantes: 659
#Datos Faltantes por columna
data.frame(colSums(is.na(base1)))
#>              colSums.is.na.base1..
#> id                               0
#> zona                             0
#> piso                           372
#> estrato                          0
#> preciom                          0
#> areaconst                        0
#> parqueaderos                   287
#> banios                           0
#> habitaciones                     0
#> tipo                             0
#> barrio                           0
#> longitud                         0
#> latitud                          0

Preparación de los Datos:

En esta sección nos enfocaremos en preparar nuestro dataset para que pueda ser empleado en el modelo de regresión lineal. Para esto nos enfocaremos en corregir errores o parametros que puedan afectar el rendimiento del modelo.

En este caso debemos imputar los datos faltantes, dado que todo se da en una variable ordinal como parqueadores (que contiene numeros enteros) emplearemos la mediana para rellenar los datos faltantes.

#Reemplazamos los datos faltantes por la mediana en la variable parqueaderos
base1$parqueaderos[is.na(base1$parqueaderos)] <- median(base1$parqueaderos, na.rm = TRUE)

#Volvemos a revisar la existencia de datos faltantes
sum(is.na(base1))
#> [1] 372

Creación del modelo:

En esta sección llevaremos a cabo la creación del modelo de regresión lineal multiple para predecir los precios de las viviendas.

Emplearemos función summary() para obtener información detallada sobre el modelo estimado:

#Modelo de regresión multiple
modelo <- lm(preciom ~ areaconst + estrato + habitaciones + banios, data = base1)
summary(modelo)
#> 
#> Call:
#> lm(formula = preciom ~ areaconst + estrato + habitaciones + banios, 
#>     data = base1)
#> 
#> Residuals:
#>     Min      1Q  Median      3Q     Max 
#> -961.17  -79.37  -17.71   49.14 1072.88 
#> 
#> Coefficients:
#>                Estimate Std. Error t value Pr(>|t|)    
#> (Intercept)  -234.04276   29.69581  -7.881  1.2e-14 ***
#> areaconst       0.82516    0.04346  18.986  < 2e-16 ***
#> estrato        85.77820    7.20303  11.909  < 2e-16 ***
#> habitaciones    1.23258    4.12582   0.299    0.765    
#> banios         26.82962    5.32670   5.037  6.0e-07 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Residual standard error: 159 on 717 degrees of freedom
#> Multiple R-squared:  0.6507, Adjusted R-squared:  0.6488 
#> F-statistic:   334 on 4 and 717 DF,  p-value: < 2.2e-16

Interpretación de los resultados

Residuals: Estos son los errores del modelo, es decir, la diferencia entre los precios reales y los precios predichos por el modelo. Idealmente, estos residuos deberían estar distribuidos de forma aleatoria y centrados en cero.

Coefficients:

(Intercept): Este sería el valor del precio cuando todas las demás variables son cero. En este caso, no tiene una interpretación práctica directa, ya que no tendría sentido una vivienda con área cero ó cero habitaciones.

areaconst: El coeficiente es 0.82516 y es altamente significativo. Esto nos indica que, por cada unidad adicional de área construida, el precio aumenta en 0.82516 millones, manteniendo todo lo demás constante.

estrato: El coeficiente es 85.77820 y también es altamente significativo. Esto indica que, por cada aumento de un estrato, el precio aumenta en 85.77820 millones, manteniendo todo lo demás constante.

habitaciones: El coeficiente es 1.23258 , pero no es estadísticamente significativo (p-valor = 0.765). Esto sugiere que, en este modelo, el número de habitaciones no tiene un efecto significativo sobre el precio.

banios: El coeficiente es 26.82962 y es altamente significativo. Cada baño adicional aumenta el precio en 26.82962 millones.

Residual standard error: Es una medida de la dispersión de los residuos. En este caso, es de 159, lo que indica que, en promedio, los precios predichos por el modelo se desvían de los precios reales en aproximadamente 159 millones.

Multiple R-squared & Adjusted R-squared: El R² es 0.6507, lo que significa que el modelo explica el 65.48% de la variabilidad en los precios. El R² ajustado es similar (0.6488).

F-statistic & p-value: Estos indican la significancia global del modelo. En este caso, el p-valor es muy pequeño lo que significa que el modelo en su conjunto es estadísticamente significativo.

Conclusión de los resultados: El modelo presenta buenos indicadores en esta primer vista a los resultados.

# 1. Linealidad
plot(modelo, 1) # Gráfico de residuos vs valores ajustados

# Prueba de Ramsey RESET
resettest(modelo)
#> 
#>  RESET test
#> 
#> data:  modelo
#> RESET = 25.856, df1 = 2, df2 = 715, p-value = 1.44e-11
# 2. Homocedasticidad
plot(modelo, 3) # Gráfico de escala-ubicación (raíz cuadrada de residuos estandarizados vs valores ajustados)

# Prueba de Breusch-Pagan
bptest(modelo)
#> 
#>  studentized Breusch-Pagan test
#> 
#> data:  modelo
#> BP = 134.62, df = 4, p-value < 2.2e-16
# 3. Independencia de los errores
plot(modelo, 2) # Gráfico Q-Q de los residuos

# Prueba de Durbin-Watson
dwtest(modelo)
#> 
#>  Durbin-Watson test
#> 
#> data:  modelo
#> DW = 1.6297, p-value = 2.592e-07
#> alternative hypothesis: true autocorrelation is greater than 0
# 4. Normalidad de los errores
shapiro.test(modelo$residuals) # Test de Shapiro-Wilk para normalidad
#> 
#>  Shapiro-Wilk normality test
#> 
#> data:  modelo$residuals
#> W = 0.83721, p-value < 2.2e-16

Interpretación de los supuestos

Shapiro-Wilk: El p-valor es muy pequeño (< 2.2e-16), lo que indica una clara violación del supuesto de normalidad de los errores.

Durbin-Watson: El estadístico DW es 1.6297 y el p-valor es significativo, lo que sugiere la presencia de autocorrelación positiva en los errores. Esto significa que los errores en un período están correlacionados con los errores en períodos anteriores.

Breusch-Pagan: El p-valor es muy pequeño, lo que indica una clara violación del supuesto de homocedasticidad. La varianza de los errores no es constante.

RESET: El p-valor es muy pequeño, lo que sugiere la presencia de no linealidad en el modelo. La relación entre las variables predictoras y la variable respuesta no es completamente lineal.

Nuestro modelo parece viola los supuestos que debe cumplirse para considerar sus resultados como estadisticamente significativos.

Para abordar esta problematica podemos pensar en las siguientes soluciones:

Posibles soluciones

No normalidad de los errores:

Transformaciones de la variable respuesta: transformar la variable preciom utilizando funciones como el logaritmo o la raíz cuadrada para ver si esto mejora la normalidad de los errores.

Heterocedasticidad:

Transformaciones de la variable respuesta: Al igual que en el caso de la no normalidad, sería buena practica transformar la variable preciom.

No linealidad:

Transformaciones de las variables predictoras: Se podría transformar algunas de las variables predictoras, como el área construida, utilizando funciones logarítmicas, raíces cuadradas o polinomios.

# Se seleccionan 196 índices aleatorios que formarán el training set.
set.seed(1)
train <- sample(x = 1:nrow(base1), nrow(base1)*0.7)


modelo_train <- lm(preciom ~ areaconst + estrato + habitaciones + banios, data = base1, subset = train)
summary(modelo_train)
#> 
#> Call:
#> lm(formula = preciom ~ areaconst + estrato + habitaciones + banios, 
#>     data = base1, subset = train)
#> 
#> Residuals:
#>     Min      1Q  Median      3Q     Max 
#> -373.46  -77.22  -19.75   49.84  990.65 
#> 
#> Coefficients:
#>                Estimate Std. Error t value Pr(>|t|)    
#> (Intercept)  -246.03145   35.43394  -6.943 1.19e-11 ***
#> areaconst       0.99073    0.05495  18.030  < 2e-16 ***
#> estrato        79.03630    8.51927   9.277  < 2e-16 ***
#> habitaciones    8.15777    5.01897   1.625  0.10471    
#> banios         18.15093    6.42111   2.827  0.00489 ** 
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Residual standard error: 154.4 on 500 degrees of freedom
#> Multiple R-squared:  0.682,  Adjusted R-squared:  0.6795 
#> F-statistic: 268.1 on 4 and 500 DF,  p-value: < 2.2e-16
cat("\n\n\n")
predicciones <- predict(object = modelo_train, newdata = base1[-train, ])


# Calcular las métricas
mse <- mse(base1$preciom, predicciones)
mae <- mae(base1$preciom, predicciones)
r_cuadrado <- summary(modelo)$r.squared

# Mostrar los resultados
cat("Error Cuadrático Medio (MSE):", mse, "\n")
#> Error Cuadrático Medio (MSE): 142865.6
cat("Error Absoluto Medio (MAE):", mae, "\n")
#> Error Absoluto Medio (MAE): 283.4937
cat("R-cuadrado:", r_cuadrado, "\n")
#> R-cuadrado: 0.6507222

Conclusión de los resultados

  1. Error Cuadrático Medio (MSE): 125245.6

En este caso significa que, en promedio, el cuadrado de la diferencia entre el precio real de una vivienda y el precio predicho por el modelo es de 125245.6 millones al cuadrado. Para tener una idea más intuitiva, calculando la raíz cuadrada del MSE (RMSE), que sería √125245.6 ≈ 353.9 millones. Esto indica que, en promedio, las predicciones del modelo se desvían del precio real en aproximadamente 353.9 millones.

  1. Error Absoluto Medio (MAE): 274.04

Indica que, en promedio, la diferencia entre el precio real de una vivienda y el precio predicho por el modelo es de 274.04 millones de pesos colombianos.

  1. R-cuadrado: 0.6507222

Significa que aproximadamente el 65.07% de la variación en los precios de las viviendas en tu conjunto de datos es explicada por las variables incluidas en tu modelo (área construida, estrato, número de habitaciones y baños). El 34.93% restante de la variación se debe a otros factores que no se consideraron en el modelo, como la ubicación o simplemente a la aleatoriedad inherente en los precios de las viviendas causa esta disparidad en los resultados.

Recomendaciones para mejorar el rendimiento:

Se debe considerar incluir variables adicionales que podrían ser relevantes para explicar el precio de las viviendas para este caso, como la ubicación (ciudad, barrio) entre otros.

Se considera importante explorar si existen relaciones no lineales entre las variables predictoras y el precio, y en caso afirmativo, aplicar transformaciones a las variables.