按条件过滤data.table,但每N行至少保留一行

时间:2019-09-11 19:24:34

标签: r 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
                                          ----

4 个答案:

答案 0 :(得分:4)

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

这会将所有行保留在

  1. testTRUE

    OR

  2. NTRUE的每个条纹中的第FALSE行。

这样,所有TRUE都被第一个条件保留,第二个条件捕获了每个条纹的第N个元素,因此也捕获了一些FALSE。 -

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

答案 1 :(得分: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说“从上一个“ true”起没有更多的than步骤了”。 (也许我需要处理这个措辞。)

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只是为了使内容逐行可读,对此没有严格的要求。

答案 2 :(得分:0)

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

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
  }
}

答案 3 :(得分:0)

这是我的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

我没有合并行,因此您可以查看每个步骤的结果。 如果输出符合预期,则可以将操作合并为较短的代码(行数更少)