Introducción

El presente proyecto surge de la necesidad de nuestra cliente Maria gerente de la inmobiliaria C&A, conforme a la solicitud recibida para la compra de dos viviendas con requisitos especificos por parte del usuario final.

Se procedió a realizar todas las etapas de ciclo de vida de la información almacenada en la base, realizando análisis exploratorio de datos, validación de datos faltantes, análisis de correlación e identificación de multicolinealidad, validación de supuestos y coeficientes, propuestas de mejoras al modelo, gráficos explicativos, desarrollo de modelos de regresión enfocados a la predicción de los valores de inmuebles y la identificación de posibles ofertas basadas en las predicciones.

En este desarrollo se resume el informe ejecutivo, y los anexos o evidencias del trabajo desarrollado con gráficos y validaciones que los respaldan.

Resumen ejecutivo:

La base de datos de la ciudad de Cali cuenta con 8319 inmuebles, los cuales tienen la siguiente distribución por tipo:

Distribución por Zonas

Distribución de inmuebles por zonas ordenados por número.

Distribución de Viviendas por Estrato

Distribución de inmuebles por estrato ordenados por número.

Precios estimados según la solicitud de Vivienda 1

Precios estimados según la solicitud de Vivienda 2

Identificación de inmuebles teniendo en cuenta las predicciones y ejecutando procesos manuales para la busqueda en Vivienda 1

Para este punto es importante resaltar que, se hicieron modificaciones en las detecciones dada la baja volumetría de la base y para permitir identificar inmuebles aproximados a la necesidad del usuario. Se identificaron las siguiente propiedades con los siguientes atributos:

id zona piso preciom areaconst parqueaderos banios habitaciones

7471 Zona Norte 02 330 240 2 4 4

1189 Zona Norte 02 330 400 2 5 8

952 Zona Norte 02 330 275 2 3 5

1108 Zona Norte 02 330 260 1 3 4

3043 Zona Norte 02 330 275 2 3 5

Identificación de inmuebles teniendo en cuenta las predicciones y ejecutando procesos automáticos(predict) para la busqueda en Vivienda 1

Para este punto es importante resaltar que, se hicieron modificaciones en las detecciones dada la baja volumetría de la base y para permitir identificar inmuebles aproximados a la necesidad del usuario. Se identificaron las siguiente propiedades con los siguientes atributos:

id zona piso preciom areaconst parqueaderos banios habitaciones

4511 Zona Norte 01 275 190 1 2 3

Identificación de inmuebles teniendo en cuenta las predicciones y ejecutando procesos manuales para la busqueda en Vivienda 2

Para este punto es importante resaltar que, se hicieron modificaciones en las detecciones dada la baja volumetría de la base y para permitir identificar inmuebles aproximados a la necesidad del usuario. Se identificaron las siguiente propiedades con los siguientes atributos:

id zona piso preciom areaconst parqueaderos banios habitaciones

7512 Zona Sur 02 670 300 3 5 6

7182 Zona Sur 08 730 573 3 8 5

Identificación de inmuebles teniendo en cuenta las predicciones y ejecutando procesos automáticos(predict) para la busqueda en Vivienda 2

Para este punto es importante resaltar que, se hicieron modificaciones en las detecciones dada la baja volumetría de la base y para permitir identificar inmuebles aproximados a la necesidad del usuario. Se identificaron las siguiente propiedades con los siguientes atributos:

id zona piso preciom areaconst parqueaderos banios habitaciones

6868 Zona Sur 03 370 300 3 6 5

7512 Zona Sur 02 670 300 3 5 6

Los residuos no siguen una distribución normal.
Presencia de heterocedasticidad (según la prueba de Breusch-Pagan).
Autocorrelación positiva en los residuos (según la prueba de Durbin-Watson).

Conclusión

El presente proyecto permitió ralizar predicciones sobre los constos de inmuebles según la necesidad espuesta por C&A, además, basadandose en las predicciones y los parametros especificos del requerimiento se identificaron casas y apartamentos que sirven para proponerle al cliente en cuestión.

Por otra parte, se realizaron las validaciones de supuestos, coeficientes y se describió como mejorar el modelo para tener mejor rendimiento y que este se vea reflejado en las métricas de desempeño. Se trabajó de forma independiente según lo especificado en la necesidad correspondiente a zonas para que los valores de imputación y las lógicas implementadas no causarán errores en la medición y evaluación del modelo.

Se adjuntan anexos de todas las fases realizadas, distribuidas por tipo y zona.

Anexo Del proyecto.

Analisis exploratorio de la base.

Identificamos los atributos y numero de registrpos que componen la base.

dim(vivienda)
## [1] 8322   13

Identificamos la estructura de la base.

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>

Resumen estadístico de los atributos.

summary(vivienda)
##        id           zona               piso              estrato     
##  Min.   :   1   Length:8322        Length:8322        Min.   :3.000  
##  1st Qu.:2080   Class :character   Class :character   1st Qu.:4.000  
##  Median :4160   Mode  :character   Mode  :character   Median :5.000  
##  Mean   :4160                                         Mean   :4.634  
##  3rd Qu.:6240                                         3rd Qu.:5.000  
##  Max.   :8319                                         Max.   :6.000  
##  NA's   :3                                            NA's   :3      
##     preciom         areaconst       parqueaderos        banios      
##  Min.   :  58.0   Min.   :  30.0   Min.   : 1.000   Min.   : 0.000  
##  1st Qu.: 220.0   1st Qu.:  80.0   1st Qu.: 1.000   1st Qu.: 2.000  
##  Median : 330.0   Median : 123.0   Median : 2.000   Median : 3.000  
##  Mean   : 433.9   Mean   : 174.9   Mean   : 1.835   Mean   : 3.111  
##  3rd Qu.: 540.0   3rd Qu.: 229.0   3rd Qu.: 2.000   3rd Qu.: 4.000  
##  Max.   :1999.0   Max.   :1745.0   Max.   :10.000   Max.   :10.000  
##  NA's   :2        NA's   :3        NA's   :1605     NA's   :3       
##   habitaciones        tipo              barrio             longitud     
##  Min.   : 0.000   Length:8322        Length:8322        Min.   :-76.59  
##  1st Qu.: 3.000   Class :character   Class :character   1st Qu.:-76.54  
##  Median : 3.000   Mode  :character   Mode  :character   Median :-76.53  
##  Mean   : 3.605                                         Mean   :-76.53  
##  3rd Qu.: 4.000                                         3rd Qu.:-76.52  
##  Max.   :10.000                                         Max.   :-76.46  
##  NA's   :3                                              NA's   :3       
##     latitud     
##  Min.   :3.333  
##  1st Qu.:3.381  
##  Median :3.416  
##  Mean   :3.418  
##  3rd Qu.:3.452  
##  Max.   :3.498  
##  NA's   :3

Identificación de datos nulos.

sum(is.na(vivienda))
## [1] 4275

Identificación de datos nulos por atributo.

apply(X = is.na(vivienda), MARGIN = 2, FUN = sum)
##           id         zona         piso      estrato      preciom    areaconst 
##            3            3         2638            3            2            3 
## parqueaderos       banios habitaciones         tipo       barrio     longitud 
##         1605            3            3            3            3            3 
##      latitud 
##            3

Identificamos numeros de viviendas por tipo

table(vivienda$tipo)
## 
## Apartamento        Casa 
##        5100        3219

