【问题标题】:R use ddply or aggregateR 使用 ddply 或聚合
【发布时间】:2012-12-27 02:37:31
【问题描述】:

我有一个包含 3 列的数据框:custId、saleDate、DelivDateTime。

> head(events22)
     custId            saleDate      DelivDate
1 280356593 2012-11-14 14:04:59 11/14/12 17:29
2 280367076 2012-11-14 17:04:44 11/14/12 20:48
3 280380097 2012-11-14 17:38:34 11/14/12 20:45
4 280380095 2012-11-14 20:45:44 11/14/12 23:59
5 280380095 2012-11-14 20:31:39 11/14/12 23:49
6 280380095 2012-11-14 19:58:32 11/15/12 00:10

这是输出:

> dput(events22)
structure(list(custId = c(280356593L, 280367076L, 280380097L, 
280380095L, 280380095L, 280380095L, 280364279L, 280364279L, 280398506L, 
280336395L, 280364376L, 280368458L, 280368458L, 280368456L, 280368456L, 
280364225L, 280391721L, 280353458L, 280387607L, 280387607L), 
    saleDate = structure(c(1352901899.215, 1352912684.484, 1352914714.971, 
    1352925944.429, 1352925099.247, 1352923112.636, 1352922476.55, 
    1352920666.968, 1352915226.534, 1352911135.077, 1352921349.592, 
    1352911494.975, 1352910529.86, 1352924755.295, 1352907511.476, 
    1352920108.577, 1352906160.883, 1352905925.134, 1352916810.309, 
    1352916025.673), class = c("POSIXct", "POSIXt"), tzone = "UTC"), 
    DelivDate = c("11/14/12 17:29", "11/14/12 20:48", "11/14/12 20:45", 
    "11/14/12 23:59", "11/14/12 23:49", "11/15/12 00:10", "11/14/12 23:35", 
    "11/14/12 22:59", "11/14/12 20:53", "11/14/12 19:52", "11/14/12 23:01", 
    "11/14/12 19:47", "11/14/12 19:42", "11/14/12 23:31", "11/14/12 23:33", 
    "11/14/12 22:45", "11/14/12 18:11", "11/14/12 18:12", "11/14/12 19:17", 
    "11/14/12 19:19")), .Names = c("custId", "saleDate", "DelivDate"
), row.names = c("1", "2", "3", "4", "5", "6", "7", "8", "9", 
"10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20"
), class = "data.frame")

我正在尝试为每个custId 查找最新的saleDateDelivDate

我可以像这样使用 plyr::ddply 做到这一点:

dd1 <-ddply(events22, .(custId),.inform = T, function(x){
x[x$saleDate == max(x$saleDate),"DelivDate"]
})

我的问题是是否有更快的方法来执行此操作,因为 ddply 方法有点耗时(完整的数据集约为 400k 行)。我看过使用aggregate(),但不知道如何获得除我排序依据之外的值。

有什么建议吗?

编辑:

这是 10k 行 @ 10 次迭代的基准测试结果:

      test replications elapsed relative user.self
2   AGG2()           10    5.96    1.000      5.93
1   AGG1()           10   20.87    3.502     20.75
5 DATATABLE()        10   61.32        1     60.31
3  DDPLY()           10   80.04   13.430     79.63
4 DOCALL()           10   90.43   15.173     88.39

编辑2: 虽然最快的 AGG2() 并没有给出正确的答案。

    > head(agg2)
     custId            saleDate      DelivDate
1 280336395 2012-11-14 16:38:55 11/14/12 19:52
2 280353458 2012-11-14 15:12:05 11/14/12 18:12
3 280356593 2012-11-14 14:04:59 11/14/12 17:29
4 280364225 2012-11-14 19:08:28 11/14/12 22:45
5 280364279 2012-11-14 19:47:56 11/14/12 23:35
6 280364376 2012-11-14 19:29:09 11/14/12 23:01
> agg2 <- AGG2()
> head(agg2)
     custId      DelivDate
1 280336395 11/14/12 17:29
2 280353458 11/14/12 17:29
3 280356593 11/14/12 17:29
4 280364225 11/14/12 17:29
5 280364279 11/14/12 17:29
6 280364376 11/14/12 17:29
> agg2 <- DDPLY()
> head(agg2)
     custId             V1
