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. When the input is a valid number, the wrapped function behaves exactly like the original; when the input would cause a crash, it silently returns the fallback you specified. This is the core idea behind possibly(): a function modifier that trades crashes for controlled defaults.
Signature
possibly(.f, otherwise = NULL, quiet = TRUE)
.f, the function to wrapotherwise, value to return on error (defaultNULL)quiet, ifTRUE(default), errors are suppressed; ifFALSE, errors print as they occur
Picking the right type for otherwise is essential because typed mapping functions like map_dbl() will fail if the fallback is not a double. When your data is numeric, use NA_real_; for character pipelines, use NA_character_; and for logical work, plain NA is the safe default.
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))
Each typed variant of map() expects a specific fallback type, and getting it wrong will itself cause an error inside the pipeline. When you set otherwise = NULL — the default — the behaviour shifts from substituting a value to silently removing failed elements from the output. This is convenient when you want to filter out problematic inputs rather than flagging them with a sentinel value.
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. If you set otherwise = NULL, failed elements are silently removed from the output when you collapse the list, which is convenient for best-effort data cleaning where you expect some proportion of inputs to fail. During development, however, you might want to see which specific inputs triggered the fallback — setting quiet = FALSE prints each error as it occurs so you can diagnose the data quality issues at their source.
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. The real power of possibly() emerges when you combine it with map() or group_modify() inside a data pipeline, where you want to compute a summary statistic for each group but some groups may be empty or contain values that break the calculation.
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:
| Feature | safely() | possibly() |
|---|---|---|
| Return on error | list with result and error | your chosen default |
| Typical use | when you need to inspect the error | when you just want a fallback |
| Output type | always a list | can 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. The pattern scales naturally to real-world tasks like batch API calls, where network timeouts and server errors are routine. Wrapping your HTTP request function with possibly() lets you process hundreds of URLs in a single map_chr() call, returning NA for any endpoint that fails to respond without halting the entire batch.
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
purrr::possibly() in practice
possibly() wraps a function with error handling, returning a default value whenever the original function raises an error. safe_log <- possibly(log, otherwise = NA_real_) creates a version of log() that returns NA instead of throwing an error when passed invalid input like negative numbers or NULL.
The otherwise argument specifies what to return on failure. It can be any value: NA, NULL, a list, or even a complex object. Choosing the right type matters: if you use possibly() inside map_dbl(), otherwise must be a scalar double, otherwise the typed map_* variant will fail. Use map() (which returns a list) when the otherwise value has a complex type.
possibly() is one of three purrr error-handling wrappers. safely() returns a list with $result and $error components, it preserves both the output and the error message. quietly() captures warnings and messages without errors. Use possibly() when you want silent recovery with a default value, safely() when you need to inspect what went wrong, and quietly() when warnings are expected but should not interrupt processing.
A typical batch-processing pattern: results <- map(inputs, possibly(process, otherwise = NULL)) followed by results <- compact(results) to drop the NULL failures. This silently skips failed inputs while processing the rest, which is appropriate for best-effort batch jobs where some inputs are expected to fail.
See also
- /reference/tidyverse/purrr-safely/, wraps a function to return a list with result and error
- /reference/tidyverse/purrr-map/, transform each element with
map() - /guides/purrr-functional-programming/ — functional programming patterns with purrr