在R中创建一个计数器变量,按ID分组,有条件地重置

时间:2017-03-23 04:39:48

标签: r data.table dplyr data-manipulation

我试图根据ID计算#连续日无效(consecDaysInactive)。

我已经创建了一个指标变量inactive,其中id为非活动状态时为1,活动时为0。我还有一个id变量和一个日期变量。我的分析数据集将有数十万行,因此效率很重要。

我试图创建的逻辑如下:

  • 每个ID,如果用户处于活动状态,consecDaysInactive = 0
  • 每个ID,如果用户处于非活动状态,并且在前一天处于活动状态consecDaysInactive = 1
  • 每个ID,如果用户在前一天处于非活动状态,consecDaysInactive = 1 +#之前连续的非活动天数
  • 对于新的id值,
  • consecDaysInactive应重置为0。

我已经能够创建累积总和,但在> =非活动行== 0之后无法将其重置为0。

我已经在下面说明了我想要的结果(consecDaysInactive),以及我能够以编程方式实现的结果(bad_consecDaysInactive)。

library(dplyr)
d <- data.frame(id = c(1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2), date=as.Date(c('2017-01-01','2017-01-02','2017-01-03','2017-01-04','2017-01-05','2017-01-06','2017-01-07','2017-01-08','2017-01-01','2017-01-02','2017-01-03','2017-01-04','2017-01-05','2017-01-06','2017-01-07','2017-01-08')), inactive=c(0,0,0,1,1,1,0,1,0,1,1,1,1,0,0,1), consecDaysInactive=c(0,0,0,1,2,3,0,1,0,1,2,3,4,0,0,1))

d <- d %>% 
  group_by(id) %>% 
  arrange(id, date) %>% 
  do( data.frame(., bad_consecDaysInactive = cumsum(ifelse(.$inactive==1, 1,0))
  )
  )
d

其中consecDaysInactive每连续一天非活动迭代+1,但每个日期用户处于活动状态时重置为0,重置为0以获取新的id值。如下面的输出所示,我无法将bad_consecDaysInactive重置为0 - 例如行

          id       date inactive consecDaysInactive bad_consecDaysInactive
       <dbl>     <date>    <dbl>              <dbl>                  <dbl>
    1      1 2017-01-01        0                  0                      0
    2      1 2017-01-02        0                  0                      0
    3      1 2017-01-03        0                  0                      0
    4      1 2017-01-04        1                  1                      1
    5      1 2017-01-05        1                  2                      2
    6      1 2017-01-06        1                  3                      3
    7      1 2017-01-07        0                  0                      3
    8      1 2017-01-08        1                  1                      4
    9      2 2017-01-01        0                  0                      0
    10     2 2017-01-02        1                  1                      1
    11     2 2017-01-03        1                  2                      2
    12     2 2017-01-04        1                  3                      3
    13     2 2017-01-05        1                  4                      4
    14     2 2017-01-06        0                  0                      4
    15     2 2017-01-07        0                  0                      4
    16     2 2017-01-08        1                  1                      5

我还考虑(并尝试)在group_by()&amp;内增加一个变量。 do(),但由于do()不是迭代的,我无法让我的计数器过去2:

d2 <- d %>%
  group_by(id) %>% 
  do( data.frame(., bad_consecDaysInactive2 = ifelse(.$inactive == 0, 0, ifelse(.$inactive==1,.$inactive+lag(.$inactive), .$inactive)))) 
d2 
如上所述

产生了

      id       date inactive consecDaysInactive bad_consecDaysInactive bad_consecDaysInactive2
   <dbl>     <date>    <dbl>              <dbl>                  <dbl>                   <dbl>
1      1 2017-01-01        0                  0                      0                       0
2      1 2017-01-02        0                  0                      0                       0
3      1 2017-01-03        0                  0                      0                       0
4      1 2017-01-04        1                  1                      1                       1
5      1 2017-01-05        1                  2                      2                       2
6      1 2017-01-06        1                  3                      3                       2
7      1 2017-01-07        0                  0                      3                       0
8      1 2017-01-08        1                  1                      4                       1
9      2 2017-01-01        0                  0                      0                       0
10     2 2017-01-02        1                  1                      1                       1
11     2 2017-01-03        1                  2                      2                       2
12     2 2017-01-04        1                  3                      3                       2
13     2 2017-01-05        1                  4                      4                       2
14     2 2017-01-06        0                  0                      4                       0
15     2 2017-01-07        0                  0                      4                       0
16     2 2017-01-08        1                  1                      5                       1

正如您所看到的,我的迭代器bad_consecDaysInactive2重置为0,但不会超过2!如果有一个data.table解决方案,我也很高兴听到它。

1 个答案:

答案 0 :(得分:2)

这是一个可爱的方式来实现for循环:

a <- c(1,1,1,1,0,0,1,0,1,1,1,0,0)
b <- rep(NA, length(a))
b[1] <- a[1]
for(i in 2:length(a)){
  b[i] <- a[i]*(a[i]+b[i-1])
}
a
b

这可能不是最有效的方法,但它会非常快。我的计算机上有一千万行的11.7秒。

a <- round(runif(10000000,0,1))
b <- rep(NA, length(a))
b[1] <- a[1]
t <- Sys.time()
for(i in 2:length(a)){
  b[i] <- a[i]*(a[i]+b[i-1])
}
b
Sys.time()-t

时差11.73612秒

但这并不能解释每个身份做事的必要性。这很容易修复,效率最低。您的示例数据框按ID排序。如果您的实际数据尚未排序,则执行此操作。然后:

a <- round(runif(10000000,0,1))
id <- round(runif(10000000,1,1000))
id <- id[order(id)]
b <- rep(NA, length(a))
b[1] <- a[1]
t <- Sys.time()
for(i in 2:length(a)){
  b[i] <- a[i]*(a[i]+b[i-1])
  if(id[i] != id[i-1]){
    b[i] <- a[i]
  }
}
b
Sys.time()-t

时差13.54373秒

如果我们包括排序id所花费的时间,那么时差接近19秒。还是不太糟糕!

在OP的评论中,我们可以使用Frank的答案获得多少效率节省?

d <- data.frame(inactive=a, id=id)

t2 <- Sys.time()
b <- setDT(d)[, v := if (inactive[1]) seq.int(.N) else 0L, by=rleid(inactive)]
Sys.time()-t2

时差为2.233547秒