rguides

purrr::possibly

possibly() wraps a function so it returns a default value instead of crashing when something goes wrong. It’s purrr’s adverb for handling errors without full try-catch machinery.

Basic Usage

library(purrr)

# log("a") fails — log of a string is not numeric
list("a", 10, 100) |>
  map_dbl(possibly(log, NA_real_))
# [1] NA 2.302585 4.605170

possibly(log, NA_real_) creates a version of log that returns NA_real_ instead of throwing an error.

Signature

possibly(.f, otherwise = NULL, quiet = TRUE)
  • .f — the function to wrap
  • otherwise — value to return on error (default NULL)
  • quiet — if TRUE (default), errors are suppressed; if FALSE, errors print as they occur

Choosing an Appropriate Default

The otherwise value should match the type of what you’re working with:

# Numeric data — use NA_real_
x |> map_dbl(possibly(mean, NA_real_))

# Character data — use NA_character_
names |> map_chr(possibly(toupper, NA_character_))

# Logical — use NA
flags |> map_lgl(possibly(isTRUE, NA))

If otherwise is NULL (the default), failed elements are dropped from the output:

list("a", 10, 100) |>
  map(possibly(log)) |>
  list_c()
# [1] 2.302585 4.605170

The string "a" is gone — list_c() only sees the successful results.

Quiet vs Noisy

By default, quiet = TRUE hides errors silently. Set quiet = FALSE to see them as they happen:

x <- list(1, "b", 3)

possibly(log, NA_real_, quiet = FALSE)(1)
# [1] 0

possibly(log, NA_real_, quiet = FALSE)("b")
# Error in log("b") : non-numeric argument to binary operator
# [1] NA

Useful during development or when you want a log of what failed.

Combining with map

possibly() shines inside map:

library(dplyr)

# Get row means for each group, but some groups are empty
mtcars |>
  group_by(cyl) |>
  group_modify(~ {
    result <- possibly(mean, NA_real_)(.$mpg)
    tibble(mean_mpg = result)
  })

possibly vs safely

These are counterparts:

Featuresafely()possibly()
Return on errorlist with result and erroryour chosen default
Typical usewhen you need to inspect the errorwhen you just want a fallback
Output typealways a listcan be simplified with list_c(), map_dbl(), etc.
# safely — gives you both result and error
safe_log <- safely(log)
safe_log("a")
# $result
# NULL
# $error
# <simpleError ...>

# possibly — gives you a fallback
possibly(log, NA_real_)("a")
# [1] NA

Use possibly() when you don’t care why something failed — you just want a sensible value instead.

Real Example: API Calls

library(httr)

fetch_page <- possibly(\(url) {
  GET(url) |>
    content(as = "text")
}, otherwise = NA_character_)

urls <- c(
  "https://api.example.com/data/1",
  "https://api.example.com/data/2",
  "https://api.example.com/data/3"
)

pages <- urls |>
  map_chr(fetch_page)

# NA for failed requests, clean text for successful ones

See Also