Complex 'if' condition for selecting variables with across()

Viewed 224

Lets say I want to convert columns to numeric if they contain 0, 1, 2, NA and have 3 unique values or less.

Previously I accomplished this with:

outcome_data<- outcome_data %>% dplyr::mutate_if(~any(c(0, 1, 2, NA) %in% .x) & length(unique(as.numeric(.x))) <= 3, as.numeric)

But it's unclear how to refer to .x as the first argument to across() to create a complex if condition. The below prompts an error:

object '.x' not found

outcome_data %>% mutate(across(any(c(0, 1, 2, NA) %in% .x) & length(unique(.x)) < 3), as.numeric))
3 Answers

In order to subset columns with a valid subscript vector, we need a numeric or character vector, here is one way using which and sapply

#devtools::install_github("tidyverse/dplyr")
library(dplyr, warn.conflicts = FALSE)
mtcars %>% 
  # which(sapply(., function(x) any(c(0, 1, 2, NA) %in% x) & n_distinct(x) < 3))
  mutate(across(.cols = which(sapply(., function(x) n_distinct(x)<3)), as.character)) %>% 
  str()

From ?across

cols, .cols: tidy-select Columns to transform. Because across() is used within functions like summarise() and mutate(), you can't select or compute upon grouping variables.

I appreciate everyone's responses on this. And I agree, it seems to me that across() doesn't make this kind of thing easy, and that extra steps are needed.

Here's how I solved it:

dummy_vars<- outcome_data %>% purrr::keep(~all(c("0", "1") %in% .x) & length(unique(.x)) <= 3) %>% names()

outcome_data<- outcome_data %>% dplyr::mutate(across(all_of(dummy_vars), as.numeric))

There was a series of issues on this topic on the dplyr Github, so they have now implemented a function that accepts predicates, where(). Like across, it's currently available through remotes::install_github('tidyverse/dplyr').

Following A. Suliman's example, we can now use:

library(dplyr)

mtcars %>%
  mutate(across(.cols = where(~any(c(0, 1, 2, NA) %in% .x) & length(unique(as.numeric(.x))) <= 3), 
                as.character)) %>%
  str()
#> 'data.frame':    32 obs. of  11 variables:
#>  $ mpg : num  21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
#>  $ cyl : num  6 6 4 6 8 6 8 4 4 6 ...
#>  $ disp: num  160 160 108 258 360 ...
#>  $ hp  : num  110 110 93 110 175 105 245 62 95 123 ...
#>  $ drat: num  3.9 3.9 3.85 3.08 3.15 2.76 3.21 3.69 3.92 3.92 ...
#>  $ wt  : num  2.62 2.88 2.32 3.21 3.44 ...
#>  $ qsec: num  16.5 17 18.6 19.4 17 ...
#>  $ vs  : chr  "0" "0" "1" "1" ...
#>  $ am  : chr  "1" "1" "1" "0" ...
#>  $ gear: num  4 4 4 3 3 3 3 4 4 4 ...
#>  $ carb: num  4 4 1 1 2 1 4 2 2 4 ...
Related