Maria comenzó como agente de bienes raíces en Cali hace 10 años. Después de laborar dos años para una empresa nacional, se traslado a Bogotá y trabajó para otra agencia de bienes raíces. Sus amigos y familiares la convencieron de que con su experiencia y conocimientos del negocio debía abrir su propia agencia. Terminó por adquirir la licencia de intermediario y al poco tiempo fundó su propia compañía, C&A (Casas y Apartamentos) en Cali. Santiago y Lina, dos vendedores de la empresa anterior aceptaron trabajar en la nueva compaña. En la actualidad ocho agentes de bienes raíces colaboran con ella en C&A.
Actualmente las ventas de bienes raíces en Cali se han visto disminuidas de manera significativa en lo corrido del año. Durante este periodo muchas instituciones bancarias de ahorro y vivienda están prestando grandes sumas de dinero para la industria y la construcción comercial y residencial. Cuando el efecto producto de las tensiones políticas y sociales disminuya, se espera que la actividad económica de este sector se reactive.
Hace dos días, María recibió una carta solicitando asesoría para la compra de dos viviendas por parte de una compañía internacional que desea ubicar a dos de sus empleados con sus familias en la ciudad. Las solicitudes incluyen las siguientes condiciones:
| Características | Vivienda 1 | Vivienda 2 |
|---|---|---|
| Tipo | Casa | Apartamento |
| Área construida | 200 | 300 |
| Parqueaderos | 1 | 3 |
| Baños | 2 | 3 |
| Habitaciones | 4 | 5 |
| Estrato | 4 o 5 | 5 o 6 |
| Zona | Norte | Sur |
| Crédito preaprobado | 350 Millones | 850 Millones |
Ayude a María a responder la solicitud, mediante técnicas modelación que usted conoce. Ella requiere le envíe un informe ejecutivo donde analice los dos casos y sus recomendaciones (Informe). Como soporte del informe debe anexar las estimaciones, validaciones y comparación de modelos requeridos (Anexos) .
library(paqueteMODELOS)
data("vivienda")
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>
library(dplyr)
vivienda$id <- as.character(vivienda$id)
vivienda$estrato <- as.character(vivienda$estrato)
df_filtrado = vivienda %>%
filter(tipo == "Casa", zona == "Zona Norte")
head(df_filtrado, 3)
## # A tibble: 3 × 13
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1209 Zona N… 02 5 320 150 2 4 6
## 2 1592 Zona N… 02 5 780 380 2 3 3
## 3 4057 Zona N… 02 6 750 445 NA 7 6
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
A continuación, se muestra el mapa de la ciudad de Cali con las viviendas que pertenecen a ambas categorías
#Generación de mapas
library(leaflet)
leaflet(data = df_filtrado) %>%
addTiles() %>% # Agrega un mapa base
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
radius = 4,
color = "blue",
stroke = FALSE,
fillOpacity = 0.8,
popup = ~paste("Zona:", zona, "<br> Precio:", preciom)
)
Como se muestra en el mapa, hay viviendas que no pertencen a la zona norte, lo que implica que el dataset contiene errores de etiquetado y requiere una actualzación. No obstante, la mayoría de las viviendas si se ubican correctamente en la zona norte de la ciudad por lo que es viable continuar con el estudio requerido.
#Acotación de variables para modelo
df_modelo <- df_filtrado %>% select(id, preciom, areaconst, estrato, banios, habitaciones, zona)
#Verificación de variables categóricas:
table(df_modelo$estrato)
##
## 3 4 5 6
## 235 161 271 55
table(df_modelo$zona)
##
## Zona Norte
## 722
#Tratamietno de variables categóricas para RLM: "Estrato" y "zona"
df_modelo$D1 = as.numeric(df_modelo$estrato=="4")
df_modelo$D2 = as.numeric(df_modelo$estrato=="5")
df_modelo$D3 = as.numeric(df_modelo$estrato=="6")
df_modelo$D4 = as.numeric(df_modelo$zona=="Zona Norte")
#Resumen da datos
summary(df_modelo)
## id preciom areaconst estrato
## Length:722 Min. : 89.0 Min. : 30.0 Length:722
## Class :character 1st Qu.: 261.2 1st Qu.: 140.0 Class :character
## Mode :character Median : 390.0 Median : 240.0 Mode :character
## Mean : 445.9 Mean : 264.9
## 3rd Qu.: 550.0 3rd Qu.: 336.8
## Max. :1940.0 Max. :1440.0
## banios habitaciones zona D1
## Min. : 0.000 Min. : 0.000 Length:722 Min. :0.000
## 1st Qu.: 2.000 1st Qu.: 3.000 Class :character 1st Qu.:0.000
## Median : 3.000 Median : 4.000 Mode :character Median :0.000
## Mean : 3.555 Mean : 4.507 Mean :0.223
## 3rd Qu.: 4.000 3rd Qu.: 5.000 3rd Qu.:0.000
## Max. :10.000 Max. :10.000 Max. :1.000
## D2 D3 D4
## Min. :0.0000 Min. :0.00000 Min. :1
## 1st Qu.:0.0000 1st Qu.:0.00000 1st Qu.:1
## Median :0.0000 Median :0.00000 Median :1
## Mean :0.3753 Mean :0.07618 Mean :1
## 3rd Qu.:1.0000 3rd Qu.:0.00000 3rd Qu.:1
## Max. :1.0000 Max. :1.00000 Max. :1
#install.packages("naniar") # Si no lo tienes
#install.packages("visdat")
library(naniar)
library(visdat)
# Contar valores faltantes por columna
colSums(is.na(df_modelo))
## id preciom areaconst estrato banios habitaciones
## 0 0 0 0 0 0
## zona D1 D2 D3 D4
## 0 0 0 0 0
# Visualización gráfica de los valores faltantes
vis_miss(df_modelo) # Mapa visual de datos faltantes
En el proceso anterior se muestra la estandarización de la variable categórica de estrato en variables binarias D1, D2, D3 las cuales pueden tomar valores de 0 o 1 según corresponda el estrato:
Si el estrato es 3, las 3 variables valen 0
Si el estratoes 4, D1 vale 1, D2 y D3 valen 0
Si el estrato es 5, D2 vale 1, D1 y D3 valen 0
Si el estrato es 6, D3 vale 1, D1 y D2 valen 0
Adicionalmente, se hace el análisis de datos faltantes para las variables escogidas para el modelo (preciom, areaconst, estrato, banios, habitaciones, D1, D2, D3).
Como se muestra en la figura, no hay datos faltantes en el Dataset.
Detección de outliers
library(ggplot2)
#Verificación de Outliers
# Crear un boxplot para cada variable numérica
df_modelo %>%
tidyr::pivot_longer(cols = where(is.numeric)) %>%
ggplot(aes(x = name, y = value)) +
geom_boxplot(outlier.colour = "red", outlier.shape = 16) +
theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
labs(title = "Detección de Outliers por Variable", x = "Variable", y = "Valor")
En la detección de outliers encontramos que existen datos atípicos en las variables de preciom y área construida, sin embargo, como pueden haber precios de vivienda con características especiales se opta inicialmente por mantener estos datos atípicos.
Detección de correlaciones entre variables cuantitativas
#Detección de correlación entre variables cuantitativas
library(GGally)
library(ggplot2)
numeric_vars_modelo <- df_modelo[, sapply(df_modelo, is.numeric)]
ggpairs(numeric_vars_modelo,
lower = list(continuous = wrap("points")), # Gráficos de dispersión en la parte inferior
upper = list(continuous = wrap("cor", size = 5)), # Correlaciones en la parte superior
diag = list(continuous = wrap("densityDiag"))) # Distribución en la diagonal
#Calcular matriz de correlaciones
cor_matrix <- cor(numeric_vars_modelo, use = "complete.obs", method = "pearson")
print(cor_matrix)
## preciom areaconst banios habitaciones D1
## preciom 1.00000000 0.73134800 0.52333570 0.32270961 -0.01425409
## areaconst 0.73134800 1.00000000 0.46281525 0.37533231 -0.00920381
## banios 0.52333570 0.46281525 1.00000000 0.57553136 0.08429962
## habitaciones 0.32270961 0.37533231 0.57553136 1.00000000 -0.02480392
## D1 -0.01425409 -0.00920381 0.08429962 -0.02480392 1.00000000
## D2 0.29951838 0.28029758 0.20753386 0.07928219 -0.41526744
## D3 0.39869798 0.22819612 0.20726548 0.04893678 -0.15383315
## D4 NA NA NA NA NA
## D2 D3 D4
## preciom 0.29951838 0.39869798 NA
## areaconst 0.28029758 0.22819612 NA
## banios 0.20753386 0.20726548 NA
## habitaciones 0.07928219 0.04893678 NA
## D1 -0.41526744 -0.15383315 NA
## D2 1.00000000 -0.22259469 NA
## D3 -0.22259469 1.00000000 NA
## D4 NA NA 1
#Heatmap de correlaciones
library(plotly)
fig <- plot_ly(z = cor_matrix,
x = colnames(cor_matrix),
y = colnames(cor_matrix),
type = "heatmap",
colorscale = "Viridis")
fig
Como se muestra en el análisis de correlaciones entre variables. No hay correlaciones mayores a 0.8 entre las variables independientes. Por otro lado, la correlación mas alta con la variable dependiente la tiene el área construida con un 0.7313
No es necesario eliminar variables.
modelo <- lm(preciom ~ areaconst + banios + habitaciones + D1 + D2 + D3, data = numeric_vars_modelo)
summary(modelo)
##
## Call:
## lm(formula = preciom ~ areaconst + banios + habitaciones + D1 +
## D2 + D3, data = numeric_vars_modelo)
##
## Residuals:
## Min 1Q Median 3Q Max
## -972.05 -73.02 -15.60 45.95 1064.25
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 30.27964 17.60512 1.720 0.0859 .
## areaconst 0.82657 0.04289 19.274 < 2e-16 ***
## banios 25.83183 5.34112 4.836 1.62e-06 ***
## habitaciones 1.81876 4.10123 0.443 0.6576
## D1 85.86991 17.27886 4.970 8.40e-07 ***
## D2 139.49127 16.13506 8.645 < 2e-16 ***
## D3 330.33116 26.46332 12.483 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 156.9 on 715 degrees of freedom
## Multiple R-squared: 0.6611, Adjusted R-squared: 0.6583
## F-statistic: 232.5 on 6 and 715 DF, p-value: < 2.2e-16
Interpretación de resultados
Intercepto :
Valor: 30.28
Es el precio estimado cuando todas las variables son 0 (probablemente sin sentido práctico, pero necesario matemáticamente).
Variables numéricas
areaconst (β = 0.8266, p < 2e-16 ***) Cada metro cuadrado adicional aumenta el precio en 0.83 unidades. Altamente significativo en el modelo (p < 0.001).
banios (β = 25.83, p = 1.62e-06 ***) Cada baño adicional incrementa el precio en 25.83 unidades. Muy significativo en el modelo(p < 0.001).
habitaciones (β = 1.818, p = 0.6576). No es significativo (p > 0.05), lo que indica que el número de habitaciones no tiene un impacto claro en el precio, al menos después de considerar otras variables.
Variables Dummy (D1, D2, D3) Las variables D1, D2 y D3 correspondientes al estrato de la vivienda:
D1 (β = 85.87, p < 0.001) Si la vivienda pertenece a la categoría D1, su precio aumenta en 85.87 unidades en comparación con la categoría base.
D2 (β = 139.49, p < 0.001) Si pertenece a D2, el precio aumenta en 139.49 unidades en comparación con la base.
D3 (β = 330.33, p < 0.001) Si pertenece a D3, el precio aumenta en 330.33 unidades, lo que indica que D3 tiene el mayor impacto en el precio.
Las tres dummies son altamente significativas, lo que indica que las categorías influyen fuertemente en el precio.
Diagnóstico del Modelo
Errores Residuales Mínimo: -972.05 (subestima el precio en 972 unidades).
Máximo: 1064.25 (sobreestima el precio en 1064 unidades).
Mediana: -15.60 (casi cero, indicando un sesgo pequeño).
Esto sugiere que el modelo funciona bien, pero hay algunas observaciones con grandes errores.
Bondad del Ajuste Multiple R-squared = 0.6611. El modelo explica el 66.11% de la variabilidad en preciom.
Adjusted R-squared = 0.6583 Similar a R² pero ajustado por el número de predictores.
F-statistic: 232.5, p-value < 2.2e-16
Conclusión
El modelo es bueno (R² = 66%), pero no explica el 100% de la variabilidad.
Área construida, baños y las dummies tienen un impacto fuerte en el precio.
El número de habitaciones NO es significativo, por lo que podríamos considerar removerlo.
El modelo tiene errores residuales grandes en algunos casos.
Modelo sin outliers
Dado que el modelo cuenta con un alto error residual, se opta por generar nuevamente el modelo eliminando los outliers identificados en preciom:
# Filtrar outliers en una variable específica
df_clean <- numeric_vars_modelo[numeric_vars_modelo$preciom %in% boxplot.stats(numeric_vars_modelo$preciom)$out == FALSE, ]
modelo_2 <- lm(preciom ~ areaconst + banios + habitaciones + D1 + D2 + D3, data = df_clean)
summary(modelo_2)
##
## Call:
## lm(formula = preciom ~ areaconst + banios + habitaciones + D1 +
## D2 + D3, data = df_clean)
##
## Residuals:
## Min 1Q Median 3Q Max
## -622.87 -65.45 -16.76 47.69 548.59
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 84.40472 12.73622 6.627 6.94e-11 ***
## areaconst 0.56860 0.03416 16.644 < 2e-16 ***
## banios 25.90748 4.01667 6.450 2.12e-10 ***
## habitaciones -1.39409 2.99269 -0.466 0.641
## D1 96.42111 12.19993 7.903 1.09e-14 ***
## D2 152.32565 11.41052 13.350 < 2e-16 ***
## D3 295.97684 20.01952 14.784 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 108.9 on 684 degrees of freedom
## Multiple R-squared: 0.6934, Adjusted R-squared: 0.6907
## F-statistic: 257.8 on 6 and 684 DF, p-value: < 2.2e-16
Con este nuevo modelo entontramos varias ventajas:
El mínimo residual pasa a -622.87, lo que es positivo para el modelo el máximo residual pasa a 548.59, lo que es positivo para el modelo el error estandar disminuye a 108.9 lo que es positivo para el modelo R2 aumento de 66.1% a 69.3% lo que significa que el modelo explica mejor la variabilidad del precio
menos dispersion en los residuos, lo que indica que el modelo se ajusta mejor reducción de los valores extremos en los residuos, ahora el modelo hace predicciones más estables
El modelo mejoró tras eliminar los outliers.
Menor error en la predicción y mayor R².
areaconst, banios y las variables dummy (D1, D2, D3) son altamente significativas.
habitaciones sigue sin ser significativa, por lo que podríamos eliminarla en futuras versiones del modelo.
#Validación simple:
set.seed(123) # Para reproducibilidad
# Proporción de entrenamiento (80%)
train_index <- sample(1:nrow(df_clean), 0.8 * nrow(df_clean))
# Crear datasets de entrenamiento y prueba
train_data <- df_clean[train_index, ]
test_data <- df_clean[-train_index, ]
#generación del modelo
modelo3 <- lm(preciom ~ areaconst + banios + habitaciones + D1 + D2 + D3, data = train_data)
summary(modelo3)
##
## Call:
## lm(formula = preciom ~ areaconst + banios + habitaciones + D1 +
## D2 + D3, data = train_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -590.02 -66.48 -16.92 48.47 544.61
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 87.20171 13.96350 6.245 8.54e-10 ***
## areaconst 0.54090 0.03749 14.430 < 2e-16 ***
## banios 25.01431 4.42398 5.654 2.53e-08 ***
## habitaciones -0.61378 3.27356 -0.187 0.851
## D1 105.91630 13.72108 7.719 5.63e-14 ***
## D2 161.43894 12.73637 12.675 < 2e-16 ***
## D3 302.43038 22.74327 13.298 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 109.6 on 545 degrees of freedom
## Multiple R-squared: 0.6863, Adjusted R-squared: 0.6829
## F-statistic: 198.8 on 6 and 545 DF, p-value: < 2.2e-16
#Predicciones
predicciones <- predict(modelo3, newdata = test_data)
# Instalar paquete si es necesario
#install.packages("Metrics")
library(Metrics)
# Calcular el RMSE (Error Cuadrático Medio)
rmse_value <- rmse(test_data$preciom, predicciones)
# Calcular R²
r2_value <- cor(test_data$preciom, predicciones)^2
# Mostrar métricas
cat("RMSE:", rmse_value, "\n")
## RMSE: 106.648
cat("R²:", r2_value, "\n")
## R²: 0.7200662
#verificacipon de multicolinealidad
#install.packages("car")
library(car)
vif(modelo3)
## areaconst banios habitaciones D1 D2 D3
## 1.450498 2.008308 1.640743 1.542231 1.723697 1.260963
#No hay multicolinealidad preocupante
El RMSE = 106.65 significa que, en promedio, la
diferencia entre los valores predichos y los valores reales de
preciom es 106.65 unidades.
El R² = 0.72 indica que el modelo explica
aproximadamente el 72% de la variabilidad del precio
por metro cuadrado (preciom) en el conjunto de
prueba.
Todas las variables tienen VIF < 5, lo que significa que no hay multicolinealidad significativa.
modelo3$coefficients
## (Intercept) areaconst banios habitaciones D1 D2
## 87.2017078 0.5408996 25.0143075 -0.6137773 105.9163000 161.4389376
## D3
## 302.4303804
#Estimación de precio de vivienda caso 1, estrato 4
#sin tener en uenta numero de habitaciones porque segun el valor p no es siginficativo.
PrecioCasa_1 = 87.2017078 + 0.5408996 * 200 + 25.0143075 * 2 + 105.9163000 * 1
PrecioCasa_1
## [1] 351.3265
#Estimación de precio de vivienda caso 1, estrato 5
PrecioCasa_2 = 87.2017078 + 0.5408996 * 200 + 25.0143075 * 2 + 161.4389376 * 1
PrecioCasa_2
## [1] 406.8492
| Caso 1 estrato 4 | Caso 1 estrato 5 |
|---|---|
| 351.3265 Mill | 406.86 Mill |
Como se muestra en la tabla, es prestamo pre aprobado es adecuado para una casa con las caracteristicas solicitadas en estrato 4. Para estrato 5 es insuficiente la cantidad preaprobada.
A continuación, se muestran las ofertas potenciales que para casas con las características solicitadas en estrato 4:
df__filtrado_clean <- df_filtrado[df_filtrado$preciom %in% boxplot.stats(df_filtrado$preciom)$out == FALSE, ]
#Posibles ofertas de interes
library(dplyr)
df__filtrado_clean <- df__filtrado_clean %>% filter(areaconst > 180, areaconst < 220)
df__filtrado_clean <- df__filtrado_clean %>% filter(banios == 2)
df__filtrado_clean <- df__filtrado_clean %>% filter(estrato == "4")
leaflet(data = df__filtrado_clean) %>%
addTiles() %>% # Agrega un mapa base
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
radius = 4,
color = "blue",
stroke = FALSE,
fillOpacity = 0.8,
popup = ~paste("Zona:", zona, "<br> Precio:", preciom, "<br> Área const:", areaconst, "<br> Baños:", banios)
)
head(df__filtrado_clean)
## # A tibble: 3 × 13
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 4511 Zona N… <NA> 4 275 190 NA 2 3
## 2 1175 Zona N… 02 4 370 216 2 2 0
## 3 1222 Zona N… 02 4 360 216 2 2 4
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
Como se muestra en el mapa, existen tres viviendas que se ajustan a los requerimientos del cliente y al presupuesto preaprobado.
vivienda$id <- as.character(vivienda$id)
vivienda$estrato <- as.character(vivienda$estrato)
df_filtrado2 = vivienda %>%
filter(tipo == "Apartamento", zona == "Zona Sur")
head(df_filtrado2, 3)
## # A tibble: 3 × 13
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 5098 Zona S… 05 4 290 96 1 2 3
## 2 698 Zona S… 02 3 78 40 1 1 2
## 3 8199 Zona S… <NA> 6 875 194 2 5 3
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
A continuación, se muestra el mapa de la ciudad de Cali con las viviendas que pertenecen a ambas categorías
#Generación de mapas
library(leaflet)
leaflet(data = df_filtrado2) %>%
addTiles() %>% # Agrega un mapa base
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
radius = 4,
color = "green",
stroke = FALSE,
fillOpacity = 0.8,
popup = ~paste("Zona:", zona, "<br> Precio:", preciom)
)
Como se muestra en el mapa, hay viviendas que no pertencen a la zona sur, lo que implica que el dataset contiene errores de etiquetado y requiere una actualzación. No obstante, la mayoría de las viviendas si se ubican correctamente en la zona sur de la ciudad por lo que es viable continuar con el estudio requerido.
#Acotación de variables para modelo
df_modelov2 <- df_filtrado2 %>% select(id, preciom, areaconst, estrato, banios, habitaciones, zona)
#Verificación de variables categóricas:
table(df_modelov2$estrato)
##
## 3 4 5 6
## 201 1091 1033 462
table(df_modelov2$zona)
##
## Zona Sur
## 2787
#Tratamietno de variables categóricas para RLM: "Estrato" y "zona"
df_modelov2$D5 = as.numeric(df_modelov2$estrato=="4")
df_modelov2$D6 = as.numeric(df_modelov2$estrato=="5")
df_modelov2$D7 = as.numeric(df_modelov2$estrato=="6")
df_modelov2$D8 = as.numeric(df_modelov2$zona=="Zona Sur")
#Resumen da datos
summary(df_modelov2)
## id preciom areaconst estrato
## Length:2787 Min. : 75.0 Min. : 40.00 Length:2787
## Class :character 1st Qu.: 175.0 1st Qu.: 65.00 Class :character
## Mode :character Median : 245.0 Median : 85.00 Mode :character
## Mean : 297.3 Mean : 97.47
## 3rd Qu.: 335.0 3rd Qu.:110.00
## Max. :1750.0 Max. :932.00
## banios habitaciones zona D5
## Min. :0.000 Min. :0.000 Length:2787 Min. :0.0000
## 1st Qu.:2.000 1st Qu.:3.000 Class :character 1st Qu.:0.0000
## Median :2.000 Median :3.000 Mode :character Median :0.0000
## Mean :2.488 Mean :2.966 Mean :0.3915
## 3rd Qu.:3.000 3rd Qu.:3.000 3rd Qu.:1.0000
## Max. :8.000 Max. :6.000 Max. :1.0000
## D6 D7 D8
## Min. :0.0000 Min. :0.0000 Min. :1
## 1st Qu.:0.0000 1st Qu.:0.0000 1st Qu.:1
## Median :0.0000 Median :0.0000 Median :1
## Mean :0.3706 Mean :0.1658 Mean :1
## 3rd Qu.:1.0000 3rd Qu.:0.0000 3rd Qu.:1
## Max. :1.0000 Max. :1.0000 Max. :1
#install.packages("naniar") # Si no lo tienes
#install.packages("visdat")
library(naniar)
library(visdat)
# Contar valores faltantes por columna
colSums(is.na(df_modelov2))
## id preciom areaconst estrato banios habitaciones
## 0 0 0 0 0 0
## zona D5 D6 D7 D8
## 0 0 0 0 0
# Visualización gráfica de los valores faltantes
vis_miss(df_modelov2) # Mapa visual de datos faltantes
Tal y como se hizo en el ejercicio de la “vivienda 1” se utiliza la estandarización de la variable categórica de estrato en variables binarias D5, D6, D7 las cuales pueden tomar valores de 0 o 1 según corresponda el estrato:
Si el estrato es 3, las 3 variables valen 0
Si el estratoes 4, D5 vale 1, D6 y D7 valen 0
Si el estrato es 5, D6 vale 1, D5 y D7 valen 0
Si el estrato es 6, D7 vale 1, D5 y D6 valen 0
Adicionalmente, se hace el análisis de datos faltantes para las variables escogidas para el modelo (preciom, areaconst, estrato, banios, habitaciones, D5, D6, D7).
Como se muestra en la figura, no hay datos faltantes en el Dataset.
Detección de outliers
library(ggplot2)
#Verificación de Outliers
# Crear un boxplot para cada variable numérica
df_modelov2 %>%
tidyr::pivot_longer(cols = where(is.numeric)) %>%
ggplot(aes(x = name, y = value)) +
geom_boxplot(outlier.colour = "orange", outlier.shape = 16) +
theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
labs(title = "Detección de Outliers por Variable", x = "Variable", y = "Valor")
En la detección de outliers encontramos que existen
datos atípicos en las variables de preciom y área construida, sin
embargo, como pueden haber precios de vivienda con características
especiales se opta inicialmente por mantener estos datos atípicos.
Detección de correlaciones entre variables cuantitativas
#Detección de correlación entre variables cuantitativas
library(GGally)
library(ggplot2)
numeric_vars_modelov2 <- df_modelov2[, sapply(df_modelov2, is.numeric)]
ggpairs(numeric_vars_modelov2,
lower = list(continuous = wrap("points")), # Gráficos de dispersión en la parte inferior
upper = list(continuous = wrap("cor", size = 5)), # Correlaciones en la parte superior
diag = list(continuous = wrap("densityDiag"))) # Distribución en la diagonal
#Calcular matriz de correlaciones
cor_matrix <- cor(numeric_vars_modelov2, use = "complete.obs", method = "pearson")
print(cor_matrix)
## preciom areaconst banios habitaciones D5
## preciom 1.00000000 0.75799553 0.71967045 0.33175381 -0.3923678
## areaconst 0.75799553 1.00000000 0.66181787 0.43396077 -0.3284779
## banios 0.71967045 0.66181787 1.00000000 0.51492273 -0.3090829
## habitaciones 0.33175381 0.43396077 0.51492273 1.00000000 -0.1888011
## D5 -0.39236782 -0.32847787 -0.30908286 -0.18880109 1.0000000
## D6 -0.01435781 0.06966305 0.05132851 0.09935863 -0.6155096
## D7 0.69177000 0.44681374 0.51990447 0.15701181 -0.3575271
## D8 NA NA NA NA NA
## D6 D7 D8
## preciom -0.01435781 0.6917700 NA
## areaconst 0.06966305 0.4468137 NA
## banios 0.05132851 0.5199045 NA
## habitaciones 0.09935863 0.1570118 NA
## D5 -0.61550958 -0.3575271 NA
## D6 1.00000000 -0.3420935 NA
## D7 -0.34209354 1.0000000 NA
## D8 NA NA 1
#Heatmap de correlaciones
library(plotly)
fig <- plot_ly(z = cor_matrix,
x = colnames(cor_matrix),
y = colnames(cor_matrix),
type = "heatmap",
colorscale = "Viridis")
fig
Como se muestra en el análisis de correlaciones entre variables. No hay correlaciones mayores a 0.8 entre las variables independientes. Por otro lado, la correlación mas alta con la variable dependiente la tiene el área construida con un 0.7579
No es necesario eliminar variables.
modeloV2 <- lm(preciom ~ areaconst + banios + habitaciones + D5 + D6 + D7, data = numeric_vars_modelov2)
summary(modeloV2)
##
## Call:
## lm(formula = preciom ~ areaconst + banios + habitaciones + D5 +
## D6 + D7, data = numeric_vars_modelov2)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1320.37 -37.14 -2.45 34.70 912.67
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -1.38858 10.37844 -0.134 0.893575
## areaconst 1.57073 0.04608 34.088 < 2e-16 ***
## banios 48.10759 2.94978 16.309 < 2e-16 ***
## habitaciones -15.20161 3.32917 -4.566 5.18e-06 ***
## D5 26.12865 7.23056 3.614 0.000307 ***
## D6 58.11575 7.46629 7.784 9.85e-15 ***
## D7 236.38216 9.22281 25.630 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 92.31 on 2780 degrees of freedom
## Multiple R-squared: 0.7683, Adjusted R-squared: 0.7678
## F-statistic: 1536 on 6 and 2780 DF, p-value: < 2.2e-16
Interpretación de resultados
Intercepto :
Valor: -1.39
Es el precio estimado cuando todas las variables son 0 (probablemente sin sentido práctico, pero necesario matemáticamente).
Variables numéricas
areaconst (β = 1.57) Cada metro cuadrado adicional aumenta el precio en 1.57 unidades. Altamente significativo en el modelo.
banios (β = 48.11) Cada baño adicional incrementa el precio en 48.11 unidades. Muy significativo en el modelo.
habitaciones (β = -15.20, p < 0.0001). Las habitaciones generan un efecto negativo. Son significativas, cada habitación adicional disminuye el precio en 15.20 unidades.
Variables Dummy (D5, D6, D7) Las variables D5, D6 y D7 correspondientes al estrato de la vivienda:
D5 (β = 26.13) Si la vivienda pertenece a la categoría D5, su precio aumenta en 26.13 unidades en comparación con la categoría base.
D6 (β = 58.12) Si pertenece a D6, el precio aumenta en 58.12 unidades en comparación con la base.
D7 (β = 236.38) Si pertenece a D7, el precio aumenta en 236.38 unidades, lo que indica que D7 tiene el mayor impacto en el precio.
Las tres dummies son altamente significativas, lo que indica que las categorías influyen fuertemente en el precio.
Diagnóstico del Modelo
Errores Residuales Mínimo: -1320.37 (subestima el precio en 1320 unidades).
Máximo: 912.67 (sobreestima el precio en 912 unidades).
Mediana: -2.45 (casi cero, indicando un sesgo pequeño).
Esto sugiere que el modelo funciona bien, pero hay algunas observaciones con grandes errores.
Bondad del Ajuste Multiple R-squared = 0.7683. El modelo explica el 76.83% de la variabilidad en preciom.
Adjusted R-squared = 0.7678 Similar a R² pero ajustado por el número de predictores.
F-statistic: 1536, p-value < 2.2e-16
Conclusión
El modelo es bueno (R² = 77%), pero no explica el 100% de la variabilidad.
Área construida, baños y las dummies tienen un impacto fuerte en el precio.
El coeficiente negativo para la variable “habitaciones” puede reflejar un problema estructural en el modelo y teniendo en cuenta que la variable “areaconst” ya involucra el tamaño de los apartamentos, consideramos removerla.
El modelo tiene errores residuales grandes en algunos casos.
Modelo sin outliers
Dado que el modelo cuenta con un alto error residual, se opta por generar nuevamente el modelo eliminando los outliers identificados en preciom:
# Filtrar outliers en una variable específica
df_cleanv2 <- numeric_vars_modelov2[numeric_vars_modelov2$preciom %in% boxplot.stats(numeric_vars_modelov2$preciom)$out == FALSE, ]
modelo_2v2 <- lm(preciom ~ areaconst + banios + habitaciones + D5 + D6 + D7, data = df_cleanv2)
summary(modelo_2v2)
##
## Call:
## lm(formula = preciom ~ areaconst + banios + habitaciones + D5 +
## D6 + D7, data = df_cleanv2)
##
## Residuals:
## Min 1Q Median 3Q Max
## -671.33 -33.52 -5.65 32.45 246.30
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 20.28990 6.57187 3.087 0.00204 **
## areaconst 0.80702 0.03707 21.773 < 2e-16 ***
## banios 30.90740 2.19002 14.113 < 2e-16 ***
## habitaciones 5.08065 2.18526 2.325 0.02015 *
## D5 41.13509 4.42830 9.289 < 2e-16 ***
## D6 89.93670 4.63006 19.425 < 2e-16 ***
## D7 191.39920 5.94258 32.208 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 55.78 on 2517 degrees of freedom
## Multiple R-squared: 0.6816, Adjusted R-squared: 0.6809
## F-statistic: 898.2 on 6 and 2517 DF, p-value: < 2.2e-16
Con este nuevo modelo entontramos varias ventajas:
Errores más pequeños y estables (menor dispersión de los residuos).
Predicciones más precisas (error estándar menor).
Variables altamente significativas, excepto “habitaciones”, que sigue siendo débil.
El modelo mejoró tras eliminar los outliers.
#Validación simple:
set.seed(123) # Para reproducibilidad
# Proporción de entrenamiento (80%)
train_indexv2 <- sample(1:nrow(df_cleanv2), 0.8 * nrow(df_cleanv2))
# Crear datasets de entrenamiento y prueba
train_datav2 <- df_cleanv2[train_indexv2, ]
test_datav2 <- df_cleanv2[-train_indexv2, ]
#generación del modelo
modelo3v2 <- lm(preciom ~ areaconst + banios + habitaciones + D5 + D6 + D7, data = train_datav2)
summary(modelo3v2)
##
## Call:
## lm(formula = preciom ~ areaconst + banios + habitaciones + D5 +
## D6 + D7, data = train_datav2)
##
## Residuals:
## Min 1Q Median 3Q Max
## -601.12 -35.09 -6.06 33.63 225.88
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 22.15875 7.52379 2.945 0.00326 **
## areaconst 0.72268 0.03972 18.196 < 2e-16 ***
## banios 31.96230 2.44448 13.075 < 2e-16 ***
## habitaciones 5.46320 2.50275 2.183 0.02916 *
## D5 42.77533 4.94058 8.658 < 2e-16 ***
## D6 92.15229 5.17717 17.800 < 2e-16 ***
## D7 200.41620 6.81867 29.392 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 56.74 on 2012 degrees of freedom
## Multiple R-squared: 0.6784, Adjusted R-squared: 0.6775
## F-statistic: 707.5 on 6 and 2012 DF, p-value: < 2.2e-16
#Predicciones
prediccionesv2 <- predict(modelo3v2, newdata = test_datav2)
# Instalar paquete si es necesario
#install.packages("Metrics")
library(Metrics)
# Calcular el RMSE (Error Cuadrático Medio)
rmse_valuev2 <- rmse(test_datav2$preciom, prediccionesv2)
# Calcular R²
r2_valuev2 <- cor(test_datav2$preciom, prediccionesv2)^2
# Mostrar métricas
cat("RMSE:", rmse_valuev2, "\n")
## RMSE: 52.2533
cat("R²:", r2_valuev2, "\n")
## R²: 0.6908462
#verificacipon de multicolinealidad
#install.packages("car")
library(car)
vif(modelo3v2)
## areaconst banios habitaciones D5 D6 D7
## 1.597013 1.996779 1.412145 3.757510 4.032045 2.296626
#No hay multicolinealidad preocupante
Menor error estándar y RMSE, lo que indica mejor precisión en las predicciones. Las variables más relevantes siguen siendo “areaconst”, “banios” y las dummies (D5, D6, D7).
modelo3v2$coefficients
## (Intercept) areaconst banios habitaciones D5 D6
## 22.1587483 0.7226794 31.9622984 5.4631966 42.7753320 92.1522864
## D7
## 200.4161995
#Estimación de precio de vivienda caso 2, estrato 5
#sin tener en uenta numero de habitaciones porque segun el valor p no es siginficativo.
PrecioApto_1 = 22.15875 + 0.72268 * 300 + 31.96230 * 3 + 92.15229 * 1
PrecioApto_1
## [1] 427.0019
#Estimación de precio de vivienda caso 2, estrato 6
PrecioApto_2 = 22.15875 + 0.72268 * 300 + 31.96230 * 3 + 200.41620 * 1
PrecioApto_2
## [1] 535.2659
| Caso 2 estrato 5 | Caso 2 estrato 6 |
|---|---|
| 427.0019 Mill | 535.2659 Mill |
Como se muestra en la tabla, el prestamo pre aprobado es adecuado para un apartamento con las caracteristicas solicitadas en estrato 5 y también para estrato 6.
A continuación, se muestran las ofertas potenciales que para apartamentos con las características solicitadas en estrato 5:
df__filtrado_clean2 <- df_filtrado2[df_filtrado2$preciom %in% boxplot.stats(df_filtrado2$preciom)$out == FALSE, ]
#Posibles ofertas de interes
library(dplyr)
df__filtrado_clean2 <- df__filtrado_clean2 %>% filter(areaconst > 260, areaconst < 320)
df__filtrado_clean2 <- df__filtrado_clean2 %>% filter(banios == 3)
df__filtrado_clean2 <- df__filtrado_clean2 %>% filter(estrato == "5")
leaflet(data = df__filtrado_clean2) %>%
addTiles() %>% # Agrega un mapa base
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
radius = 4,
color = "green",
stroke = FALSE,
fillOpacity = 0.8,
popup = ~paste("Zona:", zona, "<br> Precio:", preciom, "<br> Área const:", areaconst, "<br> Baños:", banios)
)
head(df__filtrado_clean2)
## # A tibble: 2 × 13
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 6175 Zona S… 05 5 350 270 3 3 4
## 2 7680 Zona S… 01 5 450 267 3 3 3
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
Como se muestra en el mapa, existen dos viviendas que se ajustan a los requerimientos del cliente y al presupuesto preaprobado.Teniendo en cuenta un rango de area construida superior a los 260m2