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)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.
# 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.
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')| 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 |
# 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')| Tipo | Conteo Sin Filtrado | Conteo Filtrado |
|---|---|---|
| apartamento | 4049 | 4049 |
| casa | 2289 | 0 |
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.
plotlyA 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')))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')| 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 |
##
## 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
-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\).
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.
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:
areaconst puede
diferir dependiendo de estrato y zona.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.
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.
##
## Shapiro-Wilk normality test
##
## data: fit$residuals
## W = 0.84303, p-value < 2.2e-16
##
## Jarque-Bera test for normality
##
## data: fit$residuals
## JB = 36684, p-value < 2.2e-16
##
## Anderson-Darling normality test
##
## data: fit$residuals
## A = 140.4, p-value < 2.2e-16
##
## Lilliefors (Kolmogorov-Smirnov) normality test
##
## data: fit$residuals
## D = 0.14113, p-value < 2.2e-16
##
## studentized Breusch-Pagan test
##
## data: fit
## BP = 1110, df = 5, p-value < 2.2e-16
##
## 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
##
## Durbin-Watson test
##
## data: fit
## DW = 1.6506, p-value < 2.2e-16
## alternative hypothesis: true autocorrelation is greater than 0
# 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)# 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
test_data)# 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
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).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.