准确地理解data.table何时是对另一个data.table的引用(vs对另一个data.table的副本)

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (22)

关于创建data.table从另一个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现在不再修改:

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中使用的<-(或=->)的副本。整体对象。可以使用tracemem(DT).Internal(inspect(DT)),如下所示。大data.table特征:=set()通过引用将它们传递给任何对象。

因此,如果该对象以前被复制(通过subas签名)<-或者是明确的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个十六进制值(列点的向量,以及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行的副本。print方法确实如此。用invisible()或在函数或脚本中调用时,print方法不被调用。

所有这一切也适用于内部功能;即,:=set()即使在函数中,也不要在写时复制。如果需要修改本地副本,请调用x=copy(x)在函数开始的时候。但是,记住data.table适用于大数据(以及小数据的快速编程优势)。

用户回答回答于

简单总结一下。

<-带着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知道两个不同的名称(DT2DT)指向同一个对象。所以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是用于大型数据集的,请记住。如果你有20 GBdata.table在记忆中,你需要一个方法来做到这一点。这是一个非常慎重的设计决策data.table.

只需要告诉data.table,肯定要复制20 GB的数据集,方法是使用copy()职能:

DT3 <- copy(DT)   # rather than DT3 <- DT
DT[,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))然后看看构成部分的内存地址值(见MatthewDowle的答案)。

写作:=j这样你就可以参考按组.可以按组添加新列。所以这就是为什么:=在里面是这样做的[...]

DT[, newcol:=mean(x), by=group]

扫码关注云+社区