1 Introducción

Este informe analiza la solicitud de una empresa internacional interesada en adquirir dos viviendas en Cali para sus empleados. Para ello, se emplea un enfoque basado en modelación de datos con técnicas estadísticas y aprendizaje automático en R.

Utilizando datos recientes de oferta inmobiliaria, se desarrollará un análisis exploratorio para comprender la relación entre el precio de la vivienda y sus características clave. Posteriormente, se estimará un modelo de regresión lineal múltiple para predecir el precio de las propiedades que cumplen con los criterios de la solicitud. Finalmente, se identificarán las mejores opciones de compra dentro del presupuesto preaprobado.

El estudio se estructura en siete etapas: filtrado de datos, análisis de correlación, estimación y validación del modelo, predicción de precios, selección de ofertas y recomendaciones finales. Esto permitirá optimizar la toma de decisiones y ofrecer una asesoría basada en evidencia cuantitativa.

2 Preparación de los datos

2.1 Carga de Datos

Se cargan previamente los datos con los siguientes comandos:

install.packages(“devtools”) # solo la primera vez

devtools::install_github(“centromagis/paqueteMODELOS”, force =TRUE)

2.2 Librerías Requeridas

Para el desarrollo de la actividad, se implementaron las siguientes librerias:

  • library(paqueteMODELOS)
  • library(dplyr)
  • library(ggplot2)
  • library(leaflet)
  • library(tidyr)
  • library(gt)
  • library(sf)
  • library(ggmap)
  • library(plotly)
  • library(ggcorrplot)
  • library(knitr)
  • library(kableExtra)
  • library(lmtest)

2.3 Resumen de Variables y Tipos de Datos

# Resumen de la estructura de la base de datos
tabla_resumen <- tibble(
  Variable = names(vivienda),
  Tipo_de_Dato = sapply(vivienda, class)
)

tabla_resumen %>%
  gt() %>%
  tab_header(
    title = md("**Resumen de Variables y Tipos de Datos**"),
    subtitle = md("_Estructura de la base de datos 'vivienda'_")
  ) %>%
  cols_label(
    Variable = "Variable",
    Tipo_de_Dato = "Tipo de Dato"
  ) %>%
  opt_table_outline() %>%
  opt_align_table_header(align = "center")
Resumen de Variables y Tipos de Datos
Estructura de la base de datos ‘vivienda’
Variable Tipo de Dato
id numeric
zona character
piso character
estrato numeric
preciom numeric
areaconst numeric
parqueaderos numeric
banios numeric
habitaciones numeric
tipo character
barrio character
longitud numeric
latitud numeric
# Crear la tabla con los primeros 15 registros
tabla_vivienda <- vivienda %>%
  head(15) %>%
  gt() %>%
  tab_header(title = "Primeros 15 registros del dataset 'vivienda'") %>%
  opt_table_outline() %>%
  tab_options(
    table.font.size = px(14),
    heading.title.font.size = px(18),
    column_labels.font.weight = "bold"
  ) %>%
  opt_row_striping()

# Mostrar la tabla
tabla_vivienda
Primeros 15 registros del dataset 'vivienda'
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
1147 Zona Oriente NA 3 250 70 1 3 6 Casa 20 de julio -76.51168 3.43382
1169 Zona Oriente NA 3 320 120 1 2 3 Casa 20 de julio -76.51237 3.43369
1350 Zona Oriente NA 3 350 220 2 2 4 Casa 20 de julio -76.51537 3.43566
5992 Zona Sur 02 4 400 280 3 5 3 Casa 3 de julio -76.54000 3.43500
1212 Zona Norte 01 5 260 90 1 2 3 Apartamento acopi -76.51350 3.45891
1724 Zona Norte 01 5 240 87 1 3 3 Apartamento acopi -76.51700 3.36971
2326 Zona Norte 01 4 220 52 2 2 3 Apartamento acopi -76.51974 3.42627
4386 Zona Norte 01 5 310 137 2 3 4 Apartamento acopi -76.53105 3.38296
1209 Zona Norte 02 5 320 150 2 4 6 Casa acopi -76.51341 3.47968
1592 Zona Norte 02 5 780 380 2 3 3 Casa acopi -76.51674 3.48721
4057 Zona Norte 02 6 750 445 NA 7 6 Casa acopi -76.52950 3.38527
4460 Zona Norte 02 4 625 355 3 5 5 Casa acopi -76.53179 3.40590
6081 Zona Norte 02 5 750 237 2 6 6 Casa acopi -76.54044 3.36862
7497 Zona Norte 02 6 520 98 2 2 2 Apartamento acopi -76.54999 3.43505
7824 Zona Norte 02 4 600 160 1 4 5 Casa acopi -76.55210 3.42125

2.4 Diagramas de Cajas

# Gráfico 1

# Seleccionar las variables preciom y areaconst
vivienda_num1 <- vivienda %>% select(preciom, areaconst)

# Convertir a formato largo
vivienda_long1 <- vivienda_num1 %>%
  pivot_longer(cols = everything(), names_to = "Variable", values_to = "Valor")

# Crear el gráfico de bigotes
ggplot(vivienda_long1, aes(x = Variable, y = Valor)) +
  geom_boxplot(fill = "#69b3a2", color = "black", outlier.color = "red") +
  theme_minimal() +
  labs(title = "Distribución de Precio y Área Construida",
       x = "Variables",
       y = "Valores") +
  theme(axis.text.x = element_text(size = 12))
## Warning: Removed 5 rows containing non-finite outside the scale range
## (`stat_boxplot()`).

# Gráfico 2

# Seleccionar las variables banios, habitaciones, estrato, parqueaderos
vivienda_num2 <- vivienda %>% select(banios, habitaciones, estrato, parqueaderos)

# Convertir a formato largo
vivienda_long2 <- vivienda_num2 %>%
  pivot_longer(cols = everything(), names_to = "Variable", values_to = "Valor")

# Crear el gráfico de bigotes
ggplot(vivienda_long2, aes(x = Variable, y = Valor)) +
  geom_boxplot(fill = "#ffcc66", color = "black", outlier.color = "red") +
  theme_minimal() +
  labs(title = "Distribución de Baños, Habitaciones, Estrato y Parqueaderos",
       x = "Variables",
       y = "Valores") +
  theme(axis.text.x = element_text(size = 12))
## Warning: Removed 1614 rows containing non-finite outside the scale range
## (`stat_boxplot()`).

2.5 Gráfico de Datos Faltantes

# Valores faltates por columna
colSums(is.na(vivienda))
##           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
# Crear el data frame con la cantidad de valores faltantes
missing_data <- colSums(is.na(vivienda)) %>%
  as.data.frame() %>%
  tibble::rownames_to_column(var = "variable") %>%
  rename(missing_values = ".")

# Filtrar solo las variables que tienen valores faltantes
missing_data <- missing_data %>% filter(missing_values > 0)

# Crear el gráfico de barras con etiquetas y estilo mejorado
ggplot(missing_data, aes(x = reorder(variable, missing_values), y = missing_values)) +
  geom_bar(stat = "identity", fill = "#69b3a2", color = "black", width = 0.6) +
  geom_text(aes(label = missing_values), vjust = -0.5, size = 5, fontface = "bold") +
  labs(title = "Cantidad de valores faltantes por variable",
       x = "Variable",
       y = "Cantidad de valores faltantes") +
  theme_minimal(base_size = 14) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1, face = "bold"),
        axis.text.y = element_text(face = "bold"),
        plot.title = element_text(hjust = 0.5, face = "bold", size = 16)) +
  coord_flip()

2.6 Imputación de Datos

vivienda <- vivienda %>%
  mutate(
    piso = ifelse(is.na(piso), "01", piso),  # Imputar con "01" (carácter)
    parqueaderos = ifelse(is.na(parqueaderos), 0, parqueaderos),  # Imputar con 0 (numérico)
    preciom = ifelse(is.na(preciom), mean(preciom, na.rm = TRUE), preciom),  # Imputar con la media
    areaconst = ifelse(is.na(areaconst), mean(areaconst, na.rm = TRUE), areaconst)  # Imputar con la media
  )

