【问题标题】:R merge data frames, allow inexact ID matching (e.g. with additional characters 1234 matches ab1234 )R 合并数据帧,允许不精确的 ID 匹配(例如,使用附加字符 1234 匹配 ab1234 )
【发布时间】:2014-02-05 13:33:33
【问题描述】:

我正在尝试处理一些非常混乱的数据。我需要通过样本 ID 合并两个包含不同类型数据的大型数据框。问题是一个表的样本 ID 有许多不同的格式,但大多数都包含所需的 ID 字符串以匹配其 ID 中的某处,例如一个表中的样本“1234”在另一个表中的 ID 为“ProjectB(1234)”。

我做了一个最小的可重现示例。

a<-data.frame(aID=c("1234","4567","6789","3645"),aInfo=c("blue","green","goldenrod","cerulean"))
b<-data.frame(bID=c("4567","(1234)","6789","23645","63528973"), bInfo=c("apple","banana","kiwi","pomegranate","lychee"))

使用合并获得了一部分的方式:

merge(a,b, by.x="aID", by.y="bID", all=TRUE)

       aID     aInfo       bInfo
1     1234      blue        <NA>
2     3645  cerulean        <NA>
3     4567     green       apple
4     6789 goldenrod        kiwi
5   (1234)      <NA>      banana
6    23645      <NA> pomegranate
7 63528973      <NA>      lychee

但是会喜欢的输出基本上是:

        ID     aInfo       bInfo
1     1234      blue      banana
2     3645  cerulean pomegranate
3     4567     green       apple
4     6789 goldenrod        kiwi
5 63528973      <NA>      lychee

我只是想知道是否有办法将 grep 合并到这个或其他 R-tastic 方法中?

提前致谢

【问题讨论】:

  • agrep 是近似字符串匹配
  • 示例:b[ agrep('1234', b$bID), ] b[ agrep('3645', b$bID), ] 都从 b 返回正确的 ID
  • @rawr 你应该这样回答。
  • 谢谢,我想我的问题是如何在两个数据帧的合并中使用这个agrep。
  • 我可能会先清理数据。为原始、干净和合并的数据保留单独的文件。将您的代码分开。版本控制等都是很好的编程习惯:)

标签: r regex dataframe merge


【解决方案1】:

这是一个使用data.table 的答案,灵感来自@nograpes。

## Create example tables; I added the sarcoline cases
##   so there would be examples of rows in a but not b
a <- data.table(aID=c("1234","1234","4567","6789","3645","321", "321"),
                aInfo=c("blue","blue2","green","goldenrod","cerulean",
                        "sarcoline","sarcoline2"),
                key="aID")
b <- data.table(bID=c("4567","(1234)","6789","23645","63528973"),
                bInfo=c("apple","banana","kiwi","pomegranate","lychee"),
                key="bID")

## Use agrep to get the rows of b by each aID from a
ab <- a[, b[agrep(aID, bID)], by=.(aID, aInfo)]
ab
##     aID     aInfo    bID       bInfo
## 1: 1234      blue (1234)      banana
## 2: 1234     blue2 (1234)      banana
## 3: 3645  cerulean  23645 pomegranate
## 4: 4567     green   4567       apple
## 5: 6789 goldenrod   6789        kiwi

到目前为止,我们只有一个内连接,所以现在让我们从原始表中添加不匹配的行:

ab <- rbindlist(list(ab, a[!ab[, unique(aID)]], b[!ab[, unique(bID)]]), fill=TRUE)

这些步骤是可选的,包含在内以匹配 OP 的输出:

## Update NA values of aID with the value from bID
ab[is.na(aID), aID:=bID]

## Drop the bID column
ab[, bID:=NULL]

最终结果

ab
##         aID      aInfo       bInfo
## 1:     1234       blue      banana
## 2:     1234      blue2      banana
## 3:     3645   cerulean pomegranate
## 4:     4567      green       apple
## 5:     6789  goldenrod        kiwi
## 6:      321  sarcoline          NA
## 7:      321 sarcoline2          NA
## 8: 63528973         NA      lychee

