| Title: | Functional Programming Tools |
| Version: | 1.2.0 |
| 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.1) |
| Imports: | cli (≥ 3.6.1), lifecycle (≥ 1.0.3), magrittr (≥ 1.5.0),rlang (≥ 1.1.1), vctrs (≥ 0.6.3) |
| Suggests: | carrier (≥ 0.3.0), covr, dplyr (≥ 0.7.8), httr, knitr,lubridate, mirai (≥ 2.5.1), 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.3 |
| NeedsCompilation: | yes |
| Packaged: | 2025-11-03 22:03:33 UTC; hadleywickham |
| Author: | Hadley Wickham |
| Maintainer: | Hadley Wickham <hadley@posit.co> |
| Repository: | CRAN |
| Date/Publication: | 2025-11-04 13:20:02 UTC |
purrr: Functional Programming Tools
Description

A complete and consistent functional programming toolkit for R.
Author(s)
Maintainer: Hadley Wickhamhadley@posit.co (ORCID)
Authors:
Lionel Henrylionel@posit.co
Other contributors:
Posit Software, PBC (ROR) [copyright holder, funder]
See Also
Useful links:
Report bugs athttps://github.com/tidyverse/purrr/issues
Pipe operator
Description
Pipe operator
Usage
lhs %>% rhsAccumulate intermediate results of a vector reduction
Description
accumulate() sequentially applies a 2-argument function to elements of avector. Each application of the function uses the initial value or resultof the previous application as the first argument. The second argument isthe next value of the vector. The results of each application arereturned in a list. The accumulation can optionally terminate beforeprocessing the whole vector in response to adone() signal returned bythe accumulation function.
By contrast toaccumulate(),reduce() applies a 2-argument function inthe same way, but discards all results except that of the final functionapplication.
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 ofx |> map(f, 1, 2, collapse = ",")# do:x |> map(\(x) f(x, 1, 2, collapse = ",")) This makes it easier to understand which arguments belong to whichfunction and will tend to yield better error messages. |
.init | If supplied, will be used as the first value to startthe 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 hasnames, the initial value is given the name".init", otherwisethe returned vector is kept unnamed.
If.dir is"forward" (the default), the first element is theinitial value (.init if supplied, or the first element of.x)and the last element is the final reduced value. In case of aright accumulation, this order is reversed.
The accumulation terminates early if.f returns a value wrappedin adone(). If the done box is empty, the last value isused instead and the result is one element shorter (but alwaysincludes the initial value, even when terminating at the firstiteration).
Direction
When.f is an associative operation like+ orc(), thedirection of reduction does not matter. For instance, reducing thevector1:3 with the binary function+ computes the sum((1 + 2) + 3) from the left, and the same sum(1 + (2 + 3)) from theright.
In other cases, the direction has important consequences on thereduced value. For instance, reducing a vector withlist() fromthe left produces a left-leaning nested list (or tree), whilereducinglist() 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 thecore purpose of purrr.
It can be useful to create an empty list that you plan to fill later. This issimilar to the idea ofseq_along(), which creates a vector of the samelength as its input.
Usage
list_along(x)Arguments
x | A vector. |
Value
A list of the same length asx.
Examples
x <- 1:5seq_along(x)list_along(x)Coerce array to list
Description
array_branch() andarray_tree() enable arrays to beused with purrr's functionals by turning them into lists. Thedetails of the coercion are controlled by themarginargument.array_tree() creates an hierarchical list (a tree)that has as many levels as dimensions specified inmargin,whilearray_branch() creates a flat list (by analogy, abranch) 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 theindices to be to be enlisted. If |
Details
When no margin is specified, all dimensions are used bydefault. Whenmargin is a numeric vector of length zero, thewhole array is wrapped in a list.
Examples
# We create an array with 3 dimensionsx <- 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 functionspecifications that most purrr functions allow. It is an S3generic. The default method forwards its arguments torlang::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 afunction, it is used as is. If aformula, e.g. Ifcharacter vector,numeric vector, orlist, it isconverted to an extractor function. Character vectors index byname and numeric vectors index by position; use a list to indexby position and name at different levels. If a component is notpresent, 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 oflist_simplify() which has more consistent semantics based on vctrsprinciples:
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 criticalbug 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 thelength of the vectors to be concatenated, such as |
Examples
# wasas.list(letters) |> as_vector("character")# nowas.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())Create an attribute getter function
Description
attr_getter() generates an attribute accessor function; i.e., itgenerates a function for extracting an attribute with a givenname. Unlike the base Rattr() function with default options, itdoesn'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 objectpluck(x, 2, attr_getter("obj_attr")) # From second objectWrap a function so it will automaticallybrowse() on error
Description
A function wrapped withauto_browse() will automatically enter aninteractive debugger usingbrowser() 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 returnsa different value, as described above.
Adverbs
This function is called an adverb because it modifies the effect of afunction (a verb). If you'd like to include a function created an adverbin a package, be sure to readfaq-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'texist
Description
chuck() implements a generalised form of[[ that allow you to indexdeeply and flexibly into data structures. If the index you are trying toaccess does not exist (or isNULL), 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 bean 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 andpositions). If the object being indexed is an S4 object,accessing it by name will return the corresponding slot. Dynamic dots are supported. In particular, ifyour 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 tofunction(...) f(g(...)).
Usage
compose(..., .dir = c("backward", "forward"))Arguments
... | Functions to apply in order (from right to left bydefault). Formulas are converted to functions in the usual way. Dynamic dots are supported. In particular, ifyour 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 afunction (a verb). If you'd like to include a function created an adverbin a package, be sure to readfaq-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 + 1compose(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 theyare slow and buggy, and we no longer think they are the rightapproach to solving this problem. Please usetidyr::expand_grid()instead.
Here is an example of equivalent usages forcross() andexpand_grid():
data <- list( id = c("John", "Jane"), sep = c("! ", "... "), greeting = c("Hello.", "Bonjour."))# With deprecated `cross()`data |> cross() |> map_chr(\(...) paste0(..., collapse = ""))# With `expand_grid()`tidyr::expand_grid(!!!data) |> pmap_chr(paste)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 dataframe. |
.filter | A predicate function that takes the same number ofarguments 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 andreturns the cartesian product of all its elements in a list, withone combination by element.cross_df() is likecross() but returns a data frame, with one combination byrow.
cross(),cross2() andcross3() return thecartesian product is returned in wide format. This makes it moreamenable to mapping operations.cross_df() returns the outputin long format just asexpand.grid() does. This is adaptedto rowwise operations.
When the number of combinations is large and the individualelements are heavy memory-wise, it is often useful to filterunwanted combinations on the fly with.filter. It must bea predicate function that takes the same number of arguments as thenumber of crossed objects (2 forcross2(), 3 forcross3(),length(.l) forcross()) andreturnsTRUE orFALSE. The combinations where thepredicate function returnsTRUE will be removed from theresult.
Value
cross2(),cross3() andcross()always return a list.cross_df() always returns a dataframe.cross() returns a list where each element is onecombination so that the list can be directly mappedover.cross_df() returns a data frame where each row is onecombination.
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 loopout <- 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 functionfilter <- function(x, y) x >= ycross2(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"), .default = NULL)detect_index(.x, .f, ..., .dir = c("forward", "backward"))Arguments
.x | A list or vector. |
.f | A function, specified in one of the following ways:
|
... | Additional arguments passed on to |
.dir | If |
.default | The value returned when nothing is detected. |
Value
detect the value of the first item that matches thepredicate;detect_index the position of the matching item.If not found,detect returnsNULL anddetect_indexreturns 0.
See Also
keep() for keeping all matching values.
Examples
is_even <- function(x) x %% 2 == 03: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 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()returnsTRUEwhen.pisTRUEfor at least one element.every()returnsTRUEwhen.pisTRUEfor all elements.none()returnsTRUEwhen.pisFALSEfor 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 packagerequires some precautions because the functions will contain internalpurrr code. This means that creating them once and for all whenthe package is built may cause problems when purrr is updated, becausea function that the adverb uses might no longer exist.
Instead, either create the modified function once per session on packageload or wrap the call within another function every time you use it:
Using the
.onLoad()hook:#' My function#' @exportinsist_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#' @exportinsist_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 wasinconsistent. Superseded functions will not go away, but will only receivecritical 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 logicalvector,flatten_int() an integer vector,flatten_dbl() adouble vector, andflatten_chr() a character vector.
flatten_dfr() andflatten_dfc() return data frames created byrow-binding and column-binding respectively. They require dplyr tobe installed.
Examples
x <- map(1:3, \(i) sample(4))x# wasx |> flatten_int() |> str()# nowx |> list_c() |> str()x <- list(list(1, 2), list(3, 4))# wasx |> flatten() |> str()# nowx |> list_flatten() |> str()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 apredicate 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 ofx |> map(f, 1, 2, collapse = ",")# do:x |> map(\(x) f(x, 1, 2, collapse = ",")) This makes it easier to understand which arguments belong to whichfunction and will tend to yield better error messages. |
Value
A vector the same type as.x.
Examples
pos <- function(x) x >= 0head_while(5:-5, pos)tail_while(5:-5, negate(pos))big <- function(x) x > 100head_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 formap2(x, names(x), ...) ifx has names, ormap2(x, seq_along(x), ...)if it does not. This is useful if you need to compute on both the valueand 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:
Wrap a function with |
... | Additional arguments passed on to the mapped function. We now generally recommend against using # Instead ofx |> map(f, 1, 2, collapse = ",")# do:x |> map(\(x) f(x, 1, 2, collapse = ",")) This makes it easier to understand which arguments belong to whichfunction 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 = ""))Parallelization in purrr
Description
All map functions allow parallelized operation usingmirai.
Wrap functions passed to the.f argument ofmap() and its variants within_parallel().
in_parallel() is apurrr adverb that plays two roles:
It is a signal to purrr verbs like
map()to go ahead and performcomputations in parallel.It helps you create self-contained functions that are isolated from yourworkspace. This is important because the function is packaged up(serialized) to be sent across to parallel processes. Isolation iscritical for performance because it prevents accidentally sending verylarge objects between processes.
For maps to actually be performed in parallel, the user must also setmirai::daemons(), otherwise they fall back to sequential processing.mirai::require_daemons() may be used to enforce the use of parallelprocessing. See the section 'Daemons settings' below.
Usage
in_parallel(.f, ...)Arguments
.f | A fresh formula or function. "Fresh" here means that they should bedeclared in the call to |
... | Named arguments to declare in the environment of the function. |
Value
A 'crate' (classed function).
Creating self-contained functions
They should call package functions with an explicit
::namespace. Forinstanceggplot()from the ggplot2 package must be called with itsnamespace prefix:ggplot2::ggplot(). An alternative is to uselibrary()within the function to attach a package to the search path, which allowssubsequent use of package functions without the explicit namespace.They should declare any data they depend on. Declare data by supplyingnamed arguments to
.... When.fis an anonymous function to alocally-defined function of the form\(x) fun(x),funitself must besupplied to...in the manner of:in_parallel(\(x) fun(x), fun = fun).Functions (closures) supplied to
...must themselves be self-contained,as they are modified to share the same closure as the main function. Thismeans that all helper functions and other required variables must also besupplied as further...arguments. This applies only for functionsdirectly supplied to..., and containers such as lists are notrecursively walked to find functions (meaning you're at risk ofunexpectedly including large objects with your parallel function if yousupply complex lists).
in_parallel() is a simple wrapper ofcarrier::crate() and you may referto that package for more details.
Example usage:
# The function needs to be freshly-defined, so instead of:mtcars |> map_dbl(in_parallel(sum))# Use an anonymous function:mtcars |> map_dbl(in_parallel(\(x) sum(x)))# Package functions need to be explicitly namespaced, so instead of:map(1:3, in_parallel(\(x) vec_init(integer(), x)))# Use :: to namespace all package functions:map(1:3, in_parallel(\(x) vctrs::vec_init(integer(), x)))fun <- function(x) { param + helper(x) }helper <- function(x) { x %% 2 }param <- 5# Operating in parallel, locally-defined functions, including helper# functions and other objects required by it, will not be found:map(1:3, in_parallel(\(x) fun(x)))# Use the ... argument to supply these objects:map(1:3, in_parallel(\(x) fun(x), fun = fun, helper = helper, param = param))When to use
Parallelizing a map using 'n' processes does not automatically lead to ittaking 1/n of the time. Additional overhead from setting up the parallel taskand communicating with parallel processes eats into this benefit, and canoutweigh it for very short tasks or those involving large amounts of data.
The threshold at which parallelization becomes clearly beneficial will differaccording to your individual setup and task, but a rough guide would be inthe order of 100 microseconds to 1 millisecond for each map iteration.
Daemons settings
How and where parallelization occurs is determined bymirai::daemons().This is a function from themirai package that sets up daemons(persistent background processes that receive parallel computations) on yourlocal machine or across the network.
Daemons must be set prior to performing any parallel map operation, otherwisein_parallel() will fall back to sequential processing. To ensure that mapsare always performed in parallel, placemirai::require_daemons() before themap.
It is usual to set daemons once per session. You can leave them running onyour local machine as they consume almost no resources whilst waiting toreceive tasks. The following sets up 6 daemons locally:
mirai::daemons(6)
Function arguments:
n: the number of daemons to launch on your local machine, e.g.mirai::daemons(6). As a rule of thumb, for maximum efficiency this shouldbe (at most) one less than the number of cores on your machine, leaving onecore for the main R process.urlandremote: used to set up and launch daemons for distributedcomputing over the network. Seemirai::daemons()documentation for moredetails.
Resetting daemons:
Daemons persist for the duration of your session. To reset and tear down anyexisting daemons:
mirai::daemons(0)
All daemons automatically terminate when your session ends. You do not needto explicitly terminate daemons in this instance, although it is still goodpractice to do so.
Note: if you are using parallel map within a package, do not make anymirai::daemons() calls within the package. This is as it should always beup to the user how they wish to set up parallel processing: (i) resources areonly known at run-time e.g. availability of local or remote daemons, (ii)packages should make use of existing daemons when already set, rather thanreset them, and (iii) it helps prevent inadvertently spawning too manydaemons when functions are used recursively within each other.
References
purrr's parallelization is powered bymirai. See themirai website for more details.
See Also
map() for usage examples.
Examples
# Run in interactive sessions only as spawns additional processesdefault_param <- 0.5delay <- function(secs = default_param) { Sys.sleep(secs)}slow_lm <- function(formula, data) { delay() lm(formula, data)}# Example of a 'crate' returned by in_parallel(). The object print method# shows the size of the crate and any objects contained within:crate <- in_parallel( \(df) slow_lm(mpg ~ disp, data = df), slow_lm = slow_lm, delay = delay, default_param = default_param)crate# Use mirai::mirai() to test that a crate is self-contained# by running it in a daemon and collecting its return value:mirai::mirai(crate(mtcars), crate = crate) |> mirai::collect_mirai()Transform a function to wait then retry after an error
Description
insistently() takes a function and modifies it to retry after givenamount 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 | Arate object. Defaults to jittered exponentialbackoff. |
quiet | Hide errors ( |
Value
A function that takes the same arguments as.f, but returnsa different value, as described above.
Adverbs
This function is called an adverb because it modifies the effect of afunction (a verb). If you'd like to include a function created an adverbin a package, be sure to readfaq-adverbs-export.
See Also
httr::RETRY() is a special case ofinsistently() forHTTP 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 workrisky_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 seedinsistent_risky_runif()set.seed(3) # Failing seedtry(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 combinationrate <- 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()functionreexported from rlang.exec()evaluates a function call builtfrom its inputs and supportsdynamic dots:# Before:invoke(mean, list(na.rm = TRUE), x = 1:10)# Afterexec(mean, 1:10, !!!list(na.rm = TRUE))
invoke_map()is deprecated because it's harder to understand than thecorresponding 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
# wasinvoke(runif, list(n = 10))invoke(runif, n = 10)# nowexec(runif, n = 10)# wasargs <- list("01a", "01b")invoke(paste, args, sep = "-")# nowexec(paste, !!!args, sep = "-")# wasfuns <- list(runif, rnorm)funs |> invoke_map(n = 5)funs |> invoke_map(list(list(n = 10), list(n = 5)))# nowfuns |> map(exec, n = 5)funs |> map2(list(list(n = 10), list(n = 5)), function(f, args) exec(f, !!!args))# or use pmap + a tibbledf <- tibble::tibble( fun = list(runif, rnorm), args = list(list(n = 10), list(n = 5)))df |> pmap(function(fun, args) exec(fun, !!!args))# waslist(m1 = mean, m2 = median) |> invoke_map(x = rcauchy(100))# nowlist(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 toTRUE;discard() selects all elements where.p evaluates toFALSE.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() anddiscard() are often calledselect()/filter() andreject()/drop(), but those names are already takenin R.keep() is similar toFilter(), but the argument order is moreconvenient, 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 shorthand formrep(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 TRUEx <- rerun(5, a = rbernoulli(1), b = sample(10))xx |> keep("a")x |> discard("a")# compact() discards elements that are NULL or that have length zerolist(a = "a", b = NULL, c = integer(0), d = NA, e = list()) |> compact()Keep/discard elements based on their name/position
Description
keep_at() anddiscard_at() are similar to[ ordplyr::select(): theyreturn the same type of data structure as the input, but only containingthe requested elements. (If you're looking for a function similar to[[ seepluck()/chuck()).
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 functionx |> keep_at(\(x) nchar(x) == 3)x |> discard_at(\(x) nchar(x) == 3)Lift the domain of a function
Description
lift_xy() is a composition helper. It helps you composefunctions by lifting their domain from a kind of input to anotherkind. The domain can be changed from and to a list (l), a vector(v) and dots (d). For example,lift_ld(fun) transforms afunction taking a list to a function taking dots.
The most important of those helpers is probablylift_dl()because it allows you to transform a regular function to one thattakes a list. This is often essential for composition with purrrfunctional 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 purrrto its most useful core. Superseded functions will not go away, but will onlyreceive 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 thelength of the vectors to be concatenated, such as |
Value
A function.
from ... tolist(...) orc(...)
Here dots should be taken here in a figurative way. The liftedfunctions does not need to take dots per se. The function issimply wrapped a function indo.call(), so insteadof taking multiple arguments, it takes a single named list orvector which will be interpreted as its arguments. This isparticularly useful when you want to pass a row of a data frameor a list to a function and don't want to manually pull it apartin your function.
fromc(...) tolist(...) or...
These factories allow a function taking a vector to take a listor dots instead. The lifted function internally transforms itsinputs back to an atomic vector. purrr does not obey the usual Rcasting rules (e.g.,c(1, "2") produces a charactervector) and will produce an error if the types are notcompatible. Additionally, you can enforce a particular vectortype by supplying.type.
from list(...) to c(...) or ...
lift_ld() turns a function that takes a list into afunction that takes dots.lift_vd() does the same with afunction that takes an atomic vector. These factory functions arethe inverse operations oflift_dl() andlift_dv().
lift_vd() internally coerces the inputs of..f toan atomic vector. The details of this coercion can be controlledwith.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 listfun <- 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))# nowpmap_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)))# nowmtcars |> 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 byutils::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. Wheninputs are all named, they are matched to Dynamic dots are supported. In particular, if yourreplacement values are stored in a list, you can splice that in with |
.is_node | A predicate function that determines whether an element isa node (by returning |
Examples
x <- list(x = 1:10, y = 4, z = list(a = 1, b = 2))str(x)# Update valuesstr(list_assign(x, a = 1))# Replace valuesstr(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 alonestr(list_modify(x, z = list(a = 1:5)))# Remove valuesstr(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 togetherwithvctrs::vec_c().list_rbind()combines elements into a data frame by row-binding themtogether withvctrs::vec_rbind().list_cbind()combines elements into a data frame by column-binding themtogether 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 alwaysthe same. |
name_repair | One of |
size | An optional integer size to ensure that every input has thesame 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, ..., is_node = NULL, 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. |
is_node | A predicate function that determines whether an element isa node (by returning |
name_spec | If both inner and outer names are present, controlhow they are combined. Should be a glue specification that usesvariables |
name_repair | One of |
Value
A list of the same type asx. The list might be shorterifx contains empty lists, the same length if it contains listsof length 1 or no sub-lists, or longer if it contains lists oflength > 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 islist(1, 2, 3, 4, 5) |> list_flatten() |> str()# Empty lists will disappearlist(1, list(), 2, list(3)) |> list_flatten() |> str()# Another way to see this is that it reduces the depth of the listx <- list( list(), list(list()))x |> pluck_depth()x |> list_flatten() |> pluck_depth()# Use name_spec to control how inner and outer names are combinedx <- 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()# Set `is_node = is.list` to also flatten richer objects built on lists like# data frames and linear modelsdf <- data.frame(x = 1:3, y = 4:6)x <- list( a_string = "something", a_list = list(1:3, "else"), a_df = df)x |> list_flatten(is_node = is.list)# Note that objects that are already "flat" retain their classeslist_flatten(df, is_node = is.list)Simplify a list to an atomic or S3 vector
Description
Simplification maintains a one-to-one correspondence between the inputand output, implying that each element ofx must contain a one elementvector or a one-row data frame. If you don't want to maintain thiscorrespondence, then you probably want eitherlist_c()/list_rbind() orlist_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 alwaysthe same. |
Value
A vector the same length asx.
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 oflists into a list of pairs, or a list of pairs into a pair of lists. Forexample, if you had a list of lengthn where each component had valuesaandb,list_transpose() would make a list with elementsa andb that contained lists of lengthn.
It's called transpose becausex[["a"]][["b"]] is equivalent tolist_transpose(x)[["b"]][["a"]], i.e. transposing a list flips the order ofindices 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 bea character vector (where elements are extracted by name), or an integervector (where elements are extracted by position). Defaults to the unionof the names of the elements of |
simplify | Should the result besimplified?
Alternatively, a named list specifying the simplification by outputelement. |
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 togethery |> list_transpose() |> str()# Supply a default result to further simplifyy |> 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 symmetricx |> 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 extractll <- 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 simplifyll |> list_transpose(template = c("x", "y", "z"), default = NA)Apply a function to list-elements of a list
Description
lmap(),lmap_at() andlmap_if() are similar tomap(),map_at() andmap_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
.fto access the attributes of the encapsulating list,likenames().It allows
.fto return a larger or small list than it receiveschanging 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 aboutthe 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# outputmaybe_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 xx |> lmap_at(c("a", "d"), maybe_rep) |> str()# Or only where a condition is satisfiedx |> 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 toeach element of a list or atomic vector and returning an object ofthe same length as the input.
map()always returns a list. See themodify()family forversions that return an object of the same type as the input.map_lgl(),map_int(),map_dbl()andmap_chr()return anatomic vector of the indicated type (or die trying). For these functions,.fmust return a length-1 vector of the appropriate type.map_vec()simplifies to the common type of the output. It works withmost types of simple vectors like Date, POSIXct, factors, etc.walk()calls.ffor its side-effect and returnsthe 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:
Wrap a function with |
... | Additional arguments passed on to the mapped function. We now generally recommend against using # Instead ofx |> map(f, 1, 2, collapse = ",")# do:x |> map(\(x) f(x, 1, 2, collapse = ",")) This makes it easier to understand which arguments belong to whichfunction 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 atomicvector of length 1._vec()return an atomic or S3 vector, the same type that.freturns..fcan return pretty much any type of vector, as long as its length 1.walk()returns the input.x(invisibly). This makes it easy touse in a pipe. The return value of.f()is ignored.
Any errors thrown by.f will be wrapped in an error with classpurrr_error_indexed.
See Also
map_if() for applying a function to only those elementsof.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 vector1:10 |> map(rnorm, n = 10)# You can also use an anonymous function1:10 |> map(\(x) rnorm(10, x))# Simplify output to a vector instead of a list by computing the mean of the distributions1: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 listsfavorite_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 NULLl1 <- 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 listl2 <- 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 existl2 |> 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^2mtcars |> split(mtcars$cyl) |> map(\(df) lm(mpg ~ wt, data = df)) |> map(summary) |> map_dbl("r.squared")# Run in interactive sessions only as spawns additional processes# To use parallelized map:# 1. Set daemons (number of parallel processes) first:mirai::daemons(2)# 2. Wrap .f with in_parallel():mtcars |> map_dbl(in_parallel(\(x) mean(x)))# Note that functions from packages should be fully qualified with `pkg::`# or call `library(pkg)` within the function1:10 |> map(in_parallel(\(x) vctrs::vec_init(integer(), x))) |> map_int(in_parallel(\(x) { library(vctrs); vec_size(x) }))# A locally-defined function (or any required variables)# should be passed via ... of in_parallel():slow_lm <- function(formula, data) { Sys.sleep(0.5) lm(formula, data)}mtcars |> split(mtcars$cyl) |> map(in_parallel(\(df) slow_lm(mpg ~ disp, data = df), slow_lm = slow_lm))# Tear down daemons when no longer in use:mirai::daemons(0)Map over two inputs
Description
These functions are variants ofmap() that iterate over two arguments ata 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 vectorof length 1 will be recycled to the length of the other. |
.f | A function, specified in one of the following ways:
Wrap a function with |
... | Additional arguments passed on to the mapped function. We now generally recommend against using # Instead ofx |> map(f, 1, 2, collapse = ",")# do:x |> map(\(x) f(x, 1, 2, collapse = ",")) This makes it easier to understand which arguments belong to whichfunction 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 atomicvector of length 1._vec()return an atomic or S3 vector, the same type that.freturns..fcan return pretty much any type of vector, as long as its length 1.walk()returns the input.x(invisibly). This makes it easy touse in a pipe. The return value of.f()is ignored.
Any errors thrown by.f will be wrapped in an error with classpurrr_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 justmap2(x, y, `+`)# Split into pieces, fit model to each piece, then predictby_cyl <- mtcars |> split(mtcars$cyl)mods <- by_cyl |> map(\(df) lm(mpg ~ wt, data = df))map2(mods, by_cyl, predict)Map/modify elements at given depth
Description
map_depth() callsmap(.y, .f) on all.y at the specified.depth in.x.modify_depth() callsmodify(.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:
Wrap a function with |
... | Additional arguments passed on to the mapped function. We now generally recommend against using # Instead ofx |> map(f, 1, 2, collapse = ",")# do:x |> map(\(x) f(x, 1, 2, collapse = ",")) This makes it easier to understand which arguments belong to whichfunction and will tend to yield better error messages. |
.ragged | If |
.is_node | A predicate function that determines whether an element isa node (by returning |
See Also
modify_tree() for a recursive version ofmodify_depth() thatallows 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
Thesemap(),map2(),imap(), andpmap() variants return dataframes by row-binding or column-binding the outputs together.
The functions were superseded in purrr 1.0.0 because their namessuggest they work like_lgl(),_int(), etc which require length1 outputs, but actually they return results of any size because the resultsare combined without any size checks. Additionally, they usedplyr::bind_rows() anddplyr::bind_cols() which require dplyr to beinstalled and have confusing semantics with edge cases. Supersededfunctions will not go away, but will only receive critical bug fixes.
Instead, we recommend usingmap(),map2(), etc withlist_rbind() andlist_cbind(). These usevctrs::vec_rbind() andvctrs::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 columndf <- 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 inputstry(map(df, trimws) |> list_rbind())# Instead, use modify() to apply a function to each column of a data framemodify(df, trimws)# map2 ---------------------------------------------ex_fun <- function(arg1, arg2){ col <- arg1 + arg2 x <- as.data.frame(col)}arg1 <- 1:4arg2 <- 10:13# wasmap2_dfr(arg1, arg2, ex_fun)# nowmap2(arg1, arg2, ex_fun) |> list_rbind()# wasmap2_dfc(arg1, arg2, ex_fun)# nowmap2(arg1, arg2, ex_fun) |> list_cbind()Apply a function to each element of a vector conditionally
Description
The functionsmap_if() andmap_at() take.x as input, applythe function.f to some of the elements of.x, and return alist of the same length as the input.
map_if()takes a predicate function.pas input to determinewhich elements of.xare transformed with.f.map_at()takes a vector of names or positions.atto specifywhich elements of.xare 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 apredicate function, or a logical vector of the same length as |
.f | A function, specified in one of the following ways:
Wrap a function with |
... | Additional arguments passed on to the mapped function. We now generally recommend against using # Instead ofx |> map(f, 1, 2, collapse = ",")# do:x |> map(\(x) f(x, 1, 2, collapse = ",")) This makes it easier to understand which arguments belong to whichfunction and will tend to yield better error messages. |
.else | A function applied to elements of |
.at | A logical, integer, or character vector giving the elementsto 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 limiteduse and you can now usemap_vec() instead. They are variants ofmap(),map2(),imap(),pmap(), andflatten() 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)Modify elements selectively
Description
Unlikemap() and its variants which always return a fixed objecttype (list formap(), integer vector formap_int(), etc), themodify() 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 ofxthat satisfy apredicate and leaves the others unchanged.modify_at()onlymodifies elements given by names or positions.modify2()modifies the elements of.xbut also passes theelements of.yto.f, just likemap2().imodify()passesthe names or the indices to.flikeimap()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'syour responsibility to ensure that the transformation produces avalid output. For example, if you're modifying a data frame,.fmust preserve the length of the input.
Value
An object the same class as.x
Genericity
modify() and variants are generic over classes that implementlength(),[[ and[[<- methods. If the default implementationis not compatible for your class, you can override them with yourown methods.
If you implement your ownmodify() method, make sure it satisfiesthe following invariants:
modify(x, identity) === xmodify(x, compose(f, g)) === modify(x, g) |> modify(f)
These invariants are known as thefunctor laws in computerscience.
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 charactersiris |> 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 apluck location,assigns a value there, and returns the modified data structure.modify_in()applies a function to a pluck location, assigns theresult back to that location withassign_in(), and returns themodified 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 ofpositions, 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 deepx <- 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 itstr(assign_in(x, list(1, "foo"), zap()))# Like pluck(), this works even when the element (or its parents) don't existpluck(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 functionsthat 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 isa 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 leafx |> modify_tree(leaf = \(x) x + 100) |> str()# Recursively sort the nodessort_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 changesTRUE toFALSE andFALSE toTRUE.
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 afunction (a verb). If you'd like to include a function created an adverbin a package, be sure to readfaq-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 asx |> discard(is.numeric)Partially apply a function, filling in some arguments
Description
Partial function application allows you to modify a function by pre-fillingsome of the arguments. It is particularly useful in conjunction withfunctionals and other function operators.
Usage
partial(.f, ...)Arguments
.f | a function. For the output source to read well, this should be anamed function. |
... | named arguments to Pass an empty These dots support quasiquotation. If you unquote a value, it isevaluated only once at function creation time. Otherwise, it isevaluated each time the function is called. |
Details
partial() creates a function that takes... arguments. Unlikecompose() and other function operators likenegate(), itdoesn't reuse the function signature of.f. This is becausepartial() explicitly supports NSE functions that usesubstitute() on their arguments. The only way to support those isto forward arguments through dots.
Other unsupported patterns:
It is not possible to call
partial()repeatedly on the sameargument to pre-fill it with a different expression.It is not possible to refer to other arguments in pre-filledargument.
Value
A function that takes the same arguments as.f, but returnsa different value, as described above.
Adverbs
This function is called an adverb because it modifies the effect of afunction (a verb). If you'd like to include a function created an adverbin a package, be sure to readfaq-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# evaluationmy_long_variable <- 1:10plot2 <- 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))ff()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))ff()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 indexdeeply and flexibly into data structures. (If you're looking for anequivalent of[, seekeep_at().)pluck() always succeeds, returning.default if the index you are trying to access does not exist or isNULL.(If you're looking for a variant that errors, trychuck().)
pluck<-() is the assignment equivalent, allowing you to modify an objectdeep within a nested data structure.
pluck_exists() tells you whether or not an object exists using thesame rules as pluck (i.e. aNULL element is equivalent to an absentelement).
Usage
pluck(.x, ..., .default = NULL)pluck(.x, ...) <- valuepluck_exists(.x, ...)Arguments
.x,x | A vector or environment |
... | A list of accessors for indexing into the object. Can bean 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 andpositions). If the object being indexed is an S4 object,accessing it by name will return the corresponding slot. Dynamic dots are supported. In particular, ifyour 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 integerpositions and string names, and also accepts arbitrary accessorfunctions, i.e. functions that take an object and return someinternal piece.
This is often more readable than a mix of operators and accessorsbecause it reads linearly and is free of syntacticcruft. Compare:
accessor(x[[1]])$footopluck(x, 1, accessor, "foo").These accessors never partial-match. This is unlike
$whichwill select thedispobject if you writemtcars$di.
See Also
attr_getter()for creating attribute getters suitable for usewithpluck()andchuck().modify_in()for applying a function to a plucked location.keep_at()is similar topluck(), but retain the structureof the list instead of converting it into a vector.
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 backpluck(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]]$eltpluck(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 calledvec_depth().
Usage
pluck_depth(x, is_node = NULL)Arguments
x | A vector |
is_node | Optionally override the default criteria for determine anelement 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 ofmap() that iterate over multiple argumentssimultaneously. They are parallel in the sense that each input is processedin parallel with the others, not in the sense of multicore computing, i.e.they share the same notion of "parallel" asbase::pmax() andbase::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 elementsmust be have the same length. A data frame is an important special case of |
.f | A function, specified in one of the following ways:
Wrap a function with |
... | Additional arguments passed on to the mapped function. We now generally recommend against using # Instead ofx |> map(f, 1, 2, collapse = ",")# do:x |> map(\(x) f(x, 1, 2, collapse = ",")) This makes it easier to understand which arguments belong to whichfunction 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 atomicvector of length 1._vec()return an atomic or S3 vector, the same type that.freturns..fcan return pretty much any type of vector, as long as it is length 1.pwalk()returns the input.l(invisibly). This makes it easy touse in a pipe. The return value of.f()is ignored.
Any errors thrown by.f will be wrapped in an error with classpurrr_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 positionpmap(list(x, y, z), function(first, second, third) (first + third) * second)# Matching arguments by namel <- list(a = x, b = y, c = z)pmap(l, function(c, b, a) (a + c) * b)# Vectorizing a function over multiple argumentsdf <- 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 .ldf <- data.frame( x = 1:3, y = 10:12, z = letters[1:3])plus <- function(x, y) x + y## Not run: # this won't workpmap(df, plus)## End(Not run)# but this willplus2 <- function(x, y, ...) x + ypmap_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 resultpmin(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 returnsa different value, as described above.
Adverbs
This function is called an adverb because it modifies the effect of afunction (a verb). If you'd like to include a function created an adverbin a package, be sure to readfaq-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 thecore purpose of purrr.
This is a companion toappend() to help merging twolists or atomic vectors.prepend() is a clearer semanticsignal thanc() that a vector is to be merged at the beginning ofanother, 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 tocreate 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 whatcalculation or process they belong to. We recommend keeping the namesunder 20 characters, so the whole progress bar fits comfortably even onon narrower displays.
Progress bar parameters
clear: whether to remove the progress bar from the screen aftertermination. Defaults toTRUE.format: format string. This overrides the default format string ofthe progress bar type. It must be given for thecustomtype.Format strings may contain R expressions to evaluate in braces.They support clipluralization, andstyling and they can contain specialprogress variables.format_done: format string for successful termination. By defaultthe 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 itis displayed at the beginning of the progress bar.show_after: numeric scalar. Only show the progress bar after thisnumber of seconds. It overrides thecli.progress_show_afterglobal 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,formatmust not beNULLfor this type.The default display is different for each progress bar type.
Further documentation
purrr's progress bars are powered by cli, so seeIntroduction to progress bars in cliandAdvanced cli progress barsfor more details.
Indexed errors (purrr_error_indexed)
Description
Thepurrr_error_indexed class is thrown bymap(),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,namewill beNULLparent: 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] 1cnd$name#> NULLprint(cnd$parent, backtrace = FALSE)#> <error/rlang_error>#> Error in `.f()`:#> ! This is an errorIf 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 inAdvanced R.)
This error chaining is really useful when doing interactive data analysis, but it adds some extra complexity when handling errors withtryCatch() orwithCallingHandlers().Let's see what happens by adding a custom class to the error thrown byf():
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 errorThis doesn't change the visual display, but you might be surprised if you try to catch this error withtryCatch() orwithCallingHandlers():
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 errorwithCallingHandlers( 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 errorThat's because, as described above, the error thatmap() throws will always have classpurrr_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 byf(), you'll need to userlang::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) } })#> NULLwithCallingHandlers( 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(ThetryCatch() 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() andtraceback(), but we don't currently have a better approach.In the future we expect toenhancetry_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 errorBecause we are resignalling an error, it's important to usewithCallingHandlers() and nottryCatch() in order to preserve the full backtrace context.That wayrecover(),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 withthe return value of the function and returns a list containingtheresult,output,messages andwarnings.
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 returnsa different value, as described above.
Adverbs
This function is called an adverb because it modifies the effect of afunction (a verb). If you'd like to include a function created an adverbin a package, be sure to readfaq-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())Create delaying rate settings
Description
These helpers create rate settings that you can pass toinsistently() andslowly(). You can also use them in your own functions withrate_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; generallyonly necessary if you need pauses less than one second (which maynot 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)Wait for a given time
Description
If the rate's internal counter exceeds the maximum number of timesit is allowed to sleep,rate_sleep() throws an error of classpurrr_error_rate_excess.
Usage
rate_sleep(rate, quiet = TRUE)rate_reset(rate)Arguments
rate | Arate object determining the waiting time. |
quiet | If |
Details
Callrate_reset() to reset the internal rate counter to 0.
See Also
Generate random sample from a Bernoulli distribution
Description
This function was deprecated in purrr 1.0.0 because it's not related to thecore 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 thecore 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 vectorinto a single value. The combination is driven by.f, a binaryfunction that takes two values and returns a single value: reducingf over1:3 computes the valuef(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 ofx |> 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 whichfunction and will tend to yield better error messages. |
.init | If supplied, will be used as the first value to startthe accumulation, rather than using |
.dir | The direction of reduction as a string, one of |
.y | For |
Direction
When.f is an associative operation like+ orc(), thedirection of reduction does not matter. For instance, reducing thevector1:3 with the binary function+ computes the sum((1 + 2) + 3) from the left, and the same sum(1 + (2 + 3)) from theright.
In other cases, the direction has important consequences on thereduced value. For instance, reducing a vector withlist() fromthe left produces a left-leaning nested list (or tree), whilereducinglist() from the right produces a right-leaning list.
See Also
accumulate() for a version that returns all intermediatevalues 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)Objects exported from other packages
Description
These objects are imported from other packages. Follow the linksbelow 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 NSEfunctions are not a good fit for purrr. Also,rerun(n, x) can just aseasily be expressed asmap(1:n, \(i) x)
rerun() is a convenient way of generating sample data. It works similarly toreplicate(..., 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 bere-run once for each.n.
There is one special case: if there's a single unnamed input, the secondlevel list will be dropped. In this case,rerun(n, x) behaves likereplicate(n, x, simplify = FALSE).
Examples
# old5 |> rerun(rnorm(5)) |> str()# new1:5 |> map(\(i) rnorm(5)) |> str()# old5 |> rerun(x = rnorm(5), y = rnorm(5)) |> map_dbl(\(l) cor(l$x, l$y))# new1: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 listwith componentsresult anderror. If the function succeeds,resultcontains the returned value anderror isNULL. If an error occurred,error is anerror object andresult is eitherNULL orotherwise.
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 returnsa different value, as described above.
Adverbs
This function is called an adverb because it modifies the effect of afunction (a verb). If you'd like to include a function created an adverbin a package, be sure to readfaq-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 givenamount 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 | Arate object. Defaults to a constant delay. |
quiet | Hide errors ( |
Value
A function that takes the same arguments as.f, but returnsa different value, as described above.
Adverbs
This function is called an adverb because it modifies the effect of afunction (a verb). If you'd like to include a function created an adverbin a package, be sure to readfaq-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 thatthis style of implicit/automatic splicing is a good idea; instead userlang::list2() +!!! orlist_flatten().
splice() splices all arguments into a list. Non-list objects and listswith 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 arg3splice(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 listsinto 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 valuesa andb,transpose() would make a list with elementsa andb that contained lists of length n. It's called transpose becausex[[1]][[2]] is equivalent totranspose(x)[[2]][[1]].
This function was superseded in purrr 1.0.0 becauselist_transpose()has a better name and can automatically simplify the output, as is commonlyneeded. Superseded functions will not go away, but will only receive criticalbug fixes.
Usage
transpose(.l, .names = NULL)Arguments
.l | A list of vectors to transpose. The first element is used as thetemplate; you'll get a warning if a subsequent element has a differentlength. |
.names | For efficiency, |
Value
A list with indexing transposed compared to.l.
transpose() is its own inverse, much like the transpose operation on amatrix. You can get back the original input by transposing it twice.
Examples
x <- map(1:5, \(i) list(x = runif(1), y = runif(5)))# wasx |> transpose() |> str()# nowx |> list_transpose(simplify = FALSE) |> str()# transpose() is useful in conjunction with safely() & quietly()x <- list("a", 1, 2)y <- x |> map(safely(log))# wasy |> transpose() |> str()# now:y |> list_transpose() |> str()# Previously, output simplification required a call to another functionx <- 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 simplificationx |> list_transpose()# Provide explicit component names to prevent loss of those that don't# appear in first componentll <- list( list(x = 1, y = "one"), list(z = "deux", x = 2))ll |> transpose()nms <- ll |> map(names) |> reduce(union)# wasll |> transpose(.names = nms)# nowll |> list_transpose(template = nms)# and can supply default valuell |> 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 believethat functions that use NSE are a good fit for purrr.
update_list() handles formulas and quosures that can refer tovalues existing within the input list. This function is deprecatedbecause we no longer believe that functions that use tidy evaluation area 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. Wheninputs are all named, they are matched to Dynamic dots are supported. In particular, if yourreplacement 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 actionassociated with the first valid match.
Description
This function was deprecated in purrr 1.0.0 because it's not related to thecore purpose of purrr. You can pull your code out of a pipe and use regularif/else statements instead.
when() is a flavour of pattern matching (or an if-else abstraction) inwhich a value is matched against a sequence of condition-action sets. When avalid match/condition is found the action is executed and the result of theaction 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 validmatch/condition is returned. If no matches are found, and no default isgiven, NULL will be returned.
Validity of the conditions are tested withisTRUE, or equivalentlywithidentical(condition, TRUE).In other words conditions resulting in more than one logical will neverbe valid. Note that the input value is always treated as a single object,as opposed to theifelse function.
Examples
1:10 |> when( sum(.) <= 50 ~ sum(.), sum(.) <= 100 ~ sum(.)/2, ~ 0 )# nowx <- 1:10if (sum(x) < 10) { sum(x)} else if (sum(x) < 100) { sum(x) / 2} else { 0}