# Eliminar registros con valores faltantes en las variables especificadas
vivienda <- vivienda %>%
  filter(
    !is.na(zona) & !is.na(tipo) & !is.na(latitud) & !is.na(longitud) & !is.na(id) & 
      !is.na(habitaciones) & !is.na(estrato) & !is.na(barrio) & !is.na(banios)
  )

# Verificar que no queden valores faltantes en las variables afectadas
colSums(is.na(vivienda))
##           id         zona         piso      estrato      preciom    areaconst 
##            0            0            0            0            0            0 
## parqueaderos       banios habitaciones         tipo       barrio     longitud 
##            0            0            0            0            0            0 
##      latitud 
##            0

3 Requerimiento 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?).

# 1. Filtrar la base de datos para incluir solo Casas en la Zona Norte
base1 <- vivienda %>% 
  filter(zona == "Zona Norte" & tipo == "Casa")

# 2. Mostrar los primeros 3 registros de base1
head(base1, 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            0      7            6
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
# 3. Tabla resumen para verificar la consulta
table(base1$zona)   # Verificar que solo hay "Zona Norte"
## 
## Zona Norte 
##        722
table(base1$tipo)   # Verificar que solo hay "Casa"
## 
## Casa 
##  722
# 4. Crear un mapa con los puntos de base1

# Mapa con ggplot2
ggplot(base1, aes(x = longitud, y = latitud)) +
  geom_point(color = "blue", size = 2) +
  labs(title = "Ubicación de las Casas en la Zona Norte",
       x = "Longitud", y = "Latitud") +
  theme_minimal()

# Mapa interactivo con leaflet
leaflet(base1) %>%
  addTiles() %>%
  addCircleMarkers(~longitud, ~latitud, 
                   color = "blue", 
                   radius = 4, 
                   label = ~paste("Precio:", preciom, "millones"))
# 5. Análisis de los puntos
# Si algún punto no está realmente en la Zona Norte, revisar si hay errores en los datos
outliers <- base1 %>% filter(latitud < min(vivienda$latitud[vivienda$zona == "Zona Norte"]) |
                               latitud > max(vivienda$latitud[vivienda$zona == "Zona Norte"]) |
                               longitud < min(vivienda$longitud[vivienda$zona == "Zona Norte"]) |
                               longitud > max(vivienda$longitud[vivienda$zona == "Zona Norte"]))

# Mostrar registros sospechosos si existen
if (nrow(outliers) > 0) {
  print("Puntos fuera de la Zona Norte:")
  print(outliers)
} else {
  print("Todos los puntos están correctamente ubicados en la Zona Norte.")
}
## [1] "Todos los puntos están correctamente ubicados en la Zona Norte."

3.1 Análisis del Proceso y Resultados

Al revisar la distribución de los puntos sobre el mapa, se evidencia que no todos los puntos se encuentran ubicados sobre la ‘Zona Norte’; por ende, se procede a realizar un ajuste a la distribución de los datos.

# Crear el polígono de la Zona Norte
zona_norte_poligono <- st_polygon(list(matrix(
  c(-76.540638,  3.451404,
    -76.475783,  3.468090,
    -76.493340,  3.505753,
    -76.538775,  3.496625,
    -76.540638,  3.451404),  # Cerrar el polígono
  ncol = 2, byrow = TRUE
))) %>%
  st_sfc(crs = 4326)  # Asignar sistema de coordenadas WGS84

# Convertir base1 a un objeto espacial
base1_sf <- st_as_sf(base1, coords = c("longitud", "latitud"), crs = 4326)

# Filtrar solo los puntos dentro del polígono
base1_reclasificada <- base1_sf[st_within(base1_sf, zona_norte_poligono, sparse = FALSE), ]

# Verificar cuántos registros quedaron
nrow(base1_reclasificada)
## [1] 526
# Extraer coordenadas de base1_reclasificada
base1_reclasificada <- base1_reclasificada %>%
  mutate(longitud = st_coordinates(.)[,1],
         latitud = st_coordinates(.)[,2])

# Crear el mapa sin el polígono, solo con los puntos filtrados
leaflet(base1_reclasificada) %>%
  addTiles() %>%
  addCircleMarkers(
    lng = ~longitud, lat = ~latitud,
    color = "red", fillColor = "red", fillOpacity = 0.7,
    radius = 4, popup = ~paste("Precio:", preciom, "millones")
  ) %>%
  addLegend("topright", colors = "red", labels = "Casas en Zona Norte", opacity = 1)

Para mejorar el analisis, se redistribuyen todos los puntos por zonas y se crea una nueva base con la información:

# Convertir vivienda en objeto sf con CRS 4326
vivienda_sf <- st_as_sf(vivienda, coords = c("longitud", "latitud"), crs = 4326)

# Definir las zonas como polígonos en un objeto sf
zonas_sf <- st_sf(
  ZonaRed = c("Zona Norte", "Zona Centro", "Zona Oriente", "Zona Oeste", "Zona Sur"),
  geometry = st_sfc(
    st_polygon(list(matrix(c(-76.540638, 3.451404,
                             -76.475783, 3.468090,
                             -76.493340, 3.505753,
                             -76.538775, 3.496625,
                             -76.540638, 3.451404), ncol = 2, byrow = TRUE))),
    st_polygon(list(matrix(c(-76.540638, 3.451404,
                             -76.475783, 3.468090,
                             -76.522857, 3.405660,
                             -76.548480, 3.415723,
                             -76.540638, 3.451404), ncol = 2, byrow = TRUE))),
    st_polygon(list(matrix(c(-76.475783, 3.468090,
                             -76.522857, 3.405660,
                             -76.509577, 3.388791,
                             -76.465090, 3.391528,
                             -76.475783, 3.468090), ncol = 2, byrow = TRUE))),
    st_polygon(list(matrix(c(-76.540638, 3.451404,
                             -76.548480, 3.415723,
                             -76.540416, 3.372652,
                             -76.556823, 3.367932,
                             -76.593911, 3.470449,
                             -76.540638, 3.451404), ncol = 2, byrow = TRUE))),
    st_polygon(list(matrix(c(-76.522857, 3.405660,
                             -76.509577, 3.388791,
                             -76.512123, 3.295108,
                             -76.564592, 3.305764,
                             -76.540416, 3.372652,
                             -76.548480, 3.415723,
                             -76.522857, 3.405660), ncol = 2, byrow = TRUE)))
  ),
  crs = 4326  # Asegurar que el CRS sea 4326
)

# Unir cada punto de vivienda con su respectiva zona
vivienda_redistribuida <- st_join(vivienda_sf, zonas_sf, join = st_within)

# Convertir a data.frame asegurando que las coordenadas sean accesibles
vivienda_redistribuida <- vivienda_redistribuida %>%
  mutate(longitud = st_coordinates(geometry)[,1],
         latitud = st_coordinates(geometry)[,2]) %>%
  as.data.frame()

# Definir colores para cada zona
colores_zonas <- c("Zona Norte" = "blue", "Zona Centro" = "green", 
                   "Zona Oriente" = "orange", "Zona Oeste" = "purple", 
                   "Zona Sur" = "red")

# Crear el mapa con Leaflet
leaflet(vivienda_redistribuida) %>%
  addTiles() %>%
  addCircleMarkers(
    lng = ~longitud, lat = ~latitud,
    color = ~colores_zonas[ZonaRed],
    fillColor = ~colores_zonas[ZonaRed],
    fillOpacity = 0.7,
    radius = 3,
    popup = ~paste("Zona:", ZonaRed)
  ) %>%
  addLegend("topright", 
            colors = colores_zonas, 
            labels = names(colores_zonas), 
            title = "Zonas de Cali", opacity = 1)
## Input to asJSON(keep_vec_names=TRUE) is a named vector. In a future version of jsonlite, this option will not be supported, and named vectors will be translated into arrays instead of objects. If you want JSON object output, please use a named list instead. See ?toJSON.
## Input to asJSON(keep_vec_names=TRUE) is a named vector. In a future version of jsonlite, this option will not be supported, and named vectors will be translated into arrays instead of objects. If you want JSON object output, please use a named list instead. See ?toJSON.

4 Requerimiento 2

Realice un análisis exploratorio de datos enfocado en la correlación entre la variable respuesta (precio de la casa) 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.

4.1 Caso 1

Características del Caso 1
Características Caso.1
Tipo Casa
Área construida (m2) 200
Parqueaderos 1
Baños 2
Habitaciones 4
Estrato 4 o 5
Zona Norte
Crédito preaprobado 350 millones
# Hacer una copia de la base de datos original
vivienda_casas2 <- vivienda_redistribuida 
colnames(vivienda_casas2)
##  [1] "id"           "zona"         "piso"         "estrato"      "preciom"     
##  [6] "areaconst"    "parqueaderos" "banios"       "habitaciones" "tipo"        
## [11] "barrio"       "geometry"     "ZonaRed"      "longitud"     "latitud"
# Filtrar solo casas en la Zona Norte
vivienda_casas2 <- vivienda_casas2 %>%
  filter(tipo == "Casa", ZonaRed == "Zona Norte")

str(vivienda_casas2)  # Estructura de la base de datos
## 'data.frame':    578 obs. of  15 variables:
##  $ id          : num  1209 1592 504 604 1003 ...
##  $ zona        : chr  "Zona Norte" "Zona Norte" "Zona Norte" "Zona Norte" ...
##  $ piso        : chr  "02" "02" "01" "01" ...
##  $ estrato     : num  5 5 3 5 3 5 5 5 5 4 ...
##  $ preciom     : num  320 780 180 520 380 395 460 390 780 420 ...
##  $ areaconst   : num  150 380 120 455 300 165 319 357 380 265 ...
##  $ parqueaderos: num  2 2 0 0 0 0 0 0 0 0 ...
##  $ banios      : num  4 3 3 5 5 4 5 3 3 6 ...
##  $ habitaciones: num  6 3 3 4 8 4 4 6 3 7 ...
##  $ tipo        : chr  "Casa" "Casa" "Casa" "Casa" ...
##  $ barrio      : chr  "acopi" "acopi" "acopi" "acopi" ...
##  $ geometry    :sfc_POINT of length 578; first list element:  'XY' num  -76.51 3.48
##  $ ZonaRed     : chr  "Zona Norte" "Zona Norte" "Zona Norte" "Zona Norte" ...
##  $ longitud    : num  -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num  3.48 3.49 3.47 3.46 3.47 ...
##  - attr(*, "sf_column")= chr "geometry"
##  - attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA NA ...
##   ..- attr(*, "names")= chr [1:14] "id" "zona" "piso" "estrato" ...
summary(vivienda_casas2)  # Resumen estadístico
##        id             zona               piso              estrato     
##  Min.   :  83.0   Length:578         Length:578         Min.   :3.000  
##  1st Qu.: 598.8   Class :character   Class :character   1st Qu.:3.000  
##  Median :1364.0   Mode  :character   Mode  :character   Median :4.000  
##  Mean   :2008.9                                         Mean   :4.197  
##  3rd Qu.:3221.8                                         3rd Qu.:5.000  
##  Max.   :5944.0                                         Max.   :6.000  
##     preciom         areaconst       parqueaderos        banios      
##  Min.   : 100.0   Min.   :  30.0   Min.   : 0.000   Min.   : 0.000  
##  1st Qu.: 250.0   1st Qu.: 135.0   1st Qu.: 0.000   1st Qu.: 2.000  
##  Median : 380.0   Median : 239.5   Median : 1.000   Median : 3.000  
##  Mean   : 437.2   Mean   : 261.3   Mean   : 1.517   Mean   : 3.528  
##  3rd Qu.: 550.0   3rd Qu.: 338.5   3rd Qu.: 2.000   3rd Qu.: 4.000  
##  Max.   :1800.0   Max.   :1500.0   Max.   :10.000   Max.   :10.000  
##   habitaciones        tipo              barrio                   geometry  
##  Min.   : 0.000   Length:578         Length:578         POINT        :578  
##  1st Qu.: 3.000   Class :character   Class :character   epsg:4326    :  0  
##  Median : 4.000   Mode  :character   Mode  :character   +proj=long...:  0  
##  Mean   : 4.599                                                            
##  3rd Qu.: 5.000                                                            
##  Max.   :10.000                                                            
##    ZonaRed             longitud         latitud     
##  Length:578         Min.   :-76.54   Min.   :3.453  
##  Class :character   1st Qu.:-76.52   1st Qu.:3.467  
##  Mode  :character   Median :-76.52   Median :3.475  
##                     Mean   :-76.51   Mean   :3.475  
##                     3rd Qu.:-76.50   3rd Qu.:3.484  
##                     Max.   :-76.48   Max.   :3.496
glimpse(vivienda_casas2)  # Vista rápida del contenido
## Rows: 578
## Columns: 15
## $ id           <dbl> 1209, 1592, 504, 604, 1003, 1840, 2730, 2875, 2908, 3182,…
## $ zona         <chr> "Zona Norte", "Zona Norte", "Zona Norte", "Zona Norte", "…
## $ piso         <chr> "02", "02", "01", "01", "01", "01", "01", "01", "01", "01…
## $ estrato      <dbl> 5, 5, 3, 5, 3, 5, 5, 5, 5, 4, 6, 3, 4, 3, 3, 3, 3, 3, 3, …
## $ preciom      <dbl> 320, 780, 180, 520, 380, 395, 460, 390, 780, 420, 460, 28…
## $ areaconst    <dbl> 150, 380, 120, 455, 300, 165, 319, 357, 380, 265, 350, 14…
## $ parqueaderos <dbl> 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 1, 0, 0, 2, …
## $ banios       <dbl> 4, 3, 3, 5, 5, 4, 5, 3, 3, 6, 5, 4, 2, 3, 4, 2, 2, 4, 0, …
## $ habitaciones <dbl> 6, 3, 3, 4, 8, 4, 4, 6, 3, 7, 6, 4, 4, 4, 7, 4, 3, 4, 0, …
## $ tipo         <chr> "Casa", "Casa", "Casa", "Casa", "Casa", "Casa", "Casa", "…
## $ barrio       <chr> "acopi", "acopi", "acopi", "acopi", "acopi", "acopi", "ac…
## $ geometry     <POINT [°]> POINT (-76.51341 3.47968), POINT (-76.51674 3.48721…
## $ ZonaRed      <chr> "Zona Norte", "Zona Norte", "Zona Norte", "Zona Norte", "…
## $ longitud     <dbl> -76.51341, -76.51674, -76.49768, -76.49966, -76.50743, -7…
## $ latitud      <dbl> 3.47968, 3.48721, 3.47060, 3.46284, 3.46566, 3.47651, 3.4…
colSums(is.na(vivienda_casas2))  # Cuenta los NAs por columna
##           id         zona         piso      estrato      preciom    areaconst 
##            0            0            0            0            0            0 
## parqueaderos       banios habitaciones         tipo       barrio     geometry 
##            0            0            0            0            0            0 
##      ZonaRed     longitud      latitud 
##            0            0            0
# Seleccionar columnas de interés
df <- vivienda_casas2 %>%
  select(preciom, areaconst, estrato, banios, habitaciones)

# Matriz de correlación
cor_matrix <- cor(df, use = "complete.obs")
print(cor_matrix)
##                preciom areaconst   estrato    banios habitaciones
## preciom      1.0000000 0.7408076 0.6161823 0.5338786    0.3517152
## areaconst    0.7408076 1.0000000 0.4898166 0.5179782    0.4493405
## estrato      0.6161823 0.4898166 1.0000000 0.4540176    0.1262610
## banios       0.5338786 0.5179782 0.4540176 1.0000000    0.6210145
## habitaciones 0.3517152 0.4493405 0.1262610 0.6210145    1.0000000
# Visualizar la matriz de correlación
ggcorrplot(cor_matrix, 
           method = "square",  # Puedes cambiar a "circle"
           type = "lower",
           lab = TRUE, lab_size = 4, 
           colors = c("red", "white", "blue"),
           title = "Matriz de Correlación - Casas en Zona Norte", 
           ggtheme = theme_minimal())

  • La correlación más fuerte se da entre Precio por metro cuadrado y Área Construida (0.74). Esto indica que a mayor área construida, mayor es el precio por metro cuadrado.
  • Estrato y Precio por metro cuadrado tienen una correlación de 0.62, lo que sugiere que las viviendas en estratos más altos suelen tener un mayor precio por metro cuadrado.
  • Baños y Precio por metro cuadrado tienen una correlación de 0.53, lo que indica que a medida que aumenta el número de baños, el precio también tiende a aumentar.
  • Habitaciones y Precio por metro cuadrado tienen la correlación más baja (0.35), lo que implica que el número de habitaciones no influye tanto en el precio como otras variables.

El precio por metro cuadrado en la zona norte de Cali está fuertemente relacionado con el área construida y el estrato. Otras variables como el número de baños también influyen, pero en menor medida.

4.2 Gráficos de dispersión interactivos con Plotly

# Precio vs Área Construida
p1 <- ggplot(df, aes(x = areaconst, y = preciom)) +
  geom_point(alpha = 0.5, color = 'blue') +
  labs(title = "Precio vs Área Construida", x = "Área Construida (m2)", y = "Precio por metro cuadrado")
p1_plotly <- ggplotly(p1)
p1_plotly
  • Se observa una relación positiva: a mayor área construida, mayor precio por metro cuadrado.
  • Sin embargo, la dispersión sugiere que hay valores atípicos (puntos alejados del patrón general), lo que indica que algunas casas con mucha área pueden no seguir la misma tendencia de precio.

El área construida es un buen predictor del precio por metro cuadrado, pero se deben considerar valores atípicos que podrían influir en el modelo de regresión.

# Precio vs Estrato
p2 <- ggplot(df, aes(x = factor(estrato), y = preciom)) +
  geom_boxplot(fill = "skyblue") +
  labs(title = "Distribución del Precio por Estrato", x = "Estrato", y = "Precio por metro cuadrado")
p2_plotly <- ggplotly(p2)
p2_plotly
  • Muestra la distribución del precio por metro cuadrado en diferentes estratos socioeconómicos.
  • Se observan diferencias significativas en los precios entre estratos, con valores más altos en estratos más altos.
  • Hay presencia de valores atípicos.
# Precio vs Número de Baños
p3 <- ggplot(df, aes(x = banios, y = preciom)) +
  geom_jitter(alpha = 0.5, color = 'red') +
  labs(title = "Precio vs Número de Baños", x = "Número de Baños", y = "Precio por metro cuadrado")
p3_plotly <- ggplotly(p3)
p3_plotly
  • Cada punto representa una propiedad.
  • Parece haber una ligera tendencia creciente, lo que sugiere que un mayor número de baños podría estar relacionado con un precio por metro cuadrado más alto.
# Precio vs Número de Habitaciones
p4 <- ggplot(df, aes(x = habitaciones, y = preciom)) +
  geom_jitter(alpha = 0.5, color = 'green') +
  labs(title = "Precio vs Número de Habitaciones", x = "Número de Habitaciones", y = "Precio por metro cuadrado")
p4_plotly <- ggplotly(p4)
p4_plotly
  • Similar al gráfico anterior, pero con el número de habitaciones como variable explicativa.
  • Se observa una tendencia dispersa, sin una clara relación lineal.

4.3 Análisis Exploratorio de Datos: Relación entre Precio y Variables Clave

  1. Distribución del Precio por Estrato

El boxplot generado muestra la distribución del precio por metro cuadrado en diferentes estratos socioeconómicos. Se pueden observar las siguientes tendencias:

  • A medida que el estrato socioeconómico aumenta, el precio por metro cuadrado también tiende a incrementarse.
  • Existen valores atípicos en todos los estratos, indicando propiedades con precios significativamente más altos que el promedio.
  • La variabilidad del precio es mayor en estratos superiores, lo que sugiere una mayor dispersión de valores en el mercado inmobiliario de estas zonas. Este patrón es consistente con la expectativa de que en estratos más altos se encuentran propiedades con mejores condiciones y ubicaciones privilegiadas, lo que justifica su mayor precio.
  1. Relación entre Precio y Número de Baños

El gráfico de dispersión muestra la relación entre el precio por metro cuadrado y el número de baños en las propiedades analizadas. Los hallazgos clave son:

  • Se observa una ligera tendencia creciente, lo que sugiere que un mayor número de baños puede estar asociado con un precio más alto.
  • Sin embargo, la relación no es completamente lineal, ya que hay una gran variabilidad de precios dentro de cada categoría de número de baños.
  • Algunas propiedades con pocos baños tienen precios elevados, lo que indica que otros factores influyen significativamente en el valor de las viviendas. Este resultado indica que, aunque el número de baños es un factor a considerar, no es el único determinante del precio.
  • Relación entre Precio y Número de Habitaciones Otro gráfico de dispersión analiza la relación entre el precio por metro cuadrado y el número de habitaciones. Se pueden notar los siguientes aspectos:
  • No se aprecia una relación clara o lineal entre el número de habitaciones y el precio.
  • La mayoría de las propiedades tienen entre 2 y 6 habitaciones, pero dentro de cada categoría de habitaciones hay una amplia variabilidad en los precios.
  • Propiedades con un número similar de habitaciones pueden tener precios muy distintos, lo que indica la influencia de otras variables como ubicación, acabados, antigüedad y demanda en la zona.

Estos resultados sugieren que el número de habitaciones, por sí solo, no es un fuerte predictor del precio por metro cuadrado.

Conclusiones del Análisis Exploratorio

  • El estrato socioeconómico es una variable con fuerte relación con el precio, mostrando un claro aumento del valor en estratos más altos.
  • El número de baños parece estar relacionado con el precio, aunque no de manera determinante.
  • El número de habitaciones no muestra una relación clara con el precio, lo que indica que otros factores pueden ser más relevantes. Estos hallazgos permitirán orientar la selección de variables para el modelo de regresión, priorizando aquellas con mayor poder explicativo en la determinación del precio de las viviendas en la zona norte de Cali.

4.4 Caso 2

Características del Caso 2
Características Caso.2
Tipo Apartamento
Área construida (m2) 300
Parqueaderos 3
Baños 3
Habitaciones 5
Estrato 5 o 6
Zona Sur
Crédito preaprobado 850 millones
# Hacer una copia de la base de datos original
vivienda_apartamentos<- vivienda_redistribuida 
colnames(vivienda_apartamentos)
##  [1] "id"           "zona"         "piso"         "estrato"      "preciom"     
##  [6] "areaconst"    "parqueaderos" "banios"       "habitaciones" "tipo"        
## [11] "barrio"       "geometry"     "ZonaRed"      "longitud"     "latitud"
# Filtrar solo casas en la Zona Norte
vivienda_apartamentos <- vivienda_apartamentos %>%
  filter(tipo == "Apartamento", ZonaRed == "Zona Sur")

str(vivienda_apartamentos)  # Estructura de la base de datos
## 'data.frame':    1965 obs. of  15 variables:
##  $ id          : num  1724 4386 5424 6043 6412 ...
##  $ zona        : chr  "Zona Norte" "Zona Norte" "Zona Norte" "Zona Norte" ...
##  $ piso        : chr  "01" "01" "03" "05" ...
##  $ estrato     : num  5 5 4 5 6 5 4 4 5 5 ...
##  $ preciom     : num  240 310 320 420 620 170 220 162 225 250 ...
##  $ areaconst   : num  87 137 108 124 135 56 75 60 65 75 ...
##  $ parqueaderos: num  1 2 2 3 2 1 0 0 0 0 ...
##  $ banios      : num  3 3 3 3 3 1 2 2 2 2 ...
##  $ habitaciones: num  3 4 3 3 4 2 3 3 3 3 ...
##  $ tipo        : chr  "Apartamento" "Apartamento" "Apartamento" "Apartamento" ...
##  $ barrio      : chr  "acopi" "acopi" "acopi" "acopi" ...
##  $ geometry    :sfc_POINT of length 1965; first list element:  'XY' num  -76.52 3.37
##  $ ZonaRed     : chr  "Zona Sur" "Zona Sur" "Zona Sur" "Zona Sur" ...
##  $ longitud    : num  -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num  3.37 3.38 3.41 3.41 3.34 ...
##  - attr(*, "sf_column")= chr "geometry"
##  - attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA NA ...
##   ..- attr(*, "names")= chr [1:14] "id" "zona" "piso" "estrato" ...
summary(vivienda_apartamentos)  # Resumen estadístico
##        id           zona               piso              estrato     
##  Min.   :1250   Length:1965        Length:1965        Min.   :3.000  
##  1st Qu.:2175   Class :character   Class :character   1st Qu.:4.000  
##  Median :3543   Mode  :character   Mode  :character   Median :5.000  
##  Mean   :3720                                         Mean   :4.779  
##  3rd Qu.:5240                                         3rd Qu.:5.000  
##  Max.   :7346                                         Max.   :6.000  
##     preciom         areaconst       parqueaderos        banios     
##  Min.   :  70.0   Min.   : 40.00   Min.   : 0.000   Min.   :0.000  
##  1st Qu.: 190.0   1st Qu.: 68.00   1st Qu.: 1.000   1st Qu.:2.000  
##  Median : 260.0   Median : 86.00   Median : 1.000   Median :2.000  
##  Mean   : 318.5   Mean   : 98.94   Mean   : 1.226   Mean   :2.554  
##  3rd Qu.: 350.0   3rd Qu.:110.00   3rd Qu.: 2.000   3rd Qu.:3.000  
##  Max.   :1750.0   Max.   :605.00   Max.   :10.000   Max.   :7.000  
##   habitaciones       tipo              barrio                   geometry   
##  Min.   :0.000   Length:1965        Length:1965        POINT        :1965  
##  1st Qu.:3.000   Class :character   Class :character   epsg:4326    :   0  
##  Median :3.000   Mode  :character   Mode  :character   +proj=long...:   0  
##  Mean   :2.951                                                             
##  3rd Qu.:3.000                                                             
##  Max.   :6.000                                                             
##    ZonaRed             longitud         latitud     
##  Length:1965        Min.   :-76.55   Min.   :3.334  
##  Class :character   1st Qu.:-76.54   1st Qu.:3.366  
##  Mode  :character   Median :-76.53   Median :3.372  
##                     Mean   :-76.53   Mean   :3.375  
##                     3rd Qu.:-76.52   3rd Qu.:3.385  
##                     Max.   :-76.51   Max.   :3.415
glimpse(vivienda_apartamentos)  # Vista rápida del contenido
## Rows: 1,965
## Columns: 15
## $ id           <dbl> 1724, 4386, 5424, 6043, 6412, 1770, 1353, 1391, 1489, 149…
## $ zona         <chr> "Zona Norte", "Zona Norte", "Zona Norte", "Zona Norte", "…
## $ piso         <chr> "01", "01", "03", "05", "05", "06", "01", "01", "01", "01…
## $ estrato      <dbl> 5, 5, 4, 5, 6, 5, 4, 4, 5, 5, 5, 4, 5, 4, 4, 5, 5, 4, 4, …
## $ preciom      <dbl> 240, 310, 320, 420, 620, 170, 220, 162, 225, 250, 325, 22…
## $ areaconst    <dbl> 87, 137, 108, 124, 135, 56, 75, 60, 65, 75, 107, 84, 90, …
## $ parqueaderos <dbl> 1, 2, 2, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ banios       <dbl> 3, 3, 3, 3, 3, 1, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, …
## $ habitaciones <dbl> 3, 4, 3, 3, 4, 2, 3, 3, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, 2, …
## $ tipo         <chr> "Apartamento", "Apartamento", "Apartamento", "Apartamento…
## $ barrio       <chr> "acopi", "acopi", "acopi", "acopi", "acopi", "acopi", "ac…
## $ geometry     <POINT [°]> POINT (-76.517 3.36971), POINT (-76.53105 3.38296),…
## $ ZonaRed      <chr> "Zona Sur", "Zona Sur", "Zona Sur", "Zona Sur", "Zona Sur…
## $ longitud     <dbl> -76.51700, -76.53105, -76.53638, -76.54015, -76.54252, -7…
## $ latitud      <dbl> 3.36971, 3.38296, 3.40770, 3.40970, 3.34299, 3.37251, 3.3…
colSums(is.na(vivienda_apartamentos))  # Cuenta los NAs por columna
##           id         zona         piso      estrato      preciom    areaconst 
##            0            0            0            0            0            0 
## parqueaderos       banios habitaciones         tipo       barrio     geometry 
##            0            0            0            0            0            0 
##      ZonaRed     longitud      latitud 
##            0            0            0
# Seleccionar columnas de interés
df2 <- vivienda_apartamentos %>%
  select(preciom, areaconst, estrato, banios, habitaciones)
# Matriz de correlación
cor_matrix2 <- cor(df2, use = "complete.obs")
print(cor_matrix2)
##                preciom areaconst   estrato    banios habitaciones
## preciom      1.0000000 0.8200215 0.6720870 0.7394536    0.3384449
## areaconst    0.8200215 1.0000000 0.5475383 0.6948584    0.4076364
## estrato      0.6720870 0.5475383 1.0000000 0.5969642    0.2403555
## banios       0.7394536 0.6948584 0.5969642 1.0000000    0.4894832
## habitaciones 0.3384449 0.4076364 0.2403555 0.4894832    1.0000000
# Visualizar la matriz de correlación-----
ggcorrplot(cor_matrix2, 
           method = "circle",  # Puedes cambiar a "circle"
           type = "lower",
           lab = TRUE, lab_size = 4, 
           colors = c("red", "white", "blue"),
           title = "Matriz de Correlación - Apartamentos zona Sur", 
           ggtheme = theme_minimal())

# Precio vs Área Construida
p12 <- ggplot(df, aes(x = areaconst, y = preciom)) +
  geom_point(alpha = 0.5, color = 'blue') +
  labs(title = "Precio vs Área Construida zona sur ", x = "Área Construida (m2)", y = "Precio por metro cuadrado")
p12_plotly <- ggplotly(p12)
p12_plotly
# Precio vs Estrato
p22 <- ggplot(df, aes(x = factor(estrato), y = preciom)) +
  geom_boxplot(fill = "skyblue") +
  labs(title = "Distribución del Precio por Estrato zona Sur", x = "Estrato", y = "Precio por metro cuadrado")
p22_plotly <- ggplotly(p22)
p22_plotly
# Precio vs Número de Baños
p32 <- ggplot(df, aes(x = banios, y = preciom)) +
  geom_jitter(alpha = 0.5, color = 'red') +
  labs(title = "Precio vs Número de Baños Zona Sur", x = "Número de Baños", y = "Precio por metro cuadrado")
p32_plotly <- ggplotly(p32)
p32_plotly
# Precio vs Número de Habitaciones
p42 <- ggplot(df, aes(x = habitaciones, y = preciom)) +
  geom_jitter(alpha = 0.5, color = 'green') +
  labs(title = "Precio vs Número de Habitaciones Zona Sur", x = "Número de Habitaciones", y = "Precio por metro cuadrado")
p42_plotly <- ggplotly(p42)
p42_plotly

5 Requerimiento 3

Estime un modelo de regresión lineal múltiple con las variables del punto anterior:

\[ \text{precio} = f(\text{área construida}, \text{estrato}, \text{número de cuartos}, \text{número de parqueaderos}, \text{número de baños}) \]

e interprete los coeficientes si son estadísticamente significativos.

Las interpretaciones deben estar contextualizadas y se debe discutir si los resultados son lógicos.

Adicionalmente, interprete el coeficiente \(R^2\) y discuta el ajuste del modelo e implicaciones (qué podrían hacer para mejorarlo).

# Filtrar por Zona Norte y tipo Casa
vivienda_norte_casas <- vivienda_redistribuida %>%
        filter(ZonaRed == "Zona Norte", tipo == "Casa")

# 1. Verificar valores NA en las variables de interés
colSums(is.na(vivienda_norte_casas[, c("preciom", "areaconst", "estrato", "habitaciones", "parqueaderos", "banios")]))
##      preciom    areaconst      estrato habitaciones parqueaderos       banios 
##            0            0            0            0            0            0
#2.estimar modelo con las variables: área construida, estrato, número de parqueaderos, número de baños.
modelo1 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, 
             data = vivienda_norte_casas)

summary(modelo1)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = vivienda_norte_casas)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -917.89  -73.86  -15.42   35.12 1129.07 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -170.32280   36.33205  -4.688 3.45e-06 ***
## areaconst       0.80380    0.04969  16.176  < 2e-16 ***
## estrato        72.46792    8.93096   8.114 3.00e-15 ***
## habitaciones   -1.31170    5.16698  -0.254  0.79969    
## parqueaderos   22.49742    4.86174   4.627 4.58e-06 ***
## banios         18.49152    6.80958   2.716  0.00682 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 161.1 on 572 degrees of freedom
## Multiple R-squared:  0.6563, Adjusted R-squared:  0.6533 
## F-statistic: 218.5 on 5 and 572 DF,  p-value: < 2.2e-16

MODELO 1

\[ \text{Precio} = -170.32 + 0.80 \times \text{areaconst} + 72.47 \times \text{estrato} + 22.49 \times \text{parqueaderos} + 18.49 \times \text{banios} + \varepsilon \]

Los coeficientes indican cómo cada variable influye en el precio de la vivienda. El intercepto no tiene sentido práctico, el área construida y el estrato tienen un impacto positivo y significativo en el precio, mientras que el número de habitaciones no parece relevante. Tener parqueaderos y baños adicionales sí aumenta el precio de manera significativa. El modelo explica el 65.63% de la variabilidad del precio (R²), lo cual es moderado, pero hay otros factores que podrían mejorar la predicción, como la ubicación y los materiales. Además, la variable habitaciones podría reconsiderarse.

