【问题标题】:Why does the extract method for data frames make two copies?为什么数据帧的提取方法会制作两份?
【发布时间】:2019-06-18 13:09:06
【问题描述】:

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

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

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

我有一个为cols 的每个元素运行的for 循环。

  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 [[<- 

Hadley 在他的例子中指出:

其实每次迭代都不是复制一次数据框,也不是两次,而是 三次! [[.data.frame 制作了两份副本,还有一份 是因为 [[.data.frame 是一个递增的常规函数 x 的引用计数。

谁能解释一下为什么[[&lt;-.data.frame 方法需要复制两份?

【问题讨论】:

  • 我很确定原因与this 回答中讨论的相同;即你得到一个用于更改值的副本,最后从class&lt;- 得到另一个副本,因为此时它在 NAM(2) 对象上被调用。该答案还表明,随着时间的推移,R 已经改进了一些东西,因此您可以看到浅拷贝和深拷贝,因此并非每个“拷贝”都必然会产生如此大的性能损失。

标签: r memory


【解决方案1】:

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

如果您查看 R 语言定义,您会看到 df[["name"]] &lt;- 3.2 被实现为

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

所以一份副本被放入*tmp*。如果你调用debug("[[&lt;-.data.frame"),你会发现它确实被一个名为*tmp*的参数调用,并且 tracemem() 将显示第一次复制发生在您进入之前。

函数[[&lt;-.data.frame 是一个常规函数,其头部如下:

function (x, i, j, value)  

该函数被称为

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

现在有三个对数据框的引用:全局环境中的df,内部代码中的*tmp*,以及该函数中的x。 (其实还有一个调用泛型的中间步骤,但它是一个原语,所以不需要重新引用。)

x 的类在函数中被更改;触发副本。然后x的组件之一被更改;那是另一个副本。这样就变成了 3。

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

【讨论】:

  • 我认为这完全正确。不过,Hadley 的书中说第一个重复并不是复杂化,而是因为 [[
  • @user21359:这不是真正的答案。增加引用计数意味着下一次修改将触发复制,它不会触发复制本身。哈德利并不比你我更了解这里发生的事情。
猜你喜欢
  • 2018-01-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-12
  • 1970-01-01
  • 1970-01-01
  • 2021-02-07
  • 1970-01-01
相关资源
最近更新 更多