【讨论】:

    【解决方案2】:

    在条件下执行merge 有点棘手。我不认为你可以用merge 来做到这一点,所以你最终不得不用by 编写一个自定义函数。这是非常低效的,但是merge 也是如此。如果您有数百万行,请考虑data.table。这就是您将如何执行“内部联接”,其中只返回匹配的行。

    # I slightly modified your data to test multiple matches    
    a<-data.frame(aID=c("1234","1234","4567","6789","3645"),aInfo=c("blue","blue2","green","goldenrod","cerulean"))
    b<-data.frame(bID=c("4567","(1234)","6789","23645","63528973"), bInfo=c("apple","banana","kiwi","pomegranate","lychee"))
    
    f<-function(x) merge(x,b[agrep(x$aID[1],b$bID),],all=TRUE)
    do.call(rbind,by(a,a$aID,f))
    
    #         aID     aInfo    bID       bInfo
    # 1234.1 1234      blue (1234)      banana
    # 1234.2 1234     blue2 (1234)      banana
    # 3645   3645  cerulean  23645 pomegranate
    # 4567   4567     green   4567       apple
    # 6789   6789 goldenrod   6789        kiwi
    

    进行完全连接有点棘手。这是一种方式,但效率仍然很低:

    f<-function(x,b) {
      matches<-b[agrep(x[1,1],b[,1]),]
      if (nrow(matches)>0) merge(x,matches,all=TRUE)
      # Ugly... but how else to create a data.frame full of NAs?
      else merge(x,b[NA,][1,],all.x=TRUE)
    }
    d<-do.call(rbind,by(a,a$aID,f,b))
    left.over<-!(b$bID %in% d$bID)
    rbind(d,do.call(rbind,by(b[left.over,],'bID',f,a))[names(d)])
    
    #         aID     aInfo      bID       bInfo
    # 1234.1 1234      blue   (1234)      banana
    # 1234.2 1234     blue2   (1234)      banana
    # 3645   3645  cerulean    23645 pomegranate
    # 4567   4567     green     4567       apple
    # 6789   6789 goldenrod     6789        kiwi
    # bID    <NA>      <NA> 63528973      lychee
    

    【讨论】:

    • 抱歉,如果您想要的只是与all.x=TRUE 合并,而不是all=TRUE,则此方法有效。我会尽快更新。
    • 我更新了答案以包括完整的加入。
    • 事实上,我想我在这里拥有一个通用的merge.by.condition 函数已经完成了一半。
    • 你的意思是像plyr::join? :) 充满 NA 的数据框 data.frame(matrix(NA, 10, 5))
    • @rawr 不,不像plyr:::join,因为这不允许您加入条件,而不仅仅是两列相等。实际上,文档指出plyr:::join 的功能merge 此外,虽然可以通过这种方式生成一个充满NAdata.frame,但也有必要拥有名字,这样rbind 就不会吐了。另外,我需要动态计算行数和列数,但这并不难。
    【解决方案3】:

    在合并之前,我会再清理一下您的出价。如果您知道所有奇怪的 bID 格式化方式,那么使用gsub() 清理它们应该很简单。

    在您的示例中,要删除括号,我会执行类似的操作

    expr <- '\\((.*)\\)'
    b$bID <- gsub(expr, replace='\\1', b$bID)
    

    expr 中发生了一些事情。首先是.*,它是任意字符任意次数的正则表达式。用括号括起来让gsub 知道我们想要保留它,并且可以在替换表达式中引用它。为了使用左右括号作为实际字符,我们需要用双反斜杠对它们进行转义。将所有这些放在一起将读作;我想保留左括号和右括号之间的所有内容。

    请注意,您可以使用替换表达式(例如 replace='id_\\1')来做一些花哨的事情。

    关于在数字序列中查找 ID,您必须尝试子字符串匹配或其他方法,但我认为这不是一个好方法。

    希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-09-10
      • 2015-04-17
      • 2013-10-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-18
      相关资源
      最近更新 更多