Modelización

Frameworks, engines y herramientas de interpretabilidad para modelado estadístico y machine learning en R

r
modeling
machine-learning
tidymodels
interpretability
resampling
Referencia comentada de los paquetes que estructuran un flujo moderno de modelización en R: tidymodels como framework de referencia, engines (xgboost, ranger, glmnet, randomForestSRC), interpretabilidad (DALEX, vip) y alternativas históricas (caret, mlr3).

Sobre modelización en R

El ecosistema de modelización en R se ha reordenado en la última década en torno a tres familias bien diferenciadas. Conviene tener clara la jerarquía antes de elegir qué aprender o qué imponer en un proyecto:

  • tidymodels es la propuesta moderna y la opción por defecto para proyectos nuevos. Es un meta-paquete mantenido por el equipo de Posit (Max Kuhn, Hadley Wickham y colaboradores) que cubre todo el pipeline, preprocesado (recipes), especificación del modelo (parsnip), orquestación (workflows), resampling y tuning (rsample, tune), métricas (yardstick) e interpretabilidad (vip). Reemplaza a caret.
  • caret es el framework histórico (Max Kuhn, 2007 en adelante). Sigue funcionando y aparece en código antiguo y en tutoriales, pero está en modo mantenimiento. Su autor ha redirigido el desarrollo hacia tidymodels. Para un proyecto nuevo en 2026 no hay razón técnica para empezarlo en caret.
  • mlr3 es la alternativa orientada a objetos (basada en R6). Filosofía y API muy distintas a tidymodels, más cercana a scikit-learn en estilo. Equipo mantenedor independiente (LMU Munich) y desarrollo activo. Es una elección legítima cuando trabajas con pipelines complejos de OO o necesitas integración profunda con paradox para tuning bayesiano avanzado.

Detrás de cualquiera de estos frameworks viven los engines reales: xgboost, ranger, glmnet, randomForestSRC, kernlab, lightgbm. El framework no calcula nada, solo orquesta. Es importante distinguir la decisión de “qué algoritmo entreno” (engine) de “cómo lo orquesto” (framework). Cambiar de engine dentro de tidymodels cuesta una línea (set_engine("ranger")set_engine("xgboost")). Cambiar de framework cuesta reescribir todo el pipeline.

Tres principios transversales que conviene interiorizar:

  • Reproducibilidad y set.seed. El resampling y los engines aleatorios (random forest, xgboost) exigen semillas explícitas. En backends paralelos (doFuture, doParallel) la propagación de semillas necesita tratamiento específico, furrr::furrr_options(seed = TRUE) o tune_grid(control = control_grid(parallel_over = "everything")).
  • El preprocesado vive dentro del workflow. Centrar, escalar, imputar o codificar variables categóricas se hace dentro de recipes y se aplica fold a fold en el resampling. Hacerlo fuera (con dplyr::mutate antes del split) introduce data leakage.
  • Train/test split antes de todo. El test set se aparta al principio y no se mira hasta el final. La selección de hiperparámetros, el feature engineering y la comparación de modelos viven dentro del train set, validados con resampling interno (CV, bootstrap).

Esta página cataloga los paquetes que estructuran la mayor parte de los flujos de modelización en R. El orden refleja jerarquía conceptual: primero el framework de referencia (tidymodels), después los engines más relevantes, luego interpretabilidad, y al final las alternativas con peso histórico o nicho OO.


tidymodels

tidymodels es el framework de referencia para modelización en R en 2026. No es un paquete sino una colección coordinada (recipes, parsnip, workflows, rsample, tune, yardstick, dials, vip) que cubre el ciclo completo de un proyecto de modelado supervisado: preprocesamiento, especificación de modelo, resampling, tuning de hiperparámetros, evaluación e interpretabilidad.

Desarrollado por el equipo de Posit con Max Kuhn (autor de caret) al frente. Filosofía coherente con el resto del tidyverse: pipes, verbos especializados, objetos inmutables. Cada subpaquete tiene una responsabilidad acotada, esto es deliberado y facilita razonar sobre el pipeline.

Cuándo usarlo

  • Proyectos nuevos de modelado supervisado (clasificación, regresión, survival) en R.
  • Cuando necesitas un pipeline reproducible que combine preprocesamiento variable, resampling y tuning de hiperparámetros.
  • Cuando el equipo ya trabaja con el tidyverse y la curva de adopción es plana.
  • Producción ligera: workflows serializables (butcher para reducir tamaño) que se cargan y predicen sin reentrenar.

