I try to use the tidymodels R-package for an ml pipeline. I can define a preprocessing pipeline (a recipe) on the training data and apply it to each re-sample of my cross-validation. But this uses the (global) training data to preprocess the folds. What I would find rather correct is to define a preprocessing recipe on each "analysis" (i.e., training) part of the fold and apply it to the "assessment" (i.e., testing) part of the fold.
The following code gives an example of my problem:
library(tidyverse)
library(tidymodels)
set.seed(1000)
mtcars = mtcars |> select(mpg, hp)
init_split <- initial_split(mtcars, prop = 0.9)
preprocessing_recipe <- recipe(mpg ~ hp,
data = training(init_split)
) |>
step_normalize(all_predictors())
preprocessing_recipe = preprocessing_recipe %>% prep()
preprocessing_recipe
cv_folds <- bake(preprocessing_recipe, new_data = training(init_split)) %>%
vfold_cv(v = 3)
## these resamples are not properly scaled:
training(cv_folds$splits[[1]]) %>% lapply(mean)
## $hp
## [1] 0.1442218
training(cv_folds$splits[[1]]) %>% lapply(sd)
## $hp
## [1] 1.167365
## while the preprocessing on the training data leads to exactly scaled data:
preprocessing_recipe$template %>% lapply(mean)
## $hp
## [1] -1.249001e-16
preprocessing_recipe$template %>% lapply(sd)
## $hp
## [1] 1
The reason why the above fails is clear. But how can I change the above pipeline (efficiently, elegantly) to define a recipe on each train part of the fold and apply it to the test part? In my view this is the way to do this that avoids data leakage. I haven't found any hints in the documentation of any posts. Thanks!
When you are using a recipe you are as part of a full pipeline, you are unlikely to want to
prep()orbake()it yourself outside of diagnostic purposes. What we recommend is to use the recipe with aworkflow()to be able to attach it to a modeling model. Here I'm adding a linear regression specification. These two together can befit()andpredict()ed on. but you can also fit them inside your cross-validation loop, withfit_resamples()ortune_grid()depending on your needs.For more information see:
We can see that the workflow is fit inside each fold by looking at the estimates of the recipe. I added a function to the
extractargument ofcontrol_resamples()that pull out trained mean and sd that were calculated in the recipe.And we can see that they match the mean and sd from the original folds