Pet*_*ine 181 copy r reference assignment-operator data.table
我在理解传递的引用属性方面遇到了一些麻烦data.table.有些操作似乎"打破"了参考,我想准确理解发生了什么.
在data.table从另一个创建a 时data.table(通过<-,然后更新新表:=,原始表也会被更改.这是预期的,按照:
?data.table::copy
和stackoverflow:传递引用操作在数据表包中
这是一个例子:
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如何处理这些操作,以确保我不会在我的代码中引入潜在的错误.
如果有人能向我解释这一点,我将非常感激.
Mat*_*wle 136
是的,它是R中的子分配使用<-(=或者->)来制作整个对象的副本.您可以使用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被复制了,而不仅仅是改变了需要改变的元素.这对于避免大数据以及为什么:=和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个十六进制值(列点矢量和两列中的每一列)保持不变.因此它通过引用进行了真正的修改,完全没有副本.
或者,我们可以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]你会看到一份报告.这是该print方法执行的前10行的副本.在invisible()函数或脚本中使用或调用时,print不会调用该方法.
所有这些也适用于内部功能; 即,即使在函数内:=,set()也不要在写入时复制.如果需要修改本地副本,请x=copy(x)在函数开头调用.但是,请记住data.table大数据(以及小数据的更快编程优势).我们故意不想复制大对象(永远).因此,我们不需要考虑通常的3*工作记忆因素经验法则.我们尝试只需要一个大到一列的工作内存(即工作内存因子为1/ncol而不是3).
sta*_*ant 100
<-和data.table就像基地一样; 即,之后进行子分配<-(例如更改列名或更改元素DT[i,j]<-v)之前不会进行复制.然后它就像基地一样获取整个对象的副本.这就是所谓的写时复制.我认为,更好地称为副本分配!当您使用特殊:=操作员或set*由其提供的功能时,它不会复制data.table.如果您有大数据,则可能需要使用它们.:=并且set*不会复制data.table,甚至不能复制功能.
鉴于此示例数据:
DT <- data.table(a=c(1,2), b=c(11,12))
以下只是将另一个名称"绑定" DT2到当前绑定到该名称的同一数据对象DT:
DT2 <- DT
这从不复制,也从不在基地复制.它只标记数据对象,以便R知道两个不同的名称(DT2和DT)指向同一个对象.因此,如果其中任何一个被分配给后来,R将需要复制该对象.
这也是完美的data.table.该:=不是做这件事.所以以下是故意的错误:=,不仅仅是绑定对象名称:
DT2 := DT    # not what := is for, not defined, gives a nice error
:=用于通过引用进行分配.但你不像在基地那样使用它:
DT[3,"foo"] := newvalue    # not like this
你这样使用它:
DT[3,foo:=newvalue]    # like this
DT通过引用改变了.假设您new通过引用数据对象添加新列,则无需执行此操作:
DT <- DT[,new:=1L]
因为RHS已经DT通过引用而改变了.额外的DT <-是误解了什么:=.你可以在那里写,但它是多余的.
DT通过参考改变:=,即使在函数内:
f <- function(X){
    X[,new2:=2L]
    return("something else")
}
f(DT)   # will change DT
DT2 <- DT
f(DT)   # will change both DT and DT2 (they're the same data object)
data.table适用于大型数据集,请记住.如果你有20GB data.table的内存,那么你需要一种方法来做到这一点.这是一个非常慎重的设计决定data.table.
当然可以复制.您只需要通过使用以下copy()函数告诉data.table您确定要复制20GB数据集:
DT3 <- copy(DT)   # rather than DT3 <- DT
DT3[,new3:=3L]     # now, this just changes DT3 because it's a copy, not DT too.
要避免复制,请不要使用基本类型分配或更新:
DT$new4 <- 1L                 # will make a copy so use :=
attr(DT,"sorted") <- "a"      # will make a copy use setattr() 
如果您想确保通过引用使用更新.Internal(inspect(x))并查看成分的内存地址值(请参阅Matthew Dowle的答案).
写:=在j像,可以让你subassign参照按组.您可以按组引用添加新列.所以这就是为什么:=这样做的内部[...]:
DT[, newcol:=mean(x), by=group]