Identificamos numero de viviendas por zona.

table(vivienda$zona)
## 
##  Zona Centro   Zona Norte   Zona Oeste Zona Oriente     Zona Sur 
##          124         1920         1198          351         4726

Desarrollo de la actividad: Casas - zona norte.

1. Realice un filtro a la base de datos e incluya solo las ofertas de : base1: casas, de la zona norte de la ciudad. Presente los primeros 3 registros de las bases y algunas tablas que comprueben la consulta. (Adicional un mapa con los puntos de las bases. Discutir si todos los puntos se ubican en la zona correspondiente o se presentan valores en otras zonas, por que?).

Se puede evidenciar en el gráfico de mapa, que no todos los puntos corresponden a la zona norte, aunque en la base así lo sugiere. Esto puede identificarse por el comportamiento de los puntos en el mapa basados en los atributos Latitud y Longitud.

Filtramos solo las casas de la zona norte y procedemos a mostrar los tres primeros registros

zona_selec = subset(vivienda, tipo == "Casa" & zona == "Zona Norte")
zona_selec[1:3,]
## # A tibble: 3 × 13
##      id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
##   <dbl> <chr>   <chr>   <dbl>   <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>

Validamos numero registros para la zona y tipo seleccionados

df= table(zona_selec$zona, zona_selec$tipo)
head(df)
##             
##              Casa
##   Zona Norte  722

Valores faltantes base: casas zona norte

apply(X = is.na(zona_selec), MARGIN = 2, FUN = sum)
##           id         zona         piso      estrato      preciom    areaconst 
##            0            0          372            0            0            0 
## parqueaderos       banios habitaciones         tipo       barrio     longitud 
##          287            0            0            0            0            0 
##      latitud 
##            0

Mapa de viviendas en la zona norte.

En el siguiente mapa, se puede evidenciar que no todas las viviendas de la zona norte se encuentran georreferenciadas correctamente dentro de una misma zona en la ciudad de Calí.

mapa_norte = leaflet(data = zona_selec) %>%
  addTiles() %>%
  addCircleMarkers(lng = ~longitud, lat = ~latitud, 
                   radius = 2,
                   color = "red",
                   fillOpacity = 0.7,
                   stroke = FALSE)
mapa_norte

Análisis e imputaciones

Teniendo en cuenta que en el análisis exploratorio de la zona norte se detectaron valores faltantes en las variables Piso y parqueaderos, se procederá a realizar las imputaciones correspondientes. Por otra parte, cabe resaltar que piso es una variable ordinal, por consiguiente, se tratará como variable categorica.

Convertimos el campo piso y estrato a factor ordenado, dado a que es una variable ordinal y las variables zona, tipo, barrio quedan como factores nominales, dado que, no tienen orden específico.

zona_selec$piso <- factor(
  zona_selec$piso,
  ordered = TRUE
)

zona_selec$estrato <- factor(
  zona_selec$estrato,
  ordered = TRUE
)
zona_selec$zona   <- factor(zona_selec$zona)
zona_selec$tipo   <- factor(zona_selec$tipo)
zona_selec$barrio <- factor(zona_selec$barrio)
str(zona_selec)
## spc_tbl_ [722 × 13] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ id          : num [1:722] 1209 1592 4057 4460 6081 ...
##  $ zona        : Factor w/ 1 level "Zona Norte": 1 1 1 1 1 1 1 1 1 1 ...
##  $ piso        : Ord.factor w/ 5 levels "01"<"02"<"03"<..: 2 2 2 2 2 2 2 3 NA NA ...
##  $ estrato     : Ord.factor w/ 4 levels "3"<"4"<"5"<"6": 3 3 4 2 3 2 3 3 1 1 ...
##  $ preciom     : num [1:722] 320 780 750 625 750 600 420 490 230 190 ...
##  $ areaconst   : num [1:722] 150 380 445 355 237 160 200 118 160 435 ...
##  $ parqueaderos: num [1:722] 2 2 NA 3 2 1 4 2 NA NA ...
##  $ banios      : num [1:722] 4 3 7 5 6 4 4 4 2 0 ...
##  $ habitaciones: num [1:722] 6 3 6 5 6 5 5 4 3 0 ...
##  $ tipo        : Factor w/ 1 level "Casa": 1 1 1 1 1 1 1 1 1 1 ...
##  $ barrio      : Factor w/ 103 levels "acopi","alameda del río",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ longitud    : num [1:722] -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num [1:722] 3.48 3.49 3.39 3.41 3.37 ...
##  - 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>
# Verificamos el éxito de la imputación.
sum(is.na(zona_selec$piso))
## [1] 372
sum(is.na(zona_selec$parqueaderos))
## [1] 287

Aplicampos el metodo Knn para la imputación, teniendo en cuenta los tipos de variable y el numero de K=5.

# Aplicamos imputación KNN asegurando que 'zona' y 'estrato' no sean modificadas
zona_selec <- kNN(
  data = zona_selec, 
  variable = c("piso", "parqueaderos"),  # Solo imputamos estas
  dist_var = c("preciom", "banios", "habitaciones", "areaconst"),  # Variables numéricas para distancia
  k = 5
)

# Eliminamos columnas auxiliares .imp
zona_selec <- zona_selec[, !grepl(".imp$", colnames(zona_selec))]
# Verificamos el éxito de la imputación.
sum(is.na(zona_selec$piso))
## [1] 0
sum(is.na(zona_selec$parqueaderos))
## [1] 0

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

Precio vs Área Construida.

p1 <- ggplot(zona_selec, aes(x = areaconst, y = preciom)) +
  geom_point(alpha = 0.6, color = "blue") +
  labs(title = "Relación entre Precio y Área Construida",
       x = "Área Construida (m²)",
       y = "Precio de la vivienda (millones)") +
  theme_minimal()

# Convertir a gráfico interactivo con plotly
ggplotly(p1)

Precio vs Estrato.

p2 <- ggplot(zona_selec, aes(x = factor(estrato), y = preciom)) +
  geom_boxplot(fill = "skyblue", alpha = 0.7) +
  labs(title = "Distribución del Precio según el Estrato",
       x = "Estrato",
       y = "Precio de la vivienda (millones)") +
  theme_minimal()

ggplotly(p2)

Precio vs Número de Baños.

p3 <- ggplot(zona_selec, aes(x = factor(banios), y = preciom)) +
  geom_boxplot(fill = "lightgreen", alpha = 0.7) +
  labs(title = "Distribución del Precio según Número de Baños",
       x = "Número de Baños",
       y = "Precio de la vivienda (millones)") +
  theme_minimal()

ggplotly(p3)

Precio vs Número de Habitaciones.

p4 <- ggplot(zona_selec, aes(x = factor(habitaciones), y = preciom)) +
  geom_boxplot(fill = "coral", alpha = 0.7) +
  labs(title = "Distribución del Precio según Número de Habitaciones",
       x = "Número de Habitaciones",
       y = "Precio de la vivienda (millones)") +
  theme_minimal()

ggplotly(p4)

Precio según la Zona.

p5 <- ggplot(zona_selec, aes(x = zona, y = preciom, fill = zona)) +
  geom_boxplot(alpha = 0.7) +
  labs(title = "Distribución del Precio según la Zona",
       x = "Zona",
       y = "Precio de la vivienda (millones)") +
  theme_minimal() +
  theme(legend.position = "none") # Oculta la leyenda para evitar redundancia

