Martin
Martin

Reputation: 594

Get name of function from loop variable

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]

Upvotes: 1

Views: 333

Answers (1)

daroczig
daroczig

Reputation: 28672

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

}

Upvotes: 2

Related Questions