Cuándo NO usarlo

  • Deep learning / redes neuronales serias. Usa torch (R), keras3 o salta a Python (pytorch, jax). parsnip tiene wrappers para keras/brulee pero no es el flujo natural.
  • Pipelines orientados a objetos muy complejos con herencia, callbacks y composición programática profunda, mlr3 se siente más natural ahí.
  • Modelos jerárquicos / bayesianos con estructura específica, brms, rstanarm o stan directo son mejores herramientas. parsnip tiene linear_reg(engine = "stan") pero el control es limitado.

Conceptos clave

  • recipes define el preprocesado como una secuencia de pasos (step_*) que se entrenan (prep) sobre el train set y se aplican (bake) al test. La separación prep/bake es lo que evita el data leakage.
  • parsnip unifica la especificación del modelo desacoplándola del engine: rand_forest() |> set_engine("ranger") o set_engine("randomForest") cambia el motor sin tocar el resto del pipeline.
  • workflows empaqueta receta + modelo en un único objeto. Es lo que se entrena, se resamplea, se tunea y se serializa.
  • rsample genera particiones (initial_split, vfold_cv, bootstraps, group_vfold_cv para datos agrupados, sliding_* para series temporales).
  • tune orquesta el tuning: tune_grid() (grid search), tune_bayes() (bayesiano sobre un GP) y tune_race_anova() (early stopping de combinaciones inferiores).
  • yardstick son las métricas como verbos tidy (roc_auc, rmse, accuracy, mcc). Funcionan sobre tibbles con columnas truth y estimate.
  • dials define el espacio de hiperparámetros con tipos correctos (mtry(), trees(), tree_depth()). Se finaliza con finalize() cuando hay parámetros que dependen del dataset (p. ej. mtry() necesita conocer el número de predictores).

Patrón mínimo

library(tidymodels)

# 1. Split inicial — el test no se toca hasta el final
set.seed(42)
split <- initial_split(data, prop = 0.8, strata = outcome)
train <- training(split)
test  <- testing(split)

# 2. Receta de preprocesado
rec <- recipe(outcome ~ ., data = train) |>
  step_impute_median(all_numeric_predictors()) |>
  step_dummy(all_nominal_predictors()) |>
  step_zv(all_predictors()) |>
  step_normalize(all_numeric_predictors())

# 3. Modelo + engine (xgboost como ejemplo)
mod <- boost_tree(
  trees      = 1000,
  tree_depth = tune(),
  learn_rate = tune(),
  min_n      = tune()
) |>
  set_engine("xgboost") |>
  set_mode("classification")

# 4. Workflow
wf <- workflow() |>
  add_recipe(rec) |>
  add_model(mod)

# 5. Resampling para tuning
folds <- vfold_cv(train, v = 5, strata = outcome)

# 6. Tuning bayesiano
set.seed(123)
tuned <- tune_bayes(
  wf,
  resamples = folds,
  initial   = 10,
  iter      = 30,
  metrics   = metric_set(roc_auc, accuracy),
  control   = control_bayes(no_improve = 10, save_pred = TRUE)
)

# 7. Finalizar y entrenar en todo el train
best <- select_best(tuned, metric = "roc_auc")
final_wf <- finalize_workflow(wf, best)
final_fit <- last_fit(final_wf, split)

# 8. Métricas sobre el test (única vez que se mira)
collect_metrics(final_fit)

Trampas habituales

  • Preprocesado fuera de la receta = data leakage. El error más común. Cualquier transformación que dependa del target o de estadísticos del dataset (media, mediana, codificación de categóricas) debe ir dentro de recipes, no en un mutate previo.
  • set.seed en paralelo. Cuando tune_grid corre en backend paralelo, una sola set.seed() global no garantiza reproducibilidad entre workers. Pasa control = control_grid(parallel_over = "everything") y, para furrr, furrr_options(seed = TRUE).
  • step_dummy antes de step_zv. Si binarizas y luego algún nivel queda constante (porque solo aparecía en ciertos folds), step_zv lo elimina. Orden importa: dummies → zv → normalize.
  • Target encoding con leakage. step_lencode_* (de embed) hace target encoding correctamente dentro de la receta (un valor por fold). Implementarlo manualmente con group_by(cat) |> mutate(enc = mean(y)) antes del split filtra información del test.
  • fit_resamples vs last_fit. fit_resamples evalúa con CV (no toca el test). last_fit entrena en todo el train y evalúa en el test, solo una vez al final del proyecto.
  • mtry() requiere finalize(). Hiperparámetros que dependen del dataset (mtry, min_n en algunos engines) deben pasarse por finalize(param_set, train) antes de generar el grid.

Enlaces

Relacionados en esta página


xgboost

xgboost es la implementación de referencia de gradient boosting sobre árboles con segunda derivada y regularización L1/L2. Desarrollado por Tianqi Chen et al. (2014), es el engine que ganó la mayoría de competiciones Kaggle entre 2015 y 2019 y sigue siendo baseline de facto para tabular data fuera de deep learning.

En R se usa raramente a pelo: la interfaz idiomática es vía parsnip::boost_tree(engine = "xgboost") dentro de tidymodels. La API nativa (xgb.train, xgb.DMatrix) es funcional pero más rugosa y exige convertir manualmente factores a numérico.

Cuándo usarlo

  • Datos tabulares con mezcla de numéricas y categóricas codificadas. Suele ser el primer modelo no lineal a probar tras una baseline lineal.
  • Datasets entre 1k y unos pocos millones de filas. Por encima, lightgbm suele ser más rápido y con un consumo de memoria menor.
  • Cuando necesitas un modelo robusto a outliers en las features (los árboles no se inmutan) y a interacciones no lineales.

Cuándo NO usarlo

  • Datasets muy pequeños (< 500 filas). El sobreajuste es difícil de controlar y un modelo lineal regularizado (glmnet) o random forest (ranger) suelen rendir similar con mucha menos varianza.
  • Datos puramente numéricos lineales. Un GLM con glmnet es interpretable, rápido y suele ganar.
  • Series temporales con estructura fuerte. Necesita feature engineering temporal explícito (lags, ventanas). Sin eso, los árboles ignoran la dependencia secuencial.
  • Variables de muy alta cardinalidad. Target encoding sin protección filtra información. Usa step_lencode_mixed de embed o cambia a lightgbm con su soporte nativo de categóricas.

Conceptos clave

  • Booster: gbtree (default), gblinear, dart. El primero es el habitual. dart añade dropout y suele ayudar en problemas muy ruidosos.
  • Hiperparámetros que mueven la aguja: tree_depth (3-8 suele bastar), learn_rate (0.01-0.1 con muchos árboles, 0.1-0.3 con pocos), min_n, mtry/colsample_bytree, sample_size/subsample.
  • nrounds + early_stopping_rounds. Pasar muchos árboles y dejar que el early stopping decida es más eficiente que tunear nrounds directamente.
  • Importance: gain, weight, cover. gain es la métrica útil por defecto. weight solo cuenta splits y engaña.
  • GPU. tree_method = "hist" (CPU) o "gpu_hist" (GPU CUDA). En tabular grande el speed-up es real.

Patrón mínimo

library(tidymodels)

xgb_spec <- boost_tree(
  trees      = 1000,
  tree_depth = tune(),
  min_n      = tune(),
  learn_rate = tune(),
  loss_reduction = tune(),
  sample_size    = tune(),
  mtry           = tune()
) |>
  set_engine("xgboost", counts = FALSE) |>
  set_mode("classification")

# Espacio de hiperparámetros razonable
xgb_grid <- grid_space_filling(
  tree_depth(range = c(3L, 8L)),
  min_n(range = c(2L, 40L)),
  learn_rate(range = c(-3, -1), trans = log10_trans()),
  loss_reduction(),
  sample_size = sample_prop(c(0.5, 1.0)),
  finalize(mtry(), train),
  size = 30
)

Trampas habituales

  • Factores sin codificar. xgboost exige matriz numérica. parsnip lo gestiona automáticamente. Con la API nativa hay que model.matrix() a mano y reproducir el mismo orden de columnas en predicción.
  • learn_rate y trees están acoplados. Bajar learn_rate exige subir trees. No los tunees independientemente sin early stopping.
  • Validación de hiperparámetros con un único split. En datasets medianos, una única validación interna sobreajusta el tuning. Usa 5-fold CV mínimo.
  • colsample_bytree vs mtry. parsnip traduce mtry (número entero de columnas) a colsample_bytree (proporción). Pasar counts = TRUE al engine cambia la interpretación. Comprueba qué espera tu dials::mtry().
  • Determinismo. Aun con set.seed, xgboost puede dar resultados no idénticos en multi-thread. Para reproducibilidad estricta fija nthread = 1 o acepta una variabilidad pequeña.

Enlaces

Relacionados en esta página

  • tidymodels, framework natural sobre el que orquestar xgboost.
  • ranger, alternativa más rápida cuando random forest es suficiente.
  • vip, DALEX, para interpretar el modelo entrenado.

ranger

ranger es la implementación rápida y multi-thread de random forest en R (C++ con paralelismo nativo). Reemplazó a randomForest (Liaw & Wiener) como engine por defecto para random forest cuando importa el tiempo de entrenamiento o trabajas con más de unos pocos miles de árboles.

Autor: Marvin N. Wright (Universidad de Bremen, 2017). Suele ser entre 5x y 50x más rápido que randomForest y soporta variables de alta cardinalidad sin convertirlas a numéricas a la fuerza.

