Functional Programming Tools

A complete and consistent functional programming toolkit for R.

Purrr makes your pure functions purr by completing R's functional programming tools with important features from other languages, in the style of the JS packages underscore.js, lodash and lazy.js.

Get the released version from CRAN:


Or the development version from github with:

# install.packages("devtools")

The following example uses purrr to solve a fairly realistic problem: split a data frame into pieces, fit a model to each piece, summarise and extract R^2.

mtcars %>%
  split(.$cyl) %>%
  map(~ lm(mpg ~ wt, data = .)) %>%
  map(summary) %>%

Note the three types of input to map(): a function, a formula (converted to an anonymous function), or a string (used to extract named components).

The following more complicated example shows how you might generate 100 random test-training splits, fit a model to each training split then evaluate based on the test split:

random_group <- function(n, probs) {
  probs <- probs / sum(probs)
  g <- findInterval(seq(0, 1, length = n), c(0, cumsum(probs)),
    rightmost.closed = TRUE)
partition <- function(df, n, probs) {
  replicate(n, split(df, random_group(nrow(df), probs)), FALSE) %>%
    transpose() %>%
msd <- function(x, y) sqrt(mean((x - y) ^ 2))
# Generate 100 random test-training splits
boot <- partition(mtcars, 100, c(training = 0.8, test = 0.2))
boot <- boot %>% mutate(
  # Fit the models
  models = map(training, ~ lm(mpg ~ wt, data = .)),
  # Make predictions on test data
  preds = map2(models, test, predict),
  diffs = map2(preds, test %>% map("mpg"), msd)
# Evaluate mean-squared difference between predicted and actual
  • Apply a function to each element: map() returns a list; map_lgl()/map_int()/map_dbl()/map_chr() return a vector; walk() invisibly returns original list, calling the function for its side effects; map2() and pmap() vectorise over multiple inputs; at_depth() maps a function at a specified level of nested lists.

  • Apply a function conditionally with map_if() (where a predicate returns TRUE) and map_at() (at specific locations).

  • Apply a function to slices of a data frame with by_slice(), or to each row with by_row() or map_rows().

  • Apply a function to list-elements of a list with lmap(), lmap_if() and lmap_at(). Compared to traditional mapping, the function is applied to x[i] instead of x[[i]], preserving the surrounding list and attributes.

  • Reduce a list to a single value by iteratively applying a binary function: reduce() and reduce_right().

  • Figure out if a list contains an object: contains().

  • Order, sort and split a list based on its components with split_by(), order_by() and sort_by().

  • Transpose a list with transpose().

  • Create the cartesian product of the elements of several lists with cross_n() and cross_d().

  • Flatten a list with flatten().

  • Splice lists and other objects with splice().

(A predicate function is a function that either returns TRUE or FALSE)

  • keep() or discard() elements that satisfy the predicate..

  • Does every() element or some() elements satisfy the predicate?

  • Find the value (detect()) and index (detect_index()) of the first element that satisfies the predicate.

  • Find the head/tail that satisfies a predicate: head_while(), tail_while().

  • invoke() every function in a list with given arguments and returns a list, invoke_lgl()/invoke_int()/invoke_dbl()/invoke_chr() returns vectors.
  • Fill in function arguments with partial().

  • Change the way your function takes input with lift() and the lift_xy() family of composition helpers.

  • Compose multiple functions into a single function with compose().

  • Negate a predicate funtion with negate().

  • Convert an array or matrix to a list with array_tree() and array_branch().

  • Convert a list to a vector with as_vector().

The goal is not to try and simulate Haskell in R: purrr does not implement currying or destructuring binds or pattern matching. The goal is to give you similar expressiveness to an FP language, while allowing you to write code that looks and works like R.

  • Instead of point free style, use the pipe, %>%, to write code that can be read from left to right.

  • Instead of currying, we use ... to pass in extra arguments.

  • Anonymous functions are verbose in R, so we provide two convenient shorthands. For unary functions, ~ .x + 1 is equivalent to function(.x) .x + 1. For chains of transformations functions, . %>% f() %>% g() is equivalent to function(.) . %>% f() %>% g().

  • R is weakly typed, we need variants map_int(), map_dbl(), etc since we don't know what .f will return.

  • R has named arguments, so instead of providing different functions for minor variations (e.g. detect() and detectLast()) I use a named argument, .first. Type-stable functions are easy to reason about so additional arguments will never change the type of the output.

  • rlist, another R package to support working with lists. Similar goals but somewhat different philosophy.

  • List operations defined in the Haskell prelude

  • Scala's list methods.


purrr 0.2.2

  • Fix for dev tibble support.

  • as_function() now supports list arguments which allow recursive indexing using either names or positions. They now always stop when encountering the first NULL (#173).

  • accumulate and reduce correctly pass extra arguments to the worker function.

purrr 0.2.1

  • as_function() gains a .null argument that for character and numeric values allows you to specify what to return for null/absent elements (#110). This can be used with any map function, e.g. map_int(x, 1, .null = NA)

  • as_function() is now generic.

  • New is_function() that returns TRUE only for regular functions.

  • Fix crash on GCC triggered by invoke_rows().

purrr 0.2.0

  • There are two handy infix functions:

    • x %||% y is shorthand for if (is.null(x)) y else x (#109).
    • x %@% "a" is shorthand for attr(x, "a", exact = TRUE) (#69).
  • accumulate() has been added to handle recursive folding. It is shortand for Reduce(f, .x, accumulate = TRUE) and follows a similar syntax to reduce() (#145). A right-hand version accumulate_right() was also added.

  • map_df() row-binds output together. It's the equivalent of plyr::ldply() (#127)

  • flatten() is now type-stable and always returns a list. To return a simpler vector, use flatten_lgl(), flatten_int(), flatten_dbl(), flatten_chr(), or flatten_df().

  • invoke() has been overhauled to be more useful: it now works similarly to map_call() when .x is NULL, and hence map_call() has been deprecated. invoke_map() is a vectorised complement to invoke() (#125), and comes with typed variants invoke_map_lgl(), invoke_map_int(), invoke_map_dbl(), invoke_map_chr(), and invoke_map_df().

  • transpose() replaces zip2(), zip3(), and zip_n() (#128). The name more clearly reflects the intent (transposing the first and second levels of list). It no longer has fields argument or the .simplify argument; instead use the new simplify_all() function.

  • safely(), quietly(), and possibly() are experimental functions for working with functions with side-effects (e.g. printed output, messages, warnings, and errors) (#120). safely() is a version of try() that modifies a function (rather than an expression), and always returns a list with two components, result and error.

  • list_along() and rep_along() generalise the idea of seq_along(). (#122).

  • is_null() is the snake-case version of is.null().

  • pmap() (parallel map) replaces map_n() (#132), and has typed-variants suffixed pmap_lgl(), pmap_int(), pmap_dbl(), pmap_chr(), and pmap_df().

  • set_names() is a snake-case alternative to setNames() with stricter equality checking, and more convenient defaults for pipes: x %>% set_names() is equivalent to setNames(x, x) (#119).

We are still figuring out what belongs in dplyr and what belongs in purrr. Expect much experimentation and many changes with these functions.

  • map() now always returns a list. Data frame support has been moved to map_df() and dmap(). The latter supports sliced data frames as a shortcut for the combination of by_slice() and dmap(): x %>% by_slice(dmap, fun, .collate = "rows"). The conditional variants dmap_at() and dmap_if() also support sliced data frames and will recycle scalar results to the slice size.

  • map_rows() has been renamed to invoke_rows(). As other rows-based functionals, it collates results inside lists by default, but with column collation this function is equivalent to plyr::mdply().

  • The rows-based functionals gain a .to option to name the output column as well as a .collate argument. The latter allows to collate the output in lists (by default), on columns or on rows. This makes these functions more flexible and more predictable.

  • as_function(), which converts formulas etc to functions, is now exported (#123).

  • rerun() is correctly scoped (#95)

  • update_list() can now modify an element called x (#98).

  • map*() now use custom C code, rather than relying on lapply(), mapply() etc. The performance characteristcs are very similar, but it allows us greater control over the output (#118).

  • map_lgl() now has second argument .f, not .p (#134).

  • flatmap() -> use map() followed by the appropriate flatten().

  • map_call() -> invoke().

  • map_n() -> pmap(); walk_n() -> pwalk().

  • map3(x, y, z) -> map_n(list(x, y, z)); walk3(x, y, z) ->pwalk(list(x, y, z))`

Reference manual

It appears you don't have a PDF plugin for this browser. You can click here to download the reference manual.


0.2.4 by Lionel Henry, a month ago,

Report a bug at

Browse source code at

Authors: Lionel Henry [aut, cre], Hadley Wickham [aut], RStudio [cph, fnd]

Documentation:   PDF Manual  

GPL-3 | file LICENSE license

Imports magrittr, rlang, tibble

Suggests covr, dplyr, knitr, rmarkdown, testthat

Imported by BradleyTerryScalable, DiagrammeR, EventStudy, GSODR, HURDAT, ImputeRobust, LAGOSNE, PKPDmisc, RSQLServer, RevEcoR, SCORPIUS, ShinyTester, WRTDStidal, ahnr, alphavantager, anomalyDetection, apa, ari, atlantistools, automagic, available, banR, blscrapeR, breathtestcore, breathteststan, bsplus, bupaR, cdcfluview, censys, cepR, childsds, civis, colorednoise, comtradr, congressbr, corrr, countyweather, cymruservices, cytominer, datadogr, dbplyr, desctable, detrendr, dexter, diceR, docxtractr, edeaR, eesim, epidata, epitable, esc, estatapi, evaluator, exampletestr, exifr, fbar, fcuk, fingertipsR, flextable, ftDK, fuzzr, fuzzyjoin, gdns, geoparser, ggeffects, gghighlight, gglogo, ggmosaic, ggpubr, ggstance, giphyr, gitlabr, googleAnalyticsR, googleLanguageR, googledrive, googlesheets, gutenbergr, highcharter, hrbrthemes, hurricaneexposure, icpsrdata, imager, incgraph, inferr, influxdbr, ipumsr, jpmesh, kntnr, kokudosuuchi, livechatR, longurl, mathpix, memery, modelr, modeval, mschart, nandb, naniar, nesRdata, normalr, officer, olsrr, pcr, perccalc, pewdata, phylopath, pinnacle.API, plotly, pointblank, pollen, pollstR, postlightmercury, prcr, processmapR, proustr, psycho, purrrlyr, qiitr, quokar, rdrop2, readOffice, recipes, rmapzen, rmsfuns, roadoi, rsample, rtypeform, scanstatistics, sergeant, shiny.semantic, sjPlot, sjlabelled, sjmisc, sjstats, snakecase, sperrorest, splashr, spotifyr, spup, starmie, sugrrants, surveydata, survminer, survutils, tatoo, tensorr, tibbletime, tidyRSS, tidyboot, tidycensus, tidygenomics, tidyposterior, tidyquant, tidyr, tidyselect, tidystats, tidytext, tidyverse, timetk, tmuxr, twilio, uaparserjs, ukbtools, unpivotr, uptasticsearch, useful, valaddin, vdiffr, veccompare, visdat, wand, webTRISr, widgetframe, widyr, xesreadR, xpose, zeligverse.

Depended on by simglm.

Suggested by broom, countytimezones, d3r, dodgr, edgarWebR, eechidna, fourierin, jpndistrict, leaflet, leaflet.esri, leaflet.extras, quanteda, rAltmetric, rcongresso, repurrrsive, rnoaa, rtdists, sweep, valr, zeallot.

See at CRAN