我在理解data.table
的按引用传递属性时遇到了一些问题。有些操作似乎“破坏”了引用,我想确切地了解到底发生了什么。
在从另一个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
行似乎以某种方式“破坏了”引用。我猜这以某种方式调用了一个副本,但我希望完全了解R是如何处理这些操作的,以确保我不会在我的代码中引入潜在的bug。
如果有人能给我解释一下,我将不胜感激。
发布于 2012-04-19 18:49:25
是的,它是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
,而不仅仅是更改需要更改的元素。对于大数据来说,避免这种情况很重要,这也是为什么在data.table
中引入了:=
和set()
。
现在,使用我们复制的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)。
发布于 2013-01-12 19:52:14
只是一个简短的总结。
带有data.table
的<-
就像是base;也就是说,直到使用<-
完成了一个子赋值(比如更改列名或更改一个元素,比如DT[i,j]<-v
),才会进行复制。然后,它会像base一样获取整个对象的副本。这就是所谓的写时复制。我想,更为人所知的是分包复制!当您使用特殊的:=
运算符或data.table
提供的set*
函数时,它不会复制。如果你有很大的数据,你可能想用它们来代替。:=
和set*
不会复制data.table
,即使在函数中也是如此。
给定此示例数据:
DT <- data.table(a=c(1,2), b=c(11,12))
下面的代码只是将另一个名称DT2
“绑定”到当前绑定到名称DT
的同一数据对象:
DT2 <- DT
这不会复制,也不会在base中复制。它只是标记数据对象,以便R知道两个不同的名称(DT2
和DT
)指向同一个对象。因此R将需要复制对象,如果之后将其中任何一个子赋值给。
这对data.table
来说也是完美的。:=
不是用来做这件事的。因此,以下是一个故意的错误,因为:=
不仅仅用于绑定对象名称:
DT2 := DT # not what := is for, not defined, gives a nice error
:=
是引用赋值的子赋值。但是你不能像在base中那样使用它:
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 do的data.table
,那么你需要一种方法来做到这一点。这是data.table
经过深思熟虑的设计决定。
当然,可以复制。您只需使用copy()
函数告诉data.table,您确定要复制20 by的数据集:
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
中编写:=
允许您按引用、按组进行子赋值。您可以通过参照按组添加新列。这就是为什么在[...]
中:=
是这样做的:
DT[, newcol:=mean(x), by=group]
https://stackoverflow.com/questions/10225098
复制相似问题