【问题标题】:Subset a dataframe in two dataframes by values in two columns of another dataframe [duplicate]通过另一个数据帧的两列中的值对两个数据帧中的数据帧进行子集[重复]
【发布时间】:2018-11-22 17:20:05
【问题描述】:

我有两个数据框。 df1 看起来像(或我感兴趣的 df1 列):

position
2
6
12
18
25
31

df2 看起来像:

start   end
2       17
24      29

我想保留 df1 中仅位于(=)df2 的开始和结束坐标之间的位置,以便过滤后 df1 看起来像这样:

position
2
6
12
25

然后我想将过滤掉的 df1 的“剩余”值保留在另一个数据框中,我们称之为 df4。

df4 看起来像:

position
18
31

我可以使用 for 循环(来自 perl,目前正在学习 R)以 perl 方式执行此操作,但我很确定我可以以某种方式使用 filter 或其他一些 dplyr 或基本 R 函数组合来实现此目的。

任何帮助将不胜感激!

编辑:添加了 df4 计算,因为我的问题被标记为重复,这在其他类似线程中找不到。这是我感兴趣的事情,可以让我的代码更快!

【问题讨论】:

  • 你的意思是 df4 的 18、31
  • 是的,更正了!

标签: r dplyr


【解决方案1】:

单行,简单的基础解决方案:

df1[df1$position %in% unlist(apply(df2,1,function(x) x["start"]:x["end"])),]

apply 只生成一个包含所有开始和结束之间的案例的向量。

【讨论】:

  • 经过快速检查,这似乎是最快的解决方案。
【解决方案2】:

这是一个base R 选项

do.call(rbind, Map(function(i, j) 
  df1[df1$position > i & df1$position < j, , drop = FALSE], 
      df2$start, df2$end))
#    position
#1        3
#2        6
#3       12
#5       25

或使用fuzzy_join

library(fuzzyjoin)
library(dplyr)
fuzzy_inner_join(df1, df2, by = c('position' = 'start', 'position' = 'end'), 
        match_fun = list(`>`, `<`)) %>%
    select(position)
#  position
#1        3
#2        6
#3       12
#4       25

或者使用来自data.table的非等连接

setDT(df2)[df1, on = .(start < position, end > position), .(position), nomatch = 0]
#   position
#1:        3
#2:        6
#3:       12
#4:       25

数据

df1 <- structure(list(position = c(3L, 6L, 12L, 18L, 25L, 31L)), row.names = c(NA, 
 -6L), class = "data.frame")

df2 <- structure(list(start = c(2L, 24L), end = c(17L, 29L)), 
 class = "data.frame", row.names = c(NA, -2L))

【讨论】:

    【解决方案3】:

    base R 解决方案(无需包)

    keepRows<-
    sapply(df1$position, function(x_o) {
        any(apply(df2, 1, function(x) {x_o => x[1] & x_o <= x[2]}))
    })
    
    df1[keepRows,, drop = FALSE]
    

    结果:

    #  position
    #1        3
    #2        6
    #3       12
    #5       25
    

    请注意:

    • 这基本上是一个双循环,我不知道用base::来解决这个问题。

    • 是否应包含边框值?您对“之间”非常模糊。目前,我不包括它们。您可以使用&lt;=, &gt;= 进行更改。


    要获得“剩余”,请使用否定:

    df1[!keepRows,, drop = FALSE]
    

    【讨论】:

    • 根据你的建议让我的问题更清楚。
    【解决方案4】:

    我们可以full_join这两个数据框,然后过滤startend列中的行。示例中的Flag 列仅用于连接。最后,我们可以使用distinct 删除重复的行。

    library(dplyr)
    
    df3 <- df1 %>%
      mutate(Flag = 1) %>%
      full_join(df2 %>% mutate(Flag = 1), by = "Flag") %>%
      filter(position >= start, position <= end) %>%
      distinct(position)
    df3
    #   position
    # 1        3
    # 2        6
    # 3       12
    # 4       25
    

    数据

    df1 <- read.table(text = "position
    3
                      6
                      12
                      18
                      25
                      31", header = TRUE)
    
    df2 <- read.table(text = "start   end
    2       17
    24      29",
                      header = TRUE)
    

    【讨论】:

    • 您好 www,这是一个易于理解的 dplyr 解决方案!我对 dplyr 比对 base r 更熟悉,所以感谢您的大力响应!此外,这个答案提供了我要求的数据框!有没有办法保留 df4 中过滤掉的内容而不必重复此代码? (见问题编辑)
    • 我认为我发现了这一点。我可以使用:df4
    【解决方案5】:

    这是另一种以 df2 开头的方法(我并不是说这比 Andre 的方法更明智):

    subset(df1, apply(apply(df2, 1, function (x) {dplyr::between(df1$position, x["start"], x["end"])}), 1, any))
    

    在做出决定之前,您可能应该对建议的方法进行一些基准测试。

    【讨论】:

    • @AndreElrico 像 DT 这样的东西有时必须转换数据。这并不能使它成为所有类型问题的理想解决方案。如果数据已经是 data.table 也没关系。
    • @AndreElrico 由于我不使用data.table,我实际使用的函数来自dplyr。不过感谢您的提示。我忘了它不在基地里。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-18
    • 2018-03-30
    • 1970-01-01
    • 2021-03-10
    相关资源
    最近更新 更多