python - Python 中的模糊查找

标签 python pandas fuzzywuzzy

我有两个 CSV 文件。一个包含供应商数据,一个包含员工数据。与 excel 中的“模糊查找”类似,我希望进行两种类型的匹配并输出两个 csv 文件中的所有列,包括一个新列作为每行的相似率。在 excel 中,我会使用 0.80 阈值。下面是示例数据,我的实际数据在其中一个文件中有 200 万行,如果在 excel 中完成,这将是一场噩梦。
输出 1:
从供应商文件中,将“供应商名称”与员工文件中的“员工姓名”进行模糊匹配。显示两个文件中的所有列和一个新的相似比列
输出 2:
从供应商文件中,将“SSN”与“员工”文件中的“SSN”进行模糊匹配。显示两个文件中的所有列和一个新的相似比列
这是两个独立的输出
数据框 1:供应商数据


公司
供应商编号
供应商名称
发票号码
交易金额
供应商类型
社会保障号


15
58421
克利福德布朗
854
500
杂项
668419628

150
9675
绿色
7412
70
一度
774801971

200
15789
史密斯,约翰
80
40
员工
965214872

200
69997
哈伦,司曼
964
100
杂项
741-98-7821


数据框 2:员工数据


员工姓名
员工ID
经理
社会保障号


布朗,克利福德
1
经理 1
668-419-628

蓝色,城市
2
经理2
874126487

史密斯,约翰
3
经理 3
965-21-4872

哈伦,西蒙
4
经理 4
741-98-7820


预期输出 1 - 匹配名称


员工姓名
员工ID
经理
社会保障号
公司
供应商编号
供应商名称
发票号码
交易金额
供应商类型
社会保障号
相似率


布朗,克利福德
1
经理 1
668-419-628
150
58421
克利福德布朗
854
500
杂项
668419628
1.00

史密斯,约翰
3
经理 3
965-21-4872
200
15789
史密斯,约翰
80
40
员工
965214872
1.00

哈伦,西蒙
4
经理 4
741-98-7820
200
69997
哈伦,司曼
964
100
杂项
741-98-7821
0.96

蓝色,城市
2
经理2
874126487



0.00


预期输出 2 - 匹配 SSN


员工姓名
员工ID
经理
社会保障号
公司
供应商编号
供应商名称
发票号码
交易金额
供应商类型
社会保障号
相似率


布朗,克利福德
1
经理 1
668-419-628
150
58421
克利福德,布朗
854
500
杂项
668419628
0.97

史密斯,约翰
3
经理 3
965-21-4872
200
15789
史密斯,约翰
80
40
员工
965214872
0.97

蓝色,城市
2
经理2
874126487



0.00

哈伦,西蒙
4
经理 4
741-98-7820



0.00


我试过下面的代码:

import pandas as pd
from fuzzywuzzy import fuzz

df1 = pd.read_excel(r'Directory\Sample Vendor Data.xlsx')
df2 = pd.read_excel(r'Directory\Sample Employee Data.xlsx')

matched_names = []

for row1 in df1.index:
    name1 = df1._get_value(row1, 'Vendor Name')  
    for row2 in df2.index:
        name2 = df2._get_value(row2, 'Full Name')  
        match = fuzz.ratio(name1, name2)
        if match > 80:  # This is the threshold
            match.append([name1, name2, match])

df_ratio = pd.DataFrame(columns=['Vendor Name', 'Employee Name','match'], data=matched_names)
df_ratio.to_csv(r'directory\MatchingResults.csv',  encoding='utf-8')
我只是没有得到我想要的结果,并准备重新发明整个脚本。任何建议都有助于改进我的脚本。请注意,我对 Python 还很陌生,所以请保持温和。我对这个例子的新方法完全开放。
9 月 23 日更新:
仍然有问题......我现在能够获得相似率,但无法从两个 CSV 文件中获得所有列。问题是这两个文件完全不同,所以当我连接时,它给出了 NaN 值。有什么建议?新代码如下:
import numpy as np
from fuzzywuzzy import fuzz
from itertools import product
import pandas as pd

