rtidyversetidyeval

Use custom function (with tidy eval) within mutate & across


I have the following data:

my_data <- structure(list(case_one = c("A", "B", "A", "B", "A", "B", "A", 
"B", "A", "B"), case_two = c("A", "A", "A", "A", "A", "B", "B", 
"B", "B", "B"), id = 1:10), class = c("tbl_df", "tbl", "data.frame"
), row.names = c(NA, -10L))


# A tibble: 10 × 3
   case_one case_two    id
   <chr>    <chr>    <int>
 1 A        A            1
 2 B        A            2
 3 A        A            3
 4 B        A            4
 5 A        A            5
 6 B        B            6
 7 A        B            7
 8 B        B            8
 9 A        B            9
10 B        B           10

What works:

creating custom function and using it to transform each column individually

times_100 <- function(data, case_column){
  data %>%
    mutate("{{case_column}}" := case_when(
      {{case_column}} == "A" ~ paste({{case_column}}, id, sep = "_"),
      {{case_column}} == "B" ~ paste({{case_column}}, id * 100, sep = "_")
    ))
}


my_data %>%
  times_100(case_one) %>% 
  times_100(case_two)


# A tibble: 10 × 3
   case_one case_two    id
   <chr>    <chr>    <int>
 1 A_1      A_1          1
 2 B_200    A_2          2
 3 A_3      A_3          3
 4 B_400    A_4          4
 5 A_5      A_5          5
 6 B_600    B_600        6
 7 A_7      B_700        7
 8 B_800    B_800        8
 9 A_9      B_900        9
10 B_1000   B_1000      10

What I want:

Using the custom function to work within mutate(across(contains("case")), so I can apply it to multiple columns at once.

# like this: 
my_data %>%
  mutate(across(contains("case"), ~ tolower(.x)))

# but with my custom function

I tried different ways of changing my function so it would work within that context, but cant get it to work.


Solution

  • Another option, which corresponds to your expected function, is to drop the mutate call and the data argument and use case_when() directly, but we need to add id as argument to access the column:

    library(dplyr)
    
    times_100 <- function(x, id) {
      case_when(
          x == "A" ~ paste(x, id, sep = "_"),
          x == "B" ~ paste(x, id * 100, sep = "_")
        )
    }
    
    my_data %>%
      mutate(across(contains("case"), ~ times_100(.x, id)))
    

    Data from OP

    my_data <- structure(list(case_one = c("A", "B", "A", "B", "A", "B", "A", 
    "B", "A", "B"), case_two = c("A", "A", "A", "A", "A", "B", "B", 
    "B", "B", "B"), id = 1:10), class = c("tbl_df", "tbl", "data.frame"
    ), row.names = c(NA, -10L))