【问题标题】:How to merge two pandas DataFrames based on a similarity function?如何基于相似度函数合并两个熊猫数据框?
【发布时间】:2016-05-24 16:26:06
【问题描述】:

给定数据集 1

name,x,y
st. peter,1,2
big university portland,3,4

和数据集 2

name,x,y
saint peter3,4
uni portland,5,6

目标是合并

d1.merge(d2, on="name", how="left")

虽然名称没有完全匹配。所以我正在寻找一种模糊匹配。在这种情况下,技术并不重要,更重要的是如何将其有效地整合到 pandas 中。

例如,st. peter 可能与另一个中的 saint peter 匹配,但 big university portland 可能偏差太大,我们不会将其与 uni portland 匹配。

一种考虑方法是允许以最低的 Levenshtein 距离加入,但前提是它低于 5 次编辑(st. --> saint 为 4)。

生成的数据框应仅包含 st. peter 行,并包含“名称”变体以及 xy 变量。

有没有办法使用 pandas 进行这种合并?

【问题讨论】:

  • 我认为您可以创建新列 df1['new']df2['new'] 您的自定义函数,然后通过此列合并它们,例如 d1.merge(d2, on="new", how="left")
  • 该功能只是决定哪个是最佳匹配,并决定是否存在匹配。考虑后面添加的 Levenhstein 距离示例。

标签: python pandas merge fuzzy-comparison


【解决方案1】:

假设你有返回最佳匹配的函数,否则返回 None:

def best_match(s, candidates):
    ''' Return the item in candidates that best matches s.

    Will return None if a good enough match is not found.
    '''
    # Some code here.

然后你可以加入它返回的值,但是你可以用不同的方式来做,这会导致不同的输出(所以我认为,我没有看太多这个问题):

(df1.assign(name=df1['name'].apply(lambda x: best_match(x, df2['name'])))
 .merge(df2, on='name', how='left'))

(df1.merge(df2.assign(name=df2['name'].apply(lambda x: best_match(x, df1['name'])))),
           on='name', how='left'))

【讨论】:

    【解决方案2】:

    我现在能得到的最简单的想法是创建具有所有名称之间距离的特殊数据框:

    >>> from Levenshtein import distance
    >>> df1['dummy'] = 1
    >>> df2['dummy'] = 1
    >>> merger = pd.merge(df1, df2, on=['dummy'], suffixes=['1','2'])[['name1','name2', 'x2', 'y2']]
    >>> merger
                         name1         name2  x2  y2
    0                st. peter   saint peter   3   4
    1                st. peter  uni portland   5   6
    2  big university portland   saint peter   3   4
    3  big university portland  uni portland   5   6
    
    >>> merger['res'] = merger.apply(lambda x: distance(x['name1'], x['name2']), axis=1)
    >>> merger
                         name1         name2  x2  y2  res
    0                st. peter   saint peter   3   4    4
    1                st. peter  uni portland   5   6    9
    2  big university portland   saint peter   3   4   18
    3  big university portland  uni portland   5   6   11
    >>> merger = merger[merger['res'] <= 5]
    >>> merger
           name1        name2  x2  y2  res
    0  st. peter  saint peter   3   4    4
    
    >>> del df1['dummy']
    >>> del merger['res']
    >>> pd.merge(df1, merger, how='left', left_on='name', right_on='name1')
                          name  x  y      name1        name2  x2  y2
    0                st. peter  1  2  st. peter  saint peter   3   4
    1  big university portland  3  4        NaN          NaN NaN NaN
    

    【讨论】:

    • 这不会随着更大的帧缩放。我只有 6000 x 6000 匹配,但使用这种技术会爆炸。
    • 好吧,你当然可以加快速度——首先加入那些完全匹配的,然后只在剩下的上使用 levenshtein 的东西
    • 关键是您首先创建了一个 2 x 2 x num_vars 大小的框架。这意味着一个 6000 x 6000 x num_vars 大小的框架......
    【解决方案3】:

    你看fuzzywuzzy了吗?

    你可能会这样做:

    import pandas as pd
    import fuzzywuzzy.process as fwp
    
    choices = list(df2.name)
    
    def fmatch(row): 
        minscore=95 #or whatever score works for you
        choice,score = fwp.extractOne(row.name,choices)
        return choice if score > minscore else None
    
    df1['df2_name'] = df1.apply(fmatch,axis=1)
    merged = pd.merge(df1, 
                      df2,
                      left_on='df2_name',
                      right_on='name',
                      suffixes=['_df1','_df2'],
                      how = 'outer') # assuming you want to keep unmatched records
    

    Caveat Emptor:我还没有尝试运行它。

    【讨论】:

    • 我想知道我们如何沿着匹配的选择记录匹配的分数?检查分数的分布,可以更好地指导阈值的有意义选择。 ??
    猜你喜欢
    • 1970-01-01
    • 2017-06-11
    • 2016-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-28
    • 2019-09-27
    相关资源
    最近更新 更多