Bundle steps with workflows
In Lesson 1 you built a recipe to clean the lender's data, and in Lesson 2 you built a model spec to predict who defaults. Right now those are two separate objects sitting on your desk. To actually score the test set you have to thread them together by hand: prep the recipe, bake the train data, fit the model, bake the test data the same way, then predict. Every place you re-type a step is a place train and test can quietly drift apart. A workflow bolts the recipe and the model into one object that does the whole thing in a single fit() and a single predict().
By the end of this lesson you will be able to:
- Explain why a recipe and a model kept as two loose objects invite a train/test preprocessing mismatch
- Bundle them into one workflow and fit the whole pipeline with a single call
- Predict on raw new data and let the workflow re-apply the recipe for you, then swap the model with one line
Prerequisites: you can run R and use the |> pipe, and you have built a recipe to prep and bake your data and a model spec you can fit and predict with.
Two objects, threaded by hand
Here is the lender's loan book again, rebuilt right here so this page runs on its own, along with the recipe from Lesson 1 and the model spec from Lesson 2.
Now score the test set the by-hand way. It takes five separate steps, and you have to keep them in step with each other every single time.
It works, but look at how much can go wrong. Forget step 3 and the model never sees the test set. Bake the test set with a recipe you accidentally re-prepped on test, and the leakage you closed in Lesson 1 comes right back. Add a model later and you must remember to bake its data too.
Where the by-hand pipeline breaks
A teammate scores the model like this: prep the recipe on train and predict on train, then, for the test set, prep a brand-new recipe on test and bake test with it. The test accuracy looks great. What went wrong?
A workflow is one container
A workflow is a single object that holds your preprocessor and your model together. You start an empty one with workflow(), then add the two pieces you already have.
Read it back and the workflow tells you exactly what it will do: take raw data, run the three recipe steps, then fit a logistic regression. Notice that nothing has been fitted yet. Just like a recipe or a spec, a workflow is a blueprint until you fit it.
One fit() does the whole pipeline
Calling fit() on the workflow does both jobs at once. It preps the recipe on the training data, bakes that data, and fits the model on the result, all inside a single call.
The workflow really did run glm for you. extract_fit_engine() hands back the same kind of glm object you met in Lesson 2, now fitted on data the recipe had already prepared.
It helps to see a fitted workflow for what it is: a composition of two learned functions. To predict the outcome for a new applicant \(x\), the workflow computes
\[ \hat{y} = m\big(b(x)\big) \]
where \(b\) is the baking function the recipe learned on the training set (it carries the frozen medians, means and standard deviations from Lesson 1), and \(m\) is the model fitted in Lesson 2. Both \(b\) and \(m\) are learned once, on the training data, inside that one fit() call. At prediction time the workflow runs the same \(b\), then the same \(m\), on whatever \(x\) you hand it.
Attach the model
Here is a workflow with the recipe already added. Add the model spec so the container holds both pieces, then print it.
Show answer
wf2 <- workflow() |>
add_recipe(rec) |>
add_model(spec)
wf2predict() bakes the new data for you
Now the moment it all pays off. To predict on the held-out applicants, you hand the fitted workflow the raw test set. You do not bake it first; the workflow bakes it for you, using the recipe it already learned on train.
Ask for probabilities instead and you get the same tidy shape parsnip always returns, one column per class.
Is the workflow really doing the same thing as the five hand-steps from before? Check it directly: pull the predicted class from each and compare.
Identical, row for row. The workflow is the by-hand pipeline, bundled into one object, with no chance of baking the test set differently from the training set.
What does predict() do here?
Your fitted workflow contains a recipe (impute, normalize, dummy-code) and a model. You call predict(wf_fit, new_data = raw_test) on brand-new applicants whose columns have not been baked. What happens?
Swap the model, keep the rest
Because the workflow holds the model in one named slot, you can replace it without touching the recipe or any of the surrounding code. update_model() swaps the model spec; the recipe rides along unchanged.
A random forest is a completely different algorithm from a logistic regression, yet the recipe, the fit() call, and the predict() call did not change. And when you need a fitted piece on its own, the extractors hand it back.
A fresh workflow, end to end
Put it all together. The lender wants to try a single decision tree through the same recipe. The spec and the empty workflow are built for you. Complete the one call that preps the recipe and fits the tree in a single step.
Show answer
library(rpart)
tree_spec <- decision_tree(tree_depth = 5) |>
set_engine("rpart") |>
set_mode("classification")
tree_wf <- workflow() |>
add_recipe(rec) |>
add_model(tree_spec)
tree_fit <- fit(tree_wf, data = train)
predict(tree_fit, new_data = test) |> nrow() # one tidy prediction per held-out applicant
#> [1] 60References
- workflows package documentation (tidymodels) - the official reference for
workflow(),add_recipe(),add_model(), andfit(). - Tidy Modeling with R, ch. 7: A model workflow - Kuhn and Silge on why bundling preprocessing and modeling is sound practice, not just convenience.
- Get Started: Preprocess your data with recipes - the official walk-through that pairs a recipe with a model inside a workflow, end to end.
- workflows function reference - every verb you can use, including
update_model(),update_recipe(), and theextract_*()family.
Lesson 3 complete
You can now bundle a recipe and a model into a single workflow that fits the whole pipeline in one call and bakes new data for you at predict time, so train and test can never drift apart. Swapping the model is one line, and the rest of your code does not flinch.
Next, Lesson 4: Resample with rsample. One honest train/test split still rests its whole verdict on a single slice of luck. You will run the entire workflow across many resampled folds, so your estimate of how good the model is becomes something you can actually trust.