rguides

keep() / discard() / compact()

keep(.x, .p, ...)
discard(.x, .p, ...)
compact(.x, ...)

These functions filter elements in a list or vector based on a predicate function. keep() retains elements where the predicate returns TRUE, discard() does the opposite, and compact() removes NULL, empty strings, and zero-length elements.

Syntax

keep(.x, .p, ...)
discard(.x, .p, ...)
compact(.x, ...)

Parameters

ParameterTypeDefaultDescription
.xlist or atomic,A vector or list to filter
.pfunction,A predicate function that returns TRUE/FALSE
...additional arguments,Additional arguments passed to .p

For compact(), .x is the only required parameter.

Examples

The following examples walk through the three functions in order, starting with keep() for basic filtering, then discard() for removing what you do not want, and finally compact() for the common special case of stripping out NULL elements from a list.

Basic usage with keep()

library(purrr)

# Keep even numbers
x <- list(1, 2, 3, 4, 5, 6)
keep(x, ~ .x %% 2 == 0)
# [[1]]
# [1] 2
# [[2]]
# [1] 4
# [[3]]
# [1] 6

Where keep() retains matching elements, discard() does the inverse: it drops every element for which the predicate returns TRUE. This is cleaner than writing a negated predicate with keep() when your condition naturally expresses what to remove instead of what to preserve.

Using discard() to remove elements

# Remove NULL and NA values
y <- list(1, NULL, 3, NA, 5)
discard(y, is.null)
# [[1]]
# [1] 1
# [[2]]
# [1] 3
# [[3]]
# [1] NA
# [[4]]
# [1] 5

# Discard empty strings
z <- list("apple", "", "banana", "cherry", "")
discard(z, ~ .x == "")
# [[1]]
# [1] "apple"
# [[2]]
# [1] "banana"
# [[3]]
# [1] "cherry"

For the specific case of removing NULL and empty elements, compact() provides a simpler interface than discard(). It requires no predicate because the filtering rule is fixed: drop anything that is NULL, an empty string, a zero-length vector, or FALSE. This covers the most common cleanup scenario in a single call.

Using compact() for quick cleanup

# Remove NULL and empty elements
data <- list(name = "Alice", email = NULL, age = 30, notes = "")
compact(data)
# $name
# [1] "Alice"
# $age
# [1] 30

# Compact works with all empty types
mixed <- list(1, NULL, "", character(0), NA, "hello")
compact(mixed)
# [[1]]
# [1] 1
# [[2]]
# [1] "hello"

The core functions combine naturally for more complex filtering tasks. The patterns below show how to chain type-based removal, numeric filtering, and data cleaning into reusable workflows that handle real-world data with inconsistent types and missing values quickly and effectively.

Common patterns

Conditional element removal

# Remove elements based on type
mixed_types <- list(1, "a", 2, "b", NULL, 3)
discard(mixed_types, is.null)
# [[1]]
# [1] 1
# [[2]]
# [1] "a"
# ...

# Keep only numeric values
keep(mixed_types, is.numeric)
# [[1]]
# [1] 1
# [[2]]
# [1] 2
# [[3]]
# [1] 3

When filtering alone is not enough and you need to reshape the data as well, the functions compose with dplyr verbs. The pipeline below demonstrates compacting rows that contain missing values by first treating each row as a list and then stripping out any that are incomplete.

Data cleaning pipelines

library(dplyr)

# Clean up a data frame with some missing values
df <- data.frame(
  name = c("Alice", NA, "Bob", "Charlie"),
  value = c(10, 20, NA, 40)
)

# Compact rows (remove rows with any NA)
df %>% pmap(~ c(...)) %>% compact() %>% matrix(ncol = 2, byrow = TRUE) %>% as.data.frame()

The sections below summarise key patterns that experienced R users reach for when working with lists and data frames in production code. They cover predicate forms, complementary function pairs, and the relationship between compact() and the more general discard().

keep() and discard() in practice

keep() and discard() filter a list or vector based on a predicate function. keep(x, is.numeric) returns only the numeric elements; discard(x, is.null) removes NULL elements. They are the tidyverse equivalents of Filter() from base R, with a cleaner interface.

The predicate .p can be a function, a formula (lambda), or a logical vector. keep(x, ~ . > 0) keeps positive elements. keep(df, is.numeric) applied to a data frame keeps only the numeric columns, since a data frame is a list of columns. discard(results, is.null) is equivalent to compact(results), which removes all NULL elements.

compact() is a special case of discard() that removes NULL and FALSE values. It is the most common use of this filtering pattern: after map(inputs, possibly(f, NULL)), compact(results) collects only the successful results.

keep() and discard() are complementary: keep(x, .p) and discard(x, .p) partition x into two sets. For collecting both partitions at once, the pattern is: passing <- keep(x, .p); failing <- discard(x, .p). If you need both, running the predicate twice is acceptable for small inputs; for large inputs, consider partition() from the furrr package for parallel evaluation.

keep() filters a list or vector to elements where the predicate returns TRUE. The predicate can be a function, a formula (~ .x > 0), or a character string (which extracts named sub-elements and tests truthiness). For named lists, keep(x, ~ !is.null(.x)) removes NULL elements. keep_at() and discard_at() select by name or index instead of by predicate.

See also

  • map(), transform each element with map()
  • reduce(), collapse a list to a single value
  • walk(), call a function for its side effects