【问题标题】:Split date sequence into one chunk (containing start & end date) for each month将每个月的日期序列拆分为一个块(包含开始和结束日期)
【发布时间】:2018-09-12 16:12:18
【问题描述】:

假设我有一个如下所示的数据框:

df <- data.frame(group = c("a", "a", "b"),
                 start = as.Date(c("2018-01-01", "2018-09-01", "2018-02-01")),
                 end = as.Date(c("2018-02-15", "2018-12-31", "2018-03-30")))

group      start        end
     a 2018-01-01 2018-02-15
     a 2018-09-01 2018-12-31
     b 2018-02-01 2018-03-30

我想得到以下预期输出:

output <- data.frame(group = c("a", "a", "a", "a", "a", "a", "b", "b"),
                  start = as.Date(c("2018-01-01", "2018-02-01", "2018-09-01",
                                    "2018-10-01", "2018-11-01", "2018-12-01",
                                    "2018-02-01", "2018-03-01")),
                  end = as.Date(c("2018-01-31", "2018-02-15", "2018-09-30",
                                  "2018-10-31", "2018-11-30", "2018-12-31",
                                  "2018-02-28", "2018-03-30")))

 group      start        end
     a 2018-01-01 2018-01-31
     a 2018-02-01 2018-02-15
     a 2018-09-01 2018-09-30
     a 2018-10-01 2018-10-31
     a 2018-11-01 2018-11-30
     a 2018-12-01 2018-12-31
     b 2018-02-01 2018-02-28
     b 2018-03-01 2018-03-30

对于序列中的每个月,我希望得到一个单独的行,该行将由 1) 序列的开始日期分隔,如果后者 > 比月初或月初 & 2) 结束日期如果后者 > 序列的结束日期或序列的结束日期,则为月份。

关于如何做到这一点的任何想法?

【问题讨论】:

    标签: r date sequence


    【解决方案1】:

    data.table 解决方案

    解决这类问题我最喜欢的武器是data.table's very very fast foverlaps

    df <- data.frame(group = c("a", "a", "b"),
                     start = as.Date(c("2018-01-01", "2018-09-01", "2018-02-01")),
                     end = as.Date(c("2018-02-15", "2018-12-31", "2018-03-30")))
    
    #create data-frame with from-to by month
    df2 <- data.frame( start = seq( as.Date("2018-01-01"), length = 12, by = "1 month" ),
                       end = seq( as.Date( "2018-02-01"), length = 12, by= "1 month" ) - 1,
                       stringsAsFactors = FALSE )
    
    library(data.table)
    
    #setDT on both data.frames... df2 needs to be keyed in order for foverlaps to work.
    dt <- foverlaps( setDT( df ), setDT( df2, key = c("start", "end") ), type = "any", mult = "all" )[]
    #choose keep the right columns (start/end)
    dt[ start < i.start, start := i.start ]
    dt[ end > i.end, end := i.end ]
    #cleaning
    dt[, `:=`(i.start = NULL, i.end = NULL)][]
    
     #         start        end group
    # 1: 2018-01-01 2018-01-31     a
    # 2: 2018-02-01 2018-02-15     a
    # 3: 2018-09-01 2018-09-30     a
    # 4: 2018-10-01 2018-10-31     a
    # 5: 2018-11-01 2018-11-30     a
    # 6: 2018-12-01 2018-12-31     a
    # 7: 2018-02-01 2018-02-28     b
    # 8: 2018-03-01 2018-03-30     b
    

    基准

    与@AntoniosK 的 tidyverse 解决方案(效果一样好,并且更易读 ;-))相比,foverlaps 有 50% 的时间完成了这项工作

    # Unit: milliseconds
    # expr       min       lq      mean    median        uq       max neval
    # tidyverse 10.418585 10.79064 12.531207 11.080309 11.753030 93.110804   100
    # foverlaps  5.320911  5.59506  5.861865  5.846766  6.009146  9.606981   100
    

    【讨论】:

    • 谢谢 - 但乍一看,输出似乎与预期不符?
    • @arg0naut 哎呀......手指滑了......修复了这个问题。查看更新的答案
    【解决方案2】:
    df <- data.frame(group = c("a", "a", "b"),
                     start = as.Date(c("2018-01-01", "2018-09-01", "2018-02-01")),
                     end = as.Date(c("2018-02-15", "2018-12-31", "2018-03-30")))
    
    library(tidyverse)
    library(lubridate)
    
    df %>%
      group_by(id = row_number()) %>%             # for each row
      mutate(seq = list(seq(start, end, "day")),  # create a sequence of dates with 1 day step
             month = map(seq, month)) %>%         # get the month for each one of those dates in sequence
      unnest() %>%                                # unnest data
      group_by(group, id, month) %>%              # for each group, row and month
      summarise(start = min(seq),                 # get minimum date as start
                end = max(seq)) %>%               # get maximum date as end
      ungroup() %>%                               # ungroup
      select(-id, - month)                        # remove unecessary columns
    
    # # A tibble: 8 x 3
    #   group start      end       
    #  <fct> <date>     <date>    
    # 1 a     2018-01-01 2018-01-31
    # 2 a     2018-02-01 2018-02-15
    # 3 a     2018-09-01 2018-09-30
    # 4 a     2018-10-01 2018-10-31
    # 5 a     2018-11-01 2018-11-30
    # 6 a     2018-12-01 2018-12-31
    # 7 b     2018-02-01 2018-02-28
    # 8 b     2018-03-01 2018-03-30
    

    【讨论】:

      【解决方案3】:

      这是另一种可能的data.table 方法:

      library(data.table)
      setDT(df)
      
      #to create a data.table of monthly periods
      earliest <- as.POSIXlt(df[,min(start)]) 
      earliest$mday <- 1L
      earliest <- as.Date(earliest)
      
      latest <- as.POSIXlt(df[,max(end)])
      latest$mday <- 1L
      latest <- seq(as.Date(latest), by="1 month", length.out=2L)[2L]
      
      startOfMonths <- seq(earliest, latest, by="1 month")
      monthsDT <- data.table(
          som=startOfMonths[-length(startOfMonths)],
          eom=startOfMonths[-1L] - 1L)
      
      #perform non-equi join where som falls within start and end
      ans <- monthsDT[df, .(group, start, som=x.som, end, eom=x.eom), 
          by=.EACHI, on=.(som>=start, som<=end)][, -(1L:2L)]
      
      #get desired output according to OP's requirement
      ans[, .(group, start=max(start, som), end=min(end, eom)), by=seq_len(ans[,.N])][, -1L]
      

      输出:

         group      start        end
      1:     a 2018-01-01 2018-01-31
      2:     a 2018-02-01 2018-02-15
      3:     a 2018-09-01 2018-09-30
      4:     a 2018-10-01 2018-10-31
      5:     a 2018-11-01 2018-11-30
      6:     a 2018-12-01 2018-12-31
      7:     b 2018-02-01 2018-02-28
      8:     b 2018-03-01 2018-03-30
      

      【讨论】:

        猜你喜欢
        • 2023-03-17
        • 1970-01-01
        • 2020-10-05
        • 2012-05-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多