使用 data.table 并与 base 和 dplyr 进行比较
data.table 方法
这是一个基于 data.table 的答案版本,比基本版本和 dplyr 版本都快。
set.seed(65L)
df <- data.table(v1 = sample(0:4, 1000, replace = TRUE), v2 = 0)
df[, v2 := cumsum(v1 > 0)]
head(df, 12)
v1 v2
1: 2 1
2: 1 2
3: 3 3
4: 0 3
5: 0 3
6: 4 4
7: 2 5
8: 4 6
9: 4 7
10: 0 7
11: 4 8
12: 2 9
三法比较:等价
set.seed(65L)
df <- data.frame(v1 = sample(0:4, 1000, replace = TRUE), v2 = 0)
df2 <- df
dt <- as.data.table(df)
# data.table
dt[, v2 := cumsum(v1 > 0)]
# base R
if (df$v1[1L] > 0) {df$v2[1L] <- 1}
for (i in 2:length(df$v1)) {
df$v2[i] <- df$v2[i - 1] + if (df$v1[i] > 0) {1} else {0}
}
# dplyr
if (df2$v1[1L] > 0) {df2$v2[1L] <- 1}
df2 <- df2 %>% mutate(v2 = cumsum(v1>0))
all.equal(dt, df, check.attributes = FALSE)
[1] TRUE
all.equal(dt, df2, check.attributes = FALSE)
[1] TRUE
all.equal(df, df2, check.attributes = FALSE)
[1] TRUE
三法对比:速度
library(microbenchmark)
microbenchmark(DT = dt[, v2 := cumsum(v1 > 0)],
Base = {if (df$v1[1L] > 0) {df$v2[1L] <- 1};for (i in 2:length(df$v1)) {df$v2[i] <- df$v2[i - 1] + if (df$v1[i] > 0) {1} else {0}}},
DP = {if (df2$v1[1L] > 0) {df2$v2[1L] <- 1};df2 <- df2 %>% mutate(v2 = cumsum(v1>0))},
setup = 'set.seed(65L);df <- data.table(v1 = sample(0:4, 1000, replace = TRUE), v2 = 0); df2 <- df; dt <- as.data.table(df)',
control = list(order = 'block'), times = 1000L)
Unit: microseconds
expr min lq mean median uq max neval cld
DT 204.1 210.20 216.6067 212.0 216.80 382.9 1000 a
Base 7956.1 8322.85 8936.3439 8457.6 8702.25 22219.4 1000 c
DP 916.0 930.50 994.4782 939.8 977.60 6157.4 1000 b
所以dplyr 方法比基本循环快约 9 倍,data.table 方法比 dplyr 快约 4.5 倍,比基本循环快 40 倍以上!