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

六月ゝ 毕业季﹏ 提交于 2019-12-05 06:52:21

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.

You have only one column, so both approaches below will give you the same result.

You only have to modify your functions' list.

library(dplyr)

myfuns <- c(f1 = ~.^2, f2 = ~exp(.), f3 = ~sqrt(.))

tibble(a = 1:3) %>% mutate_at(vars(a), myfuns)

tibble(a = 1:3) %>% mutate_all(myfuns)


# # 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

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).

A base alternative :

myfuns <- c(f1 = "a^2", f2 = "exp(a)", f3 = "sqrt(a)")
df <- data.frame(a = 1:3)
df[names(myfuns)] <- lapply(myfuns , function(x) eval(parse(text= x), envir = df))
df
#>   a f1        f2       f3
#> 1 1  1  2.718282 1.000000
#> 2 2  4  7.389056 1.414214
#> 3 3  9 20.085537 1.732051

Created on 2019-07-08 by the reprex package (v0.3.0)

One way using parse_expr from rlang

library(tidyverse)
library(rlang)

tibble(a = 1:3) %>% 
   mutate(ans =  map(myfuns, ~eval(parse_expr(.)))) %>%
   #OR mutate(ans =  map(myfuns, ~eval(parse(text  = .)))) %>%
   unnest() %>%
   group_by(a) %>%
   mutate(temp = row_number()) %>%
   spread(a, ans) %>%
   select(-temp) %>%
   rename_all(~names(myfuns))

# A tibble: 3 x 3
#    f1    f2    f3
#  <dbl> <dbl> <dbl>
#1     1  2.72  1   
#2     4  7.39  1.41
#3     9  20.1  1.73

you can try also a purrr approach

# define the functions
f1 <- function(a) a^2
f2 <- function(a, b) a + b
f3 <- function(b) sqrt(b)

# put all functions in one list
tibble(funs=list(f1, f2, f3)) %>%
  # give each function a name 
  mutate(fun_id=paste0("f", row_number())) %>% 
  # add to each row/function the matching column profile
  # first extract the column names you specified in each function 
  #mutate(columns=funs %>% 
  #         toString() %>% 
  #         str_extract_all(., "function \\(.*?\\)", simplify = T) %>% 
  #         str_extract_all(., "(?<=\\().+?(?=\\))", simplify = T) %>%
  #         gsub(" ", "", .) %>% 
  #         str_split(., ",")) %>%
  # with the help of Konrad we can use fn_fmls_names
  mutate(columns=map(funs, ~ rlang::fn_fmls_names(.)))  %>% 
  # select the columns and add to our tibble/data.frame  
  mutate(params=map(columns, ~select(df, .))) %>% 
  # invoke the functions
  mutate(results = invoke_map(.f = funs, .x = params)) %>% 
  # transform  to desired output
  unnest(results) %>% 
  group_by(fun_id) %>% 
  mutate(n=row_number()) %>% 
  spread(fun_id, results) %>% 
  left_join(mutate(df, n=row_number()), .) %>% 
  select(-n)
Joining, by = "n"
# A tibble: 5 x 5
      a     b    f1    f2    f3
  <dbl> <dbl> <dbl> <dbl> <dbl>
1     2     1     4     3  1   
2     4     1    16     5  1   
3     5     2    25     7  1.41
4     7     2    49     9  1.41
5     8     2    64    10  1.41

some data

df <- data_frame(
  a = c(2, 4, 5, 7, 8),
  b = c(1, 1, 2, 2, 2))
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!