R ggplot2 奇怪行为。看起来它是通过引用传递的。

5
我将尝试复制ggplot对象,然后更改新复制对象的某些属性,例如将颜色线条更改为红色。
假设这段代码:
df = data.frame(cbind(x=1:10, y=1:10))
a = ggplot(df, aes(x=x, y=y)) + geom_line()
b = a

然后,如果我改变变量a的线条颜色。
a$layers[[1]]$geom_params$colour = "red"

它还改变了b的颜色。

> b$layers[[1]]$geom_params$colour 
[1] "red"    # why it is not "black"?  

我希望能够有两个不同属性的对象ab。为了正确地实现这一点,我需要再次调用绘图命令,并使用b = ggplot(df, aes(xy, y=z)) + geom_line()来绘制b的图形。然而,在算法的这个阶段,没有办法知道绘图命令ggplot(df, aes(x=x, y=y)) + geom_line()的内容。
你知道这是怎么回事吗?ggplot对象是否以不同的方式处理?
谢谢!

嗯,我同意这有点可怕,但是如果你看一下 str(a),你会注意到这些层是原型对象,所以这很可能解释了为什么你会看到这种行为。 - joran
2个回答

7
问题出在ggplot使用proto库来模拟面向对象的对象。proto库依赖于环境来收集对象的变量。由于环境是通过引用传递的,这就是你看到的行为的原因(也是没有人建议以这种方式更改层属性的原因之一)。
无论如何,我们可以从proto文档中调整一个示例,尝试创建ggplot对象的层的深度副本。这应该会“断开”它们之间的关联。这是这样一个辅助函数。
duplicate.ggplot<-function(x) {
    require(proto)
    r<-x
    r$layers <- lapply(r$layers, function(x) {
        as.proto(as.list(x), parent=x)
    })
    r
}

所以,如果我们运行
df = data.frame(cbind(x=1:10, y=1:10))
a = ggplot(df, aes(x=x, y=y)) + geom_line()
b = a
c = duplicate.ggplot(a)

a$layers[[1]]$geom_params$colour = "red"

如果我们将这三个都绘制出来,会得到

enter image description here

这表明我们可以独立地改变 "c" 而不影响 "a"。


不错。我在思考是否有一种规范的方法可以在 R 级别(而不是 C 级别)执行赋值操作,以确保复制被创建。 - joran
@joran 我不知道如何在R代码中强制执行环境的深拷贝,但这并不意味着它不存在。 - MrFlick
非常好的解释。非常感谢! - Daniel Bonetti
这个不再起作用了。显示出错信息 "在 assign(s, x[[s]], envir = envir) 中,'envir' 参数无效"。有没有什么修复的想法? - Jan Stanstrup

4
忽略ggplot的细节,有一个简单的技巧可以在R中深度复制(几乎)任何对象:
obj_copy <- unserialize(serialize(obj, NULL))

这将对象序列化为适合写入磁盘的二进制表示,并从该表示重构对象。它相当于将对象保存到文件中,然后再次加载它(即saveRDS后面跟readRDS),只是它实际上从未保存到文件中。这可能不是最有效的解决方案,但它应该适用于几乎任何可以保存到文件中的对象。
您可以使用这个技巧定义一个deepcopy函数:
deepcopy <- function(p) {
    unserialize(serialize(p, NULL))
}

这似乎成功地打破了相关ggplot之间的链接。

显然,对于无法序列化的对象,例如来自bigmemory包的大矩阵,此方法将无法使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接