options(warn = -1)
suppressMessages(suppressWarnings(library(cluster)))
suppressMessages(suppressWarnings(library(knitr)))
suppressMessages(suppressWarnings(library(rmarkdown)))
suppressMessages(suppressWarnings(library(paqueteMETODOS)))
suppressMessages(suppressWarnings(library(naniar)))
suppressMessages(suppressWarnings(library(mice)))
suppressMessages(suppressWarnings(library(ggmice)))
suppressMessages(suppressWarnings(library(pastecs)))
suppressMessages(suppressWarnings(library(stringr)))
suppressMessages(suppressWarnings(library(stringi)))
suppressMessages(suppressWarnings(library(dplyr)))
suppressMessages(suppressWarnings(library(tidyverse)))
suppressMessages(suppressWarnings(library(kableExtra)))
suppressMessages(suppressWarnings(library(reticulate)))
suppressMessages(suppressWarnings(library(lsr)))
suppressMessages(suppressWarnings(library(tibble)))
suppressMessages(suppressWarnings(library(patchwork)))
suppressMessages(suppressWarnings(library(sf)))
suppressMessages(suppressWarnings(library(osmdata)))
suppressMessages(suppressWarnings(library(mapview)))
suppressMessages(suppressWarnings(library(leaflet)))
suppressMessages(suppressWarnings(library(heatmaply)))
suppressMessages(suppressWarnings(library(ggplot2)))
suppressMessages(suppressWarnings(library(stats)))
suppressMessages(suppressWarnings(library(e1071)))
suppressMessages(suppressWarnings(library(grid)))
suppressMessages(suppressWarnings(library(gridExtra)))
suppressMessages(suppressWarnings(library(moments)))
suppressMessages(suppressWarnings(library(diptest)))
suppressMessages(suppressWarnings(library(LaplacesDemon)))
suppressMessages(suppressWarnings(library(ggExtra)))
suppressMessages(suppressWarnings(library(RColorBrewer)))
suppressMessages(suppressWarnings(library(hexbin)))
suppressMessages(suppressWarnings(library(MASS)))
suppressMessages(suppressWarnings(library(ggfortify)))
suppressMessages(suppressWarnings(library(latex2exp)))
suppressMessages(suppressWarnings(library(lmtest)))
suppressMessages(suppressWarnings(library(interactions)))
suppressMessages(suppressWarnings(library(leafpop)))
suppressMessages(suppressWarnings(library(corrplot)))
suppressMessages(suppressWarnings(library(factoextra)))
suppressMessages(suppressWarnings(library(plotly)))
suppressMessages(suppressWarnings(library(FactoMineR)))
suppressMessages(suppressWarnings(library(gplots)))
suppressMessages(suppressWarnings(library(caret)))
suppressMessages(suppressWarnings(library(glmnet)))  
suppressMessages(suppressWarnings(library(Metrics)))
data(vivienda)

Actividad


Numeral 1

Realice un filtro a la base de datos e incluya sólo las ofertas de apartamentos. Presente los primeros 3 registros de las bases y algunas tablas que comprueben la consulta.


Limpieza de datos

# Función para limpieza de datos
clean_vivienda_data <- function(df) {

  df$tipo <- tolower(df$tipo)
  df$barrio <- tolower(df$barrio)
  original_rows <- nrow(df)
  

  df$tipo <- gsub("apto", "apartamento", df$tipo)
  

  df$barrio <- iconv(df$barrio, "UTF-8", "ASCII//TRANSLIT")
  barrio_replacements <- c(
    'urbanizacion la flora' = 'la flora',
    'caney' = 'el caney',
    'cristales' = 'los cristales',
    'alf?crez real' = 'alferez real',
    'parcelaciones pance' = 'pance',
    'juanamb??' = 'juanambu',
    'santa monica residencial' = 'santa monica',
    'mel?cndez' = 'melendez',
    'el aguacatal' = 'aguacatal',
    'bajo aguacatal' = 'aguacatal',
    'miradol del aguacatal' = 'aguacatal',
    'sector aguacatal' = 'aguacatal',
    'arboleda campestre candelaria' = 'arboledas',
    'arboleda' = 'arboledas'
  )


  df$barrio <- sapply(df$barrio, function(b) {
    if (b %in% names(barrio_replacements)) {
      barrio_replacements[[b]]
    } else {
      b
    }
  })
  
  df <- df[df$latitud >= 3.35 & df$latitud <= 3.55 & 
           df$longitud >= -76.60 & df$longitud <= -76.45, ]
  
  df <- subset(df, banios != 0 & habitaciones != 0)
  df <- df[!duplicated(df$id), ]
  df <- df[, !(names(df) %in% "piso")]
  df <- df[!is.na(df$parqueaderos), ]
  #df$price_per_sqm <- df$preciom / df$areaconst
  filtered_rows <- nrow(df)
  difference <- original_rows - filtered_rows
  percentage_loss <- (difference / original_rows) * 100
  list(cleaned_data = df, information_loss = percentage_loss)
}


