根据前面的值按组替换一系列值
Replace a sequence of values by group depending on preceeding values
我有一个这种形式的数据table(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 组中替换字符 "b" 之前的整个字符序列 "a",并且我想用 "b" 替换它们。所以条件是如果"a"或者"a"的序列出现在"b"之前,替换掉所有的"a"s。 (实际上,在我真正的table中,当"b"前面有"a"、"x"或"y"时,前面的字符应该被替换,但我应该能够概括)
在上面的例子中,第 3 行中 "a" 的值应该被替换(用 data.table 中的 (shift) 很容易做到),以及所有 "a" s 在第 12-14 行(不知道该怎么做)。因此,所需的输出是这样的:
> 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 解决方案。
这不是很漂亮,但我认为这就是您所追求的:
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
您可以使用 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]
- 创建一个新列 "x",每个值的 运行 个长度 ID 按 id
分组
- join 自身,同时修改 运行 长度 ids (x) 为前值,值为 "a"(您要更改的特定值),然后使用 "b"
- 之后删除列 x
结果是:
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" 和 "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) 当'values'为"a"时,创建一个变量'v2',即NA
。 (2) 通过向后进行的下一次观察来填充缺失值。 (3) 当原来的'values'为"a",'v2'中对应的填充值为"b"时,将'v'更新为'v2'。
# 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
(由问题 nafill, setnafill for character, factor and other types 中的@chinsoon12 修改)。
使用 zoo::nalocf
:
可以稍微缩短 NA
替换代码
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)
) 的答案时,事实证明这个备选方案实际上比其他备选方案更快。
我有一个这种形式的数据table(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 组中替换字符 "b" 之前的整个字符序列 "a",并且我想用 "b" 替换它们。所以条件是如果"a"或者"a"的序列出现在"b"之前,替换掉所有的"a"s。 (实际上,在我真正的table中,当"b"前面有"a"、"x"或"y"时,前面的字符应该被替换,但我应该能够概括)
在上面的例子中,第 3 行中 "a" 的值应该被替换(用 data.table 中的 (shift) 很容易做到),以及所有 "a" s 在第 12-14 行(不知道该怎么做)。因此,所需的输出是这样的:
> 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 解决方案。
这不是很漂亮,但我认为这就是您所追求的:
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
您可以使用 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]
- 创建一个新列 "x",每个值的 运行 个长度 ID 按 id 分组
- join 自身,同时修改 运行 长度 ids (x) 为前值,值为 "a"(您要更改的特定值),然后使用 "b"
- 之后删除列 x
结果是:
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" 和 "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) 当'values'为"a"时,创建一个变量'v2',即NA
。 (2) 通过向后进行的下一次观察来填充缺失值。 (3) 当原来的'values'为"a",'v2'中对应的填充值为"b"时,将'v'更新为'v2'。
# 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
(由问题 nafill, setnafill for character, factor and other types 中的@chinsoon12 修改)。
使用 zoo::nalocf
:
NA
替换代码
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)
) 的答案时,事实证明这个备选方案实际上比其他备选方案更快。