workflows update_model() in R: Swap a Workflow's Model Spec
The workflows update_model() function in R replaces the parsnip model specification inside a tidymodels workflow with a new spec, while leaving the preprocessor untouched. Use it to swap engines, sweep hyperparameters, or A/B two algorithms on the same recipe without rebuilding the pipeline by hand.
update_model(wf, new_spec) # swap the model in place wf |> update_model(rf_spec) # pipe-friendly swap update_model(wf, new_spec, formula = y ~ x) # swap with a model-only formula wf |> update_model(spec) |> fit(data = df) # swap then refit update_model(wf_fit, new_spec) # resets workflow to unfitted update_recipe(wf, new_rec) # sibling verb for the preprocessor remove_model(wf) |> add_model(new_spec) # equivalent two-step path
Need explanation? Read on for examples and pitfalls.
What update_model() does
update_model() replaces the parsnip spec slot of a workflow with a new spec. It returns a new workflow object with the same preprocessor but a different model. The original workflow is not modified; the function is pure and assignment-safe.
The verb exists because add_model() refuses to overwrite an existing spec. Once a model is attached, calling add_model() a second time raises an error. update_model() is the explicit replacement door. It is the right tool whenever the recipe stays the same and only the algorithm or its hyperparameters change, which happens constantly during engine comparison, lambda tuning, and quick A/B tests across model families.
update_model() syntax and arguments
update_model() takes a workflow, a replacement spec, and an optional formula. The signature mirrors add_model(), so swapping between the two verbs is a one-word rename.
The x argument must be a workflow() object that already has a model attached; calling update_model() on a workflow with no model raises an error. The spec argument must be an unfit parsnip spec built with linear_reg(), rand_forest(), boost_tree(), or any other model family function plus set_engine(). Passing a fitted parsnip object is rejected. The formula argument lets you give the model a different formula from the preprocessor, useful when the recipe creates engineered columns that the engine should consume selectively.
update_model() is the most common spec-side iteration verb. The three patterns below cover almost every use case in practice.
| Swap target | Typical pattern | What stays constant | ||
|---|---|---|---|---|
| Engine (same family) | `linear_reg() | > set_engine("lm") to linear_reg(penalty = 0.1) |
> set_engine("glmnet")` | Recipe, outcome, predictor set |
| Hyperparameters | rand_forest(trees = 500) to rand_forest(trees = 2000, min_n = 5) |
Recipe, engine, algorithm family | ||
| Algorithm family | linear_reg() to rand_forest() |
Recipe and outcome only |
Each row is one update_model() call. The preprocessor is shared across all variants, so any difference in fit or hold-out metric is attributable to the spec alone.
Swap models with update_model(): four examples
Every example uses built-in mtcars and airquality datasets so the focus stays on the swap mechanics rather than on the data.
Example 1: Swap engines inside the linear regression family
The base case is one recipe and two engines. Build the workflow once, then swap engines without touching the preprocessor.
The original wf still holds the lm spec. wf_glmnet is a separate object with a lasso-regularized spec. Both can be fit independently to compare ordinary least squares against regularized regression on the same normalized predictors.
Example 2: Sweep hyperparameters of the same algorithm
Holding the engine constant and varying hyperparameters is what update_model() does inside tune_grid(). You can run the loop yourself when you want exact control.
update_model() lets you swap just the trees argument across iterations. The preprocessor is built once and reused, so the cost of each iteration is the model fit alone. This pattern is what tune_grid() automates; doing it by hand is useful for debugging and for sweeps too small to justify a tuning workflow.
Example 3: A/B compare two algorithm families
Comparing two algorithms on identical preprocessing is where update_model() earns its keep most often. The recipe is fixed, the spec varies, and any difference in cross-validated performance is attributable to the algorithm.
update_model() returns a workflow that fit_resamples() consumes directly. You never rebuild the recipe, so the comparison is clean: only the model changes between the two metric rows.
Example 4: Update on a fitted workflow resets it
Calling update_model() on a fitted workflow is legal but discards the fit. This is the most common surprise.
The returned workflow has the new spec attached but no trained parameters. Refit with fit(wf_reset, data = mtcars) before calling predict().
Common pitfalls
Three errors hit beginners the most. Each one has a one-line fix.
Try it yourself
Try it: Build a workflow with a recipe of Sepal.Length predicting Petal.Length plus a linear regression spec, then use update_model() to swap in a nearest_neighbor() regression spec with 5 neighbors. Save the swapped, unfitted workflow to ex_wf.
Click to reveal solution
Explanation: The first spec attaches with add_model(). The second swap happens through update_model() in the same pipeline. The result is a workflow with the kNN spec and the original recipe untouched.
Related tidymodels functions
update_model() lives inside the workflows verb family. Knowing the neighbours makes spec iteration faster.
workflow()creates the empty workflow that update_model() later modifies.add_model()attaches the initial parsnip spec to an empty workflow.remove_model()strips the spec out and leaves the model slot empty.update_recipe()swaps the preprocessor while leaving the model in place.extract_spec_parsnip()returns the unfit spec currently stored in a workflow.fit()retrains the workflow after a spec swap.
See the workflows package reference for the full verb family.
FAQ
Does update_model() refit the workflow?
No. update_model() only replaces the model slot. Any trained parameters from a prior fit are discarded, and the returned workflow is unfitted. Call fit(wf, data = ...) after the swap to retrain. This separation lets you queue several spec changes before paying the cost of a fit, which matters during interactive engine comparison and when scripting batch sweeps across hyperparameter values.
What is the difference between update_model() and tune_grid()?
update_model() is a single, manual swap of one spec for another. tune_grid() is the automated version: it accepts a workflow with tune() placeholders and a parameter grid, then internally calls update_model() once per grid row and per resample to fit every combination. Reach for update_model() when you want explicit control over a small handful of variants; reach for tune_grid() when you want to search a parameter space systematically.
Can update_model() swap between regression and classification?
Technically yes; the new spec can declare any mode. In practice you should not, because the recipe was almost certainly built for one outcome type. Changing mode usually means changing the outcome variable, which means rebuilding the recipe too. If you need both regression and classification on the same predictors, build two workflows from the start.
Does update_model() invalidate tuning results?
Yes. Tuning grids and resampled metrics are tied to the exact workflow used to produce them. Calling update_model() returns a new workflow that has not been tuned. Rerun tune_grid() or fit_resamples() on the updated workflow to get fresh results, and treat any prior select_best() output as invalid for the new spec.
Can I use update_model() to add steps to an existing spec?
Not directly. update_model() swaps the whole spec object, not individual settings. To extend a spec, rebuild it with the extra arguments and pass the new object to update_model(). Parsnip itself provides no in-place mutation on a spec; the design philosophy is to construct specs declaratively and swap them whole.