我在理解data.table的pass-by-reference属性时有点困难.有些操作似乎" destruct "了引用,我想确切地了解发生了什么.

从另一个data.table(通过<-)创建一个data.table,然后用:=更新新表时,原始表也会被更改.这是预期的,如下所示:

?data.table::copy

下面是一个例子:

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行在某种程度上"打破"了参考.我猜这会以某种方式调用一个副本,但我想完全理解R是如何处理这些操作的,以确保我不会在代码中引入潜在的bug.

如果有人能给我解释一下,我将不胜感激.

推荐答案

是的,在R中使用<-(或=->)的子分配可以复制whole对象.你可以用tracemem(DT).Internal(inspect(DT))来追踪,如下所示.data.table特征:=set()通过引用分配给它们传递的任何对象.因此,如果该对象以前被复制过(通过子授权<-或显式copy(DT)),那么它就是通过引用修改的副本.

DT <- data.table(a = c(1, 2), b = c(11, 12)) 
newDT <- DT 

.Internal(inspect(DT))
# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
#   @00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
#   @00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,12
# ATTRIB:  # ..snip..

.Internal(inspect(newDT))   # precisely the same object at this point
# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
#   @00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
#   @00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,12
# ATTRIB:  # ..snip..

tracemem(newDT)
# [1] "<0x0000000003b7e2a0"

newDT$b[2] <- 200
# tracemem[0000000003B7E2A0 -> 00000000040ED948]: 
# tracemem[00000000040ED948 -> 00000000040ED830]: .Call copy $<-.data.table $<- 

.Internal(inspect(DT))
# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),TR,ATT] (len=2, tl=100)
#   @00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
#   @00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,12
# ATTRIB:  # ..snip..

.Internal(inspect(newDT))
# @0000000003D97A58 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
#   @00000000040ED7F8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
#   @00000000040ED8D8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,200
# ATTRIB:  # ..snip..

请注意,即使a没有更改,a向量也是如何被复制的(不同的十六进制值表示向量的新副本).甚至整个b都被复制,而不仅仅是改变需要改变的元素.这对于 Big Data 来说很重要,也是为什么:=set()被引入data.table的原因.

现在,使用我们复制的newDT,我们可以通过引用对其进行修改:

newDT
#      a   b
# [1,] 1  11
# [2,] 2 200

newDT[2, b := 400]
#      a   b        # See FAQ 2.21 for why this prints newDT
# [1,] 1  11
# [2,] 2 400

.Internal(inspect(newDT))
# @0000000003D97A58 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
#   @00000000040ED7F8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
#   @00000000040ED8D8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,400
# ATTRIB:  # ..snip ..

请注意,所有3个十六进制值(列点的向量和2列中的每一列)保持不变.所以它确实是通过引用修改的,根本没有拷贝.

或者,我们可以参考修改原来的DT:

DT[2, b := 600]
#      a   b
# [1,] 1  11
# [2,] 2 600

.Internal(inspect(DT))
# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
#   @00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
#   @00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,600
#   ATTRIB:  # ..snip..

这些十六进制值与我们在上面看到的DT的原始值相同.键入example(copy)以获取更多示例,使用tracemem并与data.frame进行比较.

顺便说一句,如果你tracemem(DT)然后DT[2,b:=600]你会看到一份报告.方法的第一个副本是10行.当用invisible()包装或在函数或脚本中调用时,不会调用print方法.

所有这些都适用于内部函数;i、 例如,:=set()即使在函数中也不会进行写时复制.如果需要修改本地副本,则在函数开始时调用x=copy(x).但是,请记住data.table适用于 Big Data (以及小数据的更快编程优势).我们故意不想复制大对象(永远).因此,我们不需要考虑通常的3*工作记忆系数经验法则.我们尽量只需要一列的工作记忆(即工作记忆系数为1/ncol而不是3).

R相关问答推荐

通过Plotly绘制线串几何形状的3D图

使用scale_x_continuous复制ggplot 2中的离散x轴

如何使用按钮切换轨迹?

将数据集中的值增加到当前包含的最大值

如何根据R中其他列的值有条件地从列中提取数据?

提取具有连续零值的行,如果它们前面有R中的有效值

在R中,如何将变量(A,B和C)拟合在同一列中,如A和B,以及A和C在同一面板中?

Ggplot2中的重复注记

提取一个列表中单个列的重复观察结果R

根据列A中的差异变异列,其中行由列B中的相对值标识

使用带有OR条件的grepl过滤字符串

如何将网站图像添加到带有极坐标的面包裹条形图?

将多个列值转换为二进制

按组计算列中1出现的间隔年数

在不对R中的变量分组的情况下取两行的平均值

是否有一个R函数可以输出在输入的字符向量中找到的相应正则表达式模式?

使用同一行中的前一个值填充R矩阵中的缺失值

在鼠标悬停时使用Plotly更改geom_point大小

conditionPanel不考虑以下条件

如何将字符类对象中的数据转换为R中的字符串