ddply和group_by的更快替代品

时间:2018-08-09 11:44:50

标签: r performance dplyr data.table plyr

我正在尝试找出通过myDatac1两列分组的data.frame c2循环的最佳方法是什么。 具体来说,我想遍历c1c2的每个唯一组合,并将某个customFunction应用于myData中的其他列。该customFunction取决于someStatsFunction,后者输出一个data.frame

我通常会使用函数plyr::ddply,但是我的实际数据集有超过1800万行,这并不奇怪,这花费了太长时间。因此,我决定使用dplyr::group_bydplyr::do将方法更改为管道。尽管使用dplyr可以加快问题的速度(请参见下面的最小示例),但仍需要花费一些时间。我听说data.table框架可以大大加快速度(请参见示例here),但是我不知道如何使用它。我想知道是否有人可以使用data.table在下面解决我的问题,以便我也可以对其进行基准测试。

library(plyr)  
library(dplyr)  
library(rbenchmark)  

someStatsFunction  <-  function (x) {
    data.frame(name = 'something', mean = mean(x), sd = sd(x), statx = sqrt(mean(abs(x)))/sd(x)^2)
}

customFunction  <-  function (data) {
    if (!all(sort(data$time) == data$time)) {
        stop('Column \'time\' is not ordered')
    }
    someStatsFunction(data$response)
}

myData  <-  data.frame(c1 = rep(rep(1:50, each = 30), 10), c2 = rep(rep(1:30, 50), 10), response = rnorm(30 * 50 * 10), time = 1:(30 * 50 * 10))

benchmark('testPlyr' = {
            testPlyr   <-  plyr::ddply(myData, .(c1, c2), customFunction)
          },
          'testDplyr' = {
            testDplyr  <-  myData %>% dplyr::group_by(c1,c2) %>% dplyr::do(customFunction(.))
          },
          replications = 3,
          columns      = c('test', 'replications', 'elapsed', 'relative', 'user.self', 'sys.self'))

这是我得到的输出:

       test replications elapsed relative user.self sys.self
2 testDplyr            3   7.416     1.00     7.368    0.060
1  testPlyr            3   8.378     1.13     8.364    0.012

谢谢,
D

@minem的answer

之后

UPDATE

首先,我对上面的示例进行了一些修复,因为代码不正确。

第二,我在上面扩展了最小的可重现示例,以更好地(稍微)反映我的情况。 someStatsFunction可能依赖于data.table中的多个列,并基于从这些多个列派生的统计数据的一些非平凡组合来处理一堆数字。我还增加了myData的大小(因此,与原始示例相比,下面的示例现在花费的时间更长)。无论如何,我认为我设法复制了从plyrdplyr获得的输出。使用data.table可以使其运行得更快,这确实很棒(请参阅下面的基准测试)。但是,代码似乎有点笨拙:

library(plyr)  
library(dplyr)  
library(data.table)  
library(rbenchmark)  

someStatsFunction  <-  function (y, x) {
    x    <-  as.integer(x)
    mod  <-  coef(summary(lm(y ~ x)))
    data.frame(stats1  = 'something',
             intercept = mod[1],
             slope     = mod[2],
             meanx     = mean(x),
             statx     = sqrt(mean(abs(x)))/sd(y)^2)
}

customFunction  <-  function (data) {
    if (!all(sort(data$time) == data$time)) {
        stop('Column \'time\' is not ordered')
    }
    someStatsFunction(y = data$response, x = data$time)
}

myData  <-  data.frame(c1 = rep(rep(1:50, each = 30), 1095), c2 = rep(rep(1:30, 50), 1095), response = rnorm(30 * 50 * 1095), time = rep(seq(as.Date('1981-01-01'), as.Date('1983-12-31'), by = '1 day'), each = 50*30))

benchmark('testPlyr' = {
            testPlyr   <-  plyr::ddply(myData, .(c1, c2), customFunction)
        },
          'testDplyr' = {
            testDplyr  <-  myData %>% dplyr::group_by(c1,c2) %>% dplyr::do(customFunction(.))
        },
          'testDtb' = {
            vNames   <-  c('stats1', 'intercept', 'slope', 'meanx', 'statx')
            dt       <- as.data.table(myData)
            testDtb  <- dt[order(time)][, 
            (vNames) := as.list(someStatsFunction(response, time)), 
            by = .(c1, c2)][, 
            head(.SD, 1), by = .(c1, c2)][, 
            c('response', 'time') := NULL, ]
        },
    replications = 3,
    columns      = c('test', 'replications', 'elapsed', 'relative', 'user.self', 'sys.self'))