result <- clean_vivienda_data(vivienda)
df <- result$cleaned_data
loss_percentage <- result$information_loss
cat("El porcentaje de pérdida de información respecto a filas, al eliminar la columna piso y los valores NaN de parqueaderos es de:", round(loss_percentage,2), "% respecto al conjunto de datos inicial.\n")

El porcentaje de pérdida de información respecto a filas, al eliminar la columna piso y los valores NaN de parqueaderos es de: 23.84 % respecto al conjunto de datos inicial.


Filtrado para tipo == apartamento

# Filter rows where "tipo" is "apartamento"
filtered_df <- df %>%
  filter(tipo == "apartamento")

paged_table(head(filtered_df, 3), options = list(rows.print = 15)) %>%
  kable(caption = 'Tres primeras filas de dataset filtrado')
Tres primeras filas de dataset filtrado
id zona estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
1212 Zona Norte 5 260 90 1 2 3 apartamento acopi -76.51350 3.45891
1724 Zona Norte 5 240 87 1 3 3 apartamento acopi -76.51700 3.36971
2326 Zona Norte 4 220 52 2 2 3 apartamento acopi -76.51974 3.42627

Comprobar consulta

# Compute the counts for each category in the unfiltered dataframe
tipo_counts_unfiltered <- as.data.frame(table(df$tipo))
colnames(tipo_counts_unfiltered) <- c("Tipo", "Conteo Sin Filtrado")

# Compute the counts for each category in the filtered dataframe
tipo_counts_filtered <- as.data.frame(table(filtered_df$tipo))
colnames(tipo_counts_filtered) <- c("Tipo", "Conteo Filtrado")

# Merge the two dataframes by "Tipo" column
merged_counts <- merge(tipo_counts_unfiltered, tipo_counts_filtered, by = "Tipo", all = TRUE)

# Replace NA with 0 where there are missing values (i.e., a category present in one df but not the other)
merged_counts[is.na(merged_counts)] <- 0

# Render the merged counts as an HTML table with kable and paged_table
paged_table(merged_counts, options = list(rows.print = 15)) %>%
  kable(caption = 'Conteo de categorías en la variable "tipo" para dataframe filtrado y sin filtrar')
Conteo de categorías en la variable “tipo” para dataframe filtrado y sin filtrar
Tipo Conteo Sin Filtrado Conteo Filtrado
apartamento 4049 4049
casa 2289 0

Numeral 2

Realice un análisis exploratorio de datos enfocado en la correlación entre la variable respuesta (precio del inmueble) en función del área construida, estrato, numero de baños, numero de habitaciones y zona donde se ubica la vivienda. Use gráficos interactivos con el paquete plotly e interprete los resultados.


Análisis exploratorio usando plotly