Cuándo usarlo

  • Random forest como baseline honesto antes de pasar a gradient boosting. Suele bastar para mucho problema real.
  • Datasets con muchas features de bajo coste predictivo individual (p. ej. genómica, NLP con bag-of-words), los árboles aleatorios resisten bien la maldición de la dimensionalidad por sí solos.
  • Cuando necesitas estimación de incertidumbre por la vía del quantile regression forest (quantreg = TRUE).

Cuándo NO usarlo

  • Cuando ya sabes que vas a boostear. xgboost o lightgbm casi siempre baten a random forest en tabular si tunneas. RF es la baseline, no la solución.
  • Interpretabilidad lineal estricta. Si necesitas coeficientes y p-values, ranger no te los da. Usa glmnet o un GLM.
  • Datasets muy grandes con memoria limitada. RF guarda todos los árboles. Xgboost/lightgbm son más ligeros en disco.

Conceptos clave

  • Sin tuning aterriza decente. Random forest es de los algoritmos más robustos a hiperparámetros razonables. mtry = sqrt(p) clasificación / p/3 regresión es defecto sólido.
  • Hiperparámetros que sí mueven: mtry, min.node.size, num.trees (más siempre es mejor o igual. Suele bastar 500-1000).
  • importance = "permutation" es lo correcto para importancia variable. "impurity" está sesgado hacia variables con muchos niveles.
  • quantreg = TRUE activa quantile regression forests (Meinshausen, 2006), te da intervalos de predicción sin asumir distribución del error.
  • probability = TRUE en clasificación entrena un probability forest (Malley et al., 2012) con probabilidades calibradas, no solo voto mayoritario.

Patrón mínimo

library(tidymodels)

rf_spec <- rand_forest(
  mtry  = tune(),
  trees = 1000,
  min_n = tune()
) |>
  set_engine("ranger", importance = "permutation", num.threads = parallel::detectCores() - 1) |>
  set_mode("classification")

rf_grid <- grid_regular(
  finalize(mtry(), train),
  min_n(range = c(2L, 40L)),
  levels = 5
)

Trampas habituales

  • importance = "impurity" por defecto. Sesgo conocido hacia variables de alta cardinalidad. Usa "permutation" o, mejor, "impurity_corrected" (Nembrini et al., 2018) si la permutación es cara.
  • Factores con muchos niveles. ranger los soporta nativamente con respect.unordered.factors = "partition", pero el coste crece con 2^k. Para >32 niveles, codifica antes (target encoding con embed).
  • OOB no es CV. El error out-of-bag es útil como diagnóstico interno pero no sustituye a un esquema de resampling externo, especialmente con datos no i.i.d. (grupos, series).
  • num.threads global. Si paralelizas el tuning por fuera, fija num.threads = 1 dentro de ranger para evitar sobre-suscripción de CPU.

Enlaces

Relacionados en esta página


glmnet

glmnet ajusta modelos lineales generalizados con regularización elastic net (combinación convexa de L1/lasso y L2/ridge) por descenso de coordenadas. Es la implementación canónica, Friedman, Hastie y Tibshirani (Stanford), los autores del propio método.

Es la pieza que conviene tener en el cinturón como baseline siempre que haya estructura aproximadamente lineal: regresión lineal, logística, Poisson, Cox (supervivencia), multinomial. Ajusta sobre una rejilla de lambda en un solo paso (regularization path) y elige el óptimo por CV.

Cuándo usarlo

  • Baseline lineal regularizada. Antes de probar xgboost, ranger o cualquier no lineal, un glmnet con CV te dice cuánto se gana realmente con el modelo complejo.
  • p ≫ n (más features que observaciones). Lasso hace selección de variables automáticamente. Ridge estabiliza estimaciones colineales.
  • Modelos interpretables con coeficientes. Cuando hay que explicar el modelo a un comité regulatorio o clínico.
  • Cox regression con regularización (family = "cox"), uno de los pocos engines en R que lo hace fácil.

Cuándo NO usarlo

  • Relaciones fuertemente no lineales o con interacciones complejas sin haberlas codificado explícitamente (splines, polinomios, interacciones). El modelo no las descubre por sí mismo.
  • GLM bayesiano o con efectos aleatorios: usa brms, rstanarm, lme4, glmmTMB.
  • Variables categóricas con miles de niveles: la matriz dispersa funciona pero el tuning se vuelve sensible.

