Using case_when() within mutate_at() to recode several columns with different types of NA

Solution 1:

library(dplyr)

df %>%
  mutate_at(vars(-c(cold)), ~ case_when(colc >= 0.5 ~ `is.na<-`(., TRUE), TRUE ~ .))

#    cola colb      colc cold
# 1     q  156 0.2071403    a
# 2  <NA>   NA        NA    b
# 3     r    6 0.4020175    a
# 4     b  100 0.3829481    b
# 5     t   49 0.4885119    a
# 6     z   31 0.2631685    b
# 7     d  189 0.3859104    a
# 8  <NA>   NA        NA    b
# 9  <NA>   NA        NA    a
# 10    g  171 0.4743554    b

Description

When you use case_when to assign NA, you need to specify the type of NA, i.e. NA_integer_, NA_real_, NA_complex_ and NA_character_. However, mutate_at transforms multiple columns at the same time and these columns have different types, so you cannot apply one statement on all columns. Ideally, there might exist something like NA_guess to identify the type, but I don't find that so far. This method is a little tricky. I use is.na() to convert the input vector to NAs, and these NAs will be the same type as the input vector. For example:

x <- 1:5
is.na(x) <- TRUE ; x
# [1] NA NA NA NA NA
class(x)
# [1] "integer"

y <- letters[1:5]
is.na(y) <- TRUE ; y
# [1] NA NA NA NA NA
class(y)
# [1] "character"

Solution 2:

Work around similar to @NelsonGon :

library(dplyr)

df %>%
        mutate_all(as.character) %>% 
        mutate_at(vars(-c(cold)), 
                  ~case_when(colc >= 0.5 ~ NA_character_, # ifelse(is.numeric(.), NA_real_, NA_character_), 
                             TRUE ~ .
                  )
        ) %>% 
        mutate(colb = as.numeric(colb),
               colc = as.numeric(colc)
        )

#>    cola colb      colc cold
#> 1     q  156 0.2071403    a
#> 2  <NA> <NA>        NA    b
#> 3     r    6 0.4020175    a
#> 4     b  100 0.3829481    b
#> 5     t   49 0.4885119    a
#> 6     z   31 0.2631685    b
#> 7     d  189 0.3859104    a
#> 8  <NA> <NA>        NA    b
#> 9  <NA> <NA>        NA    a
#> 10    g  171 0.4743554    b