【问题标题】:Filter data.table by condition, but keeping at least one row per N rows按条件过滤 data.table,但每 N 行至少保留一行
【发布时间】:2019-09-11 19:24:34
【问题描述】:

假设我有这个微不足道的 data.table:

library(data.table)

dt <- data.table(
  day = 1:10,
  a = c(0, 1, 10, 2, 2.5, 2.3, 2.7, 2.9, 5, 8)
)

我想根据a 上的某些条件对其进行过滤。在这种情况下,a 变化超过 3 的时刻。这是微不足道的:

dt[abs(a - shift(a)) >= 3]

但是,我不想长时间丢失信息。因此,如果上述条件没有受到影响,我需要确保没有超过 3 天的“过滤掉”时间。

在上述情况下,基于a 的条件满足:

dt[, abs(a - shift(a)) >= 3]
# [1]    NA FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE  TRUE
                              -----------------------------

请注意,在结尾处有很长一段 FALSE。我想到的最好的是

dt[, abs(a - shift(a)) >= 3 | .I %% 3 == 0]
# [1]    NA FALSE  TRUE  TRUE FALSE  TRUE FALSE FALSE  TRUE  TRUE
                                     ----              ----

(即确保每第三行被接受)但它不会放置最好或最少的附加行。

最佳结果将是一个过滤器,它打破FALSE 的那条线,中间有一个TRUE(或尽可能多的)。

# [1]    NA FALSE  TRUE  TRUE FALSE FALSE TRUE FALSE FALSE  TRUE
                                          ----

【问题讨论】:

  • 要稳健(捕获所有出现)且高效(仅保存需要的行),您将需要一种迭代方法。 (1) 对基本条件进行过滤; (2) 对于每个“太长”的间隔,根据前后行确定您的间隔,并将其包含在您的逻辑中。不漂亮,但我不知道是否有一个简单的“运行逻辑”可以涵盖您的所有条件。
  • @r2evans 我认为不需要“最佳”间隔。间隔是预定义的:“确保每第三 (n) 行被接受”.
  • 是的,我并没有假设所有行总是相隔 1 天。 (我在数据处理方面有点偏执和防御性,其中 1 行的滞后并不总是意味着 1 天的滞后,也许是同一天或一周不同。)也许我过于复杂了. (如果 OP 语言改为 “没有超过 3 个”的过滤拉伸“,那么它将是明确的。)
  • 同样,“每 3 天” 事情从最近的“条件满足”行开始,不一定基于“行号模数 3”,因为可以引入比严格必要的更多的行。 (因此我对“稳健而高效”的评论是一种谨慎的平衡。)
  • @r2evans 没错。我添加.I %%3 == 0 条件的解决方案可以确保不存在长于3 的条纹。然而,它在如何选择打破条纹所需的行方面效率不高,这正是我问这个问题的原因:看看是否有更好的解决方案。

标签: r data.table


【解决方案1】:

也许有人可以在data.table 中复制此内容,但这是您可能正在寻找的逻辑。为了清楚起见,我将testrun_lengthresult 分开,但如果需要,可以将逻辑组合或包装在一个函数中。

这将保留所有行

  1. testTRUE

  2. TRUEFALSE 的每条连续中的每个 Nth 行。

这样,所有TRUEs 都由第一个条件保留,第二个条件捕获每个条纹的每个Nth 元素,因此也捕获了一些FALSEs。 -

library(dplyr)

N <- 3

dt %>% 
  mutate(
    test = abs(a - lag(a)) >= N, # flag change(a) >= N
    run_length = sequence(rle(test)$lengths), # seq along streaks of TRUE and FALSE
    result = test | run_length %% N == 0 
  ) # %>% 
  # filter(result) # uncomment this to get final dt

   day    a  test run_length result
1    1  0.0    NA          1     NA
2    2  1.0 FALSE          1  FALSE
3    3 10.0  TRUE          1   TRUE
4    4  2.0  TRUE          2   TRUE
5    5  2.5 FALSE          1  FALSE
6    6  2.3 FALSE          2  FALSE
7    7  2.7 FALSE          3   TRUE
8    8  2.9 FALSE          4  FALSE
9    9  5.0 FALSE          5  FALSE
10  10  8.0  TRUE          1   TRUE

data.table(我猜)-

dt[, (test <- abs(a - shift(a)) >= N) | sequence(rle(test)$lengths) %% N == 0]

[1] NA FALSE  TRUE  TRUE FALSE FALSE  TRUE FALSE FALSE  TRUE

