Python 如何基于相似性函数合并两个数据帧?

Python 如何基于相似性函数合并两个数据帧?,python,pandas,merge,fuzzy-comparison,Python,Pandas,Merge,Fuzzy Comparison,给定数据集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") 但名字上没有精确的匹配。所以我想做一种模糊匹配。在这种情况下,这项技术并不重要,更重要的是如何将其有效地整合到熊猫身上 例如,st.peter可能与另一个匹配saint-peter,但波特兰大大学的

给定数据集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")
但名字上没有精确的匹配。所以我想做一种模糊匹配。在这种情况下,这项技术并不重要,更重要的是如何将其有效地整合到熊猫身上

例如,
st.peter
可能与另一个匹配
saint-peter
,但
波特兰大大学
的偏差可能太大,我们无法将其与
波特兰大学
匹配

考虑它的一种方法是允许使用最小的Levenshtein距离进行连接,但前提是该距离小于5次编辑(
st.-->saint
为4)

生成的数据帧应仅包含行
st.peter
,并同时包含“name”变量和
x
y
变量


有没有一种方法可以使用pandas进行这种合并?

假设您有一个函数,它可以返回最佳匹配(如果有),否则不会返回:

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.
然后,您可以加入它返回的值,但是您可以用不同的方式进行连接,这将导致不同的输出(因此,我认为,我没有过多地研究这个问题):


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

>>> 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
>>从Levenshtein导入距离
>>>df1['dummy']=1
>>>df2['dummy']=1
>>>merge=pd.merge(df1,df2,on=['dummy'],后缀=['1','2'])[['name1','name2','x2','y2']
>>>合并
名称1名称2 x2 y2
0圣彼得圣彼得3 4
1波特兰圣彼得大学5 6
2波特兰圣彼得大大学3 4
3波特兰大学波特兰大学5 6
>>>合并['res']=merge.apply(λx:distance(x['name1'],x['name2']),axis=1)
>>>合并
名称1名称2 x2 y2 res
0圣彼得圣彼得3 4 4
1波特兰圣彼得大学5 6 9
2波特兰圣彼得大大学3 4 18
3波特兰大学波特兰大学5 6 11
>>>合并=合并[合并['res']>>合并
名称1名称2 x2 y2 res
0圣彼得圣彼得3 4 4
>>>del df1[“虚拟”]
>>>合并['res']
>>>pd.merge(df1,merge,how='left',left'u on='name',right'u on='name1')
名称x y名称1名称2 x2 y2
0圣彼得1 2圣彼得3 4
1波特兰大大学3 4南
你看了吗

您可能会这样做:

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

注意:我还没有试着运行这个。

我想你可以创建新的列
df1['new']
df2['new']
你的自定义函数,然后通过这个列像
d1.merge(d2,on=“new”,how=“left”)那样合并它们函数只是决定哪一个是最好的匹配,并决定是否有一个匹配。考虑后面添加的LevHistin距离例子。这不会用更大的帧来缩放。我只有6000比6000的匹配,但是使用这个技术会爆炸。当然,你当然可以加快一点。哪一个是完全匹配的,然后只在其余的一个上使用levenshtein。重点是,首先要创建一个2 x 2 x num_vars大小的框架。这意味着一个6000 x 6000 x num_vars大小的框架……我想知道我们如何沿着匹配的选择记录匹配的分数?检查分数分布,可以更好地指导有意义的选择e表示阈值。
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