Se plantea segundo modelo sin la variable habitaciones queda así: el precio se estima con un intercepto de -174.93 y aumenta con el área construida (0.800 por m²), el estrato (73.17 por nivel), los parqueaderos (22.44 por cada uno) y los baños (17.54 por cada uno). La eliminación de habitaciones no afectó el ajuste del modelo, ya que R², los coeficientes y el error estándar residual se mantuvieron casi iguales. Esto confirma que habitaciones no aportaba información útil.

modelo2 <- lm(preciom ~ areaconst + estrato + parqueaderos + banios, 
              data = vivienda_norte_casas)

summary(modelo2)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + parqueaderos + banios, 
##     data = vivienda_norte_casas)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -919.52  -73.87  -15.48   35.12 1126.34 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -174.92509   31.46001  -5.560 4.14e-08 ***
## areaconst       0.80024    0.04763  16.800  < 2e-16 ***
## estrato        73.17145    8.48317   8.625  < 2e-16 ***
## parqueaderos   22.43891    4.85231   4.624 4.65e-06 ***
## banios         17.53795    5.67523   3.090   0.0021 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 161 on 573 degrees of freedom
## Multiple R-squared:  0.6563, Adjusted R-squared:  0.6539 
## F-statistic: 273.5 on 4 and 573 DF,  p-value: < 2.2e-16

