首页 > 解决方案 > 逐行加速data.frame的子集,避免循环(dplyr,R)

问题描述

我是新手dplyr,我正在努力解决我认为是一个简单的功能。我有一个类似于以下的数据集:

require(dplyr)
dat <- data.frame(t = rep(seq(1, 5, 1),4), id = rep(c(rep("A",5), rep("B",5), rep("C",5), rep("D",5)), 1),
                  x = 1:20, y = 51:70, h = c(rep(1,10), rep(0,10) ) )
dat <- arrange(dat, t)
dat <- data.frame(dat, group = c("B", "A", "A", "A", "A", "B", "C", "D", "A", "B", "D", "C", "A", "D", "C", "A", "A", "C", "C", "B") )
dat

我想将一个新列附加到dat包含以下操作的数据集:

  1. 对于每一行,例如带有 的第 3 行id == C,取剩余的行,使其 in 的值group与起始的 不同id,在这种情况下为 C
  2. 按时间分组观察t
  3. id如果(在这种情况下id,第 3 行中的 C)1在 col 中具有值,则执行以下操作h:将所有值(来自基于 的组t)相加x并除以和中的值的标准差yx来自基于的组t)。Ifid的值为0in col hplace a 0。如果没有观察到,代码应该放置一个零。

例如,对于id Ain row 1,代码应该产生 a 0,因为在 time 的所有观察结果t == 1都有group == A。对于id B2中的代码应该产生(11 + 16) / sd(c(11, 16, 61, 66)).

如何以dplyr不包括的方式或任何其他方式执行此操作looping?谢谢你。

数据看起来像

dat
#    t id  x  y h group
# 1  1  A  1 51 1     B
# 2  1  B  6 56 1     A
# 3  1  C 11 61 0     A
# 4  1  D 16 66 0     A
# 5  2  A  2 52 1     A
# 6  2  B  7 57 1     B
# 7  2  C 12 62 0     C
# 8  2  D 17 67 0     D
# 9  3  A  3 53 1     A
# 10 3  B  8 58 1     B
# 11 3  C 13 63 0     D
# 12 3  D 18 68 0     C
# 13 4  A  4 54 1     A
# 14 4  B  9 59 1     D
# 15 4  C 14 64 0     C
# 16 4  D 19 69 0     A
# 17 5  A  5 55 1     A
# 18 5  B 10 60 1     C
# 19 5  C 15 65 0     C
# 20 5  D 20 70 0     B

我尝试了以下方法,但没有产生正确的结果。

dat %>% 
  group_by(t) %>% 
  mutate(new = ifelse(id != group, h * (sum(x) /map_dbl(row_number(), ~ 
      sd(c(x[-.x], y[-.x]) ))) , 0) )

标签: rfor-loopdplyrtidyverse

解决方案


这应该只是说明data.tablesvs的速度表现dplyr。我只是把变异的整个 ifelse 打包到一个 data.table 操作中,并用 ( by = t) 分组。所以结果不会是期望的结果,但是对于 dplyr 和 data.tables 的结果至少是相同的。

library(data.table)
library(dplyr)

datDT <- data.table(dat)

DTF <- function(){
  d <- datDT[ , new := ifelse(  id != group, h * (sum(x) /
                      map_dbl(row_number(x), ~ 
                                sd(c(x[-.x], y[-.x])))) , 0) , by = t]
  d
}
DPF <- function(){
  d <- dat %>% 
    group_by(t) %>% 
    mutate(new = ifelse(id != group, h * (sum(x) /map_dbl(row_number(x), ~ 
                                                            sd(c(x[-.x], y[-.x]) ))) , 0) )
  d
}

dtres = DTF()
dplres = DPF()

all.equal(dtres, data.table(dplres))


library(microbenchmark)
mc <- microbenchmark(times = 100,
                     DT =  DTF(),
                     DPLYR = DPF()
)

mc

Unit: milliseconds
  expr       min        lq      mean    median        uq      max neval cld
    DT  7.428605  7.821919  8.324179  8.056762  8.429851 15.39028   100  a 
 DPLYR 11.154076 11.439025 11.895716 11.720050 12.139022 16.40934   100   b

收益不是很大,但仍然很明显,我相信仍然可以通过设置键、摆脱 ifelse 等来完成一些优化,但我把它留给了真正的 data.table 专家:)。

因此,如果您对两者都不熟悉,也许可以深入研究 data.tables,因为您也可以将 dplyr-verbs 与它们一起使用(如下所示)并且比使用tbl结构稍微快一些。

dtres %>% 
  group_by(t) %>% 
  summarise(mN = mean(new))

推荐阅读