Estudio de caso

Enunciado

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) .

Estudio Vivienda 1

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>
  1. Inicialmente se hacen los filtros pertinentes para acotar la base de datos a las viviendas que sean casas en la Zona Norte de la ciudad:
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.

  1. A continuación se presenta el Análisis Exploratorio de Datos:
#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.

  1. Estimación del modelo de regresión lineal múltiple por método MCO
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.

    • Un R² de 0.66 es moderadamente bueno en modelos de precios de bienes raíces.
  • Adjusted R-squared = 0.6583 Similar a R² pero ajustado por el número de predictores.

    • La diferencia es mínima, lo que indica que las variables agregadas sí aportan información.
  • F-statistic: 232.5, p-value < 2.2e-16

    • El modelo es globalmente significativo, es decir, al menos una de las variables explica el precio.

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.

  1. Validación del modelo por validación simple
#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.

  1. Predicción de precio de vivienda según solicitud:
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.

  1. Potenciales ofertas

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.

Estudio Vivienda 2

1. Inicialmente se hacen los filtros pertinentes para acotar la base de datos a las viviendas que sean Apartamentos en la Zona Sur de la ciudad:

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.

  1. A continuación se presenta el Análisis Exploratorio de Datos:
#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.

  1. Estimación del modelo de regresión lineal múltiple por método MCO
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.

    • Un R² de 0.7683 es moderadamente bueno en modelos de precios de bienes raíces.
  • Adjusted R-squared = 0.7678 Similar a R² pero ajustado por el número de predictores.

    • La diferencia es mínima, lo que indica que las variables agregadas sí aportan información.
  • F-statistic: 1536, p-value < 2.2e-16

    • El modelo es globalmente significativo, es decir, las variables explicativas tienen una mejor capacidad para predecir el precio

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.

  1. Validación del modelo por validación simple
#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).

  1. Predicción de precio de vivienda según solicitud:
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.

  1. Potenciales ofertas

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