【问题标题】:faster way to create variable that aggregates a column by id [duplicate]创建按 id 聚合列的变量的更快方法 [重复]
【发布时间】:2012-01-03 18:24:35
【问题描述】:

有没有更快的方法来做到这一点?我想这是不必要的慢,像这样的任务可以通过基本函数来完成。

df <- ddply(df, "id", function(x) cbind(x, perc.total = sum(x$cand.perc)))

我对 R 很陌生。我看过 by()aggregate()tapply(),但根本没有让它们工作或以我想要的方式工作。我不想返回一个较短的向量,而是想将总和附加到原始数据帧。最好的方法是什么?

编辑:这是应用于我的数据的答案的速度比较。

> # My original solution
> system.time( ddply(df, "id", function(x) cbind(x, perc.total = sum(x$cand.perc))) )
   user  system elapsed 
 14.405   0.000  14.479 

> # Paul Hiemstra
> system.time( ddply(df, "id", transform, perc.total = sum(cand.perc)) )
   user  system elapsed 
 15.973   0.000  15.992 

> # Richie Cotton
> system.time( with(df, tapply(df$cand.perc, df$id, sum))[df$id] )
   user  system elapsed 
  0.048   0.000   0.048 

> # John
> system.time( with(df, ave(cand.perc, id, FUN = sum)) )
       user  system elapsed 
      0.032   0.000   0.030 

> # Christoph_J
> system.time( df[ , list(perc.total = sum(cand.perc)), by="id"][df])
   user  system elapsed 
  0.028   0.000   0.028 

【问题讨论】:

  • 我想知道这会做什么(可能只是暗中一枪):aggregate(cand.perc ~ id, data = df, FUN = sum)?或者你可以做tapply(df$cand.perc, INDEX = df$id, FUN = sum)。有关 apply 系列函数的进一步使用,请参阅stackoverflow.com/questions/3505701/…
  • @RomanLuštrik 这些将是原始数据的较低维度,但您可以使用 merge 来获得 OP 想要的这些
  • 我不知何故错过了“攻击原始df的总和”。谢谢!
  • 好的,很难准确地说出你想要什么。如果您进行任何类型的聚合,您将使新向量更短。所以,我猜你要的是两件事之一。一个可能是每一行的总和,附加它。另一个可能是您希望通过 id 获取聚合但保持复制,以便它仍然是作为 df 中的新列的适当长度。这些中的任何一个都正确吗?请澄清问题,以便有人知道。
  • 我正在尝试做后者。 Aggregate() 或 by() 返回长度较短的向量,但我希望按 id 列出每个观察值的总和。

标签: performance r aggregate plyr


【解决方案1】:

由于您对 R 很陌生,而且速度显然对您来说是个问题,我推荐 data.table 包,它真的很快。一种解决问题的方法如下:

library(data.table)
DT <- data.table(ID = rep(c(1:3), each=3),
                 cand.perc = 1:9,
                 key="ID")
DT <- DT[ , perc.total := sum(cand.perc), by = ID]
DT
      ID Perc.total cand.perc
 [1,]  1          6         1
 [2,]  1          6         2
 [3,]  1          6         3
 [4,]  2         15         4
 [5,]  2         15         5
 [6,]  2         15         6
 [7,]  3         24         7
 [8,]  3         24         8
 [9,]  3         24         9

免责声明:我不是 data.table 专家(还 ;-),所以可能有更快的方法来做到这一点。如果您有兴趣使用该软件包,请查看软件包网站以帮助您入门:http://datatable.r-forge.r-project.org/

