Lesson 3 of 7

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.

The problem

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.

RInteractive R
library(recipes) library(parsnip) set.seed(7) n <- 240 loans <- data.frame( income = round(runif(n, 22000, 98000)), # annual income age = round(runif(n, 21, 60)), employed = round(runif(n, 2, 160)), # months at current job home = factor(sample(c("own", "rent", "mortgage"), n, TRUE)) ) risk <- with(loans, plogis(-1.1 + 1.4 * (income < 45000) + 1.0 * (employed < 20) + 0.6 * (home == "rent"))) loans$defaulted <- factor(ifelse(runif(n) < risk, "yes", "no")) train <- loans[1:180, ] # 180 applicants to learn from test <- loans[181:240, ] # 60 held back to predict on table(train$defaulted) #> no yes #> 114 66

  
RInteractive R
# Lesson 1's recipe: impute, scale, dummy-code (learned on train) rec <- recipe(defaulted ~ income + age + employed + home, data = train) |> step_impute_median(all_numeric_predictors()) |> step_normalize(all_numeric_predictors()) |> step_dummy(all_nominal_predictors()) # Lesson 2's model: a logistic regression, fit by glm spec <- logistic_reg() |> set_engine("glm") |> set_mode("classification")

  

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.

RInteractive R
prepped <- prep(rec, training = train) # 1. learn medians/means/sds on TRAIN train_bake <- bake(prepped, new_data = train) # 2. apply them to train test_bake <- bake(prepped, new_data = test) # 3. apply the SAME numbers to test fit_manual <- fit(spec, defaulted ~ ., data = train_bake) # 4. fit on baked train predict(fit_manual, new_data = test_bake) |> head() # 5. predict on baked test #> # A tibble: 6 x 1 #> .pred_class #> <fct> #> 1 no #> 2 yes #> 3 no #> 4 no #> 5 no #> 6 no

  

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.

Warning
Every hand-typed step is a chance for the test data to travel a different path than the training data. A mismatch here does not throw an error; it silently inflates or wrecks your score. This bookkeeping is exactly what a workflow takes off your hands.