Conceptos clave

  • alpha controla la mezcla L1/L2: alpha = 1 lasso puro, alpha = 0 ridge puro, intermedio elastic net. lambda la fuerza global. cv.glmnet tunea lambda por CV. alpha hay que pasarlo por fuera.
  • standardize = TRUE por defecto. glmnet escala internamente y devuelve los coeficientes en la escala original. No centres ni escales antes a mano salvo que sepas exactamente lo que haces.
  • Matriz dispersa. glmnet acepta Matrix::sparseMatrix y mantiene la dispersión. Esencial cuando trabajas con datos tipo bag-of-words o one-hot de alta cardinalidad.
  • lambda.min vs lambda.1se. El primero minimiza el error de CV. El segundo es el modelo más simple dentro de 1 desviación estándar, convención para favorecer parsimonia.
  • relax = TRUE ajusta un modelo no regularizado solo sobre las variables seleccionadas por el lasso (técnica de Meinshausen), reduce el sesgo del shrinkage en los coeficientes superviventes.

Patrón mínimo

library(tidymodels)

logit_spec <- logistic_reg(
  penalty = tune(),
  mixture = tune()    # alpha
) |>
  set_engine("glmnet") |>
  set_mode("classification")

# glmnet exige predictores numéricos; las dummies van en la receta
rec <- recipe(outcome ~ ., data = train) |>
  step_novel(all_nominal_predictors()) |>
  step_dummy(all_nominal_predictors()) |>
  step_zv(all_predictors()) |>
  step_normalize(all_numeric_predictors())

# Path en lambda es eficiente: rejilla de penalty + 2-3 valores de mixture
grid <- grid_regular(
  penalty(range = c(-5, 0), trans = log10_trans()),
  mixture(range = c(0.0, 1.0)),
  levels = c(penalty = 30, mixture = 5)
)

Trampas habituales

  • Centrar/escalar a mano antes de la receta. glmnet ya estandariza internamente. Hacerlo dos veces no rompe nada pero confunde la interpretación de los coeficientes.
  • alpha y lambda en el mismo grid no es eficiente. glmnet calcula el path completo en lambda casi gratis para un alpha fijo. Tunear mixture con pocos valores (3-5) y penalty con muchos (20-50) aprovecha eso.
  • Categorías nuevas en test. Si en test aparece un nivel que no estaba en train, model.matrix falla. step_novel antes de step_dummy lo resuelve.
  • Coeficientes en escala estandarizada. coef(glmnet_fit) devuelve coeficientes en la escala original. Si vas a interpretar, asegúrate de no confundirlos con los internos.
  • family = "cox" espera Surv(time, event) como respuesta, sintaxis distinta a la del resto de familias.

Enlaces

Relacionados en esta página


randomForestSRC

randomForestSRC (RF-SRC) es una implementación de random forest especializada en regresión, clasificación, supervivencia, regresión cuantílica y multivariante. Su valor diferencial frente a ranger está en los modos de survival forest (Ishwaran et al., 2008), competing risks y imbalanced classification con muestreo q-classification.

Autor: Hemant Ishwaran (Universidad de Miami). Es la referencia para random survival forests, ampliamente citado en análisis clínico de tiempo-a-evento.

Cuándo usarlo

  • Análisis de supervivencia con random forest (rfsrc con Surv(time, event) ~ .). Alternativa no paramétrica a Cox cuando la proporcionalidad de riesgos no es razonable.
  • Riesgos competitivos (competing.risk = TRUE), dos o más eventos terminales mutuamente excluyentes.
  • Clasificación con desbalanceo extremo. Soporte de muestreo balanceado por bootstrap dentro del árbol.
  • Outcomes multivariantes: varios y simultáneos con árboles que splittan sobre la métrica conjunta.

Cuándo NO usarlo

  • RF de clasificación o regresión estándar. ranger es más rápido y suficiente. Reserva RF-SRC para los casos que ranger no cubre bien.
  • Survival simple con proporcionalidad de riesgos razonable: un Cox (survival::coxph o glmnet con family = "cox") es interpretable y suele rendir parecido.

Conceptos clave

  • rfsrc() es la función principal. Detecta el modo por la respuesta (Surv → survival. Factor → classification. Numérico → regression).
  • VIMP (Variable Importance) interno con permutación. Disponible en variantes permute, random, anti.
  • subsample() para intervalos de confianza sobre la importancia variable, implementa la corrección de Ishwaran & Lu (2019).
  • OOB error reportado por defecto: útil pero, como en ranger, no sustituye un esquema de CV externo.
  • Paralelización via options(rf.cores = ...) y options(mc.cores = ...) para predicciones.

Patrón mínimo

library(randomForestSRC)
library(survival)

# Random survival forest
rsf <- rfsrc(
  Surv(time, status) ~ .,
  data       = veteran,
  ntree      = 1000,
  nodesize   = 15,
  importance = "permute"
)

# Tabla de importancia
print(rsf$importance)

# Predicción de función de supervivencia para nuevas observaciones
pred <- predict(rsf, newdata = veteran[1:5, ])
pred$survival[1:5, 1:5]

