Anatomía de tidymodels: el ecosistema
tidymodels como meta-paquete
tidymodels no es un paquete, es un meta-paquete, igual que tidyverse. Cuando lo cargas:
install.packages("tidymodels")
library(tidymodels)R carga en bloque siete paquetes que trabajan juntos. Cada uno tiene una responsabilidad clara, y todos comparten la filosofía y las convenciones del tidyverse.
── Attaching packages ────────────────────────────────────── tidymodels 1.2.0 ──
✔ broom 1.0.7 ✔ recipes 1.1.0
✔ dials 1.3.0 ✔ rsample 1.2.1
✔ dplyr 1.1.4 ✔ tibble 3.2.1
✔ ggplot2 3.5.1 ✔ tidyr 1.3.1
✔ infer 1.0.7 ✔ tune 1.2.1
✔ modeldata 1.4.0 ✔ workflows 1.1.4
✔ parsnip 1.2.1 ✔ workflowsets 1.1.0
✔ purrr 1.0.2 ✔ yardstick 1.3.1
El listado incluye paquetes del tidyverse (dplyr, ggplot2, purrr…) y los específicos de modelado. Vamos a centrarnos en los siete del modelado.
Los 7 paquetes core
| Paquete | Responsabilidad | Verbos clave |
|---|---|---|
rsample |
Dividir datos: train/test, cross-validation, bootstrap | initial_split(), vfold_cv(), bootstraps() |
recipes |
Preprocesado declarativo (normalizar, dummy, imputar) | recipe(), step_normalize(), step_dummy(), prep(), bake() |
parsnip |
Especificar modelos de forma agnóstica al engine | linear_reg(), rand_forest(), set_engine(), set_mode() |
workflows |
Combinar recipe + modelo en un único objeto | workflow(), add_recipe(), add_model(), fit(), predict() |
tune |
Optimizar hiperparámetros con cross-validation | tune_grid(), tune_bayes(), select_best(), finalize_workflow() |
yardstick |
Calcular métricas (RMSE, AUC, accuracy, F1…) | metrics(), roc_auc(), rmse(), metric_set() |
workflowsets |
Comparar varios workflows en paralelo | workflow_set(), workflow_map(), rank_results() |
Más dos auxiliares: dials (espacios de búsqueda para hiperparámetros) y broom (tidying de outputs de modelos para análisis).
La filosofía: separar responsabilidades
Lo que distingue a tidymodels de la generación anterior (caret) es que cada paso del pipeline está en un paquete distinto, con una API limpia, en lugar de tener una función monolítica.
Beneficios:
- Componibilidad: puedes intercambiar piezas (cambiar el modelo sin tocar el preprocesado).
- Reproducibilidad: cada paso es un objeto que se guarda y se reaplica idénticamente.
- Extensibilidad: añadir un engine nuevo no requiere modificar el resto del ecosistema.
Es la misma filosofía del tidyverse aplicada a modelado.
Comparación con caret (predecesor)
caret (Max Kuhn, ~2008) era el paquete dominante de ML en R durante una década. Una sola función, caret::train(), hacía todo. Funcionó muy bien para problemas estándar, pero:
- Difícil de extender con modelos nuevos.
- Workflow opaco, argumentos crípticos y muchas opciones implícitas.
- Sin paralelismo nativo bien diseñado.
tidymodels (Max Kuhn de nuevo, ~2020) reemplazó a caret con arquitectura modular. caret sigue funcionando pero está en modo mantenimiento, para código nuevo, usa tidymodels. Si trabajas en proyectos heredados con caret, los patrones traducen bastante directos al nuevo framework.
Comparación con scikit-learn (Python)
Si vienes de Python, el mapa mental es directo:
| Python (scikit-learn) | R (tidymodels) |
|---|---|
train_test_split() |
initial_split() |
Pipeline([...]) |
workflow() |
StandardScaler, OneHotEncoder |
step_normalize(), step_dummy() |
Estimator (clases de modelo) |
parsnip model specs |
GridSearchCV |
tune_grid() |
cross_val_score |
fit_resamples() |
Métricas (accuracy_score…) |
yardstick::accuracy() y otros |
La filosofía es muy similar: separar preprocesado, modelo, evaluación. tidymodels añade el wrapper workflow que unifica todo en un objeto serializable, algo que sklearn también tiene con Pipeline pero con menos énfasis cultural.
El flujo típico end-to-end
library(tidymodels)
# 1. Dividir datos
split <- initial_split(datos, prop = 0.8, strata = outcome)
train <- training(split)
test <- testing(split)
# 2. Cross-validation folds (para tuning)
folds <- vfold_cv(train, v = 5, strata = outcome)
# 3. Receta de preprocesado
receta <- recipe(outcome ~ ., data = train) |>
step_normalize(all_numeric_predictors()) |>
step_dummy(all_nominal_predictors())
# 4. Especificar modelo (con tuning)
modelo <- rand_forest(mtry = tune(), min_n = tune(), trees = 500) |>
set_engine("ranger") |>
set_mode("classification")
# 5. Crear workflow
wf <- workflow() |>
add_recipe(receta) |>
add_model(modelo)
# 6. Tunear hiperparámetros
res <- tune_grid(
wf,
resamples = folds,
grid = 20,
metrics = metric_set(roc_auc)
)
# 7. Quedarse con el mejor y entrenar en todo el training set
final_wf <- wf |>
finalize_workflow(select_best(res, metric = "roc_auc")) |>
fit(train)
# 8. Evaluar en test set (la única vez que tocas test)
predict(final_wf, test) |>
bind_cols(test |> select(outcome)) |>
metrics(truth = outcome, estimate = .pred_class)Ocho pasos, ocho funciones limpias. Cada paso es un objeto inspeccionable. Si quieres cambiar el modelo, tocas solo el paso 4. Si quieres añadir preprocesado, solo el 3. La separación es real, no decorativa.
Trampas habituales
- Cargar
library(tidymodels)ylibrary(caret)juntos. Generan conflictos de nombres (select,train, etc.). Elige uno por proyecto. - Esperar que
tidymodelssea más rápido quecaret. No lo es necesariamente. Lo que da es estructura y reproducibilidad. Para velocidad pura, configurar paralelismo (future::plan(multisession)) es lo que importa. - Confundir
parsnipcon el motor real.parsnipes una interfaz. El cálculo real lo hace el engine subyacente (ranger,xgboost,glmnet…). Si el engine no está instalado, el modelo falla, pero el error a veces es críptico. - Olvidar
set_mode(). Muchos modelos admiten clasificación y regresión.set_mode("classification")oset_mode("regression")no es opcional, el modelo se construye distinto en cada caso.
En la siguiente entrega
Conoces el ecosistema. El primer paso de cualquier proyecto de ML es dividir los datos correctamente, train, test y opcionalmente validation. La división determina si tu evaluación de generalización es honesta o un teatro. Vemos rsample en detalle. Es lo siguiente.