4

I have list of functions which also contains one user defined function:

> fun <- function(x) {x}
> funs <- c(median, mean, fun)

Is it possible to get function names as strings from this list? My only workaround so far was to create vector which contains function names as strings:

> fun.names <- c("median", "mean", "fun")

When I want to get variable name I use to do this trick (if this is not correct correct me please) but as you can see it only work for one variable not for list:

> as.character(substitute(mean))
[1] "mean"

> as.character(substitute(funs))
[1] "funs"

Is there something that will work also for list? Is there any difference if list contains functions or data types?

EDIT: I need to pass this list of functions (plus another data) to another function. Then those functions from list will be applied to dataset. Function names are needed because if there are several functions passed in list I want to being able to determine which function was applied. So far I've been using this:

window.size   <- c(1,2,3)
combinations  <- expand.grid(window.size, c(median, mean))
combinations  <- cbind(combinations, rep(c("median","mean"), each = length(window.size)))
Wakan Tanka
  • 7,542
  • 16
  • 69
  • 122
  • 3
    Some more background on how the list is created would help, because the usual solution would be to create the list with names in the first place. So it would help to see if/why you can't do that in this case. – joran Apr 01 '16 at 22:06
  • As @joran says, you will have `names` if you write `funs <- c(median=median, mean=mean, fun=fun)` – HubertL Apr 01 '16 at 22:09
  • 1
    For a truly exotic variation, since you haven't addressed our questions, [this](http://stackoverflow.com/q/21011672/324364) question shows how to write a function that creates an "automatically" named list. – joran Apr 01 '16 at 22:15

2 Answers2

2

Generally speaking, this is not possible. Consider this definition of funs:

funs <- c(median,mean,function(x) x);

In this case, there's no name associated with the user-defined function at all. There's no rule in R that says all functions must be bound to a name at any point in time.

If you want to start making some assumptions about whether and where all such lambdas are defined, then possibilities open up.

One idea is to search the closure environment of each function for an entry that matches (identically) to the function itself, and then use that name. This will incur a performance penalty due to the comparison work, but may be tolerable if you don't have to run it repetitively:

getFunNameFromClosure <- function(fun) names(which(do.call(c,eapply(environment(fun),identical,fun)))[1L]);

Demo:

fun <- function(x) x;
funs <- c(median,mean,fun);
sapply(funs,getFunNameFromClosure);
## [1] "median" "mean"   "fun"

Caveats:

1: As explained earlier, this will not work on functions that were never bound to a name. Furthermore, it will not work on functions whose closure environment does not contain a binding to the function. This could happen if the function was bound to a name in a different environment than its closure (via a return value, superassignment, or assign() call) or if its closure environment was explicitly changed.

2: It is possible to bind a function to multiple names. Thus, the name you get as a result of the eapply() search may not be the one you expect. Here's a good demonstration of this:

getFunNameFromClosure(ls); ## gets wrong name
## [1] "objects"
identical(ls,objects); ## this is why
## [1] TRUE
bgoldst
  • 34,190
  • 6
  • 38
  • 64
  • 1
    Good analysis. It is informative to look at `dput (funs)`. It shows that the "names" of those objects have already been looked and only their value is left in the returned object from the `funs <- c(mean.median,fun)` operation. It's a bit like what is happening in `lapply` where people expect to be able to find names but only get values inside functions. The names get reassigned later. – IRTFM Apr 02 '16 at 01:19
1

Here is a hacky approach:

funs <- list(median, mean)

fun_names = sapply(funs, function(x) {
  s = as.character(deparse(eval(x)))[[2]]
  gsub('UseMethod\\(|[[:punct:]]', '', s)
})

names(funs) <- fun_names

funs

$median
function (x, na.rm = FALSE) 
UseMethod("median")
<bytecode: 0x103252878>
<environment: namespace:stats>

$mean
function (x, ...) 
UseMethod("mean")
<bytecode: 0x103ea11b8>
<environment: namespace:base>

combinations  <- expand.grid(window.size, fun_names, c(median, mean))
marbel
  • 7,560
  • 6
  • 49
  • 68