Trampas habituales

  • Tiempo de entrenamiento. RF-SRC es más lento que ranger por su mayor versatilidad. Para clasificación pura no merece la pena.
  • VIMP en survival se interpreta como cambio en concordance index tras permutar, no es lo mismo que VIMP en regresión.
  • block.size y bootstrap = "by.user" para esquemas de remuestreo no estándar (grupos, clusters). Por defecto se usa bootstrap clásico, que con datos agrupados infla la confianza.
  • na.action = "na.impute" imputa con árboles internos, cómodo pero opaco. Para análisis serios, imputa antes con mice o missForest y documenta.

Enlaces

Relacionados en esta página

  • ranger, alternativa más rápida para RF estándar.
  • glmnet, alternativa lineal para survival (Cox regularizado).

DALEX

DALEX (Descriptive mAchine Learning EXplanations) es un framework agnóstico al modelo para interpretabilidad: variable importance, partial dependence, Break Down / SHAP, Ceteris Paribus y diagnósticos de residuos. Funciona con cualquier modelo que tenga predict(): tidymodels, caret, mlr3, randomForest, xgboost, ranger, modelos lm/glm, keras

Mantenido por el grupo MI2.AI (Universidad Tecnológica de Varsovia), dirigido por Przemysław Biecek. Pareja natural con su libro Explanatory Model Analysis (gratuito online), que es además la mejor introducción conceptual a interpretabilidad de modelos.

Cuándo usarlo

  • Auditar un modelo black-box (gradient boosting, random forest, red neuronal) en producción o pre-deploy.
  • Comparar varios modelos en términos de importancia variable y comportamiento sobre features clave.
  • Generar explicaciones locales (por observación) para casos clínicos / regulatorios.

Cuándo NO usarlo

  • Si solo necesitas importancia variable rápida sobre un modelo tidymodels, vip es más ligero y se integra directo en el workflow.
  • SHAP exacto sobre xgboost grande: usa treeshap o SHAPforxgboost, que aprovechan la estructura de árbol y son órdenes de magnitud más rápidos que las aproximaciones model-agnostic de DALEX (predict_parts con type = "shap").

Conceptos clave

  • explain() envuelve el modelo en un objeto explainer con predict_function, datos de validación, respuesta. Es el constructor central, todo lo demás opera sobre el explainer.
  • model_parts() importance global por permutación.
  • model_profile() partial dependence (efecto marginal medio sobre una variable). Variante accumulated para ALE (más robusto a correlación entre features).
  • predict_parts() explicación local: Break Down (descomposición secuencial) o SHAP (Shapley values, model-agnostic, costoso).
  • predict_profile() Ceteris Paribus, cómo cambia la predicción de una observación al variar una sola feature.
  • model_diagnostics() residuos, funnel plot, distribución de errores por subgrupos.

Patrón mínimo

library(DALEX)
library(DALEXtra)   # bridge directo con tidymodels, mlr3, caret

# Suponiendo `final_fit` un workflow tidymodels entrenado
explainer <- explain_tidymodels(
  final_fit,
  data  = test |> dplyr::select(-outcome),
  y     = as.integer(test$outcome == "yes"),
  label = "xgboost_v1"
)

# Importancia global por permutación
vi <- model_parts(explainer, loss_function = loss_one_minus_auc, B = 25)
plot(vi)

# Partial dependence sobre una feature
pdp <- model_profile(explainer, variables = "age")
plot(pdp)

# Explicación local de una observación
bd <- predict_parts(explainer, new_observation = test[1, ], type = "break_down")
plot(bd)

Trampas habituales

  • El predict_function por defecto puede no ser el adecuado. Para clasificación binaria, DALEX espera probabilidades de la clase positiva (no clase predicha). Con tidymodels, explain_tidymodels lo configura bien. Con engines exóticos, fíjalo explícitamente.
  • PDP con features correlacionadas engaña. Partial dependence asume independencia entre la feature en estudio y el resto. Cuando hay colinealidad fuerte, ALE (type = "accumulated") es más fiel.
  • SHAP model-agnostic es lento. Si tu modelo es xgboost o lightgbm, usa treeshap, calcula SHAP exacto en tiempo polinómico aprovechando la estructura de árbol.
  • Compara modelos sobre el mismo data y y. Crear varios explainer con datos distintos invalida cualquier comparación entre model_parts.

Enlaces

Relacionados en esta página

  • vip, alternativa más ligera para importancia variable.
  • tidymodels, integración directa vía DALEXtra::explain_tidymodels.

vip

vip (Variable Importance Plots) es un paquete ligero y enfocado: calcula y plotea importancia variable para casi cualquier modelo de R. Es la opción por defecto dentro de tidymodels cuando solo necesitas un ranking de features sin la maquinaria completa de DALEX.

Autor: Brandon M. Greenwell. Forma parte del tidymodels universe de facto: la documentación de tidymodels lo usa como herramienta estándar de interpretabilidad.

