rguides

purrr::discard

discard() removes elements from a list or vector where your predicate returns TRUE. It’s the mirror image of keep(), where keep() retains matches, discard() drops them.

Basic usage

library(purrr)

x <- list(a = 1:3, b = "hello", c = 4:6, d = "world")

discard(x, is.numeric)
#> $b
#> [1] "hello"
#>
#> $d
#> [1] "world"

The opposite operation, keep(), retains elements where the predicate returns TRUE rather than dropping them. Both functions accept the same predicate types and return the same structure as the input. Choosing between them is a matter of which reads more naturally for the condition you are expressing.

keep(x, is.numeric)
#> $a
#> [1] 1 2 3
#>
#> $c
#> [1] 4 5 6

The .p predicate argument accepts several forms beyond a plain function reference. You can pass an anonymous function for inline logic, a formula shorthand for conciseness, or a character string that extracts and tests a named sub-element from each list item.

Predicate syntax

The .p argument works the same way as in keep():

Named function:

discard(x, is.character)

When the predicate needs more than a simple type check, an anonymous function gives you full control over the condition without defining a separate named function. This is the most flexible predicate form and works with any logical expression you can write in R:

Anonymous function:

discard(x, \(elem) length(elem) > 2)

For lists of named sub-lists, the string shorthand is a compact alternative. Instead of writing a predicate function, you pass the name of a sub-element to test for truthiness, which reduces boilerplate when working with nested data structures:

String shorthand for lists, drops elements where the named sub-element is falsy:

z <- list(
  list(name = "alice", active = TRUE),
  list(name = "bob", active = FALSE),
  list(name = "carol", active = TRUE)
)

discard(z, "active")
#> [[1]]
#> $name
#> [1] "bob"

When your predicate needs extra parameters beyond the element itself, use the ... argument of discard(). Any additional arguments you supply are forwarded directly to the predicate function on each call, which avoids creating a wrapper function just to pass a threshold or reference value.

Additional arguments

Pass extra arguments to your predicate with ...:

nums <- list(a = c(1, 2, 3), b = c(4, 5), c = c(6, 7, 8, 9))

discard(nums, \(x) mean(x) > 4)
#> $a
#> [1] 1 2 3

Since discard() returns the same type it receives, it slots naturally into a dplyr pipeline. You can split a data frame by a grouping variable, filter out groups that do not meet a size threshold, and then apply further transformations without ever leaving the pipe chain.

Pipe-Friendly chaining

library(dplyr)

mtcars %>%
  split(.$cyl) %>%
  discard(\(df) nrow(df) <= 10) %>%
  map(dim)

Relationship to keep() and compact()

FunctionKeeps elements where predicate…Removes elements where predicate…
keep()returns TRUEreturns FALSE
discard()returns FALSEreturns TRUE
compact(),is empty or NULL

purrr::discard() in practice

discard() removes elements from a list or vector where the predicate returns TRUE. It is the complement of keep(): discard(x, is.null) removes all NULL elements, while keep(x, Negate(is.null)) does the same. Choose whichever reads more naturally for the condition being expressed.

A common pattern is error filtering after safely(): map(inputs, safely(f)) |> map("error") |> discard(is.null) collects only the errors from a batch operation. Combined with keep(): successes <- map(inputs, safely(f)) |> map("result") |> discard(is.null).

discard() with a formula predicate: discard(x, ~ length(.) == 0) removes empty vectors from a list. discard(df, ~ all(is.na(.))) removes columns from a data frame that are entirely NA. These column-filtering operations on data frames are idiomatic.

Filter() from base R is the equivalent without the tidyverse: Filter(Negate(is.null), x). For simple cases, x[!vapply(x, is.null, logical(1))] also works. Use discard() when you want consistency with the rest of a functional pipeline.

discard() is the complement of keep(), it removes elements where the predicate returns TRUE. Both accept a function, a formula shortcut (~ .x > 0), or a string name for extracting named elements. When applied to a list of data frames, discard(list, ~ nrow(.x) == 0) removes empty frames. discard_at() targets elements by name or position rather than predicate.

See also