【讨论】:

  • 这很有趣。速度还不是什么大问题,但是 plyr 版本似乎非常慢。但是,应用您的解决方案会给我以下错误:Error in `[.data.table`(df[, list(perc.total = sum(cand.perc)), by = "id"], : When i is a data.table, x must be sorted to avoid a vector scan of x per row of i
  • @ilprincipe 好吧,即使速度不是这样的问题,我仍然建议你看看data.table:虽然一开始我的语法有点混乱(以前用data.frames),现在觉得它优雅易懂。我几乎完全用data.tables 替换了data.frames(而data.table 只是data.frame,因此您也可以在data.table 上使用所有基本R 函数)。由于您刚刚开始,您可能会发现从一开始就更容易使用。
  • @Christoph_J 我没想过那样做。很不错。如果DT 有很多列,可能会有更快的方法;例如,通过.N 计算聚合rep(),然后使用:= 通过引用添加该列。或者,等到:= by group 实施。
  • @ilprincipe 重新报错,需要在df上设置key。就像 Christoph 使用 key= 所做的那样,或者参见 ?setkey
  • @MatthewDowle 感谢您让我知道这确实是一个很好的方法。我到了 ;-) 无论如何,我期待着 := by group 功能的实现。这将使它更容易。
【解决方案2】:

对于任何类型的聚合,如果您希望生成的向量与输入向量的长度相同,并且在分组向量ave 中分组重复,这就是您想要的。

df$perc.total <- ave(df$cand.perc, df$id, FUN = sum)

【讨论】:

  • 跳。这正是我想要的(我也看过 colSums 等),但我不知道 ave 可以采用除均值之外的功能。这也是最快的解决方案。我将在我的帖子中添加速度比较。
  • ave(df$cand.perc, df$id, FUN = sum)(你最先发布的解决方案)和with(df, ave(cand.perc, id, FUN = sum))有什么区别?如果我没记错的话,效果是完全一样的。为什么要使用with()
  • with 在您不想一直写 data.frame 的名称时很方便。当名字很短并且只有两次时,我不会打扰它。它还具有其他功能,但在这种情况下,结果将是相同的......我已将其回滚到我写的答案。
【解决方案3】:

使用tapply 获取组统计数据,然后将它们添加回您的数据集中。

可重现的例子:

means_by_wool <- with(warpbreaks, tapply(breaks, wool, mean))
warpbreaks$means.by.wool <- means_by_wool[warpbreaks$wool]

针对您的场景的未经测试的解决方案:

sum_by_id <- with(df, tapply(cand.perc, id, sum))
df$perc.total <- sum_by_id[df$id]

【讨论】:

  • 不客气,尽管 John 的解决方案更好。请记住,您可以为所有有用的答案投票。
【解决方案4】:

如果以上都不符合您的需求,您可以尝试转置您的数据

dft=t(df)

然后使用aggregate

dfta=aggregate(dft,by=list(rownames(dft)),FUN=sum)

接下来取回你的行名

rownames(dfta)=dfta[,1]
dfta=dfta[,2:ncol(dfta)]

转回原来的方向

df2=t(dfta)

并绑定到原始数​​据

newdf=cbind(df,df2)

【讨论】:

  • 其实,我放弃 stata 的原因之一就是这个。必须转置数据来完成这样的任务既乏味又不必要。
【解决方案5】:

你为什么使用 cbind(x, ...) ddply 的输出会自动追加。这应该有效:

ddply(df, "id", transform, perc.total = sum(cand.perc))

摆脱多余的 cbind 应该会加快速度。

【讨论】:

  • 这不会附加到原始数据集,而是返回一个比原始数据集更短的数据集(id 唯一),只有 id 和 cand.perc 作为变量。
  • 尝试转换而不是汇总。
  • 有效,但同样慢。
  • @ilprincipe - 如果速度是主要问题,请注意上面的建议:data.table,或者考虑设置适当的后端并将parallel = TRUE 参数添加到 plyr。提供有关您的问题的规模和范围的更多详细信息可以让人们给出更好的答案,尽管我不得不想象以上五个中的一个应该足以解决几乎所有问题。
  • 有关使用 plyr 进行并行处理的提示,请参阅我博客上的以下帖子:numbertheory.nl/2011/11/14/…
【解决方案6】:

您还可以加载您最喜欢的 foreach 后端并尝试为 ddply 使用 .parallel=TRUE 参数。

【讨论】:

    猜你喜欢
    • 2012-02-02
    • 1970-01-01
    • 1970-01-01
    • 2018-08-09
    • 1970-01-01
    • 2013-03-27
    • 2021-11-18
    • 1970-01-01
    • 2020-01-03
    相关资源
    最近更新 更多