Punto 1: Filtro de Base de Datos - Casas Zona Norte

Primero, verificamos los datos, ademas

Se filtra para que nos indique los datos

de la zona norte

Carga de Librerías y Datos

# Carga de librerías
library(devtools)
library(paqueteMODELOS)
library(dplyr)
library(leaflet)
library(knitr)

# Carga de datos
data("vivienda")
cat("Base de datos original:", nrow(vivienda), "registros\n")
## Base de datos original: 8322 registros
# Filtrar para obtener solo casas de la zona norte
base1 <- vivienda %>%
  filter(tipo == "Casa", zona == "Norte")

cat("Base filtrada (casas zona norte):", nrow(base1), "registros\n")
## Base filtrada (casas zona norte): 0 registros

2. Exploración

Se realiza una exploración, para verificar el tipo de valores

Encontrando valores como “Casa”, “Apartamento” y las diferentes

zonas donde estan ubicadas.

Se observa en la tabla, las frecuencias de las casas, apartamentos y

cada una de las zonas donde estan ubicadas. Esto con la finalidad, de

verificar la oferta inmobiliara de la ciudad en especial de la zona norte

y los tipos de vivienda.

cat("Valores únicos en 'tipo':\n")
## Valores únicos en 'tipo':
unique(vivienda$tipo)
## [1] "Casa"        "Apartamento" NA
cat("\nValores únicos en 'zona':\n")
## 
## Valores únicos en 'zona':
unique(vivienda$zona)
## [1] "Zona Oriente" "Zona Sur"     "Zona Norte"   "Zona Oeste"   "Zona Centro" 
## [6] NA
cat("\nFrecuencia de tipos de vivienda:\n")
## 
## Frecuencia de tipos de vivienda:
table(vivienda$tipo) %>% kable()
## Warning in attr(x, "align"): 'xfun::attr()' está en desuso.
## Utilizar 'xfun::attr2()' en su lugar.
## Ver help("Deprecated")
## Warning in attr(x, "format"): 'xfun::attr()' está en desuso.
## Utilizar 'xfun::attr2()' en su lugar.
## Ver help("Deprecated")
Var1 Freq
Apartamento 5100
Casa 3219
cat("\nFrecuencia de zonas:\n")
## 
## Frecuencia de zonas:
table(vivienda$zona) %>% kable()
## Warning in attr(x, "align"): 'xfun::attr()' está en desuso.
## Utilizar 'xfun::attr2()' en su lugar.
## Ver help("Deprecated")
## Warning in attr(x, "align"): 'xfun::attr()' está en desuso.
## Utilizar 'xfun::attr2()' en su lugar.
## Ver help("Deprecated")
Var1 Freq
Zona Centro 124
Zona Norte 1920
Zona Oeste 1198
Zona Oriente 351
Zona Sur 4726

3.Filtro base de datos

Se filtra la base de datos para verificar cuantos registros

existe de interes para el estudio (zona norte)

# Filtrar con los valores exactos
base1 <- vivienda %>%
  filter(tipo == "Casa", zona == "Zona Norte")

cat("Base filtrada (casas Zona Norte):", nrow(base1), "registros\n")
## Base filtrada (casas Zona Norte): 722 registros
# Verificar cuántos registros tenemos
if(nrow(base1) > 0) {
  cat("¡Filtro exitoso! Se encontraron", nrow(base1), "casas en Zona Norte\n")
} else {
  cat("No se encontraron registros. Revisar los valores en la exploración.\n")
}
## ¡Filtro exitoso! Se encontraron 722 casas en Zona Norte

4. Primeros registros

Se valida los primeros tres registros del dataset