A continuación se genera un gráfico interactivo donde el usuario podrá escoger qué variable graficar con respecto a preciom.

  • preciom vs areaconst: Mayores áreas construidas implican mayor precio. Existen algunos puntos atípicos para áreas construidas mayores de 500 m2 que muestran precios menores a los esperados por la tendencia del resto de datos.
  • preciom vs parqueaderos: Al igual que areconst una mayor cantidad de parqueaderos implica mayor precio. Sin embargo, esta tendencia no es tan marcada como el área construida dado que se muestran valores de 58 para 1 parqueadero, 78 para 2 parqueaderos, 200 para 3 parqueaderos y 240 para 4 parqueaderos. El rango de precios para cada número de parqueaderos aumenta gradualmente hasta alcanzar un número de 4 parqueaderos.
  • preciom vs banios: Esta variable es directamente proporcional a preciom. Se muestran intervalos que se mueven a precios más altos a medida que el número de baños aumenta hasta un número de 5 baños.
  • preciom vs habitaciones: Esta variable es directamente proporcional a preciom. En el caso de habitaciones, los precios mínimos de apartamentos con una sola habitación parecen superar a los precios mínimos de dos, tres y cuatro habitaciones por lo que pueden estar afectados por otras variables como zona y barrio.
  • preciom vs zona: Se obtienen los valores más altos para la zona oeste con una media de 580 millones. Seguida de la zona oeste, se tiene a la zona norte, zona sur, zona centro y finalmente zona oriente.
  • preciom vs estrato: Como es de esperarse los precios crecen con el estrato obteniendo medias de 128.5 para estrato 3, 210 para estrato 4, 300 para estrato 5 y 620 para estrato 6. Llama la atención que el rango intercuartílico para estrato 6 es mucho mayor que para el resto, por lo que este estrato muestra más variación para sus precios aunque esté en general asociado con precios más altos.
suppressMessages(suppressWarnings(
# Add scatter plot traces for numerical variables
p <- plot_ly(filtered_df, x = ~areaconst, y = ~preciom, name = "areaconst", type = 'scatter', mode = 'markers',
             marker = list(size = 10, color = ~preciom, colorscale = 'Viridis', showscale = TRUE), visible = TRUE)))

suppressMessages(suppressWarnings(
p <- add_trace(p, x = ~parqueaderos, y = ~preciom, name = "parqueaderos", type = 'scatter', mode = 'markers',
               marker = list(size = 10, color = ~preciom, colorscale = 'Viridis', showscale = TRUE), visible = FALSE)))

suppressMessages(suppressWarnings(
p <- add_trace(p, x = ~banios, y = ~preciom, name = "banios", type = 'scatter', mode = 'markers',
               marker = list(size = 10, color = ~preciom, colorscale = 'Viridis', showscale = TRUE), visible = FALSE)))

suppressMessages(suppressWarnings(
p <- add_trace(p, x = ~habitaciones, y = ~preciom, name = "habitaciones", type = 'scatter', mode = 'markers',
               marker = list(size = 10, color = ~preciom, colorscale = 'Viridis', showscale = TRUE), visible = FALSE)))

# Add box plot traces for categorical variables
p <- add_trace(p, x = ~zona, y = ~preciom, name = "zona", type = 'box', visible = FALSE)
p <- add_trace(p, x = ~estrato, y = ~preciom, name = "estrato", type = 'box', visible = FALSE)
p <- add_trace(p, x = ~barrio, y = ~preciom, name = "barrio", type = 'box', visible = FALSE)

# Create the dropdown buttons
dropdown_buttons <- list(
  list(method = "update",
       args = list(list(visible = c(TRUE, rep(FALSE, 6))),
                   list(title = "preciom vs areaconst", xaxis = list(title = "areaconst"))),
       label = "areaconst"),
  list(method = "update",
       args = list(list(visible = c(FALSE, TRUE, rep(FALSE, 5))),
                   list(title = "preciom vs parqueaderos", xaxis = list(title = "parqueaderos"))),
       label = "parqueaderos"),
  list(method = "update",
       args = list(list(visible = c(FALSE, FALSE, TRUE, rep(FALSE, 4))),
                   list(title = "preciom vs banios", xaxis = list(title = "banios"))),
       label = "banios"),
  list(method = "update",
       args = list(list(visible = c(FALSE, FALSE, FALSE, TRUE, rep(FALSE, 3))),
                   list(title = "preciom vs habitaciones", xaxis = list(title = "habitaciones"))),
       label = "habitaciones"),
  list(method = "update",
       args = list(list(visible = c(rep(FALSE, 4), TRUE, FALSE, FALSE)),
                   list(title = "preciom vs zona", xaxis = list(title = "zona"))),
       label = "zona"),
  list(method = "update",
       args = list(list(visible = c(rep(FALSE, 5), TRUE, FALSE)),
                   list(title = "preciom vs estrato", xaxis = list(title = "estrato"))),
       label = "estrato"),
  list(method = "update",
       args = list(list(visible = c(rep(FALSE, 6), TRUE)),
                   list(title = "preciom vs barrio", xaxis = list(title = "barrio"))),
       label = "barrio")
)

