准确地理解data.table何时是对另一个data.table的引用

我很难理解data.table..有些操作似乎“打破”了引用,我想确切地了解发生了什么。

关于创建data.table从另一个data.table(经<-,然后通过以下方法更新表:=,原来的表也被修改了。这是预期的,如下所示:

?data.table::copyStackOverflow: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 12newDT <- DT        
# reference, not copynewDT[1, a := 100] # modify new DTprint(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 operationnewDT[1, a := 100]print(DT)#      a  b# [1,] 1 11# [2,] 2 12

所以看起来newDT$b[2] <- 200行在某种程度上破坏了引用。我猜想这会以某种方式调用一个副本,但我想完全了解R是如何处理这些操作的,以确保我不会在代码中引入潜在的bug。

如果有人能向我解释这件事,我会非常感激的。


潇潇雨雨
浏览 789回答 2
2回答

肥皂起泡泡

是的,它是在R中使用的<-(或=或->)的副本。整体对象。您可以使用tracemem(DT)和.Internal(inspect(DT)),如下所示。这个data.table特征:=和set()通过引用将它们传递给任何对象。因此,如果该对象以前被复制(通过subas签名)<-或显式copy(DT))然后是通过引用修改的副本。DT <- data.table(a = c(1, 2), b = c(11, 12))&nbsp;newDT <- DT&nbsp;.Internal(inspect(DT))# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)#&nbsp; &nbsp;@00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2#&nbsp; &nbsp;@00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,12# ATTRIB:&nbsp; # ..snip...Internal(inspect(newDT))&nbsp; &nbsp;# precisely the same object at this point# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)#&nbsp; &nbsp;@00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2#&nbsp; &nbsp;@00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,12# ATTRIB:&nbsp; # ..snip..tracemem(newDT)# [1] "<0x0000000003b7e2a0"newDT$b[2] <- 200# tracemem[0000000003B7E2A0 -> 00000000040ED948]:&nbsp;# tracemem[00000000040ED948 -> 00000000040ED830]: .Call copy $<-.data.table $<-&nbsp;.Internal(inspect(DT))# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),TR,ATT] (len=2, tl=100)#&nbsp; &nbsp;@00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2#&nbsp; &nbsp;@00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,12# ATTRIB:&nbsp; # ..snip...Internal(inspect(newDT))# @0000000003D97A58 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)#&nbsp; &nbsp;@00000000040ED7F8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2#&nbsp; &nbsp;@00000000040ED8D8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,200# ATTRIB:&nbsp; # ..snip..注意,即使是a向量被复制(不同的十六进制值表示向量的新副本),尽管a没有改变。甚至整个b被复制,而不仅仅是更改需要更改的元素。对于大型数据来说,这是很重要的避免,以及为什么:=和set()被介绍给data.table.现在,用我们的拷贝newDT我们可以参考修改:newDT#&nbsp; &nbsp; &nbsp; a&nbsp; &nbsp;b# [1,] 1&nbsp; 11# [2,] 2 200newDT[2, b := 400]#&nbsp; &nbsp; &nbsp; a&nbsp; &nbsp;b&nbsp; &nbsp; &nbsp; &nbsp; # See FAQ 2.21 for why this prints newDT# [1,] 1&nbsp; 11# [2,] 2 400.Internal(inspect(newDT))# @0000000003D97A58 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)#&nbsp; &nbsp;@00000000040ED7F8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2#&nbsp; &nbsp;@00000000040ED8D8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,400# ATTRIB:&nbsp; # ..snip ..注意,所有3个十六进制值(列点的向量,以及2列中的每一列)都保持不变。因此,它确实是通过引用而修改的,根本没有副本。或者,我们可以修改原始DT引用:DT[2, b := 600]#&nbsp; &nbsp; &nbsp; a&nbsp; &nbsp;b# [1,] 1&nbsp; 11# [2,] 2 600.Internal(inspect(DT))# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)#&nbsp; &nbsp;@00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2#&nbsp; &nbsp;@00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,600#&nbsp; &nbsp;ATTRIB:&nbsp; # ..snip..这些十六进制值与我们看到的原始值相同。DT上面。类型example(copy)有关更多示例,请使用tracemem以及与.的比较data.frame.顺便说一下,如果你tracemem(DT)然后DT[2,b:=600]你会看到一份报告。的前10行的副本。print方法可以。用invisible()或在函数或脚本中调用时,print方法不被调用。所有这一切也适用于内部功能;即,:=和set()不要在写时复制,即使在函数中也是如此。如果需要修改本地副本,请调用x=copy(x)在函数开始的时候。但是,记住data.table适用于大数据(以及小数据的快速编程优势)。我们故意不想复制大对象(永远)。因此,我们不需要考虑通常的3*工作记忆因素的经验法则。我们试着只需要一个列那么大的工作记忆(即工作记忆系数为1/nol,而不是3)。

繁花如伊

简单总结一下。<-带着data.table就像基本;也就是说,在完成以下操作之前,将不进行复制。<-(例如更改列名或更改元素,如DT[i,j]<-v)。然后,它获取整个对象的副本,就像基一样。这就是所谓的抄写。我想,应该更好地称为“仿冒”!当您使用“特殊”时,它不会复制。:=运算符,或set*由data.table..如果您有大量数据,您可能希望使用它们。:=和set*不会复制data.table甚至在职能范围内。给定此示例数据:DT&nbsp;<-&nbsp;data.table(a=c(1,2),&nbsp;b=c(11,12))以下只是“绑定”另一个名称DT2绑定到当前绑定到名称的相同数据对象。DT&nbsp;:DT2&nbsp;<-&nbsp;DT这永远不会复制,也不会在基础上复制。它只是标记数据对象,以便R知道两个不同的名称(DT2和DT)指向同一个对象。所以R需要复制对象,如果两者都是副签名以后的事。那是完美的data.table也是。这个:=不是为了这么做。因此,下面是一个故意的错误,因为:=不只是用于绑定对象名称:DT2&nbsp;:=&nbsp;DT&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;not&nbsp;what&nbsp;:=&nbsp;is&nbsp;for,&nbsp;not&nbsp;defined,&nbsp;gives&nbsp;a&nbsp;nice&nbsp;error:=是为了次签署参考一下。但你不像你在基地里那样使用它:DT[3,"foo"]&nbsp;:=&nbsp;newvalue&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;not&nbsp;like&nbsp;this你就这样用它:DT[3,foo:=newvalue]&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;like&nbsp;this改变了DT参考一下。假设您添加了一个新列new通过引用数据对象,没有必要这样做:DT&nbsp;<-&nbsp;DT[,new:=1L]因为RHS已经改变了DT参考一下。额外的DT <-就是误解了什么:=的确如此。你可以把它写在那里,但它是多余的。DT因引用而更改:=,即使在职能范围内:f&nbsp;<-&nbsp;function(X){ &nbsp;&nbsp;&nbsp;&nbsp;X[,new2:=2L] &nbsp;&nbsp;&nbsp;&nbsp;return("something&nbsp;else")}f(DT)&nbsp;&nbsp;&nbsp;#&nbsp;will&nbsp;change&nbsp;DTDT2&nbsp;<-&nbsp;DT f(DT)&nbsp;&nbsp;&nbsp;#&nbsp;will&nbsp;change&nbsp;both&nbsp;DT&nbsp;and&nbsp;DT2&nbsp;(they're&nbsp;the&nbsp;same&nbsp;data&nbsp;object)data.table是用于大型数据集的,请记住。如果你有20 GBdata.table在记忆中,你需要一个方法来做到这一点。这是一个非常慎重的设计决策data.table.当然可以复制。您只需要告诉data.table,您肯定要复制您的20 GB数据集,方法是使用copy()职能:DT3&nbsp;<-&nbsp;copy(DT)&nbsp;&nbsp;&nbsp;#&nbsp;rather&nbsp;than&nbsp;DT3&nbsp;<-&nbsp;DTDT3[,new3:=3L]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;now,&nbsp;this&nbsp;just&nbsp;changes&nbsp;DT3&nbsp;because&nbsp;it's&nbsp;a&nbsp;copy,&nbsp;not&nbsp;DT&nbsp;too.为了避免复制,不要使用基类型分配或更新:DT$new4&nbsp;<-&nbsp;1L&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;will&nbsp;make&nbsp;a&nbsp;copy&nbsp;so&nbsp;use&nbsp;:=attr(DT,"sorted")&nbsp;<-&nbsp;"a"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;will&nbsp;make&nbsp;a&nbsp;copy&nbsp;use&nbsp;setattr()如果您希望确保您正在通过引用进行更新,请使用.Internal(inspect(x))然后看看成分的内存地址值(见MatthewDowle的答案)。写字:=在……里面j这样你就可以参考按组..可以按组按引用添加新列。所以这就是为什么:=在里面是这样做的[...]&nbsp;:DT[,&nbsp;newcol:=mean(x),&nbsp;by=group]
打开App,查看更多内容
随时随地看视频慕课网APP