if(nrow(base1) > 0) {
  cat("Primeros 3 registros de la base filtrada:\n")
  head(base1, 3) %>% 
    select(tipo, zona, preciom, areaconst, barrio, habitaciones, banios, parqueaderos, estrato) %>%
    kable(caption = "Primeras tres casas de la Zona Norte")
} else {
  cat("No hay registros para mostrar.\n")
}
## Primeros 3 registros de la base filtrada:
## Warning in attr(x, "align"): 'xfun::attr()' está en desuso.
## Utilizar 'xfun::attr2()' en su lugar.
## Ver help("Deprecated")
## Warning in attr(x, "format"): 'xfun::attr()' está en desuso.
## Utilizar 'xfun::attr2()' en su lugar.
## Ver help("Deprecated")
Primeras tres casas de la Zona Norte
tipo zona preciom areaconst barrio habitaciones banios parqueaderos estrato
Casa Zona Norte 320 150 acopi 6 4 2 5
Casa Zona Norte 780 380 acopi 3 3 2 5
Casa Zona Norte 750 445 acopi 6 7 NA 6

5. Verificación de filtros

Se prueba la efectividad del filtro al buscar

los valores de casas de la zona norte, tambien se hace

un resumen estadistico para verificar valores faltantes o

que afecten el analisis.

if(nrow(base1) > 0) {
  cat("Verificación del filtro por tipo de vivienda:\n")
  table(base1$tipo) %>% kable(caption = "Distribución por tipo de vivienda")
  
  cat("\nVerificación del filtro por zona:\n")
  table(base1$zona) %>% kable(caption = "Distribución por zona")
  
  cat("\nResumen de variables clave:\n")
  summary(base1 %>% select(preciom, areaconst, habitaciones, banios, parqueaderos, estrato)) %>% 
    kable(caption = "Resumen estadístico de variables numéricas")
} else {
  cat("No hay datos para verificar.\n")
}
## Verificación del filtro por tipo de vivienda:
## Warning in attr(x, "align"): 'xfun::attr()' está en desuso.
## Utilizar 'xfun::attr2()' en su lugar.
## Ver help("Deprecated")
## 
## Verificación del filtro por zona:
## Warning in attr(x, "align"): 'xfun::attr()' está en desuso.
## Utilizar 'xfun::attr2()' en su lugar.
## Ver help("Deprecated")
## 
## Resumen de variables clave:
## Warning in attr(x, "align"): 'xfun::attr()' está en desuso.
## Utilizar 'xfun::attr2()' en su lugar.
## Ver help("Deprecated")
## Warning in attr(x, "format"): 'xfun::attr()' está en desuso.
## Utilizar 'xfun::attr2()' en su lugar.
## Ver help("Deprecated")
Resumen estadístico de variables numéricas
preciom areaconst habitaciones banios parqueaderos estrato
Min. : 89.0 Min. : 30.0 Min. : 0.000 Min. : 0.000 Min. : 1.000 Min. :3.000
1st Qu.: 261.2 1st Qu.: 140.0 1st Qu.: 3.000 1st Qu.: 2.000 1st Qu.: 1.000 1st Qu.:3.000
Median : 390.0 Median : 240.0 Median : 4.000 Median : 3.000 Median : 2.000 Median :4.000
Mean : 445.9 Mean : 264.9 Mean : 4.507 Mean : 3.555 Mean : 2.182 Mean :4.202
3rd Qu.: 550.0 3rd Qu.: 336.8 3rd Qu.: 5.000 3rd Qu.: 4.000 3rd Qu.: 3.000 3rd Qu.:5.000
Max. :1940.0 Max. :1440.0 Max. :10.000 Max. :10.000 Max. :10.000 Max. :6.000
NA NA NA NA NA’s :287 NA

6. Mapa ubicaciones

Finalmente, se realiza un mapa con las ubicaciones

de cada una de las propieades en cada sector