ggplotly(p5)
# Seleccionamos solo las variables numéricas
vivienda_numeric <- zona_selec %>%
  select(preciom, areaconst, banios,parqueaderos, habitaciones)

# Calculamos la matriz de correlación
cor_matrix <- cor(vivienda_numeric, use = "complete.obs")

# Mapa de calor de correlaciones
corrplot(cor_matrix, method = "color", type = "upper",
         tl.cex = 0.8, tl.srt = 45, addCoef.col = "black", number.cex = 0.7)

3. Estime un modelo de regresión lineal múltiple con las variables del punto anterior (precio = f(área construida, estrato, número de cuartos, número de parqueaderos, número de baños ) ) e interprete los coeficientes si son estadísticamente significativos. Las interpretaciones deber están contextualizadas y discutir si los resultados son lógicos. Adicionalmente interprete el coeficiente R2 y discuta el ajuste del modelo e implicaciones (que podrían hacer para mejorarlo).

Para esto, es importante trabajar sobre las variables aplicando dummies a las categóricas estrato y zona; luego calculamos el VIF para detectar multicolinealidad teniendo en cuenta las siguientes reglas: VIF >= 10, Alta colinealidad. VIF >= 5, Colinealidad Media. VIF < 5, Podemos implementar estas variables en nuestro modelo.

# Convertimos 'tipo' y 'estrato' en variables dummy
zona_selec <- dummy_cols(zona_selec, select_columns = c("tipo", "estrato"), remove_selected_columns = TRUE)
head(zona_selec)
##     id       zona piso preciom areaconst parqueaderos banios habitaciones
## 1 1209 Zona Norte   02     320       150            2      4            6
## 2 1592 Zona Norte   02     780       380            2      3            3
## 3 4057 Zona Norte   02     750       445            3      7            6
## 4 4460 Zona Norte   02     625       355            3      5            5
## 5 6081 Zona Norte   02     750       237            2      6            6
## 6 7824 Zona Norte   02     600       160            1      4            5
##   barrio  longitud latitud tipo_Casa estrato_3 estrato_4 estrato_5 estrato_6
## 1  acopi -76.51341 3.47968         1         0         0         1         0
## 2  acopi -76.51674 3.48721         1         0         0         1         0
## 3  acopi -76.52950 3.38527         1         0         0         0         1
## 4  acopi -76.53179 3.40590         1         0         1         0         0
## 5  acopi -76.54044 3.36862         1         0         0         1         0
## 6  acopi -76.55210 3.42125         1         0         1         0         0

Calculamos el VIF.

Validamos la colinealidad perfecta, excluimos tipo por ya estar filtrado en la base y estrato 6 ya que se puede calcular con otros estratos

# Verificar colinealidad perfecta
alias(lm(preciom ~ areaconst + habitaciones + parqueaderos + banios + 
          estrato_3 + estrato_4 + estrato_5 + estrato_6, 
          data = zona_selec))$Complete
##           (Intercept) areaconst habitaciones parqueaderos banios estrato_3
## estrato_6  1           0         0            0            0     -1       
##           estrato_4 estrato_5
## estrato_6 -1        -1

Calculamos el VIF.

# Definir el modelo solo con las variables explicativas
modelo_vif <- lm(preciom ~ areaconst + habitaciones + parqueaderos + banios + estrato_3 + estrato_4 + estrato_5, data = zona_selec)

# Calcular el VIF
vif(modelo_vif)
##    areaconst habitaciones parqueaderos       banios    estrato_3    estrato_4 
##     1.552373     1.650502     1.393091     2.048797     4.547384     3.240641 
##    estrato_5 
##     3.821439

Modelo de regresión.

modelo <- lm(preciom ~ areaconst + habitaciones + parqueaderos + banios + 
              estrato_3 + estrato_4 + estrato_5, 
              data = zona_selec)

# Ver resumen del modelo
summary(modelo)
## 
## Call:
## lm(formula = preciom ~ areaconst + habitaciones + parqueaderos + 
##     banios + estrato_3 + estrato_4 + estrato_5, data = zona_selec)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -892.91  -69.63  -14.36   40.98 1024.21 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   336.05814   30.28062  11.098  < 2e-16 ***
## areaconst       0.78532    0.04266  18.411  < 2e-16 ***
## habitaciones    0.66131    4.02282   0.164 0.869471    
## parqueaderos   30.87048    5.53324   5.579 3.43e-08 ***
## banios         18.94654    5.37561   3.525 0.000451 ***
## estrato_3    -317.27805   26.02820 -12.190  < 2e-16 ***
## estrato_4    -241.51109   24.73335  -9.765  < 2e-16 ***
## estrato_5    -192.59861   23.08885  -8.342 3.77e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 153.7 on 714 degrees of freedom
## Multiple R-squared:  0.6753, Adjusted R-squared:  0.6721 
## F-statistic: 212.1 on 7 and 714 DF,  p-value: < 2.2e-16

4. Realice la validación de supuestos del modelo e interprete los resultados (no es necesario corregir en caso de presentar problemas, solo realizar sugerencias de que se podría hacer).

Interpretación de los coeficientes.

  • Multiple R-squared: Un 67.53%, indica que el modelo tiene buen poder de predicción, pero hay margen para mejorarlo.

  • Adjusted R-squared : 67.21%, significa que no hay muchas variables innecesarias en el modelo.

  • Intercepto: Cuando todas las variables son cero, el precio base es de 336.06 millones.

  • Área construida: Por cada metro cuadrado adicional el precio aumenta en 0.785 millones, es altamente significativa.

  • Habitaciones: No es significativa, no tiene impacto claro en el precio.

  • Parqueaderos: Por cada parqueadero adicional el precio aumenta en 30.87 millones, es altamente significativa.

  • Baños: Por cada baño adicional el precio aumenta en 18.95 millones, es altamente significativa.

  • Los coeficientes de estrato_3, estrato_4 y estrato_5 son negativos; esto indica que vivir en un estrato mas bajo reduce significativamente el precio.

  • Como el p-valor es < 2.2e-16, podemos rechazar la hipótesis nula de que todas las variables sean 0 (lo que indica que el modelo tiene validez estadística).

¿Cómo mejorar el modelo?

  • Se podría eliminar el atributo habitaciones, dado que, su p-valor es 0.869 indicando que no tiene relación clara con el precio.

  • Transformaciones logaritmicas.

Validación de los supuestos del modelo.

par(mfrow=c(2,2))
plot(modelo)

Hipótesis del shapiro.test:

  • Si el p-valor es mayor que el nivel de significancia del 5%, no se rechaza la hipótesis nula, indicando que los residuos son normales.

  • Si el p-valor es menor que el nivel de significancia, se rechaza la hipótesis nula, lo que sugiere que los residuos no son normales.

shapiro.test(resid(modelo))
## 
##  Shapiro-Wilk normality test
## 
## data:  resid(modelo)
## W = 0.83366, p-value < 2.2e-16

El p-valor es menor que el nivel de significancia por tanto se rechaza la hipótesis nula, que establece que los residuos tienen una distribución normal. Es decir, igual que el Anderson-Darling los residuos del modelo no son normales.

Hipótesis del Test de Anderson-Darling:

Si el p-valor es mayor que el nivel de significancia del 5%, no se rechaza la hipótesis nula, indicando que los residuos siguen una distribución normal.

Si el p-valor es menor que el nivel de significancia, se rechaza la hipótesis nula, lo que sugiere que los residuos no son normales.

