【问题标题】:Merge two dataframes by partial string match通过部分字符串匹配合并两个数据帧
【发布时间】:2017-09-22 16:11:19
【问题描述】:

我正在尝试根据部分字符串匹配合并两个不同大小的相当大的数据帧。

df1$code 包含所有 12 位代码,而 df2$code 包含 10-12 位代码的混合,其中一些较短的代码是与 df1$code 中的 12 位代码匹配的子字符串。

因此,我需要合并两个数据帧之间的所有 12 位匹配,还需要合并 df2 中具有 10-11 位代码的记录,这些记录是与 df1 匹配的子字符串。

示例数据框:

df1 <- data.frame(code_1 = c('123456789012', '210987654321', '567890543211', '987656789001', '123456654321', '678905432156', '768927461037', '780125634701', '673940175372', '167438501473'),
              name = c('bob','joe','sally','john','lucy','alan', 'fred','stephanie','greg','tom'))

df2 <- data.frame(code_2 = c('123456789012','2109876543','7890543211','98765678900','12345665432','678905432156'),
              color = c('blue', 'red', 'green', 'purple', 'orange', 'brown'))

df3 (merged)

code_1         code_2         name  color
123456789012   123456789012   bob   blue
210987654321   2109876543     joe   red
567890543211   7890543211     sally green
987656789001   98765678900    john  purple
123456654321   12345665432    lucy  orange
678905432156   678905432156   alan  brown

【问题讨论】:

  • 我们可以简单地将每个代码截断为 10 位长度,然后匹配吗?
  • 你也想要python解决方案吗?在其他人编辑之前,您最初包含了一个 python 标签。
  • 不应该'7890543211' 匹配'567890543211'?还是您只关心出现在 12 位数字开头的子字符串?
  • @user 如果您的示例是所需的结果,您可以使用sapply(df1$code, agrep, df2$code,value=TRUE) 来获取匹配值。我不知道如何从列表返回到加入。
  • 抱歉,已更正字段。我无法截断为 10 位数字,因为有些从开头和结尾都缺少数字(有些从开头和结尾都缺少一个数字)。我可以使用 R 或 Python 答案。谢谢!

标签: python r


【解决方案1】:

试试这个 SQL 连接。

library(sqldf)

