# en el chunk setup (include=FALSE está bien)
plot_metrics_table <- function(metrics_df, title_txt){
  plotly::plot_ly(
    type = "table",
    header = list(
      values = names(metrics_df),
      fill   = list(color = "#4E79A7"),
      font   = list(color = "white", size = 14)
    ),
    cells  = list(
      values = t(metrics_df),
      fill   = list(color = "#F2F2F2")
    )
  ) |>
    plotly::layout(title = list(text = title_txt))
}
# ====== Resumen Ejecutivo (echo=FALSE) ======
# Extrae valores listos para imprimir
v1e4_pt  <- if (exists("pred_v1_e4")) as.numeric(pred_v1_e4[1]) else NA
v1e4_lo  <- if (exists("pred_v1_e4")) as.numeric(pred_v1_e4[2]) else NA
v1e4_hi  <- if (exists("pred_v1_e4")) as.numeric(pred_v1_e4[3]) else NA
v1e5_pt  <- if (exists("pred_v1_e5")) as.numeric(pred_v1_e5[1]) else NA
v1e5_lo  <- if (exists("pred_v1_e5")) as.numeric(pred_v1_e5[2]) else NA
v1e5_hi  <- if (exists("pred_v1_e5")) as.numeric(pred_v1_e5[3]) else NA

v2e5_pt  <- if (exists("v2_e5")) as.numeric(predict(mod2, newdata = v2_e5)) else NA
v2e6_pt  <- if (exists("v2_e6")) as.numeric(predict(mod2, newdata = v2_e6)) else NA

mae1 <- if (exists("metrics1")) as.numeric(metrics1$Valor[metrics1$Indicador=="MAE"]) else NA
rmse1<- if (exists("metrics1")) as.numeric(metrics1$Valor[metrics1$Indicador=="RMSE"]) else NA
r21  <- if (exists("metrics1")) as.numeric(metrics1$Valor[grepl("R", metrics1$Indicador)]) else NA

mae2 <- if (exists("metrics2")) as.numeric(metrics2$Valor[metrics2$Indicador=="MAE"]) else NA
rmse2<- if (exists("metrics2")) as.numeric(metrics2$Valor[metrics2$Indicador=="RMSE"]) else NA
r22  <- if (exists("metrics2")) as.numeric(metrics2$Valor[grepl("R", metrics2$Indicador)]) else NA

Resumen ejecutivo

Objetivo: Estimar el precio de vivienda y sugerir ofertas que se ajusten a los créditos pre-aprobados: Casa Zona Norte (≤ 350M) y Apartamento Zona Sur (≤ 850M).

Metodología: Se usó un modelo de regresión lineal múltiple con variables: área construida, estrato, #habitaciones, #parqueaderos y #baños. Validamos supuestos y medimos desempeño con MAE, RMSE y R² sobre un set de prueba (80/20). Visualizaciones con plotly.

Resultados clave (Vivienda 1 – Casa Norte).

  • Predicción (estrato 4): r round(v1e4_pt,1)M (IC95% r round(v1e4_lo,1)–r round(v1e4_hi,1)M).

  • Predicción (estrato 5): r round(v1e5_pt,1)M.

  • Desempeño en test: MAE r mae1M, RMSE r rmse1M, R² r r21.

  • Se proponen 5 ofertas ≤ 350M (ver mapa y tabla en anexos).

Resultados clave (Vivienda 2 – Apto Sur).

  • Predicción (estrato 5): r round(v2e5_pt,1)M; (estrato 6): r round(v2e6_pt,1)M.

  • Desempeño en test: MAE r mae2M, RMSE r rmse2M, R² r r22.

  • Se proponen 5 ofertas ≤ 850M (ver anexos).

Conclusiones y recomendaciones.

  • El área y el estrato son los predictores más influyentes; más área/estrato → mayor precio.

  • El ajuste del modelo es razonable para fines de tamizaje; se sugiere para mejora: transformar preciom, probar interacciones (p. ej., área×estrato) y modelos robustos.

  • Las ofertas listadas cumplen los límites de crédito y se ubican en las zonas solicitadas.

  • Se recomienda inspección comercial de las top-3 por precio-área y confirmar datos de coordenadas para evitar errores de georreferenciación.



ANEXOS: Código Actividad 2


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

  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.

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

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

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

  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.

  7. Realice los pasos del 1 al 6. Para la segunda solicitud que tiene un crédito pre-aprobado por valor de $850 millones.


1. Vivienda 1 — Casa, Zona Norte (crédito ≤ 350M)