ad.test(modelo$residuals)
## 
##  Anderson-Darling normality test
## 
## data:  modelo$residuals
## A = 26.922, p-value < 2.2e-16

Dado que el p-valor es menor que un nivel de significancia, se rechaza la hipótesis nula de que los residuos siguen una distribución normal.

Hipótesis del Test de Breusch-Pagan:

Si el p-valor es mayor que el nivel de significancia (por ejemplo, 0.05), no se rechaza la hipótesis nula, indicando homocedasticidad.

Si el p-valor es menor que el nivel de significancia, se rechaza la hipótesis nula, lo que sugiere heterocedasticidad en los residuos.

lmtest::bptest(modelo)
## 
##  studentized Breusch-Pagan test
## 
## data:  modelo
## BP = 135.45, df = 7, p-value < 2.2e-16

Hipótesis del Test de Goldfeld-Quandt:

Si el p-valor es mayor que un nivel de significancia del 5%, NO se rechaza H₀, lo que indica que no hay evidencia de heterocedasticidad.

Si el p-valor es menor que el nivel de significancia del 5%, SE rechaza H₀, lo que sugiere que hay evidencia de heterocedasticidad.

lmtest::gqtest(modelo)
## 
##  Goldfeld-Quandt test
## 
## data:  modelo
## GQ = 1.1593, df1 = 353, df2 = 353, p-value = 0.08266
## alternative hypothesis: variance increases from segment 1 to 2

Hipótesis del Test de Durbin-Watson:

El estadístico de Durbin-Watson (DW) varía entre 0 y 4:

  • DW ≈ 2: No hay autocorrelación.
  • DW < 2: Hay autocorrelación positiva.
  • DW > 2: Hay autocorrelación negativa.
lmtest::dwtest(modelo)
## 
##  Durbin-Watson test
## 
## data:  modelo
## DW = 1.7211, p-value = 6.634e-05
## alternative hypothesis: true autocorrelation is greater than 0

5. Con el modelo identificado debe predecir el precio de la vivienda con las características de la primera solicitud.

Creamos dos DataFrame con la información de Vivienda1 y agrupada por estrato(4 y 5).

# Crear un dataframe con las características de la Vivienda 1 en estrato 4
vivienda1_estrato4 <- data.frame(
  areaconst = 200,
  habitaciones = 4,
  parqueaderos = 1,
  banios = 2,
  estrato_3 = 0,  # No es estrato 3
  estrato_4 = 1,  # Es estrato 4
  estrato_5 = 0   # No es estrato 5
)

# Crear un dataframe con las características de la Vivienda 1 en estrato 5
vivienda1_estrato5 <- data.frame(
  areaconst = 200,
  habitaciones = 4,
  parqueaderos = 1,
  banios = 2,
  estrato_3 = 0,  # No es estrato 3
  estrato_4 = 0,  # No es estrato 4
  estrato_5 = 1   # Es estrato 5
)

Ejecutamos las predicciones con las viviendas agrupadas por estrato.

Se puede evienciar que, si el estrato es 5, el valor de la propiedad sería de 371.93 millones, por consiguiente, supera el credito preaprobado con valor de 350 millones. Por su parte, en estrato 4 el precio de la vivienda sería de 323.02 millones, lo cuál está en el rango del credito preaprobado.

# Predecir el precio para Vivienda 1 en estrato 4
precio_pred_estrato4 <- predict(modelo, newdata = vivienda1_estrato4)

# Predecir el precio para Vivienda 1 en estrato 5
precio_pred_estrato5 <- predict(modelo, newdata = vivienda1_estrato5)

# Mostrar los resultados
print(paste("Precio estimado para Vivienda 1 en estrato 4:", round(precio_pred_estrato4, 2), "millones"))
## [1] "Precio estimado para Vivienda 1 en estrato 4: 323.02 millones"
print(paste("Precio estimado para Vivienda 1 en estrato 5:", round(precio_pred_estrato5, 2), "millones"))
## [1] "Precio estimado para Vivienda 1 en estrato 5: 371.93 millones"

6. Con las predicciones del modelo sugiera potenciales ofertas que responda a la solicitud de la vivienda 1. Tenga encuentra que la empresa tiene crédito pre-aprobado de máximo 350 millones de pesos. Realice un análisis y presente en un mapa al menos 5 ofertas potenciales que debe discutir.

Para este punto es importante resaltar que, según las predicciones la vivienda deber ser estrato 4; debido a que, el valor está dentro del rango permitido por el credito preaprobado; por consiguiente no usaremos la predicción de estrato 5 porque el valor de la propiedad excede el crédito.

Filtrando atributos para una predicción manual.

Por otra parte, este primer análisis se hace tomando el valor predicho y bsucarlos en la base segmentada por la zona especifica con los valores requeridos y el valor predicho. No nos muestra predicciones.

Cabe resaltar que se, se implementarán dos metodos, uno manual ajustando los parametros de predicción y otro automático mediante el uso de predict. En algunos casos los modelos ofrecerán opciones diferentes para el análisis, cada uno de estos metodos tiene su propia gráfica de mapa.

ofertas_potenciales <- zona_selec %>%
  filter(areaconst >= 200,  
         parqueaderos >= 1,  
         banios >= 2,  
         habitaciones >= 4,  
         between(preciom, precio_pred_estrato4, 350)) %>%  # Ahora filtramos con el precio predicho
  arrange(preciom)  # Ordenamos por precio predicho ascendente

# Seleccionar las 5 mejores ofertas
ofertas_potenciales <- head(ofertas_potenciales, 5)

# Mostrar las ofertas seleccionadas
print(ofertas_potenciales)
##     id       zona piso preciom areaconst parqueaderos banios habitaciones
## 1 7471 Zona Norte   02     330       240            2      4            4
## 2 1189 Zona Norte   02     330       400            2      5            8
## 3  952 Zona Norte   02     330       275            2      3            5
## 4 1108 Zona Norte   02     330       260            1      3            4
## 5 3043 Zona Norte   02     330       275            2      3            5
##        barrio  longitud latitud tipo_Casa estrato_3 estrato_4 estrato_5
## 1       acopi -76.54980 3.39758         1         0         1         0
## 2 la floresta -76.51300 3.44400         1         1         0         0
## 3   la merced -76.50647 3.47516         1         0         1         0
## 4   la merced -76.51060 3.48108         1         0         1         0
## 5   la merced -76.52350 3.48329         1         0         0         1
##   estrato_6
## 1         0
## 2         0
## 3         0
## 4         0
## 5         0
# Crear un mapa con las ofertas potenciales
mapa_ofertas <- leaflet(ofertas_potenciales) %>%
  addTiles() %>%
  addCircleMarkers(lng = ~longitud, lat = ~latitud,
                   radius = 6, color = "blue",
                   label = ~paste("Precio Predicho: ", round(precio_pred_estrato4, 2), "millones<br>",
                                  "Área: ", areaconst, "m²<br>",
                                  "Parqueaderos: ", parqueaderos, "<br>",
                                  "Baños: ", banios),
                   popup = ~paste("<b>Barrio:</b> ", barrio, "<br>",
                                  "<b>Precio Predicho:</b> ", round(precio_pred_estrato4, 2), "millones<br>",
                                  "<b>Área:</b> ", areaconst, "m²<br>",
                                  "<b>Estrato:</b> 4<br>",
                                  "<b>Parqueaderos:</b> ", parqueaderos, "<br>",
                                  "<b>Baños:</b> ", banios, "<br>",
                                  "<b>Habitaciones:</b> ", habitaciones)
                   )

