gpt4 book ai didi

r - “update by reference”与浅拷贝

转载 作者:行者123 更新时间:2023-12-02 04:30:04 26 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左右推出的。并且此时,基数R不是浅拷贝,而是深表复制。从3.1.0开始引入浅拷贝。

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

这个基本的R方法与data.table等效方法有何不同?差异仅与速度有关,还是与内存使用有关?

在基本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中,我们就地修改。即使在 DF3DF4列中, 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.tableDF1期间复制到 DF2。那是:
    DT2 = DT1[, c("x", "y")]

    会产生一个副本,因为我们不能通过浅副本上的引用进行子分配。它将更新所有克隆。

    最好的是,如果我们可以无缝集成浅拷贝功能,但要跟踪特定对象的列是否具有多个引用,并在可能的情况下逐个引用进行更新。 R的升级参考计数功能在这方面可能非常有用。无论如何,我们正在努力。

    对于最后一个问题:

    “什么时候差异最大?”
  • 仍然有些人必须使用旧版本的R,在这些版本中不可避免地会产生深层复制。
  • 这取决于要复制的列数,因为您对其执行了操作。当然,最糟糕的情况是您已经复制了所有列。
  • 在像this这样的情况下,浅拷贝不会受益。
  • 当您想为每个组更新data.frame的列时,并且组太多。
  • 当您想要基于与另一个data.table DT1的联接来更新诸如data.table DT2的列时,可以通过以下方式完成:
    DT1[DT2, col := i.val]

    其中i.valDT2列(i参数)中用于匹配行的值。该语法允许非常高效地执行此操作,而不必首先联接整个结果,然后更新所需的列。

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

    希望这可以帮助。这已经是一个冗长的答案。我将留下您可能留给他人或让您解决的任何问题(此答案中没有任何明显的误解)。

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

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