【问题标题】:how to speed up cross-column calculation in grouped data frame如何加快分组数据框中的跨列计算
【发布时间】:2019-04-07 05:50:14
【问题描述】:

--- 从rstudio community forum 交叉发布tidyverse 之外的潜在解决方案。

基本情况是组之间的计算是独立的,但是每个组都需要输入一些从自己计算出来的参数。一个简单的例子是找到小于列最大值一半的第一个元素的索引。唯一的变化是一列X 需要使用通过其他列A, B, C 计算的最大值。

我有一个使用group_map(类似于do)的解决方案来解决我在grouped calculation 上的问题。但性能似乎不是最佳的。与group_map 一起使用时,summarise_at 似乎需要更长的时间(与没有它的时间相比)

library(tidyverse)

times <- 1e5
cols <- 4
df3 <- as.data.frame(x = matrix(rnorm(times * cols, mean = 5), ncol = cols)) %>% 
   rename(A = V1, B = V2, C = V3, X = V4)

df3 <- cbind(grp = rep(seq_len(1e3), each = 100), df3) %>% 
   group_by(grp)

system.time(
  df3 %>% 
    group_map(~
    { 
      all_max <- summarise_at(., vars(A:C), max) %>% mutate(X = rowMeans(.))
      map2_df(., all_max, ~match(TRUE, .x < 0.5 * .y))
    }
    )
)
#>    user  system elapsed 
#>    3.87    0.00    3.98

system.time(
  df3 %>% summarise_at(vars(A:C), max) %>% mutate(X = rowMeans(.))
)
#>    user  system elapsed 
#>    0.02    0.00    0.01

system.time(
  df3 %>% summarise_at(vars(A:X), ~match(TRUE, . < 0.5 * max(.)))
)  
#>    user  system elapsed 
#>    0.25    0.02    0.26

reprex package (v0.2.1) 于 2019 年 4 月 5 日创建

有提高性能的想法吗?似乎大多数函数都是基于列的,我还没有找到有效地完成这个简单任务的解决方案。

【问题讨论】:

  • 也许您想描述您的问题而不是发布您的解决方案? xyproblem.info
  • 问题是关于解决方案的性能。我将进行编辑以澄清。
  • 我真的不明白你想用你的代码做什么,你能再补充一些吗?您提供的三个 sn-ps 都提供不同的输出。
  • 另外,如果没有set.seed,对你想要做的事情进行逆向工程就特别困难
  • @MichaelChirico 抱歉,第一个给出了我想要的结果。第二个和第三个只是检查操作的时间。我想证明summarisegrooup_map 一起使用时要慢得多

标签: r data.table tidyverse


【解决方案1】:

据我所知,这在我的机器上不到半秒就完成了与您的代码相同的工作:

library(data.table)
DT = as.data.table(matrix(rnorm(times * cols, mean = 5), times, cols))
setnames(DT, c('A', 'B', 'C', 'X'))
DT[ , grp := rep(seq_len(1e3), each = 100)]

setkey(DT, grp)

DT[DT[ , lapply(.SD, max), keyby = grp, .SDcols = !'X'
       ][ , X := Reduce(`+`, .SD)/ncol(.SD), .SDcols = !'grp'], {
  i.A; i.B; i.C; i.X
  lapply(names(.SD), function(j) 
    which.max(eval(as.name(j)) < .5 * eval(as.name(paste0('i.', j)))))
}, on = 'grp', by = .EACHI, .SDcols = !'grp']
#        grp V1 V2 V3 V4
#    1:    1  3 30  1  4
#    2:    2  6 15  4 10
#    3:    3  2  5  7  2
#    4:    4  8 16  5  8
#    5:    5 10  3  1  7
#   ---                 
#  996:  996 11  5  3 13
#  997:  997  3  3  3 11
#  998:  998 14 21  2 10
#  999:  999 18  2  1 41
# 1000: 1000  8  7  3  3

本质上,您正在创建相关上限的查找表并重新加入。

你可以这样分开:

lookup = 
  DT[ , lapply(.SD, max), keyby = grp, .SDcols = !'X'
     ][ , X := Reduce(`+`, .SD)/ncol(.SD), .SDcols = !'grp']
DT[lookup, on = 'grp', {
  i.A; i.B; i.C; i.X
  lapply(names(.SD), function(j) 
    which.max(eval(as.name(j)) < .5 * eval(as.name(paste0('i.', j)))))
}, by = .EACHI, .SDcols = !'grp']

一旦分离,您还可以获得获取get 的灵活性(根据我的经验,它比eval(as.name()) 慢):

DT[lookup, on = 'grp', {
  lapply(names(.SD), function(j) 
    which.max(eval(as.name(j)) < .5 * get(paste0('i.', j))))
}, by = .EACHI, .SDcols = !'grp']
#        grp V1 V2 V3 V4
#    1:    1  1  5 26  3
#    2:    2  6  7  3  4
#    3:    3  2  6  1 13
#    4:    4  5  2 12  5
#    5:    5  9 12  2  4
#   ---                 
#  996:  996  1  3  4  1
#  997:  997  1  6  3 13
#  998:  998 10 13  9  8
#  999:  999  2  4 13  4
# 1000: 1000  7 30 19 14

【讨论】:

  • 确实快多了。看起来连接和计算是一起完成的,我认为dplyr 是不可能的。你能帮忙解释一下括号中的计算吗?第一行i.A; i.B; i.C; i.X 是做什么的?
  • @Dong 这只是一种强制将这些变量填充到j 环境中的技巧——如果我们排除它,下一行将出错,提示找不到i.A。此外,计算可以很容易地分开;查看我的编辑
  • 感谢您的解释。你有没有办法在j 环境中拥有所有变量(分组变量除外)?我的实际用例有不确定的列数。
  • @Dong 查看编辑;我引导this相关答案;我知道这很笨拙,事实上,使这更流畅是一些出色的功能请求的主题:#935#1180
  • 谢谢。我确实很难理解,可能不是一个通用的解决方案。例如,如果我的函数比较复杂,需要单独定义,如何将向量(此处为eval(as.name(j)))和数字(此处为get(paste0('i.', j)))传递给函数。该函数似乎需要能够搜索调用它的环境。
猜你喜欢
  • 1970-01-01
  • 2019-12-13
  • 2019-04-10
  • 2015-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-26
相关资源
最近更新 更多