dplyr::across()
across(.cols = everything(), .fns = NULL, ..., .names = NULL) across() and where() are dplyr functions that enable column-wise transformations. across() applies the same function(s) to multiple columns simultaneously, replacing values in place. where() is a selection helper that picks columns based on their type or a condition, making it easy to target specific column types for transformation.
Syntax
across(.cols = everything(), .fns = NULL, ..., .names = NULL)
where(.predicate, ...)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
.cols | <tidy-select> | everything() | Columns to transform. Accepts column names, positions, or selection helpers. |
.fns | <list> / <function> | NULL | Functions to apply. Can be a single function, named list, or unnamed list of functions. |
.names | <string> | NULL | Formula for naming output columns. Uses {col} and {fn} glue syntax. |
.predicate | <function> | , | A function that returns TRUE/FALSE for each column. |
... | <arguments> | , | Additional arguments passed to .fns. |
Examples
Basic usage with mutate
Apply mean() to all numeric columns to center them around zero.
library(dplyr)
df <- tibble(
a = c(1, 3, 5),
b = c(2, 4, 6),
name = c("Alice", "Bob", "Carol")
)
df |> mutate(across(where(is.numeric), ~ .x - mean(.x)))
#> # A tibble: 3 × 3
#> a b name
#> <dbl> <dbl> <chr>
#> 1 -2 -2 Alice
#> 2 0 0 Bob
#> 3 2 2 Carol
Applying multiple functions
Passing a named list to .fns lets you compute several summary statistics per column in one pass. Each function in the list receives every selected column, and the list names become suffixes in the output column names. This replaces the awkward pattern of writing separate summarise() calls for every statistic and column pair, which quickly becomes unmanageable with more than a handful of columns.
df <- tibble(
x = c(1, 2, 3, 4, 5),
y = c(10, 20, 30, 40, 50),
z = c("a", "b", "c", "d", "e")
)
df |> summarise(across(where(is.numeric), list(mean = mean, sd = sd)))
#> # A tibble: 1 × 4
#> x_mean x_sd y_mean y_sd
#> <dbl> <dbl> <dbl> <dbl>
#> 1 3 1.4 30 15
Custom naming with .names
When .fns is a named list, dplyr automatically constructs output names by appending the function name to each column name — x becomes x_mean and x_sd. The .names argument overrides this convention with a glue string. The placeholders {col} and {fn} resolve to the original column name and function name respectively, letting you control the separator and ordering. This becomes essential when the default names would clash with existing columns or when you need names that a downstream pipeline expects.
df <- tibble(
score1 = c(85, 92, 78),
score2 = c(90, 88, 95),
name = c("Alice", "Bob", "Carol")
)
df |> mutate(across(starts_with("score"), round, .names = "{col}_rounded"))
#> # A tibble: 3 × 5
#> score1 score2 name score1_rounded score2_rounded
#> <dbl> <dbl> <chr> <dbl> <dbl>
#> 1 85 90 Alice 85 90
#> 2 92 88 Bob 92 88
#> 3 78 95 Carol 78 95
Using where() with other verbs
The where() selection helper is not restricted to across(). It works anywhere tidyselect semantics are accepted, including select(), rename(), and relocate(). The predicate function receives each column as its argument and must return a single logical value. Built-in type-checkers like is.numeric, is.character, is.factor, and is.logical cover most use cases, but you can also write custom predicates — for instance, to select columns whose names match a pattern or whose values exceed a threshold.
df <- tibble(
id = 1:3,
age = c(25, 30, 35),
salary = c(50000, 60000, 70000),
name = c("A", "B", "C")
)
# Select all numeric columns
df |> select(where(is.numeric))
#> # A tibble: 3 × 3
#> id age salary
#> <int> <dbl> <dbl>
#> 1 1 25 50000
#> 2 2 30 60000
#> 3 3 35 70000
# Summarise all numeric columns
df |> summarise(across(where(is.numeric), sum))
#> # A tibble: 1 × 3
#> id age salary
#> <int> <dbl> <dbl>
#> 1 6 90 180000
Common patterns
The following examples show idiomatic across() usage patterns that appear frequently in data analysis workflows. Each pattern replaces common dplyr boilerplate with a compact, readable across() call. These patterns cover numeric scaling, type conversion, and applying different functions to different column types in a single pipeline step.
Scaling all numeric columns
# Standardize (z-score) all numeric columns
df <- tibble(
a = c(1, 2, 3, 4, 5),
b = c(10, 20, 30, 40, 50)
)
df |> mutate(across(where(is.numeric), ~ (.x - mean(.x)) / sd(.x)))
Converting character columns to factors
When a data frame stores categorical data as plain character vectors, modeling functions like lm() and glm() will treat each unique string as a separate level rather than recognizing them as factor categories. Converting with across(where(is.character), factor) is the concise way to fix this for every character column at once. Be aware that the default factor ordering is alphabetical, which may not match the intended order for plotting or modeling.
df <- tibble(
name = c("Alice", "Bob", "Carol"),
score = c(85, 92, 78)
)
df |> mutate(across(where(is.character), factor))
Conditional transformations
You can chain multiple across() calls in a single mutate() to apply different transformations to different column types. Each across() targets a specific subset of columns with where(), and dplyr applies all of them to the data frame in the order they appear. This pattern avoids intermediate assignments and keeps the transformation logic readable when column types and corresponding functions are well-defined.
df <- tibble(
a = c(1.5, 2.5, 3.5),
b = c(4.1, 5.2, 6.3),
name = c("x", "y", "z")
)
df |> mutate(
across(where(is.double), floor),
across(where(is.character), toupper)
)
across() applies one or more functions to multiple columns selected with tidy-select syntax. Inside mutate(), across(where(is.numeric), scale) scales all numeric columns. Inside summarise(), across(c(x, y), list(mean = mean, sd = sd)) computes mean and SD for selected columns. The .names argument controls output column names using a {.col}_{.fn} glue template. across() replaces the older _at(), _if(), and _all() scoped verb variants.
Using across() effectively
across() is most powerful when the same transformation must apply to many columns. Manually repeating mutate calls for each column is verbose and error-prone, changing the transformation requires editing every call. With across(), the transformation is written once and applied to all selected columns, making the intent clear and the code maintainable. This matters most for datasets with many columns of the same type, such as wide survey data or time-series data with one column per period.
The column selector in across() uses tidyselect syntax. where(is.numeric) selects all numeric columns, which is the most common usage. starts_with("q_") selects columns by name prefix. c(col1, col2, col3) selects specific named columns. Combining selectors with c() selects the union; the ! operator negates. Using where() with a predicate function is particularly useful for type-based operations like converting all factor columns or scaling all numeric columns.
When applying multiple functions with a named list, across() creates multiple output columns with names constructed from the original column name and the function name. Managing these names with .names = "{.col}_{.fn}" or a custom glue pattern keeps the output organized. For creating both a raw and a transformed version of each column, the naming pattern distinguishes them clearly.
The .cols argument evaluates in the context of the data frame, not the calling environment, which means column names are bare and do not need quoting. When the column selection itself comes from a variable — a character vector of column names to process — use all_of() inside across() to refer to the external variable.