1

I need to loop through a number of functions, and plot/print the result next to the function name. I learned (this question/answer) that I have to use substitute / eval. This works nicely if each function name per se is enclosed in substitute() (see (A) and (B) below). Is there a way to automatize this, e.g. by using a construction similar to sapply? (C) obviously fails because substitute encloses also the c() clause, but maybe there is a something that I missed to make (D) work? Or any other ideas? Or is there no way?

This is what I tried (small examples, real code has many more functions and plotting stuff).

# A
x <- c(1:10)
my.fun <- substitute(mean)                                 # works
print(paste(deparse(my.fun), ": ", eval(my.fun)(x), sep=""))

# B
for (my.fun in c(substitute(mean), substitute(median))) {  # works, but lots of typing for longer function lists
  print(paste(deparse(my.fun), ": ", eval(my.fun)(x), sep=""))
}

# C
for (my.fun in substitute(c(mean, median))) {              # error: invalid for() loop sequence
  print(paste(deparse(my.fun), ": ", eval(my.fun)(x), sep=""))
}

# D
for (my.fun in sapply(c(mean, median), substitute)) {      # error: '...' used in an incorrect context
  print(paste(deparse(my.fun), ": ", eval(my.fun)(x), sep=""))
}

# E                                                        # also not helpful
my.functions <- c(mean, median)
my.fun.2 <- NULL
for (i in 1:2) {
  my.fun.2 <- c(my.fun.2, substitute(my.functions[i]))
}
# my.fun.2
# [[1]]
# my.functions[i]
# [[2]]
# my.functions[i]
Community
  • 1
  • 1
Martin
  • 594
  • 5
  • 16

1 Answers1

2

What about this "one-liner"? :)

> x <- c(1:10)
> f <- function(...)
+     sapply(
+         sapply(
+             as.list(substitute(list(...)))[-1L],
+             deparse),
+         function(fn)
+             get(fn)(x))
> f(mean, median)
  mean median 
   5.5    5.5 

In short, you can pass the functions as multiple arguments, then quickly deparse those before actually evaluating the functions one by one. So the above function with a few extra comments:

#' Evaluate multiple functions on some values
#' @param ... any number of function that will take \code{x} as the argument
#' @param x values to be passed to the functions
#' @examples
#' f(mean, median)
#' f(mean, median, sum, x = mtcars$hp)
f <- function(..., x = c(1:10)) {

    ## get provided function names
    fns <- sapply(as.list(substitute(list(...)))[-1L], deparse)

    ## run each function as an anonymous function on "x"
    sapply(fns, function(fn) get(fn)(x))

}

Or with do.call instead of this latter anonymous function:

f <- function(..., x = c(1:10)) {

    ## get provided function names
    fns <- sapply(as.list(substitute(list(...)))[-1L], deparse)

    ## run each function on "x"
    sapply(fns, do.call, args = list(x))

}
daroczig
  • 28,004
  • 7
  • 90
  • 124
  • You should probably replace `get` by `match.fun` or at least `get(…, mode = 'function')`. Otherwise this will fail when a function’s name is shadowed by another variable (which is allowed in R). Alternatively, use the argument directly: you can both `substitute` function arguments *and* use them regularly. – Konrad Rudolph Mar 29 '15 at 20:55
  • @KonradRudolph the updated answer with `do.call` does the job in such case as well – daroczig Mar 29 '15 at 21:06
  • Fantastic - exactly what I was looking for! And thanks to both also for explaining different variants and for explanations. Still trying to customize to `substitute` and that sort of functions, but that helps a lot! – Martin Mar 29 '15 at 21:43