为什么数据帧的提取方法要制作两个副本?

时间:2019-01-24 17:55:17

标签: r memory

我试图更好地理解R中for循环的性能。我修改了Hadley的书here中的示例,但我仍然感到困惑。

我有以下设置,其中for循环遍历几个随机列:

set.seed(123)
df <- as.data.frame(matrix(runif(1e3), ncol = 10))
cols <- sample(names(df), 2)
tracemem(df)

我有一个for循环,该循环针对cols的每个元素运行。

  for (i in seq_along(cols)) {
      df[[cols[i]]] <- 3.2
  }

我得到以下副本列表。

tracemem[0x1c54040 -> 0x20e1470]: 
tracemem[0x20e1470 -> 0x20e17b8]: [[<-.data.frame [[<- 
tracemem[0x20e17b8 -> 0x20dc4b8]: [[<-.data.frame [[<- 
tracemem[0x20dc4b8 -> 0x20dc800]: 
tracemem[0x20dc800 -> 0x20dc8a8]: [[<-.data.frame [[<- 
tracemem[0x20dc8a8 -> 0x20dcaa0]: [[<-.data.frame [[<- 

哈德利在他的例子中指出:

  

实际上,每次迭代复制的数据帧不是一次,不是两次,而是   三次! [[.data.frame,制作了两个副本,另一个副本   因为[[..data.frame是一个递增的常规函数   x的参考计数。

有人可以解释为什么 [[<-.data.frame方法需要制作两个副本吗?

1 个答案:

答案 0 :(得分:1)

这并不是您问题的完整答案,但这只是一个开始。

如果您查看R语言定义,则会发现df[["name"]] <- 3.2的实现方式为

`*tmp*` <- df
df <- "[[<-.data.frame"(`*tmp*`, "name", value=3.2)
rm(`*tmp*`)

因此将一份副本放入*tmp*中。如果调用debug("[[<-.data.frame"),您会看到它确实被称为*tmp*的参数调用,并且 tracemem()将显示第一次复制是在您输入之前进行的。

函数[[<-.data.frame是具有以下标头的常规函数​​:

function (x, i, j, value)  

该函数被称为

`[[<-.data.frame`(`*tmp*`, "name", value = 3.2)

现在对数据框有三个引用:全局环境中的df,内部代码中的*tmp*和该函数中的x。 (实际上,有一个中间步骤可以调用泛型,但是它是原始的,因此不需要进行新的引用。)

函数中x的类已更改;触发副本。然后更改x的组成部分之一;那是另一份副本。这样就变成了3。

仅凭猜测,我会说第一次重复的原因是,复杂的替换可能引用原始值,并且避免了检索部分修改的值的可能性。