knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE)
if (!requireNamespace("paqueteMODELOS", quietly = TRUE)) {
  try(remotes::install_github("centromagis/paqueteMODELOS", quiet = TRUE), silent = TRUE)
}
suppressPackageStartupMessages({
  library(paqueteMODELOS)
  library(dplyr)
  library(stringr)
  library(ggplot2)
  library(plotly)
  library(leaflet)
  library(broom)
  library(car)
  library(lmtest)
})
## Warning: package 'GGally' was built under R version 4.4.3
## Warning: package 'ggplot2' was built under R version 4.4.3
## Warning: package 'knitr' was built under R version 4.4.3
## Warning: package 'dplyr' was built under R version 4.4.3
## Warning: package 'plotly' was built under R version 4.4.3
## Warning: package 'leaflet' was built under R version 4.4.3
## Warning: package 'car' was built under R version 4.4.3
## Warning: package 'carData' was built under R version 4.4.3
## Warning: package 'lmtest' was built under R version 4.4.3
## Warning: package 'zoo' was built under R version 4.4.3

Paso 1: Filtrar casas en zona norte

data("vivienda")
nm <- names(vivienda)

find_col <- function(patterns, nm) {
  for (p in patterns) {
    m <- nm[str_detect(nm, regex(p, ignore_case = TRUE))]
    if (length(m) >= 1) return(m[1])
  }
  NA_character_
}

tipo_col    <- find_col(c("tipo", "property_type"), nm)
zona_col    <- find_col(c("zona", "sector", "region"), nm)
precio_col  <- find_col(c("precio", "price", "valor"), nm)
area_col    <- find_col(c("area", "area_construida", "m2"), nm)
estrato_col <- find_col(c("estrato", "stratum"), nm)
cuartos_col <- find_col(c("cuart", "habita", "rooms"), nm)
banos_col   <- find_col(c("bañ", "bano", "bath"), nm)
parq_col    <- find_col(c("parque", "parking"), nm)
lat_col     <- find_col(c("lat", "latitude"), nm)
lon_col     <- find_col(c("lon", "longitud"), nm)

base_norte_casas <- vivienda %>%
  filter(str_detect(.data[[tipo_col]], regex("casa|house", ignore_case = TRUE)) &
         str_detect(.data[[zona_col]], regex("norte|north", ignore_case = TRUE)))

