如何在检测到特定字符串后使用过滤器和 dplyr 删除数据帧行

How to remove rows of dataframe after a particular string is detected, using filter and dplyr

我有如下例所示的数据。对于每个参与者,如果特定字符串 ("trial_end") 出现在 my_strings 列中,我想在它出现后删除所有行。

library(dplyr)
library(stringr)
library(tibble)

df1 <- tibble::tribble(
  ~participant_id, ~timestamp,     ~my_strings,
  1L,        1L,  "other_string",
  1L,        2L,  "other_string",
  1L,        3L, "trial_end",
  1L,        4L,  "other_string",
  2L,        1L,  "other_string",
  2L,        2L,  "other_string",
  2L,        3L,  "other_string",
  2L,        4L,  "other_string",
  3L,        1L,  "other_string",
  3L,        2L, "trial_end",
  3L,        3L,  "other_string",
  3L,        4L,  "other_string"
)

我的第一次尝试是使用 str_detect 查找字符串的存在,which 提供行号,然后使用 filter 仅保留该行和所有之前的那些:

df2 <- df1 %>% 
  group_by(participant_id) %>%
        filter(row_number() < (which(str_detect(my_strings, "trial_end"))) + 1)

当未检测到字符串时,这似乎会引发错误(例如此处示例中的参与者 2)。

我的下一次尝试是添加条件 if_else,试图有效地说 'IF the target string is detected THEN remove all the rows after for that participant, ELSE if the string is not detected, do nothing'.

df3 <- df1 %>% 
  group_by(participant_id) %>%
  if_else(str_detect(my_strings, "trial_end"),
        filter(row_number() < (which(str_detect(my_strings, "trial_end"))) + 1),
        filter(timestamp < max(timestamp)))

这也返回了一个错误: 错误:condition 必须是逻辑向量,而不是 grouped_df/tbl_df/tbl/data.frame 对象。

我最后的尝试是通过将条件 if else 放在 filter 中来利用此处已有的另一个答案,但这也产生了错误。

df4 <- df1 %>% 
  group_by(participant_id) %>%
  filter(if(str_detect(my_strings, "trial_end") < (which(str_detect(my_strings, "trial_end")) + 1)) 
            else < n())

谁能指出解决这个问题的最佳方法? filter 是不是做错了?

非常感谢。

为清楚起见,期望的结果如下所示:

desired_output <- tibble::tribble(
                    ~participant_id, ~timestamp,    ~my_strings,
                                 1L,         1L, "other_string",
                                 1L,         2L, "other_string",
                                 1L,         3L,    "trial_end",
                                 2L,         1L, "other_string",
                                 2L,         2L, "other_string",
                                 2L,         3L, "other_string",
                                 2L,         4L, "other_string",
                                 3L,         1L, "other_string",
                                 3L,         2L,    "trial_end"
                    )

您可以编写一个小的辅助函数来在检测到字符串后删除行,如果未检测到该字符串,它不会删除任何内容。

library(dplyr)
drop_string_after <- function(string_vec, string) {
  i <- match(string, string_vec)
  if(is.na(i)) seq_along(string_vec) else seq_len(i)
}

并为每个参与者应用此功能:

df1 %>%
  group_by(participant_id) %>%
  slice(drop_string_after(my_strings, 'trial_end')) %>%
  ungroup

#  participant_id timestamp my_strings  
#           <int>     <int> <chr>       
#1              1         1 other_string
#2              1         2 other_string
#3              1         3 trial_end   
#4              2         1 other_string
#5              2         2 other_string
#6              2         3 other_string
#7              2         4 other_string
#8              3         1 other_string
#9              3         2 trial_end   

要使用 filter,您需要更改函数的 return 值。

drop_string_after <- function(string_vec, string) {
  i <- match(string, string_vec)
  if(is.na(i)) TRUE else row_number() <= i
}

df1 %>%
  group_by(participant_id) %>%
  filter(drop_string_after(my_strings, 'trial_end')) %>%
  ungroup

您可以计算直到前一行的匹配项的累积总和,然后过滤以仅包括每个参与者到第一个匹配项的行:

df1 %>%
  group_by(participant_id) %>%
  filter(lag(cumsum(my_strings == "trial_end"), default = 0) < 1) %>%
  ungroup()

# A tibble: 9 x 3
  participant_id timestamp my_strings  
           <int>     <int> <chr>       
1              1         1 other_string
2              1         2 other_string
3              1         3 trial_end   
4              2         1 other_string
5              2         2 other_string
6              2         3 other_string
7              2         4 other_string
8              3         1 other_string
9              3         2 trial_end   

一个选项可以是:

df1 %>%
    group_by(participant_id) %>%
    slice(if(all(my_strings != "trial_end")) 1:n() else 1:which(my_strings == "trial_end"))

  participant_id timestamp my_strings  
           <int>     <int> <chr>       
1              1         1 other_string
2              1         2 other_string
3              1         3 trial_end   
4              2         1 other_string
5              2         2 other_string
6              2         3 other_string
7              2         4 other_string
8              3         1 other_string
9              3         2 trial_end 
critpart <- 0
for (i in 1:nrow(df1)){
  if (is.na(df1$participant_id[i])) next
  if (df1$my_strings[i] == "trial_end"){
    critpart <- df1$participant_id[i]
    next
  }
  if (df1$participant_id[i] == critpart){
    df1[i,] <- NA
  }
}

df1 <- df1 %>% filter(!is.na(participant_id))