我在理解 data.table
的pass-by-reference属性时遇到了一些麻烦 . 有些操作似乎是'break'的参考,而我正在发生这种情况 .
在从另一个 data.table
创建 data.table
(通过 <-
,然后通过 :=
更新新表时,原始表也会被更改 . 这是预期的,按照:
?data.table::copy
和stackoverflow: pass-by-reference-the-operator-in-the-data-table-package
这是一个例子:
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
现在不再被修改:
DT = data.table(a=c(1,2), b=c(11,12))
newDT <- DT
newDT$b[2] <- 200 # new operation
newDT[1, a := 100]
print(DT)
# a b
# [1,] 1 11
# [2,] 2 12
所以似乎 newDT$b[2] <- 200
行以某种方式'breaks'参考 . 我在我的代码中引入了潜在的错误 .
如果有人能向我解释这一点,我将非常感激 .
2 回答
是的,它是在R中使用
<-
(或=
或->
)进行子分配,它会复制整个对象 . 您可以使用tracemem(DT)
和.Internal(inspect(DT))
跟踪它,如下所示 .data.table
的特征是:=
和set()
通过引用它们传递的任何对象来分配 . 因此,如果该对象先前已被复制(通过子分配<-
或显式copy(DT)
),那么它就是通过引用修改的副本 .注意甚至
a
向量是如何被复制的(不同的十六进制值表示向量的新副本),即使a
没有被更改 . 甚至整个b
都被复制了,而不仅仅是改变了需要改变的元素 . 这对于避免大数据很重要,以及为什么:=
和set()
被引入data.table
.现在,通过复制
newDT
,我们可以通过引用修改它:请注意,所有3个十六进制值(列点矢量和两列中的每一列)保持不变 . 因此它通过引用进行了真正的修改,完全没有副本 .
或者,我们可以通过引用修改原始
DT
:这些十六进制值与我们在
DT
上面看到的原始值相同 . 输入example(copy)
以获取更多使用tracemem
的示例,并与data.frame
进行比较 .顺便说一句,如果你
tracemem(DT)
然后DT[2,b:=600]
你会看到一份报告 . 这是print
方法执行的前10行的副本 . 当使用invisible()
包装或在函数或脚本中调用时,不会调用print
方法 .所有这些也适用于内部功能;即,即使在函数内,
:=
和set()
也不会在写入时复制 . 如果需要修改本地副本,请在函数开头调用x=copy(x)
. 但是,请记住data.table
适用于大数据(以及较小的数据编程优势) . 我们故意不需要允许通常的3 *工作记忆因素经验法则 . 我们尝试只需要一个大到一列的工作记忆(即工作记忆因子为1 / ncol而不是3) .只是一个简单的总结 .
<-
与data.table
就像基地一样;即,之后使用<-
进行子分配(例如更改列名或更改DT[i,j]<-v
之类的元素)之前不会复制 . 然后它就像基地一样获取整个对象的副本 . 这就是所谓的写时复制 . 我认为会更好地称为副本分配!当您使用特殊:=
运算符或data.table
提供的set*
函数时,它不会复制 . 如果你有大数据,你可能想要使用它们 .:=
和set*
将不会复制data.table
,即使在函数内也是如此 .鉴于此示例数据:
以下只是"binds"另一个名称
DT2
以相同的数据对象绑定当前绑定的名称DT
:这永远不会复制,也永远不会复制到基地 . 它只标记数据对象,以便R知道两个不同的名称(
DT2
和DT
)指向同一个对象 . 因此,如果其中任何一个被分配给后来,R将需要复制该对象 .那也是
data.table
的完美之选 .:=
不是这样做的 . 所以以下是故意的错误,因为:=
不仅仅是绑定对象名称::=
用于通过引用进行子分配 . 但你不像在基地那样使用它:你这样使用它:
这改变了
DT
作为参考 . 假设您通过引用数据对象添加新列new
,则无需执行此操作:因为RHS已经通过引用改变了
DT
. 额外的DT <-
是误解:=
的作用 . 你可以在那里写,但它是多余的 .DT
由引用改变,:=
,甚至在函数内:data.table
适用于大型数据集,请记住 . 如果你的内存中有20GBdata.table
,那么你需要一种方法来做到这一点 . 这是data.table
的一个非常慎重的设计决定 .当然可以复制 . 您只需要告诉data.table您确定要复制20GB数据集,使用
copy()
功能:要避免复制,请不要使用基本类型分配或更新:
如果您想确保通过引用更新,请使用
.Internal(inspect(x))
并查看成分的内存地址值(请参阅Matthew Dowle的答案) .在
j
中编写:=
就可以通过组按引用进行子分配 . 您可以按组引用添加新列 . 这就是:=
在[...]
内完成的原因: