# 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
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.
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).
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).
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.
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?).
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.
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).
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).
Con el modelo identificado debe predecir el precio de la vivienda con las características de la primera solicitud.
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.
Realice los pasos del 1 al 6. Para la segunda solicitud que tiene un crédito pre-aprobado por valor de $850 millones.
# 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")
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")
)
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)")
)
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")
)
par(mfrow = c(2,2)); plot(mod1); par(mfrow = c(1,1))
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
# 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.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")
)
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)")
)
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")
)
par(mfrow = c(2,2)); plot(mod2); par(mfrow = c(1,1))
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)"))
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
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