Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write nested functions with dplyr and dots elipse?

Tags:

r

nested

dplyr

nse

I am trying to put this as simple as possible

Some sample data :

library(magrittr)
library(dplyr)
library(rlang)

# sample data
tib <- tibble(
  a = 1:3,
  b = 4:6,
  c = 7:9
)

Now a function that makes the sum of two columns:

foo = function(df, x, y) {

  x <- enquo(x)
  y <- enquo(y)

  df %>% 
   select( !! x, !! y) %>% 
   mutate(sum = !! x + !! y) 
}

Hopefully it works:

foo(tib, a, b) # to show it works

# A tibble: 3 x 3
#       a     b   sum
#   <int> <int> <int>
# 1     1     4     5
# 2     2     5     7
# 3     3     6     9

Now I want to write a second function with non-fixed number of arguments, which calls foo with all possible pairs of arguments:

foo.each(tib, a, b, c) 
# calls foo(tib, a, b)
# calls foo(tib, a, c)
# calls foo(tib, b, c)
# i.e calls foo for each possible pair

I've tried this but that does NOT work:

foo.each = function(df, ...) {
  args <- sapply(substitute(list(...))[-1], deparse)
  args

  nb.args <- args %>% length
  for (i in nb.args:2)
    for (j in 1:(i - 1))
      foo(df, args[i], args[j]) %>% print
}

The issue is inside of foo:

   mutate(sum = !! x + !! y) 

I think that it is evaluated as:

  mutate(sum = args[i] + args[j])

I have tried many things included usage of rlang::quos but I am sick of it and I need your help.


Edit : Chris has found a clever and simple trick to correct my foo.each function. Is there a more natural way to deal with the ... elipse in this case?

For example is there a better way to get args at the begining of the function than this?

  args <- sapply(substitute(list(...))[-1], deparse)
like image 382
pietrodito Avatar asked Aug 22 '18 08:08

pietrodito


1 Answers

Your foo function is expecting variable names to be passed to it, while you are attempting to pass args[i] to it which are strings.

A combination of sym and unquoting !! does the trick:

foo.each = function(df, ...) {
  args <- sapply(substitute(list(...))[-1], deparse)
  args

  nb.args <- args %>% length
  for (i in nb.args:2)
    for (j in 1:(i - 1))
      foo(df, !!sym(args[i]), !!sym(args[j])) %>% print
}
like image 166
Chris Avatar answered Sep 29 '22 14:09

Chris