if(nrow(base1) > 0) {
  # Crear mapa interactivo
  mapa_base1 <- leaflet(base1) %>%
    addTiles() %>%
    addCircleMarkers(
      lng = ~longitud, 
      lat = ~latitud,
      popup = ~paste(
        "<b>Precio:</b>", preciom, "millones<br>",
        "<b>Barrio:</b>", barrio, "<br>",
        "<b>Área:</b>", areaconst, "m²<br>",
        "<b>Habitaciones:</b>", habitaciones, "<br>",
        "<b>Baños:</b>", banios, "<br>",
        "<b>Parqueaderos:</b>", parqueaderos, "<br>",
        "<b>Estrato:</b>", estrato
      ),
      color = "blue",
      fillOpacity = 0.7,
      radius = 6,
      clusterOptions = markerClusterOptions()
    ) %>%
    setView(
      lng = mean(base1$longitud, na.rm = TRUE), 
      lat = mean(base1$latitud, na.rm = TRUE), 
      zoom = 12
    ) %>%
    addControl(
      "Casas - Zona Norte",
      position = "topright"
    )
  
  # Mostrar mapa
  mapa_base1
} else {
  cat("No hay datos para crear el mapa.\n")
}

Análisis de Validación de Ubicación

Se verifica las coordenadas de las zonas

if(nrow(base1) > 0) {
  # Verificar si hay puntos fuera de la zona norte
  coordenadas_limite <- data.frame(
    norte_max = max(base1$latitud, na.rm = TRUE),
    norte_min = min(base1$latitud, na.rm = TRUE),
    este_max = max(base1$longitud, na.rm = TRUE),
    este_min = min(base1$longitud, na.rm = TRUE)
  )
  
  cat("Límites geográficos de las propiedades:\n")
  kable(coordenadas_limite, caption = "Límites de coordenadas de las viviendas")
  
  # Contar registros con coordenadas fuera de rangos esperados
  latitud_media <- mean(base1$latitud, na.rm = TRUE)
  longitud_media <- mean(base1$longitud, na.rm = TRUE)
  
  cat("\nCoordenadas promedio de las viviendas:\n")
  cat("Latitud promedio:", round(latitud_media, 4), "\n")
  cat("Longitud promedio:", round(longitud_media, 4), "\n")
} else {
  cat("No hay datos para validación de ubicación.\n")
}
## Límites geográficos de las propiedades:
## Warning in attr(x, "align"): 'xfun::attr()' está en desuso.
## Utilizar 'xfun::attr2()' en su lugar.
## Ver help("Deprecated")
## 
## Coordenadas promedio de las viviendas:
## Latitud promedio: 3.4602 
## Longitud promedio: -76.5171

PUNTO 2.

Se realiza la limpieza de la base de datos

En la primer figura, se verifica los precios

de cada zona en función del área,se puede observar

que la zona sur presenta los valores con mayores precios

y mayor área ocupada.

En el grafico de bigotes, notamos que los estratos 3 presenta los precios más bajos

Mientras que el estrato 5, presenta la mayor variabilidad de precios

y brechas en el mercado.

En el ultimo grafico, el precio se ve afectado en su mayoria, por el estrato

y área construida.

# Paquetes para EDA
library(plotly)
## Warning: package 'plotly' was built under R version 4.4.3
## 
## Adjuntando el paquete: 'plotly'
## The following object is masked from 'package:ggplot2':
## 
##     last_plot
## The following object is masked from 'package:stats':
## 
##     filter
## The following object is masked from 'package:graphics':
## 
##     layout
library(tidyr)
library(forcats)

# Limpieza mínima y tipos
vivienda2 <- vivienda %>%
  mutate(
    zona = as.factor(zona),
    tipo = as.factor(tipo),
    estrato = as.integer(estrato)
  ) %>%
  select(preciom, areaconst, estrato, banios, habitaciones, parqueaderos, zona, tipo, barrio, latitud, longitud) %>%
  filter(complete.cases(.))

