在 R 中创建一个按 ID 分组的计数器变量,该变量有条件地重置
Creating a counter variable in R grouped by ID that conditionally resets
我正在尝试计算每个 ID 连续# 天不活动(consecDaysInactive
)。
我已经创建了一个指标变量 inactive
,它在 id 不活动的日子里为 1,在活动的时候为 0。我还有一个 id 变量和一个 date 变量。我的分析数据集将有数十万行,因此效率很重要。
我尝试创建的逻辑如下:
- 每个 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()
和 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 解决方案,我也很乐意听到。
这是一个使用 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 秒。还不错!
使用弗兰克在 OP 评论中的回答,我们可以节省多少效率?
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 秒
我正在尝试计算每个 ID 连续# 天不活动(consecDaysInactive
)。
我已经创建了一个指标变量 inactive
,它在 id 不活动的日子里为 1,在活动的时候为 0。我还有一个 id 变量和一个 date 变量。我的分析数据集将有数十万行,因此效率很重要。
我尝试创建的逻辑如下:
- 每个 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()
和 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 解决方案,我也很乐意听到。
这是一个使用 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 秒。还不错!
使用弗兰克在 OP 评论中的回答,我们可以节省多少效率?
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 秒