# Add dropdown menu to the plot
p <- layout(p,
            title = "preciom vs areaconst",
            xaxis = list(title = "areaconst"),
            yaxis = list(title = "preciom"),
            showlegend = FALSE,
            updatemenus = list(
              list(
                y = 0.8,
                buttons = dropdown_buttons
              )
            ))

# Render the plot
p
# Interactive map of prices
suppressMessages(suppressWarnings(
plot_ly(filtered_df, lat = ~latitud, lon = ~longitud, text = ~paste('Zona:', zona, '<br>Estrato:', estrato, '<br>Precio:', preciom),
        marker = list(color = ~preciom, colorscale = 'Viridis', showscale = TRUE),
        type = 'scattermapbox', mode="markers") %>%
  layout(mapbox = list(style = 'open-street-map',
                       zoom = 11, center = list(lat = median(filtered_df$latitud), lon = median(filtered_df$longitud))),
         title = 'Precio por ubicación')))

Numeral 3


Estimación GLM

Estime 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 ) ) e interprete los coeficientes si son estadísticamente significativos. Las interpretaciones deben están contextualizadas y discutir si los resultados son lógicos. Adicionalmente interprete el coeficiente R2 y discuta el ajuste del modelo e implicaciones (que podrían hacer para mejorarlo).

fit <- lm(preciom ~ areaconst+estrato+habitaciones+parqueaderos+banios, data=filtered_df)
conf_int <- confint(fit, level=0.95)
ci_areaconst <- conf_int["areaconst", ]


r_squared <- summary(fit)$r.squared
spearman <- cor(vivienda4$areaconst, vivienda4$preciom,  method = "spearman")
kendall <- cor(vivienda4$areaconst, vivienda4$preciom,  method = "kendall")

# Get the coefficients of the linear model
intercept <- round(coef(fit)[1],3)
slope <- round(coef(fit)[2],3)

# Get the summary of the model
summary_fit <- summary(fit)
coefficients_table <- summary_fit$coefficients
coefficients_df <- as.data.frame(coefficients_table)
coefficients_df$`Pr(>|t|)` <- formatC(coefficients_df$`Pr(>|t|)`, format="e", digits=2)
coefficients_df$Estimate <- round(coefficients_df$Estimate, 3)
coefficients_df$`Std. Error` <- round(coefficients_df$`Std. Error`, 3)
coefficients_df$`t value` <- round(coefficients_df$`t value`, 3)
paged_table(coefficients_df, options = list(rows.print = 20)) %>%
  kable(caption = 'Resumen coeficientes Modelo Base')
Resumen coeficientes Modelo Base
Estimate Std. Error t value Pr(>|t|)
(Intercept) -251.530 16.387 -15.350 9.98e-52
areaconst 2.040 0.051 40.363 1.32e-299
estrato 53.846 3.101 17.365 3.34e-65
habitaciones -51.894 4.009 -12.943 1.42e-37
parqueaderos 92.164 4.385 21.019 3.55e-93
banios 57.280 3.606 15.885 3.63e-55
summary_fit
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = filtered_df)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1727.94   -56.30     0.33    48.95  1005.40 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -251.53009   16.38673  -15.35   <2e-16 ***
## areaconst       2.03961    0.05053   40.36   <2e-16 ***
## estrato        53.84559    3.10080   17.36   <2e-16 ***
## habitaciones  -51.89396    4.00947  -12.94   <2e-16 ***
## parqueaderos   92.16385    4.38469   21.02   <2e-16 ***
## banios         57.28023    3.60589   15.88   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 136.5 on 4043 degrees of freedom
## Multiple R-squared:  0.7865, Adjusted R-squared:  0.7862 
## F-statistic:  2979 on 5 and 4043 DF,  p-value: < 2.2e-16

Interpretación

Coeficientes
  • Intercepto (-251.53): El modelo indica que cuando los predictores son 0, el valor de preciom sería negativo lo que no tiene sentido práctico.

