18

This is a simple question but the answer is apparently not so simple... Is it possible to combine environments in R?

E1 = new.env()
E2 = new.env()
E1$x = 25
E2$y = 7

Ok, now I want an environment (say, E3) that has both x and y defined.

c(E1, E2)
#doesn't work
E3 = new.env(E1, E2)
#doesn't work

I have found other similar questions but they don't seem to work for me.

Use case: Maybe there's a reason this isn't easy...the reason I want to do this is thus: I use some functions to load up data. Previously, I've just loaded it into the global environment, but I now have many different functions loading different types of data (which I call variously as needed), and so I wanted to keep the loaded data a bit more compartmentalized. If I call 2 different loading functions E1=loadData1() and E2=loadData2(), and I now want to call a function that uses variables from both of these functions, I'd like to be able to say with(E1 & E2, someFunction()). Hence, merging my loaded environments seems appropriate.

So, what's the right way to merge them? And, as an aside, do you have a different suggestion for how to better accomplish what I'm doing, if merging environments is not the right way?

Community
  • 1
  • 1
nsheff
  • 3,063
  • 2
  • 24
  • 29
  • 3
    Why don't you use lists instead of environments? – Roland Sep 26 '14 at 10:54
  • Because it's easier to write a loadData() function that populates some variables and then says return(environment()) than to put each in a list... I guess I could convert to a list and maybe this is the way to go, but it just seemed like an extra step. – nsheff Sep 26 '14 at 11:49

3 Answers3

15

1) Make one environment the parent of the other and use with(child, ...) :

parent <- new.env(); parent$x <- 1
child <- new.env(parent = parent); child$y <- 2

with(child, x + y) # x comes from child and y from parent
## [1] 3

You can link as many environments as you like in as long a chain as necessary.

Note that if the child were initially created with no parent then you can add a parent later using:

parent.env(child) <- parent

Thus we define LoadData1 and LoadData2 as:

# define LoadData1 to have a parent argument
LoadData1 <- function(parent = emptyenv()) {
        # calculation of environment e goes here
        parent.env(e) <- parent
        e
}

# define LoadData2 to have a parent argument
LoadData2 <- function(parent = emptyenv()) {
        # calculation of environment e goes here
        parent.env(e) <- parent
        e
}

# run
e1 <- LoadData1()
e2 <- LoadData2(parent = e1)
with(e2, dataFrom1 + dataFrom2)

If you don't want to modify LoadData1 and LoadData2 from what they are now:

e1 <- LoadData1()
e2 <- LoadData2()
parent.env(e2) <- e1
with(e2, dataFrom1 + dataFrom2)

2) Convert to lists:

with(c(as.list(e1), as.list(e2)), somefunction())

ADDED Second approach.

G. Grothendieck
  • 254,981
  • 17
  • 203
  • 341
9

You can do it by combining them, converting the environments to lists, and then converting back:

E3 <- as.environment(sapply(c(E1,E2),as.list))
ls(env=E3)
[1] "x" "y"
E3$x
[1] 25
E3$y
[1] 7
James
  • 65,548
  • 14
  • 155
  • 193
  • +1. Agreed but should environments be used like this? Surely there must be a way for the functions to return the data in some previously agreed structure that then can be parsed appropriately by somefunction()... – andrewzm Sep 26 '14 at 10:49
  • @andrewzm You mean a list? Probably easier, but environments support hashing if that is important. – James Sep 26 '14 at 11:37
  • @James +1 Thanks, I guess going through a list was in the back of my mind but I was hoping there was an easier way... – nsheff Sep 26 '14 at 11:59
  • @James As an aside, I guess I should just skip the environments thing altogether and go with lists all the way through, then. – nsheff Sep 26 '14 at 12:00
6

I made this function:

> appendEnv = function(e1, e2) {
+     e1name = deparse(substitute(e1))
+     e2name = deparse(substitute(e2))
+     listE1 = ls(e1)
+     listE2 = ls(e2)
+     for(v in listE2) {
+         if(v %in% listE1) warning(sprintf("Variable %s is in e1, too!", v))
+         e1[[v]] = e2[[v]]
+     }
+ }

> e1 = new.env()
> e2 = new.env()
> e1$x = 1
> e1$y = 2
> e2$y = 3
> e2$z = 4
> appendEnv(e1, e2)
Warning message:
In appendEnv(e1, e2) : Variable y is in e1, too!
> as.list(e1)
$x
[1] 1

$y
[1] 3

$z
[1] 4
qed
  • 22,298
  • 21
  • 125
  • 196
  • +1 Thanks, this is a good idea that directly answers the question, and also brings up the issue that the environments could have conflicting variables... – nsheff Nov 14 '14 at 14:13