gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-02 04:49:26 27 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/30210988/

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