df1 = pd.read_excel(r'Directory\Sample Vendor Data.xlsx')
df2 = pd.read_excel(r'Directory\Sample Workday Data.xlsx')

df1['full_name']= df1['Vendor Name']
df2['full_name'] = df2['Employee Name']

df1_name = df1['full_name']
df2_name = df2['full_name']

frames = [pd.DataFrame(df1), pd.DataFrame(df2)]
df = pd.concat(frames).reset_index(drop=True)

dist = [fuzz.ratio(*x) for x in product(df.full_name, repeat=2)]
dfresult = pd.DataFrame(np.array(dist).reshape(df.shape[0], df.shape[0]), columns=df.full_name.values.tolist())

#create of list of dataframes 
listOfDfs = [dfresult.loc[idx] for idx in np.split(dfresult.index, df.shape[0])]

DataFrameDict = {df['full_name'][i]: listOfDfs[i] for i in range(dfresult.shape[0])}

for name in DataFrameDict.keys():
    print(name)
    #print(DataFrameDict[name]

df = pd.DataFrame(list(DataFrameDict.items())).df.to_excel(r'Directory\TestOutput.xlsx', index = False)

最佳答案

为了水平连接两个 DataFrame,我通过匹配的供应商名称的索引对齐员工 DataFrame。如果没有匹配的供应商名称,我只是放置一个空行。
更多详情:

  • 我迭代了供应商名称,对于每个供应商名称,我将得分最高的员工姓名的索引添加到索引列表中。请注意,我添加了 最多一个 将员工记录与每个供应商名称相匹配。
  • 如果找不到匹配项(分数太低),我将手动添加到员工数据框中的空记录的索引添加。
  • 然后使用此索引列表对员工 DataDrame 重新排序。
  • 最后,我只是水平合并两个 DataFrame。请注意,此时的两个 DataFrame 不必具有相同的大小,但在这种情况下,concat方法只是通过将丢失的行附加到较小的 DataFrame 来填补空白。

  • 代码如下:
    
    import numpy as np
    import pandas as pd
    from thefuzz import process as fuzzy_process    # the new repository of fuzzywuzzy
    
    # import dataframes
    ...
    
    # adding empty row
    employees_df = employees_df.append(pd.Series(dtype=np.float64), ignore_index=True)
    index_of_empty = len(employees_df) - 1
    
    # matching between vendor and employee names
    indexed_employee_names_dict = dict(enumerate(employees_df["Employee Name"]))
    matched_employees = set()
    ordered_employees = []
    scores = []
    for vendor_name in vendors_df["Vendor Name"]:
        match = fuzzy_process.extractOne(
            query=vendor_name,
            choices=indexed_employee_names_dict,
            score_cutoff=80
        )
        score, index = match[1:] if match is not None else (0.0, index_of_empty)
        matched_employees.add(index)
        ordered_employees.append(index)
        scores.append(score)
    
    # detect unmatched employees to be positioned at the end of the dataframe
    missing_employees = [i for i in range(len(employees_df)) if i not in matched_employees]
    ordered_employees.extend(missing_employees)
    ordered_employees_df = employees_df.iloc[ordered_employees].reset_index()
    
    merged_df = pd.concat([vendors_df, ordered_employees_df], axis=1)
    # adding the scores column and sorting by its values
    scores.extend([0] * len(missing_employees))
    merged_df["Similarity Ratio"] = pd.Series(scores) / 100
    merged_df = merged_df.sort_values("Similarity Ratio", ascending=False)
    
    对于根据SSN列进行匹配,完全可以用同样的方式完成,只需要替换上面代码中的列名即可。此外,该过程可以概括为一个接受 DataFrames 和列名的函数:
    def match_and_merge(df1: pd.DataFrame, df2: pd.DataFrame, col1: str, col2: str, cutoff: int = 80):
        # adding empty row
        df2 = df2.append(pd.Series(dtype=np.float64), ignore_index=True)
        index_of_empty = len(df2) - 1
    
        # matching between vendor and employee names
        indexed_strings_dict = dict(enumerate(df2[col2]))
        matched_indices = set()
        ordered_indices = []
        scores = []
        for s1 in df1[col1]:
            match = fuzzy_process.extractOne(
                query=s1,
                choices=indexed_strings_dict,
                score_cutoff=cutoff
            )
            score, index = match[1:] if match is not None else (0.0, index_of_empty)
            matched_indices.add(index)
            ordered_indices.append(index)
            scores.append(score)
    
        # detect unmatched employees to be positioned at the end of the dataframe
        missing_indices = [i for i in range(len(df2)) if i not in matched_indices]
        ordered_indices.extend(missing_indices)
        ordered_df2 = df2.iloc[ordered_indices].reset_index()
    
        # merge rows of dataframes
        merged_df = pd.concat([df1, ordered_df2], axis=1)
    
        # adding the scores column and sorting by its values
        scores.extend([0] * len(missing_indices))
        merged_df["Similarity Ratio"] = pd.Series(scores) / 100
        return merged_df.sort_values("Similarity Ratio", ascending=False)
    
    
    if __name__ == "__main__":
        vendors_df = pd.read_excel(r'Directory\Sample Vendor Data.xlsx')
        employees_df = pd.read_excel(r'Directory\Sample Workday Data.xlsx')
        
        merged_df = match_and_merge(vendors_df, employees_df, "Vendor Name", "Employee Name")
        merged_df.to_excel("merged_by_names.xlsx", index=False)
        
        merged_df = match_and_merge(vendors_df, employees_df, "SSN", "SSN")
        merged_df.to_excel("merged_by_ssn.xlsx", index=False)
    
    上面的代码产生以下输出:
    merge_by_names.xlsx


    公司
    供应商编号
    供应商名称
    发票号码
    交易金额
    供应商类型
    社会保障号
    指数
    员工姓名
    员工ID
    经理
    社会保障号
    相似率


    200
    15789
    史密斯,约翰
    80
    40
    员工
    965214872
    2
    史密斯,约翰
    3
    经理 3
    965-21-4872
    1

    15
    58421
    克利福德布朗
    854
    500
    杂项
    668419628
    0
    布朗,克利福德
    1
    经理 1
    668-419-628
    0.95

    200
    69997
    哈伦,司曼
    964
    100
    杂项
    741-98-7821
    3
    哈伦,西蒙
    4
    经理 4
    741-98-7820
    0.92

    150
    9675
    绿色
    7412
    70
    一度
    774801971
    4




    0








    1
    蓝色,城市
    2
    经理2
    874126487
    0


    合并的_by_ssn.xlsx


    公司
    供应商编号
    供应商名称
    发票号码
    交易金额
    供应商类型
    社会保障号
    指数
    员工姓名
    员工ID
    经理
    社会保障号
    相似率


    200
    69997
    哈伦,司曼
    964
    100
    杂项
    741-98-7821
    3
    哈伦,西蒙
    4
    经理 4
    741-98-7820
    0.91

    15
    58421
    克利福德布朗
    854
    500
    杂项
    668419628
    0
    布朗,克利福德
    1
    经理 1
    668-419-628
    0.9

    200
    15789
    史密斯,约翰
    80
    40
    员工
    965214872
    2
    史密斯,约翰
    3
    经理 3
    965-21-4872
    0.9

    150
    9675
    绿色
    7412
    70
    一度
    774801971
    4




    0








    1
    蓝色,城市
    2
    经理2
    874126487
    0

    关于python - Python 中的模糊查找,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69276410/

    相关文章:

    Python 模块在 bash 中返回错误,但不在 IDLE 中返回错误

    python - 解析包含不同长度Python的时间戳

    python - BeautifulSoup 在使用 'class' 时卡住

    python - 我如何使用 django.auth.views.login?

    python - 如何使用 pandas DataFrame 计算列表字典?

    python - 使用 fuzzywuzzy 在数据框中创建新列

    python - FuzzyWuzzy 错误 : WARNING:root:Applied processor reduces input query to empty string, 所有比较的得分为 0。 [查询: '/']

    python - "is"在 python IDE 中不工作,但在命令行中工作

    python - 使用 pandas 根据值按日期对数据进行分组

    python - pandas 根据多个条件迭代行,然后从列中减去?