我有一个这样的数据表(2000000+行,1000+组)。
set.seed(1)
dt <- data.table(id = rep(1:3, each = 5), values = sample(c("a", "b","c"), 15, TRUE))
> dt
id values
1: 1 a
2: 1 c
3: 1 a
4: 1 b
5: 1 a
6: 2 c
7: 2 c
8: 2 b
9: 2 b
10: 2 c
11: 3 c
12: 3 a
13: 3 a
14: 3 a
15: 3 b
我想在每一个ID组中 替换掉字符 "a "的整个序列 在字符 "b "之前 我想用 "b "替换掉它们 所以条件是 如果在 "b "前出现 "a "或一连串的 "a",则在 "b "前出现 "a"。, 替换掉所有的 "a "字. (实际上,在我的真实表格中,是当 "b "前面有 "a"、"x "或 "y "时,前面的字符应该被替换,但我应该可以通用)
在上面的例子中,第3行中的 "a "的值应该被替换(用data.table中的(shift)很容易做到),还有第12-14行中所有的 "a"(不知道怎么做)。所以,我们需要的输出是这样的。
> dt
id values
1: 1 a
2: 1 c
3: 1 b
4: 1 b
5: 1 a
6: 2 c
7: 2 c
8: 2 b
9: 2 b
10: 2 c
11: 3 c
12: 3 b
13: 3 b
14: 3 b
15: 3 b
我想到的是从最后一个索引开始循环 但我不确定如果我有多个分组(比如ID和DATE)该怎么做 无论如何,这似乎不是最快的dt解决方案。
你可以使用 rle()
.
注。 为了避免歧义,我把 "values"
列为 "var"
因为 rle()
函数还产生一个包含一个名为 "values"
.
dt[, new := with(rle(var), rep(ifelse(values == "a" & c(values[-1], "") == "b", "b", values), lengths)), by = id]
dt
# id var new
# 1: 1 a a
# 2: 1 c c
# 3: 1 a b
# 4: 1 b b
# 5: 1 a a
# 6: 2 c c
# 7: 2 c c
# 8: 2 b b
# 9: 2 b b
# 10: 2 c c
# 11: 3 c c
# 12: 3 a b
# 13: 3 a b
# 14: 3 a b
# 15: 3 b b
下面是另一种data.table的方法。
dt[, x := rleid(values), by = .(id)]
dt[dt[values == "b", .(id, x=x-1, values="a")],
on = .(id, x, values),
values := "b"
][, x := NULL]
结果是:
dt
# id values
# 1: 1 a
# 2: 1 c
# 3: 1 b
# 4: 1 b
# 5: 1 a
# 6: 2 c
# 7: 2 c
# 8: 2 b
# 9: 2 b
# 10: 2 c
# 11: 3 c
# 12: 3 b
# 13: 3 b
# 14: 3 b
# 15: 3 b
这里有一个概括性的例子 就是你想把 "a", "x", 或者 "y "后面的值用 "b "代替:
dt[, x := rleid(values), by = .(id)]
dt[dt[values == "b", .(values=c("a", "x", "y")), by = .(id, x=x-1)],
on = .(id, x, values),
values := "b"
][, x := NULL]
迟来的人已经提供了好几个不错的运行长度替代方案;)所以我在这里试一下 nafill
取而代之的是
(1)创建一个变量'v2',这个变量就是 NA
当'值'为 "a "时。(2)通过向后进行的下一次观测来填补缺失的值。(3)当原'值'为 "a "而'v2'中对应的填充值为 "b "时,用'v2'更新'v'。
# 1
dt[values != "a" , v2 := values]
# 2
d1[, v2 := v2[nafill(replace(seq_len(.N), is.na(v2), NA), type = "nocb")], by = id]
# 3
dt[values == "a" & v2 == "b", values := v2]
# clean-up
dt[ , v2 := NULL]
目前.nafill
只适用于数字变量,因此 replace
躐等 # 2
(修改自@chinsoon12在问题中的发言) nafill、setnafill用于字符、因子和其他类型的填充。).
该 NA
替换代码可能会稍微缩短,使用 zoo::nalocf
:
dt[, v2 := zoo::na.locf(v2, fromLast = TRUE, na.rm = FALSE), by = id]
但是,请注意: na.locf
是比较慢的。
当比较大数据上的答案时(data.table(id = rep(1:1e4, each = 1e4, replace = TRUE), values = sample(c("a", "b", "c"), 1e8, replace = TRUE)
),事实证明,这种选择实际上比其他的快。
这并不漂亮,但我认为这就是你所追求的。
dt[, .N, by = .(id, values = paste0(values, rleid(values)))
][, values := sub("[0-9]+", "", values)
][, values := fifelse(values == "a" & shift(values, -1L) == "b" & !is.na(shift(values, -1L)), "b", values), by = id
][, .SD[rep(seq_len(.N), N)]
][, !"N"]
id values
1: 1 a
2: 1 c
3: 1 b
4: 1 b
5: 1 a
6: 2 c
7: 2 c
8: 2 b
9: 2 b
10: 2 c
11: 3 c
12: 3 b
13: 3 b
14: 3 b
15: 3 b