# Mostrar el mapa
mapa_ofertas

Filtrando atributos para una predicción automática con predict aplicada a la base.

En este caso hacemos la predicción con el método predict aplicado a las propiedas y filtrando por las predicciones anteriores.

Podemos evidenciar que la propiedad con ID 4511 es la predicción y procedemos a graficarla en el mapa.

# Generar las predicciones para todas las viviendas
zona_selec$precio_predicho <- predict(modelo, newdata = zona_selec)

# Filtrar viviendas con precios predichos similares o menores a Vivienda 1 (323.02 millones)
ofertas_potenciales <- zona_selec %>%
  filter(estrato_4 == 1,  
         areaconst >= 160 & areaconst <= 240,  
         parqueaderos <= 2,  
         banios >= 1 & banios <= 2,  
         habitaciones >= 3 & habitaciones <= 5,  
         precio_predicho <= precio_pred_estrato4) %>%  
  arrange(precio_predicho)  

# Seleccionar las 5 mejores ofertas
ofertas_potenciales <- head(ofertas_potenciales, 5)

# Mostrar los resultados
print(ofertas_potenciales)
##     id       zona piso preciom areaconst parqueaderos banios habitaciones
## 1 4511 Zona Norte   01     275       190            1      2            3
##   barrio  longitud latitud tipo_Casa estrato_3 estrato_4 estrato_5 estrato_6
## 1  acopi -76.53198 3.45165         1         0         1         0         0
##   precio_predicho
## 1        314.5046
# Crear un mapa con las ofertas potenciales
mapa_ofertas <- leaflet(ofertas_potenciales) %>%
  addTiles() %>%
  addCircleMarkers(lng = ~longitud, lat = ~latitud,
                   radius = 6, color = "blue",
                   label = ~paste("Precio Predicho: ", round(precio_predicho, 2), "millones<br>",
                                  "Área: ", areaconst, "m²<br>",
                                  "Parqueaderos: ", parqueaderos, "<br>",
                                  "Baños: ", banios),
                   popup = ~paste("<b>Barrio:</b> ", barrio, "<br>",
                                  "<b>Precio Predicho:</b> ", round(precio_predicho, 2), "millones<br>",
                                  "<b>Área:</b> ", areaconst, "m²<br>",
                                  "<b>Estrato:</b> 4<br>",
                                  "<b>Parqueaderos:</b> ", parqueaderos, "<br>",
                                  "<b>Baños:</b> ", banios, "<br>",
                                  "<b>Habitaciones:</b> ", habitaciones)
                   )

# Mostrar el mapa
mapa_ofertas

Desarrollo de la actividad: Apartamentos - zona sur.

1. Realice un filtro a la base de datos e incluya solo las ofertas de : base1: casas, de la zona norte de la ciudad. Presente los primeros 3 registros de las bases y algunas tablas que comprueben la consulta. (Adicional un mapa con los puntos de las bases. Discutir si todos los puntos se ubican en la zona correspondiente o se presentan valores en otras zonas, por que?).

Filtramos solo las casas de la zona norte y procedemos a mostrar los tres primeros registros

zona_selec = subset(vivienda, tipo == "Apartamento" & zona == "Zona Sur")
zona_selec[1:3,]
## # A tibble: 3 × 13
##      id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
##   <dbl> <chr>   <chr>   <dbl>   <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>

Validamos numero registros para la zona y tipo seleccionados

df= table(zona_selec$zona, zona_selec$tipo)
head(df)
##           
##            Apartamento
##   Zona Sur        2787

Valores faltantes base: casas zona sur.

apply(X = is.na(zona_selec), MARGIN = 2, FUN = sum)
##           id         zona         piso      estrato      preciom    areaconst 
##            0            0          622            0            0            0 
## parqueaderos       banios habitaciones         tipo       barrio     longitud 
##          406            0            0            0            0            0 
##      latitud 
##            0

Mapa de viviendas en la zona sur

Se puede evidenciar en el gráfico de mapa, que no todos los puntos corresponden a la zona sur, aunque en la base así lo sugiere. Esto puede identificarse por el comportamiento de los puntos en el mapa basados en los atributos Latitud y Longitud.

mapa_sur = leaflet(data = zona_selec) %>%
  addTiles() %>%
  addCircleMarkers(lng = ~longitud, lat = ~latitud, 
                   radius = 2,
                   color = "blue",
                   fillOpacity = 0.7,
                   stroke = FALSE)
mapa_sur

Análisis e imputaciones

Teniendo en cuenta que en el análisis exploratorio de la zona norte se detectaron valores faltantes en las variables Piso y parqueaderos, se procederá a realizar las imputaciones correspondientes. Por otra parte, cabe resaltar que piso es una variable ordinal, por consiguiente, se tratará como variable categorica.

Convertimos el campo piso y estrato a factor ordenado, dado a que es una variable ordinal y las variables zona, tipo, barrio quedan como factores nominales, dado que, no tienen orden específico.

zona_selec$piso <- factor(
  zona_selec$piso,
  ordered = TRUE
)

zona_selec$estrato <- factor(
  zona_selec$estrato,
  ordered = TRUE
)
zona_selec$zona   <- factor(zona_selec$zona)
zona_selec$tipo   <- factor(zona_selec$tipo)
zona_selec$barrio <- factor(zona_selec$barrio)
str(zona_selec)
## spc_tbl_ [2,787 × 13] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ id          : num [1:2787] 5098 698 8199 1241 5370 ...
##  $ zona        : Factor w/ 1 level "Zona Sur": 1 1 1 1 1 1 1 1 1 1 ...
##  $ piso        : Ord.factor w/ 12 levels "01"<"02"<"03"<..: 5 2 NA NA NA 6 8 NA NA NA ...
##  $ estrato     : Ord.factor w/ 4 levels "3"<"4"<"5"<"6": 2 1 4 1 1 2 1 1 1 2 ...
##  $ preciom     : num [1:2787] 290 78 875 135 135 220 210 105 115 220 ...
##  $ areaconst   : num [1:2787] 96 40 194 117 78 75 72 68 58 84 ...
##  $ parqueaderos: num [1:2787] 1 1 2 NA NA 1 2 NA 1 NA ...
##  $ banios      : num [1:2787] 2 1 5 2 1 2 2 2 2 2 ...
##  $ habitaciones: num [1:2787] 3 2 3 3 3 3 3 3 2 3 ...
##  $ tipo        : Factor w/ 1 level "Apartamento": 1 1 1 1 1 1 1 1 1 1 ...
##  $ barrio      : Factor w/ 141 levels "acopi","aguablanca",..: 1 2 3 4 4 5 5 5 5 6 ...
##  $ longitud    : num [1:2787] -76.5 -76.5 -76.6 -76.5 -76.5 ...
##  $ latitud     : num [1:2787] 3.45 3.4 3.46 3.44 3.44 ...
##  - 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>
# Verificamos el éxito de la imputación.
sum(is.na(zona_selec$piso))
## [1] 622
sum(is.na(zona_selec$parqueaderos))
## [1] 406

Aplicampos el metodo Knn para la imputación, teniendo en cuenta los tipos de variable y el numero de K=5.