这是我得到的输出:

       test replications elapsed relative user.self sys.self
2 testDplyr            3  28.209    3.101    20.841    7.317
3   testDtb            3   9.098    1.000    10.958    0.385
1  testPlyr            3  28.224    3.102    21.741    7.167

速度大大提高。但是,我必须先对数据进行排序,然后再应用someStatsFunction(即无需在if处使用customFunction语句),然后使用列response运行函数。和time中的myData。而且,来自

的原始输出
dt[order(time)][, (vNames) := as.list(someStatsFunction(response, time)), by = .(c1, c2)]

给出的表不会返回1500个值(即c1c2的30 * 50组合),而是多次重复c1c2的组合。此外,它确实会返回原始的responsetime列,尽管我只想绑定c1中的统计信息的c2someStatsFunction的唯一组合(就像在使用plyr和/或dplyr的输出中一样,因此是我的最终代码

testDtb  <- dt[order(time)][, 
(vNames) := as.list(someStatsFunction(response, time)), 
by = .(c1, c2)][, 
head(.SD, 1), by = .(c1, c2)][, 
c('response', 'time') := NULL, ]

反正我还能以更简单的方式获得相同的输出吗?

2 个答案:

答案 0 :(得分:1)

尝试:

dt <- as.data.table(myData)
rr <- dt[, .(
  lon = c1,
  lat = c2,
  name = 'something',
  mean = mean(response),
  sd = sd(response),
  statx = sqrt(abs(response)) / sd(response) ^ 2

), keyby = .(c1, c2)]
rr
#        c1 c2 lon lat      name        mean        sd     statx
#     1:  1  1   1   1 something  0.23841637 0.9384408 0.3253456
#     2:  1  1   1   1 something  0.23841637 0.9384408 0.2421654
#     3:  1  1   1   1 something  0.23841637 0.9384408 0.5321797
#     4:  1  1   1   1 something  0.23841637 0.9384408 0.4136648
#     5:  1  1   1   1 something  0.23841637 0.9384408 1.5863249
# ---                                                        
# 14996: 50 30  50  30 something -0.04082032 0.7156352 2.3970053
# 14997: 50 30  50  30 something -0.04082032 0.7156352 0.8375551
# 14998: 50 30  50  30 something -0.04082032 0.7156352 1.7826972
# 14999: 50 30  50  30 something -0.04082032 0.7156352 1.0293926
# 15000: 50 30  50  30 something -0.04082032 0.7156352 0.1376940

答案 1 :(得分:0)

由于 @ chinsoon12 提供的answer,我得以得到理想的结果:

library(plyr)  
library(dplyr)  
library(data.table)  
library(rbenchmark)  

someStatsFunction  <-  function (y, x) {
    x    <-  as.integer(x)
    mod  <-  coef(summary(lm(y ~ x)))
    data.frame(stats1  = 'something',
             intercept = mod[1],
             slope     = mod[2],
             meanx     = mean(x),
             statx     = sqrt(mean(abs(x)))/sd(y)^2)
}

customFunction  <-  function (data) {
    if (!all(sort(data$time) == data$time)) {
        stop('Column \'time\' is not ordered')
    }
    someStatsFunction(y = data$response, x = data$time)
}

myData  <-  data.frame(c1 = rep(rep(1:50, each = 30), 1095), c2 = rep(rep(1:30, 50), 1095), response = rnorm(30 * 50 * 1095), time = rep(seq(as.Date('1981-01-01'), as.Date('1983-12-31'), by = '1 day'), each = 50*30))

benchmark('testPlyr' = {
            testPlyr   <-  plyr::ddply(myData, .(c1, c2), customFunction)
        },
          'testDplyr' = {
            testDplyr  <-  myData %>% dplyr::group_by(c1,c2) %>% dplyr::do(customFunction(.))
        },
          'testDtb' = {
            testDtb  <-  setDT(myData)[order(time), someStatsFunction(response, time), by=.(c1, c2)]
        },
    replications = 3,
    columns      = c('test', 'replications', 'elapsed', 'relative', 'user.self', 'sys.self'))

这是基准测试的结果:

       test replications elapsed relative user.self sys.self
2 testDplyr            3  68.383    3.976    48.120   20.392
3   testDtb            3  17.201    1.000    17.232    0.008
1  testPlyr            3  57.938    3.368    49.676    8.304

如果您想知道不同方法之间的结果是否相同,请检查:

all.equal(testDplyr, testDtb)
# [1] TRUE
all.equal(testDplyr, testPlyr)
# [1] TRUE