将数据与r中的部分匹配合并

Posted

技术标签:

【中文标题】将数据与r中的部分匹配合并【英文标题】:merge data with partial match in r 【发布时间】:2012-05-23 22:43:51 【问题描述】:

我有两个数据集

datf1 <- data.frame (name = c("regular", "kklmin", "notSo", "Jijoh",
 "Kish", "Lissp", "Kcn", "CCCa"),
 number1 = c(1, 8, 9,  2,  18, 25, 33,   8))
#-----------
    name number1
1 regular       1
2  kklmin       8
3   notSo       9
4   Jijoh       2
5    Kish      18
6   Lissp      25
7     Kcn      33
8    CCCa       8

 datf2 <- data.frame (name = c("reGulr", "ntSo", "Jijoh", "sean", "LiSsp",
 "KcN", "CaPN"),
   number2 = c(2, 8, 12,    13, 20, 18,   13))
#-------------
   name number2
1 reGulr       2
2   ntSo       8
3  Jijoh      12
4   sean      13
5  LiSsp      20
6    KcN      18
7   CaPN      13

我想按名称列合并它们,但是允许部分匹配(以避免妨碍合并大型数据集中的拼写错误,甚至检测此类拼写错误),例如

(1) 如果在任意位置连续四个字母(如果字母数小于 4 则全部) - 匹配即可

 ABBCD = BBCDK = aBBCD = ramABBBCD = ABB 

(2) 匹配中不区分大小写例如ABBCD = aBbCd

(3) 新数据集将保留两个名称(来自 datf1 和 datf2 的名称)。这样我们就可以检测该字母是否匹配(可以单独一列显示匹配多少个字母)

这样的合并可能吗?

编辑:

datf1 <- data.frame (name = c("xxregular", "kklmin", "notSo", "Jijoh",
             "Kish", "Lissp", "Kcn", "CCCa"),
                     number1 = c(1, 8, 9,  2,  18, 25, 33,   8))
datf2 <- data.frame (name = c("reGulr", "ntSo", "Jijoh", "sean", 
             "LiSsp", "KcN", "CaPN"),
                     number2 = c(2, 8, 12,  13, 20, 18,   13))


uglyMerge(datf1, datf2)
       name1  name2 number1 number2 matches
1  xxregular   <NA>       1      NA       0
2     kklmin   <NA>       8      NA       0
3      notSo   <NA>       9      NA       0
4      Jijoh  Jijoh       2      12       5
5       Kish   <NA>      18      NA       0
6      Lissp  LiSsp      25      20       5
7        Kcn    KcN      33      18       3
8       CCCa   <NA>       8      NA       0
9       <NA> reGulr      NA       2       0
10      <NA>   ntSo      NA       8       0
11      <NA>   sean      NA      13       0
12      <NA>   CaPN      NA      13       0

【问题讨论】:

尝试修复一些格式。我看到您添加了一份似乎来自@sgibb 回复的“uglyMerge”副本。 'xxregular' 与 'reGulr' 的不匹配对您来说可能很明显,但您可能需要向我们解释,因为它似乎符合您的规范 fuzzyjoin 【参考方案1】:

也许有一个简单的解决方案,但我找不到任何解决方案。 恕我直言,您必须自己实施这种合并。 请在下面找到一个丑陋的例子(有很大的改进空间):

uglyMerge <- function(df1, df2) 

    ## lower all strings to allow case-insensitive comparison
    lowerNames1 <- tolower(df1[, 1]);
    lowerNames2 <- tolower(df2[, 1]);

    ## split strings into single characters
    names1 <- strsplit(lowerNames1, "");
    names2 <- strsplit(lowerNames2, "");

    ## create the final dataframe
    mergedDf <- data.frame(name1=as.character(df1[,1]), name2=NA, 
                        number1=df1[,2], number2=NA, matches=0,
                        stringsAsFactors=FALSE);

    ## store names of dataframe2 (to remember which strings have no match)
    toMerge <- df2[, 1];

    for (i in seq(along=names1)) 
        for (j in seq(along=names2)) 
            ## set minimal match to 4 or to string length
            minMatch <- min(4, length(names2[[j]]));

            ## find single matches
            matches <- names1[[i]] %in% names2[[j]];

            ## look for consecutive matches
            r <- rle(matches);

            ## any matches found?
            if (any(r$values)) 
                ## find max consecutive match
                possibleMatch <- r$value == TRUE;
                maxPos <- which(which.max(r$length[possibleMatch]) & possibleMatch)[1];

                ## store max conscutive match length
                maxMatch <- r$length[maxPos];

                ## to remove FALSE-POSITIVES (e.g. CCC and kcn) find 
                ## largest substring
                start <- sum(r$length[0:(maxPos-1)]) + 1;
                stop <- start + r$length[maxPos] - 1;
                maxSubStr <- substr(lowerNames1[i], start, stop);

                ## all matching criteria fulfilled
                isConsecutiveMatch <- maxMatch >= minMatch &&
                                    grepl(pattern=maxSubStr, x=lowerNames2[j], fixed=TRUE) &&
                                    nchar(maxSubStr) > 0;

                if (isConsecutiveMatch) 
                    ## merging
                    mergedDf[i, "matches"] <- maxMatch
                    mergedDf[i, "name2"] <- as.character(df2[j, 1]);
                    mergedDf[i, "number2"] <- df2[j, 2];

                    ## don't append this row to mergedDf because already merged
                    toMerge[j] <- NA;

                    ## stop inner for loop here to avoid possible second match
                    break;
                
            
         
    

    ## append not matched rows to mergedDf
    toMerge <- which(df2[, 1] == toMerge);
    df2 <- data.frame(name1=NA, name2=as.character(df2[toMerge, 1]), 
                    number1=NA, number2=df2[toMerge, 2], matches=0, 
                    stringsAsFactors=FALSE);
    mergedDf <- rbind(mergedDf, df2);

    return (mergedDf);

输出:

> uglyMerge(datf1, datf2)
    name1  name2 number1 number2 matches
1  xxregular reGulr       1       2       5
2     kklmin   <NA>       8      NA       0
3      notSo   <NA>       9      NA       0
4      Jijoh  Jijoh       2      12       5
5       Kish   <NA>      18      NA       0
6      Lissp  LiSsp      25      20       5
7        Kcn    KcN      33      18       3
8       CCCa   <NA>       8      NA       0
9       <NA>   ntSo      NA       8       0
10      <NA>   sean      NA      13       0
11      <NA>   CaPN      NA      13       0

【讨论】:

感谢您提供的出色解决方案。它适用于我提供的示例。但是我可能需要进一步调整,因为如果它不是前四个连续字母,它似乎不起作用,只需查看我的编辑,我在常规之前添加了附加 xxx,不匹配。不过,它会给我一个很好的开始,谢谢!! @hijo 抱歉,我的子字符串计算中有一些错误。请使用我的修改版本。 使用edit distance 来进行字符串匹配可能对您也很有价值。它在 R 中有implementation。【参考方案2】:

agrep 将帮助您入门。

类似:

lapply(tolower(datf1$name), function(x) agrep(x, tolower(datf2$name)))

然后你可以调整max.distance参数,直到你得到合适的匹配量。然后随意合并。

【讨论】:

以上是关于将数据与r中的部分匹配合并的主要内容,如果未能解决你的问题,请参考以下文章

在 Shiny 中使用部分 textInput 作为 R 中的变量

是否有一个 R 函数来匹配基于具有部分相似性的字符串的数据框列?

子集不是基于精确匹配,而是 R 中的部分

R中的快速部分字符串匹配

合并具有部分匹配值的行

R中的部分动物字符串匹配