# Aplicamos imputación KNN asegurando que 'zona' y 'estrato' no sean modificadas
zona_selec <- kNN(
  data = zona_selec, 
  variable = c("piso", "parqueaderos"),  # Solo imputamos estas
  dist_var = c("preciom", "banios", "habitaciones", "areaconst"),  # Variables numéricas para distancia
  k = 5
)

# Eliminamos columnas auxiliares .imp
zona_selec <- zona_selec[, !grepl(".imp$", colnames(zona_selec))]
# Verificamos el éxito de la imputación.
sum(is.na(zona_selec$piso))
## [1] 0
sum(is.na(zona_selec$parqueaderos))
## [1] 0

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

Precio vs Área Construida.

p1 <- ggplot(zona_selec, aes(x = areaconst, y = preciom)) +
  geom_point(alpha = 0.6, color = "blue") +
  labs(title = "Relación entre Precio y Área Construida",
       x = "Área Construida (m²)",
       y = "Precio de la vivienda (millones)") +
  theme_minimal()

# Convertir a gráfico interactivo con plotly
ggplotly(p1)

Precio vs Estrato.

p2 <- ggplot(zona_selec, aes(x = factor(estrato), y = preciom)) +
  geom_boxplot(fill = "skyblue", alpha = 0.7) +
  labs(title = "Distribución del Precio según el Estrato",
       x = "Estrato",
       y = "Precio de la vivienda (millones)") +
  theme_minimal()

ggplotly(p2)

Precio vs Número de Baños.

p3 <- ggplot(zona_selec, aes(x = factor(banios), y = preciom)) +
  geom_boxplot(fill = "lightgreen", alpha = 0.7) +
  labs(title = "Distribución del Precio según Número de Baños",
       x = "Número de Baños",
       y = "Precio de la vivienda (millones)") +
  theme_minimal()

ggplotly(p3)

Precio vs Número de Habitaciones.

p4 <- ggplot(zona_selec, aes(x = factor(habitaciones), y = preciom)) +
  geom_boxplot(fill = "coral", alpha = 0.7) +
  labs(title = "Distribución del Precio según Número de Habitaciones",
       x = "Número de Habitaciones",
       y = "Precio de la vivienda (millones)") +
  theme_minimal()

ggplotly(p4)

Precio según la Zona.

p5 <- ggplot(zona_selec, aes(x = zona, y = preciom, fill = zona)) +
  geom_boxplot(alpha = 0.7) +
  labs(title = "Distribución del Precio según la Zona",
       x = "Zona",
       y = "Precio de la vivienda (millones)") +
  theme_minimal() +
  theme(legend.position = "none") # Oculta la leyenda para evitar redundancia

ggplotly(p5)
# Seleccionamos solo las variables numéricas
vivienda_numeric <- zona_selec %>%
  select(preciom, areaconst, banios,parqueaderos, habitaciones)

# Calculamos la matriz de correlación
cor_matrix <- cor(vivienda_numeric, use = "complete.obs")

# Mapa de calor de correlaciones
corrplot(cor_matrix, method = "color", type = "upper",
         tl.cex = 0.8, tl.srt = 45, addCoef.col = "black", number.cex = 0.7)

3. Estime un modelo de regresión lineal múltiple con las variables del punto anterior (precio = f(área construida, estrato, número de cuartos, número de parqueaderos, número de baños ) ) e interprete los coeficientes si son estadísticamente significativos. Las interpretaciones deber están contextualizadas y discutir si los resultados son lógicos. Adicionalmente interprete el coeficiente R2 y discuta el ajuste del modelo e implicaciones (que podrían hacer para mejorarlo).

Para esto, es importante trabajar sobre las variables aplicando dummies a las categóricas estrato y zona; luego calculamos el VIF para detectar multicolinealidad teniendo en cuenta las siguientes reglas: VIF >= 10, Alta colinealidad. VIF >= 5, Colinealidad Media. VIF < 5, Podemos implementar estas variables en nuestro modelo.

# Convertimos 'tipo' y 'estrato' en variables dummy
zona_selec <- dummy_cols(zona_selec, select_columns = c("tipo", "estrato"), remove_selected_columns = TRUE)
head(zona_selec)
##     id     zona piso preciom areaconst parqueaderos banios habitaciones
## 1 5098 Zona Sur   05     290        96            1      2            3
## 2  698 Zona Sur   02      78        40            1      1            2
## 3 8199 Zona Sur   05     875       194            2      5            3
## 4 1241 Zona Sur   03     135       117            1      2            3
## 5 5370 Zona Sur   03     135        78            1      1            3
## 6 6975 Zona Sur   06     220        75            1      2            3
##          barrio  longitud latitud tipo_Apartamento estrato_3 estrato_4
## 1         acopi -76.53464 3.44987                1         0         1
## 2    aguablanca -76.50100 3.40000                1         1         0
## 3     aguacatal -76.55700 3.45900                1         0         0
## 4       alameda -76.51400 3.44100                1         1         0
## 5       alameda -76.53600 3.43600                1         1         0
## 6 alférez real -76.54627 3.39109                1         0         1
##   estrato_5 estrato_6
## 1         0         0
## 2         0         0
## 3         0         1
## 4         0         0
## 5         0         0
## 6         0         0

Calculamos el VIF.

Validamos la colinealidad perfecta, excluimos tipo por ya estar filtrado en la base y estrato 3 ya que se puede calcular con otros estratos.

# Verificar colinealidad perfecta
alias(lm(preciom ~ areaconst + habitaciones + parqueaderos + banios + 
          estrato_3 + estrato_4 + estrato_5 + estrato_6, 
          data = zona_selec))$Complete
##           (Intercept) areaconst habitaciones parqueaderos banios estrato_3
## estrato_6  1           0         0            0            0     -1       
##           estrato_4 estrato_5
## estrato_6 -1        -1

Calculamos el VIF.

# Definir el modelo solo con las variables explicativas
modelo_vif <- lm(preciom ~ areaconst + habitaciones + parqueaderos + banios  + estrato_4 + estrato_5 + estrato_6, data = zona_selec)

# Calcular el VIF
vif(modelo_vif)
##    areaconst habitaciones parqueaderos       banios    estrato_4    estrato_5 
##     2.160350     1.454313     1.884140     2.561924     4.074578     4.257006 
##    estrato_6 
##     3.984241

Modelo de regresión.

modelo <- lm(preciom ~ areaconst + habitaciones + parqueaderos + banios + 
              estrato_4 + estrato_5 + estrato_6, 
              data = zona_selec)

# Ver resumen del modelo
summary(modelo)
## 
## Call:
## lm(formula = preciom ~ areaconst + habitaciones + parqueaderos + 
##     banios + estrato_4 + estrato_5 + estrato_6, data = zona_selec)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1059.47   -36.05    -2.33    34.83   899.30 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -43.17127   10.08063  -4.283 1.91e-05 ***
## areaconst      1.28958    0.04624  27.886  < 2e-16 ***
## habitaciones -12.00833    3.15317  -3.808 0.000143 ***
## parqueaderos  64.32633    3.54277  18.157  < 2e-16 ***
## banios        39.11496    2.83311  13.806  < 2e-16 ***
## estrato_4     28.41974    6.83880   4.156 3.34e-05 ***
## estrato_5     54.11112    7.06401   7.660 2.55e-14 ***
## estrato_6    206.48160    8.87574  23.264  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 87.3 on 2779 degrees of freedom
## Multiple R-squared:  0.7928, Adjusted R-squared:  0.7923 
## F-statistic:  1519 on 7 and 2779 DF,  p-value: < 2.2e-16

