19

保存环境时出现我不理解的行为。下面的代码演示了这个问题。我本来希望这两个文件(far-too-big.RDataright-size.RData)大小相同,而且非常小,因为它们包含的环境是空的。

事实上,far-too-big.RData最终的大小与bigfile.RData.

我在 WinXP 5.1 SP3 上使用 2.14.1 和 2.15.2 得到了相同的结果。谁能解释为什么会这样?

两者far-too-big.RDataright-size.RData,当加载到新的 R 会话中时,似乎什么都不包含。即他们返回character(0)响应ls()。但是,如果我将保存切换为 include ascii=TRUE,并在文本编辑器中打开结果,我可以看到其中far-too-big.RData包含bigfile.RData.

a <- matrix(runif(1000000, 0, 1), ncol=1000)
save(a, file="bigfile.RData")
fn <- function() {
    load("bigfile.RData")
    test <- new.env()
    save(test, file="far-too-big.RData")
    test1 <- new.env(parent=globalenv())
    save(test1, file="right-size.RData")
}
fn()
4

2 回答 2

16

这不是我的专业领域,但我相信环境是这样工作的。

  • 任何环境都会继承其父环境中的所有内容。
  • 所有函数调用都会创建自己的环境。

在您的情况下,上述结果是:

  1. 当您运行fn()它时,它会创建自己的本地环境(绿色),默认情况下其父级是globalenv()(灰色)。
  2. 当您在其父级中创建环境test(红色)时,默认为' 的环境(绿色)。因此将包括对象。fn()fn()testa
  3. 当您创建环境test1(蓝色)并明确声明其父级是globalenv()它与fn()环境分离并且不继承对象时a

因此,在保存时,test您还保存了 object 的(有些隐藏的)副本a。保存时不会发生这种情况,test1因为它不包含对象a

在此处输入图像描述

更新

显然,这是一个比我以前认为的更复杂的话题。虽然我现在可能只是引用@joris-mays 的回答,但我想最后尝试一下。

对我来说,最直观的环境可视化是树形结构,见下文,其中每个节点都是一个环境,箭头指向其各自的封闭环境(我想相信它与其父级相同,但必须做框架,超出了我的世界角落)。给定的环境包含您通过向下移动树可以到达的所有对象,并且它可以访问通过向上移动树可以到达的所有对象。当您保存环境时,您似乎保存了所有对象和环境,这些对象和环境都被它包围并且可以从它访问(除了globalenv())。

然而,带回家的信息正如 Joris 已经说过的那样:将您的对象保存为列表,您无需担心。

在此处输入图像描述

如果您想了解更多信息,我可以推荐 Norman Matloff 的优秀书籍《R 编程艺术》。它的目标是 R 中的软件开发,而不是主要的数据分析,并假设您有相当多的编程经验。我必须承认我还没有完全消化环境部分,但由于这本书的其余部分写得很好,教学法也很好,我认为这一部分也是。

于 2012-12-17T13:55:50.167 回答
9

实际上,这与@Backlin 显示的相反:父环境是包含其他环境的环境。因此,在您定义的情况下, 的封闭环境test是 的本地环境fn, 的封闭环境test1是全局环境,如下所示:

在此处输入图像描述

环境的行为与 R 中的其他对象不同,因为它们在传递给函数或在赋值中使用时不会被复制。环境对象本身内部由指向的指针组成:

  • 一个框架(这是一个包含值的对列表)
  • 封闭环境(如上所述)
  • 哈希表(如果环境未哈希,则为列表或 NULL)

环境包含指针这一事实使一切变得不同。环境并不是那么容易处理,它们实际上非常棘手。看看下面的代码:

> test <- new.env()
> test$a <- 1
> test2 <- test
> test2$a <- 2
> test$a
[1] 2

test因此,您从in复制的唯一内容test2是指针。如果您更改 中的值test2,您test也会更改其中的值。(实际上,您只更改该值一次,但testtest2指向同一帧)。

当你试图保存一个环境时,R别无选择,只能获取框架、哈希表和封闭环境的值并保存它们。由于封闭环境本身就是一个环境,R 还将保存所有封闭环境,直到它到达全局环境。由于全局环境在内部代码中以特殊方式处理,因此(幸运的是)没有保存在文件中。

注意封闭环境和父框架之间的区别:假设我们定义我们的函数有点不同:

a <- matrix(runif(1000000, 0, 1), ncol=1000)
save(a, file="bigfile.RData")
fn <- function() {
    load("bigfile.RData")
    test <- new.env()
    save(test, file="far-too-big.RData")
    test1 <- new.env(parent=globalenv())
    save(test1, file="right-size.RData")
}

fn2 <- function(){
    z <- matrix(runif(1000000,0,1),ncol=1000)
    fn()
}
fn2()

现在我们有以下情况:

在此处输入图像描述

有人会认为文件“far-too-big.RData”同时包含矩阵 a 和矩阵 z,但事实并非如此。它只包含矩阵a。这是因为封闭环境fn全局环境。的父框架fn的环境fn2,但创建的环境对象fn包含一个指向全局环境的指针。

另一方面,如果我们执行以下操作:

fn <- function() {
    load("bigfile.RData")
    test <- new.env()
    test$b <- a
    test2 <- new.env(parent=test)
    save(test2, file="far-too-big.RData")
}

test2现在包含在两个环境中(beingtest和 的环境fun),并且这两个环境也都保存在文件中。所以你得到这种情况:

在此处输入图像描述

不管怎样,我个人避免将环境保存为环境,因为可能出错的地方更多。在我看来,将环境保存为列表在 99.9% 的情况下是更好的选择:

fn2 <- function(){
    load("bigfile.RData")
    test <- new.env()
    test$x <- "something"
    test$fn <- ls
    testlist <- as.list(test)
    save(testlist, file="right-size.RData")
}
fn2()

如果你需要它是一个环境,你可以在加载时将它转换回来。

load("right-size.RData")
test <- as.environment(testlist)
于 2012-12-18T15:24:37.777 回答