# 2.1 Dispersión interactiva Precio vs Área (color = zona)
p_scatter_area <- plot_ly(
  vivienda2,
  x = ~areaconst, y = ~preciom, color = ~zona, type = "scatter", mode = "markers",
  hoverinfo = "text",
  text = ~paste("Barrio:", barrio,
                "<br>Estrato:", estrato,
                "<br>Baños:", banios,
                "<br>Habitaciones:", habitaciones,
                "<br>Parqueaderos:", parqueaderos)
)
p_scatter_area
# 2.2 Boxplot interactivo Precio por Estrato
p_box_estrato <- plot_ly(vivienda2, x = ~as.factor(estrato), y = ~preciom, type = "box")
p_box_estrato
# 2.3 Heatmap de correlaciones (solo numéricas)
num_vars <- vivienda2 %>% select(preciom, areaconst, estrato, banios, habitaciones, parqueaderos)
corr_mat <- cor(num_vars, use = "pairwise.complete.obs")
p_heat <- plot_ly(
  x = colnames(corr_mat), y = rownames(corr_mat),
  z = corr_mat, type = "heatmap"
)
p_heat

PUNTO 3

En este item, se construye el modelo

Al realizar el modelo se obtiene un R cuadrado de 0.73

esto nos indica que el modelo logra predeccir los valores

Adecuadamente.

# Paquetes necesarios
library(broom)   # Para organizar resultados del modelo
library(car)     # Para VIF más adelante
## Warning: package 'car' was built under R version 4.4.3
## Cargando paquete requerido: carData
## Warning: package 'carData' was built under R version 4.4.3
## 
## Adjuntando el paquete: 'car'
## The following object is masked from 'package:dplyr':
## 
##     recode
## The following object is masked from 'package:boot':
## 
##     logit
# Ajustar el modelo de regresión lineal múltiple
mod_lm <- lm(preciom ~ areaconst + estrato + banios + habitaciones + parqueaderos + zona,
             data = vivienda2)

# Resumen del modelo
summary(mod_lm)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + banios + habitaciones + 
##     parqueaderos + zona, data = vivienda2)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1452.89   -85.65   -10.95    59.31  1018.96 
## 
## Coefficients:
##                    Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      -254.03343   24.79054 -10.247  < 2e-16 ***
## areaconst           0.85161    0.02158  39.459  < 2e-16 ***
## estrato            85.63039    2.98804  28.658  < 2e-16 ***
## banios             59.79635    2.55941  23.363  < 2e-16 ***
## habitaciones      -29.44326    2.22741 -13.219  < 2e-16 ***
## parqueaderos       74.71110    2.51343  29.725  < 2e-16 ***
## zonaZona Norte    -97.09282   22.35596  -4.343 1.43e-05 ***
## zonaZona Oeste     27.75437   23.04952   1.204   0.2286    
## zonaZona Oriente  -50.96421   25.37667  -2.008   0.0447 *  
## zonaZona Sur      -90.21347   22.13048  -4.076 4.63e-05 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 171.9 on 6707 degrees of freedom
## Multiple R-squared:  0.7371, Adjusted R-squared:  0.7368 
## F-statistic:  2090 on 9 and 6707 DF,  p-value: < 2.2e-16
# Tabla ordenada de coeficientes para el informe
tidy(mod_lm) %>% arrange(p.value)
## # A tibble: 10 × 5
##    term             estimate std.error statistic   p.value
##    <chr>               <dbl>     <dbl>     <dbl>     <dbl>
##  1 areaconst           0.852    0.0216     39.5  2.06e-306
##  2 parqueaderos       74.7      2.51       29.7  1.66e-182
##  3 estrato            85.6      2.99       28.7  1.72e-170
##  4 banios             59.8      2.56       23.4  3.96e-116
##  5 habitaciones      -29.4      2.23      -13.2  2.12e- 39
##  6 (Intercept)      -254.      24.8       -10.2  1.84e- 24
##  7 zonaZona Norte    -97.1     22.4        -4.34 1.43e-  5
##  8 zonaZona Sur      -90.2     22.1        -4.08 4.63e-  5
##  9 zonaZona Oriente  -51.0     25.4        -2.01 4.47e-  2
## 10 zonaZona Oeste     27.8     23.0         1.20 2.29e-  1
# R² y R² ajustado (qué tan bien explica el modelo)
glance(mod_lm)[, c("r.squared", "adj.r.squared", "sigma")]
## # A tibble: 1 × 3
##   r.squared adj.r.squared sigma
##       <dbl>         <dbl> <dbl>
## 1     0.737         0.737  172.

