nachocab
nachocab

Reputation: 14364

how to dynamically call a variable?

I'm working with three experiments that I store in three lists: exp1, exp2 and exp3. Each list has several items (name, conditions, dataset_a, dataset_b, etc.). Sometimes I want to perform an operation on all the experiments. Is there a way to store their names in a variable and call them dynamically? This doesn't work:

all_exp <- list(exp1=exp1,exp2=exp2,exp3=exp3)

Because if I later add something to the experiments, all_exp has a hard copy of the experiments at the previous state, not a reference.

This sort of works:

all_exp_names <- c("exp1","exp2","exp3")
all_exp <- lapply(all_exp_names, function(exp_name) (eval(parse(text=exp_name))))

but there must be a simpler way, and besides, the returned list loses the experiment names.

Upvotes: 5

Views: 3628

Answers (2)

Ben Bolker
Ben Bolker

Reputation: 226087

I think you want

lapply(all_exp_names,get)

but that doesn't actually return a named list. You can do

setNames(lapply(all_exp_names,get),all_exp_names)

which is admittedly a little clunky, although you could package it in a function if you use it frequently (see @JoshOBrien's answer for a slightly better solution to this).

The more idiomatic thing to do, I think, would be simply to keep your reference copies of the data in a named list in the first place.

edit: my original delayed assignment/evaluation code below was clever, but completely missed the point that R has a built-in delayedAssign function, which does the same thing (but probably more robustly) as my makeDelayVar function below:

delayedAssign("exp_all",list(exp1=exp1,exp2=exp2))

(Thanks to do.call and curve can not plot a function inside another function environment for pointing this out.) The trick of defining an infix operator %<% as shown below might still be handy, though.


If you really want delayed assignment, this works (but not simply):

makeActiveBinding("all_exp",function() list(exp1=exp1,exp2=exp2), .GlobalEnv)
exp1 <- 2
exp2 <- 3
all_exp
## $exp1
## [1] 2
##  
## $exp2
## [1] 3

You could wrap this in a makeDelayVar function too, although you might have to be careful about the evaluation environment.

makeDelayVar <- function(var,val) {
   makeActiveBinding(deparse(substitute(var)), function() val, parent.frame())
}
makeDelayVar(all_exp, list(exp1=exp1,exp2=exp2))
all_exp

This works the same as above (you can remove exp1 and exp2, define all_exp, and then re-define exp[12] if you want to confirm that this procedure is really doing delayed evaluation).

To get even sillier, you can define %<% to do delayed assignment (R allows infix operators to be defined as %[character]%):

`%<%` <- makeDelayVar
all_exp %<% list(exp1,exp2)

I would use this with caution, though -- it could be fragile in some circumstances. For example, restrict it to interactive contexts where you will know right away if it breaks or does something funny, and don't try to be clever passing the results of delayed evaluation as arguments to functions etc.

Upvotes: 7

Josh O&#39;Brien
Josh O&#39;Brien

Reputation: 162321

If you want to keep the names, you could use sapply(..., simplify=FALSE, USE.NAMES=TRUE):

A <- B <- C <- 1:4
nms <- c("A", "B", "C")

sapply(nms, get, simplify=FALSE, USE.NAMES=TRUE)
## $A
## [1] 1 2 3 4
## 
## $B
## [1] 1 2 3 4
## 
## $C
## [1] 1 2 3 4

Upvotes: 7

Related Questions