根据前面的值按组替换一系列值

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)) 的答案时,事实证明这个备选方案实际上比其他备选方案更快。