【讨论】:

  • 那个 data.table 版本就像一个魅力。你能稍微扩展一下吗?我在理解它时遇到了一些麻烦(主要是with 语句,因为我的理解是我可以将它重写为... | sequence(lengths(rle(test))) %% 3 == 0,但这实际上给出了不同的结果)。我也从未见过在j 中使用&lt;- 来创建可用于其他计算的临时列。
  • 支持rle 解决方案,这是我一直在考虑的概念,但并没有把我的大脑包裹起来。
  • @Wasabi 我编辑了删除with() 的答案。至于(test &lt;- ...),我的印象是它会在全局环境中创建对象test,但显然它没有,但代码仍然有效!我不能说为什么,因为我从来没有真正使用过data.table。无论如何,我在dplyr 代码中添加了更多解释和 cmets,以提高逻辑的清晰度。告诉我。
【解决方案2】:

如果你是认真的

没有超过 3 个

的“过滤掉”延伸

那么这里是一个尝试。您的abs(a - shift(a)) 将第一个条件保留为NA,这与cumsum 步骤相混淆,因此我们可以将其替换为

c(FALSE, abs(diff(a)) >= 3)
.I > 1 & abs(a - shift(a)) >= 3
abs(a - shift(a, fill = a[1])) >= 3

这确保第一行不会被忽略。对于这个演示,我将使用第三个,因为它与您对shift 的使用一致,而不是您喜欢的维护。

挑战就是这样:给定一个向量,识别元素,使所选元素之间的差距永远不会超过某个定义的值(在本例中为 3)。将no_further 定义为“没有进一步的than 与之前的'true' 相比有这么多步骤”。 (也许我需要改进措辞。)

no_further(4:10, than = 3)      # expect: '7'
# [1] FALSE FALSE FALSE  TRUE FALSE FALSE FALSE

v <- c(4, 6, 8, 9, 10)
### 4 to 8 is too far, need '6' to be included
### 6 to 8 is good
### 6 to 9 is good, but since 6 to 10 is too far, need '9' to be included
no_further(v, than = 3) # expect: '6', '9'
# [1] FALSE  TRUE FALSE  TRUE FALSE

no_further <- function(x, than) {
  i <- 1
  out <- logical(length(x))
  while (i < length(x)) {
    d <- x - x[i]
    if (!is.na(toobig <- which(d > than)[1])) {
      out[ toobig-1 ] <- TRUE
      i <- toobig-1
    } else break
  }
  out
}

然后我们可以在每组数据(定义为从另一个条件开始的组)中使用此函数:

library(magrittr) # solely for demo with %>% pipes, not needed for the function
dt %>%
  .[, keep := abs(a - shift(a, fill = a[1])) >= 3 ] %>%
  .[, grp1 := cumsum(keep) ] %>%
  .[, keep2 := keep | no_further(day, than = 3), by = "grp1" ]
#     day    a  keep grp1 keep2
#  1:   1  0.0 FALSE    0 FALSE
#  2:   2  1.0 FALSE    0 FALSE
#  3:   3 10.0  TRUE    1  TRUE
#  4:   4  2.0  TRUE    2  TRUE
#  5:   5  2.5 FALSE    2 FALSE
#  6:   6  2.3 FALSE    2 FALSE
#  7:   7  2.7 FALSE    2  TRUE
#  8:   8  2.9 FALSE    2 FALSE
#  9:   9  5.0 FALSE    2 FALSE
# 10:  10  8.0  TRUE    3  TRUE

我使用magrittr 只是为了让内容逐行可读,没有严格的要求。

【讨论】:

  • 忽略我之前的评论(以防你看到它)。我想我终于明白你在做什么了。我认为您的意思是在答案开始时“过滤掉”超过3 ,对吗?
【解决方案3】:

好的,所以这可能是最不优雅的解决方案,但使用您的示例:

temp <- dt[,abs(a - shift(a)) >=3]

for(i in 3:length(temp)) {
  if(!(temp[i]|temp[i-1]|temp[i-2])) {
    temp[[i]] <- T
  }
}

【讨论】:

    【解决方案4】:

    这是我的data.table 方法

    (编辑:阅读其他答案后,它遵循@shree的data.table方法的逻辑)。

    #create a column which is TRUE when a changes >= 3
    dt[, change_3 := (abs(a - shift(a)) >= 3)]
    #create groups based on value the change_3 column
    dt[, no_change_gr := rleidv( dt$change_3 ) ]
    #create rownumbers within each group of no_change_gr
    dt[, no_change_rowid := rowid( no_change_id )]
    #mark rownumbers where %%3 == 0 with TRUE
    dt[no_change_rowid %% 3 == 0, false_3 := TRUE]
    #filter out rows where either change_3 or false_3 is TRUE
    dt[ change_3 == TRUE | false_3 == TRUE, .(day,a)][]
    
    #    day    a
    # 1:   3 10.0
    # 2:   4  2.0
    # 3:   7  2.7
    # 4:  10  8.0
    

    我没有合并行,所以你可以查看每一步的结果。 如果输出符合预期,您可以将操作合并为更短的代码(行更少)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-11-27
      • 2019-07-13
      • 2016-02-10
      • 2022-09-28
      • 1970-01-01
      • 2016-11-11
      • 1970-01-01
      相关资源
      最近更新 更多