【问题标题】:For loop generating months between dates in RFor循环在R中的日期之间生成月份
【发布时间】:2018-12-07 16:34:27
【问题描述】:

我有一个数据框,它有三列employid、开始日期(ydm)和结束日期(ydm)。我的目标是创建另一个具有两列的数据框,一列是员工 ID,另一列是日期。第二个数据框将围绕第一个数据框构建,这样它将从第一个数据框获取 id,并且列日期将占用该员工的开始日期和结束日期之间的所有月份。简而言之,我会根据员工的开始日期和结束日期将第一个数据帧中的数据按月扩展。

我实际上使用 for 循环成功地创建了代码。问题是,它非常慢,而且我读到的一些地方是为了避免 r 中的循环。有没有一种方法可以更快地做到这一点?

我的数据框和代码示例如下:

# Creating Data frame
    a<- data.frame(employeeid =c('a','b','c'), StartDate= c('2018-1-1','2018-1-5','2018-11-2'),
                   EndDate= c('2018-1-3','2018-1-9','2018-1-8'), stringsAsFactors = F)
    a$StartDate <- ydm(a$StartDate)
    a$EndDate <- ydm(a$EndDate)

    #second empty data frame
    a1 <-a
    a1 <- a1[0,1:2]

    #my code starts
    r <- 1
    r.1 <- 1
    for (id in a$employeeid) {

      #r.1 <- 1
      for ( i  in format(seq(a[r,2],a[r,3],by="month"), "%Y-%m-%d") ) { 
        a1[r.1,1] <- a[r,1]
        a1[r.1,2] <- i
        r.1 <- r.1 +1  
      } 
      r <- r+1
    } 

这会导致:

我想要同样的结果,但要快一点

【问题讨论】:

  • 刚刚编辑,YMD或YDM也不是问题,可以随时更改

标签: r for-loop


【解决方案1】:

tidyverse 几乎是一条线:

> result
# A tibble: 12 x 2
   employeeid date      
   <chr>      <date>    
 1 a          2018-01-01
 2 a          2018-02-01
 3 a          2018-03-01
 4 b          2018-05-01
 5 b          2018-06-01
 6 b          2018-07-01
 7 b          2018-08-01
 8 b          2018-09-01
 9 c          2018-11-01
10 c          2018-12-01
11 c          2019-01-01
12 c          2019-02-01

代码

result <- df %>%
    group_by(employeeid) %>%
    summarise(date = list(seq(StartDate,
                              EndDate,
                              by = "month"))) %>%
    unnest()

数据

library(tidyverse)
library(lubridate)
df <- data.frame(employeeid = c('a', 'b', 'c'), 
                 StartDate = ymd(c('2018-1-1', '2018-5-1', '2018-11-1')),
                 EndDate = ymd(c('2018-3-1', '2018-9-1', '2019-02-1')),
                 stringsAsFactors = FALSE)

【讨论】:

    【解决方案2】:

    我会尝试通过使用 apply 和一个自定义函数来解决这个问题,该函数计算结束和开始的差异。

    我不确定你想要的输出是什么样的,但是在下面示例的函数中,开始和结束之间的所有月份都粘贴在一个字符串中。

    library(lubridate)
    
    # Creating Data frame
    a<- data.frame(employeeid =c('a','b','c'), StartDate= c('2018-1-1','2018-1-5','2018-11-2'),
                   EndDate= c('2018-2-3','2019-1-9','2020-1-8'), stringsAsFactors = F)
    a$StartDate <- ymd(a$StartDate)
    a$EndDate <- ymd(a$EndDate)
    
    # create month-name month nummeric value mapping
    month_names = month.abb[1:12]
    
    
    month_dif = function(dates) # function to calc the dif. it expects a 2 units vector to be passed over
    {
      start = dates[1] # first unit of the vector is expected to be the start date
      end = dates[2] # second unit is expected to be the end date
    
      start_month = month(start)
      end_month = month(end) 
      start_year = year(start) 
      end_year = year(end)
      year_dif = end_year - start_year
    
      if(year_dif == 0){ #if start and end both are in the same year month is start till end
        return(paste(month_names[start_month:end_month], collapse= ", " ))
      } else { #if there is an overlap, mont is start till dezember and jan till end (with x full year in between)
              paste(c(month_names[start_month:12],
              rep(month_names, year_dif-1),
              month_names[1:end_month]), collapse = ", ")
      }
    }
    
    apply(a[2:3], 1, month_dif) 
    

    输出:

    > apply(a[2:3], 1, month_dif)
    [1] "Jan, Feb"                                                                 
    [2] "Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec, Jan"          
    [3] "Nov, Dec, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec, Jan"
    

    【讨论】:

      【解决方案3】:

      您可以使用applydo.call 的组合:

      out_apply_list <- apply(X=a, MARGIN=1,
                          FUN=function(x) {
                            data.frame(id= x[1], 
                                       date=seq(from = as.Date(x[2], "%Y-%d-%m"), 
                                                to = as.Date(x[3], "%Y-%d-%m"), 
                                                by = "month"),
                                       row.names = NULL) 
      })
      
      df <- do.call(what = rbind, args = out_apply_list)
      

      它为您提供以下输出:

      > df
         id       date
      1   a 2018-01-01
      2   a 2018-02-01
      3   a 2018-03-01
      4   b 2018-05-01
      5   b 2018-06-01
      6   b 2018-07-01
      7   b 2018-08-01
      8   b 2018-09-01
      9   c 2018-02-11
      10  c 2018-03-11
      11  c 2018-04-11
      12  c 2018-05-11
      13  c 2018-06-11
      14  c 2018-07-11
      

      【讨论】:

        【解决方案4】:

        为了完整起见,这里用data.table简明一行:

        library(data.table)
        setDT(a)[, .(StartDate = seq(StartDate, EndDate, by = "month")), by = employeeid]
        
            employeeid  StartDate
         1:          a 2018-01-01
         2:          a 2018-02-01
         3:          a 2018-03-01
         4:          b 2018-05-01
         5:          b 2018-06-01
         6:          b 2018-07-01
         7:          b 2018-08-01
         8:          b 2018-09-01
         9:          c 2018-02-11
        10:          c 2018-03-11
        11:          c 2018-04-11
        12:          c 2018-05-11
        13:          c 2018-06-11
        14:          c 2018-07-11
        

        【讨论】:

          猜你喜欢
          • 2020-08-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-05-16
          • 2020-09-20
          • 1970-01-01
          相关资源
          最近更新 更多