Sys.setlocale("LC_ALL", "Spanish_Colombia.UTF-8")
## [1] "LC_COLLATE=Spanish_Colombia.utf8;LC_CTYPE=Spanish_Colombia.utf8;LC_MONETARY=Spanish_Colombia.utf8;LC_NUMERIC=C;LC_TIME=Spanish_Colombia.utf8"
En este informe se desarrolla un análisis de regresión lineal múltiple para apoyar la toma de decisiones de la empresa C&A en la selección de dos viviendas para una compañía internacional. El objetivo es construir modelos que permitan explicar y predecir el precio de viviendas a partir de características como el área construida, estrato, número de habitaciones, parqueaderos y baños.
Además del modelado, se realiza un proceso de revisión de calidad de datos, que incluye inspección de valores faltantes, imputación cuando sea necesaria y detección de valores atípicos. Esto es importante porque la calidad de los datos influye directamente en la estabilidad y capacidad predictiva del modelo.
Finalmente, con base en las predicciones obtenidas, se identifican ofertas potenciales que se ajusten a las restricciones de cada solicitud y se veran en mapas.
Estimar y validar modelos de regresión lineal múltiple para predecir el precio de viviendas y recomendar ofertas potenciales para dos solicitudes específicas del caso C&A.
# Paquetes
library(paqueteMODELOS)
library(dplyr)
library(ggplot2)
library(plotly)
library(leaflet)
library(caret)
library(tidyr)
library(knitr)
library(kableExtra)
library(lmtest)
library(car)
#library(MASS)
data("vivienda")
# Revisar estructura general
str(vivienda)
## 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>
dim(vivienda)
## [1] 8322 13
Las variables principales usadas en este trabajo son:
preciom: precio de la vivienda en millones de
pesos.areaconst: área construida.estrato: nivel socioeconómico.parqueaderos: número de parqueaderos.banios: número de baños.habitaciones: número de habitaciones.tipo: tipo de vivienda.zona: zona de ubicación.latitud y longitud: coordenadas
geográficas.# Moda
moda <- function(x) {
ux <- unique(na.omit(x))
ux[which.max(tabulate(match(x, ux)))]
}
# Imputaciones
imputar_datos <- function(df) {
df2 <- df
for (col in names(df2)) {
if (is.numeric(df2[[col]])) {
if (any(is.na(df2[[col]]))) {
df2[[col]][is.na(df2[[col]])] <- median(df2[[col]], na.rm = TRUE)
}
} else {
if (any(is.na(df2[[col]]))) {
df2[[col]][is.na(df2[[col]])] <- moda(df2[[col]])
}
}
}
return(df2)
}
#IQR
marcar_outliers_iqr <- function(x) {
q1 <- quantile(x, 0.25, na.rm = TRUE)
q3 <- quantile(x, 0.75, na.rm = TRUE)
iqr <- q3 - q1
li <- q1 - 1.5 * iqr
ls <- q3 + 1.5 * iqr
return(ifelse(x < li | x > ls, 1, 0))
}
# MMetricias
metricas_modelo <- function(real, pred) {
data.frame(
RMSE = RMSE(pred, real),
MAE = MAE(pred, real),
R2 = R2(pred, real)
)
}
Se trabajará con dos subconjuntos de la base:
Para cada caso se seguirá esta secuencia:
base1 <- vivienda %>%
filter(tipo == "Casa", zona == "Zona Norte")
dim(base1)
## [1] 722 13
head(base1, 3) %>%
kable(caption = "Primeros 3 registros de base1") %>%
kable_styling(full_width = FALSE)
| id | zona | piso | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | barrio | longitud | latitud |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1209 | Zona Norte | 02 | 5 | 320 | 150 | 2 | 4 | 6 | Casa | acopi | -76.51341 | 3.47968 |
| 1592 | Zona Norte | 02 | 5 | 780 | 380 | 2 | 3 | 3 | Casa | acopi | -76.51674 | 3.48721 |
| 4057 | Zona Norte | 02 | 6 | 750 | 445 | NA | 7 | 6 | Casa | acopi | -76.52950 | 3.38527 |
table(base1$tipo)
##
## Casa
## 722
table(base1$zona)
##
## Zona Norte
## 722
base1_mapa <- base1 %>%
filter(!is.na(latitud), !is.na(longitud))
leaflet(base1_mapa) %>%
addTiles() %>%
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
radius = 4,
popup = ~paste0(
"<b>Barrio:</b> ", barrio,
"<br><b>Precio (millones):</b> ", preciom,
"<br><b>Área:</b> ", areaconst,
"<br><b>Estrato:</b> ", estrato
)
)
El mapa permite verificar si las coordenadas realmente se concentran en la zona norte. Hay datos fuera de la zona, esto podría deberse a errores de digitación o problemas en la georreferenciacion.
base1_mapa %>%
arrange(latitud) %>%
dplyr::select(barrio, latitud, longitud) %>%
head(10)
## # A tibble: 10 × 3
## barrio latitud longitud
## <chr> <dbl> <dbl>
## 1 juanamb√∫ 3.33 -76.5
## 2 Cali 3.34 -76.5
## 3 Cali 3.34 -76.6
## 4 acopi 3.35 -76.5
## 5 Cali 3.35 -76.5
## 6 Cali 3.35 -76.5
## 7 acopi 3.35 -76.5
## 8 acopi 3.35 -76.5
## 9 acopi 3.36 -76.5
## 10 acopi 3.36 -76.5
base1 %>%
arrange(desc(latitud)) %>%
dplyr::select(barrio, latitud, longitud) %>%
head(10)
## # A tibble: 10 × 3
## barrio latitud longitud
## <chr> <dbl> <dbl>
## 1 alameda del río 3.50 -76.5
## 2 floralia 3.49 -76.5
## 3 brisas de los 3.49 -76.5
## 4 brisas de los 3.49 -76.5
## 5 brisas de los 3.49 -76.5
## 6 floralia 3.49 -76.5
## 7 menga 3.49 -76.5
## 8 brisas de los 3.49 -76.5
## 9 zona norte 3.49 -76.5
## 10 brisas de los 3.49 -76.5
q1_lat <- quantile(base1$latitud, 0.25, na.rm = TRUE)
q3_lat <- quantile(base1$latitud, 0.75, na.rm = TRUE)
iqr_lat <- q3_lat - q1_lat
li_lat <- q1_lat - 1.5 * iqr_lat
ls_lat <- q3_lat + 1.5 * iqr_lat
q1_lon <- quantile(base1$longitud, 0.25, na.rm = TRUE)
q3_lon <- quantile(base1$longitud, 0.75, na.rm = TRUE)
iqr_lon <- q3_lon - q1_lon
li_lon <- q1_lon - 1.5 * iqr_lon
ls_lon <- q3_lon + 1.5 * iqr_lon
base1_geo <- base1 %>%
dplyr::filter(
latitud >= li_lat,
latitud <= ls_lat,
longitud >= li_lon,
longitud <= ls_lon
)
Se hace un ajuste a las coordenadas para omitir aquellos datos que no pertenecen al norte
leaflet(base1_geo) %>%
addTiles() %>%
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
radius = 4,
popup = ~paste0(
"<b>Barrio:</b> ", barrio,
"<br><b>Precio:</b> ", preciom
)
)
Ahora si tenemos un mapa con toda la distribucion lo mas cercana a la realidad de la zona norte
na_base1 <- sapply(base1, function(x) sum(is.na(x)))
na_base1_df <- data.frame(variable = names(na_base1), faltantes = as.numeric(na_base1))
na_base1_df %>%
kable(caption = "Valores faltantes en base1") %>%
kable_styling(full_width = FALSE)
| variable | faltantes |
|---|---|
| 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 |
En un análisis de regresión, los valores faltantes pueden reducir el tamaño de la muestra o generar errores si no se tratan.
Se usa la mediana porque es la forma mas robusta de trabajar con numericos extremos
base1_imp <- imputar_datos(base1)
na_base1_despues <- sapply(base1_imp, function(x) sum(is.na(x)))
data.frame(variable = names(na_base1_despues),
faltantes_despues = as.numeric(na_base1_despues)) %>%
kable(caption = "Valores faltantes después de imputación en base1") %>%
kable_styling(full_width = FALSE)
| variable | faltantes_despues |
|---|---|
| id | 0 |
| zona | 0 |
| piso | 0 |
| estrato | 0 |
| preciom | 0 |
| areaconst | 0 |
| parqueaderos | 0 |
| banios | 0 |
| habitaciones | 0 |
| tipo | 0 |
| barrio | 0 |
| longitud | 0 |
| latitud | 0 |
En este punto no se eliminan automáticamente los valores atípicos. Primero se identifican para evaluar si podrían afectar demasiado el modelo. Esto es útil porque en vivienda sí pueden existir precios altos o áreas grandes reales, y no todo outlier es un error pueden ser casas premium
vars_num1 <- c("preciom", "areaconst", "estrato", "parqueaderos", "banios", "habitaciones")
outliers_base1 <- base1_imp %>%
mutate(
out_preciom = marcar_outliers_iqr(preciom),
out_areaconst = marcar_outliers_iqr(areaconst),
out_estrato = marcar_outliers_iqr(estrato),
out_parqueaderos = marcar_outliers_iqr(parqueaderos),
out_banios = marcar_outliers_iqr(banios),
out_habitaciones = marcar_outliers_iqr(habitaciones)
) %>%
mutate(
total_outliers = out_preciom + out_areaconst + out_estrato +
out_parqueaderos + out_banios + out_habitaciones
)
resumen_outliers1 <- data.frame(
variable = c("preciom", "areaconst", "estrato", "parqueaderos", "banios", "habitaciones"),
cantidad_outliers = c(
sum(outliers_base1$out_preciom),
sum(outliers_base1$out_areaconst),
sum(outliers_base1$out_estrato),
sum(outliers_base1$out_parqueaderos),
sum(outliers_base1$out_banios),
sum(outliers_base1$out_habitaciones)
)
)
resumen_outliers1 %>%
kable(caption = "Cantidad de outliers detectados en base1") %>%
kable_styling(full_width = FALSE)
| variable | cantidad_outliers |
|---|---|
| preciom | 31 |
| areaconst | 26 |
| estrato | 0 |
| parqueaderos | 277 |
| banios | 14 |
| habitaciones | 27 |
Dado que hay varios outliers no sabemos si estos casos se dan por que son viviendas de lujo o son errores en la data, se va a crear dos bases para entender si hay diferencia entre usar la data con o sin atipicos
base1_clean <- outliers_base1 %>%
filter(total_outliers == 0)
dim(base1_imp)
## [1] 722 13
dim(base1_clean)
## [1] 412 20
El objetivo aquí es estudiar cómo cambia el precio con las variables explicativas. Esto ayuda a entender si el uso de una regresión lineal tiene sentido.
plot_ly(
data = base1_imp,
x = ~areaconst,
y = ~preciom,
type = "scatter",
mode = "markers",
color = ~as.factor(estrato),
text = ~paste("Barrio:", barrio,
"<br>Habitaciones:", habitaciones,
"<br>Baños:", banios),
hoverinfo = "text"
) %>%
layout(
title = "Caso 1: Precio vs Área construida",
xaxis = list(title = "Área construida"),
yaxis = list(title = "Precio (millones)")
)
El grafico de dispersion entre areaa construida y el precio de la vivienda, muestra una relacion positiva entre ambas variables, las viviendas de mayor area construida tienden a tener precios mas altos.
Se observa que los estratos socioeconomicos mas altos se asocian con precios mas altos, lo que sugiere que esa variable tambien influye en la valorizacion de las propiedades
Se ven algunos valores extremos que corresponden a viviendas de gran tamaño o alto precio, lo cual podrian ser del segmento de lujo
plot_ly(
data = base1_imp,
x = ~banios,
y = ~preciom,
type = "box",
color = ~as.factor(banios)
) %>%
layout(
title = "Caso 1: Precio por número de baños",
xaxis = list(title = "Baños"),
yaxis = list(title = "Precio (millones)")
)
Las viviendas con mayor numero de baños tienden a presentar precios mas altos, lo cual es normal en el mercado inmobiliario. Se observa una dispersion de precios en viviendas con mayor numero de baños, lo que puede estar mostrando propiedades de alto valor o con caracteristicas muy especificas
plot_ly(
data = base1_imp,
x = ~habitaciones,
y = ~preciom,
type = "box",
color = ~as.factor(habitaciones)
) %>%
layout(
title = "Caso 1: Precio por número de habitaciones",
xaxis = list(title = "Habitaciones"),
yaxis = list(title = "Precio (millones)")
)
Las viviendas con mayor numero de habitaciones tienden a presentar precios mas altos, lo cual es consistente con el comportamiento del mercado, adicionalmente se ve que aumentar el numero de habitaciones aumenta la dispersion de los precios,lo que puede indicar presencias de casas de mayor tamaño o propiedades de alto valor
plot_ly(
data = base1_imp,
x = ~parqueaderos,
y = ~preciom,
type = "box",
color = ~as.factor(parqueaderos)
) %>%
layout(
title = "Caso 1: Precio por número de parqueaderos",
xaxis = list(title = "Parqueaderos"),
yaxis = list(title = "Precio (millones)")
)
Las viviendas con mayor numero de parqueaderos presentan precios mas altos, lo cual es consistente a el comportamiento esperado
Se observa una dispersion de precios mas alto para las viviendas con un numero de parqueaderos altos, lo cual podria reflejar la presencia de propiedades grandes o de alto valor.
cor_base1 <- base1_imp %>%
select(preciom, areaconst, estrato, parqueaderos, banios, habitaciones) %>%
cor(use = "complete.obs")
cor_base1 %>%
kable(caption = "Matriz de correlación - Caso 1") %>%
kable_styling(full_width = FALSE)
| preciom | areaconst | estrato | parqueaderos | banios | habitaciones | |
|---|---|---|---|---|---|---|
| preciom | 1.0000000 | 0.7313480 | 0.6123503 | 0.3033762 | 0.5233357 | 0.3227096 |
| areaconst | 0.7313480 | 1.0000000 | 0.4573818 | 0.2586839 | 0.4628152 | 0.3753323 |
| estrato | 0.6123503 | 0.4573818 | 1.0000000 | 0.2039056 | 0.4083039 | 0.1073141 |
| parqueaderos | 0.3033762 | 0.2586839 | 0.2039056 | 1.0000000 | 0.2922145 | 0.1928669 |
| banios | 0.5233357 | 0.4628152 | 0.4083039 | 0.2922145 | 1.0000000 | 0.5755314 |
| habitaciones | 0.3227096 | 0.3753323 | 0.1073141 | 0.1928669 | 0.5755314 | 1.0000000 |
La matriz de correlacion muestra que la variable area construida presenta la relacion mas fuerte con el precio de la vivienda ( 0.73), seguida por el estrato (0.61 ) y el numero de baños (0.52). Estas variables parecen ser los principales factores asociados al valor de las casas en este conjunto de datos
Las variables de habitaciones y parqueaderos presentan correlaciones positivas, pero en menor magnitud, lo que sugiere que si bien influyen en el precio no lo hacen en tan gran medida
Se oibservan correlaciones moderadas entre algunas variables explicativas, como los baños y habitaciones, lo que era esperable por las caracteristicas estructurales de las viviendas, segun lo que vemos no vemos problemas severos de multicolinealidad
Vamos a dividir la base en entrenamiento prueba ( 80 - 20% siendo 20% el test)
set.seed(123)
idx_train1 <- createDataPartition(base1_imp$preciom, p = 0.8, list = FALSE)
train1 <- base1_imp[idx_train1, ]
test1 <- base1_imp[-idx_train1, ]
dim(train1)
## [1] 579 13
dim(test1)
## [1] 143 13
Tambien tenemos el modelo de la base limpia ( quitando los outliers)
set.seed(123)
idx_train1c <- createDataPartition(base1_clean$preciom, p = 0.8, list = FALSE)
train1_clean <- base1_clean[idx_train1c, ]
test1_clean <- base1_clean[-idx_train1c, ]
dim(train1_clean)
## [1] 332 20
dim(test1_clean)
## [1] 80 20
Modelo Base
modelo1 <- lm(preciom ~ areaconst + estrato + habitaciones + banios,
data = train1)
summary(modelo1)
##
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + banios,
## data = train1)
##
## Residuals:
## Min 1Q Median 3Q Max
## -944.52 -80.99 -14.45 52.15 912.51
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -259.06556 33.00747 -7.849 2.07e-14 ***
## areaconst 0.81608 0.04803 16.991 < 2e-16 ***
## estrato 92.15564 7.94172 11.604 < 2e-16 ***
## habitaciones 1.71397 4.39893 0.390 0.697
## banios 26.20489 5.74957 4.558 6.32e-06 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 156.5 on 574 degrees of freedom
## Multiple R-squared: 0.6648, Adjusted R-squared: 0.6625
## F-statistic: 284.6 on 4 and 574 DF, p-value: < 2.2e-16
Segun el modelo, variables como area, estrato, baños y parqueaderos tienen una relacion significativa con el precio de la vivienda. En particular el area construida presenta el mayor impacto sobre el precio, lo cual es consistente con la realidad del mercado
Por otro lado la variable de numero de habitacion no resulto estadisticamente significativa dentro del modelo, lo que sugiere que el efecto lo arrastran otras variables como el area
El modelo presenta un coeficiente de determinacion R2 de 0.6678 lo que indica que el 66% de la variabilidad del precio puede ser explicada por las variables del modelo
Aun asi volvemos a correr el modelo, esta vez sin habitaciones para entender si mejoran las metricas
modelo2 <- lm(preciom ~ areaconst + estrato + parqueaderos + banios,
data = train1)
summary(modelo2)
##
## Call:
## lm(formula = preciom ~ areaconst + estrato + parqueaderos + banios,
## data = train1)
##
## Residuals:
## Min 1Q Median 3Q Max
## -910.07 -78.19 -16.20 49.00 917.04
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -268.9739 29.9957 -8.967 < 2e-16 ***
## areaconst 0.8064 0.0469 17.196 < 2e-16 ***
## estrato 90.2130 7.6673 11.766 < 2e-16 ***
## parqueaderos 14.5714 6.3818 2.283 0.0228 *
## banios 25.6583 5.0538 5.077 5.19e-07 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 155.8 on 574 degrees of freedom
## Multiple R-squared: 0.6677, Adjusted R-squared: 0.6654
## F-statistic: 288.4 on 4 and 574 DF, p-value: < 2.2e-16
A partir de este segundo modelo, las variables como area, estrato, parqueaderos y baños , siguen siendo significativas El modelo presenta un R2 de 0.6677 lo cual quiere decir que si bien quitamos una variable, no mejoro ni empeoro el desempeño del modelo, lo que confirma que esta variable es explicada por otras variables dentro del modelo
Modelo sin los Outliers
modelo1_clean <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = train1_clean)
summary(modelo1_clean)
##
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos +
## banios, data = train1_clean)
##
## Residuals:
## Min 1Q Median 3Q Max
## -238.27 -60.63 -11.11 38.81 519.00
##
## Coefficients: (1 not defined because of singularities)
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -186.05928 28.97631 -6.421 4.76e-10 ***
## areaconst 0.75477 0.06102 12.369 < 2e-16 ***
## estrato 85.15785 7.12759 11.948 < 2e-16 ***
## habitaciones -9.66851 4.45548 -2.170 0.0307 *
## parqueaderos NA NA NA NA
## banios 29.58859 5.70785 5.184 3.81e-07 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 105 on 327 degrees of freedom
## Multiple R-squared: 0.725, Adjusted R-squared: 0.7217
## F-statistic: 215.6 on 4 and 327 DF, p-value: < 2.2e-16
En este tercer modelo, se utilizo una version del dataset quitando los valores atipicos Los resultados muestran una mejora en el ajuste del modelo, evidenciada por el aumento del R2 de 0.66 a 0.72 Tambien el error residual del modelo se redujo significativamente, lo que nos quiere decir que los datos atipicos estaban influyendo de manera nevativa en el modelo
# modelo1_clean2 <- lm(preciom ~ areaconst + estrato + parqueaderos + banios,
# data = train1_clean)
#
# summary(modelo1_clean2)
#Dio el mismo resultado, entonces no vale la pena dejarlo
pred1_base <- predict(modelo1, newdata = test1)
pred1_clean <- predict(modelo1_clean, newdata = test1_clean)
met_base1 <- metricas_modelo(test1$preciom, pred1_base) %>% mutate(modelo = "Base")
met_clean1 <- metricas_modelo(test1_clean$preciom, pred1_clean) %>% mutate(modelo = "Sin outliers")
bind_rows(met_base1, met_clean1) %>%
select(modelo, everything()) %>%
kable(caption = "Comparación de métricas - Caso 1") %>%
kable_styling(full_width = FALSE)
| modelo | RMSE | MAE | R2 |
|---|---|---|---|
| Base | 169.27104 | 102.48831 | 0.5929570 |
| Sin outliers | 98.21813 | 77.60659 | 0.7201693 |
Para evaluar el desempeño del modelo, se comparan dos versiones : un modelo base utilizando todos los datos y un modelo estimado despues de eliminar los atipicos. Los resultados muestran que el modelo sin outliers presentan un mejor desempeño en todas las metricas.
En particular el RMSE se reduce de 167 a 98 , el MAE se reduce de 100 a 77 y el R2 de 0.60 a 0.72 lo que indica que el modelo sin outliers explica una mayor proporcion de la variabilidad del precio de las viviendas
Esto sugiere que eliminar los outliers mejora en gran medida el modelo
modelo1_final <- modelo1_clean
nombre_modelo1_final <- "Modelo sin outliers"
summary(modelo1_final)
##
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos +
## banios, data = train1_clean)
##
## Residuals:
## Min 1Q Median 3Q Max
## -238.27 -60.63 -11.11 38.81 519.00
##
## Coefficients: (1 not defined because of singularities)
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -186.05928 28.97631 -6.421 4.76e-10 ***
## areaconst 0.75477 0.06102 12.369 < 2e-16 ***
## estrato 85.15785 7.12759 11.948 < 2e-16 ***
## habitaciones -9.66851 4.45548 -2.170 0.0307 *
## parqueaderos NA NA NA NA
## banios 29.58859 5.70785 5.184 3.81e-07 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 105 on 327 degrees of freedom
## Multiple R-squared: 0.725, Adjusted R-squared: 0.7217
## F-statistic: 215.6 on 4 and 327 DF, p-value: < 2.2e-16
El modelo de regresion lineal multiple muestra que variables como area construida, estrato y numero de baños tienen una relacion positiva y significativa con el precio de las viviendas. Esto indica que en general , las viviendas con mayor area, ubicadas en estratos altos y con mas baños tienden a tener precios mas elevados
El modelo presenta un R2 de 0.725 lo que significa que aproximadamente el 72,5% de la variacion del precio de las viviendas puede ser explicadas por las variables incluidas en el modelo. Este resultado indica que el modelo tiene una buena capacidad para explicar el precio.
par(mfrow = c(2,2))
plot(modelo1_final)
par(mfrow = c(1,1))
En el grafico Residuals vs Fitted se observa dispersion relativamente aleatoria de los residuos, esto sugiere que la relacion entre variables es aproximadamente lineal. El grafico Q-Q Residuals muestra que la mayoria de los puntos siguen la linea teorica, lo que indica que los residuos presentan un comportamiento cercano a la noramlidad ( Aunque hay algunas desviaciones en los extremos) El grafico Scale-Location se observa una dispersion moderada de los residuos, lo cual sugiere que la varianza es relativamente constante El grafico Residuals vs Leverage, nos ayuda a ver observaciones influyentes, aunque no se observan puntos extremadamente dominantes que afecten en gran medida el modelo
En general los resultados sugieren que el modelo cumple razonablemente los supuestos basicos de la regresion lineal
La solicitud 1 corresponde a una casa en zona norte con:
Dado que el estrato puede ser 4 o 5, se realizan dos predicciones.
nueva_viv1_e4 <- data.frame(
areaconst = 200,
estrato = 4,
habitaciones = 4,
parqueaderos = 1,
banios = 2
)
nueva_viv1_e5 <- data.frame(
areaconst = 200,
estrato = 5,
habitaciones = 4,
parqueaderos = 1,
banios = 2
)
pred_viv1_e4 <- predict(modelo1_final, newdata = nueva_viv1_e4, interval = "prediction")
pred_viv1_e5 <- predict(modelo1_final, newdata = nueva_viv1_e5, interval = "prediction")
pred_viv1_e4
## fit lwr upr
## 1 326.0293 118.6429 533.4157
pred_viv1_e5
## fit lwr upr
## 1 411.1871 203.1059 619.2684
Se generan dos escenarios por que se permite estrato 4 o 5 por ende los resultados quedan
Estrato 4: Precio estimado 326 Millones, entre 118 y 533 Millones Estrato 5; Precio estimado 411 Millones, entre 203 y 619 Millones
Aquí se buscan viviendas reales dentro de la base que se parezcan a la solicitud y respeten el presupuesto.
ofertas1 <- base1_imp %>%
filter(
preciom <= 350,
estrato %in% c(4,5),
habitaciones >= 4,
banios >= 2,
parqueaderos >= 1
) %>%
mutate(
dif_area = abs(areaconst - 200),
dif_precio_ref = abs(preciom - as.numeric(pred_viv1_e4[1]))
) %>%
arrange(dif_area, dif_precio_ref) %>%
slice(1:5)
ofertas1 %>%
select(barrio, zona, tipo, preciom, areaconst, estrato, habitaciones, banios, parqueaderos, latitud, longitud) %>%
kable(caption = "Top 5 ofertas potenciales - Vivienda 1") %>%
kable_styling(full_width = FALSE)
| barrio | zona | tipo | preciom | areaconst | estrato | habitaciones | banios | parqueaderos | latitud | longitud |
|---|---|---|---|---|---|---|---|---|---|---|
| la flora | Zona Norte | Casa | 320 | 200 | 5 | 4 | 4 | 2 | 3.48893 | -76.51524 |
| la merced | Zona Norte | Casa | 320 | 200 | 4 | 4 | 4 | 2 | 3.48029 | -76.51156 |
| el bosque | Zona Norte | Casa | 350 | 200 | 5 | 4 | 3 | 3 | 3.48503 | -76.53010 |
| el bosque | Zona Norte | Casa | 335 | 202 | 5 | 5 | 4 | 1 | 3.48399 | -76.53044 |
| vipasa | Zona Norte | Casa | 340 | 203 | 5 | 4 | 3 | 2 | 3.48257 | -76.51803 |
Las condiciones utilizadas corresponden a valores mínimos deseados para la vivienda. Por lo tanto, las ofertas encontradas pueden presentar características superiores, como un mayor número de baños o parqueaderos, lo cual representa una mejora respecto a los requerimientos iniciales.
ofertas1_mapa <- ofertas1 %>%
filter(!is.na(latitud), !is.na(longitud))
leaflet(ofertas1_mapa) %>%
addTiles() %>%
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
radius = 6,
color = "blue",
popup = ~paste0(
"<b>Barrio:</b> ", barrio,
"<br><b>Precio:</b> ", preciom, " millones",
"<br><b>Área:</b> ", areaconst,
"<br><b>Estrato:</b> ", estrato,
"<br><b>Habitaciones:</b> ", habitaciones,
"<br><b>Baños:</b> ", banios,
"<br><b>Parqueaderos:</b> ", parqueaderos
)
)
Estas ofertas se seleccionan por cercanía a las condiciones solicitadas y por cumplir el presupuesto máximo de 350 millones. La recomendación final debe considerar no solo el precio estimado, sino también la similitud con el área, estrato y número de habitaciones.
Los resultados muestran que existen varias viviendas con características similares en barrios como La Flora, La Merced, El Bosque y Vipasa, con precios entre 320 y 350 millones y áreas cercanas a 200 m². Esto sugiere que el valor estimado por el modelo es consistente con los precios observados en el mercado para viviendas con características similares.
Para el segundo caso se deben considerar solo apartamentos en zona sur.
base2 <- vivienda %>%
filter(tipo == "Apartamento", zona == "Zona Sur")
dim(base2)
## [1] 2787 13
head(base2, 3) %>%
kable(caption = "Primeros 3 registros de base2") %>%
kable_styling(full_width = FALSE)
| id | zona | piso | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | barrio | longitud | latitud |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 5098 | Zona Sur | 05 | 4 | 290 | 96 | 1 | 2 | 3 | Apartamento | acopi | -76.53464 | 3.44987 |
| 698 | Zona Sur | 02 | 3 | 78 | 40 | 1 | 1 | 2 | Apartamento | aguablanca | -76.50100 | 3.40000 |
| 8199 | Zona Sur | NA | 6 | 875 | 194 | 2 | 5 | 3 | Apartamento | aguacatal | -76.55700 | 3.45900 |
table(base2$tipo)
##
## Apartamento
## 2787
table(base2$zona)
##
## Zona Sur
## 2787
base2_mapa <- base2 %>%
filter(!is.na(latitud), !is.na(longitud))
leaflet(base2_mapa) %>%
addTiles() %>%
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
radius = 4,
popup = ~paste0(
"<b>Barrio:</b> ", barrio,
"<br><b>Precio (millones):</b> ", preciom,
"<br><b>Área:</b> ", areaconst,
"<br><b>Estrato:</b> ", estrato
)
)
q1_lat <- quantile(base2$latitud, 0.25, na.rm = TRUE)
q3_lat <- quantile(base2$latitud, 0.75, na.rm = TRUE)
iqr_lat <- q3_lat - q1_lat
li_lat <- q1_lat - 1.5 * iqr_lat
ls_lat <- q3_lat + 1.5 * iqr_lat
q1_lon <- quantile(base2$longitud, 0.25, na.rm = TRUE)
q3_lon <- quantile(base2$longitud, 0.75, na.rm = TRUE)
iqr_lon <- q3_lon - q1_lon
li_lon <- q1_lon - 1.5 * iqr_lon
ls_lon <- q3_lon + 1.5 * iqr_lon
base2_geo <- base2 %>%
dplyr::filter(
latitud >= li_lat,
latitud <= ls_lat,
longitud >= li_lon,
longitud <= ls_lon
)
leaflet(base2_geo) %>%
addTiles() %>%
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
radius = 4,
popup = ~paste0(
"<b>Barrio:</b> ", barrio,
"<br><b>Precio:</b> ", preciom
)
)
Ahora si tenemos un mapa con toda la distribucion lo mas cercana a la realidad de la zona sur ( Aun quedan datos que pueden estar fuera del rango, pueden ser datos mal georeferenciados o mal clasificados )
na_base2 <- sapply(base2, function(x) sum(is.na(x)))
data.frame(variable = names(na_base2), faltantes = as.numeric(na_base2)) %>%
kable(caption = "Valores faltantes en base2") %>%
kable_styling(full_width = FALSE)
| variable | faltantes |
|---|---|
| id | 0 |
| zona | 0 |
| piso | 622 |
| estrato | 0 |
| preciom | 0 |
| areaconst | 0 |
| parqueaderos | 406 |
| banios | 0 |
| habitaciones | 0 |
| tipo | 0 |
| barrio | 0 |
| longitud | 0 |
| latitud | 0 |
En un análisis de regresión, los valores faltantes pueden reducir el tamaño de la muestra o generar errores si no se tratan. Se hara uso de lo siguiente:
La mediana es útil porque es robusta frente a valores extremos.
base2_imp <- imputar_datos(base2)
na_base2_despues <- sapply(base2_imp, function(x) sum(is.na(x)))
data.frame(variable = names(na_base2_despues),
faltantes_despues = as.numeric(na_base2_despues)) %>%
kable(caption = "Valores faltantes después de imputación en base2") %>%
kable_styling(full_width = FALSE)
| variable | faltantes_despues |
|---|---|
| id | 0 |
| zona | 0 |
| piso | 0 |
| estrato | 0 |
| preciom | 0 |
| areaconst | 0 |
| parqueaderos | 0 |
| banios | 0 |
| habitaciones | 0 |
| tipo | 0 |
| barrio | 0 |
| longitud | 0 |
| latitud | 0 |
outliers_base2 <- base2_imp %>%
mutate(
out_preciom = marcar_outliers_iqr(preciom),
out_areaconst = marcar_outliers_iqr(areaconst),
out_estrato = marcar_outliers_iqr(estrato),
out_parqueaderos = marcar_outliers_iqr(parqueaderos),
out_banios = marcar_outliers_iqr(banios),
out_habitaciones = marcar_outliers_iqr(habitaciones)
) %>%
mutate(
total_outliers = out_preciom + out_areaconst + out_estrato +
out_parqueaderos + out_banios + out_habitaciones
)
resumen_outliers2 <- data.frame(
variable = c("preciom", "areaconst", "estrato", "parqueaderos", "banios", "habitaciones"),
cantidad_outliers = c(
sum(outliers_base2$out_preciom),
sum(outliers_base2$out_areaconst),
sum(outliers_base2$out_estrato),
sum(outliers_base2$out_parqueaderos),
sum(outliers_base2$out_banios),
sum(outliers_base2$out_habitaciones)
)
)
resumen_outliers2 %>%
kable(caption = "Cantidad de outliers detectados en base2") %>%
kable_styling(full_width = FALSE)
| variable | cantidad_outliers |
|---|---|
| preciom | 263 |
| areaconst | 177 |
| estrato | 0 |
| parqueaderos | 33 |
| banios | 141 |
| habitaciones | 885 |
Dado que hay varios outliers no sabemos si estos casos se dan por que son viviendas de lujo o son errores en la data, se va a crear dos bases para entender si hay diferencia entre usar la data con o sin atipicos
base2_clean <- outliers_base2 %>%
filter(total_outliers == 0)
dim(base2_imp)
## [1] 2787 13
dim(base2_clean)
## [1] 1722 20
plot_ly(
data = base2_imp,
x = ~areaconst,
y = ~preciom,
type = "scatter",
mode = "markers",
color = ~as.factor(estrato),
text = ~paste("Barrio:", barrio,
"<br>Habitaciones:", habitaciones,
"<br>Banos:", banios),
hoverinfo = "text"
) %>%
layout(
title = "Caso 2: Precio vs Área construida",
xaxis = list(title = "Área construida"),
yaxis = list(title = "Precio (millones)")
)
El grafico de dispersion entre areaa construida y el precio de los apartamentos, muestra una relacion positiva entre ambas variables, las viviendas de mayor area construida tienden a tener precios mas altos.
Se observa que los estratos socioeconomicos mas altos ( estrato 5 y 6 ) se asocian con precios mas altos, lo que sugiere que esa variable tambien influye en la valorizacion de las propiedades
Se ven algunos valores extremos que corresponden a viviendas de gran tamaño o alto precio, lo cual podrian ser del segmento de lujo
plot_ly(
data = base2_imp,
x = ~banios,
y = ~preciom,
type = "box",
color = ~as.factor(banios)
) %>%
layout(
title = "Caso 2: Precio por número de baños",
xaxis = list(title = "Baños"),
yaxis = list(title = "Precio (millones)")
)
El gráfico muestra que, en general, a mayor número de baños, mayor precio del apartamento. Las viviendas con 5 o 6 baños presentan los valores más altos, mientras que aquellas con 1 o 2 baños se concentran en precios menores. Esto sugiere que el número de baños está asociado positivamente con el valor de los apartamentos
plot_ly(
data = base2_imp,
x = ~habitaciones,
y = ~preciom,
type = "box",
color = ~as.factor(habitaciones)
) %>%
layout(
title = "Caso 2: Precio por número de habitaciones",
xaxis = list(title = "Habitaciones"),
yaxis = list(title = "Precio (millones)")
)
Las viviendas con mayor numero de habitaciones tienden a presentar precios mas altos, lo cual es consistente con el comportamiento del mercado, adicionalmente se ve que aumentar el numero de habitaciones aumenta la dispersion de los precios,lo que puede indicar presencias de apartamentos de mayor tamaño o propiedades de alto valor
cor_base2 <- base2_imp %>%
select(preciom, areaconst, estrato, parqueaderos, banios, habitaciones) %>%
cor(use = "complete.obs")
cor_base2 %>%
kable(caption = "Matriz de correlación - Caso 2") %>%
kable_styling(full_width = FALSE)
| preciom | areaconst | estrato | parqueaderos | banios | habitaciones | |
|---|---|---|---|---|---|---|
| preciom | 1.0000000 | 0.7579955 | 0.6727067 | 0.6967313 | 0.7196705 | 0.3317538 |
| areaconst | 0.7579955 | 1.0000000 | 0.4815593 | 0.5787405 | 0.6618179 | 0.4339608 |
| estrato | 0.6727067 | 0.4815593 | 1.0000000 | 0.4983633 | 0.5686171 | 0.2125953 |
| parqueaderos | 0.6967313 | 0.5787405 | 0.4983633 | 1.0000000 | 0.5633010 | 0.2517695 |
| banios | 0.7196705 | 0.6618179 | 0.5686171 | 0.5633010 | 1.0000000 | 0.5149227 |
| habitaciones | 0.3317538 | 0.4339608 | 0.2125953 | 0.2517695 | 0.5149227 | 1.0000000 |
La matriz de correlación muestra que el precio de la vivienda tiene una relación positiva principalmente con el área construida (0.76), seguida por el número de baños (0.72), parqueaderos (0.70) y estrato (0.67). Esto indica que estas variables influyen de manera importante en el valor de los apartamentos. En contraste, el número de habitaciones presenta una correlación más baja (0.33)
Vamos a dividir la base en entrenamiento prueba ( 80 - 20% siendo 20% el test)
set.seed(123)
idx_train2 <- createDataPartition(base2_imp$preciom, p = 0.8, list = FALSE)
train2 <- base2_imp[idx_train2, ]
test2 <- base2_imp[-idx_train2, ]
dim(train2)
## [1] 2231 13
dim(test2)
## [1] 556 13
Tambien tenemos el modelo de la base limpia ( quitando los outliers)
set.seed(123)
idx_train2c <- createDataPartition(base2_clean$preciom, p = 0.8, list = FALSE)
train2_clean <- base2_clean[idx_train2c, ]
test2_clean <- base2_clean[-idx_train2c, ]
dim(train2_clean)
## [1] 1379 20
dim(test2_clean)
## [1] 343 20
modelo2 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = train2)
summary(modelo2)
##
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos +
## banios, data = train2)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1185.54 -38.92 -2.76 37.82 919.34
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -266.62819 14.49194 -18.398 < 2e-16 ***
## areaconst 1.39711 0.05587 25.007 < 2e-16 ***
## estrato 59.71370 3.02394 19.747 < 2e-16 ***
## habitaciones -19.31148 3.71704 -5.195 2.23e-07 ***
## parqueaderos 68.32691 4.02302 16.984 < 2e-16 ***
## banios 46.70035 3.42887 13.620 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 93.62 on 2225 degrees of freedom
## Multiple R-squared: 0.76, Adjusted R-squared: 0.7594
## F-statistic: 1409 on 5 and 2225 DF, p-value: < 2.2e-16
Segun el modelo, variables como area, estrato, baños y parqueaderos tienen una relacion significativa con el precio de la vivienda. En particular el area construida presenta el mayor impacto sobre el precio, lo cual es consistente con la realidad del mercado
Por otro lado la variable de numero de habitaciones no resulto estadisticamente significativa dentro del modelo
El modelo presenta un coeficiente de determinacion R2 de 0.76 lo que indica que el 76% de la variabilidad del precio puede ser explicada por las variables del modelo
modelo2_clean <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = train2_clean)
summary(modelo2_clean)
##
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos +
## banios, data = train2_clean)
##
## Residuals:
## Min 1Q Median 3Q Max
## -215.850 -25.170 -0.099 27.016 159.128
##
## Coefficients: (1 not defined because of singularities)
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -160.87761 7.42135 -21.678 < 2e-16 ***
## areaconst 2.06429 0.08188 25.211 < 2e-16 ***
## estrato 39.42774 1.97312 19.982 < 2e-16 ***
## habitaciones NA NA NA NA
## parqueaderos 28.80915 3.56230 8.087 1.33e-15 ***
## banios 9.96042 2.83560 3.513 0.000458 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 45.26 on 1374 degrees of freedom
## Multiple R-squared: 0.7503, Adjusted R-squared: 0.7496
## F-statistic: 1032 on 4 and 1374 DF, p-value: < 2.2e-16
En este segundo modelo, se utilizo una version del dataset quitando los valores atipicos Los resultados muestran una desmejoria en el ajuste del modelo, evidenciada por la disminucion del R2 de 0.76 a 0.75
Elerror residual del modelo se redujo significativamente, lo que nos quiere decir que los datos atipicos estaban influyendo de manera negativa en el modelo
pred2_base <- predict(modelo2, newdata = test2)
pred2_clean <- predict(modelo2_clean, newdata = test2_clean)
met_base2 <- metricas_modelo(test2$preciom, pred2_base) %>% mutate(modelo = "Base")
met_clean2 <- metricas_modelo(test2_clean$preciom, pred2_clean) %>% mutate(modelo = "Sin outliers")
bind_rows(met_base2, met_clean2) %>%
select(modelo, everything()) %>%
kable(caption = "Comparación de métricas - Caso 2") %>%
kable_styling(full_width = FALSE)
| modelo | RMSE | MAE | R2 |
|---|---|---|---|
| Base | 90.61755 | 55.89612 | 0.7826907 |
| Sin outliers | 50.63154 | 37.01886 | 0.6929699 |
La comparación de métricas muestra que el modelo sin outliers reduce considerablemente el error, pasando de un RMSE de 90.62 a 50.63 y de un MAE de 55.89 a 37.02, lo que indica predicciones más precisas.
Aunque el R2 disminuye ligeramente de 0.78 a 0.69, el modelo sin valores atípicos presenta un mejor desempeño en términos de error, sugiriendo que los outliers estaban afectando la estabilidad de las estimaciones.
modelo2_final <- modelo2_clean
nombre_modelo2_final <- "Modelo sin outliers"
summary(modelo2_final)
##
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos +
## banios, data = train2_clean)
##
## Residuals:
## Min 1Q Median 3Q Max
## -215.850 -25.170 -0.099 27.016 159.128
##
## Coefficients: (1 not defined because of singularities)
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -160.87761 7.42135 -21.678 < 2e-16 ***
## areaconst 2.06429 0.08188 25.211 < 2e-16 ***
## estrato 39.42774 1.97312 19.982 < 2e-16 ***
## habitaciones NA NA NA NA
## parqueaderos 28.80915 3.56230 8.087 1.33e-15 ***
## banios 9.96042 2.83560 3.513 0.000458 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 45.26 on 1374 degrees of freedom
## Multiple R-squared: 0.7503, Adjusted R-squared: 0.7496
## F-statistic: 1032 on 4 and 1374 DF, p-value: < 2.2e-16
Se elige el modelo sin outliers dado que este modelo tiene mejores estadisticas aun cuando sacrificamos un poco del R2
par(mfrow = c(2,2))
plot(modelo2_final)
par(mfrow = c(1,1))
Los gráficos de validación muestran que los residuos se distribuyen alrededor de cero, lo que sugiere que el modelo captura razonablemente la relación entre las variables. Sin embargo, el gráfico Q-Q indica algunas desviaciones en los extremos, lo que sugiere que la normalidad de los residuos no se cumple completamente. Además, el gráfico Scale-Location muestra cierta variación en la dispersión, lo que podría indicar heterocedasticidad. En general, el modelo es útil para predicción, aunque podrían aplicarse transformaciones o métodos robustos para mejorar el cumplimiento de los supuestos.
La solicitud 2 corresponde a un apartamento en zona sur con:
Se generan dos predicciones, una para estrato 5 y otra para estrato 6.
nueva_viv2_e5 <- data.frame(
areaconst = 300,
estrato = 5,
habitaciones = 5,
parqueaderos = 3,
banios = 3
)
nueva_viv2_e6 <- data.frame(
areaconst = 300,
estrato = 6,
habitaciones = 5,
parqueaderos = 3,
banios = 3
)
pred_viv2_e5 <- predict(modelo2_final, newdata = nueva_viv2_e5, interval = "prediction")
pred_viv2_e6 <- predict(modelo2_final, newdata = nueva_viv2_e6, interval = "prediction")
pred_viv2_e5
## fit lwr upr
## 1 771.8578 678.3122 865.4035
pred_viv2_e6
## fit lwr upr
## 1 811.2856 718.0928 904.4783
Se generan dos escenarios por que se permite estrato 5 o 6 por ende los resultados quedan
Estrato 5: Precio estimado 771 Millones, entre 678 y 865 Millones Estrato 6; Precio estimado 811 Millones, entre 718 y 908 Millones
ofertas2 <- base2_imp %>%
filter(
preciom <= 850,
estrato %in% c(5,6),
habitaciones >= 5,
banios >= 3,
parqueaderos >= 3
) %>%
mutate(
dif_area = abs(areaconst - 300),
dif_precio_ref = abs(preciom - as.numeric(pred_viv2_e5[1]))
) %>%
arrange(dif_area, dif_precio_ref) %>%
slice(1:5)
ofertas2 %>%
select(barrio, zona, tipo, preciom, areaconst, estrato, habitaciones, banios, parqueaderos, latitud, longitud) %>%
kable(caption = "Top 5 ofertas potenciales - Vivienda 2") %>%
kable_styling(full_width = FALSE)
| barrio | zona | tipo | preciom | areaconst | estrato | habitaciones | banios | parqueaderos | latitud | longitud |
|---|---|---|---|---|---|---|---|---|---|---|
| seminario | Zona Sur | Apartamento | 670 | 300 | 5 | 6 | 5 | 3 | 3.40900 | -76.55000 |
| seminario | Zona Sur | Apartamento | 530 | 256 | 5 | 5 | 5 | 3 | 3.40748 | -76.55408 |
| guadalupe | Zona Sur | Apartamento | 730 | 573 | 5 | 5 | 8 | 3 | 3.40800 | -76.54800 |
Para esta solicitud unicamente se encontraron 3 apartamentos, los cuales cumplen con las caracteristicas y se encuentran dentro del presupuesto maximo de 850 Millones, los barrios serian Seminario y Guadalupe, en la zona Sur, con caracteristicas muy similares. Estos apartamentos representan alternativas viables segun la estimacion del modelo y las restricciones del credito disponible
ofertas2_mapa <- ofertas2 %>%
filter(!is.na(latitud), !is.na(longitud))
leaflet(ofertas2_mapa) %>%
addTiles() %>%
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
radius = 6,
color = "blue",
popup = ~paste0(
"<b>Barrio:</b> ", barrio,
"<br><b>Precio:</b> ", preciom, " millones",
"<br><b>Área:</b> ", areaconst,
"<br><b>Estrato:</b> ", estrato,
"<br><b>Habitaciones:</b> ", habitaciones,
"<br><b>Baños:</b> ", banios,
"<br><b>Parqueaderos:</b> ", parqueaderos
)
)
Validando la ubicacion geografica vemos que los 3 apartamentos estan relativamente cerca los unos de los otros, por lo que representan alternativas viables que cumplen con las características solicitadas y el presupuesto disponible.
comparacion_general <- bind_rows(
met_base1 %>% mutate(caso = "Caso 1"),
met_clean1 %>% mutate(caso = "Caso 1"),
met_base2 %>% mutate(caso = "Caso 2"),
met_clean2 %>% mutate(caso = "Caso 2")
)
comparacion_general %>%
select(caso, modelo, RMSE, MAE, R2) %>%
kable(caption = "Comparación general de métricas") %>%
kable_styling(full_width = FALSE)
| caso | modelo | RMSE | MAE | R2 |
|---|---|---|---|---|
| Caso 1 | Base | 169.27104 | 102.48831 | 0.5929570 |
| Caso 1 | Sin outliers | 98.21813 | 77.60659 | 0.7201693 |
| Caso 2 | Base | 90.61755 | 55.89612 | 0.7826907 |
| Caso 2 | Sin outliers | 50.63154 | 37.01886 | 0.6929699 |
La comparación general de métricas muestra que los modelos sin outliers presentan menores errores de predicción (RMSE y MAE) en ambos casos, lo que indica estimaciones más precisas. Aunque en el Caso 2 el R2 disminuye ligeramente, la reducción en los errores sugiere que eliminar valores atípicos mejora la estabilidad del modelo. En general, los modelos ajustados sin outliers ofrecen mejor desempeño para la predicción de precios de vivienda.
A partir del análisis realizado se evidenció que variables como el área construida, el estrato, el número de baños y los parqueaderos presentan una relación significativa con el precio de la vivienda. En particular, el área construida mostró la mayor influencia, lo cual es consistente con el comportamiento del mercado inmobiliario. Por otro lado las habitaciones fueron la variable con menor reelevancia lo que quiere decir que otras variables ya la explicaban.
El análisis exploratorio permitió identificar la presencia de valores atípicos, los cuales afectaban el ajuste del modelo. Al remover estos valores se observó una reducción en los errores de predicción (RMSE y MAE), lo que indica una mejora en la estabilidad del modelo, aunque en algunos casos el coeficiente R2 disminuyó ligeramente.
Finalmente, el modelo permitió estimar precios para las viviendas solicitadas e identificar ofertas reales dentro del presupuesto disponible, lo que demuestra la utilidad del análisis para apoyar procesos de toma de decisiones en el mercado inmobiliario.
Para futuros análisis se recomienda incluir más variables que puedan influir en el precio, como la antigüedad del inmueble, características del barrio o cercanía a servicios y transporte.
También sería útil explorar otros modelos predictivos que puedan capturar relaciones más complejas entre las variables, con el fin de mejorar la precisión de las estimaciones.
Por último, se recomienda continuar realizando procesos de limpieza y validación de datos, ya que la presencia de valores atípicos puede afectar significativamente el desempeño de los modelos.