10

How can I get a do.call with a variable list of arguments and functions to work with the standard evaluation version of summarise_ in dplyr?

## Some sample data, function, and variables to interpolate
set.seed(0)
dat <- data.frame(a=runif(10), b=runif(10))
fn <- function(x, y) IQR(x / y, na.rm = TRUE)
funs <- list(fn="fn")
targs <- list("a", "b")

This is the lazyeval::interp I'm trying to make work

library(dplyr)
interp(~do.call(fn, xs), .values=list(fn=funs$fn, xs=targs))
# ~do.call("fn", list("a", "b"))

but it doesnt work,

dat %>%
  summarise_(out = interp(~do.call(fn, xs), .values=list(fn=funs$fn, xs=targs)))

Expected result

dat %>%
  summarise(out = do.call(fn, list(a, b)))
#        out
# 1 1.084402

If I add in some print statements, I know the problem is that the "a" and "b" aren't being interpreted properly, but I haven't been able to figure out how to quote them properly.

fn <- function(x, y) { print(x); print(y); IQR(x / y, na.rm = TRUE) }
dat %>%
  summarise_(out = interp(~do.call(fn, xs), fn=funs$fn, xs=targs))
# [1] "a"
# [1] "b"
# Error: non-numeric argument to binary operator
Rorschach
  • 31,301
  • 5
  • 78
  • 129
  • I may be missing something (having never understood the appeal of `interp` constructs), but `dat %>% summarise(out = do.call(fn, unname(.[unlist(targs)])))` works to get the `list(a,b)` or `dat %>% summarise(out = do.call(fn, lapply(targs, function(x) .[[x]])))` – Frank Nov 15 '15 at 01:30
  • Do you really need the `do.call` here? Couldn't it just be something like `dat %>% summarise_(out = interp(~f(x,y), f = as.name(funs$fn), x = as.name(targs[[1]]), y = as.name(targs[[2]])))`? – talat Nov 16 '15 at 09:53
  • Could you clarify the situation about not knowing the number of arguments the functions take? It's possible someone could help you come up with a way to solve that problem without `do.call` if you give an example of what you are trying to achieve. – aosmith Nov 17 '15 at 16:39
  • @TheTime I was thinking working with the `eval(parse(text=...))` paradigm might work, making `targs = "list(a, b)"`, but that creates the same problem where `a` is still interpreted as `"a"`. This thing of sometimes using strings without the quotes is the bane of R and creates endless problems. – Mike Williamson Nov 23 '15 at 22:03
  • I don't understand why the answer supplied by @Frank doesn't work. Where should `group_by` be added to break it? – Sam Dickson Nov 24 '15 at 18:58

1 Answers1

6

The targs argument needs to be a call class. The variables in the call (a and b) need to be a name class. All this is done in the second (and third) line below. ?call, ?as.name, and ?is.language might make the line more understandable.

dat <- data.frame(a=runif(10), b=runif(10), grp=rep(1:2, each=5))
targs_quoted = do.call(call, c("list", lapply(targs, as.name)), quote=TRUE)
# In hardcoded form, targs_quoted = quote(list(a, b))
dat %>%
  group_by(grp) %>%
  summarise_(out = interp(~do.call(fn, xs), 
                          .values=list(fn=funs$fn, xs=targs_quoted)))

# Source: local data frame [2 x 2]
#     
#       grp       out
#     (int)     (dbl)
#  1     1  1.0754497
#  2     2  0.9892201

dplyr's "nse" (non-standard evaluation) vignette was very helpful here. I found that the . always referred to the entire table, not the grouped table. That's why some of the recommendations in the comments didn't "work" as wanted.

kdauria
  • 6,300
  • 4
  • 34
  • 53