areaconst: Por cada incremento de areaconst, se espera que preciom incremente en 2.04 unidades, manteniendo el resto de predictores constantes. El coeficiente positivo va de la mano con lo visto en la etapa exploratoria donde precios mayores están asociados con áreas más grandes.

  • estrato: Cada aumento de estrato se asocia con un aumento de 53.85 en preciom. Esto muestra la clara relación donde a mayor clasificación de estrato se tendrán mayor precios de inmuebles.

  • habitaciones: Cada habitación adicional se asocia con una disminución de 51.89 en preciom. Esta disminución es contraintuitiva por lo que debería investigarse más a fondo con el fin de determinar una transformación o evitar multicolinearidad.

  • parqueaderos: Cada parqueadero implica un incremento de 92.16 unidades, siguiendo una mayor valorización de la propiedad.

  • banios: Cada baño adicional incrementa el precio en 57.28 unidades, sugiriendo que una mayor cantidad de baños impactan positivamente el valor del inmueble.

  • Todos los coeficientes son estadísticamente significativos \(p<2e-16\).


Métricas
  • R-squared (0.7876): Este valor indica que aproximadamente el 78.76% de la variabilidad en preciom puede ser explicada por este modelo. Existe un 21.24% de variabilidad que aún podría ser capturada por el modelo.

  • RMSE (136.12): Este valor es la desviación estándar de los errores de predicción o residuales. Esto provee una medida de que tan bien las predicciones del modelo se ajustan a los datos. Un RSME = 136.12 indica un error de predicción moderado.


Mejora de modelos
  • Investigación de multicolinearidad: Dado que habitaciones tienen un coeficiente negativo, se debe revisar multicolinearidad sobre predictores. Esto podría estar distorsionando los estimados de los coeficientes del predictor.

  • Feature Engineering:

    • Términos de interacción: Explorar efectos de interacción entre variables. El impacto de areaconst puede diferir dependiendo de estrato y zona.
    • Predictores polinómicos: Si existen relaciones no lineales, se podrían introducir términos polinómicos.
  • Regularización:

    • Regresión Lasso/Ridge: Usar métodos de reUse regularized regression methods like Lasso (L1) or Ridge (L2) to penalize large coefficients, which can help mitigate overfitting and handle multicollinearity.

    • Cross-validation y Tuneado de hiperparámetros: Explorar resultados para validación cruzada asegurando que el modelo generaliza bien. Si se usa regularización, se puede ajustar la fuerza de regularización para L1 o L2.



Numeral 4


Validación de supuestos

  • Linealidad y Homoscedasticidad: Los gráficos de Residuals vs Fitted y Scale - Location muestran a primera vista no linealidad y heterosquedasticidad respectivamente.

  • Normalidad: El Normal Q-Q muestra que los puntos no siguen una distribución normal especialemente en la cola superior que puede deberse a la presencia de outliers o que los residuos tienen una distribución con colas más pesadas que la distribución normal.

  • Presencia de valores extremos y de alta influencia: Los gráficos Result vs Leverage, Cook’s distance y Cook’s dist vs leverage permiten determinar los outliers y los puntos de alta influencia en el modelo cuando al ser incluidos o excluidos, estos alteran los resultados de la regresión. Estos valores, usualmente están relacionados con valores altos de residuales. En el gráfico de Result vs Leverage, todos los puntos están dentro de las líneas de distancia de Cook por lo que los datos parecen no mostrar puntos de alta influencia pero si se presentan outliers con índices 1163, 2286, 3605 y 3769.

autoplot(fit, which = 1:6)

shapiro.test(fit$residuals)
## 
##  Shapiro-Wilk normality test
## 
## data:  fit$residuals
## W = 0.84303, p-value < 2.2e-16
normtest::jb.norm.test(fit$residuals)
## 
##  Jarque-Bera test for normality
## 
## data:  fit$residuals
## JB = 36684, p-value < 2.2e-16
nortest::ad.test(fit$residuals)
## 
##  Anderson-Darling normality test
## 
## data:  fit$residuals
## A = 140.4, p-value < 2.2e-16
nortest::lillie.test(fit$residuals)
## 
##  Lilliefors (Kolmogorov-Smirnov) normality test
## 
## data:  fit$residuals
## D = 0.14113, p-value < 2.2e-16
lmtest::bptest(fit)
## 
##  studentized Breusch-Pagan test
## 
## data:  fit
## BP = 1110, df = 5, p-value < 2.2e-16
lmtest::gqtest(fit)
## 
##  Goldfeld-Quandt test
## 
## data:  fit
## GQ = 1.596, df1 = 2019, df2 = 2018, p-value < 2.2e-16
## alternative hypothesis: variance increases from segment 1 to 2
lmtest::dwtest(fit)
## 
##  Durbin-Watson test
## 
## data:  fit
## DW = 1.6506, p-value < 2.2e-16
## alternative hypothesis: true autocorrelation is greater than 0

