Richard J. Acton
Richard J. Acton

Reputation: 915

Nested `if_else()` and `is.na()` logic inconsistency?

I'm trying to use mutate and if_else() to get the result of following logical statement applied to two columns of a data frame:

True if Yes in a or b, NA if NA in both, FALSE if both No or No & NA

library(magrittr)
library(dplyr)

data.frame(
    "a"=c(NA,"No","Yes","Yes","No","No",NA),
    "b"=c(NA,"No","Yes","No","Yes",NA,"No")
) %>% 
mutate(
    logical = if_else(
        a == "Yes" | b == "Yes",
        TRUE,
        if_else(
            is.na(a) & is.na(b),
            NA,
            FALSE
        )
    )
)
#>      a    b logical
#> 1 <NA> <NA>      NA
#> 2   No   No   FALSE
#> 3  Yes  Yes    TRUE
#> 4  Yes   No    TRUE
#> 5   No  Yes    TRUE
#> 6   No <NA>      NA
#> 7 <NA>   No      NA

In the last two rows I get NA not the expected result FALSE. Expected because is.na(a) & is.na(b) should be returning FALSE, as they appear to in the example below.

# False as expected here
if_else(is.na(NA) & is.na("No"),NA,FALSE)
#> [1] FALSE

Am I missing something about the way if_else works?

Created on 2019-02-06 by the reprex package (v0.2.1)

Upvotes: 1

Views: 85

Answers (2)

akrun
akrun

Reputation: 887231

We need to add the condition in the first if_else to take care of the NA elements, otherwise, a comparison with NA elements returns NA

df1 %>% 
   mutate(logical = if_else((a == "Yes" & !is.na(a)) |
            (b == "Yes" & !is.na(b)), TRUE, 
      if_else(is.na(a) & is.na(b), NA, FALSE )))
#     a    b logical
#1 <NA> <NA>      NA
#2   No   No   FALSE
#3  Yes  Yes    TRUE
#4  Yes   No    TRUE
#5   No  Yes    TRUE
#6   No <NA>   FALSE
#7 <NA>   No   FALSE

NOTE: Here, we are trying to resolve the OP's underlying issue


Also, we can replace == with %in% and NA issue will be resolved

df1 %>%
   mutate(logical = if_else(a %in% "Yes" | b %in% "Yes", TRUE, 
                    if_else(is.na(a) & is.na(b), NA, FALSE)))

Or using base R

replace((rowSums(df1 == "Yes", na.rm = TRUE) > 0), rowSums(is.na(df1) == 2, NA)
#[1]    NA FALSE  TRUE  TRUE  TRUE FALSE FALSE

data

df1 <- data.frame(
 "a"=c(NA,"No","Yes","Yes","No","No",NA),
 "b"=c(NA,"No","Yes","No","Yes",NA,"No")
   )

Upvotes: 0

arg0naut91
arg0naut91

Reputation: 14764

You could also do:

library(dplyr)

data.frame(
  "a"=c(NA,"No","Yes","Yes","No","No",NA),
  "b"=c(NA,"No","Yes","No","Yes",NA,"No")
) %>%
  mutate(
    logical = case_when(
      a == "Yes" | b == "Yes" ~ TRUE,
      is.na(a) & is.na(b) ~ NA,
      TRUE ~ FALSE
    )
  )

Output:

     a    b logical
1 <NA> <NA>      NA
2   No   No   FALSE
3  Yes  Yes    TRUE
4  Yes   No    TRUE
5   No  Yes    TRUE
6   No <NA>   FALSE
7 <NA>   No   FALSE

Upvotes: 3

Related Questions