gpt4 book ai didi

r - 准确了解 data.table 何时是对另一个 data.table 的引用(相对于副本)

转载 作者:行者123 更新时间:2023-12-03 17:53:23 25 4
gpt4 key购买 nike

我在理解 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 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 是如何处理这些操作的,以确保我不会在我的代码中引入潜在的错误。

如果有人可以向我解释这一点,我将不胜感激。

最佳答案

是的,它是 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 个十六进制值(列点向量和 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]你会看到一份报告。这是 print 的前 10 行的副本。方法可以。当用 invisible() 包裹时或者在函数或脚本中调用时, print方法没有被调用。

所有这些也适用于函数内部;即 :=set()不要在写入时复制,即使在函数内也是如此。如果您需要修改本地副本,请调用 x=copy(x)在函数的开头。但是,请记住 data.table适用于大数据(以及小数据更快的编程优势)。我们故意不想复制大对象(永远)。因此,我们不需要考虑通常的 3* 工作内存因子经验法则。我们尝试只需要一列大小的工作内存(即工作内存因子为 1/ncol 而不是 3)。

关于r - 准确了解 data.table 何时是对另一个 data.table 的引用(相对于副本),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15360344/

25 4 0