# Cargando paquetes
library(sf)
## Linking to GEOS 3.13.0, GDAL 3.10.1, PROJ 9.5.1; sf_use_s2() is TRUE
library(paqueteMODELOS)
## Cargando paquete requerido: boot
## Cargando paquete requerido: broom
## Cargando paquete requerido: GGally
## Cargando paquete requerido: ggplot2
## Cargando paquete requerido: gridExtra
## Cargando paquete requerido: knitr
## Cargando paquete requerido: summarytools
# Carga de datos desde el paquete
if (!requireNamespace("devtools", quietly = TRUE)) install.packages("devtools")
if (!requireNamespace("paqueteMODELOS", quietly = TRUE)) {
  devtools::install_github("centromagis/paqueteMODELOS", force = TRUE)
}
library(paqueteMODELOS)
data("vivienda")


1.1 Paso 1: Filtro y verificación geográfica

df_casa_norte <- vivienda |>
  dplyr::filter(tipo == "Casa", zona == "Zona Norte")

head(df_casa_norte, 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>
# Mapa de puntos (plotly)
df1_geo <- df_casa_norte |>
  dplyr::filter(!is.na(latitud), !is.na(longitud))

plotly::plot_ly(
  df1_geo, x = ~longitud, y = ~latitud,
  type = "scatter", mode = "markers",
  color = ~factor(estrato),
  text = ~paste0("ID: ", id,
                 "<br>Precio: ", preciom, " M",
                 "<br>Área: ", areaconst, " m²",
                 "<br>Estrato: ", estrato),
  hoverinfo = "text"
) |>
  plotly::layout(
    title = list(text = "Ubicación relativa • Casas (Zona Norte)"),
    xaxis = list(title = "Longitud"),
    yaxis = list(title = "Latitud")
  )


1.2 Paso 2: Exploración de datos

vars1 <- c("preciom","areaconst","estrato","habitaciones","parqueaderos","banios")
df1 <- df_casa_norte[, c("id","latitud","longitud", vars1)]

# Dispersión Precio vs Área
plotly::plot_ly(
  df1, x = ~areaconst, y = ~preciom,
  color = ~factor(estrato), size = ~habitaciones,
  type = "scatter", mode = "markers",
  text = ~paste("ID:", id,
                "<br>Área:", areaconst, "m²",
                "<br>Precio:", preciom, "M",
                "<br>Estrato:", estrato,
                "<br>Hab.:", habitaciones),
  hoverinfo = "text"
) |>
  plotly::layout(
    title = list(text = "Precio vs Área • Casas (Zona Norte)"),
    xaxis = list(title = "Área (m²)"),
    yaxis = list(title = "Precio (M)")
  )
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
# Boxplot de precio por estrato
plotly::plot_ly(
  df1, x = ~factor(estrato), y = ~preciom,
  type = "box", color = ~factor(estrato)
) |>
  plotly::layout(
    title = list(text = "Precio por Estrato • Casas (Zona Norte)"),
    xaxis = list(title = "Estrato"),
    yaxis = list(title = "Precio (M)")
  )


1.3 Paso 3: Modelo lineal múltiple e interpretación

df1m <- tidyr::drop_na(df1[, vars1])

mod1 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
           data = df1m)

summary(mod1)   # Coeficientes y R²/R² adj.
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = df1m)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -784.29  -77.56  -16.03   47.67  978.61 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -238.17090   44.40551  -5.364 1.34e-07 ***
## areaconst       0.67673    0.05281  12.814  < 2e-16 ***
## estrato        80.63495    9.82632   8.206 2.70e-15 ***
## habitaciones    7.64511    5.65873   1.351    0.177    
## parqueaderos   24.00598    5.86889   4.090 5.14e-05 ***
## banios         18.89938    7.48800   2.524    0.012 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 155.1 on 429 degrees of freedom
## Multiple R-squared:  0.6041, Adjusted R-squared:  0.5995 
## F-statistic: 130.9 on 5 and 429 DF,  p-value: < 2.2e-16
# Coeficientes con IC95% (plotly)
coef1 <- broom::tidy(mod1, conf.int = TRUE)

plotly::plot_ly(
  coef1 |> dplyr::filter(term != "(Intercept)"),
  x = ~estimate, y = ~term, type = "bar", orientation = "h",
  error_x = list(array = ~conf.high - estimate,
                 arrayminus = ~estimate - conf.low)
) |>
  plotly::layout(
    title = list(text = "Coeficientes β̂ con IC95% • Casas (Zona Norte)"),
    xaxis = list(title = "Efecto en precio (M)"),
    yaxis = list(title = "Variable")
  )


1.4 Paso 4: Validación de supuestos

par(mfrow = c(2,2)); plot(mod1); par(mfrow = c(1,1))


1.5 Paso 5: Predicción del caso puntual (estrato 4 y 5)

v1_e4 <- data.frame(areaconst = 200, estrato = 4, habitaciones = 4, parqueaderos = 1, banios = 2)
v1_e5 <- data.frame(areaconst = 200, estrato = 5, habitaciones = 4, parqueaderos = 1, banios = 2)

pred_v1_e4 <- predict(mod1, newdata = v1_e4, interval = "prediction", level = .95)
pred_v1_e5 <- predict(mod1, newdata = v1_e5, interval = "prediction", level = .95)

pred_v1_e4; pred_v1_e5
##       fit      lwr      upr
## 1 312.101 6.205196 617.9968
##        fit      lwr      upr
## 1 392.7359 86.19637 699.2755


1.6 Paso 6: Split de prueba, indicadores y ofertas ≤ 350M

# PASO 1.6 (Casas Norte) — chunk autocontenido
set.seed(123)

vars1 <- c("preciom","areaconst","estrato","habitaciones","parqueaderos","banios")
df1m  <- tidyr::drop_na( df_casa_norte[, vars1] )

sp1 <- rsample::initial_split(df1m, prop=.8)
tr1 <- rsample::training(sp1)
te1 <- rsample::testing(sp1)

mod1_s <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data=tr1)
pred1  <- data.frame(real = te1$preciom,
                     pred = as.numeric(predict(mod1_s, newdata=te1)))
pred1$resid <- pred1$real - pred1$pred

MAE1  <- mean(abs(pred1$resid), na.rm=TRUE)
RMSE1 <- sqrt(mean(pred1$resid^2, na.rm=TRUE))
R2_1  <- cor(pred1$real, pred1$pred, use="complete.obs")^2

metrics1 <- data.frame(
  Indicador = c("MAE","RMSE","R² (test)"),
  Valor     = c(round(MAE1,2), round(RMSE1,2), round(R2_1,3))
)

# ⬇️ aquí SIEMPRE existirá metrics1 al knit
plot_metrics_table(metrics1, "Indicadores de rendimiento • Casas (Zona Norte)")


2. Vivienda 2 — Apartamento, Zona Sur (crédito ≤ 850M)

2.1 Paso 1: Filtro y verificación

df_apto_sur <- vivienda |>
  dplyr::filter(tipo == "Apartamento", zona == "Zona Sur")

head(df_apto_sur, 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>
df2_geo <- df_apto_sur |>
  dplyr::filter(!is.na(latitud), !is.na(longitud))

plotly::plot_ly(
  df2_geo, x = ~longitud, y = ~latitud,
  type = "scatter", mode = "markers",
  color = ~factor(estrato),
  text = ~paste0("ID: ", id,
                 "<br>Precio: ", preciom, " M",
                 "<br>Área: ", areaconst, " m²"),
  hoverinfo = "text"
) |>
  plotly::layout(
    title = list(text = "Ubicación relativa • Aptos (Zona Sur)"),
    xaxis = list(title = "Longitud"),
    yaxis = list(title = "Latitud")
  )


2.2 Paso 2: EDA con plotly

vars2 <- c("preciom","areaconst","estrato","habitaciones","parqueaderos","banios")
df2 <- df_apto_sur[, c("id","latitud","longitud", vars2)]

plotly::plot_ly(
  df2, x = ~areaconst, y = ~preciom,
  color = ~factor(estrato), size = ~habitaciones,
  type = "scatter", mode = "markers",
  text = ~paste("ID:", id,
                "<br>Área:", areaconst, "m²",
                "<br>Precio:", preciom, "M",
                "<br>Estrato:", estrato,
                "<br>Hab.:", habitaciones),
  hoverinfo = "text"
) |>
  plotly::layout(
    title = list(text = "Precio vs Área • Aptos (Zona Sur)"),
    xaxis = list(title = "Área (m²)"),
    yaxis = list(title = "Precio (M)")
  )
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
plotly::plot_ly(
  df2, x = ~factor(estrato), y = ~preciom,
  type = "box", color = ~factor(estrato)
) |>
  plotly::layout(
    title = list(text = "Precio por Estrato • Aptos (Zona Sur)"),
    xaxis = list(title = "Estrato"),
    yaxis = list(title = "Precio (M)")
  )


2.3 Paso 3: Modelo lineal múltiple e interpretación

df2m <- tidyr::drop_na(df2[, vars2])

mod2 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
           data = df2m)

summary(mod2)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = df2m)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1092.02   -42.28    -1.33    40.58   926.56 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -261.62501   15.63220 -16.736  < 2e-16 ***
## areaconst       1.28505    0.05403  23.785  < 2e-16 ***
## estrato        60.89709    3.08408  19.746  < 2e-16 ***
## habitaciones  -24.83693    3.89229  -6.381 2.11e-10 ***
## parqueaderos   72.91468    3.95797  18.422  < 2e-16 ***
## banios         50.69675    3.39637  14.927  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 98.02 on 2375 degrees of freedom
## Multiple R-squared:  0.7485, Adjusted R-squared:  0.748 
## F-statistic:  1414 on 5 and 2375 DF,  p-value: < 2.2e-16
coef2 <- broom::tidy(mod2, conf.int = TRUE)
plotly::plot_ly(
  coef2 |> dplyr::filter(term != "(Intercept)"),
  x = ~estimate, y = ~term, type = "bar", orientation = "h",
  error_x = list(array = ~conf.high - estimate,
                 arrayminus = ~estimate - conf.low)
) |>
  plotly::layout(
    title = list(text = "Coeficientes β̂ con IC95% • Aptos (Zona Sur)"),
    xaxis = list(title = "Efecto en precio (M)"),
    yaxis = list(title = "Variable")
  )


2.4 Paso 4: Validación de supuestos

par(mfrow = c(2,2)); plot(mod2); par(mfrow = c(1,1))


2.5 Paso 5: Predicciones con set de prueba

sp2 <- rsample::initial_split(df2m, prop = .8)
tr2  <- rsample::training(sp2)
te2  <- rsample::testing(sp2)

mod2_s <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = tr2)
pred2  <- data.frame(real = te2$preciom,
                     pred = as.numeric(predict(mod2_s, newdata = te2)))
pred2$resid <- pred2$real - pred2$pred

# Real vs Predicho
plotly::plot_ly(pred2, x = ~real, y = ~pred,
                type = "scatter", mode = "markers") |>
  plotly::add_lines(x = range(pred2$real),
                    y = range(pred2$real), name = "Ideal x=y") |>
  plotly::layout(title = list(text = "Set de prueba • Aptos (Zona Sur)"),
                 xaxis = list(title = "Real (M)"),
                 yaxis = list(title = "Predicho (M)"))


2.6 Paso 6: Indicadores y ofertas ≤ 850M

MAE2  <- mean(abs(pred2$resid), na.rm = TRUE)
RMSE2 <- sqrt(mean(pred2$resid^2, na.rm = TRUE))
R2_2  <- cor(pred2$real, pred2$pred, use = "complete.obs")^2

metrics2 <- data.frame(Indicador = c("MAE","RMSE","R² (test)"),
                       Valor = c(round(MAE2,2), round(RMSE2,2), round(R2_2,3)))
plot_metrics_table(metrics2, "Indicadores de rendimiento • Aptos (Zona Sur)")
# Ofertas candidatas <= 850M
pres2 <- 850
cand2 <- df_apto_sur |>
  dplyr::mutate(pred = as.numeric(predict(mod2, newdata = df_apto_sur))) |>
  dplyr::filter(!is.na(pred), pred <= pres2) |>
  dplyr::arrange(abs(pred - pres2)) |>
  dplyr::slice_head(n = 5) |>
  dplyr::mutate(gap = round(pres2 - pred, 1))

cand2[, c("id","areaconst","estrato","habitaciones","parqueaderos","banios","preciom","pred","gap")]
## # A tibble: 5 × 9
##      id areaconst estrato habitaciones parqueaderos banios preciom  pred   gap
##   <dbl>     <dbl>   <dbl>        <dbl>        <dbl>  <dbl>   <dbl> <dbl> <dbl>
## 1  6510       290       6            4            3      5    1600  849.   0.7
## 2  5472       310       6            3            3      4    1590  849.   0.9
## 3  6576       210       6            3            4      5     660  844.   5.8
## 4  6887       170       6            3            4      6    1050  844.   6.5
## 5  6197       290       6            3            3      4    1700  823.  26.6
geo2 <- cand2 |> dplyr::filter(!is.na(latitud), !is.na(longitud))
plotly::plot_ly(
  geo2, x = ~longitud, y = ~latitud,
  type = "scatter", mode = "markers+text",
  text = ~paste0("ID: ", id,
                 "<br>Pred: ", round(pred,1), " M",
                 "<br>Área: ", areaconst, " m²",
                 "<br>Estrato: ", estrato),
  textposition = "top center",
  marker = list(size = ~pmin(scales::rescale(pred, to = c(14,28)), 28)),
  color = ~factor(estrato)
) |>
  plotly::layout(
    title = list(text = "Top 5 ≤ 850M • Aptos (Zona Sur)"),
    xaxis = list(title = "Longitud"), yaxis = list(title = "Latitud")
  )
## Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels
## Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels


Predicción puntual de la solucitud

v2_e5 <- data.frame(areaconst = 300, estrato = 5, habitaciones = 5, parqueaderos = 3, banios = 3)
v2_e6 <- data.frame(areaconst = 300, estrato = 6, habitaciones = 5, parqueaderos = 3, banios = 3)

predict(mod2, newdata = v2_e5, interval = "prediction", level = .95)
##        fit     lwr      upr
## 1 675.0247 481.455 868.5945
predict(mod2, newdata = v2_e6, interval = "prediction", level = .95)
##        fit      lwr      upr
## 1 735.9218 542.3141 929.5296
# Apéndice de reproducibilidad
sessionInfo()
## R version 4.4.3 (2025-02-28 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26100)
## 
## Matrix products: default
## 
## 
## locale:
## [1] LC_COLLATE=Spanish_Colombia.utf8  LC_CTYPE=Spanish_Colombia.utf8   
## [3] LC_MONETARY=Spanish_Colombia.utf8 LC_NUMERIC=C                     
## [5] LC_TIME=Spanish_Colombia.utf8    
## 
## time zone: America/Bogota
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] paqueteMODELOS_0.1.0 summarytools_1.1.4   knitr_1.50          
## [4] gridExtra_2.3        GGally_2.3.0         ggplot2_3.5.2       
## [7] broom_1.0.9          boot_1.3-31          sf_1.0-21           
## 
## loaded via a namespace (and not attached):
##  [1] tidyselect_1.2.1   viridisLite_0.4.2  dplyr_1.1.4        farver_2.1.2      
##  [5] S7_0.2.0           lazyeval_0.2.2     fastmap_1.2.0      promises_1.3.2    
##  [9] digest_0.6.37      timechange_0.3.0   mime_0.12          lifecycle_1.0.4   
## [13] ellipsis_0.3.2     magrittr_2.0.3     compiler_4.4.3     rlang_1.1.6       
## [17] sass_0.4.9         tools_4.4.3        utf8_1.2.6         yaml_2.3.10       
## [21] data.table_1.17.0  htmlwidgets_1.6.4  pkgbuild_1.4.8     classInt_0.4-11   
## [25] plyr_1.8.9         RColorBrewer_1.1-3 pkgload_1.4.0      KernSmooth_2.23-26
## [29] miniUI_0.1.2       withr_3.0.2        purrr_1.1.0        grid_4.4.3        
## [33] urlchecker_1.0.1   profvis_0.4.0      future_1.67.0      xtable_1.8-4      
## [37] e1071_1.7-16       globals_0.18.0     scales_1.4.0       MASS_7.3-64       
## [41] cli_3.6.5          rmarkdown_2.29     generics_0.1.4     remotes_2.5.0     
## [45] rstudioapi_0.17.1  httr_1.4.7         reshape2_1.4.4     sessioninfo_1.2.3 
## [49] DBI_1.2.3          cachem_1.1.0       proxy_0.4-27       pander_0.6.6      
## [53] stringr_1.5.1      parallel_4.4.3     matrixStats_1.5.0  base64enc_0.1-3   
## [57] vctrs_0.6.5        devtools_2.4.5     jsonlite_1.9.1     rapportools_1.2   
## [61] listenv_0.9.1      crosstalk_1.2.1    magick_2.8.7       plotly_4.10.4     
## [65] tidyr_1.3.1        jquerylib_0.1.4    units_0.8-7        parallelly_1.45.1 
## [69] glue_1.8.0         ggstats_0.10.0     codetools_0.2-20   rsample_1.3.1     
## [73] lubridate_1.9.4    stringi_1.8.7      gtable_0.3.6       later_1.4.2       
## [77] tibble_3.3.0       furrr_0.3.1        pillar_1.11.0      htmltools_0.5.8.1 
## [81] R6_2.6.1           tcltk_4.4.3        evaluate_1.0.4     shiny_1.10.0      
## [85] backports_1.5.0    memoise_2.0.1      httpuv_1.6.15      pryr_0.1.6        
## [89] bslib_0.9.0        class_7.3-23       Rcpp_1.1.0         checkmate_2.3.2   
## [93] xfun_0.52          usethis_3.1.0      fs_1.6.5           pkgconfig_2.0.3