Cuándo usarlo

  • Importancia variable rápida sobre un workflow tidymodels entrenado.
  • Comparar modelos sobre el mismo dataset en términos de qué features pesan más.
  • Generar el plot estándar para informes y dashboards, vip(model) devuelve un objeto ggplot directamente customizable.

Cuándo NO usarlo

  • Si necesitas interpretabilidad local (por observación), partial dependence, ALE, SHAP o diagnósticos completos, DALEX.
  • Si trabajas con modelos no estándar fuera del soporte de vip y no quieres montar el predict_function a mano.

Conceptos clave

  • vi() devuelve la tabla. vip() el plot.
  • Métodos: method = "model" (importancia nativa del modelo, p. ej. gain de xgboost), "permute" (permutación model-agnostic), "firm" (basada en partial dependence), "shap" (Shapley values, requiere fastshap).
  • Compatible con tidymodels: pasa el modelo entrenado con extract_fit_parsnip(workflow).
  • pred_wrapper es el argumento clave cuando usas method = "permute" con un modelo cuyo predict no devuelve probabilidades por defecto.

Patrón mínimo

library(vip)
library(tidymodels)

# Importancia nativa del engine (e.g. xgboost gain)
final_fit |>
  extract_fit_parsnip() |>
  vip(num_features = 20)

# Importancia por permutación (model-agnostic)
fit <- extract_fit_parsnip(final_fit)
vi(
  fit$fit,
  method      = "permute",
  train       = test,
  target      = "outcome",
  metric      = "auc",
  pred_wrapper = function(object, newdata) {
    predict(object, newdata, type = "prob")[, "yes"]
  },
  nsim = 25
)

Trampas habituales

  • method = "model" no está disponible para todos los engines. xgboost, ranger, glmnet sí. Modelos exóticos no. Cae a "permute" cuando falte.
  • Importancia nativa de xgboost (gain) está sesgada hacia features con muchos splits. Permutación es más honesta cuando el ranking es la decisión final.
  • vip(fit) vs vip(workflow). Pasa el fit extraído (extract_fit_parsnip), no el workflow entero, algunas variantes de vip no entienden el wrapper.

Enlaces

Relacionados en esta página

  • DALEX, alternativa completa de interpretabilidad model-agnostic.
  • tidymodels, integración directa.

caret

caret (Classification And REgression Training) fue durante una década el framework de referencia para modelado supervisado en R. Max Kuhn lo lanzó en 2007 y consolidó una API uniforme sobre 200+ engines, con resampling, preprocesado y tuning integrados.

En 2026 está en modo mantenimiento. El propio Kuhn redirigió su desarrollo hacia tidymodels a partir de 2018. Sigue funcionando, sigue siendo correcto, y sigue siendo legítimo encontrarlo en código heredado. Para proyectos nuevos no hay razón técnica para empezarlos aquí.

Cuándo usarlo

  • Mantener o extender código heredado que ya está en caret. Reescribirlo a tidymodels solo si hay un beneficio claro (no solo modernidad).
  • Reproducir resultados de papers o tutoriales antiguos que usan caret.
  • Casos donde el wrapper específico de caret para un engine raro aún no tiene equivalente en parsnip (cada vez menos comunes).

Cuándo NO usarlo

  • Proyectos nuevos. Use tidymodels. La API es más limpia, está activamente desarrollada y tiene mejor integración con el resto del tidyverse.
  • Pipelines complejos con preprocesado variable por fold: recipes lo hace mucho más limpio que el preprocesado integrado de caret.

Conceptos clave

  • train() es la función pivote: combina preprocesado, resampling, tuning y fit final.
  • trainControl() define el esquema de resampling (method = "cv", "repeatedcv", "boot", "LGOCV").
  • expand.grid() para el grid de hiperparámetros, manual, no hay dials equivalente.
  • preProcess acepta una lista de strings (c("center", "scale", "knnImpute")), comparado con recipes es menos componible.
  • varImp() importancia variable model-agnostic.

Patrón mínimo

library(caret)

ctrl <- trainControl(
  method          = "repeatedcv",
  number          = 5,
  repeats         = 3,
  classProbs      = TRUE,
  summaryFunction = twoClassSummary,
  savePredictions = "final"
)

grid <- expand.grid(
  mtry          = c(2, 4, 8),
  splitrule     = "gini",
  min.node.size = c(1, 5, 10)
)

set.seed(42)
fit <- train(
  outcome ~ .,
  data       = train,
  method     = "ranger",
  metric     = "ROC",
  trControl  = ctrl,
  tuneGrid   = grid,
  preProcess = c("center", "scale")
)

predict(fit, newdata = test, type = "prob")

