Q1 2023 tidymodels digest

  tidymodels, yardstick, recipes, dials

  Emil Hvitfeldt

The tidymodels framework is a collection of R packages for modeling and machine learning using tidyverse principles.

Since the beginning of 2021, we have been publishing quarterly updates here on the tidyverse blog summarizing what’s new in the tidymodels ecosystem. The purpose of these regular posts is to share useful new features and any updates you may have missed. You can check out the tidymodels tag to find all tidymodels blog posts here, including our roundup posts as well as those that are more focused, like these posts from the past couple of months:

Since our last roundup post, there have been CRAN releases of 24 tidymodels packages. Here are links to their NEWS files:

We’ll highlight a few especially notable changes below: more informative errors and faster code. First, loading the collection of packages:

library(tidymodels)
library(embed)

data("ames", package = "modeldata")

More informative errors

In the last few months we have been focused on refining error messages so that they are easier for the users to pinpoint what went wrong and where. Since the modeling pipeline can be quite complicated, getting uninformative errors is a no-go.

Across the tidymodels, error messages will now indicate the user-facing function that caused the error rather than the internal function that it came from.

From dials, an error that looked like

degree(range = c(1L, 5L))
#> Error in `new_quant_param()`:
#> ! Since `type = 'double'`, please use that data type for the range.

Now says that the error came from degree() rather than new_quant_param()

degree(range = c(1L, 5L))
#> Error in `degree()`:
#> ! Since `type = 'double'`, please use that data type for the range.

The same thing can be seen with the yardstick metrics

mtcars |>
  accuracy(vs, am)
#> Error in `dplyr::summarise()`:
#> ℹ In argument: `.estimate = metric_fn(truth = vs, estimate = am, na_rm =
#>   na_rm)`.
#> Caused by error in `validate_class()`:
#> ! `truth` should be a factor but a numeric was supplied.

which now errors much more informatively

mtcars |>
  accuracy(vs, am)
#> Error in `accuracy()`:
#> ! `truth` should be a factor, not a `numeric`.

Lastly, one of the biggest improvements came in recipes, which now shows which step caused the error instead of saying it happened in prep() or bake(). This is a huge improvement since preprocessing pipelines which often string together many preprocessing steps.

Before

recipe(~., data = ames) |>
  step_novel(Neighborhood, new_level = "Gilbert") |>
  prep()
#> Error in `prep()`:
#> ! Columns already contain the new level: Neighborhood

Now

recipe(~., data = ames) |>
  step_novel(Neighborhood, new_level = "Gilbert") |>
  prep()
#> Error in `step_novel()`:
#> Caused by error in `prep()` at recipes/R/recipe.R:437:8:
#> ! Columns already contain the new level: Neighborhood

Especially when calls to recipes functions are deeply nested inside the call stack, like in fit_resamples() or tune_grid(), these changes make a big difference.

Things are getting faster

As we have written about in The tidymodels is getting a whole lot faster and Writing performant code with tidy tools, we have been working on tightening up the performance of the tidymodels code. These changes are mostly related to the infrastructure code, meaning that the speedup will bring you to closer underlying implementations.

A different kind of speedup is found with the addition of the step_pca_truncated() step added in the embed package.

Principal Component Analysis is a really powerful and fast method for dimensionality reduction of large data sets. However, for data with many columns, it can be computationally expensive to calculate all the principal components. step_pca_truncated() works in much the same way as step_pca() but it only calculates the number of components it needs

pca_normal <- recipe(Sale_Price ~ ., data = ames) |>
  step_dummy(all_nominal_predictors()) |>
  step_pca(all_numeric_predictors(), num_comp = 3)

pca_truncated <- recipe(Sale_Price ~ ., data = ames) |>
  step_dummy(all_nominal_predictors()) |>
  step_pca_truncated(all_numeric_predictors(), num_comp = 3)
tictoc::tic()
prep(pca_normal) |> bake(ames)
#> # A tibble: 2,930 × 4
#>    Sale_Price     PC1    PC2   PC3
#>         <int>   <dbl>  <dbl> <dbl>
#>  1     215000 -31793.  4151. -197.
#>  2     105000 -12198.  -611. -524.
#>  3     172000 -14911.  -265. 7568.
#>  4     244000 -12072. -1813.  918.
#>  5     189900 -14418.  -345. -302.
#>  6     195500 -10704. -1367. -204.
#>  7     213500  -5858. -2805.  114.
#>  8     191500  -5932. -2762.  131.
#>  9     236500  -6368. -2862.  325.
#> 10     189000  -8368. -2219.  126.
#> # ℹ 2,920 more rows
tictoc::toc()
#> 0.782 sec elapsed
tictoc::tic()
prep(pca_truncated) |> bake(ames)
#> # A tibble: 2,930 × 4
#>    Sale_Price     PC1    PC2   PC3
#>         <int>   <dbl>  <dbl> <dbl>
#>  1     215000 -31793.  4151. -197.
#>  2     105000 -12198.  -611. -524.
#>  3     172000 -14911.  -265. 7568.
#>  4     244000 -12072. -1813.  918.
#>  5     189900 -14418.  -345. -302.
#>  6     195500 -10704. -1367. -204.
#>  7     213500  -5858. -2805.  114.
#>  8     191500  -5932. -2762.  131.
#>  9     236500  -6368. -2862.  325.
#> 10     189000  -8368. -2219.  126.
#> # ℹ 2,920 more rows
tictoc::toc()
#> 0.162 sec elapsed

The speedup will be orders of magnitude larger for very wide data.

Acknowledgements

We’d like to thank those in the community that contributed to tidymodels in the last quarter:

We’re grateful for all of the tidymodels community, from observers to users to contributors. Happy modeling!