gpt4 book ai didi

r - "update by reference"vs 浅拷贝

转载 作者:行者123 更新时间:2023-12-04 01:51:22 25 4
gpt4 key购买 nike

函数set或表达式 :=[.data.table允许用户通过引用更新 data.tables。这种行为与将操作的结果重新分配给原始 data.frame 有何不同?

keepcols<-function(DF,cols){
eval.parent(substitute(DF<-DF[,cols,with=FALSE]))
}
keeprows<-function(DF,i){
eval.parent(substitute(DF<-DF[i,]))
}

因为表达式中的 RHS <-是 R 的最新版本中初始数据帧的浅拷贝,这些函数似乎非常有效。这个基本 R 方法与 data.table 等效方法有何不同?差异仅与速度有关还是与内存使用有关?什么时候差异最大?

一些(速度)基准。当数据集只有两个变量时,速度差异似乎可以忽略不计,并且随着变量的增加而变得更大。
library(data.table)

# Long dataset
N=1e7; K=100
DT <- data.table(
id1 = sample(sprintf("id%03d",1:K), N, TRUE),
v1 = sample(5, N, TRUE)
)
system.time(DT[,a_inplace:=mean(v1)])
user system elapsed
0.060 0.013 0.077
system.time(DT[,a_inplace:=NULL])
user system elapsed
0.044 0.010 0.060


system.time(DT <- DT[,c(.SD,a_usual=mean(v1)),.SDcols=names(DT)])
user system elapsed
0.132 0.025 0.161
system.time(DT <- DT[,list(id1,v1)])
user system elapsed
0.124 0.026 0.153


# Wide dataset
N=1e7; K=100
DT <- data.table(
id1 = sample(sprintf("id%03d",1:K), N, TRUE),
id2 = sample(sprintf("id%03d",1:K), N, TRUE),
id3 = sample(sprintf("id%010d",1:(N/K)), N, TRUE),
v1 = sample(5, N, TRUE),
v2 = sample(1e6, N, TRUE),
v3 = sample(round(runif(100,max=100),4), N, TRUE)
)
system.time(DT[,a_inplace:=mean(v1)])
user system elapsed
0.057 0.014 0.089
system.time(DT[,a_inplace:=NULL])
user system elapsed
0.038 0.009 0.061

system.time(DT <- DT[,c(.SD,a_usual=mean(v1)),.SDcols=names(DT)])
user system elapsed
2.483 0.146 2.602
system.time(DT <- DT[,list(id1,id2,id3,v1,v2,v3)])
user system elapsed
1.143 0.088 1.220

最佳答案

data.table , :=和所有 set*函数通过引用更新对象。这是在 2012 年 IIRC 左右的某个时候引入的。而此时,base R 不是浅拷贝,而是深拷贝。从 3.1.0 开始引入浅拷贝。

这是一个冗长/冗长的答案,但我认为这回答了您的前两个问题:

How is this base R method different from the data.table equivalent? Is the difference related only to speed or also memory use?



在 base R v3.1.0+ 中,当我们这样做时:
DF1 = data.frame(x=1:5, y=6:10, z=11:15)
DF2 = DF1[, c("x", "y")]
DF3 = transform(DF2, y = ifelse(y>=8L, 1L, y))
DF4 = transform(DF2, y = 2L)
  • 来自 DF1DF2 ,两列都只是浅复制。
  • 来自 DF2DF3栏目y必须单独复制/重新分配,但 x再次被浅复制。
  • 来自 DF2DF4 ,同(2)。

  • 也就是说,只要列保持不变,列就会被浅复制——在某种程度上,除非绝对必要,否则复制会被延迟。

    data.table ,我们就地修改。甚至在 DF3 期间的意思和 DF4栏目 y不会被复制。
    DT2[y >= 8L, y := 1L] ## (a)
    DT2[, y := 2L]

    在这里,由于 y已经是一个整数列,我们正在按整数修改它,通过引用,这里根本没有新的内存分配。

    当您想通过引用进行子分配(标记为上面的 (a))时,这也特别有用。这是我们在 data.table 中非常喜欢的一个方便的功能.

    免费提供的另一个优势(我从我们的互动中了解到)是,当我们必须将 data.table 的所有列转换为 numeric 时。输入,例如, character类型:
    DT[, (cols) := lapply(.SD, as.numeric), .SDcols = cols]

    在这里,由于我们通过引用更新,每个字符列都被引用替换为它的数字对应项。在替换之后,不再需要较早的字符列并且可以进行垃圾收集。但是,如果您要使用基本 R 来执行此操作:
    DF[] = lapply(DF, as.numeric)

    所有列都必须转换为数字,并且必须保存在一个临时变量中,然后最后将被分配回 DF .这意味着,如果您有 10 列和 1 亿行,每个都是字符类型,那么您的 DF占用空间:
    10 * 100e6 * 4 / 1024^3 = ~ 3.7GB

    numeric类型是大小的两倍,我们总共需要 7.4GB + 3.7GB为我们提供使用基数 R 进行转换的空间。

    但请注意 data.table复制期间 DF1DF2 .那是:
    DT2 = DT1[, c("x", "y")]

    结果是一个副本,因为我们不能在浅副本上通过引用进行子分配。它将更新所有克隆。

    如果我们可以无缝集成浅拷贝功能,但跟踪特定对象的列是否有多个引用,并尽可能按引用更新,那将会很棒。 R 的升级引用计数功能在这方面可能非常有用。无论如何,我们正在为此努力。

    对于您的最后一个问题:

    "When is the difference most sizeable?"


  • 仍然有人不得不使用旧版本的 R,在这些地方无法避免深拷贝。
  • 这取决于由于您对其执行的操作而被复制的列数。当然,最坏的情况是您已经复制了所有列。
  • 有类似 this 的案例浅复制不会受益。
  • 当您想为每个组更新 data.frame 的列,并且组太多时。
  • 当您想更新例如 data.table DT1 的列时基于与另一个数据表的连接 DT2 - 这可以这样做:
    DT1[DT2, col := i.val]

    哪里i.指的是来自 val 的值DT2的栏目(i 参数)用于匹配行。此语法允许非常有效地执行此操作,而不必先连接整个结果,然后更新所需的列。

  • 总而言之,通过引用更新可以节省大量时间并且速度很快。但是人们有时不喜欢就地更新对象,并愿意为此牺牲速度/内存。除了现有的引用更新之外,我们还试图找出如何最好地提供此功能。

    希望这可以帮助。这已经是一个相当冗长的答案了。我会将您可能留给其他人或让您弄清楚的任何问题(除了此答案中的任何明显误解)。

    关于r - "update by reference"vs 浅拷贝,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25945392/

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