MODELO 2

\[ \text{Precio} = -174.93 + 0.80 \times \text{areaconst} + 73.17 \times \text{estrato} + 22.44 \times \text{parqueaderos} + 17.54 \times \text{banios} + \varepsilon \]

Se plantea un modelo 3 con transformación logaritmica, en este se usa log_preciom y log_areaconst

vivienda_modelo <- vivienda_norte_casas  # Crear una copia
vivienda_modelo$log_preciom <- log(vivienda_modelo$preciom)
vivienda_modelo$log_areaconst <- log(vivienda_modelo$areaconst)
modelo3 <- lm(log_preciom ~ log_areaconst + estrato + parqueaderos + banios, data = vivienda_modelo)
summary(modelo3)
## 
## Call:
## lm(formula = log_preciom ~ log_areaconst + estrato + parqueaderos + 
##     banios, data = vivienda_modelo)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.76903 -0.16864 -0.01433  0.13534  1.12494 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   2.275475   0.104766  21.720  < 2e-16 ***
## log_areaconst 0.508213   0.024472  20.767  < 2e-16 ***
## estrato       0.165433   0.014726  11.234  < 2e-16 ***
## parqueaderos  0.037913   0.008080   4.692 3.39e-06 ***
## banios        0.044230   0.009654   4.582 5.67e-06 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2673 on 573 degrees of freedom
## Multiple R-squared:  0.7877, Adjusted R-squared:  0.7862 
## F-statistic: 531.3 on 4 and 573 DF,  p-value: < 2.2e-16