1 280336395 11/14/12 19:52
2 280353458 11/14/12 18:12
3 280356593 11/14/12 17:29
4 280364225 11/14/12 22:45
5 280364279 11/14/12 23:35
6 280364376 11/14/12 23:01

【问题讨论】:

    标签: r aggregate plyr


    【解决方案1】:

    我也会在这里推荐data.table,但既然您要求aggregate 解决方案,这里是一个结合aggregatemerge 来获取所有列的解决方案:

    merge(events22, aggregate(saleDate ~ custId, events22, max))
    

    如果您只需要“custId”和“DelivDate”列,则只需 aggregate

    aggregate(list(DelivDate = events22$saleDate), 
              list(custId = events22$custId),
              function(x) events22[["DelivDate"]][which.max(x)])
    

    最后,这里有一个使用sqldf的选项:

    library(sqldf)
    sqldf("select custId, DelivDate, max(saleDate) `saleDate` 
          from events22 group by custId")
    

    基准测试

    我不是基准测试或data.table 专家,但令我惊讶的是data.table 在这里并不快。 我怀疑在更大的数据集上结果会完全不同,例如,你的 400k 行之一。无论如何,这里有一些基准代码modeled after @mnel's answer here,因此您可以对您的实际数据集进行一些测试以供将来参考。

    library(rbenchmark)
    

    首先,根据您要进行基准测试的内容设置您的函数。

    DDPLY <- function() { 
      x <- ddply(events22, .(custId), .inform = T, 
                 function(x) {
                   x[x$saleDate == max(x$saleDate),"DelivDate"]}) 
    }
    DATATABLE <- function() { x <- dt[, .SD[which.max(saleDate), ], by = custId] }
    AGG1 <- function() { 
      x <- merge(events22, aggregate(saleDate ~ custId, events22, max)) }
    AGG2 <- function() { 
      x <- aggregate(list(DelivDate = events22$saleDate), 
                     list(custId = events22$custId),
                     function(x) events22[["DelivDate"]][which.max(x)]) }
    SQLDF <- function() { 
      x <- sqldf("select custId, DelivDate, max(saleDate) `saleDate` 
                 from events22 group by custId") }
    DOCALL <- function() {
      do.call(rbind, 
              lapply(split(events22, events22$custId), function(x){
                x[which.max(x$saleDate), ]
              })
      )
    }
    

    其次,做基准测试。

    benchmark(DDPLY(), DATATABLE(), AGG1(), AGG2(), SQLDF(), DOCALL(), 
              order = "elapsed")[1:5]
    #          test replications elapsed relative user.self
    # 4      AGG2()          100   0.285    1.000     0.284
    # 3      AGG1()          100   0.891    3.126     0.896
    # 6    DOCALL()          100   1.202    4.218     1.204
    # 2 DATATABLE()          100   1.251    4.389     1.248
    # 1     DDPLY()          100   1.254    4.400     1.252
    # 5     SQLDF()          100   2.109    7.400     2.108
    

    【讨论】:

    • AGG2 可能很快,因为您没有返回所有列。
    • @TylerRinker,OPs 当前方法中的ddply 也没有。
    • 是的,在那个链接的问题中,@mnel 展示了如何 not 进行基准测试,并解释了为什么在小型数据集上将复制设置为 100 会导致显着差异的时间不显着。此处计时的任务需要 0.00285 到 0.021 秒。如果这很重要,那么该任务可能应该用编译语言编码。此外,dt[, .SD[which.max(saleDate), ], by = custId] 在 data.table 中并不是最快的方法,但我们希望将来对其进行优化以自动将其变成最快的方法,因为它是最自然的。
    • @AnandaMahto 如果您为基准测试创建一些随机数据(例如使用rnormsample),您可以轻松地使用变量,例如:因子的唯一级别数、总大小数据集等。
    • @AnandaMahto:AGG2 最快,但似乎没有返回正确的值。见上面 EDIT2。
    【解决方案2】:

    ddplyaggregate 之间最快的,我想应该是 aggregate,尤其是在处理大量数据时。但是,最快的是data.table

    require(data.table)
    dt <- data.table(events22)
    dt[, .SD[which.max(saleDate),], by=custId]
    

    来自?data.table.SD 是一个包含 x 子集的 data.table 每个组的数据,不包括组列。

    【讨论】:

    • 请在此处致电data.table
    【解决方案3】:

    这应该很快,但data.table 可能更快:

    do.call(rbind, 
        lapply(split(events22, events22$custId), function(x){
            x[which.max(x$saleDate), ]
        })
    )
    

    【讨论】:

      【解决方案4】:

      这是一个更快的data.table 函数:

      DATATABLE <- function() { 
        dt <- data.table(events, key=c('custId', 'saleDate'))
        dt[, maxrow := 1:.N==.N, by = custId]
        return(dt[maxrow==TRUE, list(custId, DelivDate)])
      }
      

      请注意,此函数会创建一个data.table 并对数据进行排序,这是您只需执行一次的步骤。如果你去掉这一步(也许你有一个多步数据处理管道,并创建一次data.table,作为第一步),该函数的速度是原来的两倍以上。

      我还修改了之前的所有函数以返回结果,以便于比较:

      DDPLY <- function() { 
        return(ddply(events, .(custId), .inform = T, 
                     function(x) {
                       x[x$saleDate == max(x$saleDate),"DelivDate"]}))
      }
      AGG1 <- function() { 
        return(merge(events, aggregate(saleDate ~ custId, events, max)))}
      
      SQLDF <- function() { 
        return(sqldf("select custId, DelivDate, max(saleDate) `saleDate` 
                   from events group by custId"))}
      DOCALL <- function() {
        return(do.call(rbind, 
                       lapply(split(events, events$custId), function(x){
                         x[which.max(x$saleDate), ]
                       })
        ))
      }
      

      这是 10k 行的结果,重复 10 次:

      library(rbenchmark)
      library(plyr)
      library(data.table)
      library(sqldf)
      events <- do.call(rbind, lapply(1:500, function(x) events22))
      events$custId <- sample(1:nrow(events), nrow(events))
      
      benchmark(a <- DDPLY(), b <- DATATABLE(), c <- AGG1(), d <- SQLDF(),
       e <- DOCALL(), order = "elapsed", replications=10)[1:5]
      
                    test replications elapsed relative user.self
      2 b <- DATATABLE()           10    0.13    1.000      0.13
      4     d <- SQLDF()           10    0.42    3.231      0.41
      3      c <- AGG1()           10   12.11   93.154     12.03
      1     a <- DDPLY()           10   32.17  247.462     32.01
      5    e <- DOCALL()           10   56.05  431.154     55.85
      

      由于所有函数都返回它们的结果,我们可以验证它们都返回相同的答案:

      c <- c[order(c$custId),]
      dim(a); dim(b); dim(c); dim(d); dim(e)
      all(a$V1==b$DelivDate)
      all(a$V1==c$DelivDate)
      all(a$V1==d$DelivDate)
      all(a$V1==e$DelivDate)
      

      /Edit:在较小的 20 行数据集上,data.table 仍然是最快的,但差距较小:

                    test replications elapsed relative user.self
      2 b <- DATATABLE()          100    0.22    1.000      0.22
      3      c <- AGG1()          100    0.42    1.909      0.42
      5    e <- DOCALL()          100    0.48    2.182      0.49
      1     a <- DDPLY()          100    0.55    2.500      0.55
      4     d <- SQLDF()          100    1.00    4.545      0.98
      

      /Edit2:如果我们从函数中删除 data.table 创建,我们会得到以下结果:

      dt <- data.table(events, key=c('custId', 'saleDate'))
      DATATABLE2 <- function() { 
        dt[, maxrow := 1:.N==.N, by = custId]
        return(dt[maxrow==TRUE, list(custId, DelivDate)])
      }
      benchmark(a <- DDPLY(), b <- DATATABLE2(), c <- AGG1(), d <- SQLDF(),
                 e <- DOCALL(), order = "elapsed", replications=10)[1:5]
                    test replications elapsed relative user.self
      2 b <- DATATABLE()           10    0.09    1.000      0.08
      4     d <- SQLDF()           10    0.41    4.556      0.39
      3      c <- AGG1()           10   11.73  130.333     11.67
      1     a <- DDPLY()           10   31.59  351.000     31.50
      5    e <- DOCALL()           10   55.05  611.667     54.91
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-01-04
        • 2014-04-23
        • 2015-12-16
        • 2013-12-04
        相关资源
        最近更新 更多