dplyr across(): Apply the Same Function to Multiple Columns at Once

across() applies the same function to multiple columns inside mutate() or summarise(). It replaced mutate_if, mutate_at, and mutate_all with one unified verb.

Before across, you wrote mutate(col1 = round(col1), col2 = round(col2), col3 = round(col3)). Now: mutate(across(c(col1, col2, col3), round)).

Basic: One Function, Multiple Columns

library(dplyr) # Round all numeric columns iris |> mutate(across(where(is.numeric), ~ round(.x, 1))) |> head(4)


  
library(dplyr) # Mean of specific columns per group mtcars |> group_by(cyl) |> summarise(across(c(mpg, hp, wt), ~ round(mean(.x), 1)), .groups = "drop")


  

Column Selection Inside across()

library(dplyr) # By name mtcars |> summarise(across(c(mpg, hp, wt), mean)) |> round(1) # By pattern iris |> summarise(across(starts_with("Sepal"), ~ round(mean(.x), 2))) # By type iris |> summarise(across(where(is.numeric), ~ round(median(.x), 2))) # By exclusion mtcars |> summarise(across(-c(cyl, vs, am, gear, carb), mean)) |> round(1)


  

Multiple Functions per Column

Use a named list to apply several functions, creating multiple output columns.

library(dplyr) mtcars |> group_by(cyl) |> summarise( across(c(mpg, hp), list(mean = ~ round(mean(.x), 1), sd = ~ round(sd(.x), 1)), .names = "{.col}_{.fn}" ), .groups = "drop" )


  

.names: Control Output Column Names

library(dplyr) # Default: "{.col}_{.fn}" → mpg_mean, mpg_sd # Custom: "{.fn}_{.col}" → mean_mpg, sd_mpg mtcars |> summarise(across(c(mpg, hp), list(avg = mean, min = min, max = max), .names = "{.fn}_{.col}" )) |> round(1)


  

across in mutate: Transform Columns

library(dplyr) # Z-score standardize, saving as new columns mtcars |> mutate(across(c(mpg, hp, wt), ~ round((.x - mean(.x)) / sd(.x), 2), .names = "{.col}_z" )) |> select(mpg, mpg_z, hp, hp_z, wt, wt_z) |> head(5)


  

if_any() and if_all(): across for filter

library(dplyr) # Keep rows where ANY numeric column > 6 (in iris) iris |> filter(if_any(where(is.numeric), ~ .x > 6)) |> head(5) # Keep rows where ALL Sepal columns > 5 iris |> filter(if_all(starts_with("Sepal"), ~ .x > 5)) |> head(5)


  

Practice Exercises

Exercise 1: Standardize All Numeric Columns

Z-score standardize every numeric column in iris.

library(dplyr)


  
Click to reveal solution ```r
library(dplyr) iris |> mutate(across(where(is.numeric), ~ round((.x - mean(.x)) / sd(.x), 2))) |> head(5)

  

Summary

Pattern Code
All numeric across(where(is.numeric), fn)
By name across(c(col1, col2), fn)
By pattern across(starts_with("x"), fn)
Multiple fns across(cols, list(mean = mean, sd = sd))
Custom names .names = "{.fn}_{.col}"
Filter any filter(if_any(cols, ~ .x > 5))
Filter all filter(if_all(cols, ~ .x > 5))

FAQ

What replaced mutate_at, mutate_if, mutate_all?

across() replaced all three in dplyr 1.0. mutate_if(is.numeric, fn)mutate(across(where(is.numeric), fn)). mutate_at(vars(x,y), fn)mutate(across(c(x,y), fn)).

Can I use across() in filter()?

Not directly. Use if_any() or if_all() instead: filter(if_any(cols, ~ .x > threshold)).

What's Next?