MODELO 3

\[ \log(\text{precio}) = 1.459 + 0.669 \times \log(\text{área construida}) + 0.179 \times \text{estrato} + 0.090 \times \text{parqueaderos} + 0.068 \times \text{baños} \]

Este modelo explica el 86.17% de la variabilidad en el logaritmo del precio, mejorando significativamente el ajuste anterior. Como usa logaritmos, los coeficientes indican cambios porcentuales: un 1% más de área construida sube el precio un 0.67%, cada nivel de estrato lo aumenta en 17.9%, un parqueadero adicional en 8.97% y un baño en 6.85%. Los residuos son menores, lo que sugiere que el modelo predice mejor los precios.

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

Las pruebas de supuestos en modelos de regresión incluyen verificar la normalidad de los residuos (test de Shapiro-Wilk o gráfico Q-Q), homocedasticidad (prueba de Breusch-Pagan o gráfico de residuos), no colinealidad (VIF) y ausencia de autocorrelación (prueba de Durbin-Watson), a continuación realizaremos estas pruebas:

par(mfrow = c(2, 2))  # Dividir la pantalla en 4 gráficos

plot(modelo1)  # Modelo lineal sin habitaciones

plot(modelo2)  # Modelo lineal con menos variables

plot(modelo3)  # Modelo log-log