PUNTO 4

En el item 4, se realiza la validación del modelo

el grafico de residuos vs predicciones, se verifica que no tiene

un patrón (aleatorio) y en el grafico normaldiad de errores, estos los

los puntos no forman una linea

Sin embargo al realizar la prueba de heterocedasticidad, el modelo presenta errores

teniendo problemas con determinadas propieades. Mientras en la prueba multicolineal,

se determian que las distintas variables aportan información importante al modelo.

Es recomendable tener precaución con el modelo al determinar los precios de las

propieades, se recomienda realizar la transformación de variables.

library(lmtest)   # Para prueba Breusch-Pagan
## Warning: package 'lmtest' was built under R version 4.4.3
## Cargando paquete requerido: zoo
## Warning: package 'zoo' was built under R version 4.4.3
## 
## Adjuntando el paquete: 'zoo'
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
# 1. Residuos vs Ajustados (deberían verse dispersos alrededor de 0, sin forma curva)
plot(mod_lm, which = 1)

# 2. QQ-plot (residuos deberían seguir la diagonal si son normales)
plot(mod_lm, which = 2)

# 3. Prueba de heterocedasticidad (homogeneidad de varianzas)
bptest(mod_lm)
## 
##  studentized Breusch-Pagan test
## 
## data:  mod_lm
## BP = 1287.1, df = 9, p-value < 2.2e-16
# 4. Multicolinealidad (VIF: valores > 5 preocupan)
vif(mod_lm)
##                  GVIF Df GVIF^(1/(2*Df))
## areaconst    2.198314  1        1.482671
## estrato      1.829207  1        1.352482
## banios       2.836157  1        1.684089
## habitaciones 2.098907  1        1.448761
## parqueaderos 1.816922  1        1.347932
## zona         1.391059  4        1.042121

PUNTO 5

Se realizan, nuevas predecciones con las variables de los estratos 4 y 5

# Crear nuevas observaciones con estrato 4 y 5
new_v1_e4 <- data.frame(
  areaconst = 200, estrato = 4, banios = 2, habitaciones = 4, parqueaderos = 1,
  zona = factor("Zona Norte", levels = levels(vivienda2$zona))
)
new_v1_e5 <- data.frame(
  areaconst = 200, estrato = 5, banios = 2, habitaciones = 4, parqueaderos = 1,
  zona = factor("Zona Norte", levels = levels(vivienda2$zona))
)

# Predicciones con intervalos de confianza
pred_v1_e4 <- predict(mod_lm, new_v1_e4, interval = "prediction")
pred_v1_e5 <- predict(mod_lm, new_v1_e5, interval = "prediction")

pred_v1_e4
##       fit       lwr      upr
## 1 238.249 -98.91081 575.4088
pred_v1_e5
##        fit       lwr      upr
## 1 323.8794 -13.32727 661.0861

PUNTO 6

Se realiza el filtro inteligente, en las propieades en la zona norte

entre los distintos estratos, permitiendo así seleccionar la adecuada

segun el precio, área y numero de habitaciones.

# Agregar columna con predicción para todas las viviendas
vivienda2$pred_lm <- predict(mod_lm, vivienda2)

# Filtrar candidatas (condiciones similares a Vivienda 1)
ofertas_v1 <- vivienda2 %>%
  filter(tipo == "Casa",
         zona == "Zona Norte",
         areaconst >= 180 & areaconst <= 220,   # rango alrededor de 200 m²
         banios >= 2,
         habitaciones >= 4,
         parqueaderos >= 1,
         pred_lm <= 350)

