【问题标题】:filter dataframe on several columns using base or dplyr使用 base 或 dplyr 在几列上过滤数据框
【发布时间】:2020-07-12 17:56:52
【问题描述】:

我正在尝试根据另一个数据报中的列过滤数据帧的行。基本上,我想提取位置在开始和结束之间的具有相同 ID 的行。还有一个额外的技巧是 ID 的格式不同。
最后,脚本中涉及的数据量很大,因此无论是节省内存还是速度都很好。
将不胜感激得到一些提示。

library(dplyr)

df1 <- data.frame(id = c(1, 1, 1, 2, 2, 2, 3, 3, 3), 
                  pos = c(30, 40, 50, 35, 45, 55, 60, 63, 39))

df2 <- data.frame(idstr = c("id1", "id1", "id3", "id4", "id4"), 
                  start=c(30, 20, 30, 40, 20 ),
                  end = c(40, 30, 50, 60, 45))

df.base <- df1[ paste0("id", df1$id) == df2$idstr && 
                 df1$pos >= df2$start &&
                 df1$pos <= df2$end,]

df.dplyr <- df1 %>%
            left_join(df2, by  = c('id' == 'idstr') ) %>%
            filter(pos >= start & pos <= end) %>%
            select(id, pos)

编辑: 预期输出,来自 df1 的行满足条件(它们的位置在具有相同 id 的 df2 范围内),所以如果没有错误: 身份,位置
1、30
1、40
3、39

解释:例如,df1[3,] id == 1 和 pos == 50 查看 df2,没有 df2$id == "id1" 且 df2$start = 50 的行,因此 df1[3,] 将被过滤掉。

【问题讨论】:

    标签: r dplyr


    【解决方案1】:

    我们可以在data.table 中使用非等值连接。在两个数据集中创建类似的“id”,然后加入on“id”列,并使用“pos”和“start”、“end”列进行非等连接

    library(data.table)
    setDT(df1)[, id := paste0('id', id)]
    df1[df2, on = .(id = idstr, pos >= start, pos <= end)]
    

    【讨论】:

    • 感谢 Akrun 的建议。但我认为它不起作用:我得到两行 id 1 和 pos 30,而我在 df1 中只有 1 个这样的条目。我还没有使用 MarBlo 的建议完全验证结果,但至少行为看起来更连贯。
    【解决方案2】:

    我已通过提取数字将您的 2 个 DF df1df2,从 df2 变异的列 idstr 转换为数字。然后用left_joingroup_byfilter 得到结果。

    library(dplyr)
    
    
    df1 <- data.frame(id = c(1, 1, 1, 2, 2, 2, 3, 3, 3), pos = c(30, 40, 50, 35, 45, 55, 60, 63, 39))
    
    df2 <- data.frame(idstr = c("id1", "id1", "id3", "id4", "id4"), 
                      start=c(30, 20, 30, 40, 20 ),
                      end = c(40, 30, 50, 60, 45))
    
    
    df2 %>% 
      mutate(idstr = as.numeric(stringr::str_extract(idstr, '[0-9]'))) %>% 
      left_join(df1, by = c('idstr' = 'id')) %>% 
      dplyr::filter(pos >= start & pos <= end)
    #> # A tibble: 4 x 4
    #> # Groups:   idstr [2]
    #>   idstr start   end   pos
    #>   <dbl> <dbl> <dbl> <dbl>
    #> 1     1    30    40    30
    #> 2     1    30    40    40
    #> 3     1    20    30    30
    #> 4     3    30    50    39
    

    有一个df1$id == 1 适合df2 中的2 个起始位置。因此它必须是 id = 1 的 3 个位置。 如果其中一个限制是排他性的 - 就像下面的代码一样 - 它符合您的愿望。

    
    df2 %>% 
      mutate(idstr = as.numeric(stringr::str_extract(idstr, '[0-9]'))) %>% 
      left_join(df1, by = c('idstr' = 'id')) %>% 
      dplyr::filter(pos > start & pos <= end)
    
    #>   idstr start end pos
    #> 1     1    30  40  40
    #> 2     1    20  30  30
    #> 3     3    30  50  39
    

    【讨论】:

    • 好像行得通,我会适应我的真实数据集,谢谢!但有一件事:在我看来 group_by 毫无意义?还是我错过了什么?
    • 实际上我错了,查看输出的第 3 行,这不应该存在,因为它是 df1 的第 1 行输出两次。目标是显示符合条件的 df1 的所有行,所以如果不是我的错误,我希望 3 行作为我编写的虚拟测试的输出。 (原始帖子已编辑以添加预期的输出。我想我可以隐藏问题但是如果我在 df1 中有两行具有相同的 id 和 pos 怎么办......
    • @Will 关键是问题,如果限制是包容性的。请看一下新的编辑。
    • 嗨。如果我遵循,它仍然不正确。目标是列出位置在 df2 行之间的 df1 的元素,所以即使 df1 的一行与 df2 的 2 行匹配,就 idstr 和 start 和 end 而言(确实包括限制),我仍然会希望它只出现一次。我没有办法做到这一点。行号丢失了,所以我无法过滤唯一的行,如果我过滤唯一的(id,pos),那么如果 df1 的两行具有相同的 id 和 pos,我将丢失一行...
    猜你喜欢
    • 2016-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-27
    • 2015-01-27
    相关资源
    最近更新 更多