Using dplyr::if_else with purrr

Viewed 618

I am trying to use purrr::map_dbl on a list where I want different behavior if the list element is NULL.

x <- list(NULL, c(1, 2), c(3, 4))
purrr::map_dbl(x, function(y) { dplyr::if_else(is.null(y), 0, y[1] + y[2]) })

This does not work as expected, instead giving the error:

Error: false must be length 1 (length of condition), not 0

Debugging the if_else call, I see that y[1] + y[2] is evaluated to integer(0). Why doesn't this work?

The following all work as I would expect:

> purrr::map_dbl(x, function(y) { dplyr::if_else(is.null(y), 0, y[1]) })
[1] 0 1 3
> purrr::map_dbl(x, function(y) { dplyr::if_else(is.null(y), 0, y[2]) })
[1] 0 2 4
> purrr::map_dbl(x, ~ dplyr::if_else(is.null(.x), 0, .x[1]))
[1] 0 1 3
> purrr::map_dbl(x, function(y) { base::ifelse(is.null(y), 0, y[1] + y[2]) })
[1] 0 3 7
> purrr::map_dbl(x, function(y) { if (is.null(y)) 0 else y[1] + y[2] })
[1] 0 3 7

What is different about the original call?

2 Answers

We can debug, it easily with browser()

purrr::map_dbl(x, function(y) {
        browser()
        dplyr::if_else(is.null(y), 0, y[1] + y[2]) 
 })
Called from: .f(.x[[i]], ...)
Browse[1]> 
debug at #1: dplyr::if_else(is.null(y), 0, y[1] + y[2])
Browse[2]> 
Error: `false` must be length 1 (length of `condition`), not 0
Call `rlang::last_error()` to see a backtrace

So, here the length is the issue.

According to?if_else, requires all arguments to have the same length

Values to use for TRUE and FALSE values of condition. They must be either the same length as condition, or length 1. They must also be the same type: if_else() checks that they have the same type and same class. All other attributes are taken from true.


To dig further into the issue, it still works if the value is not NULL

v1 <- 1
if_else(v1==1, 0, v1[1] + v1[2])
#[1] 0

But, as soon as we change it to NA or NULL, it becomes an issue, could be due to the type

@CBraun made an interesting observation

NULL[1] + NULL[2]
#integer(0)

returns length 0,

if_else(is.na(v1), 0, integer(0))

Error: false must be length 1 (length of condition), not 0 Call rlang::last_error() to see a backtrace

However,

NA + NA #[1] NA

is of length 1, but still returns error

v1 <- NA
if_else(is.na(v1), 0, v1[1] + v1[2])

Error: false must be a double vector, not an integer vector Call rlang::last_error() to see a backtrace

If we use the correct NA dispatched, it works

v1 <- NA_real_
if_else(is.na(v1), 0, v1[1] + v1[2])
#[1] 0

Notice that here it is the type issue. All in all, as mentioned in the documentation, length and type should match for if_else

Bottomline: When the value is NULL, the behavior is strange because of the output of + is integer(0) of length 0


It is a case where we can use if/else instead of if_else

purrr::map_dbl(x, ~ if(is.null(.x)) 0 else sum(.x))
#[1] 0 3 7 

In that respect, use the sum instead of calling the arguments separately y[[1]], y[[2]] as this cause imbalance in the length

purrr::map_dbl(x, ~ ifelse(is.null(.x), 0, sum(.x)))
#[1] 0 3 7

Note that ifelse also requires the lengths to be same, though it works here due to recycling of values

A vector of the same length and attributes (including dimensions and "class") as test and data values from the values of yes or no.

purrr::map_dbl(x, ~ ifelse(is.null(.x), 0, .x[[1]] + .x[[2]]))
#[1] 0 3 7

NOTE: All the methods are used to check the OP's condition. But, if the objective is to get the result, there are other ways.

An alternative is to use the na.rm argument in sum to ignore NA or NULL values when adding values together. This way we can skip the if else logic:

purrr::map_dbl(x, sum, na.rm = TRUE) 
# [1] 0 3 7

Here is the Base R equivalent (as pointed out by akrun):

sapply(x, sum, na.rm = TRUE)
Related