#prueba de normalidad
shapiro.test(resid(modelo1))  # Para el modelo 1
## 
##  Shapiro-Wilk normality test
## 
## data:  resid(modelo1)
## W = 0.79872, p-value < 2.2e-16
shapiro.test(resid(modelo2))  # Para el modelo 2
## 
##  Shapiro-Wilk normality test
## 
## data:  resid(modelo2)
## W = 0.79873, p-value < 2.2e-16
shapiro.test(resid(modelo3))  # Para el modelo 3
## 
##  Shapiro-Wilk normality test
## 
## data:  resid(modelo3)
## W = 0.9746, p-value = 1.831e-08
#prueba homocedasticidad
bptest(modelo1)  # Prueba de Breusch-Pagan para modelo 1
## 
##  studentized Breusch-Pagan test
## 
## data:  modelo1
## BP = 90.478, df = 5, p-value < 2.2e-16
bptest(modelo2)  # Prueba de Breusch-Pagan para modelo 2
## 
##  studentized Breusch-Pagan test
## 
## data:  modelo2
## BP = 90.077, df = 4, p-value < 2.2e-16
bptest(modelo3)  # Prueba de Breusch-Pagan para modelo 3
## 
##  studentized Breusch-Pagan test
## 
## data:  modelo3
## BP = 25.887, df = 4, p-value = 3.336e-05
# independencia de los residuos
dwtest(modelo1)  # Prueba de Durbin-Watson para modelo 1
## 
##  Durbin-Watson test
## 
## data:  modelo1
## DW = 1.7275, p-value = 0.0004411
## alternative hypothesis: true autocorrelation is greater than 0
dwtest(modelo2)  # Prueba de Durbin-Watson para modelo 2
## 
##  Durbin-Watson test
## 
## data:  modelo2
## DW = 1.725, p-value = 0.0003999
## alternative hypothesis: true autocorrelation is greater than 0
dwtest(modelo3)  # Prueba de Durbin-Watson para modelo 3
## 
##  Durbin-Watson test
## 
## data:  modelo3
## DW = 1.725, p-value = 0.0003934
## alternative hypothesis: true autocorrelation is greater than 0

Los tres modelos presentan problemas con los supuestos. La prueba de Shapiro-Wilk indica que los residuos no siguen una distribución normal (p < 2.2e-16 en todos los casos), aunque el modelo 3 tiene un W más alto, lo que sugiere una mejor aproximación a la normalidad. La prueba de Breusch-Pagan muestra heterocedasticidad significativa en los tres modelos (p < 2.2e-16), lo que implica que la varianza de los residuos no es constante. La prueba de Durbin-Watson señala autocorrelación positiva en los tres modelos (p < 0.05), con el modelo 3 mostrando el mayor problema (DW = 1.5074). Esto indica que los residuos no son completamente independientes, lo que podría afectar la validez de las inferencias.

Para mejorar el ajuste, se recomienda transformar variables, se pueden aplicar transformaciones como logaritmos o Box-Cox para mejorar la normalidad de los residuos y estabilizar la varianza. Si hay heterocedasticidad, se pueden usar errores estándar robustos o regresión ponderada. Para la autocorrelación, se recomienda incluir términos autorregresivos, probar modelos ARIMA o aplicar estimadores como Prais-Winsten. Si hay multicolinealidad, calcular el VIF y eliminar variables redundantes o usar regresión ridge. También se puede mejorar la especificación del modelo incluyendo variables relevantes, términos polinómicos o interacciones. Finalmente, comparar modelos alternativos como regresión robusta o cuantílica puede ser útil si los problemas persisten.

7 Requerimiento 5

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

# Crear data frame con valores a predecir
solicitud1 <- data.frame(
        areaconst = 200,
        parqueaderos = 1,
        banios = 2,
        habitaciones = 4,
        estrato = c(4, 5)  # Se hacen dos predicciones, una para cada estrato
)
# Predicción con el Modelo 1 
pred_modelo1 <- predict(modelo1, newdata = solicitud1)
# Predicción con el Modelo 2
pred_modelo2 <- predict(modelo2, newdata = solicitud1)
# Mostrar resultados
data.frame(estrato = c(4,5), precio_modelo1 = pred_modelo1, precio_modelo2 = pred_modelo2)
##   estrato precio_modelo1 precio_modelo2
## 1       4       334.5417       335.3229
## 2       5       407.0096       408.4943
# Agregar log_areaconst
solicitud1$log_areaconst <- log(solicitud1$areaconst)

# Predicción en escala logarítmica
log_pred_modelo3 <- predict(modelo3, newdata = solicitud1)

# Transformar de log(precio) a precio real
pred_modelo3 <- exp(log_pred_modelo3)
# Mostrar resultados
data.frame(estrato = c(4,5), precio_modelo3 = pred_modelo3)
##   estrato precio_modelo3
## 1       4       316.1625
## 2       5       373.0415

Según los resultados obtenidos el cliente podría obtener las casas de estrato 4 para el modelo 1 y 2, en cambio para el modelo 3 no podría adquirir una casa con las características deseadas debido a que el menor valor corresponde a 383.4801 millones excediendo en más de 33 millones su presupuesto.

8 Requerimiento 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.

MODELO 1

# Generar las predicciones para toda la base de apartamentos en la Zona Norte
vivienda_norte_casas_modelo1 <- vivienda_norte_casas %>%
        mutate(precio_estimado_1 = predict(modelo1, newdata = vivienda_norte_casas))

# Filtrar apartamentos cuyo precio estimado está dentro del crédito aprobado
ofertas_potenciales1 <- vivienda_norte_casas_modelo1 %>%
        filter(precio_estimado_1 <= 350) %>%
        select(longitud, latitud, precio_estimado_1, areaconst, estrato, habitaciones, banios) %>%
        arrange(desc(precio_estimado_1)) %>%
        head(5)  # Seleccionar las 5 mejores opciones
# Crear mapa con leaflet
leaflet(ofertas_potenciales1) %>%
        addTiles() %>%
        addCircleMarkers(
                ~longitud, ~latitud,
                radius = 5,
                color = "blue",
                label = ~paste0("Precio: ", round(precio_estimado_1, 2), "M | Área: ", areaconst, "m² | Estrato: ", estrato, " | Hab: ", habitaciones, " | Baños: ", banios),
                popup = ~paste0("Precio: ", round(precio_estimado_1, 2), "M <br> Área: ", areaconst, "m² <br> Estrato: ", estrato, " <br> Habitaciones: ", habitaciones, " <br> Baños: ", banios)
        ) %>%
        addProviderTiles(providers$CartoDB.Positron)
ofertas_potenciales1
##    longitud latitud precio_estimado_1 areaconst estrato habitaciones banios
## 1 -76.50235 3.47350          345.7651       240       3            7      5
## 2 -76.49500 3.46700          345.6295        99       5            3      3
## 3 -76.51661 3.48049          344.9659       290       3            7      4
## 4 -76.52616 3.46978          344.8257        98       5            3      3
## 5 -76.49920 3.49241          343.3625       265       3            7      5

MODELO 2

# Generar las predicciones para toda la base de apartamentos en la Zona Norte
vivienda_norte_casas_modelo2 <- vivienda_norte_casas %>%
        mutate(precio_estimado_2 = predict(modelo2, newdata = vivienda_norte_casas))

# Filtrar apartamentos cuyo precio estimado está dentro del crédito aprobado
ofertas_potenciales2 <- vivienda_norte_casas_modelo2 %>%
        filter(precio_estimado_2 <= 350) %>%
        select(longitud, latitud, precio_estimado_2, areaconst, estrato, habitaciones, banios) %>%
        arrange(desc(precio_estimado_2)) %>%
        head(5)  # Seleccionar las 5 mejores opciones
# Crear mapa con leaflet
leaflet(ofertas_potenciales2) %>%
        addTiles() %>%
        addCircleMarkers(
                ~longitud, ~latitud,
                radius = 5,
                color = "blue",
                label = ~paste0("Precio: ", round(precio_estimado_2, 2), "M | Área: ", areaconst, "m² | Estrato: ", estrato, " | Hab: ", habitaciones, " | Baños: ", banios),
                popup = ~paste0("Precio: ", round(precio_estimado_2, 2), "M <br> Área: ", areaconst, "m² <br> Estrato: ", estrato, " <br> Habitaciones: ", habitaciones, " <br> Baños: ", banios)
        ) %>%
        addProviderTiles(providers$CartoDB.Positron)