head(base_norte_casas, 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>
if (!is.na(lat_col) && !is.na(lon_col)) {
  leaflet(data = base_norte_casas) %>%
    addTiles() %>%
    addCircleMarkers(
      lng = base_norte_casas[[lon_col]],
      lat = base_norte_casas[[lat_col]],
      popup = paste("Precio:", base_norte_casas[[precio_col]]),
      radius = 4, color = "blue", fillOpacity = 0.7
    )
}

Paso 2: Análisis exploratorio

# Filtrar columnas válidas (no NA y presentes en la base)
eda_vars <- c(precio_col, area_col, estrato_col, cuartos_col, banos_col)
eda_vars <- eda_vars[!is.na(eda_vars) & eda_vars %in% names(base_norte_casas)]

# Validar que haya al menos 2 columnas para análisis
if (length(eda_vars) < 2) {
  stop("No hay suficientes variables detectadas para el análisis exploratorio.")
}

# Crear el dataframe de análisis
eda_df <- base_norte_casas %>%
  select(all_of(eda_vars)) %>%
  na.omit()

# Matriz de dispersión interactiva
plot_ly(type = 'splom', data = eda_df,
        dimensions = lapply(names(eda_df), function(colname) list(label = colname, values = ~ get(colname))),
        marker = list(size = 4)) %>%
  layout(title = "Matriz de dispersión interactiva")
# Gráfico precio vs área
if (!is.na(precio_col) && !is.na(area_col)) {
  ggplotly(ggplot(base_norte_casas, aes(x = .data[[area_col]], y = .data[[precio_col]])) +
    geom_point() + geom_smooth(method = "lm", se = FALSE, color = "red") +
    labs(title = "Precio vs Área Construida") + theme_minimal())
}
# Boxplot por estrato
if (!is.na(precio_col) && !is.na(estrato_col)) {
  ggplotly(ggplot(base_norte_casas, aes(x = factor(.data[[estrato_col]]), y = .data[[precio_col]])) +
    geom_boxplot(fill = "skyblue") + labs(title = "Precio por Estrato") + theme_minimal())
}

Paso 3: Modelo de regresión lineal múltiple

predictoras <- c(area_col, estrato_col, cuartos_col, parq_col, banos_col)
predictoras <- predictoras[!is.na(predictoras) & predictoras %in% names(base_norte_casas)]
formula_mod <- as.formula(paste0("`", precio_col, "` ~ ", paste0("`", predictoras, "`", collapse = " + ")))
modelo <- lm(formula_mod, data = base_norte_casas)
summary(modelo)
## 
## Call:
## lm(formula = formula_mod, data = base_norte_casas)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -824.73  -80.43  -17.86   47.66 1005.36 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -246.78807   44.54968  -5.540 5.29e-08 ***
## areaconst       0.69459    0.05266  13.190  < 2e-16 ***
## estrato        87.80722    9.46502   9.277  < 2e-16 ***
## habitaciones   15.05430    4.86787   3.093  0.00211 ** 
## parqueaderos   27.33995    5.75390   4.752 2.76e-06 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 156.1 on 430 degrees of freedom
##   (287 observations deleted due to missingness)
## Multiple R-squared:  0.5982, Adjusted R-squared:  0.5945 
## F-statistic: 160.1 on 4 and 430 DF,  p-value: < 2.2e-16

Paso 4: Validación de supuestos

res <- resid(modelo)
fitted <- fitted(modelo)

ggplot(data.frame(res, fitted), aes(x = fitted, y = res)) +
  geom_point() + geom_hline(yintercept = 0, linetype = 2) + theme_minimal()

ggplot(data.frame(res), aes(sample = res)) +
  stat_qq() + stat_qq_line() + theme_minimal()

shapiro.test(res)
## 
##  Shapiro-Wilk normality test
## 
## data:  res
## W = 0.85251, p-value < 2.2e-16
bptest(modelo)
## 
##  studentized Breusch-Pagan test
## 
## data:  modelo
## BP = 77.133, df = 4, p-value = 7.05e-16
durbinWatsonTest(modelo)
##  lag Autocorrelation D-W Statistic p-value
##    1       0.1167176      1.764351   0.018
##  Alternative hypothesis: rho != 0

Paso 5: Predicción para vivienda solicitada

nueva_vivienda <- data.frame(
  `area_construida` = 120,
  `estrato` = 4,
  `habitaciones` = 3,
  `parqueaderos` = 2,
  `banos` = 2
)
names(nueva_vivienda) <- predictoras
predict(modelo, newdata = nueva_vivienda)
##        1 
## 287.6349

Paso 6: Ofertas sugeridas (≤ 350 millones)

ofertas_potenciales <- base_norte_casas %>%
  filter(.data[[precio_col]] <= 350000000)

head(ofertas_potenciales, 5)
## # A tibble: 5 × 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  4460 Zona N… 02          4     625       355            3      5            5
## 5  6081 Zona N… 02          5     750       237            2      6            6
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
if (!is.na(lat_col) && !is.na(lon_col)) {
  leaflet(data = ofertas_potenciales) %>%
    addTiles() %>%
    addCircleMarkers(
      lng = ofertas_potenciales[[lon_col]],
      lat = ofertas_potenciales[[lat_col]],
      popup = paste("Precio:", ofertas_potenciales[[precio_col]]),
      radius = 6, color = "orange", fillOpacity = 0.8
    )
}