基于条件的两个大数据集上的模糊字符串匹配 - Python

5
我有两个大数据集已经读入 Pandas DataFrames 中(分别约有 20K 行和 40K 行)。当我试图在地址字段上使用 pandas.merge 直接合并这两个 DFs 时,与行数相比,我的匹配结果很少。所以我想尝试模糊字符串匹配,看看是否能提高输出匹配的数量。
我尝试通过在 DF1(20K 行)中创建一个新列来实现这一点,该新列是将 fuzzywuzzy extractone 函数应用于 DF2[addressline] 的 DF1[addressline] 的结果。不久后我就意识到这将需要永远,因为它将进行接近10亿次比较。
这两个数据集都有“县”字段,我的要求是:是否有一种方法可以有条件地对两个 DF 中的“addressline”字段进行模糊字符串匹配,基于“county”字段相同?在研究类似我的问题时,我偶然发现了这个讨论:Fuzzy logic on big datasets using Python 但我仍然不明白如何根据县份分组/阻塞字段。非常感谢您给予任何建议!
import pandas as pd
from fuzzywuzzy import process

def fuzzy_match(x, choices, scorer, cutoff):
  return process.extractOne(x, choices = choices, scorer = scorer, score_cutoff= cutoff)[0]

test = pd.DataFrame({'Address1':['123 Cheese Way','234 Cookie Place','345 Pizza Drive','456 Pretzel Junction'],'ID':['X','U','X','Y']}) 
test2 = pd.DataFrame({'Address1':['123 chese wy','234 kookie Pl','345 Pizzza DR','456 Pretzel Junktion'],'ID':['X','U','X','Y']}) 
test['Address1'] = test['Address1'].apply(lambda x: x.lower()) 
test2['Address1'] = test2['Address1'].apply(lambda x: x.lower()) 
test['FuzzyAddress1'] = test['Address1'].apply(fuzzy_match, args = (test2['Address1'], fuzz.ratio, 80))

我添加了两个示例图片,它们展示了导入Excel的两种不同DF。由于对我的问题不重要,因此并未包括所有字段。为再次强调我的最终目标,我想在其中一个DF中添加一个新列,该列将是使用模糊匹配从第二个DF的其他地址行中获取的顶部结果,但仅适用于两个DF之间县份相匹配的行。从那里开始,我计划合并这两个DF,一个基于模糊匹配的地址和第二个DF中的地址行列。希望这不会听起来令人困惑。

这是一个有趣的问题,但我花了很长时间才读懂你的意思。你能否包含创建可运行的最小示例数据框的代码?只需要4或5行就可以说明你想要做什么。 - maxymoo
@maxymoo 这是您要求的示例:test = pd.DataFrame({'Address1':['123 Cheese Way','234 Cookie Place','345 Pizza Drive','456 Pretzel Junction'],'ID':['X','U','X','Y']}) test2 = pd.DataFrame({'Address1':['123 chese wy','234 kookie Pl','345 Pizzza DR','456 Pretzel Junktion'],'ID':['X','U','X','Y']}) test['Address1'] = test['Address1'].apply(lambda x: x.lower()) test2['Address1'] = test2['Address1'].apply(lambda x: x.lower()) test['FuzzyAddress1'] = test['Address1'].apply(fuzzy_match, args = (test2['Address1'], fuzz.ratio, 80)) 这里的分组将是“ID”,以进行模糊匹配。 - Nirav
1
我已经编辑了你的问题,只保留了与你问题相关的代码,这样可以减少回答者在问题中不必要代码的数量。 - maxymoo
1个回答

8
您可以修改您的fuzzy_match函数,将id作为变量传入,并在进行模糊匹配之前使用它来对选择项进行子集化(请注意,这需要在整个数据框上应用函数而不仅仅是地址列)。
def fuzzy_match(x, choices, scorer, cutoff):
    match = process.extractOne(x['Address1'], 
                               choices=choices.loc[choices['ID'] == x['ID'], 
                                                   'Address1'], 
                               scorer=scorer, 
                               score_cutoff=cutoff)
    if match:
        return match[0]

test['FuzzyAddress1'] = test.apply(fuzzy_match, 
                                   args=(test2, fuzz.ratio, 80), 
                                   axis=1)

谢谢您的建议@maxymoo!出于好奇,您是否有任何想法来解决这个问题:“为了减少比较次数,您可以首先将具有某些共同特征的记录分组,例如地址字段的前五个字符或公共令牌。然后,只比较共享功能的记录。这个想法被称为“阻塞”,通常会减少您必须进行的总比较次数,使其变得可管理。”从我发布的链接中获取。我一直在尝试使用groupby,但它没有给我想要的结果。 - Nirav
尽管我尝试了你的建议,但我仍然遇到了这个错误:KeyError: ('Address1','发生在索引Address1处')。 - Nirav
1
@Nirav,你有包含 axis=1 吗? - maxymoo
1
是的,我做到了。在提到错误的评论后,我尝试在谷歌上找到解决方案,并成功地弄清楚了如何将轴=1。无论如何,还是感谢大家的帮助! - Nirav
@maxymoo 感谢您的回答。您有任何想法为什么我似乎会得到“TypeError:('NoneType'对象不可订阅,出现在索引20处)”?如果我删除错误索引,则会在不同的索引处获得相同的错误。我不明白为什么这些特定行会导致问题。 - Mitchell
1
@Mitchell,这是因为这些行没有匹配项(基于截断),我已经编辑了我的答案来检查这一点,但你也可以调整你的截断。 - maxymoo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接