【问题标题】:Creating a counter variable in R grouped by ID that conditionally resets [duplicate]在R中创建一个按ID分组的计数器变量,有条件地重置[重复]
【发布时间】:2017-08-15 11:52:34
【问题描述】:

我正在尝试计算每个 ID 连续 # 天不活动 (consecDaysInactive)。

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

我试图创建的逻辑如下:

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

我已经能够创建一个累积总和,但无法在 >= 非活动行 ==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,并且对于新的 id 值重置为 0。如下输出所示,我无法将 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()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 解决方案,我也很乐意听到。

【问题讨论】:

  • 这样的? library(data.table); setDT(d)[, consecDaysInactive2:=cumsum(inactive), by=.(id, cumsum(!inactive))]
  • library(data.table); setDT(d)[, v := if (inactive[1]) seq.int(.N) else 0L, by=rleid(inactive)]
  • 感谢 chinsoon12 和 Frank -- 这两个都运行良好。我将以此为契机探索 data.table 库。 @Frank,关于您将这篇文章标记为重复,我认为这与您标记的文章不同,其中 OP 要求一种在 dplyr 中使用 data.table 函数的方法,目的是制作一个随不同 ID 值递增的常量。我尝试的操作不同,我没有在 dplyr 中要求 data.table 方法; dplyr 是我尝试过的方法,但无法实现我的目标,因此提出了问题。再次感谢您的帮助。
  • @rsty Np。是的,你的问题原来是一个由两部分组成的问题(如我所见):第一个由链接解决(如何在 1s 上进行分组);第二个是相当微不足道的(算在这些组中)。所以在 dplyr group_by(g = rleid(something)) %&gt;% mutate(r = if (x[1]==1) row_number() else 0L) 或类似的。如果其他人想取消重复,那很好,但我想我会留下它。

标签: r data.table dplyr data-manipulation


【解决方案1】:

这是使用 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 都需要做的事情。这很容易解决,而且效率损失最小。您的示例数据框按 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 秒。还是不错的!

使用 Frank 在 OP 上的 cmets 中的答案,我们可以节省多少效率?

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 秒

【讨论】:

  • 感谢雅各布!这与较小的调整一起工作,以将其置于数据框形式。这是一个很好的 for 循环解决方案;我的 R 已经很生锈了,这将很有用。
猜你喜欢
  • 2017-01-31
  • 1970-01-01
  • 2017-01-07
  • 2013-08-31
  • 2020-12-30
  • 2016-10-19
  • 2020-04-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多