ofertas_potenciales2
##    longitud latitud precio_estimado_2 areaconst estrato habitaciones banios
## 1 -76.51661 3.48049          346.8097       290       3            7      4
## 2 -76.50235 3.47350          346.7748       240       3            7      5
## 3 -76.49500 3.46700          345.2084        99       5            3      3
## 4 -76.52616 3.46978          344.4081        98       5            3      3
## 5 -76.49920 3.49241          344.3418       265       3            7      5

MODELO 3

# Generar las predicciones para toda la base de apartamentos en la Zona Sur
vivienda_norte_casas_modelo1 <- vivienda_norte_casas %>%
        mutate(precio_estimado_1 = predict(modelo1, newdata = vivienda_norte_casas))

# Filtrar apartamentos cuyo precio estimado está dentro del crédito aprobado
ofertas_potenciales1 <- vivienda_norte_casas_modelo1 %>%
        filter(precio_estimado_1 <= 350) %>%
        select(longitud, latitud, precio_estimado_1, areaconst, estrato, habitaciones, banios, parqueaderos) %>%
        arrange(desc(precio_estimado_1)) %>%
        head(5)  # Seleccionar las 5 mejores opciones

# Crear mapa con leaflet
leaflet(ofertas_potenciales1) %>%
        addTiles() %>%
        addCircleMarkers(
                ~longitud, ~latitud,
                radius = 5,
                color = "blue",
                label = ~paste0("Precio: ", round(precio_estimado_1, 2), "M | Área: ", areaconst, "m² | Estrato: ", estrato, " | Hab: ", habitaciones, " | Baños: ", banios," | Parqueaderos: ", parqueaderos),
                popup = ~paste0("Precio: ", round(precio_estimado_1, 2), "M <br> Área: ", areaconst, "m² <br> Estrato: ", estrato, " <br> Habitaciones: ", habitaciones, " <br> Baños: ", banios)
        ) %>%
        addProviderTiles(providers$CartoDB.Positron)
print(ofertas_potenciales1)
##    longitud latitud precio_estimado_1 areaconst estrato habitaciones banios
## 1 -76.50235 3.47350          345.7651       240       3            7      5
## 2 -76.49500 3.46700          345.6295        99       5            3      3
## 3 -76.51661 3.48049          344.9659       290       3            7      4
## 4 -76.52616 3.46978          344.8257        98       5            3      3
## 5 -76.49920 3.49241          343.3625       265       3            7      5
##   parqueaderos
## 1            1
## 2            1
## 3            0
## 4            1
## 5            0
# Generar las predicciones para toda la base de apartamentos en la Zona Sur
vivienda_norte_casas_modelo2 <- vivienda_norte_casas %>%
        mutate(precio_estimado_2 = predict(modelo2, newdata = vivienda_norte_casas))

# Filtrar apartamentos cuyo precio estimado está dentro del crédito aprobado
ofertas_potenciales2 <- vivienda_norte_casas_modelo2 %>%
        filter(precio_estimado_2 <= 350) %>%
        select(longitud, latitud, precio_estimado_2, areaconst, estrato, habitaciones, banios, parqueaderos) %>%
        arrange(desc(precio_estimado_2)) %>%
        head(5)  # Seleccionar las 5 mejores opciones

# Crear mapa con leaflet
leaflet(ofertas_potenciales2) %>%
        addTiles() %>%
        addCircleMarkers(
                ~longitud, ~latitud,
                radius = 5,
                color = "blue",
                label = ~paste0("Precio: ", round(precio_estimado_2, 2), "M | Área: ", areaconst, "m² | Estrato: ", estrato, " | Hab: ", habitaciones, " | Baños: ", banios," | Parqueaderos: ", parqueaderos),
                popup = ~paste0("Precio: ", round(precio_estimado_2, 2), "M <br> Área: ", areaconst, "m² <br> Estrato: ", estrato, " <br> Habitaciones: ", habitaciones, " <br> Baños: ", banios)
        ) %>%
        addProviderTiles(providers$CartoDB.Positron)
print(ofertas_potenciales2)
##    longitud latitud precio_estimado_2 areaconst estrato habitaciones banios
## 1 -76.51661 3.48049          346.8097       290       3            7      4
## 2 -76.50235 3.47350          346.7748       240       3            7      5
## 3 -76.49500 3.46700          345.2084        99       5            3      3
## 4 -76.52616 3.46978          344.4081        98       5            3      3
## 5 -76.49920 3.49241          344.3418       265       3            7      5
##   parqueaderos
## 1            0
## 2            1
## 3            1
## 4            1
## 5            0
#Modelo 3
# Modelo 3
# Generar las predicciones para toda la base de viviendas en la Zona Norte
vivienda_norte_casas <- vivienda_norte_casas %>%
        mutate(log_areaconst = log(areaconst))

# Aplicar el modelo 3 para estimar precios
vivienda_norte_casas_modelo3 <- vivienda_norte_casas %>%
        mutate(precio_estimado_31 = predict(modelo3, newdata = vivienda_norte_casas))

# Transformar de nuevo los valores a su escala original
vivienda_norte_casas_modelo3 <- vivienda_norte_casas_modelo3 %>%
        mutate(precio_estimado_31 = exp(precio_estimado_31),
               areaconst = exp(log_areaconst)) 

# Filtrar casas que cumplan con las condiciones del crédito
ofertas_potenciales3 <- vivienda_norte_casas_modelo3 %>%
        filter(precio_estimado_31 <= 350) %>%
        select(longitud, latitud, precio_estimado_31, areaconst, estrato, habitaciones, banios, parqueaderos) %>%
        arrange(desc(precio_estimado_31)) %>%
        head(5)  # Seleccionar las 5 mejores opciones

# Crear mapa con leaflet
leaflet(ofertas_potenciales3) %>%
        addTiles() %>%
        addCircleMarkers(
                ~longitud, ~latitud,
                radius = 5,
                color = "blue",
                label = ~paste0("Precio: ", round(precio_estimado_31, 2), "M | Área: ", round(areaconst, 2), "m² | Estrato: ", estrato, " | Hab: ", habitaciones, " | Baños: ", banios, " | Parqueaderos: ", parqueaderos),
                popup = ~paste0("Precio: ", round(precio_estimado_31, 2), "M <br> Área: ", round(areaconst, 2), "m² <br> Estrato: ", estrato, " <br> Habitaciones: ", habitaciones, " <br> Baños: ", banios, " <br> Parqueaderos: ", parqueaderos)
        ) %>%
        addProviderTiles(providers$CartoDB.Positron)
ofertas_potenciales3
##    longitud latitud precio_estimado_31 areaconst estrato habitaciones banios
## 1 -76.53504 3.48958           349.8938       150       5            4      3
## 2 -76.51568 3.48876           348.1091       160       5            4      3
## 3 -76.51273 3.48960           348.0828       158       5            3      4
## 4 -76.52720 3.48433           346.8576       240       4            3      2
## 5 -76.52400 3.48400           346.0965       145       5            4      4
##   parqueaderos
## 1            2
## 2            1
## 3            0
## 4            1
## 5            1

Estas son las mejores opciones disponibles según el modelo, considerando las características y el presupuesto establecido. Aunque algunas propiedades no cumplen con todos los requisitos inicialmente definidos, han sido seleccionadas por su alto potencial y relación calidad-precio dentro del rango determinado. Es importante destacar que, en muchos casos, pequeñas modificaciones o ajustes en las condiciones pueden hacer que una vivienda se acerque más a las expectativas deseadas. Por ejemplo, algunas casas podrían aumentar su valor si contaran con más parqueaderos o mejoras en su infraestructura. Además, es posible que con un ligero incremento en el presupuesto se puedan encontrar opciones que se ajusten completamente a los criterios de búsqueda. Evaluar estas alternativas con una perspectiva flexible puede ayudar a tomar una mejor decisión de compra, maximizando el beneficio obtenido en función de la inversión disponible.