Title: | Functional Programming Tools |
Version: | 1.0.4 |
Description: | A complete and consistent functional programming toolkit for R. |
License: | MIT + file LICENSE |
URL: | https://purrr.tidyverse.org/, https://github.com/tidyverse/purrr |
BugReports: | https://github.com/tidyverse/purrr/issues |
Depends: | R (≥ 4.0) |
Imports: | cli (≥ 3.6.1), lifecycle (≥ 1.0.3), magrittr (≥ 1.5.0), rlang (≥ 1.1.1), vctrs (≥ 0.6.3) |
Suggests: | covr, dplyr (≥ 0.7.8), httr, knitr, lubridate, rmarkdown, testthat (≥ 3.0.0), tibble, tidyselect |
LinkingTo: | cli |
VignetteBuilder: | knitr |
Biarch: | true |
Config/build/compilation-database: | true |
Config/Needs/website: | tidyverse/tidytemplate, tidyr |
Config/testthat/edition: | 3 |
Config/testthat/parallel: | TRUE |
Encoding: | UTF-8 |
RoxygenNote: | 7.3.2 |
NeedsCompilation: | yes |
Packaged: | 2025-01-29 21:33:56 UTC; hadleywickham |
Author: | Hadley Wickham |
Maintainer: | Hadley Wickham <hadley@posit.co> |
Repository: | CRAN |
Date/Publication: | 2025-02-05 18:00:01 UTC |
purrr: Functional Programming Tools
Description
A complete and consistent functional programming toolkit for R.
Author(s)
Maintainer: Hadley Wickham hadley@posit.co (ORCID)
Authors:
Lionel Henry lionel@posit.co
Other contributors:
Posit Software, PBC [copyright holder, funder]
See Also
Useful links:
Report bugs at https://github.com/tidyverse/purrr/issues
Pipe operator
Description
Pipe operator
Usage
lhs %>% rhs
Accumulate intermediate results of a vector reduction
Description
accumulate()
sequentially applies a 2-argument function to elements of a
vector. Each application of the function uses the initial value or result
of the previous application as the first argument. The second argument is
the next value of the vector. The results of each application are
returned in a list. The accumulation can optionally terminate before
processing the whole vector in response to a done()
signal returned by
the accumulation function.
By contrast to accumulate()
, reduce()
applies a 2-argument function in
the same way, but discards all results except that of the final function
application.
accumulate2()
sequentially applies a function to elements of two lists, .x
and .y
.
Usage
accumulate(
.x,
.f,
...,
.init,
.dir = c("forward", "backward"),
.simplify = NA,
.ptype = NULL
)
accumulate2(.x, .y, .f, ..., .init, .simplify = NA, .ptype = NULL)
Arguments
.x |
A list or atomic vector. |
.f |
For For The accumulation terminates early if |
... |
Additional arguments passed on to the mapped function. We now generally recommend against using # Instead of x |> map(f, 1, 2, collapse = ",") # do: x |> map(\(x) f(x, 1, 2, collapse = ",")) This makes it easier to understand which arguments belong to which function and will tend to yield better error messages. |
.init |
If supplied, will be used as the first value to start
the accumulation, rather than using |
.dir |
The direction of accumulation as a string, one of
|
.simplify |
If |
.ptype |
If |
.y |
For |
Value
A vector the same length of .x
with the same names as .x
.
If .init
is supplied, the length is extended by 1. If .x
has
names, the initial value is given the name ".init"
, otherwise
the returned vector is kept unnamed.
If .dir
is "forward"
(the default), the first element is the
initial value (.init
if supplied, or the first element of .x
)
and the last element is the final reduced value. In case of a
right accumulation, this order is reversed.
The accumulation terminates early if .f
returns a value wrapped
in a done()
. If the done box is empty, the last value is
used instead and the result is one element shorter (but always
includes the initial value, even when terminating at the first
iteration).
Life cycle
accumulate_right()
is soft-deprecated in favour of the .dir
argument as of rlang 0.3.0. Note that the algorithm has
slightly changed: the accumulated value is passed to the right
rather than the left, which is consistent with a right reduction.
Direction
When .f
is an associative operation like +
or c()
, the
direction of reduction does not matter. For instance, reducing the
vector 1:3
with the binary function +
computes the sum ((1 + 2) + 3)
from the left, and the same sum (1 + (2 + 3))
from the
right.
In other cases, the direction has important consequences on the
reduced value. For instance, reducing a vector with list()
from
the left produces a left-leaning nested list (or tree), while
reducing list()
from the right produces a right-leaning list.
See Also
reduce()
when you only need the final reduced value.
Examples
# With an associative operation, the final value is always the
# same, no matter the direction. You'll find it in the first element for a
# backward (left) accumulation, and in the last element for forward
# (right) one:
1:5 |> accumulate(`+`)
1:5 |> accumulate(`+`, .dir = "backward")
# The final value is always equal to the equivalent reduction:
1:5 |> reduce(`+`)
# It is easier to understand the details of the reduction with
# `paste()`.
accumulate(letters[1:5], paste, sep = ".")
# Note how the intermediary reduced values are passed to the left
# with a left reduction, and to the right otherwise:
accumulate(letters[1:5], paste, sep = ".", .dir = "backward")
# By ignoring the input vector (nxt), you can turn output of one step into
# the input for the next. This code takes 10 steps of a random walk:
accumulate(1:10, \(acc, nxt) acc + rnorm(1), .init = 0)
# `accumulate2()` is a version of `accumulate()` that works with
# 3-argument functions and one additional vector:
paste2 <- function(acc, nxt, sep = ".") paste(acc, nxt, sep = sep)
letters[1:4] |> accumulate(paste2)
letters[1:4] |> accumulate2(c("-", ".", "-"), paste2)
# You can shortcircuit an accumulation and terminate it early by
# returning a value wrapped in a done(). In the following example
# we return early if the result-so-far, which is passed on the LHS,
# meets a condition:
paste3 <- function(out, input, sep = ".") {
if (nchar(out) > 4) {
return(done(out))
}
paste(out, input, sep = sep)
}
letters |> accumulate(paste3)
# Note how we get twice the same value in the accumulation. That's
# because we have returned it twice. To prevent this, return an empty
# done box to signal to accumulate() that it should terminate with the
# value of the last iteration:
paste3 <- function(out, input, sep = ".") {
if (nchar(out) > 4) {
return(done())
}
paste(out, input, sep = sep)
}
letters |> accumulate(paste3)
# Here the early return branch checks the incoming inputs passed on
# the RHS:
paste4 <- function(out, input, sep = ".") {
if (input == "f") {
return(done())
}
paste(out, input, sep = sep)
}
letters |> accumulate(paste4)
# Simulating stochastic processes with drift
## Not run:
library(dplyr)
library(ggplot2)
map(1:5, \(i) rnorm(100)) |>
set_names(paste0("sim", 1:5)) |>
map(\(l) accumulate(l, \(acc, nxt) .05 + acc + nxt)) |>
map(\(x) tibble(value = x, step = 1:100)) |>
list_rbind(names_to = "simulation") |>
ggplot(aes(x = step, y = value)) +
geom_line(aes(color = simulation)) +
ggtitle("Simulations of a random walk with drift")
## End(Not run)
Create a list of given length
Description
This function was deprecated in purrr 1.0.0 since it's not related to the core purpose of purrr.
It can be useful to create an empty list that you plan to fill later. This is
similar to the idea of seq_along()
, which creates a vector of the same
length as its input.
Usage
list_along(x)
Arguments
x |
A vector. |
Value
A list of the same length as x
.
Examples
x <- 1:5
seq_along(x)
list_along(x)
Coerce array to list
Description
array_branch()
and array_tree()
enable arrays to be
used with purrr's functionals by turning them into lists. The
details of the coercion are controlled by the margin
argument. array_tree()
creates an hierarchical list (a tree)
that has as many levels as dimensions specified in margin
,
while array_branch()
creates a flat list (by analogy, a
branch) along all mentioned dimensions.
Usage
array_branch(array, margin = NULL)
array_tree(array, margin = NULL)
Arguments
array |
An array to coerce into a list. |
margin |
A numeric vector indicating the positions of the
indices to be to be enlisted. If |
Details
When no margin is specified, all dimensions are used by
default. When margin
is a numeric vector of length zero, the
whole array is wrapped in a list.
Examples
# We create an array with 3 dimensions
x <- array(1:12, c(2, 2, 3))
# A full margin for such an array would be the vector 1:3. This is
# the default if you don't specify a margin
# Creating a branch along the full margin is equivalent to
# as.list(array) and produces a list of size length(x):
array_branch(x) |> str()
# A branch along the first dimension yields a list of length 2
# with each element containing a 2x3 array:
array_branch(x, 1) |> str()
# A branch along the first and third dimensions yields a list of
# length 2x3 whose elements contain a vector of length 2:
array_branch(x, c(1, 3)) |> str()
# Creating a tree from the full margin creates a list of lists of
# lists:
array_tree(x) |> str()
# The ordering and the depth of the tree are controlled by the
# margin argument:
array_tree(x, c(3, 1)) |> str()
Convert an object into a mapper function
Description
as_mapper
is the powerhouse behind the varied function
specifications that most purrr functions allow. It is an S3
generic. The default method forwards its arguments to
rlang::as_function()
.
Usage
as_mapper(.f, ...)
## S3 method for class 'character'
as_mapper(.f, ..., .null, .default = NULL)
## S3 method for class 'numeric'
as_mapper(.f, ..., .null, .default = NULL)
## S3 method for class 'list'
as_mapper(.f, ..., .null, .default = NULL)
Arguments
.f |
A function, formula, or vector (not necessarily atomic). If a function, it is used as is. If a formula, e.g.
This syntax allows you to create very compact anonymous
functions. Note that formula functions conceptually take dots
(that's why you can use If character vector, numeric vector, or list, it is
converted to an extractor function. Character vectors index by
name and numeric vectors index by position; use a list to index
by position and name at different levels. If a component is not
present, the value of |
... |
Additional arguments passed on to methods. |
.default , .null |
Optional additional argument for extractor functions
(i.e. when |
Examples
as_mapper(\(x) x + 1)
as_mapper(1)
as_mapper(c("a", "b", "c"))
# Equivalent to function(x) x[["a"]][["b"]][["c"]]
as_mapper(list(1, "a", 2))
# Equivalent to function(x) x[[1]][["a"]][[2]]
as_mapper(list(1, attr_getter("a")))
# Equivalent to function(x) attr(x[[1]], "a")
as_mapper(c("a", "b", "c"), .default = NA)
Coerce a list to a vector
Description
These functions were superseded in purrr 1.0.0 in favour of
list_simplify()
which has more consistent semantics based on vctrs
principles:
-
as_vector(x)
is nowlist_simplify(x)
-
simplify(x)
is nowlist_simplify(x, strict = FALSE)
-
simplify_all(x)
ismap(x, list_simplify, strict = FALSE)
Superseded functions will not go away, but will only receive critical bug fixes.
Usage
as_vector(.x, .type = NULL)
simplify(.x, .type = NULL)
simplify_all(.x, .type = NULL)
Arguments
.x |
A list of vectors |
.type |
Can be a vector mold specifying both the type and the
length of the vectors to be concatenated, such as |
Examples
# was
as.list(letters) |> as_vector("character")
# now
as.list(letters) |> list_simplify(ptype = character())
# was:
list(1:2, 3:4, 5:6) |> as_vector(integer(2))
# now:
list(1:2, 3:4, 5:6) |> list_c(ptype = integer())
Map at depth
Description
This function is defunct and has been replaced by map_depth()
.
See also modify_depth()
for a version that preserves the types of
the elements of the tree.
Usage
at_depth(.x, .depth, .f, ...)
Create an attribute getter function
Description
attr_getter()
generates an attribute accessor function; i.e., it
generates a function for extracting an attribute with a given
name. Unlike the base R attr()
function with default options, it
doesn't use partial matching.
Usage
attr_getter(attr)
Arguments
attr |
An attribute name as string. |
See Also
Examples
# attr_getter() takes an attribute name and returns a function to
# access the attribute:
get_rownames <- attr_getter("row.names")
get_rownames(mtcars)
# These getter functions are handy in conjunction with pluck() for
# extracting deeply into a data structure. Here we'll first
# extract by position, then by attribute:
obj1 <- structure("obj", obj_attr = "foo")
obj2 <- structure("obj", obj_attr = "bar")
x <- list(obj1, obj2)
pluck(x, 1, attr_getter("obj_attr")) # From first object
pluck(x, 2, attr_getter("obj_attr")) # From second object
Wrap a function so it will automatically browse()
on error
Description
A function wrapped with auto_browse()
will automatically enter an
interactive debugger using browser()
when ever it encounters an error.
Usage
auto_browse(.f)
Arguments
.f |
A function to modify, specified in one of the following ways:
|
Value
A function that takes the same arguments as .f
, but returns
a different value, as described above.
Adverbs
This function is called an adverb because it modifies the effect of a function (a verb). If you'd like to include a function created an adverb in a package, be sure to read faq-adverbs-export.
See Also
Other adverbs:
compose()
,
insistently()
,
negate()
,
partial()
,
possibly()
,
quietly()
,
safely()
,
slowly()
Examples
# For interactive usage, auto_browse() is useful because it automatically
# starts a browser() in the right place.
f <- function(x) {
y <- 20
if (x > 5) {
stop("!")
} else {
x
}
}
if (interactive()) {
map(1:6, auto_browse(f))
}
Get an element deep within a nested data structure, failing if it doesn't exist
Description
chuck()
implements a generalised form of [[
that allow you to index
deeply and flexibly into data structures. If the index you are trying to
access does not exist (or is NULL
), it will throw (i.e. chuck) an error.
Usage
chuck(.x, ...)
Arguments
.x |
A vector or environment |
... |
A list of accessors for indexing into the object. Can be an positive integer, a negative integer (to index from the right), a string (to index into names), or an accessor function (except for the assignment variants which only support names and positions). If the object being indexed is an S4 object, accessing it by name will return the corresponding slot. Dynamic dots are supported. In particular, if
your accessors are stored in a list, you can splice that in with
|
See Also
pluck()
for a quiet equivalent.
Examples
x <- list(a = 1, b = 2)
# When indexing an element that doesn't exist `[[` sometimes returns NULL:
x[["y"]]
# and sometimes errors:
try(x[[3]])
# chuck() consistently errors:
try(chuck(x, "y"))
try(chuck(x, 3))
Compose multiple functions together to create a new function
Description
Create a new function that is the composition of multiple functions,
i.e. compose(f, g)
is equivalent to function(...) f(g(...))
.
Usage
compose(..., .dir = c("backward", "forward"))
Arguments
... |
Functions to apply in order (from right to left by default). Formulas are converted to functions in the usual way. Dynamic dots are supported. In particular, if
your functions are stored in a list, you can splice that in with
|
.dir |
If |
Value
A function
Adverbs
This function is called an adverb because it modifies the effect of a function (a verb). If you'd like to include a function created an adverb in a package, be sure to read faq-adverbs-export.
See Also
Other adverbs:
auto_browse()
,
insistently()
,
negate()
,
partial()
,
possibly()
,
quietly()
,
safely()
,
slowly()
Examples
not_null <- compose(`!`, is.null)
not_null(4)
not_null(NULL)
add1 <- function(x) x + 1
compose(add1, add1)(8)
fn <- compose(\(x) paste(x, "foo"), \(x) paste(x, "bar"))
fn("input")
# Lists of functions can be spliced with !!!
fns <- list(
function(x) paste(x, "foo"),
\(x) paste(x, "bar")
)
fn <- compose(!!!fns)
fn("input")
Produce all combinations of list elements
Description
These functions were deprecated in purrr 1.0.0 because they
are slow and buggy, and we no longer think they are the right
approach to solving this problem. Please use tidyr::expand_grid()
instead.
Here is an example of equivalent usages for cross()
and
expand_grid()
:
data <- list( id = c("John", "Jane"), sep = c("! ", "... "), greeting = c("Hello.", "Bonjour.") ) # With deprecated `cross()` data |> cross() |> map_chr(\(...) paste0(..., collapse = "")) #> [1] "John! Hello." "Jane! Hello." "John... Hello." "Jane... Hello." #> [5] "John! Bonjour." "Jane! Bonjour." "John... Bonjour." "Jane... Bonjour." # With `expand_grid()` tidyr::expand_grid(!!!data) |> pmap_chr(paste) #> [1] "John! Hello." "John! Bonjour." "John... Hello." "John... Bonjour." #> [5] "Jane! Hello." "Jane! Bonjour." "Jane... Hello." "Jane... Bonjour."
Usage
cross(.l, .filter = NULL)
cross2(.x, .y, .filter = NULL)
cross3(.x, .y, .z, .filter = NULL)
cross_df(.l, .filter = NULL)
Arguments
.l |
A list of lists or atomic vectors. Alternatively, a data
frame. |
.filter |
A predicate function that takes the same number of arguments as the number of variables to be combined. |
.x , .y , .z |
Lists or atomic vectors. |
Details
cross2()
returns the product set of the elements of
.x
and .y
. cross3()
takes an additional
.z
argument. cross()
takes a list .l
and
returns the cartesian product of all its elements in a list, with
one combination by element. cross_df()
is like
cross()
but returns a data frame, with one combination by
row.
cross()
, cross2()
and cross3()
return the
cartesian product is returned in wide format. This makes it more
amenable to mapping operations. cross_df()
returns the output
in long format just as expand.grid()
does. This is adapted
to rowwise operations.
When the number of combinations is large and the individual
elements are heavy memory-wise, it is often useful to filter
unwanted combinations on the fly with .filter
. It must be
a predicate function that takes the same number of arguments as the
number of crossed objects (2 for cross2()
, 3 for
cross3()
, length(.l)
for cross()
) and
returns TRUE
or FALSE
. The combinations where the
predicate function returns TRUE
will be removed from the
result.
Value
cross2()
, cross3()
and cross()
always return a list. cross_df()
always returns a data
frame. cross()
returns a list where each element is one
combination so that the list can be directly mapped
over. cross_df()
returns a data frame where each row is one
combination.
See Also
Examples
# We build all combinations of names, greetings and separators from our
# list of data and pass each one to paste()
data <- list(
id = c("John", "Jane"),
greeting = c("Hello.", "Bonjour."),
sep = c("! ", "... ")
)
data |>
cross() |>
map(lift(paste))
# cross() returns the combinations in long format: many elements,
# each representing one combination. With cross_df() we'll get a
# data frame in long format: crossing three objects produces a data
# frame of three columns with each row being a particular
# combination. This is the same format that expand.grid() returns.
args <- data |> cross_df()
# In case you need a list in long format (and not a data frame)
# just run as.list() after cross_df()
args |> as.list()
# This format is often less practical for functional programming
# because applying a function to the combinations requires a loop
out <- vector("character", length = nrow(args))
for (i in seq_along(out))
out[[i]] <- invoke("paste", map(args, i))
out
# It's easier to transpose and then use invoke_map()
args |> transpose() |> map_chr(\(x) exec(paste, !!!x))
# Unwanted combinations can be filtered out with a predicate function
filter <- function(x, y) x >= y
cross2(1:5, 1:5, .filter = filter) |> str()
# To give names to the components of the combinations, we map
# setNames() on the product:
x <- seq_len(3)
cross2(x, x, .filter = `==`) |>
map(setNames, c("x", "y"))
# Alternatively we can encapsulate the arguments in a named list
# before crossing to get named components:
list(x = x, y = x) |>
cross(.filter = `==`)
Find the value or position of the first match
Description
Find the value or position of the first match
Usage
detect(
.x,
.f,
...,
.dir = c("forward", "backward"),
.right = NULL,
.default = NULL
)
detect_index(.x, .f, ..., .dir = c("forward", "backward"), .right = NULL)
Arguments
.x |
A list or vector. |
.f |
A function, specified in one of the following ways:
|
... |
Additional arguments passed on to |
.dir |
If |
.right |
|
.default |
The value returned when nothing is detected. |
Value
detect
the value of the first item that matches the
predicate; detect_index
the position of the matching item.
If not found, detect
returns NULL
and detect_index
returns 0.
See Also
keep()
for keeping all matching values.
Examples
is_even <- function(x) x %% 2 == 0
3:10 |> detect(is_even)
3:10 |> detect_index(is_even)
3:10 |> detect(is_even, .dir = "backward")
3:10 |> detect_index(is_even, .dir = "backward")
# Since `.f` is passed to as_mapper(), you can supply a
# lambda-formula or a pluck object:
x <- list(
list(1, foo = FALSE),
list(2, foo = TRUE),
list(3, foo = TRUE)
)
detect(x, "foo")
detect_index(x, "foo")
# If you need to find all values, use keep():
keep(x, "foo")
# If you need to find all positions, use map_lgl():
which(map_lgl(x, "foo"))
Do every, some, or none of the elements of a list satisfy a predicate?
Description
-
some()
returnsTRUE
when.p
isTRUE
for at least one element. -
every()
returnsTRUE
when.p
isTRUE
for all elements. -
none()
returnsTRUE
when.p
isFALSE
for all elements.
Usage
every(.x, .p, ...)
some(.x, .p, ...)
none(.x, .p, ...)
Arguments
.x |
A list or vector. |
.p |
A predicate function (i.e. a function that returns either
|
... |
Additional arguments passed on to |
Value
A logical vector of length 1.
Examples
x <- list(0:10, 5.5)
x |> every(is.numeric)
x |> every(is.integer)
x |> some(is.integer)
x |> none(is.character)
# Missing values are propagated:
some(list(NA, FALSE), identity)
# If you need to use these functions in a context where missing values are
# unsafe (e.g. in `if ()` conditions), make sure to use safe predicates:
if (some(list(NA, FALSE), rlang::is_true)) "foo" else "bar"
Best practices for exporting adverb-wrapped functions
Description
Exporting functions created with purrr adverbs in your package requires some precautions because the functions will contain internal purrr code. This means that creating them once and for all when the package is built may cause problems when purrr is updated, because a function that the adverb uses might no longer exist.
Instead, either create the modified function once per session on package load or wrap the call within another function every time you use it:
Using the
.onLoad()
hook:#' My function #' @export insist_my_function <- function(...) "dummy" my_function <- function(...) { # Implementation } .onLoad <- function(lib, pkg) { insist_my_function <<- purrr::insistently(my_function) }
Using a wrapper function:
my_function <- function(...) { # Implementation } #' My function #' @export insist_my_function <- function(...) { purrr::insistently(my_function)(...) }
Flatten a list of lists into a simple vector
Description
These functions were superseded in purrr 1.0.0 because their behaviour was inconsistent. Superseded functions will not go away, but will only receive critical bug fixes.
-
flatten()
has been superseded bylist_flatten()
. -
flatten_lgl()
,flatten_int()
,flatten_dbl()
, andflatten_chr()
have been superseded bylist_c()
. -
flatten_dfr()
andflatten_dfc()
have been superseded bylist_rbind()
andlist_cbind()
respectively.
Usage
flatten(.x)
flatten_lgl(.x)
flatten_int(.x)
flatten_dbl(.x)
flatten_chr(.x)
flatten_dfr(.x, .id = NULL)
flatten_dfc(.x)
Arguments
.x |
A list to flatten. The contents of the list can be anything for
|
Value
flatten()
returns a list, flatten_lgl()
a logical
vector, flatten_int()
an integer vector, flatten_dbl()
a
double vector, and flatten_chr()
a character vector.
flatten_dfr()
and flatten_dfc()
return data frames created by
row-binding and column-binding respectively. They require dplyr to
be installed.
Examples
x <- map(1:3, \(i) sample(4))
x
# was
x |> flatten_int() |> str()
# now
x |> list_c() |> str()
x <- list(list(1, 2), list(3, 4))
# was
x |> flatten() |> str()
# now
x |> list_flatten() |> str()
Infix attribute accessor
Description
This function was deprecated in purrr 0.3.0. Instead, lease use the %@%
operator exported in rlang. It has an interface more consistent with @
:
uses NSE, supports S4 fields, and has an assignment variant.
Usage
x %@% name
Arguments
x |
Object |
name |
Attribute name |
Does a list contain an object?
Description
Does a list contain an object?
Usage
has_element(.x, .y)
Arguments
.x |
A list or atomic vector. |
.y |
Object to test for |
Examples
x <- list(1:10, 5, 9.9)
x |> has_element(1:10)
x |> has_element(3)
Find head/tail that all satisfies a predicate.
Description
Find head/tail that all satisfies a predicate.
Usage
head_while(.x, .p, ...)
tail_while(.x, .p, ...)
Arguments
.x |
A list or atomic vector. |
.p |
A single predicate function, a formula describing such a
predicate function, or a logical vector of the same length as |
... |
Additional arguments passed on to the mapped function. We now generally recommend against using # Instead of x |> map(f, 1, 2, collapse = ",") # do: x |> map(\(x) f(x, 1, 2, collapse = ",")) This makes it easier to understand which arguments belong to which function and will tend to yield better error messages. |
Value
A vector the same type as .x
.
Examples
pos <- function(x) x >= 0
head_while(5:-5, pos)
tail_while(5:-5, negate(pos))
big <- function(x) x > 100
head_while(0:10, big)
tail_while(0:10, big)
Apply a function to each element of a vector, and its index
Description
imap(x, ...)
, an indexed map, is short hand for
map2(x, names(x), ...)
if x
has names, or map2(x, seq_along(x), ...)
if it does not. This is useful if you need to compute on both the value
and the position of an element.
Usage
imap(.x, .f, ...)
imap_lgl(.x, .f, ...)
imap_chr(.x, .f, ...)
imap_int(.x, .f, ...)
imap_dbl(.x, .f, ...)
imap_vec(.x, .f, ...)
iwalk(.x, .f, ...)
Arguments
.x |
A list or atomic vector. |
.f |
A function, specified in one of the following ways:
|
... |
Additional arguments passed on to the mapped function. We now generally recommend against using # Instead of x |> map(f, 1, 2, collapse = ",") # do: x |> map(\(x) f(x, 1, 2, collapse = ",")) This makes it easier to understand which arguments belong to which function and will tend to yield better error messages. |
Value
A vector the same length as .x
.
See Also
Other map variants:
lmap()
,
map()
,
map2()
,
map_depth()
,
map_if()
,
modify()
,
pmap()
Examples
imap_chr(sample(10), paste)
imap_chr(sample(10), \(x, idx) paste0(idx, ": ", x))
iwalk(mtcars, \(x, idx) cat(idx, ": ", median(x), "\n", sep = ""))
Transform a function to wait then retry after an error
Description
insistently()
takes a function and modifies it to retry after given
amount of time whenever it errors.
Usage
insistently(f, rate = rate_backoff(), quiet = TRUE)
Arguments
f |
A function to modify, specified in one of the following ways:
|
rate |
A rate object. Defaults to jittered exponential backoff. |
quiet |
Hide errors ( |
Value
A function that takes the same arguments as .f
, but returns
a different value, as described above.
Adverbs
This function is called an adverb because it modifies the effect of a function (a verb). If you'd like to include a function created an adverb in a package, be sure to read faq-adverbs-export.
See Also
httr::RETRY()
is a special case of insistently()
for
HTTP verbs.
Other adverbs:
auto_browse()
,
compose()
,
negate()
,
partial()
,
possibly()
,
quietly()
,
safely()
,
slowly()
Examples
# For the purpose of this example, we first create a custom rate
# object with a low waiting time between attempts:
rate <- rate_delay(0.1)
# insistently() makes a function repeatedly try to work
risky_runif <- function(lo = 0, hi = 1) {
y <- runif(1, lo, hi)
if(y < 0.9) {
stop(y, " is too small")
}
y
}
# Let's now create an exponential backoff rate with a low waiting
# time between attempts:
rate <- rate_backoff(pause_base = 0.1, pause_min = 0.005, max_times = 4)
# Modify your function to run insistently.
insistent_risky_runif <- insistently(risky_runif, rate, quiet = FALSE)
set.seed(6) # Succeeding seed
insistent_risky_runif()
set.seed(3) # Failing seed
try(insistent_risky_runif())
# You can also use other types of rate settings, like a delay rate
# that waits for a fixed amount of time. Be aware that a delay rate
# has an infinite amount of attempts by default:
rate <- rate_delay(0.2, max_times = 3)
insistent_risky_runif <- insistently(risky_runif, rate = rate, quiet = FALSE)
try(insistent_risky_runif())
# insistently() and possibly() are a useful combination
rate <- rate_backoff(pause_base = 0.1, pause_min = 0.005)
possibly_insistent_risky_runif <- possibly(insistent_risky_runif, otherwise = -99)
set.seed(6)
possibly_insistent_risky_runif()
set.seed(3)
possibly_insistent_risky_runif()
Invoke functions.
Description
These functions were superded in purrr 0.3.0 and deprecated in purrr 1.0.0.
-
invoke()
is deprecated in favour of the simplerexec()
function reexported from rlang.exec()
evaluates a function call built from its inputs and supports dynamic dots:# Before: invoke(mean, list(na.rm = TRUE), x = 1:10) # After exec(mean, 1:10, !!!list(na.rm = TRUE))
-
invoke_map()
is deprecated because it's harder to understand than the corresponding code usingmap()
/map2()
andexec()
:# Before: invoke_map(fns, list(args)) invoke_map(fns, list(args1, args2)) # After: map(fns, exec, !!!args) map2(fns, list(args1, args2), \(fn, args) exec(fn, !!!args))
Usage
invoke(.f, .x = NULL, ..., .env = NULL)
invoke_map(.f, .x = list(NULL), ..., .env = NULL)
invoke_map_lgl(.f, .x = list(NULL), ..., .env = NULL)
invoke_map_int(.f, .x = list(NULL), ..., .env = NULL)
invoke_map_dbl(.f, .x = list(NULL), ..., .env = NULL)
invoke_map_chr(.f, .x = list(NULL), ..., .env = NULL)
invoke_map_raw(.f, .x = list(NULL), ..., .env = NULL)
invoke_map_dfr(.f, .x = list(NULL), ..., .env = NULL)
invoke_map_dfc(.f, .x = list(NULL), ..., .env = NULL)
Arguments
.f |
For |
.x |
For |
... |
Additional arguments passed to each function. |
.env |
Environment in which |
Examples
# was
invoke(runif, list(n = 10))
invoke(runif, n = 10)
# now
exec(runif, n = 10)
# was
args <- list("01a", "01b")
invoke(paste, args, sep = "-")
# now
exec(paste, !!!args, sep = "-")
# was
funs <- list(runif, rnorm)
funs |> invoke_map(n = 5)
funs |> invoke_map(list(list(n = 10), list(n = 5)))
# now
funs |> map(exec, n = 5)
funs |> map2(list(list(n = 10), list(n = 5)), function(f, args) exec(f, !!!args))
# or use pmap + a tibble
df <- tibble::tibble(
fun = list(runif, rnorm),
args = list(list(n = 10), list(n = 5))
)
df |> pmap(function(fun, args) exec(fun, !!!args))
# was
list(m1 = mean, m2 = median) |> invoke_map(x = rcauchy(100))
# now
list(m1 = mean, m2 = median) |> map(function(f) f(rcauchy(100)))
Keep/discard elements based on their values
Description
keep()
selects all elements where .p
evaluates to TRUE
;
discard()
selects all elements where .p
evaluates to FALSE
.
compact()
discards elements where .p
evaluates to an empty vector.
Usage
keep(.x, .p, ...)
discard(.x, .p, ...)
compact(.x, .p = identity)
Arguments
.x |
A list or vector. |
.p |
A predicate function (i.e. a function that returns either
|
... |
Additional arguments passed on to |
Details
In other languages, keep()
and discard()
are often called select()
/
filter()
and reject()
/ drop()
, but those names are already taken
in R. keep()
is similar to Filter()
, but the argument order is more
convenient, and the evaluation of the predicate function .p
is stricter.
See Also
keep_at()
/discard_at()
to keep/discard elements by name.
Examples
rep(10, 10) |>
map(sample, 5) |>
keep(function(x) mean(x) > 6)
# Or use a formula
rep(10, 10) |>
map(sample, 5) |>
keep(\(x) mean(x) > 6)
# Using a string instead of a function will select all list elements
# where that subelement is TRUE
x <- rerun(5, a = rbernoulli(1), b = sample(10))
x
x |> keep("a")
x |> discard("a")
# compact() discards elements that are NULL or that have length zero
list(a = "a", b = NULL, c = integer(0), d = NA, e = list()) |>
compact()
Keep/discard elements based on their name/position
Description
Keep/discard elements based on their name/position
Usage
keep_at(x, at)
discard_at(x, at)
Arguments
See Also
keep()
/discard()
to keep/discard elements by value.
Examples
x <- c(a = 1, b = 2, cat = 10, dog = 15, elephant = 5, e = 10)
x |> keep_at(letters)
x |> discard_at(letters)
# Can also use a function
x |> keep_at(~ nchar(.x) == 3)
x |> discard_at(~ nchar(.x) == 3)
Lift the domain of a function
Description
lift_xy()
is a composition helper. It helps you compose
functions by lifting their domain from a kind of input to another
kind. The domain can be changed from and to a list (l), a vector
(v) and dots (d). For example, lift_ld(fun)
transforms a
function taking a list to a function taking dots.
The most important of those helpers is probably lift_dl()
because it allows you to transform a regular function to one that
takes a list. This is often essential for composition with purrr
functional tools. Since this is such a common function,
lift()
is provided as an alias for that operation.
These functions were superseded in purrr 1.0.0 because we no longer believe "lifting" to be a mainstream operation, and we are striving to reduce purrr to its most useful core. Superseded functions will not go away, but will only receive critical bug fixes.
Usage
lift(..f, ..., .unnamed = FALSE)
lift_dl(..f, ..., .unnamed = FALSE)
lift_dv(..f, ..., .unnamed = FALSE)
lift_vl(..f, ..., .type)
lift_vd(..f, ..., .type)
lift_ld(..f, ...)
lift_lv(..f, ...)
Arguments
..f |
A function to lift. |
... |
Default arguments for |
.unnamed |
If |
.type |
Can be a vector mold specifying both the type and the
length of the vectors to be concatenated, such as |
Value
A function.
from ... to list(...)
or c(...)
Here dots should be taken here in a figurative way. The lifted
functions does not need to take dots per se. The function is
simply wrapped a function in do.call()
, so instead
of taking multiple arguments, it takes a single named list or
vector which will be interpreted as its arguments. This is
particularly useful when you want to pass a row of a data frame
or a list to a function and don't want to manually pull it apart
in your function.
from c(...)
to list(...)
or ...
These factories allow a function taking a vector to take a list
or dots instead. The lifted function internally transforms its
inputs back to an atomic vector. purrr does not obey the usual R
casting rules (e.g., c(1, "2")
produces a character
vector) and will produce an error if the types are not
compatible. Additionally, you can enforce a particular vector
type by supplying .type
.
from list(...) to c(...) or ...
lift_ld()
turns a function that takes a list into a
function that takes dots. lift_vd()
does the same with a
function that takes an atomic vector. These factory functions are
the inverse operations of lift_dl()
and lift_dv()
.
lift_vd()
internally coerces the inputs of ..f
to
an atomic vector. The details of this coercion can be controlled
with .type
.
See Also
Examples
### Lifting from ... to list(...) or c(...)
x <- list(x = c(1:100, NA, 1000), na.rm = TRUE, trim = 0.9)
lift_dl(mean)(x)
# You can also use the lift() alias for this common operation:
lift(mean)(x)
# now:
exec(mean, !!!x)
# Default arguments can also be specified directly in lift_dl()
list(c(1:100, NA, 1000)) |> lift_dl(mean, na.rm = TRUE)()
# now:
mean(c(1:100, NA, 1000), na.rm = TRUE)
# lift_dl() and lift_ld() are inverse of each other.
# Here we transform sum() so that it takes a list
fun <- sum |> lift_dl()
fun(list(3, NA, 4, na.rm = TRUE))
# now:
fun <- function(x) exec("sum", !!!x)
exec(sum, 3, NA, 4, na.rm = TRUE)
### Lifting from c(...) to list(...) or ...
# In other situations we need the vector-valued function to take a
# variable number of arguments as with pmap(). This is a job for
# lift_vd():
pmap_dbl(mtcars, lift_vd(mean))
# now
pmap_dbl(mtcars, \(...) mean(c(...)))
### Lifting from list(...) to c(...) or ...
# This kind of lifting is sometimes needed for function
# composition. An example would be to use pmap() with a function
# that takes a list. In the following, we use some() on each row of
# a data frame to check they each contain at least one element
# satisfying a condition:
mtcars |> pmap_lgl(lift_ld(some, partial(`<`, 200)))
# now
mtcars |> pmap_lgl(\(...) any(c(...) > 200))
Modify a list
Description
-
list_assign()
modifies the elements of a list by name or position. -
list_modify()
modifies the elements of a list recursively. -
list_merge()
merges the elements of a list recursively.
list_modify()
is inspired by utils::modifyList()
.
Usage
list_assign(.x, ..., .is_node = NULL)
list_modify(.x, ..., .is_node = NULL)
list_merge(.x, ..., .is_node = NULL)
Arguments
.x |
List to modify. |
... |
New values of a list. Use These values should be either all named or all unnamed. When
inputs are all named, they are matched to Dynamic dots are supported. In particular, if your
replacement values are stored in a list, you can splice that in with
|
.is_node |
A predicate function that determines whether an element is
a node (by returning |
Examples
x <- list(x = 1:10, y = 4, z = list(a = 1, b = 2))
str(x)
# Update values
str(list_assign(x, a = 1))
# Replace values
str(list_assign(x, z = 5))
str(list_assign(x, z = NULL))
str(list_assign(x, z = list(a = 1:5)))
# Replace recursively with list_modify(), leaving the other elements of z alone
str(list_modify(x, z = list(a = 1:5)))
# Remove values
str(list_assign(x, z = zap()))
# Combine values with list_merge()
str(list_merge(x, x = 11, z = list(a = 2:5, c = 3)))
# All these functions support dynamic dots features. Use !!! to splice
# a list of arguments:
l <- list(new = 1, y = zap(), z = 5)
str(list_assign(x, !!!l))
Combine list elements into a single data structure
Description
-
list_c()
combines elements into a vector by concatenating them together withvctrs::vec_c()
. -
list_rbind()
combines elements into a data frame by row-binding them together withvctrs::vec_rbind()
. -
list_cbind()
combines elements into a data frame by column-binding them together withvctrs::vec_cbind()
.
Usage
list_c(x, ..., ptype = NULL)
list_cbind(
x,
...,
name_repair = c("unique", "universal", "check_unique"),
size = NULL
)
list_rbind(x, ..., names_to = rlang::zap(), ptype = NULL)
Arguments
x |
A list. For |
... |
These dots are for future extensions and must be empty. |
ptype |
An optional prototype to ensure that the output type is always the same. |
name_repair |
One of |
size |
An optional integer size to ensure that every input has the same size (i.e. number of rows). |
names_to |
By default, |
Examples
x1 <- list(a = 1, b = 2, c = 3)
list_c(x1)
x2 <- list(
a = data.frame(x = 1:2),
b = data.frame(y = "a")
)
list_rbind(x2)
list_rbind(x2, names_to = "id")
list_rbind(unname(x2), names_to = "id")
list_cbind(x2)
Flatten a list
Description
Flattening a list removes a single layer of internal hierarchy, i.e. it inlines elements that are lists leaving non-lists alone.
Usage
list_flatten(
x,
...,
name_spec = "{outer}_{inner}",
name_repair = c("minimal", "unique", "check_unique", "universal")
)
Arguments
x |
A list. |
... |
These dots are for future extensions and must be empty. |
name_spec |
If both inner and outer names are present, control
how they are combined. Should be a glue specification that uses
variables |
name_repair |
One of |
Value
A list of the same type as x
. The list might be shorter
if x
contains empty lists, the same length if it contains lists
of length 1 or no sub-lists, or longer if it contains lists of
length > 1.
Examples
x <- list(1, list(2, 3), list(4, list(5)))
x |> list_flatten() |> str()
x |> list_flatten() |> list_flatten() |> str()
# Flat lists are left as is
list(1, 2, 3, 4, 5) |> list_flatten() |> str()
# Empty lists will disappear
list(1, list(), 2, list(3)) |> list_flatten() |> str()
# Another way to see this is that it reduces the depth of the list
x <- list(
list(),
list(list())
)
x |> pluck_depth()
x |> list_flatten() |> pluck_depth()
# Use name_spec to control how inner and outer names are combined
x <- list(x = list(a = 1, b = 2), y = list(c = 1, d = 2))
x |> list_flatten() |> names()
x |> list_flatten(name_spec = "{outer}") |> names()
x |> list_flatten(name_spec = "{inner}") |> names()
Simplify a list to an atomic or S3 vector
Description
Simplification maintains a one-to-one correspondence between the input
and output, implying that each element of x
must contain a one element
vector or a one-row data frame. If you don't want to maintain this
correspondence, then you probably want either list_c()
/list_rbind()
or
list_flatten()
.
Usage
list_simplify(x, ..., strict = TRUE, ptype = NULL)
Arguments
x |
A list. |
... |
These dots are for future extensions and must be empty. |
strict |
What should happen if simplification fails? If |
ptype |
An optional prototype to ensure that the output type is always the same. |
Value
A vector the same length as x
.
Examples
list_simplify(list(1, 2, 3))
# Only works when vectors are length one and have compatible types:
try(list_simplify(list(1, 2, 1:3)))
try(list_simplify(list(1, 2, "x")))
# Unless you strict = FALSE, in which case you get the input back:
list_simplify(list(1, 2, 1:3), strict = FALSE)
list_simplify(list(1, 2, "x"), strict = FALSE)
Transpose a list
Description
list_transpose()
turns a list-of-lists "inside-out". For instance it turns a pair of
lists into a list of pairs, or a list of pairs into a pair of lists. For
example, if you had a list of length n
where each component had values a
and b
, list_transpose()
would make a list with elements a
and
b
that contained lists of length n
.
It's called transpose because x[["a"]][["b"]]
is equivalent to
list_transpose(x)[["b"]][["a"]]
, i.e. transposing a list flips the order of
indices in a similar way to transposing a matrix.
Usage
list_transpose(
x,
...,
template = NULL,
simplify = NA,
ptype = NULL,
default = NULL
)
Arguments
x |
A list of vectors to transpose. |
... |
These dots are for future extensions and must be empty. |
template |
A "template" that describes the output list. Can either be
a character vector (where elements are extracted by name), or an integer
vector (where elements are extracted by position). Defaults to the union
of the names of the elements of |
simplify |
Should the result be simplified?
Alternatively, a named list specifying the simplification by output element. |
ptype |
An optional vector prototype used to control the simplification. Alternatively, a named list specifying the prototype by output element. |
default |
A default value to use if a value is absent or |
Examples
# list_transpose() is useful in conjunction with safely()
x <- list("a", 1, 2)
y <- x |> map(safely(log))
y |> str()
# Put all the errors and results together
y |> list_transpose() |> str()
# Supply a default result to further simplify
y |> list_transpose(default = list(result = NA)) |> str()
# list_transpose() will try to simplify by default:
x <- list(list(a = 1, b = 2), list(a = 3, b = 4), list(a = 5, b = 6))
x |> list_transpose()
# this makes list_tranpose() not completely symmetric
x |> list_transpose() |> list_transpose()
# use simplify = FALSE to always return lists:
x |> list_transpose(simplify = FALSE) |> str()
x |>
list_transpose(simplify = FALSE) |>
list_transpose(simplify = FALSE) |> str()
# Provide an explicit template if you know which elements you want to extract
ll <- list(
list(x = 1, y = "one"),
list(z = "deux", x = 2)
)
ll |> list_transpose()
ll |> list_transpose(template = c("x", "y", "z"))
ll |> list_transpose(template = 1)
# And specify a default if you want to simplify
ll |> list_transpose(template = c("x", "y", "z"), default = NA)
Apply a function to list-elements of a list
Description
lmap()
, lmap_at()
and lmap_if()
are similar to map()
, map_at()
and
map_if()
, except instead of mapping over .x[[i]]
, they instead map over
.x[i]
.
This has several advantages:
It makes it possible to work with functions that exclusively take a list.
It allows
.f
to access the attributes of the encapsulating list, likenames()
.It allows
.f
to return a larger or small list than it receives changing the size of the output.
Usage
lmap(.x, .f, ...)
lmap_if(.x, .p, .f, ..., .else = NULL)
lmap_at(.x, .at, .f, ...)
Arguments
Value
A list or data frame, matching .x
. There are no guarantees about
the length.
See Also
Other map variants:
imap()
,
map()
,
map2()
,
map_depth()
,
map_if()
,
modify()
,
pmap()
Examples
set.seed(1014)
# Let's write a function that returns a larger list or an empty list
# depending on some condition. It also uses the input name to name the
# output
maybe_rep <- function(x) {
n <- rpois(1, 2)
set_names(rep_len(x, n), paste0(names(x), seq_len(n)))
}
# The output size varies each time we map f()
x <- list(a = 1:4, b = letters[5:7], c = 8:9, d = letters[10])
x |> lmap(maybe_rep) |> str()
# We can apply f() on a selected subset of x
x |> lmap_at(c("a", "d"), maybe_rep) |> str()
# Or only where a condition is satisfied
x |> lmap_if(is.character, maybe_rep) |> str()
Apply a function to each element of a vector
Description
The map functions transform their input by applying a function to each element of a list or atomic vector and returning an object of the same length as the input.
-
map()
always returns a list. See themodify()
family for versions that return an object of the same type as the input. -
map_lgl()
,map_int()
,map_dbl()
andmap_chr()
return an atomic vector of the indicated type (or die trying). For these functions,.f
must return a length-1 vector of the appropriate type. -
map_vec()
simplifies to the common type of the output. It works with most types of simple vectors like Date, POSIXct, factors, etc. -
walk()
calls.f
for its side-effect and returns the input.x
.
Usage
map(.x, .f, ..., .progress = FALSE)
map_lgl(.x, .f, ..., .progress = FALSE)
map_int(.x, .f, ..., .progress = FALSE)
map_dbl(.x, .f, ..., .progress = FALSE)
map_chr(.x, .f, ..., .progress = FALSE)
map_vec(.x, .f, ..., .ptype = NULL, .progress = FALSE)
walk(.x, .f, ..., .progress = FALSE)
Arguments
.x |
A list or atomic vector. |
.f |
A function, specified in one of the following ways:
|
... |
Additional arguments passed on to the mapped function. We now generally recommend against using # Instead of x |> map(f, 1, 2, collapse = ",") # do: x |> map(\(x) f(x, 1, 2, collapse = ",")) This makes it easier to understand which arguments belong to which function and will tend to yield better error messages. |
.progress |
Whether to show a progress bar. Use |
.ptype |
If |
Value
The output length is determined by the length of the input. The output names are determined by the input names. The output type is determined by the suffix:
No suffix: a list;
.f()
can return anything.-
_lgl()
,_int()
,_dbl()
,_chr()
return a logical, integer, double, or character vector respectively;.f()
must return a compatible atomic vector of length 1. -
_vec()
return an atomic or S3 vector, the same type that.f
returns..f
can return pretty much any type of vector, as long as its length 1. -
walk()
returns the input.x
(invisibly). This makes it easy to use in a pipe. The return value of.f()
is ignored.
Any errors thrown by .f
will be wrapped in an error with class
purrr_error_indexed.
See Also
map_if()
for applying a function to only those elements
of .x
that meet a specified condition.
Other map variants:
imap()
,
lmap()
,
map2()
,
map_depth()
,
map_if()
,
modify()
,
pmap()
Examples
# Compute normal distributions from an atomic vector
1:10 |>
map(rnorm, n = 10)
# You can also use an anonymous function
1:10 |>
map(\(x) rnorm(10, x))
# Simplify output to a vector instead of a list by computing the mean of the distributions
1:10 |>
map(rnorm, n = 10) |> # output a list
map_dbl(mean) # output an atomic vector
# Using set_names() with character vectors is handy to keep track
# of the original inputs:
set_names(c("foo", "bar")) |> map_chr(paste0, ":suffix")
# Working with lists
favorite_desserts <- list(Sophia = "banana bread", Eliott = "pancakes", Karina = "chocolate cake")
favorite_desserts |> map_chr(\(food) paste(food, "rocks!"))
# Extract by name or position
# .default specifies value for elements that are missing or NULL
l1 <- list(list(a = 1L), list(a = NULL, b = 2L), list(b = 3L))
l1 |> map("a", .default = "???")
l1 |> map_int("b", .default = NA)
l1 |> map_int(2, .default = NA)
# Supply multiple values to index deeply into a list
l2 <- list(
list(num = 1:3, letters[1:3]),
list(num = 101:103, letters[4:6]),
list()
)
l2 |> map(c(2, 2))
# Use a list to build an extractor that mixes numeric indices and names,
# and .default to provide a default value if the element does not exist
l2 |> map(list("num", 3))
l2 |> map_int(list("num", 3), .default = NA)
# Working with data frames
# Use map_lgl(), map_dbl(), etc to return a vector instead of a list:
mtcars |> map_dbl(sum)
# A more realistic example: split a data frame into pieces, fit a
# model to each piece, summarise and extract R^2
mtcars |>
split(mtcars$cyl) |>
map(\(df) lm(mpg ~ wt, data = df)) |>
map(summary) |>
map_dbl("r.squared")
Map/modify elements at given depth
Description
map_depth()
calls map(.y, .f)
on all .y
at the specified .depth
in
.x
. modify_depth()
calls modify(.y, .f)
on .y
at the specified
.depth
in .x
.
Usage
map_depth(.x, .depth, .f, ..., .ragged = .depth < 0, .is_node = NULL)
modify_depth(.x, .depth, .f, ..., .ragged = .depth < 0, .is_node = NULL)
Arguments
.x |
A list or atomic vector. |
.depth |
Level of
|
.f |
A function, specified in one of the following ways:
|
... |
Additional arguments passed on to the mapped function. We now generally recommend against using # Instead of x |> map(f, 1, 2, collapse = ",") # do: x |> map(\(x) f(x, 1, 2, collapse = ",")) This makes it easier to understand which arguments belong to which function and will tend to yield better error messages. |
.ragged |
If |
.is_node |
A predicate function that determines whether an element is
a node (by returning |
See Also
modify_tree()
for a recursive version of modify_depth()
that
allows you to apply a function to every leaf or every node.
Other map variants:
imap()
,
lmap()
,
map()
,
map2()
,
map_if()
,
modify()
,
pmap()
Other modify variants:
modify()
,
modify_tree()
Examples
# map_depth() -------------------------------------------------
# Use `map_depth()` to recursively traverse nested vectors and map
# a function at a certain depth:
x <- list(a = list(foo = 1:2, bar = 3:4), b = list(baz = 5:6))
x |> str()
x |> map_depth(2, \(y) paste(y, collapse = "/")) |> str()
# Equivalent to:
x |> map(\(y) map(y, \(z) paste(z, collapse = "/"))) |> str()
# When ragged is TRUE, `.f()` will also be passed leaves at depth < `.depth`
x <- list(1, list(1, list(1, list(1, 1))))
x |> str()
x |> map_depth(4, \(x) length(unlist(x)), .ragged = TRUE) |> str()
x |> map_depth(3, \(x) length(unlist(x)), .ragged = TRUE) |> str()
x |> map_depth(2, \(x) length(unlist(x)), .ragged = TRUE) |> str()
x |> map_depth(1, \(x) length(unlist(x)), .ragged = TRUE) |> str()
x |> map_depth(0, \(x) length(unlist(x)), .ragged = TRUE) |> str()
# modify_depth() -------------------------------------------------
l1 <- list(
obj1 = list(
prop1 = list(param1 = 1:2, param2 = 3:4),
prop2 = list(param1 = 5:6, param2 = 7:8)
),
obj2 = list(
prop1 = list(param1 = 9:10, param2 = 11:12),
prop2 = list(param1 = 12:14, param2 = 15:17)
)
)
# In the above list, "obj" is level 1, "prop" is level 2 and "param"
# is level 3. To apply sum() on all params, we map it at depth 3:
l1 |> modify_depth(3, sum) |> str()
# modify() lets us pluck the elements prop1/param2 in obj1 and obj2:
l1 |> modify(c("prop1", "param2")) |> str()
# But what if we want to pluck all param2 elements? Then we need to
# act at a lower level:
l1 |> modify_depth(2, "param2") |> str()
# modify_depth() can be with other purrr functions to make them operate at
# a lower level. Here we ask pmap() to map paste() simultaneously over all
# elements of the objects at the second level. paste() is effectively
# mapped at level 3.
l1 |> modify_depth(2, \(x) pmap(x, paste, sep = " / ")) |> str()
Functions that return data frames
Description
These map()
, map2()
, imap()
, and pmap()
variants return data
frames by row-binding or column-binding the outputs together.
The functions were superseded in purrr 1.0.0 because their names
suggest they work like _lgl()
, _int()
, etc which require length
1 outputs, but actually they return results of any size because the results
are combined without any size checks. Additionally, they use
dplyr::bind_rows()
and dplyr::bind_cols()
which require dplyr to be
installed and have confusing semantics with edge cases. Superseded
functions will not go away, but will only receive critical bug fixes.
Instead, we recommend using map()
, map2()
, etc with list_rbind()
and
list_cbind()
. These use vctrs::vec_rbind()
and vctrs::vec_cbind()
under the hood, and have names that more clearly reflect their semantics.
Usage
map_dfr(.x, .f, ..., .id = NULL)
map_dfc(.x, .f, ...)
imap_dfr(.x, .f, ..., .id = NULL)
imap_dfc(.x, .f, ...)
map2_dfr(.x, .y, .f, ..., .id = NULL)
map2_dfc(.x, .y, .f, ...)
pmap_dfr(.l, .f, ..., .id = NULL)
pmap_dfc(.l, .f, ...)
Arguments
.id |
Either a string or Only applies to |
Examples
# map ---------------------------------------------
# Was:
mtcars |>
split(mtcars$cyl) |>
map(\(df) lm(mpg ~ wt, data = df)) |>
map_dfr(\(mod) as.data.frame(t(as.matrix(coef(mod)))))
# Now:
mtcars |>
split(mtcars$cyl) |>
map(\(df) lm(mpg ~ wt, data = df)) |>
map(\(mod) as.data.frame(t(as.matrix(coef(mod))))) |>
list_rbind()
# for certain pathological inputs `map_dfr()` and `map_dfc()` actually
# both combine the list by column
df <- data.frame(
x = c(" 13", " 15 "),
y = c(" 34", " 67 ")
)
# Was:
map_dfr(df, trimws)
map_dfc(df, trimws)
# But list_rbind()/list_cbind() fail because they require data frame inputs
try(map(df, trimws) |> list_rbind())
# Instead, use modify() to apply a function to each column of a data frame
modify(df, trimws)
# map2 ---------------------------------------------
ex_fun <- function(arg1, arg2){
col <- arg1 + arg2
x <- as.data.frame(col)
}
arg1 <- 1:4
arg2 <- 10:13
# was
map2_dfr(arg1, arg2, ex_fun)
# now
map2(arg1, arg2, ex_fun) |> list_rbind()
# was
map2_dfc(arg1, arg2, ex_fun)
# now
map2(arg1, arg2, ex_fun) |> list_cbind()
Apply a function to each element of a vector conditionally
Description
The functions map_if()
and map_at()
take .x
as input, apply
the function .f
to some of the elements of .x
, and return a
list of the same length as the input.
-
map_if()
takes a predicate function.p
as input to determine which elements of.x
are transformed with.f
. -
map_at()
takes a vector of names or positions.at
to specify which elements of.x
are transformed with.f
.
Usage
map_if(.x, .p, .f, ..., .else = NULL)
map_at(.x, .at, .f, ..., .progress = FALSE)
Arguments
.x |
A list or atomic vector. |
.p |
A single predicate function, a formula describing such a
predicate function, or a logical vector of the same length as |
.f |
A function, specified in one of the following ways:
|
... |
Additional arguments passed on to the mapped function. We now generally recommend against using # Instead of x |> map(f, 1, 2, collapse = ",") # do: x |> map(\(x) f(x, 1, 2, collapse = ",")) This makes it easier to understand which arguments belong to which function and will tend to yield better error messages. |
.else |
A function applied to elements of |
.at |
A logical, integer, or character vector giving the elements to select. Alternatively, a function that takes a vector of names, and returns a logical, integer, or character vector of elements to select.
|
.progress |
Whether to show a progress bar. Use |
See Also
Other map variants:
imap()
,
lmap()
,
map()
,
map2()
,
map_depth()
,
modify()
,
pmap()
Examples
# Use a predicate function to decide whether to map a function:
iris |> map_if(is.factor, as.character) |> str()
# Specify an alternative with the `.else` argument:
iris |> map_if(is.factor, as.character, .else = as.integer) |> str()
# Use numeric vector of positions select elements to change:
iris |> map_at(c(4, 5), is.numeric) |> str()
# Use vector of names to specify which elements to change:
iris |> map_at("Species", toupper) |> str()
Functions that return raw vectors
Description
These functions were deprecated in purrr 1.0.0 because they are of limited
use and you can now use map_vec()
instead. They are variants of map()
,
map2()
, imap()
, pmap()
, and flatten()
that return raw vectors.
Usage
map_raw(.x, .f, ...)
map2_raw(.x, .y, .f, ...)
imap_raw(.x, .f, ...)
pmap_raw(.l, .f, ...)
flatten_raw(.x)
Map over two inputs
Description
These functions are variants of map()
that iterate over two arguments at
a time.
Usage
map2(.x, .y, .f, ..., .progress = FALSE)
map2_lgl(.x, .y, .f, ..., .progress = FALSE)
map2_int(.x, .y, .f, ..., .progress = FALSE)
map2_dbl(.x, .y, .f, ..., .progress = FALSE)
map2_chr(.x, .y, .f, ..., .progress = FALSE)
map2_vec(.x, .y, .f, ..., .ptype = NULL, .progress = FALSE)
walk2(.x, .y, .f, ..., .progress = FALSE)
Arguments
.x , .y |
A pair of vectors, usually the same length. If not, a vector of length 1 will be recycled to the length of the other. |
.f |
A function, specified in one of the following ways:
|
... |
Additional arguments passed on to the mapped function. We now generally recommend against using # Instead of x |> map(f, 1, 2, collapse = ",") # do: x |> map(\(x) f(x, 1, 2, collapse = ",")) This makes it easier to understand which arguments belong to which function and will tend to yield better error messages. |
.progress |
Whether to show a progress bar. Use |
.ptype |
If |
Value
The output length is determined by the length of the input. The output names are determined by the input names. The output type is determined by the suffix:
No suffix: a list;
.f()
can return anything.-
_lgl()
,_int()
,_dbl()
,_chr()
return a logical, integer, double, or character vector respectively;.f()
must return a compatible atomic vector of length 1. -
_vec()
return an atomic or S3 vector, the same type that.f
returns..f
can return pretty much any type of vector, as long as its length 1. -
walk()
returns the input.x
(invisibly). This makes it easy to use in a pipe. The return value of.f()
is ignored.
Any errors thrown by .f
will be wrapped in an error with class
purrr_error_indexed.
See Also
Other map variants:
imap()
,
lmap()
,
map()
,
map_depth()
,
map_if()
,
modify()
,
pmap()
Examples
x <- list(1, 1, 1)
y <- list(10, 20, 30)
map2(x, y, \(x, y) x + y)
# Or just
map2(x, y, `+`)
# Split into pieces, fit model to each piece, then predict
by_cyl <- mtcars |> split(mtcars$cyl)
mods <- by_cyl |> map(\(df) lm(mpg ~ wt, data = df))
map2(mods, by_cyl, predict)
Modify elements selectively
Description
Unlike map()
and its variants which always return a fixed object
type (list for map()
, integer vector for map_int()
, etc), the
modify()
family always returns the same type as the input object.
-
modify()
is a shortcut forx[[i]] <- f(x[[i]]); return(x)
. -
modify_if()
only modifies the elements ofx
that satisfy a predicate and leaves the others unchanged.modify_at()
only modifies elements given by names or positions. -
modify2()
modifies the elements of.x
but also passes the elements of.y
to.f
, just likemap2()
.imodify()
passes the names or the indices to.f
likeimap()
does. -
modify_in()
modifies a single element in apluck()
location.
Usage
modify(.x, .f, ...)
modify_if(.x, .p, .f, ..., .else = NULL)
modify_at(.x, .at, .f, ...)
modify2(.x, .y, .f, ...)
imodify(.x, .f, ...)
Arguments
Details
Since the transformation can alter the structure of the input; it's
your responsibility to ensure that the transformation produces a
valid output. For example, if you're modifying a data frame, .f
must preserve the length of the input.
Value
An object the same class as .x
Genericity
modify()
and variants are generic over classes that implement
length()
, [[
and [[<-
methods. If the default implementation
is not compatible for your class, you can override them with your
own methods.
If you implement your own modify()
method, make sure it satisfies
the following invariants:
modify(x, identity) === x modify(x, compose(f, g)) === modify(x, g) |> modify(f)
These invariants are known as the functor laws in computer science.
See Also
Other map variants:
imap()
,
lmap()
,
map()
,
map2()
,
map_depth()
,
map_if()
,
pmap()
Other modify variants:
map_depth()
,
modify_tree()
Examples
# Convert factors to characters
iris |>
modify_if(is.factor, as.character) |>
str()
# Specify which columns to map with a numeric vector of positions:
mtcars |> modify_at(c(1, 4, 5), as.character) |> str()
# Or with a vector of names:
mtcars |> modify_at(c("cyl", "am"), as.character) |> str()
list(x = sample(c(TRUE, FALSE), 100, replace = TRUE), y = 1:100) |>
list_transpose(simplify = FALSE) |>
modify_if("x", \(l) list(x = l$x, y = l$y * 100)) |>
list_transpose()
# Use modify2() to map over two vectors and preserve the type of
# the first one:
x <- c(foo = 1L, bar = 2L)
y <- c(TRUE, FALSE)
modify2(x, y, \(x, cond) if (cond) x else 0L)
# Use a predicate function to decide whether to map a function:
modify_if(iris, is.factor, as.character)
# Specify an alternative with the `.else` argument:
modify_if(iris, is.factor, as.character, .else = as.integer)
Modify a pluck location
Description
-
assign_in()
takes a data structure and a pluck location, assigns a value there, and returns the modified data structure. -
modify_in()
applies a function to a pluck location, assigns the result back to that location withassign_in()
, and returns the modified data structure.
Usage
modify_in(.x, .where, .f, ...)
assign_in(x, where, value)
Arguments
.x , x |
A vector or environment |
.where , where |
A pluck location, as a numeric vector of positions, a character vector of names, or a list combining both. The location must exist in the data structure. |
.f |
A function to apply at the pluck location given by |
... |
Arguments passed to |
value |
A value to replace in |
See Also
Examples
# Recall that pluck() returns a component of a data structure that
# might be arbitrarily deep
x <- list(list(bar = 1, foo = 2))
pluck(x, 1, "foo")
# Use assign_in() to modify the pluck location:
str(assign_in(x, list(1, "foo"), 100))
# Or zap to remove it
str(assign_in(x, list(1, "foo"), zap()))
# Like pluck(), this works even when the element (or its parents) don't exist
pluck(x, 1, "baz")
str(assign_in(x, list(2, "baz"), 100))
# modify_in() applies a function to that location and update the
# element in place:
modify_in(x, list(1, "foo"), \(x) x * 200)
# Additional arguments are passed to the function in the ordinary way:
modify_in(x, list(1, "foo"), `+`, 100)
Recursively modify a list
Description
modify_tree()
allows you to recursively modify a list, supplying functions
that either modify each leaf or each node (or both).
Usage
modify_tree(
x,
...,
leaf = identity,
is_node = NULL,
pre = identity,
post = identity
)
Arguments
x |
A list. |
... |
Reserved for future use. Must be empty |
leaf |
A function applied to each leaf. |
is_node |
A predicate function that determines whether an element is
a node (by returning |
pre , post |
Functions applied to each node. |
See Also
Other modify variants:
map_depth()
,
modify()
Examples
x <- list(list(a = 2:1, c = list(b1 = 2), b = list(c2 = 3, c1 = 4)))
x |> str()
# Transform each leaf
x |> modify_tree(leaf = \(x) x + 100) |> str()
# Recursively sort the nodes
sort_named <- function(x) {
nms <- names(x)
if (!is.null(nms)) {
x[order(nms)]
} else {
x
}
}
x |> modify_tree(post = sort_named) |> str()
Negate a predicate function so it selects what it previously rejected
Description
Negating a function changes TRUE
to FALSE
and FALSE
to TRUE
.
Usage
negate(.p)
Arguments
.p |
A predicate function (i.e. a function that returns either
|
Value
A new predicate function.
Adverbs
This function is called an adverb because it modifies the effect of a function (a verb). If you'd like to include a function created an adverb in a package, be sure to read faq-adverbs-export.
See Also
Other adverbs:
auto_browse()
,
compose()
,
insistently()
,
partial()
,
possibly()
,
quietly()
,
safely()
,
slowly()
Examples
x <- list(x = 1:10, y = rbernoulli(10), z = letters)
x |> keep(is.numeric) |> names()
x |> keep(negate(is.numeric)) |> names()
# Same as
x |> discard(is.numeric)
Partially apply a function, filling in some arguments
Description
Partial function application allows you to modify a function by pre-filling some of the arguments. It is particularly useful in conjunction with functionals and other function operators.
Usage
partial(
.f,
...,
.env = deprecated(),
.lazy = deprecated(),
.first = deprecated()
)
Arguments
.f |
a function. For the output source to read well, this should be a named function. |
... |
named arguments to Pass an empty These dots support quasiquotation. If you unquote a value, it is evaluated only once at function creation time. Otherwise, it is evaluated each time the function is called. |
.env |
|
.lazy |
|
.first |
|
Details
partial()
creates a function that takes ...
arguments. Unlike
compose()
and other function operators like negate()
, it
doesn't reuse the function signature of .f
. This is because
partial()
explicitly supports NSE functions that use
substitute()
on their arguments. The only way to support those is
to forward arguments through dots.
Other unsupported patterns:
It is not possible to call
partial()
repeatedly on the same argument to pre-fill it with a different expression.It is not possible to refer to other arguments in pre-filled argument.
Value
A function that takes the same arguments as .f
, but returns
a different value, as described above.
Adverbs
This function is called an adverb because it modifies the effect of a function (a verb). If you'd like to include a function created an adverb in a package, be sure to read faq-adverbs-export.
See Also
Other adverbs:
auto_browse()
,
compose()
,
insistently()
,
negate()
,
possibly()
,
quietly()
,
safely()
,
slowly()
Examples
# Partial is designed to replace the use of anonymous functions for
# filling in function arguments. Instead of:
compact1 <- function(x) discard(x, is.null)
# we can write:
compact2 <- partial(discard, .p = is.null)
# partial() works fine with functions that do non-standard
# evaluation
my_long_variable <- 1:10
plot2 <- partial(plot, my_long_variable)
plot2()
plot2(runif(10), type = "l")
# Note that you currently can't partialise arguments multiple times:
my_mean <- partial(mean, na.rm = TRUE)
my_mean <- partial(my_mean, na.rm = FALSE)
try(my_mean(1:10))
# The evaluation of arguments normally occurs "lazily". Concretely,
# this means that arguments are repeatedly evaluated across invocations:
f <- partial(runif, n = rpois(1, 5))
f
f()
f()
# You can unquote an argument to fix it to a particular value.
# Unquoted arguments are evaluated only once when the function is created:
f <- partial(runif, n = !!rpois(1, 5))
f
f()
f()
# By default, partialised arguments are passed before new ones:
my_list <- partial(list, 1, 2)
my_list("foo")
# Control the position of these arguments by passing an empty
# `... = ` argument:
my_list <- partial(list, 1, ... = , 2)
my_list("foo")
Safely get or set an element deep within a nested data structure
Description
pluck()
implements a generalised form of [[
that allow you to index
deeply and flexibly into data structures. It always succeeds, returning
.default
if the index you are trying to access does not exist or is NULL
.
pluck<-()
is the assignment equivalent, allowing you to modify an object
deep within a nested data structure.
pluck_exists()
tells you whether or not an object exists using the
same rules as pluck (i.e. a NULL
element is equivalent to an absent
element).
Usage
pluck(.x, ..., .default = NULL)
pluck(.x, ...) <- value
pluck_exists(.x, ...)
Arguments
.x , x |
A vector or environment |
... |
A list of accessors for indexing into the object. Can be an positive integer, a negative integer (to index from the right), a string (to index into names), or an accessor function (except for the assignment variants which only support names and positions). If the object being indexed is an S4 object, accessing it by name will return the corresponding slot. Dynamic dots are supported. In particular, if
your accessors are stored in a list, you can splice that in with
|
.default |
Value to use if target is |
value |
A value to replace in |
Details
You can pluck or chuck with standard accessors like integer positions and string names, and also accepts arbitrary accessor functions, i.e. functions that take an object and return some internal piece.
This is often more readable than a mix of operators and accessors because it reads linearly and is free of syntactic cruft. Compare:
accessor(x[[1]])$foo
topluck(x, 1, accessor, "foo")
.These accessors never partial-match. This is unlike
$
which will select thedisp
object if you writemtcars$di
.
See Also
attr_getter()
for creating attribute getters suitable
for use with pluck()
and chuck()
. modify_in()
for
applying a function to a pluck location.
Examples
# Let's create a list of data structures:
obj1 <- list("a", list(1, elt = "foo"))
obj2 <- list("b", list(2, elt = "bar"))
x <- list(obj1, obj2)
# pluck() provides a way of retrieving objects from such data
# structures using a combination of numeric positions, vector or
# list names, and accessor functions.
# Numeric positions index into the list by position, just like `[[`:
pluck(x, 1)
# same as x[[1]]
# Index from the back
pluck(x, -1)
# same as x[[2]]
pluck(x, 1, 2)
# same as x[[1]][[2]]
# Supply names to index into named vectors:
pluck(x, 1, 2, "elt")
# same as x[[1]][[2]][["elt"]]
# By default, pluck() consistently returns `NULL` when an element
# does not exist:
pluck(x, 10)
try(x[[10]])
# You can also supply a default value for non-existing elements:
pluck(x, 10, .default = NA)
# The map() functions use pluck() by default to retrieve multiple
# values from a list:
map_chr(x, 1)
map_int(x, c(2, 1))
# pluck() also supports accessor functions:
my_element <- function(x) x[[2]]$elt
pluck(x, 1, my_element)
pluck(x, 2, my_element)
# Even for this simple data structure, this is more readable than
# the alternative form because it requires you to read both from
# right-to-left and from left-to-right in different parts of the
# expression:
my_element(x[[1]])
# If you have a list of accessors, you can splice those in with `!!!`:
idx <- list(1, my_element)
pluck(x, !!!idx)
Compute the depth of a vector
Description
The depth of a vector is how many levels that you can index/pluck into it.
pluck_depth()
was previously called vec_depth()
.
Usage
pluck_depth(x, is_node = NULL)
Arguments
x |
A vector |
is_node |
Optionally override the default criteria for determine an
element can be recursed within. The default matches the behaviour of
|
Value
An integer.
Examples
x <- list(
list(),
list(list()),
list(list(list(1)))
)
pluck_depth(x)
x |> map_int(pluck_depth)
Map over multiple input simultaneously (in "parallel")
Description
These functions are variants of map()
that iterate over multiple arguments
simultaneously. They are parallel in the sense that each input is processed
in parallel with the others, not in the sense of multicore computing, i.e.
they share the same notion of "parallel" as base::pmax()
and base::pmin()
.
Usage
pmap(.l, .f, ..., .progress = FALSE)
pmap_lgl(.l, .f, ..., .progress = FALSE)
pmap_int(.l, .f, ..., .progress = FALSE)
pmap_dbl(.l, .f, ..., .progress = FALSE)
pmap_chr(.l, .f, ..., .progress = FALSE)
pmap_vec(.l, .f, ..., .ptype = NULL, .progress = FALSE)
pwalk(.l, .f, ..., .progress = FALSE)
Arguments
.l |
A list of vectors. The length of Vectors of length 1 will be recycled to any length; all other elements must be have the same length. A data frame is an important special case of |
.f |
A function, specified in one of the following ways:
|
... |
Additional arguments passed on to the mapped function. We now generally recommend against using # Instead of x |> map(f, 1, 2, collapse = ",") # do: x |> map(\(x) f(x, 1, 2, collapse = ",")) This makes it easier to understand which arguments belong to which function and will tend to yield better error messages. |
.progress |
Whether to show a progress bar. Use |
.ptype |
If |
Value
The output length is determined by the maximum length of all elements of .l
.
The output names are determined by the names of the first element of .l
.
The output type is determined by the suffix:
No suffix: a list;
.f()
can return anything.-
_lgl()
,_int()
,_dbl()
,_chr()
return a logical, integer, double, or character vector respectively;.f()
must return a compatible atomic vector of length 1. -
_vec()
return an atomic or S3 vector, the same type that.f
returns..f
can return pretty much any type of vector, as long as it is length 1. -
pwalk()
returns the input.l
(invisibly). This makes it easy to use in a pipe. The return value of.f()
is ignored.
Any errors thrown by .f
will be wrapped in an error with class
purrr_error_indexed.
See Also
Other map variants:
imap()
,
lmap()
,
map()
,
map2()
,
map_depth()
,
map_if()
,
modify()
Examples
x <- list(1, 1, 1)
y <- list(10, 20, 30)
z <- list(100, 200, 300)
pmap(list(x, y, z), sum)
# Matching arguments by position
pmap(list(x, y, z), function(first, second, third) (first + third) * second)
# Matching arguments by name
l <- list(a = x, b = y, c = z)
pmap(l, function(c, b, a) (a + c) * b)
# Vectorizing a function over multiple arguments
df <- data.frame(
x = c("apple", "banana", "cherry"),
pattern = c("p", "n", "h"),
replacement = c("P", "N", "H"),
stringsAsFactors = FALSE
)
pmap(df, gsub)
pmap_chr(df, gsub)
# Use `...` to absorb unused components of input list .l
df <- data.frame(
x = 1:3,
y = 10:12,
z = letters[1:3]
)
plus <- function(x, y) x + y
## Not run:
# this won't work
pmap(df, plus)
## End(Not run)
# but this will
plus2 <- function(x, y, ...) x + y
pmap_dbl(df, plus2)
# The "p" for "parallel" in pmap() is the same as in base::pmin()
# and base::pmax()
df <- data.frame(
x = c(1, 2, 5),
y = c(5, 4, 8)
)
# all produce the same result
pmin(df$x, df$y)
map2_dbl(df$x, df$y, min)
pmap_dbl(df, min)
Wrap a function to return a value instead of an error
Description
Create a modified version of .f
that return a default value (otherwise
)
whenever an error occurs.
Usage
possibly(.f, otherwise = NULL, quiet = TRUE)
Arguments
.f |
A function to modify, specified in one of the following ways:
|
otherwise |
Default value to use when an error occurs. |
quiet |
Hide errors ( |
Value
A function that takes the same arguments as .f
, but returns
a different value, as described above.
Adverbs
This function is called an adverb because it modifies the effect of a function (a verb). If you'd like to include a function created an adverb in a package, be sure to read faq-adverbs-export.
See Also
Other adverbs:
auto_browse()
,
compose()
,
insistently()
,
negate()
,
partial()
,
quietly()
,
safely()
,
slowly()
Examples
# To replace errors with a default value, use possibly().
list("a", 10, 100) |>
map_dbl(possibly(log, NA_real_))
# The default, NULL, will be discarded with `list_c()`
list("a", 10, 100) |>
map(possibly(log)) |>
list_c()
Prepend a vector
Description
This function was deprecated in purrr 1.0.0 because it's not related to the core purpose of purrr.
This is a companion to append()
to help merging two
lists or atomic vectors. prepend()
is a clearer semantic
signal than c()
that a vector is to be merged at the beginning of
another, especially in a pipe chain.
Usage
prepend(x, values, before = NULL)
Arguments
x |
the vector to be modified. |
values |
to be included in the modified vector. |
before |
a subscript, before which the values are to be appended. If
|
Value
A merged vector.
Examples
x <- as.list(1:3)
x |> append("a")
x |> prepend("a")
x |> prepend(list("a", "b"), before = 3)
prepend(list(), x)
Progress bars in purrr
Description
purrr's map functions have a .progress
argument that you can use to
create a progress bar. .progress
can be:
-
FALSE
, the default: does not create a progress bar. -
TRUE
: creates a basic unnamed progress bar. A string: creates a basic progress bar with the given name.
A named list of progress bar parameters, as described below.
It's good practice to name your progress bars, to make it clear what calculation or process they belong to. We recommend keeping the names under 20 characters, so the whole progress bar fits comfortably even on on narrower displays.
Progress bar parameters
-
clear
: whether to remove the progress bar from the screen after termination. Defaults toTRUE
. -
format
: format string. This overrides the default format string of the progress bar type. It must be given for thecustom
type. Format strings may contain R expressions to evaluate in braces. They support cli pluralization, and styling and they can contain special progress variables. -
format_done
: format string for successful termination. By default the same asformat
. -
format_failed
: format string for unsuccessful termination. By default the same asformat
. -
name
: progress bar name. This is by default the empty string and it is displayed at the beginning of the progress bar. -
show_after
: numeric scalar. Only show the progress bar after this number of seconds. It overrides thecli.progress_show_after
global option. -
type
: progress bar type. Currently supported types are:-
iterator
: the default, a for loop or a mapping function, -
tasks
: a (typically small) number of tasks, -
download
: download of one file, -
custom
: custom type,format
must not beNULL
for this type. The default display is different for each progress bar type.
-
Further documentation
purrr's progress bars are powered by cli, so see Introduction to progress bars in cli and Advanced cli progress bars for more details.
Indexed errors (purrr_error_indexed
)
Description
The purrr_error_indexed
class is thrown by map()
, map2()
, pmap()
, and friends.
It wraps errors thrown during the processing on individual elements with information about the location of the error.
Structure
purrr_error_indexed
has three important fields:
-
location
: the location of the error as a single integer. -
name
: the name of the location as a string. If the element was not named,name
will beNULL
-
parent
: the original error thrown by.f
.
Let's see this in action by capturing the generated condition from a very simple example:
f <- function(x) { rlang::abort("This is an error") } cnd <- rlang::catch_cnd(map(c(1, 4, 2), f)) class(cnd) #> [1] "purrr_error_indexed" "rlang_error" "error" #> [4] "condition" cnd$location #> [1] 1 cnd$name #> NULL print(cnd$parent, backtrace = FALSE) #> <error/rlang_error> #> Error in `.f()`: #> ! This is an error
If the input vector is named, name
will be non-NULL
:
cnd <- rlang::catch_cnd(map(c(a = 1, b = 4, c = 2), f)) cnd$name #> [1] "a"
Handling errors
(This section assumes that you're familiar with the basics of error handling in R, as described in Advanced R.)
This error chaining is really useful when doing interactive data analysis, but it adds some extra complexity when handling errors with tryCatch()
or withCallingHandlers()
.
Let's see what happens by adding a custom class to the error thrown by f()
:
f <- function(x) { rlang::abort("This is an error", class = "my_error") } map(c(1, 4, 2, 5, 3), f) #> Error in `map()`: #> i In index: 1. #> Caused by error in `.f()`: #> ! This is an error
This doesn't change the visual display, but you might be surprised if you try to catch this error with tryCatch()
or withCallingHandlers()
:
tryCatch( map(c(1, 4, 2, 5, 3), f), my_error = function(err) { # use NULL value if error NULL } ) #> Error in `map()`: #> i In index: 1. #> Caused by error in `.f()`: #> ! This is an error withCallingHandlers( map(c(1, 4, 2, 5, 3), f), my_error = function(err) { # throw a more informative error abort("Wrapped error", parent = err) } ) #> Error in `map()`: #> i In index: 1. #> Caused by error in `.f()`: #> ! This is an error
That's because, as described above, the error that map()
throws will always have class purrr_error_indexed
:
tryCatch( map(c(1, 4, 2, 5, 3), f), purrr_error_indexed = function(err) { print("Hello! I am now called :)") } ) #> [1] "Hello! I am now called :)"
In order to handle the error thrown by f()
, you'll need to use rlang::cnd_inherits()
on the parent error:
tryCatch( map(c(1, 4, 2, 5, 3), f), purrr_error_indexed = function(err) { if (rlang::cnd_inherits(err, "my_error")) { NULL } else { rlang::cnd_signal(err) } } ) #> NULL withCallingHandlers( map(c(1, 4, 2, 5, 3), f), purrr_error_indexed = function(err) { if (rlang::cnd_inherits(err, "my_error")) { abort("Wrapped error", parent = err) } } ) #> Error: #> ! Wrapped error #> Caused by error in `map()`: #> i In index: 1. #> Caused by error in `.f()`: #> ! This is an error
(The tryCatch()
approach is suboptimal because we're no longer just handling errors, but also rethrowing them.
The rethrown errors won't work correctly with (e.g.) recover()
and traceback()
, but we don't currently have a better approach.
In the future we expect to enhance try_fetch()
to make this easier to do 100% correctly).
Finally, if you just want to get rid of purrr's wrapper error, you can resignal the parent error:
withCallingHandlers( map(c(1, 4, 2, 5, 3), f), purrr_error_indexed = function(err) { rlang::cnd_signal(err$parent) } ) #> Error in `.f()`: #> ! This is an error
Because we are resignalling an error, it's important to use withCallingHandlers()
and not tryCatch()
in order to preserve the full backtrace context.
That way recover()
, traceback()
, and related tools will continue to work correctly.
Wrap a function to capture side-effects
Description
Create a modified version of .f
that captures side-effects along with
the return value of the function and returns a list containing
the result
, output
, messages
and warnings
.
Usage
quietly(.f)
Arguments
.f |
A function to modify, specified in one of the following ways:
|
Value
A function that takes the same arguments as .f
, but returns
a different value, as described above.
Adverbs
This function is called an adverb because it modifies the effect of a function (a verb). If you'd like to include a function created an adverb in a package, be sure to read faq-adverbs-export.
See Also
Other adverbs:
auto_browse()
,
compose()
,
insistently()
,
negate()
,
partial()
,
possibly()
,
safely()
,
slowly()
Examples
f <- function() {
print("Hi!")
message("Hello")
warning("How are ya?")
"Gidday"
}
f()
f_quiet <- quietly(f)
str(f_quiet())
Wait for a given time
Description
If the rate's internal counter exceeds the maximum number of times
it is allowed to sleep, rate_sleep()
throws an error of class
purrr_error_rate_excess
.
Usage
rate_sleep(rate, quiet = TRUE)
rate_reset(rate)
Arguments
rate |
A rate object determining the waiting time. |
quiet |
If |
Details
Call rate_reset()
to reset the internal rate counter to 0.
See Also
Create delaying rate settings
Description
These helpers create rate settings that you can pass to insistently()
and
slowly()
. You can also use them in your own functions with rate_sleep()
.
Usage
rate_delay(pause = 1, max_times = Inf)
rate_backoff(
pause_base = 1,
pause_cap = 60,
pause_min = 1,
max_times = 3,
jitter = TRUE
)
is_rate(x)
Arguments
pause |
Delay between attempts in seconds. |
max_times |
Maximum number of requests to attempt. |
pause_base , pause_cap |
|
pause_min |
Minimum time to wait in the backoff; generally only necessary if you need pauses less than one second (which may not be kind to the server, use with caution!). |
jitter |
Whether to introduce a random jitter in the waiting time. |
x |
An object to test. |
Examples
# A delay rate waits the same amount of time:
rate <- rate_delay(0.02)
for (i in 1:3) rate_sleep(rate, quiet = FALSE)
# A backoff rate waits exponentially longer each time, with random
# jitter by default:
rate <- rate_backoff(pause_base = 0.2, pause_min = 0.005)
for (i in 1:3) rate_sleep(rate, quiet = FALSE)
Generate random sample from a Bernoulli distribution
Description
This function was deprecated in purrr 1.0.0 because it's not related to the core purpose of purrr.
Usage
rbernoulli(n, p = 0.5)
Arguments
n |
Number of samples |
p |
Probability of getting |
Value
A logical vector
Examples
rbernoulli(10)
rbernoulli(100, 0.1)
Generate random sample from a discrete uniform distribution
Description
This function was deprecated in purrr 1.0.0 because it's not related to the core purpose of purrr.
Usage
rdunif(n, b, a = 1)
Arguments
n |
Number of samples to draw. |
a , b |
Range of the distribution (inclusive). |
Examples
table(rdunif(1e3, 10))
table(rdunif(1e3, 10, -5))
Reduce a list to a single value by iteratively applying a binary function
Description
reduce()
is an operation that combines the elements of a vector
into a single value. The combination is driven by .f
, a binary
function that takes two values and returns a single value: reducing
f
over 1:3
computes the value f(f(1, 2), 3)
.
Usage
reduce(.x, .f, ..., .init, .dir = c("forward", "backward"))
reduce2(.x, .y, .f, ..., .init)
Arguments
.x |
A list or atomic vector. |
.f |
For For The reduction terminates early if |
... |
Additional arguments passed on to the reduce function. We now generally recommend against using # Instead of x |> reduce(f, 1, 2, collapse = ",") # do: x |> reduce(\(x, y) f(x, y, 1, 2, collapse = ",")) This makes it easier to understand which arguments belong to which function and will tend to yield better error messages. |
.init |
If supplied, will be used as the first value to start
the accumulation, rather than using |
.dir |
The direction of reduction as a string, one of
|
.y |
For |
Direction
When .f
is an associative operation like +
or c()
, the
direction of reduction does not matter. For instance, reducing the
vector 1:3
with the binary function +
computes the sum ((1 + 2) + 3)
from the left, and the same sum (1 + (2 + 3))
from the
right.
In other cases, the direction has important consequences on the
reduced value. For instance, reducing a vector with list()
from
the left produces a left-leaning nested list (or tree), while
reducing list()
from the right produces a right-leaning list.
See Also
accumulate()
for a version that returns all intermediate
values of the reduction.
Examples
# Reducing `+` computes the sum of a vector while reducing `*`
# computes the product:
1:3 |> reduce(`+`)
1:10 |> reduce(`*`)
# By ignoring the input vector (nxt), you can turn output of one step into
# the input for the next. This code takes 10 steps of a random walk:
reduce(1:10, \(acc, nxt) acc + rnorm(1), .init = 0)
# When the operation is associative, the direction of reduction
# does not matter:
reduce(1:4, `+`)
reduce(1:4, `+`, .dir = "backward")
# However with non-associative operations, the reduced value will
# be different as a function of the direction. For instance,
# `list()` will create left-leaning lists when reducing from the
# right, and right-leaning lists otherwise:
str(reduce(1:4, list))
str(reduce(1:4, list, .dir = "backward"))
# reduce2() takes a ternary function and a second vector that is
# one element smaller than the first vector:
paste2 <- function(x, y, sep = ".") paste(x, y, sep = sep)
letters[1:4] |> reduce(paste2)
letters[1:4] |> reduce2(c("-", ".", "-"), paste2)
x <- list(c(0, 1), c(2, 3), c(4, 5))
y <- list(c(6, 7), c(8, 9))
reduce2(x, y, paste)
# You can shortcircuit a reduction and terminate it early by
# returning a value wrapped in a done(). In the following example
# we return early if the result-so-far, which is passed on the LHS,
# meets a condition:
paste3 <- function(out, input, sep = ".") {
if (nchar(out) > 4) {
return(done(out))
}
paste(out, input, sep = sep)
}
letters |> reduce(paste3)
# Here the early return branch checks the incoming inputs passed on
# the RHS:
paste4 <- function(out, input, sep = ".") {
if (input == "j") {
return(done(out))
}
paste(out, input, sep = sep)
}
letters |> reduce(paste4)
Reduce from the right (retired)
Description
reduce_right()
is soft-deprecated as of purrr 0.3.0. Please use
the .dir
argument of reduce()
instead. Note that the algorithm
has changed. Whereas reduce_right()
computed f(f(3, 2), 1)
,
reduce(.dir = \"backward\")
computes f(1, f(2, 3))
. This is the
standard way of reducing from the right.
To update your code with the same reduction as reduce_right()
,
simply reverse your vector and use a left reduction:
# Before: reduce_right(1:3, f) # After: reduce(rev(1:3), f)
reduce2_right()
is deprecated as of purrr 0.3.0 without
replacement. It is not clear what algorithmic properties should a
right reduction have in this case. Please reach out if you know
about a use case for a right reduction with a ternary function.
Usage
reduce_right(.x, .f, ..., .init)
reduce2_right(.x, .y, .f, ..., .init)
accumulate_right(.x, .f, ..., .init)
Arguments
.x |
A list or atomic vector. |
.f |
For For The reduction terminates early if |
... |
Additional arguments passed on to the reduce function. We now generally recommend against using # Instead of x |> reduce(f, 1, 2, collapse = ",") # do: x |> reduce(\(x, y) f(x, y, 1, 2, collapse = ",")) This makes it easier to understand which arguments belong to which function and will tend to yield better error messages. |
.init |
If supplied, will be used as the first value to start
the accumulation, rather than using |
.y |
For |
Objects exported from other packages
Description
These objects are imported from other packages. Follow the links below to see their documentation.
- rlang
%||%
,done
,exec
,is_atomic
,is_bare_atomic
,is_bare_character
,is_bare_double
,is_bare_integer
,is_bare_list
,is_bare_logical
,is_bare_numeric
,is_bare_vector
,is_character
,is_double
,is_empty
,is_formula
,is_function
,is_integer
,is_list
,is_logical
,is_null
,is_scalar_atomic
,is_scalar_character
,is_scalar_double
,is_scalar_integer
,is_scalar_list
,is_scalar_logical
,is_scalar_vector
,is_vector
,rep_along
,set_names
,zap
Re-run expressions multiple times
Description
This function was deprecated in purrr 1.0.0 because we believe that NSE
functions are not a good fit for purrr. Also, rerun(n, x)
can just as
easily be expressed as map(1:n, \(i) x)
rerun()
is a convenient way of generating sample data. It works similarly to
replicate(..., simplify = FALSE)
.
Usage
rerun(.n, ...)
Arguments
.n |
Number of times to run expressions |
... |
Expressions to re-run. |
Value
A list of length .n
. Each element of ...
will be
re-run once for each .n
.
There is one special case: if there's a single unnamed input, the second
level list will be dropped. In this case, rerun(n, x)
behaves like
replicate(n, x, simplify = FALSE)
.
Examples
# old
5 |> rerun(rnorm(5)) |> str()
# new
1:5 |> map(\(i) rnorm(5)) |> str()
# old
5 |>
rerun(x = rnorm(5), y = rnorm(5)) |>
map_dbl(\(l) cor(l$x, l$y))
# new
1:5 |>
map(\(i) list(x = rnorm(5), y = rnorm(5))) |>
map_dbl(\(l) cor(l$x, l$y))
Wrap a function to capture errors
Description
Creates a modified version of .f
that always succeeds. It returns a list
with components result
and error
. If the function succeeds, result
contains the returned value and error
is NULL
. If an error occurred,
error
is an error
object and result
is either NULL
or otherwise
.
Usage
safely(.f, otherwise = NULL, quiet = TRUE)
Arguments
.f |
A function to modify, specified in one of the following ways:
|
otherwise |
Default value to use when an error occurs. |
quiet |
Hide errors ( |
Value
A function that takes the same arguments as .f
, but returns
a different value, as described above.
Adverbs
This function is called an adverb because it modifies the effect of a function (a verb). If you'd like to include a function created an adverb in a package, be sure to read faq-adverbs-export.
See Also
Other adverbs:
auto_browse()
,
compose()
,
insistently()
,
negate()
,
partial()
,
possibly()
,
quietly()
,
slowly()
Examples
safe_log <- safely(log)
safe_log(10)
safe_log("a")
list("a", 10, 100) |>
map(safe_log) |>
transpose()
# This is a bit easier to work with if you supply a default value
# of the same type and use the simplify argument to transpose():
safe_log <- safely(log, otherwise = NA_real_)
list("a", 10, 100) |>
map(safe_log) |>
transpose() |>
simplify_all()
Wrap a function to wait between executions
Description
slowly()
takes a function and modifies it to wait a given
amount of time between each call.
Usage
slowly(f, rate = rate_delay(), quiet = TRUE)
Arguments
f |
A function to modify, specified in one of the following ways:
|
rate |
A rate object. Defaults to a constant delay. |
quiet |
Hide errors ( |
Value
A function that takes the same arguments as .f
, but returns
a different value, as described above.
Adverbs
This function is called an adverb because it modifies the effect of a function (a verb). If you'd like to include a function created an adverb in a package, be sure to read faq-adverbs-export.
See Also
Other adverbs:
auto_browse()
,
compose()
,
insistently()
,
negate()
,
partial()
,
possibly()
,
quietly()
,
safely()
Examples
# For these example, we first create a custom rate
# with a low waiting time between attempts:
rate <- rate_delay(0.1)
# slowly() causes a function to sleep for a given time between calls:
slow_runif <- slowly(\(x) runif(1), rate = rate, quiet = FALSE)
out <- map(1:5, slow_runif)
Splice objects and lists of objects into a list
Description
This function was deprecated in purrr 1.0.0 because we no longer believe that
this style of implicit/automatic splicing is a good idea; instead use
rlang::list2()
+ !!!
or list_flatten()
.
splice()
splices all arguments into a list. Non-list objects and lists
with a S3 class are encapsulated in a list before concatenation.
Usage
splice(...)
Arguments
... |
Objects to concatenate. |
Value
A list.
Examples
inputs <- list(arg1 = "a", arg2 = "b")
# splice() concatenates the elements of inputs with arg3
splice(inputs, arg3 = c("c1", "c2")) |> str()
list(inputs, arg3 = c("c1", "c2")) |> str()
c(inputs, arg3 = c("c1", "c2")) |> str()
Transpose a list.
Description
transpose()
turns a list-of-lists "inside-out"; it turns a pair of lists
into a list of pairs, or a list of pairs into pair of lists. For example,
if you had a list of length n where each component had values a
and
b
, transpose()
would make a list with elements a
and
b
that contained lists of length n. It's called transpose because
x[[1]][[2]]
is equivalent to transpose(x)[[2]][[1]]
.
This function was superseded in purrr 1.0.0 because list_transpose()
has a better name and can automatically simplify the output, as is commonly
needed. Superseded functions will not go away, but will only receive critical
bug fixes.
Usage
transpose(.l, .names = NULL)
Arguments
.l |
A list of vectors to transpose. The first element is used as the template; you'll get a warning if a subsequent element has a different length. |
.names |
For efficiency, |
Value
A list with indexing transposed compared to .l
.
transpose()
is its own inverse, much like the transpose operation on a
matrix. You can get back the original input by transposing it twice.
Examples
x <- map(1:5, \(i) list(x = runif(1), y = runif(5)))
# was
x |> transpose() |> str()
# now
x |> list_transpose(simplify = FALSE) |> str()
# transpose() is useful in conjunction with safely() & quietly()
x <- list("a", 1, 2)
y <- x |> map(safely(log))
# was
y |> transpose() |> str()
# now:
y |> list_transpose() |> str()
# Previously, output simplification required a call to another function
x <- list(list(a = 1, b = 2), list(a = 3, b = 4), list(a = 5, b = 6))
x |> transpose() |> simplify_all()
# Now can take advantage of automatic simplification
x |> list_transpose()
# Provide explicit component names to prevent loss of those that don't
# appear in first component
ll <- list(
list(x = 1, y = "one"),
list(z = "deux", x = 2)
)
ll |> transpose()
nms <- ll |> map(names) |> reduce(union)
# was
ll |> transpose(.names = nms)
# now
ll |> list_transpose(template = nms)
# and can supply default value
ll |> list_transpose(template = nms, default = NA)
Update a list with formulas
Description
update_list()
was deprecated in purrr 1.0.0, because we no longer believe
that functions that use NSE are a good fit for purrr.
update_list()
handles formulas and quosures that can refer to
values existing within the input list. This function is deprecated
because we no longer believe that functions that use tidy evaluation are
a good fit for purrr.
Usage
update_list(.x, ...)
Arguments
.x |
List to modify. |
... |
New values of a list. Use These values should be either all named or all unnamed. When
inputs are all named, they are matched to Dynamic dots are supported. In particular, if your
replacement values are stored in a list, you can splice that in with
|
Match/validate a set of conditions for an object and continue with the action associated with the first valid match.
Description
This function was deprecated in purrr 1.0.0 because it's not related to the
core purpose of purrr. You can pull your code out of a pipe and use regular
if
/else
statements instead.
when()
is a flavour of pattern matching (or an if-else abstraction) in
which a value is matched against a sequence of condition-action sets. When a
valid match/condition is found the action is executed and the result of the
action is returned.
Usage
when(., ...)
Arguments
. |
the value to match against |
... |
formulas; each containing a condition as LHS and an action as RHS. named arguments will define additional values. |
Value
The value resulting from the action of the first valid match/condition is returned. If no matches are found, and no default is given, NULL will be returned.
Validity of the conditions are tested with isTRUE
, or equivalently
with identical(condition, TRUE)
.
In other words conditions resulting in more than one logical will never
be valid. Note that the input value is always treated as a single object,
as opposed to the ifelse
function.
Examples
1:10 |>
when(
sum(.) <= 50 ~ sum(.),
sum(.) <= 100 ~ sum(.)/2,
~ 0
)
# now
x <- 1:10
if (sum(x) < 10) {
sum(x)
} else if (sum(x) < 100) {
sum(x) / 2
} else {
0
}