【问题标题】:Select rows from a dataframe based on a pattern formed by consecutive values in a column根据列中连续值形成的模式从数据框中选择行
【发布时间】:2019-07-11 22:45:49
【问题描述】:

我的 R 水平一般,希望对以下操作有所帮助。

假设我有以下数据框:

    >df
    ID   Label
    P1   M
    P1   S
    P2   M
    P2   M
    P2   S
    P3   M
    P3   S
    P3   M
    P4   S
    P4   M
    P5   M
    P5   M
    P5   S

我希望能够针对每个 ID 选择出现在变量 Label 的特定序列中的行。

对于模式"MS",预期输出为

    ID   Label
    P1   M
    P1   S
    P2   M
    P2   S
    P3   M
    P3   S

对于模式"MMS",预期的输出将是

    ID   Label
    P2   M
    P2   M
    P2   S
    P5   M
    P5   M
    P5   S

对于模式"SM",预期输出为:

    ID   Label
    P3   S
    P3   M
    P4   S
    P4   M

请考虑这样一个事实,即我正在处理的数据有很多行,并且我需要构建的解决方案需要适用于任意长度的模式。 (例如“MSS”、“SM”、“MMSSMS”等)。我虚心地请求您的帮助。

编辑:我已经更新了这个问题(示例数据框和模式"MMS" 的输出示例。我想补充一点,我希望在使用ID 变量对数据进行分组后进行模式匹配,这样模式就可以在ID分组的数据组中找到。抱歉第一次没有搞清楚。

最终编辑:@akrun、@boski 和 @tmfmnk 的回答对我有用。与@tmfmnk 的解决方案(在 40 万行数据上约 29 秒)相比,@boski 和@akrun 的解决方案在执行时间上更快(在 40 万行数据上约 2-10 秒)。我建议读者参考所有这三个解决方案。

【问题讨论】:

    标签: r dataframe dplyr


    【解决方案1】:

    一种选择是比较 lead 值并获取按“ID”分组的索引

    library(data.table)
    i1 <- unique(setDT(df)[, lapply(which(Reduce(`&`, 
      Map(`==`, shift(Label, n = 0:2, type = "lead"), c("M", "M", "S")))), 
           function(i) .I[i:(i+2)]) , by = ID]$V1)
    df[i1]
    #    ID Label
    #1: P2     M
    #2: P2     M
    #3: P2     S
    #4: P5     M
    #5: P5     M
    #6: P5     S
    

    数据

    df <- structure(list(ID = c("P1", "P1", "P2", "P2", "P2", "P3", "P3", 
    "P3", "P4", "P4", "P5", "P5", "P5"), Label = c("M", "S", "M", 
    "M", "S", "M", "S", "M", "S", "M", "M", "M", "S")), 
    class = "data.frame", row.names = c(NA, -13L))
    

    【讨论】:

    • 看起来这个解决方案需要输入模式的长度。我需要对我的数据(大约 400k 行)进行尝试,然后告诉你它的执行情况。
    • 我已经编辑了这个问题。在模式匹配之前,我想按 ID 对数据进行分组,并对各个组进行这种模式匹配并制作子集。
    • @SJEROMEGIDEON。在这种情况下,您需要指定by = ID]
    • 谢谢阿克伦!您会用相同的更改编辑答案吗?我认为这对其他人有用。 :D 编辑:添加 by = ID] 会给出错误的结果。
    • 谢谢!有用。只需根据图案的长度修改0:2i+2 部分。我会在我的 400k 行数据集上尝试这个 sn-p,明天让你知道结果。
    【解决方案2】:

    您可以尝试使用gregexpr()。先粘贴所有标签,找到你要找的图案的起始位置。

    > df
       ID Label
    1  P1     M
    2  P1     S
    3  P2     M
    4  P2     M
    5  P2     S
    6  P3     M
    7  P3     S
    8  P3     S
    9  P4     S
    10 P4     M
    11 P5     M
    12 P5     M
    13 P5     S
    

    编辑

    我之前的解决方案没有检索到整个模式(只是开始)。

    pattern="SM"
    starts=gregexpr(pattern=pattern,paste(df$Label,collapse=""))[[1]]
    positions=as.vector(sapply(starts,function(x){ 
      s=seq(x,x+nchar(pattern)-1)
      if (all(df$ID[s]==df$ID[x])){
        return(s)
      } else {return(rep(NA,nchar(pattern)))}
      }))
    positions=positions[which(!is.na(positions))]
    
    df[positions,]
    df[positions,]
       ID Label
    1  P1     M
    2  P1     S
    4  P2     M
    5  P2     S
    6  P3     M
    7  P3     S
    12 P5     M
    13 P5     S
    
    pattern="MMS"
       ID Label
    3  P2     M
    4  P2     M
    5  P2     S
    11 P5     M
    12 P5     M
    13 P5     S
    
    pattern="SM"
       ID Label
    9  P4     S
    10 P4     M
    

    【讨论】:

    • 嘿博斯基!感谢您的回答,但是您为 ID 变量采用的值是错误的。看看吧。
    • 嘿,我更新了答案以返回整个模式,并且还使标签与您的相同。
    • 嘿!再次感谢。你的答案非常接近。很抱歉,我的问题并不清楚。我已经编辑了我的问题。我想在按 ID 对数据进行分组后找到这些模式。我又添加了一个例子来说明我的问题。
    • 我不认为我明白你在说什么。我得到的输出与你的例子完全相同。我已经更新了你的新成员。 编辑好的,我现在看到了
    • 现在可以使用了。它会在返回位置之前检查所有 id 是否相等。
    【解决方案3】:

    原始问题的一个基本解决方案可能是:

    nchar <- nchar("MS")
    x <- grepRaw("MS", paste(df$Label, collapse = ""), all = TRUE)
    y <- rep(x, each = nchar) + 0:(nchar - 1)
    
    df[1:nrow(df) %in% y, ]
    
      ID Label
    1 P1     M
    2 P1     S
    4 P2     M
    5 P2     S
    6 P3     M
    7 P3     S
    
    nchar <- nchar("SM")
    x <- grepRaw("SM", paste(df$Label, collapse = ""), all = TRUE)
    y <- rep(x, each = nchar) + 0:(nchar - 1)
    
    df[1:nrow(df) %in% y, ]
    
       ID Label
    2  P1     S
    3  P2     M
    5  P2     S
    6  P3     M
    9  P4     S
    10 P4     M
    

    或者写成dplyr的形式:

    nchar <- nchar("MS")
    df %>%
     filter(row_number() %in% c(rep(grepRaw("MS", paste(Label, collapse = ""), all = TRUE), 
                each = nchar) + 0:(nchar - 1)))
    
       ID Label
    1  P1     M
    2  P1     S
    3  P2     M
    4  P2     S
    5  P3     M
    6  P3     S
    7  P3     M
    8  P4     S
    9  P5     M
    10 P5     S
    

    还解决了问题的编辑:

    nchar <- nchar("MS")
    df %>%
     group_by(ID) %>%
     filter(row_number() %in% c(rep(grepRaw("MS", paste(Label, collapse = ""), all = TRUE), 
                each = nchar) + 0:(nchar - 1)))
    
      ID    Label
      <fct> <fct>
    1 P1    M    
    2 P1    S    
    3 P2    M    
    4 P2    S    
    5 P3    M    
    6 P3    S    
    7 P5    M    
    8 P5    S  
    

    【讨论】:

    • 嘿!感谢您的回答!这正是我想要的。
    猜你喜欢
    • 2021-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-05
    • 1970-01-01
    • 2015-10-23
    相关资源
    最近更新 更多