在组内获取最大日期的有效方法

时间:2017-03-02 19:58:35

标签: r dplyr tidyverse

我经常有数据集,我随着时间的推移有多个事件措施,我想在一个月内为每个事件采取最大日期。我这样做是通过创建年份和月份变量,然后按日期按降序排序,然后group_by除日期之外的所有变量,然后使用slice获取最大日期。我听到Hadely在视频中说arrange是一个缓慢的操作。我想知道这样做的有效方法是在整齐的范围内。

请发布base,data.table和其他答案,以便其他用途可以从这个问题中受益,但我的愿望是一个整齐的方法。

我目前如何进行此操作:

library(tidyverse)

set.seed(10)
dat <- data_frame(
    date = sample(seq(as.Date('1999/01/01'), as.Date('2001/01/01'), by="day"), 1000, TRUE),
    cash = sample(1010:1030, 1000, TRUE),
    stage = sample(LETTERS[1:7], 1000, TRUE)
) %>% distinct()


dat %>%
    mutate(
        year = format(date, '%Y'),
        month = format(date, '%B')
    ) %>%
    arrange(desc(date)) %>%
    group_by(cash, stage, year, month) %>%
    slice(1)

2 个答案:

答案 0 :(得分:6)

OP不包括扩大基准的方法,所以我自己制作:

library(data.table)
library(dplyr)    

n = 3e6
n_days = 20000

set.seed(10)
dat <- data_frame(
    date = sample(
      seq(as.Date('1999/01/01'), as.Date('1999/01/01') + n_days - 1, by="day")
      , n, TRUE),
    cash = sample(1010:1030, n, TRUE),
    stage = sample(LETTERS[1:7], n, TRUE)
) %>% distinct()

DT = data.table(dat)[, date := as.IDate(date)]

测试:

# OP's approach
system.time(
  res <- dat %>%
    mutate(
        year = format(date, '%Y'),
        month = format(date, '%B')
    ) %>%
    arrange(desc(date)) %>%
    group_by(cash, stage, year, month) %>%
    slice(1)
)
#    user  system elapsed 
#    9.44    0.09    9.54 

# a data.table way
system.time({
  DTres <- DT[, g := date - mday(date) + 1L ][order(-date), .SD[1L], by=.(cash, stage, g)]
})
#    user  system elapsed 
#    0.51    0.00    0.52 

# verify
fsetequal(
  data.table(res[, c("cash","stage","date")])[, date := as.IDate(date)][], 
  DTres[, c("cash","stage","date")]
) # TRUE

翻译回dplyr:

system.time({
  newres <- dat %>% mutate(g = date - as.POSIXlt(date)$mday + 1) %>% 
    arrange(desc(date)) %>% group_by(cash, stage, g) %>% slice(1L)
})
# Error, who knows why?

system.time({
  newres <- dat %>% mutate(g = date + 1 - date %>% as.POSIXlt %>% `[[`("mday")) %>% 
    arrange(desc(date)) %>% group_by(cash, stage, g) %>% slice(1L)
})
#    user  system elapsed 
#    1.47    0.04    1.52

fsetequal(
  data.table(res[c("date","cash","stage")]),   
  data.table(newres[c("date","cash","stage")])
) # TRUE

# or ...
iddat <- dat %>% mutate(date = data.table::as.IDate(date))
mday <- data.table::mday
system.time({
  borrowres <- iddat %>% arrange(desc(date)) %>% 
    distinct(cash, stage, g = date - mday(date) + 1L)
})
#    user  system elapsed 
#    0.92    0.02    0.94 

fsetequal(
  data.table(borrowres[names(DTres)]),   
  DTres
) # TRUE

当我调整nn_days时,相对时间没有太大变化。感谢@Arun这种舍入方式。以前,我有round(date, "months")。似乎关键是使用算术而不是format。我不确定时间上的剩余差异;也许它可以通过使用dtplyr来解决。切换到arrange %>% distinct除了清理语法之外没什么用。

旁注:我正在加载dplyr而不是tidyverse,因为我真的不知道后者包含什么。我用tidyverse试了一下,得到了相同的时间。

答案 1 :(得分:2)

其他几个data.table选项:

f.dt <- function(dat) {
    DT <- data.table(dat)[,yearmon := format(date, "%Y %B")]
    DT[order(-date),idx := 1:.N, 
       by = .(cash, stage, yearmon)
    ][idx == 1, !"idx"][]
}

f2.dt <- function(dat) {
    DT <- data.table(dat)[,yearmon := format(date, "%Y %B")]
    DT[DT[, .I[which.max(date)], 
        by = .(cash, stage, yearmon)]$V1,][]
}

第二个使用Señor O's second approach in the linked question

针对

进行测试
f.dplyr <- function(dat) {
    dat %>%
        mutate(
            yearmon = format(date, '%Y %B')
        ) %>%
        arrange(desc(date)) %>%
        group_by(cash, stage, yearmon) %>%
        slice(1)    
}

使用Frank的数据,

fsetequal(f.dt(dat), data.table(f.dplyr(dat)))
# [1] TRUE

fsetequal(f2.dt(dat), data.table(f.dplyr(dat)))
# [1] TRUE

microbenchmark::microbenchmark(
    f.dplyr(dat),
    f.dt(dat),
    f2.dt(dat),
    times = 10L
)
# Unit: seconds
#          expr      min       lq     mean   median       uq      max neval
#  f.dplyr(dat) 3.446304 3.562061 3.601803 3.598340 3.625105 3.860911    10
#     f.dt(dat) 1.525025 1.540881 1.727772 1.561149 1.718817 2.422788    10
#    f2.dt(dat) 1.299834 1.315242 1.510534 1.384346 1.667197 2.262938    10

数据

n = 3e6
n_days = 20000

set.seed(10)
dat <- dplyr::data_frame(
    date = sample(
      seq(as.Date('1999/01/01'), 
          as.Date('1999/01/01') + n_days - 1, 
          by = "day"), n, TRUE),
    cash = sample(1010:1030, n, TRUE),
    stage = sample(LETTERS[1:7], n, TRUE)
) %>% dplyr::distinct()