Trampas habituales

  • set.seed antes de train no basta en paralelo. caret con doParallel necesita seeds explícito dentro de trainControl(seeds = ...). Documentado pero fácil de olvidar.
  • preProcess se aplica al dataset entero antes del split de CV en algunas versiones, verifica con la documentación de tu versión específica para evitar leakage sutil.
  • El nombre del engine cambia entre caret y el paquete subyacente. method = "ranger" no es el mismo grid que parsnip::rand_forest(engine = "ranger"), caret añade splitrule que parsnip no expone directamente.
  • Migrar a tidymodels no es trivial. Si el código caret existente funciona y está validado, reescribirlo introduce riesgo. Migra cuando haya beneficio concreto, no por estética.

Enlaces

Relacionados en esta página

  • tidymodels, sucesor moderno y mantenido.
  • mlr3, alternativa contemporánea con estilo OO.

mlr3

mlr3 es la alternativa a tidymodels con filosofía orientada a objetos (basada en R6). Sucesor de mlr (2016), reescrito desde cero por el equipo de la LMU Munich (Bernd Bischl et al.). Estilo de API más cercano a scikit-learn que al tidyverse.

Ecosistema modular: mlr3 (core), mlr3learners (engines), mlr3pipelines (preprocesado componible tipo grafo), mlr3tuning, mlr3mbo (tuning bayesiano basado en paradox), mlr3viz (visualización). Es elección legítima, no inferior, cuando el estilo OO encaja mejor con el equipo o el dominio.

Cuándo usarlo

  • Equipos cómodos con OO (Python scikit-learn, Java) que prefieren llamadas task$train(learner) sobre piping verboso.
  • Pipelines de preprocesado muy componibles, mlr3pipelines permite construir grafos DAG de operaciones con branching condicional, algo más expresivo que recipes.
  • Tuning bayesiano sofisticado con mlr3mbo y paradox, control fino del acquisition function, surrogates alternativos, multi-objetivo.
  • AutoML con mlr3automl o el meta-paquete mlr3verse.

Cuándo NO usarlo

  • Si el equipo ya está en tidyverse. La curva de adopción de OO + R6 no es trivial. Tidymodels rinde igual y se integra mejor con el resto del flujo.
  • Si solo necesitas un pipeline estándar. El extra de potencia de mlr3pipelines no compensa la verbosidad cuando el problema es directo.

Conceptos clave

  • Task = dataset + target + metadatos. TaskClassif, TaskRegr, TaskSurv son las subclases.
  • Learner = algoritmo. lrn("classif.ranger"), lrn("regr.xgboost"). Cada learner tiene param_set (hiperparámetros tipados).
  • Resampling = esquema de validación. rsmp("cv", folds = 5), rsmp("holdout"), rsmp("loo").
  • Measure = métrica. msr("classif.auc"), msr("regr.rmse").
  • Pipeline (mlr3pipelines) = grafo de PipeOps, preprocesado, modelo, ensembling, todo serializable como un GraphLearner.
  • paradox define el espacio de hiperparámetros con tipos (ParamInt, ParamDbl, ParamFct) y dependencias.

Patrón mínimo

library(mlr3verse)
library(mlr3learners)

# 1. Task
task <- TaskClassif$new("my_task", backend = train, target = "outcome")

# 2. Learner
learner <- lrn("classif.xgboost",
  predict_type = "prob",
  nrounds      = to_tune(p_int(100, 2000)),
  max_depth    = to_tune(p_int(3, 8)),
  eta          = to_tune(p_dbl(1e-3, 0.3, logscale = TRUE))
)

# 3. Resampling + measure
resampling <- rsmp("cv", folds = 5)
measure    <- msr("classif.auc")

# 4. Tuning bayesiano
instance <- tune(
  tuner      = tnr("mbo"),
  task       = task,
  learner    = learner,
  resampling = resampling,
  measure    = measure,
  term_evals = 40
)

# 5. Mejor configuración
instance$result_learner_param_vals

Trampas habituales

  • R6 es por referencia. Modificar un learner lo cambia en todos los sitios que lo referencian. learner$clone() antes de modificar es el patrón seguro.
  • set.seed global no basta: mlr3 gestiona semillas dentro del Resampling y del tuner. Para reproducibilidad estricta, fija el seed del resampling explícitamente.
  • API distinta a tidymodels. Migrar código entre frameworks rara vez es directo. Planifica la elección al principio del proyecto.
  • Documentación dispersa entre subpaquetes. mlr3book (online) es la referencia central. Navegar las viñetas individuales sin él es frustrante.

Enlaces

Relacionados en esta página

  • tidymodels, alternativa con estilo tidyverse y la elección por defecto en R hoy.
  • caret, antecesor histórico del modelado supervisado en R.