首页 > 解决方案 > 来自两个不同 csv 文件的 Python 模糊查找(模糊匹配)

问题描述

帮助在两个不同的 CSV 文件(包含公司信息)之间进行 FuzzyLookup。

CSV #1 data1.csv 也有两列(5000 行)
名称、ID
CSV #2 data2.csv 有两列(105 万行)
LegalName、AcctNumber

目标是对 LegalNames 进行名称的模糊查找
(根据准确率显示两个匹配的 LegalNames 是理想的)

我看到有以下库: https ://github.com/seatgeek/fuzzywuzzy

另外,以下 GitHub 显示了我想要做什么的类似想法
https://github.com/Cheukting/fuzzy-match-company-name

其中使用 Pandas 和 FuzzyWuzzy

她的方法很有帮助,因为她还尝试过滤公司名称中最常见的名称(例如“a”、“the”、“LLC”、“Ltd”、“and”等)

唯一的事情是,据我了解,她比较的是同一个列表中是否有重复项,而不是两个不同的列表中。因此,在 data1.csv 和 data2.csv 中使用相同的概念是理想的

此外,您也可以随意贡献一种使用不同库的方法!欢迎任何帮助:)

下面是她的代码:


我们使用的数据可在http://download.companieshouse.gov.uk/en_output.html上找到,它是一个公开许可的公开可用数据集,其中包含英国注册(有限责任)公司的列表。(此处显示的版本是 2018 年 5 月的快照)

import pandas as pd
pd.set_option('display.max_columns', 1000)
df = pd.read_csv("BasicCompanyDataAsOneFile-2020-10-01.csv")
df.head()

这是一个有很多行的大表,可能需要一段时间才能加载

df.columns
df['RegAddress.PostTown'].value_counts().head(30)

词频
由于我们有很多公司,我们将仅以剑桥的公司为例。

首先,我们在所有公司名称中找到 30 个最常用的词。由于我们预计它们会在不同的公司中重复很多,因此我们无法匹配使用它们的公司名称。我们这样做的方式是,如果名称中存在任何关键字,我们将扣除一对匹配分数。

from collections import Counter
all_names = df['CompanyName'][df['RegAddress.PostTown']=='CAMBRIDGE'].unique()
names_freq = Counter()
for name in all_names:
    names_freq.update(str(name).split(" "))
key_words = [word for (word,_) in names_freq.most_common(30)]
print(key_words)

显示长度

len(all_names)

通过分组匹配
然后我们按名称的第一个字符对名称进行分组。由于列表太长,一次匹配它们需要很长时间(要考虑 15889 x 15889 对)。解决方法是按组匹配它们,假设如果名称在第一个字符处不匹配,则它们不太可能是相同的名称。

all_main_name = pd.DataFrame(columns=['sort_gp','names','alias','score'])
all_names.sort()
all_main_name['names'] = all_names
all_main_name['sort_gp'] = all_main_name['names'].apply(lambda x: x[0])

模糊匹配
这里对于每个组,我们使用fuzzywuzzy.token_sort_ratio 来匹配名称。与基本的fuzzywuzzy.ratio不同,它使用Levenshtein Distance来计算差异,它允许名称中的标记(单词)交换顺序并仍然给出“完美”匹配。(参考:https ://github.com/seatgeek/fuzzywuzzy )

from fuzzywuzzy import fuzz

all_sort_gp = all_main_name['sort_gp'].unique()

def no_key_word(name):
    """check if the name contain the keywords in travel company"""
    output = True
    for key in key_words:
        if key in name:
            output = False
    return output

for sortgp in all_sort_gp:
    this_gp = all_main_name.groupby(['sort_gp']).get_group(sortgp)
    gp_start = this_gp.index.min()
    gp_end = this_gp.index.max()
    for i in range(gp_start,gp_end+1):
    
        # if self has not got alias, asign to be alias of itself
        if pd.isna(all_main_name['alias'].iloc[i]):
            all_main_name['alias'].iloc[i] = all_main_name['names'].iloc[i]
            all_main_name['score'].iloc[i] = 100
        
        # if the following has not got alias and fuzzy match, asign to be alias of this one
        for j in range(i+1,gp_end+1):
            if pd.isna(all_main_name['alias'].iloc[j]):
                fuzz_socre = fuzz.token_sort_ratio(all_main_name['names'].iloc[i],all_main_name['names'].iloc[j])
                if not no_key_word(all_main_name['names'].iloc[j]):
                    fuzz_socre -= 10
                if (fuzz_socre > 85):
                    all_main_name['alias'].iloc[j] = all_main_name['alias'].iloc[i]
                    all_main_name['score'].iloc[j] = fuzz_socre
                    
        if i % (len(all_names)//10) == 0:
            print("progress: %.2f" % (100*i/len(all_names)) + "%")
                
all_main_name.to_csv('company_in_cambridge.csv')

预习

all_main_name[(all_main_name['names']!=all_main_name['alias']) & (all_main_name['alias'].notna())]

谢谢!

预期答案:

all_main_name[(all_main_name['names']!=all_main_name['alias']) & (all_main_name['alias'].notna())]

sort_gp, 名称, 别名, 分数
955, A, AMADEUS EII LP, AMADEUS EI LP, 96
956, A, AMADEUS EIII LP, AMADEUS EI LP, 93
957, A, AMADEUS GI LP, AMADEUS EI LP, 92
958, A, AMADEUS HI LP, AMADEUS EI LP, 92
960, A, AMADEUS II 'A', AMADEUS I, 86

标签: pythonpython-3.xpandaslookupfuzzywuzzy

解决方案


推荐阅读