【问题标题】:Compare and merge two dataframes比较和合并两个数据框
【发布时间】:2017-02-16 12:35:23
【问题描述】:

我在 R 中有以下两个数据框:

df1 = data.frame(c("A", "A", "A", "B", "B"), c(1, 11, 21, 35, 45), c(6, 20, 30, 40, 60), c(1, 2, 3, 4, 5))
colnames(df1) = c("X", "Y", "Z", "score")

df1 
  X  Y  Z score
1 A  1  6     1
2 A 11 20     2
3 A 21 30     3
4 B 35 40     4
5 B 45 60     5

df2 = data.frame(c("A", "A", "A", "A", "B", "B", "B", "C"), c(1, 6, 21, 50, 20, 31, 50, 10), c(5, 20, 30, 60, 30, 40, 60, 20), c("x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8"))
colnames(df2) = c("X", "Y", "Z", "out")

df2
  X  Y  Z out
1 A  1  5  x1
2 A  6 20  x2
3 A 21 30  x3
4 A 50 60  x4 
5 B 20 30  x5
6 B 31 40  x6
7 B 50 60  x7
8 C 10 20  x8

对于 df1 中的每一行,我要检查:

  • 是否与 'X' 中的值和 df2 中的任何其他 'X' 值匹配
  • 如果上述情况属实:我想检查来自 'Y' 和 'Z' 的值是否在来自 df2 的值 'Y' 和 'Z' 的范围内
  • 如果两者都为真:那么我想将 'out' 中的值添加到 df1。

输出应该是这样的:

output = data.frame(c("A", "A", "A", "B", "B"), c(1, 11, 21, 35, 45), c(6, 20, 30, 40, 60), c(1, 2, 3, 4, 5), c("x1, x2", "x2", "x3", "x4", "x5"))
colnames(output) = c("X", "Y", "Z", "score", "out")

  X  Y  Z score    out
1 A  1  6     1 x1, x2
2 A 11 20     2     x2
3 A 21 30     3     x3
4 B 35 40     4     x6
5 B 45 60     5     x7

原始 df1 保留有一个额外的列“out”,该列被添加。

“输出”的第 1 行,在“输出”列中包含“x1,x2”。原因:“X”列中的值匹配,范围 1 到 6 与 df2 中的第 1 行和第 2 行重叠。

我之前曾问过这个问题 (Compare values from two dataframes and merge),建议使用 foverlaps 函数。但是,由于 df1 和 df2 之间的列不同以及 df2 中的额外行,我无法使其工作。

【问题讨论】:

    标签: r


    【解决方案1】:

    这里有两种可能的方法,a) 使用新实现的 non equi 连接功能,b) foverlaps 正如您特别提到的那样......

    a) 非等连接

    dt2[dt1, on=.(X, Z>=Y, Y<=Z), 
          .(score, out=paste(out, collapse=",")), 
        by=.EACHI]
    

    其中dt1dt2 是对应于df1df2 的data.tables。请注意,您必须在结果中恢复列名 ZY(因为列名来自 dt2 但值来自 dt1

    dt2 中与每一行对应的匹配行是 dt1,是根据提供给 on 参数的条件找到的,.() 会针对这些匹配行中的每个进行评估(因为by=.EACHI)。

    b)foverlaps

    setkey(dt1, X, Y, Z)
    olaps <- foverlaps(dt2, dt1, type="any", nomatch=0L)
    olaps[, .(score=score[1L], out=paste(out, collapse=",")), by=.(X,Y,Z)]
    

    【讨论】:

      【解决方案2】:
      library(dplyr)
      
      df1 = data.frame(c("A", "A", "A", "B", "B"), c(1, 11, 21, 35, 45), 
                       c(6, 20, 30, 40, 60), c(1, 2, 3, 4, 5), stringsAsFactors = F)
      colnames(df1) = c("X", "Y", "Z", "score")
      
      df2 = data.frame(c("A", "A", "A", "A", "B", "B", "B", "C"), c(1, 6, 21, 50, 20, 31, 50, 10), 
                       c(5, 20, 30, 60, 30, 40, 60, 20), 
                       c("x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8"), stringsAsFactors = F)
      colnames(df2) = c("X", "Y", "Z", "out")
      
      
      df1 %>%
        left_join(df2, by="X") %>%         # join on main column
        rowwise() %>%                      # for each row
        mutate(counter = sum(seq(Y.x, Z.x) %in% seq(Y.y, Z.y))) %>%   # get how many elements of those ranges overlap
        filter(counter > 0) %>%            # keep rows with overlap
        group_by(X, Y.x, Z.x, score) %>%   # for each combination of those columns
        summarise(out = paste(out, collapse=", ")) %>%                # combine out column
        ungroup() %>%
        rename(Y = Y.x,
               Z = Z.x)
      
      # # A tibble: 5 × 5
      #       X     Y     Z score    out
      #    <chr> <dbl> <dbl> <dbl> <chr>
      # 1     A     1     6     1 x1, x2
      # 2     A    11    20     2     x2
      # 3     A    21    30     3     x3
      # 4     B    35    40     4     x6
      # 5     B    45    60     5     x7
      

      以上过程是基于dplyr包,涉及到join和一些分组和过滤。如果您的初始数据集(df1df2)非​​常大,那么join 将创建一个更大的数据集,需要一些时间来创建。

      另外,请注意,此过程适用于 character 而不是 factor 变量。如果该进程尝试加入不同级别的factor 变量,它可能会将factor 变量转换为character

      我建议您逐步运行链式命令,看看它是如何工作的,并找出我是否遗漏了可能导致代码错误的任何内容。

      【讨论】:

      • 如何为已经存在的数据框设置变量“stringAsFactors = F”?
      • 首先,尝试使用factor 变量运行相同的进程,因为它可能会在尝试连接不同级别的因子时将它们转换为character
      • @AntioniosK:我的 df1 有 9000 行,我的 df2 有 862 行。您的代码可以流畅地处理一个小子集,但是对于完整的数据,我想这需要相当长的时间......
      • 是的,这很可能是由于rowwise 分组。它必须获取每一行的范围并进行比较。
      【解决方案3】:

      这是使用sqldf的另一个选项

      library(sqldf)
      xx=sqldf('select t1.*,t2.out from df1 t1 left join df2 t2 on t1.X=t2.X and ((t2.Y between t1.Y and t1.Z) or (t2.Z between t1.Y and t1.Z))')
      aggregate(xx[ncol(xx)], xx[-ncol(xx)], FUN = function(X) paste(unique(X), collapse=", "))
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-07-04
        • 2019-03-29
        • 2013-11-26
        • 1970-01-01
        • 2021-05-13
        • 1970-01-01
        • 2020-02-24
        • 1970-01-01
        相关资源
        最近更新 更多