Skip to main content

tidymodels-review-patterns

Review patterns for tidymodels workflows, including leakage, resampling, tuning, metrics, and reproducibility.

Jump to install

Source facts

Repository
choxos/BiostatAgent
Last source activity
May 27, 2026 at 20:44
Detected SKILL.md language
English
Stars
11
Forks
1

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
tidymodels-review-patterns
description
Review patterns for tidymodels workflows, including leakage, resampling, tuning, metrics, and reproducibility.
# Tidymodels Code Review Patterns ## Overview Anti-pattern detection and best practices for tidymodels workflows based on "Tidy Modeling with R" (TMwR) principles. This skill enables systematic code review for data leakage, resampling violations, workflow issues, evaluation problems, and reproducibility concerns. ## Data Leakage Patterns (CRITICAL) ### DL-001: Recipe Fitted on Test Data **Severity**: CRITICAL **Anti-Pattern**: ```r # WRONG: Fitting recipe on test data rec <- recipe(outcome ~ ., data = test_data) |> prep() # WRONG: prep() using test data rec <- recipe(outcome ~ ., data = train_data) |> prep(training = test_data) ``` **Correct Pattern**: ```r # CORRECT: Recipe always prepped on training data only rec <- recipe(outcome ~ ., data = train_data) |> prep(training = train_data) # BEST: Use workflow (handles automatically) wf <- workflow() |> add_recipe(rec) |> add_model(model_spec) fit <- fit(wf, data = train_data) ``` **Detection**: Look for `prep()` calls with test data or recipes defined on test sets. --- ### DL-002: Preprocessing Before Split **Severity**: CRITICAL **Anti-Pattern**: ```r # WRONG: Normalizing before splitting df_normalized <- df |> mutate(across(where(is.numeric), scale)) split <- initial_split(df_normalized) # WRONG: Feature selection before split important_vars <- df |> select(where(~cor(.x, df$outcome) > 0.3)) split <- initial_split(important_vars) ``` **Correct Pattern**: ```r # CORRECT: Split first, then preprocess in recipe split <- initial_split(df, strata = outcome) train_data <- training(split) rec <- recipe(outcome ~ ., data = train_data) |> step_normalize(all_numeric_predictors()) |> step_corr(all_numeric_predictors(), threshold = 0.9) ``` **Detection**: Any transformations (scale, normalize, mutate) applied before `initial_split()`. --- ### DL-003: Target Encoding Without Cross-Validation **Severity**: CRITICAL **Anti-Pattern**: ```r # WRONG: Target encoding using full dataset statistics rec <- recipe(outcome ~ ., data = train_data) |> step_lencode_mixed(all_nominal_predictors(), outcome = vars(outcome)) # Then prep without proper CV prepped <- prep(rec) ``` **Correct Pattern**: ```r # CORRECT: Target encoding within workflow with resampling rec <- recipe(outcome ~ ., data = train_data) |> step_lencode_mixed(all_nominal_predictors(), outcome = vars(outcome)) wf <- workflow() |> add_recipe(rec) |> add_model(model_spec) # Encoding computed fresh for each fold cv_results <- fit_resamples(wf, resamples = vfold_cv(train_data)) ``` **Detection**: `step_lencode_*` or `step_embed` used outside workflow with resampling. --- ### DL-004: Feature Selection Using Test Data **Severity**: CRITICAL **Anti-Pattern**: ```r # WRONG: Selecting features based on test correlations test_cors <- cor(test_data[, -1], test_data$outcome) selected_vars <- names(test_cors[abs(test_cors) > 0.3]) # WRONG: Using test data for variable importance importance <- varImp(model, data = test_data) ``` **Correct Pattern**: ```r # CORRECT: Feature selection in recipe (computed on training only) rec <- recipe(outcome ~ ., data = train_data) |> step_select_vip(all_predictors(), outcome = "outcome", threshold = 0.8) # CORRECT: Or use recursive feature elimination with CV rfe_results <- rfe_fit( wf, resamples = vfold_cv(train_data), sizes = c(5, 10, 15, 20) ) ``` **Detection**: Variable selection operations referencing test data. --- ### DL-005: prep() Called Before initial_split() **Severity**: CRITICAL **Anti-Pattern**: ```r # WRONG: Prepping recipe on full data before split full_rec <- recipe(outcome ~ ., data = full_data) |> step_normalize(all_numeric_predictors()) |> prep() # Then splitting split <- initial_split(full_data) ``` **Correct Pattern**: ```r # CORRECT: Always split first split <- initial_split(full_data, strata = outcome) train_data <- training(split) rec <- recipe(outcome ~ ., data = train_data) |> step_normalize(all_numeric_predictors()) # Don't prep manually - let workflow handle it ``` **Detection**: Sequence analysis - `prep()` appearing before `initial_split()`. --- ## Resampling Violations (MAJOR/CRITICAL) ### RS-001: Missing Stratified Sampling **Severity**: MAJOR (CRITICAL for imbalanced data) **Anti-Pattern**: ```r # WRONG: No stratification for imbalanced outcome split <- initial_split(df) # outcome is 95%/5% imbalanced # WRONG: Unstratified CV folds <- vfold_cv(train_data, v = 10) ``` **Correct Pattern**: ```r # CORRECT: Stratify by outcome split <- initial_split(df, strata = outcome) # CORRECT: Stratified CV folds <- vfold_cv(train_data, v = 10, strata = outcome) # CORRECT: For continuous outcomes, stratify by bins split <- initial_split(df, strata = outcome, breaks = 4) ``` **Detection**: Missing `strata =` argument with classification outcomes or highly skewed continuous outcomes. --- ### RS-002: Evaluating Model on Training Data **Severity**: CRITICAL **Anti-Pattern**: ```r # WRONG: Predictions on training data for evaluation fit <- fit(wf, data = train_data) preds <- predict(fit, train_data) metrics <- yardstick::metrics(preds, truth = outcome, estimate = .pred) ``` **Correct Pattern**: ```r # CORRECT: Evaluate on held-out test data fit <- fit(wf, data = train_data) preds <- predict(fit, test_data) metrics <- yardstick::metrics( bind_cols(test_data, preds), truth = outcome, estimate = .pred ) # BEST: Use resampling for robust estimates cv_results <- fit_resamples(wf, resamples = folds) collect_metrics(cv_results) ``` **Detection**: `predict()` and metrics computed on same data used for `fit()`. --- ### RS-003: Tuning Without Nested Cross-Validation **Severity**: MAJOR **Anti-Pattern**: ```r # WRONG: Tune on same folds used for final evaluation folds <- vfold_cv(train_data) tune_results <- tune_grid(wf, resamples = folds) best_params <- select_best(tune_results) # Using same folds for "final" evaluation final_wf <- finalize_workflow(wf, best_params) fit_resamples(final_wf, resamples = folds) # Overly optimistic! ``` **Correct Pattern**: ```r # CORRECT: Nested CV or separate test set # Option 1: Hold out test set for final evaluation split <- initial_split(df, strata = outcome) train_data <- training(split) test_data <- testing(split) inner_folds <- vfold_cv(train_data, strata = outcome) tune_results <- tune_grid(wf, resamples = inner_folds) # Final evaluation on untouched test set final_fit <- fit(finalize_workflow(wf, select_best(tune_results)), train_data) predict(final_fit, test_data) # Option 2: Nested CV outer_folds <- nested_cv(train_data, outside = vfold_cv(v = 5), inside = vfold_cv(v = 5)) ``` **Detection**: Same resampling object used for both tuning and final evaluation. --- ### RS-004: Missing Random Seeds **Severity**: MAJOR **Anti-Pattern**: ```r # WRONG: No seed before random operations split <- initial_split(df) # Non-reproducible folds <- vfold_cv(train_data) # Different each run boot <- bootstraps(train_data) # Non-reproducible ``` **Correct Pattern**: ```r # CORRECT: Set seed before each random operation set.seed(123) split <- initial_split(df, strata = outcome) set.seed(456) folds <- vfold_cv(training(split), strata = outcome) # Or use tidymodels control options ctrl <- control_resamples(save_pred = TRUE) ``` **Detection**: Random operations (`initial_split`, `vfold_cv`, `bootstraps`, `mc_cv`) without preceding `set.seed()`. --- ### RS-005: Validation Set Reuse **Severity**: CRITICAL **Anti-Pattern**: ```r # WRONG: Using validation set multiple times for decisions val_split <- validation_split(train_data) # First use: model selection results1 <- fit_resamples(wf1, val_split) results2 <- fit_resamples(wf2, val_split) # Choose wf1 based on validation # Second use: hyperparameter tuning tune_results <- tune_grid(wf1, val_split) # Choose best params based on same validation set # Third use: final "evaluation" on same validation final_results <- fit_resamples(final_wf, val_split) # Overfit to validation! ``` **Correct Pattern**: ```r # CORRECT: Use CV for development, hold out final test split <- initial_split(df, strata = outcome) train_data <- training(split) test_data <- testing(split) # Touch only ONCE at the end # Use CV for all development decisions folds <- vfold_cv(train_data, strata = outcome) # Model selection via CV results1 <- fit_resamples(wf1, folds) results2 <- fit_resamples(wf2, folds) # Tuning via CV tune_results <- tune_grid(wf1, folds) # Final evaluation on test_data (only once!) final_fit <- last_fit(final_wf, split) ``` **Detection**: Same validation/test split used in multiple `fit_resamples()` or `tune_grid()` calls. --- ## Workflow Issues (MINOR/MAJOR) ### WF-001: Not Using Workflows **Severity**: MINOR to MAJOR **Anti-Pattern**: ```r # WRONG: Manual prep/bake/fit rec <- recipe(outcome ~ ., data = train_data) |> step_normalize(all_numeric_predictors()) |> prep() train_baked <- bake(rec, new_data = NULL) test_baked <- bake(rec, new_data = test_data) model <- linear_reg() |> set_engine("lm") fit <- fit(model, outcome ~ ., data = train_baked) preds <- predict(fit, test_baked) ``` **Correct Pattern**: ```r # CORRECT: Use workflow wf <- workflow() |> add_recipe(recipe(outcome ~ ., data = train_data) |> step_normalize(all_numeric_predictors())) |> add_model(linear_reg() |> set_engine("lm")) fit <- fit(wf, data = train_data) preds <- predict(fit, new_data = test_data) ``` **Benefits of workflows**: - Automatic handling of preprocessing on new data - Proper integration with tuning and resampling - Cleaner code organization - Reduced risk of data leakage --- ### WF-002: Inconsistent Preprocessing Train/Test **Severity**: MAJOR **Anti-Pattern**: ```r # WRONG: Different preprocessing for train vs test train_processed <- train_data |> mutate(across(where(is.numeric), ~(.x - mean(.x)) / sd(.x))) test_processed <- test_data |>
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub