Implementing mutate within a function called with variables

Viewed 105

I'd like to call a function several times with different variables, each time setting a value to a new variable in the data frame. Here is my failed attempt. I appreciate any help!

dat <- tibble(score1 = runif(10), score2 = score1 * 2)

call_mutate_with_vars <- function(df, var1, var2, var3) {
  df <- 
    df %>%
    mutate({{var3}} := ifelse({{var1}} >= {{var2}},0,{{var2}} - {{var1}}))
  df
}

call_mutate_with_vars(dat,"score1","score2","newscore")

I receive this error:

Error: Problem with `mutate()` column `newscore`.
i `newscore = ifelse("score1" >= "score2", 0, "score2" - "score1")`.
x non-numeric argument to binary operator
Run `rlang::last_error()` to see where the error occurred.
1 Answers

The embracing operator {{ is meant for variables passed as symbols (i.e., fx(var), not fx("var")). If you need to pass your variables as characters, you can instead use the .data pronoun.

So you can either pass symbols to your current function:

library(dplyr)
set.seed(1)

call_mutate_with_vars <- function(df, var1, var2, var3) {
  df %>%
    mutate(
      {{var3}} := ifelse(
        {{var1}} >= {{var2}},
        0,
        {{var2}} - {{var1}}
      )
    )
}

call_mutate_with_vars(dat, score1, score2, newscore)

#> # A tibble: 10 x 3
#>    score1 score2 newscore
#>     <dbl>  <dbl>    <dbl>
#>  1 0.266   0.531   0.266 
#>  2 0.372   0.744   0.372 
#>  3 0.573   1.15    0.573 
#>  4 0.908   1.82    0.908 
#>  5 0.202   0.403   0.202 
#>  6 0.898   1.80    0.898 
#>  7 0.945   1.89    0.945 
#>  8 0.661   1.32    0.661 
#>  9 0.629   1.26    0.629 
#> 10 0.0618  0.124   0.0618

Or rewrite the function to handle characters:

call_mutate_with_chr_vars <- function(df, var1, var2, var3) {
  df %>% 
    mutate(
      !!var3 := ifelse(                  # note use of !! unquote operator
        .data[[var1]] >= .data[[var2]],  # to use character as name
        0,
        .data[[var2]] - .data[[var1]]
      )
    )
}

call_mutate_with_chr_vars(dat, "score1", "score2", "newscore")

#> # A tibble: 10 x 3
#>    score1 score2 newscore
#>     <dbl>  <dbl>    <dbl>
#>  1 0.266   0.531   0.266 
#>  2 0.372   0.744   0.372 
#>  3 0.573   1.15    0.573 
#>  4 0.908   1.82    0.908 
#>  5 0.202   0.403   0.202 
#>  6 0.898   1.80    0.898 
#>  7 0.945   1.89    0.945 
#>  8 0.661   1.32    0.661 
#>  9 0.629   1.26    0.629 
#> 10 0.0618  0.124   0.0618

Created on 2022-03-07 by the reprex package (v2.0.1)

The "Programming with dplyr" vignette is a nice reference for these sorts of problems.

Related