Numeral 5, 6 y 7


  • Realice una partición en los datos de forma aleatoria donde 70% sea un set para entrenar el modelo y 30% para prueba. Estime el modelo con la muestra del 70%. Muestre los resultados.
  • Realice predicciones con el modelo anterior usando los datos de prueba (30%).
  • Calcule el error cuadrático medio, el error absoluto medio y el R2, interprete.

Train 70%

# Train test split 70:30
set.seed(123)  # Set seed for reproducibility
train_index <- createDataPartition(filtered_df$preciom, p = 0.7, list = FALSE)
train_data <- filtered_df[train_index, ]
test_data <- filtered_df[-train_index, ]

# Formula
formula <- preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios

# Train dataset with cv
train_control <- trainControl(method = "cv", number = 10)  # 10-fold cross-validation

# Train glm
glm_model <- train(formula, data = train_data, 
                   method = "glm",
                   trControl = train_control)

Mostrar resultados Train 70%

# Extract glm from caret
final_model <- glm_model$finalModel

# Summary of the final model
summary(final_model)
## 
## Call:
## NULL
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -258.46834   19.61861  -13.18   <2e-16 ***
## areaconst       2.01491    0.06124   32.90   <2e-16 ***
## estrato        55.34732    3.71591   14.89   <2e-16 ***
## habitaciones  -50.85698    4.80735  -10.58   <2e-16 ***
## parqueaderos   92.53296    5.25692   17.60   <2e-16 ***
## banios         56.69071    4.29857   13.19   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for gaussian family taken to be 18848.89)
## 
##     Null deviance: 245480206  on 2836  degrees of freedom
## Residual deviance:  53361215  on 2831  degrees of freedom
## AIC: 35987
## 
## Number of Fisher Scoring iterations: 2

Predicciones sobre 30% (test_data)

# Predicciones sobre test_data
test_predictions <- predict(glm_model, newdata = test_data)

Evaluación de modelo

# Evaluate model performance on the test set
mae <- MAE(test_predictions, test_data$preciom)
rsq <- R2(test_predictions, test_data$preciom)
mse <- mse(test_predictions, test_data$preciom)
mape <- mape(test_data$preciom, test_predictions)

cat("R-squared $R^{2}$:", rsq,
    "<br>Mean Absolute Error (MAE):", mae,
    "<br>Mean Squared Error (MSE):", mse,
    "<br>Mean Absolute Percentage Error (MAPE):", mape,
    "\n")

R-squared \(R^{2}\): 0.7953798
Mean Absolute Error (MAE): 84.43975
Mean Squared Error (MSE): 18137.82
Mean Absolute Percentage Error (MAPE): 0.2386994


Interpretación de métricas

  • R-squared \(R^{2}\): El 79.54% de la varianza en preciom puede ser explicada por el modelo actual. La varianza restante podría ser explicada por las variables que no fueron tenidas en cuenta en este modelo (ej. zona).
  • MAE: En promedio, el modelo se desvía de los valores correctos en 84.44 unidades de preciom. Para determinar si el modelo se desempeña bien se debe tener el cuenta el rango de preciom. Desde las gráficas de exploración de datos podemos observar que los valores de preciom varían entre 50 y 1950 por lo que el valor de MAE obtenido representa una desviación de las predicciones de un 4.45% de todos los valores contemplados para preciom:

\[ MAE_{pr} = \frac{84.44}{(1950-50)}\times 100 = 4.45\% \] - Mean Squared Error (MSE): El valor de MSE es alto 18137.82 indicando que para ciertos valores el modelo puede cometer errores que se alejan bastante del valor esperado. Esto estaría relacionado también con la varianza no explicada mostrada por R-squared.

  • Mean Absolute Percentage Error (MAPE): Esta métrica indica que en promedio los valores predichos por el modelo se desvián de los valores reales en un 23.87% lo que es cercano a la varianza no explicada mostrada por R-squared (20.46%).