# Mostrar primeras 5
head(ofertas_v1, 5)
## # A tibble: 5 × 12
##   preciom areaconst estrato banios habitaciones parqueaderos zona   tipo  barrio
##     <dbl>     <dbl>   <int>  <dbl>        <dbl>        <dbl> <fct>  <fct> <chr> 
## 1     270       196       3      2            4            1 Zona … Casa  calima
## 2     400       186       5      3            5            1 Zona … Casa  el bo…
## 3     355       190       4      3            5            2 Zona … Casa  la fl…
## 4     365       198       4      3            5            2 Zona … Casa  la me…
## 5     360       216       4      2            4            2 Zona … Casa  la me…
## # ℹ 3 more variables: latitud <dbl>, longitud <dbl>, pred_lm <dbl>
# Mapa de las ofertas candidatas
leaflet(ofertas_v1) %>%
  addTiles() %>%
  addCircleMarkers(
    lng = ~longitud, lat = ~latitud,
    popup = ~paste0("<b>Precio predicho:</b> ", round(pred_lm,1), " M<br>",
                    "<b>Precio real:</b> ", preciom, " M<br>",
                    "<b>Área:</b> ", areaconst, " m²<br>",
                    "<b>Estrato:</b> ", estrato, "<br>",
                    "<b>Habitaciones:</b> ", habitaciones,
                    " | <b>Baños:</b> ", banios,
                    " | <b>Parq:</b> ", parqueaderos,
                    "<br><b>Barrio:</b> ", barrio),
    radius = 6, fillOpacity = 0.7, clusterOptions = markerClusterOptions()
  )

PUNTO 7

Se realiza un filtro para los estratos 5 y 6, teniendo como base un presupuesto de

850 millones. Se logra filtrar propieades que se encuentren dentro de este presupesto

y que cumplan la condición de estrato

# Crear observaciones para estrato 5 y 6
new_v2_e5 <- data.frame(
  areaconst = 300, estrato = 5, banios = 3, habitaciones = 5, parqueaderos = 3,
  zona = factor("Zona Sur", levels = levels(vivienda2$zona))
)
new_v2_e6 <- data.frame(
  areaconst = 300, estrato = 6, banios = 3, habitaciones = 5, parqueaderos = 3,
  zona = factor("Zona Sur", levels = levels(vivienda2$zona))
)

# Predicciones
predict(mod_lm, new_v2_e5, interval = "prediction")
##        fit      lwr      upr
## 1 595.6955 258.5521 932.8389
predict(mod_lm, new_v2_e6, interval = "prediction")
##        fit      lwr      upr
## 1 681.3259 344.0971 1018.555

7.2

Finalmente, se realiza un mapa con las opciones que cumplan con estas caracteristicas.

# Filtrar candidatas similares a Vivienda 2
ofertas_v2 <- vivienda2 %>%
  filter(tipo == "Apartamento",
         zona == "Zona Sur",
         areaconst >= 270 & areaconst <= 330,
         banios >= 3,
         habitaciones >= 5,
         parqueaderos >= 3,
         pred_lm <= 850)

head(ofertas_v2, 5)
## # A tibble: 2 × 12
##   preciom areaconst estrato banios habitaciones parqueaderos zona   tipo  barrio
##     <dbl>     <dbl>   <int>  <dbl>        <dbl>        <dbl> <fct>  <fct> <chr> 
## 1     370       300       3      6            5            3 Zona … Apar… melen…
## 2     670       300       5      5            6            3 Zona … Apar… semin…
## # ℹ 3 more variables: latitud <dbl>, longitud <dbl>, pred_lm <dbl>
# Mapa de las ofertas candidatas
leaflet(ofertas_v2) %>%
  addTiles() %>%
  addCircleMarkers(
    lng = ~longitud, lat = ~latitud,
    popup = ~paste0("<b>Precio predicho:</b> ", round(pred_lm,1), " M<br>",
                    "<b>Precio real:</b> ", preciom, " M<br>",
                    "<b>Área:</b> ", areaconst, " m²<br>",
                    "<b>Estrato:</b> ", estrato, "<br>",
                    "<b>Habitaciones:</b> ", habitaciones,
                    " | <b>Baños:</b> ", banios,
                    " | <b>Parq:</b> ", parqueaderos,
                    "<br><b>Barrio:</b> ", barrio),
    radius = 6, fillOpacity = 0.7, clusterOptions = markerClusterOptions()
  )