This post uses the penguins dataset modified by Allison
Horst in all the code
examples (as an alternative to iris
).
Column-wise Workflows
The new across()
function supersedes functionalities of _at
, _if
,
_all
variants. The first argument, .cols
, selects the columns you
want to operate on. It uses tidy selection (like select()
) so you can
pick variables by position, name, and type. The second argument, .fns
,
is a function or list of functions to apply to each column. This can
also be a purrr style formula
For conditional selection (previous _if
variants), predicate function
should be wrapped in where
.
Apply multiple functions using list and use the .names
argument to
control column names.
Row-wise Workflows
Row-wise operations require a special type of grouping where each group
consists of a single row. You create this with rowwise()
.
rowwise
doesn’t need any additional arguments unless you have
variables that identify the rows, like student_id
here. This can be
helpful when you want to keep a row identifier.
Like group_by
, rowwise
doesn’t really do anything itself; it just
changes how the other verbs work.
rowwise
takes each row, feeds it into a function, and return a tibble
with the same number of rows. This essentially parallelize a function
over the rows in the dataframe. In this case, the mean()
function is
vectorized. But, if a function is already vectorized, then rowwise
is
not needed.
Another family of summary functions have “parallel” extensions where you can provide multiple variables in the arguments:
Where these functions exist, they’ll usually be faster than rowwise
.
The advantage of rowwise
is that it works with any function, not just
those that are already vectorized.
However, an advantage of rowwise
even there is other ways is that it’s
paired with c_across()
, which works like c()
but uses the same
tidyselect syntax as across()
. That makes it easy to operate on
multiple variables:
Plus, a rowwise df will naturally contain exactly the same rows after
summarize()
, the same as mutate
List Columns
Because lists can contain anything, you can use list-columns to keep
related objects together, regardless of what type of thing they are.
List-columns give you a convenient storage mechanism and rowwise
gives
you a convenient computation mechanism.
Simulation
The basic idea of using rowwise
to perform simulation is to store all
your simulation parameters in a data frame, similar to purrr::pmap
.
Then you can either generate a list-column containing the simulated
values with mutate
:
Or taking advantage of summarize
’s new features to return multiple
rows per group
In dplyr 1.1, you should use reframe()
instead of summarize()
to
return multiple rows.
Without rowwise
, you would need to use purrr::pmap
to perform the
simulation.
Group-wise Models
The new nest_by()
function works similarly to group_nest()
Now we can use mutate
to fit a model to each data frame:
And then extract model summaries or coefficients with summarize()
and
broom
functions (note that by_species
is still a rowwise data
frame):
An alternative approach
New summarize
Features
Use reframe()
instead of summarize()
to return multiple rows
starting from dplyr 1.1.
Multiple Rows and Columns
Two big changes make summarize()
much more flexible. A single summary
expression can now return:
-
A vector of any length, creating multiple rows. (so we can use summary that returns multiple values without
list
) -
A data frame, creating multiple columns.
Or return multiple columns from a single summary expression:
At the first glance this may seem not so different with supplying
multiple name-value pairs. But this can be useful inside functions. For
example, in the previous quantile
code it would be nice to be able to
reduce the duplication so that we don’t have to type the quantile values
twice. We can now write a simple function because summary expressions
can now be data frames or tibbles:
When combining glue syntax and tidy evaluation, it is easy to dynamically name the column names.
As an aside, if we name the tibble expression in summarize()
that part
will be packed in the result, which can be solved by tidyr::unpack
.
That’s because when we leave the name off, the data frame result is
automatically unpacked.
Non-summary Context
In combination with rowwise operations, summarize()
is now
sufficiently powerful to replace many workflows that previously required
a map()
function.
For example, to read all the all the .csv files in the current directory, you could write:
Move Columns
New verb relocate
is provided to change column positions with the same
syntax as select
. The default behavior is to move selected columns to
the left-hand side
Similarly, mutate
gains new arguments .after
and .before
to
control where new columns should appear.
Row Mutations
dplyr has a new experimental family of row mutation functions inspired
by SQL’s UPDATE
, INSERT
, UPSERT
, and DELETE
. Like the join
functions, they all work with a pair of data frames:
-
rows_update(x, y)
updates existing rows in x with values in y. -
rows_patch(x, y)
works like rows_update() but only changesNA
values. -
rows_insert(x, y)
adds new rows to x from y. -
rows_upsert(x, y)
updates existing rows in x and adds new rows from y. -
rows_delete(x, y)
deletes rows in x that match rows in y.
The rows_
functions match x and y using keys. All of them check
that the keys of x and y are valid (i.e. unique) before doing anything.
We can use rows_insert()
to add new rows:
Note that rows_insert()
will fail if we attempt to insert a row that
already exists:
If you want to update existing values, use rows_update()
. It will
throw an error if one of the rows to update does not exist:
rows_patch()
is a variant of rows_update()
that will only update
values in x that are NA
.
row_upsert
update a df or insert new rows.
Context Dependent Expressions
n()
is a special function in dplyr which return the number of
observations in the current group. Now the new version comes with more
such special functions, aka context dependent expressions. These
functions return information about the “current” group or “current”
variable, so only work inside specific contexts like summarize()
and
mutate()
. Specifically, a family of cur_
functions are added:
-
cur_data()
gives the current data for the current group (excluding grouping variables,cur_data_all
in developmental version returns grouping variables as well) -
cur_group()
gives the group keys, a tibble with one row and one column for each grouping variable. -
cur_group_id()
gives a unique numeric identifier for the current group -
cur_column()
gives the name of the current column (inacross()
only).
cur_data()
is deprecated in favor of pick(col1, col2, ...)
in dplyr
1.1.
Superseded Functions
top_n()
, sample_n()
, and sample_frac()
have been superseded in
favor of a new family of slice helpers: slice_min()
, slice_max()
,
slice_head()
, slice_tail()
, slice_random()
.
summarize()
gains new argument .groups
to control grouping structure
of theh result.
-
.groups = "drop_last"
drops the last grouping level (i.e. the default behaviour). -
.groups = "drop"
drops all grouping levels and returns a tibble. -
.groups = "keep"
preserves the grouping of the input. -
.groups = "rowwise"
turns each row into its own group.
Other Changes
The new rename_with()
makes it easier to rename variables
programmatically:
You can optionally choose which columns to apply the transformation to with the second argument:
mutate()
gains argument .keep
that allows you to control which
columns are retained in the output:
Recipes
This in-progress section documents tasks that would otherwise been impossible or laborious with previous version of dplyr.
Replace Missing Values in Multiple Columnes
Since tidyr::replace_na
does not support tidy select syntax, replacing
NA values in multiple columns could be a drudgery. Now this is made easy
with coalesce
and across
Rolling Regression
We can easily perform rolling computation with the slider
package and
pick()
.