gpt4 book ai didi

r - 准确理解何时 data.table 是对另一个 data.table 的引用(而不是其副本)

转载 作者:行者123 更新时间:2023-12-03 04:13:44 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/10225098/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com