4. Realice la validación de supuestos del modelo e interprete los resultados (no es necesario corregir en caso de presentar problemas, solo realizar sugerencias de que se podría hacer).

Interpretación de los coeficientes.

  • Multiple R-squared: Un 79.28%, indica que el modelo tiene buen poder de predicción, pero hay margen para mejorarlo.

  • Adjusted R-squared : 79.23%, significa que no hay muchas variables innecesarias en el modelo.

  • Intercepto: Cuando todas las variables son 0, el precio base es -43.17 millones. Este valor negativo no tiene sentido realista, lo que indica que el modelo podría mejorar con una transformación.

  • Área construida: Por cada metro cuadrado adicional el precio aumenta en 1.29 millones, es altamente significativa.

  • Habitaciones: Por cada habitación adicional, el precio disminuye en 12 millones. Ahora es significativa, lo que indica que más habitaciones podrían estar asociadas con menor precio (quizás porque las viviendas más grandes y costosas tienen menos habitaciones y más áreas comunes).

  • Parqueaderos: Por cada parqueadero adicional el precio aumenta en 64.33 millones, es altamente significativa.

  • Baños: Por cada baño adicional el precio aumenta en 39.11 millones, es altamente significativa.

  • Los coeficientes de estrato_3, estrato_4 y estrato_5 son negativos; esto indica que vivir en un estrato mas bajo reduce significativamente el precio.

  • Como el p-valor es < 2.2e-16, podemos rechazar la hipótesis nula de que todas las variables sean 0 (lo que indica que el modelo tiene validez estadística).

¿Cómo mejorar el modelo?

  • Transformaciones logaritmicas, puede ayudar a corregir el intercepto negativo y permitiría capturar efectos no lineales en el precio de las viviendas.

Validación de los supuestos del modelo.

par(mfrow=c(2,2))
plot(modelo)

Hipótesis del shapiro.test:

  • Si el p-valor es mayor que el nivel de significancia del 5%, no se rechaza la hipótesis nula, indicando que los residuos son normales.

  • Si el p-valor es menor que el nivel de significancia, se rechaza la hipótesis nula, lo que sugiere que los residuos no son normales.

shapiro.test(resid(modelo))
## 
##  Shapiro-Wilk normality test
## 
## data:  resid(modelo)
## W = 0.77438, p-value < 2.2e-16

El p-valor es menor que el nivel de significancia por tanto se rechaza la hipótesis nula, que establece que los residuos tienen una distribución normal. Es decir, igual que el Anderson-Darling los residuos del modelo no son normales.

Hipótesis del Test de Anderson-Darling:

Si el p-valor es mayor que el nivel de significancia del 5%, no se rechaza la hipótesis nula, indicando que los residuos siguen una distribución normal.

Si el p-valor es menor que el nivel de significancia, se rechaza la hipótesis nula, lo que sugiere que los residuos no son normales.

ad.test(modelo$residuals)
## 
##  Anderson-Darling normality test
## 
## data:  modelo$residuals
## A = 91.653, p-value < 2.2e-16

Dado que el p-valor es menor que un nivel de significancia, se rechaza la hipótesis nula de que los residuos siguen una distribución normal.

Hipótesis del Test de Breusch-Pagan:

Si el p-valor es mayor que el nivel de significancia (por ejemplo, 0.05), no se rechaza la hipótesis nula, indicando homocedasticidad.

Si el p-valor es menor que el nivel de significancia, se rechaza la hipótesis nula, lo que sugiere heterocedasticidad en los residuos.

lmtest::bptest(modelo)
## 
##  studentized Breusch-Pagan test
## 
## data:  modelo
## BP = 780.92, df = 7, p-value < 2.2e-16

Hipótesis del Test de Goldfeld-Quandt:

Si el p-valor es mayor que un nivel de significancia del 5%, NO se rechaza H₀, lo que indica que no hay evidencia de heterocedasticidad.

Si el p-valor es menor que el nivel de significancia del 5%, SE rechaza H₀, lo que sugiere que hay evidencia de heterocedasticidad.

lmtest::gqtest(modelo)
## 
##  Goldfeld-Quandt test
## 
## data:  modelo
## GQ = 0.90591, df1 = 1386, df2 = 1385, p-value = 0.967
## alternative hypothesis: variance increases from segment 1 to 2

Hipótesis del Test de Durbin-Watson:

El estadístico de Durbin-Watson (DW) varía entre 0 y 4:

  • DW ≈ 2: No hay autocorrelación.
  • DW < 2: Hay autocorrelación positiva.
  • DW > 2: Hay autocorrelación negativa.
lmtest::dwtest(modelo)
## 
##  Durbin-Watson test
## 
## data:  modelo
## DW = 1.7101, p-value = 7.042e-15
## alternative hypothesis: true autocorrelation is greater than 0

5. Con el modelo identificado debe predecir el precio de la vivienda con las características de la segunda solicitud.

Creamos dos DataFrame con la información de Vivienda2 y agrupada por estrato(5 y 6).

vivienda2_estrato5 <- data.frame(
  areaconst = 300,
  habitaciones = 5,
  parqueaderos = 3,
  banios = 3,
  estrato_4 = 0,  # No es estrato 3
  estrato_5 = 1,  # Es estrato 4
  estrato_6 = 0   # No es estrato 5
)


vivienda2_estrato6 <- data.frame(
  areaconst = 300,
  habitaciones = 5,
  parqueaderos = 3,
  banios = 3,
  estrato_4 = 0,  # No es estrato 3
  estrato_5 = 0,  # No es estrato 4
  estrato_6 = 1   # Es estrato 5
)

Ejecutamos las predicciones con las viviendas agrupadas por estrato.

Se puede evienciar que, si el estrato es 5, el valor de la propiedad sería de 648.09 millones.Por su parte, en estrato 6 el precio de la vivienda sería de 800.47 millones, ambos valores está en el rango del credito preaprobado.

# Predecir el precio para Vivienda 2 en estrato 5
precio2_pred_estrato5 <- predict(modelo, newdata = vivienda2_estrato5)

# Predecir el precio para Vivienda 2 en estrato 6
precio2_pred_estrato6 <- predict(modelo, newdata = vivienda2_estrato6)

# Mostrar los resultados
print(paste("Precio estimado para Vivienda 2 en estrato 5:", round(precio2_pred_estrato5, 2), "millones"))
## [1] "Precio estimado para Vivienda 2 en estrato 5: 648.09 millones"
print(paste("Precio estimado para Vivienda 2 en estrato 6:", round(precio2_pred_estrato6, 2), "millones"))
## [1] "Precio estimado para Vivienda 2 en estrato 6: 800.47 millones"

6. Con las predicciones del modelo sugiera potenciales ofertas que responda a la solicitud de la vivienda 2. Tenga encuentra que la empresa tiene crédito pre-aprobado de máximo 850 millones de pesos. Realice un análisis y presente en un mapa al menos 5 ofertas potenciales que debe discutir.

Para este punto es importante resaltar que, según las predicciones ambas predicciones según el estrato se encuentran dentro del rango del credito preaprobado que es de 850 millones.

