在R中理解data.table的参考属性

6

为了澄清一些事情,我想更好地了解在 data.table 中何时进行复制以及何时不进行复制。正如这个问题所指出的那样,如果只是运行以下代码,则最终会修改原始数据表:

library(data.table)

DT <- data.table(a=c(1,2), b=c(11,12))
print(DT)
#      a  b
# [1,] 1 11
# [2,] 2 12

newDT <- DT        # reference, not copy
newDT[1, a := 100] # modify new DT

print(DT)          # DT is modified too.
#        a  b
# [1,] 100 11
# [2,]   2 12

然而,如果这样做(例如),你最终会修改新版本:
DT = data.table(a=1:10)
DT
     a
 1:  1
 2:  2
 3:  3
 4:  4
 5:  5
 6:  6
 7:  7
 8:  8
 9:  9
10: 10

newDT = DT[a<11]
newDT
     a
 1:  1
 2:  2
 3:  3
 4:  4
 5:  5
 6:  6
 7:  7
 8:  8
 9:  9
10: 10

newDT[1:5,a:=0L]

newDT
     a
 1:  0
 2:  0
 3:  0
 4:  0
 5:  0
 6:  6
 7:  7
 8:  8
 9:  9
10: 10

DT
     a
 1:  1
 2:  2
 3:  3
 4:  4
 5:  5
 6:  6
 7:  7
 8:  8
 9:  9
10: 10

据我理解,这是因为当您执行 i 语句时, data.table 返回一个全新的表格,而不是对旧的 data.table 中选择元素所占用内存的引用。 这是正确的吗?

即使 newDT <- DT[x < 11] 也会创建一个副本。在子集创建 newDT 后,执行 newDT[, b := 5]。使用 tracemem.Internal(inspect(.)) 是理解此过程的有用工具。 - Arun
@Arun:很抱歉,我不确定我理解你的观点...你能否请解释一下你所指的是什么?你是说第一个例子与第二个例子的作用相同吗?如果是那样的话- 是的,那是真的。我只是想提供一个单独的例子来澄清事情。 - Alex
当你说“j语句”时,你指的是哪个语句?只是为了确认一下。我会写一个答案来解释我所说的内容。 - Arun
mnel的回答的第一行基本上就是我想要澄清的内容,现在可以忘记它了。 - Arun
1个回答

8
在第二个示例中创建newDT时,您正在评估i(而不是j)。:=j参数内通过引用进行分配。在i语句中没有相应的内容,因为自我引用会超额分配列,但不会超额分配行。 data.table是一个列表。它的长度==列数,但超额分配了,因此您可以添加更多列而无需复制整个表格(例如使用:=j中)。
如果我们检查数据表,则可以看到truelengthtl = 100)——即列指针槽的数量。
 .Internal(inspect(DT))
@1427d6c8 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=1, tl=100)
  @b249a30 13 INTSXP g0c4 [NAM(2)] (len=10, tl=0) 1,2,3,4,5,...

在data.table中,每个元素的长度为10,而且tl=0。目前还没有方法来增加列的truelength,以允许通过引用添加额外的行。
从?truelength:
当前,只有列指针的列表向量是过度分配的(即truelength(DT)),而不是列向量本身,这将在未来允许快速的行插入(fast row insert())。
当您评估i时,data.table并不会检查您是否仅以与原始顺序相同的方式返回了所有行(然后仅在该情况下不复制),它只是返回副本。

像往常一样,你的回答非常出色!我会避免尴尬并丢弃我的答案 :)。 - Arun
@mnel:我想我的问题没有表达清楚,或者我没有完全理解你的回答。我的意思是想了解当你评估“i”时,是否返回的是副本而不是引用。这是真的吗? - Alex
@Alex 确切地说 - 我已经重新措辞了我的最后陈述(在第二次阅读时并不特别清楚)。 - mnel

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