Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using quotations inside mutate: an alternative to mutate_(.dots = ...)

Tags:

r

dplyr

rlang

I want to apply different functions to the same column in a tibble. These functions are stored in a character string. I used to do this with mutate_ and the .dots argument like this:

library(dplyr)

myfuns <- c(f1 = "a^2", f2 = "exp(a)", f3 = "sqrt(a)")
tibble(a = 1:3) %>% 
  mutate_(.dots = myfuns)

This approach still works fine but mutate_ is deprecated. I tried to achieve the same result with mutate and the rlang package but did not get very far.

In my real example myfuns contains about 200 functions so typing them one by one is not an option.

Thanks in advance.

like image 242
Cettt Avatar asked Jul 08 '19 10:07

Cettt


2 Answers

For simple equations that take a single input, it’s sufficient to supply the function itself, e.g.

iris %>% mutate_at(vars(-Species), sqrt)

Or, when using an equation rather than a simple function, via a formula:

iris %>% mutate_at(vars(-Species), ~ . ^ 2)

When using equations that access more than a single variable, you need to use rlang quosures instead:

area = quo(Sepal.Length * Sepal.Width)
iris %>% mutate(Sepal.Area = !! area)

Here, quo creates a “quosure” — i.e. a quoted representation of your equation, same as your use of strings, except, unlike strings, this one is properly scoped, is directly usable by dplyr, and is conceptually cleaner: It is like any other R expression, except not yet evaluated. The difference is as follows:

  • 1 + 2 is an expression with value 3.
  • quo(1 + 2) is an unevaluated expression with value 1 + 2 that evaluates to 3, but it needs to be explicitly evaluated. So how do we evaluated an unevaluated expression? Well …:

Then !! (pronounced “bang bang”) unquotes the previously-quoted expression, i.e. evaluates it — inside the context of mutate. This is important, because Sepal.Length and Sepal.Width are only known inside the mutate call, not outside of it.


In all the cases above, the expressions can be inside a list, too. The only difference is that for lists you need to use !!! instead of !!:

funs = list(
    Sepal.Area = quo(Sepal.Length * Sepal.Width),
    Sepal.Ratio = quo(Sepal.Length / Sepal.Width)
)

iris %>% mutate(!!! funs)

The !!! operation is known as “unquote-splice”. The idea is that it “splices” the list elements of its arguments into the parent call. That is, it seems to modify the call as if it contained the list elements verbatim as arguments (this only works in functions, such as mutate, that support it, though).

like image 173
Konrad Rudolph Avatar answered Sep 18 '22 20:09

Konrad Rudolph


Convert your strings to expressions

myexprs <- purrr::map( myfuns, rlang::parse_expr )

then pass those expressions to regular mutate using quasiquotation:

tibble(a = 1:3) %>% mutate( !!!myexprs )
# # A tibble: 3 x 4
#       a    f1    f2    f3
#   <int> <dbl> <dbl> <dbl>
# 1     1     1  2.72  1   
# 2     2     4  7.39  1.41
# 3     3     9 20.1   1.73

Note that this will also work with strings / expressions involving multiple columns.

like image 40
Artem Sokolov Avatar answered Sep 19 '22 20:09

Artem Sokolov