rguides

negate

negate() is in the purrr package, not base R. It flips a predicate function, what returned TRUE starts returning FALSE, and vice versa. It’s an adverb in the purrr sense: a function that modifies another function rather than operating on data directly.

Basic usage

The core pattern is simple: wrap a predicate and get its opposite.

library(purrr)

x <- list(a = 1:5, b = letters, c = seq(1, 10, 2))

# Keep numeric elements
x |> keep(is.numeric) |> names()
# [1] "a" "c"

# Keep non-numeric elements (flip the predicate)
x |> keep(negate(is.numeric)) |> names()
# [1] "b"

negate(is.numeric) produces exactly the same result as discard(is.numeric), because discard() itself is defined as keep() composed with negate(). Understanding this equivalence helps you decide which form to use: negate() when you want to store the inverted predicate for reuse, and discard() when you are writing a single pipeline step where the inversion is the entire point:

x |> discard(is.numeric) |> names()
# [1] "b"

How negate works

Internally, negate() takes a predicate function and returns a new function that wraps it. The wrapper calls the original predicate and then flips the result with !. This means the returned function has the same signature as the original—it accepts the same arguments and only differs in what truth value it produces:

# negate creates a new function
is_non_numeric <- negate(is.numeric)

is_non_numeric(1:5)
# [1] FALSE

is_non_numeric(letters)
# [1] TRUE

The negation applies to the logical output of the predicate, not to individual elements inside it. When you write negate(all_positive), the wrapper evaluates all_positive(x) first and then negates the single TRUE or FALSE result. This is different from negating each element, which you would do with !all_positive(x) applied to a vector of individual values:

# All elements are positive? Yes.
all(c(1, 2, 3) > 0)
# [1] TRUE

# negate: flip the result
negate(\(x) all(x > 0))
# function(x) all(x > 0) with logic flipped

To verify the negated predicate works as expected, call it directly on a test vector. The output is the logical complement of what the original predicate would return for the same input. You can store the negated function in a variable and reuse it anywhere a predicate is expected:

pred <- negate(\(x) all(x > 0))
pred(c(1, 2, 3))
# [1] FALSE

Specifying the predicate

When you use negate(), the predicate you pass inside can take several forms. The most common approaches are named function references (for existing predicates), tilde-formula anonymous functions (the older purrr convention), and the modern \(x) lambda syntax introduced in R 4.1.0. Each form works identically with negate():

Named function:

x |> keep(is.character) |> names()
x |> keep(negate(is.character)) |> names()

Passing a named function reference is the most common pattern. It works with any predicate that already has a name, including base R functions like is.numeric(), is.na(), and is.null(), as well as functions from other packages or your own codebase.

Anonymous function with ~ formula (older style):

x |> keep(~ all(.x > 0)) |> names()

The tilde-formula syntax creates an anonymous predicate that refers to its argument as .x. This style predates the \(x) lambda syntax and is still common in older purrr code, though it is less readable than the newer approach for complex conditions.

Purrr-style anonymous function with \(x):

x |> keep(\(x) length(x) > 3) |> names()
x |> keep(negate(\(x) length(x) > 3)) |> names()

The \(x) lambda syntax, introduced in R 4.1.0, is the modern way to write inline predicates. It reads more naturally than the formula style and integrates smoothly with negate(): wrap the lambda and you get the opposite predicate without rewriting the condition logic.

Using negate with base R functions

Base R provides a rich set of predicate functions that work seamlessly with negate(). Functions like is.numeric(), is.character(), is.na(), and is.null() are predicates by design—they accept a single argument and return a logical vector. Wrapping any of these with negate() instantly gives you the inverse check without writing a wrapper:

x <- list(x = 1:10, y = rbernoulli(10), z = letters)

# Keep elements that are NOT numeric
x |> keep(negate(is.numeric)) |> names()
# [1] "y" "z"

# Keep elements that do NOT have length > 5
x |> keep(negate(\(x) length(x) > 5)) |> names()

When negate helps

Making code readable

Negating a condition can be clearer than embedding a negation in the predicate. Consider the difference between inverting the output of a predicate function versus inverting its internal logic. The negate() approach separates the concern of “what condition to test” from “whether to invert the answer,” which makes each part easier to understand and test independently:

# A double-negative is harder to parse
x |> keep(\(x) !all(is.na(x)))

# Negate the predicate itself — reads as "not all missing"
x |> keep(negate(\(x) all(is.na(x))))

The second form reads naturally: “keep elements where it is NOT true that all values are missing.” Storing the negated predicate in a named variable—for example, contains_missing <- negate(all_present)—makes this pattern even clearer, because the variable name documents the intent.

Composing with other predicates

library(purrr)

x <- list(a = 1:10, b = -5:5, c = 50:60)

# Keep elements that have any non-positive values
x |> keep(\(e) any(e <= 0) && length(e) > 3) |> names()
# [1] "b"

# Equivalent — "elements that are not all positive"
x |> keep(\(e) !all(e > 0))

When to use negate vs discard

negate() is most useful when you already have a function reference (like is.numeric or a named predicate from your own code) and want to invert it without rewriting it as a lambda. For inline predicates written with \(x), adding ! directly inside the body (\(x) !condition) is often just as clear. The key advantage of negate() is that it lets you store the inverted function in a variable, give it a descriptive name, or pass it as an argument to higher-order functions, the inverted predicate is itself a first-class function that can be reused anywhere a predicate is expected.

Negate() takes a predicate function and returns its logical complement, a new function that returns TRUE where the original returns FALSE. It is the base R equivalent of purrr::negate(). A common pattern is defining not_na <- Negate(is.na) and then using it in Filter(not_na, x) or sapply(list, not_na) to find non-NA elements. This is cleaner than writing an anonymous wrapper.

See also