Cabe resaltar que se, se implementarán dos metodos, uno manual ajustando los parametros de predicción y otro automático mediante el uso de predict. En algunos casos los modelos ofrecerán opciones diferentes para el análisis, cada uno de estos metodos tiene su propia gráfica de mapa.

En este caso, se puede evidenciar que el modelo manual predijo para el estrato 5, pero no para el 6; por otra parte, el modelo con predict no predijo para el estrato 6 y no para el estrato 5.

Filtrando atributos para una predicción manual para precio de estrato 5.

ofertas_potenciales <- zona_selec %>%
  filter(areaconst >= 300,  
         parqueaderos >= 3,  
         banios >= 3,  
         habitaciones >= 5,  
         between(preciom, precio2_pred_estrato5, 850)) %>% 
  arrange(preciom)  


ofertas_potenciales <- head(ofertas_potenciales, 5)

print(ofertas_potenciales)
##     id     zona piso preciom areaconst parqueaderos banios habitaciones
## 1 7512 Zona Sur   02     670       300            3      5            6
## 2 7182 Zona Sur   08     730       573            3      8            5
##      barrio longitud latitud tipo_Apartamento estrato_3 estrato_4 estrato_5
## 1 seminario  -76.550   3.409                1         0         0         1
## 2 guadalupe  -76.548   3.408                1         0         0         1
##   estrato_6
## 1         0
## 2         0
mapa_ofertas <- leaflet(ofertas_potenciales) %>%
  addTiles() %>%
  addCircleMarkers(lng = ~longitud, lat = ~latitud,
                   radius = 6, color = "blue",
                   label = ~paste("Precio Predicho: ", round(precio2_pred_estrato5, 2), "millones<br>",
                                  "Área: ", areaconst, "m²<br>",
                                  "Parqueaderos: ", parqueaderos, "<br>",
                                  "Baños: ", banios),
                   popup = ~paste("<b>Barrio:</b> ", barrio, "<br>",
                                  "<b>Precio Predicho:</b> ", round(precio2_pred_estrato5, 2), "millones<br>",
                                  "<b>Área:</b> ", areaconst, "m²<br>",
                                  "<b>Estrato:</b> 4<br>",
                                  "<b>Parqueaderos:</b> ", parqueaderos, "<br>",
                                  "<b>Baños:</b> ", banios, "<br>",
                                  "<b>Habitaciones:</b> ", habitaciones)
                   )


mapa_ofertas

Filtrando atributos para una predicción manual para precio de estrato 6.

Por otra parte, este primer análisis se hace tomando el valor predicho y buscarlos en la base segmentada por la zona especifica con los valores requeridos y el valor predicho. No nos muestra predicciones.

ofertas_potenciales <- zona_selec %>%
  filter(areaconst >= 300,  
         parqueaderos >= 3,  
         banios >= 3,  
         habitaciones >= 5,  
         between(preciom, precio2_pred_estrato6, 850)) %>% 
  arrange(preciom)  

ofertas_potenciales <- head(ofertas_potenciales, 5)


print(ofertas_potenciales)
##  [1] id               zona             piso             preciom         
##  [5] areaconst        parqueaderos     banios           habitaciones    
##  [9] barrio           longitud         latitud          tipo_Apartamento
## [13] estrato_3        estrato_4        estrato_5        estrato_6       
## <0 rows> (o 0- extensión row.names)

Filtrando atributos para una predicción automática con predict aplicada a la base.

En este caso hacemos la predicción con el método predict aplicado a las propiedas y filtrando por las predicciones anteriores solo del estrato 5.

zona_selec$precio_predicho <- predict(modelo, newdata = zona_selec)


ofertas_potenciales <- zona_selec %>%
  filter(areaconst >= 300,  
         parqueaderos >= 3,  
         banios >= 3,  
         habitaciones >= 5,  
         precio_predicho <= precio2_pred_estrato5) %>%  
  arrange(precio_predicho)  


ofertas_potenciales <- head(ofertas_potenciales, 5)


print(ofertas_potenciales)
##  [1] id               zona             piso             preciom         
##  [5] areaconst        parqueaderos     banios           habitaciones    
##  [9] barrio           longitud         latitud          tipo_Apartamento
## [13] estrato_3        estrato_4        estrato_5        estrato_6       
## [17] precio_predicho 
## <0 rows> (o 0- extensión row.names)
mapa_ofertas <- leaflet(ofertas_potenciales) %>%
  addTiles() %>%
  addCircleMarkers(lng = ~longitud, lat = ~latitud,
                   radius = 6, color = "blue",
                   label = ~paste("Precio Predicho: ", round(precio_predicho, 2), "millones<br>",
                                  "Área: ", areaconst, "m²<br>",
                                  "Parqueaderos: ", parqueaderos, "<br>",
                                  "Baños: ", banios),
                   popup = ~paste("<b>Barrio:</b> ", barrio, "<br>",
                                  "<b>Precio Predicho:</b> ", round(precio_predicho, 2), "millones<br>",
                                  "<b>Área:</b> ", areaconst, "m²<br>",
                                  "<b>Estrato:</b> 4<br>",
                                  "<b>Parqueaderos:</b> ", parqueaderos, "<br>",
                                  "<b>Baños:</b> ", banios, "<br>",
                                  "<b>Habitaciones:</b> ", habitaciones)
                   )


mapa_ofertas

Filtrando atributos para una predicción automática con predict aplicada a la base.

En este caso hacemos la predicción con el método predict aplicado a las propiedas y filtrando por las predicciones anteriores solo del estrato 6.

zona_selec$precio_predicho <- predict(modelo, newdata = zona_selec)


ofertas_potenciales <- zona_selec %>%
  filter(areaconst >= 300,  
         parqueaderos >= 3,  
         banios >= 3,  
         habitaciones >= 5,  
         precio_predicho <= precio2_pred_estrato6) %>%  
  arrange(precio_predicho)  


ofertas_potenciales <- head(ofertas_potenciales, 5)


print(ofertas_potenciales)
##     id     zona piso preciom areaconst parqueaderos banios habitaciones
## 1 6868 Zona Sur   03     370       300            3      6            5
## 2 7512 Zona Sur   02     670       300            3      5            6
##      barrio  longitud latitud tipo_Apartamento estrato_3 estrato_4 estrato_5
## 1  melendez -76.54537 3.37812                1         1         0         0
## 2 seminario -76.55000 3.40900                1         0         0         1
##   estrato_6 precio_predicho
## 1         0        711.3284
## 2         0        714.3162
mapa_ofertas <- leaflet(ofertas_potenciales) %>%
  addTiles() %>%
  addCircleMarkers(lng = ~longitud, lat = ~latitud,
                   radius = 6, color = "blue",
                   label = ~paste("Precio Predicho: ", round(precio_predicho, 2), "millones<br>",
                                  "Área: ", areaconst, "m²<br>",
                                  "Parqueaderos: ", parqueaderos, "<br>",
                                  "Baños: ", banios),
                   popup = ~paste("<b>Barrio:</b> ", barrio, "<br>",
                                  "<b>Precio Predicho:</b> ", round(precio_predicho, 2), "millones<br>",
                                  "<b>Área:</b> ", areaconst, "m²<br>",
                                  "<b>Estrato:</b> 4<br>",
                                  "<b>Parqueaderos:</b> ", parqueaderos, "<br>",
                                  "<b>Baños:</b> ", banios, "<br>",
                                  "<b>Habitaciones:</b> ", habitaciones)
                   )


mapa_ofertas