我试图更好地理解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
方法需要制作两个副本吗?
答案 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。
仅凭猜测,我会说第一次重复的原因是,复杂的替换可能引用原始值,并且避免了检索部分修改的值的可能性。