sqldf("select a.code_1, b.code_2, a.name, b.color 
       from df2 b left join df1 a on a.code_1 like '%' || b.code_2 || '%'")

给予:

        code_1       code_2  name  color
1 123456789012 123456789012   bob   blue
2 210987654321   2109876543   joe    red
3 567890543211   7890543211 sally  green
4 987656789001  98765678900  john purple
5 123456654321  12345665432  lucy orange
6 678905432156 678905432156  alan  brown

更新: 更新了答案以反映问题的变化,以便 (1) 子字符串可以位于目标字符串中的任何位置,并且 (2) 代码列的名称已更改为 code_1 和 @987654324 @。

【讨论】:

    【解决方案2】:

    我们可以使用grep + sapplydf2$code 中为每个df1$code 提取匹配索引,并从中创建一个matchID。接下来,我们在matchIDmerge 以获得所需的输出:

    df1$matchID = row.names(df1)
    df2$matchID = sapply(df2$code, function(x) grep(x, df1$code))
    
    df_merge = merge(df1, df2, by = "matchID")[-1]
    

    请注意,如果df1$code 不匹配任何df2$codedf2$matchID 将为空白,因此不会与df1$matchID 合并。

    结果:

    > df2
              code  color matchID
    1 123456789012   blue       1
    2   2109876543    red       2
    3   7890543211  green       3
    4  98765678900 purple       4
    5  12345665432 orange       5
    6 678905432156  brown       6
    7  14124124124  black        
    
    > df_merge
            code.x  name       code.y  color
    1 123456789012   bob 123456789012   blue
    2 210987654321   joe   2109876543    red
    3 567890543211 sally   7890543211  green
    4 987656789001  john  98765678900 purple
    5 123456654321  lucy  12345665432 orange
    6 678905432156  alan 678905432156  brown
    

    数据(添加不匹配以获得更好的演示):

    df1 <- data.frame(code = c('123456789012', '210987654321', '567890543211', '987656789001', '123456654321', '678905432156', '768927461037', '780125634701', '673940175372', '167438501473'),
                      name = c('bob','joe','sally','john','lucy','alan', 'fred','stephanie','greg','tom'),
                      stringsAsFactors = FALSE)
    
    df2 <- data.frame(code = c('123456789012','2109876543','7890543211','98765678900','12345665432','678905432156', '14124124124'),
                      color = c('blue', 'red', 'green', 'purple', 'orange', 'brown', 'black'),
                      stringsAsFactors = FALSE)
    

    【讨论】:

      【解决方案3】:

      根据新信息更新。这应该有效:

      df2$New <- lapply(df2$code_2, grep, df1$code_1,value=T)
      
      combined <- merge(df1,df2, by.x="code_1", by.y="New")
      
              code_1  name       code_2  color
      1 123456654321  lucy  12345665432 orange
      2 123456789012   bob 123456789012   blue
      3 210987654321   joe   2109876543    red
      4 567890543211 sally   7890543211  green
      5 678905432156  alan 678905432156  brown
      6 987656789001  john  98765678900 purple
      

      【讨论】:

      • 我认为这行不通,因为并非df2 中的所有 10 位代码都与 df1 中的 第一个 10 位数字匹配
      • @user 正确的是,如果子字符串在数字内的某个位置开始,它就不起作用,但是看看 OPs 示例,他只返回 5 个匹配项,即前 10 个数字匹配的位置。
      • 请注意,在我更新后,上述评论不再是问题。
      • 我试过这个,但是对于完整的数据帧(一个 210K+ 行和另外 30K+ 行,它一直在崩溃 R。将 201K+ 数据帧分成四个块在第一个块上产生以下错误:错误in order(list("899349002062", "793573098023", "815036020027", "857638004289", : 'orderVector1'中未实现的类型'list'
      【解决方案4】:

      在 python/pandas 中,你可以这样做:

      from pandas import DataFrame, Series
      df1 = DataFrame(dict(
              code1 = ('123456789012', '210987654321', '567890543211', '987656789001', '123456654321', '678905432156', '768927461037', '780125634701', '673940175372', '167438501473'),
              name = ('bob','joe','sally','john','lucy','alan', 'fred','stephanie','greg','tom')))
      
      df2 = DataFrame(dict(
              code2 = ('123456789012','2109876543','7890543211','98765678900','12345665432','678905432156'),
              color = ('blue', 'red', 'green', 'purple', 'orange', 'brown')))
      
      matches = [df1[df1['code1'].str.contains(x)].index[0] for x in df2['code2']]
      
      print(
          df1.assign(subcode=Series(data=df2['code2'], index=matches))
             .merge(df2, left_on='subcode', right_on='code2')
             .drop('subcode', axis='columns')
      )
      

      然后转储:

                code1   name         code2   color
      0  123456789012    bob  123456789012    blue
      1  210987654321    joe    2109876543     red
      2  567890543211  sally    7890543211   green
      3  987656789001   john   98765678900  purple
      4  123456654321   lucy   12345665432  orange
      5  678905432156   alan  678905432156   brown
      

      注意:我讨厌将循环与数据帧一起使用,但我猜这个,嗯,可行。

      【讨论】:

      • 他要求一个 R 解决方案
      • @Mako212 问题并不那么明确。考虑到 OP 包含一个 python 标签并且其他人删除了它,我觉得这仍然是一个有效的答案。
      • @Mako212 可能值得删除这些 cmets,因为 OP 现在已经明确表示也在寻求 Python 解决方案
      • @Paul H. 嗨,我现在正在尝试使用您的代码在我的数据集上执行此操作,但出现以下错误:IndexError: index 0 is out of bounds for axis 0 with size 0.知道为什么吗?谢谢!
      • @Paul H. 它基本上是句子。两列都有段落,其中一列包含另一列句子的子集(两列大小不同)。因此,假设数据框 1 中的第 1 列具有诸如“您好,今天是美好的一天!”之类的值。和“你做得很棒”,另一列将是这些句子的子集,例如“你好,今天”或“你做了一个”,我希望将第二列(也就是子集)中的所有内容与它们是适当子集的东西。这样有意义吗?
      猜你喜欢
      • 2021-09-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-15
      • 1970-01-01
      • 2021-08-12
      • 2021-11-19
      • 2020-11-28
      相关资源
      最近更新 更多