0

Let's assume the following data:

df <- data.frame(x = c(1, 2),
                 y = c(3, 4),
                 z = c(5, 6))

Let's further assume I have a vector that contains column names I want to work on, e.g.

var_names_1 <- c("test", "x", "y")
var_names_2 <- c("test", "z")

Now what I want to do is to mutate all columns of df that are in the var_names_... objects except the first element, using across. (in this case I would do a loop where I go through my both var_names objects).

So in the first loop I want to mutate columns x and y, in the second loop I want to mutate z.

for (i in 1:2)
{
  df %>%
    mutate(across(!!sym(paste0("var_names_", i, [-1])), as_factor))
}

However, I'm getting this error message:

Error: Problem with `mutate()` input `..1`.
x Can't subset columns that don't exist.
x Column `var_names_1` doesn't exist.
i Input `..1` is `across(`var_names_1`, as_factor)`.

I thought I have finally understood some use cases for the tidyeval things, but apparently my !!sym approach doesn't work here. Any suggestions?

deschen
  • 10,012
  • 3
  • 27
  • 50
  • Is there a reason you need to have a loop? Why not just use their position, or another tidy-select option? – LMc Feb 03 '21 at 20:34
  • You would use first select then mutate. – Juan Camilo Rivera Palacio Feb 03 '21 at 20:38
  • It's of course a simplified example, in may real-life scenario I need to apply the same mutations to two slightly different column lists that I need to define dynamically. So in one case I want to mutate across e.g. X1, X2, q, v, z. In the second case I want to mutate on "s1, s2, h, t6" or sth. like that. So there's no pattern I could use with tidy select helpers. – deschen Feb 03 '21 at 20:39
  • @JuanCamiloRiveraPalacio ok, that could be a work around, but then I'd need to join with the original full data frame again. Point here is that I have hundreds of other columns that I want/need to preserve. – deschen Feb 03 '21 at 20:40

2 Answers2

3

We can use any_of in across

library(dplyr)
df1 <- df %>%
    mutate(across(any_of(c(var_names_1, var_names_2)),  factor))

-output

str(df1)
#'data.frame':  2 obs. of  3 variables:
# $ x: Factor w/ 2 levels "1","2": 1 2
# $ y: Factor w/ 2 levels "3","4": 1 2
# $ z: Factor w/ 2 levels "5","6": 1 2 

Or if we need to pass in a loop, an option with map would be

map(list(var_names_1, var_names_2),
        ~ {df <- df %>%
                 mutate(across(any_of(.x), factor))
          })      

Or using a for loop

for(i in 1:2) {
     df <- df %>%
              mutate(across(any_of(get(str_c("var_names_", i))), factor))
 }
akrun
  • 874,273
  • 37
  • 540
  • 662
1

Maybe you should try eval + str2expression

for (i in 1:2){
  str(df %>%
    mutate(across(eval(str2expression(paste0("var_names_", i,"[-1]"))), as_factor)))
}

which shows

'data.frame':   2 obs. of  3 variables:
 $ x: num  1 2
 $ y: Factor w/ 2 levels "3","4": 1 2
 $ z: num  5 6
'data.frame':   2 obs. of  3 variables:
 $ x: num  1 2
 $ y: num  3 4
 $ z: num  5 6
ThomasIsCoding
  • 96,636
  • 9
  • 24
  • 81
  • Sorry, this was a typo in my code above. I actually have paste0 in use - will also update in my code above. – deschen Feb 03 '21 at 20:45
  • I also just realized that it does work with my toy example, but not if I have an element in my vector that I want to exclude with [-1] – deschen Feb 03 '21 at 20:50
  • @deschen See my update. Hope it works for you – ThomasIsCoding Feb 03 '21 at 21:02
  • nice, works like a charme. So I probably need to add this eval(str2expression) to my toolbox. I thought the usual !!sym way would do the trick. – deschen Feb 03 '21 at 21:05
  • The proper approach in selection contexts is to use `all_of()` and `any_of()`. See the other reply which should be accepted as the answer IMO. – Lionel Henry Feb 03 '21 at 21:18