根据前面的数值,按组替换数值序列。

问题描述 投票:8回答:4

我有一个这样的数据表(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解决方案。

r arrays string replace data.table
4个回答
1
投票

你可以使用 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

5
投票

下面是另一种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分组。
  • 加入自身,同时修改运行长度id(x)为前面的值,值为 "a"(您要改变的特定值),然后用 "b "更新值。
  • 追删

结果是:

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]

4
投票

迟来的人已经提供了好几个不错的运行长度替代方案;)所以我在这里试一下 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)),事实证明,这种选择实际上比其他的快。


2
投票

这并不漂亮,但我认为这就是你所追求的。

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
© www.soinside.com 2019 - 2024. All rights reserved.