【问题标题】:Vectorising iterative operation across rows跨行向量化迭代操作
【发布时间】:2017-05-22 12:39:41
【问题描述】:

我在这里看到了很多关于矢量化 for 循环的问题,但找不到任何涉及矢量化 for 循环以根据下面一行中单元格的值填充单元格的问题(如果我'道歉'我只是瞎了眼……)。

我有一个数据框,其中包含 160 万行薪水以及每个人开始赚取该薪水的日期。每个人可以有多个薪水,因此可以有多个行,每个行都有不同的更新日期。

虚拟数据集的代码如下:

df1 <- data.frame("id" = c(1,1,2,2,3,3,4,4,5,5,6,6),
                  "salary" = c(15456,16594,
                               17364,34564,
                               34525,33656,
                               23464,23467,
                               16794,27454,
                               40663,42743),
                  "start_date" = sample(seq(as.Date('2016/01/01'),as.Date(Sys.Date()), by="day"), 12))

df1 <- df1[order(df1$id,df1$start_date),]

我想为每个薪水创建一个包含结束日期的列,该日期计算为后续薪水条目的前一天。如果没有后续的工资条目,则将其设置为今天的日期。这是我的代码,包括一个 for 循环,可以做到这一点:

df1$end_date <- Sys.Date()

for (i in 1:(nrow(df1)-1)){
  if(df1[i,1]== df1[i+1,1]){
    df1[i,4] <- df1[i+1,3]-1
  }
  print(i)
} 

但是,我知道 for 循环不是最有效的方法,但我将如何对其进行矢量化?

【问题讨论】:

    标签: r for-loop data.table vectorization


    【解决方案1】:

    使用dplyr 包,您可以:

    library(dplyr)
    df1 %>% 
    group_by(id) %>% 
    mutate(end_date=lead(start_date-1,default=Sys.Date()))
    

    返回:

    id salary start_date   end_date
       <dbl>  <dbl>     <date>     <date>
     1     1  15456 2016-02-14 2016-03-02
     2     1  16594 2016-03-03 2017-05-22
     3     2  17364 2016-01-17 2016-11-28
     4     2  34564 2016-11-29 2017-05-22
     5     3  33656 2016-08-17 2016-11-25
     6     3  34525 2016-11-26 2017-05-22
     7     4  23464 2016-01-20 2017-05-05
     8     4  23467 2017-05-06 2017-05-22
     9     5  27454 2016-02-29 2016-12-15
    10     5  16794 2016-12-16 2017-05-22
    11     6  42743 2016-03-14 2017-01-29
    12     6  40663 2017-01-30 2017-05-22
    

    【讨论】:

      【解决方案2】:

      你可以使用library(data.table):

      setDT(df1)[, end_date := shift(start_date, type = "lead", fill = Sys.Date()), id][]
      

      【讨论】:

      • 这适用于每个 id 的第一行,但将每个 id 的最后一行设置为等于第一行日期的前一天,而我在今天的日期之后。
      【解决方案3】:

      配合data.table和shift,可以如下使用:

      df1 <- data.table("id" = c(1,1,2,2,3,3,4,4,5,5,6,6),
                        "salary" = c(15456,16594,
                                     17364,34564,
                                     34525,33656,
                                     23464,23467,
                                     16794,27454,
                                     40663,42743),
                        "start_date" = sample(seq(as.Date('2016/01/01'),as.Date(Sys.Date()), by="day"), 12))
      
      df1 <- df1[order(id,start_date),]
      
      df1[, EndDate := shift(start_date, type="lead"), id]
      df1[is.na(EndDate), EndDate := Sys.Date()]
      

      【讨论】:

        【解决方案4】:

        如果我理解您的问题,以下基本 R 代码将起作用。

        df1$end <- ave(df1$start_date, df1$id, FUN=function(x) c(tail(x, -1) - 1, Sys.Date()))
        

        ave 用于执行组级操作。执行的函数从第二个到最后一个日期减去 1。这与最后一个日期相连。

        返回

        df1
           id salary start_date        end
        1   1  15456 2016-03-20 2016-12-06
        2   1  16594 2016-12-07 2017-05-22
        3   2  17364 2016-10-17 2016-07-27
        4   2  34564 2016-07-28 2017-05-22
        5   3  34525 2016-05-26 2016-05-01
        6   3  33656 2016-05-02 2017-05-22
        7   4  23464 2017-04-17 2016-01-19
        8   4  23467 2016-01-20 2017-05-22
        9   5  16794 2016-09-12 2016-05-06
        10  5  27454 2016-05-07 2017-05-22
        11  6  40663 2016-10-03 2016-03-28
        12  6  42743 2016-03-29 2017-05-22
        

        【讨论】:

        • 查看我的回答的评论,如果start_date 是最新的每个ID,他想要Sys.Date() for end_date,...我也跑进了同一个陷阱。
        • 啊哈。感谢您的澄清。
        猜你喜欢
        • 1970-01-01
        • 2019-07-23
        • 1970-01-01
        • 1970-01-01
        • 2020-12-17
        • 1970-01-01
        • 2021-05-31
        • 1970-01-01
        • 2012-02-09
        相关资源
        最近更新 更多