【问题标题】:String matching and store results as lists in cells字符串匹配并将结果存储为单元格中的列表
【发布时间】:2020-02-22 12:32:49
【问题描述】:

我有两个非常大的表 df1 和 df2(每个表有数百万行),其中包含人员相关数据,每个表都有一个包含人名的列(列名:“姓名”)。同一个人的名字在两张表之间可以写成不同的形式(例如“Jeff McGregor”或“Mr. J McGregor”等),这就是为什么我想用 Python 中的fuzzywuzzy 包应用模糊字符串匹配(这只是比较两个字符串并返回一个相似性度量)。

作为输出(有关所需的输出表,请参见 df3),我想根据 df2 中的条目填写 df1 中的“Match_Flag”和“Match_List”列。对于 df1 中的每个(唯一)人,我想检查 df2 中是否有(模糊字符串)匹配项。如果有字符串,则“Match_Flag”列应包含“yes”,如果没有,则“no”。 “Match_list”列应包含每个名称的匹配列表。如果有一个匹配项,则列表将包含一个条目,如果有例如三个匹配项,列表将包含 3 个匹配项。如果没有匹配,列表应该是空的。

这是数据:

df1

data_df1 = {'ID':[56382, 34732, 12423, 29574, 76532], 
           'Name':['Tom Hilley', 'Andreas Puthz', 'Jeff McGregor', 'Jack Ebbstein', 'Lisa Norwat'],
           'Match_Flag':["", "", "", "", ""],
           'Match_List':["", "", "", "", ""]} 

df1 = pd.DataFrame(data_df1) 

print(df1)

      ID           Name Match_Flag Match_List
0  56382     Tom Hilley                      
1  34732  Andreas Puthz                      
2  12423  Jeff McGregor                      
3  29574  Jack Ebbstein                      
4  76532    Lisa Norwat  

df2

data_df2 = {'Name':['Tom Hilley', 'Madalina Peter', 'Russel Cross', 'Jenni Pey', 'Kanush Hawks', 'Mr. J McGregor', 'Ebbstein Jack', 'Mr. Jack Ebbstein'],
           'Age':[16, 56, 33, 44, 24, 26, 86, 32]} 

df2 = pd.DataFrame(data_df2) 

print(df2)

                Name  Age
0         Tom Hilley   16
1     Madalina Peter   56
2       Russel Cross   33
3          Jenni Pey   44
4       Kanush Hawks   24
5     Mr. J McGregor   26
6      Ebbstein Jack   86
7  Mr. Jack Ebbstein   32

df3

data_df3 = {'ID':[56382, 34732, 12423, 29574, 76532], 
               'Name':['Tom Hilley', 'Andreas Puthz', 'Jeff McGregor', 'Jack Ebbstein', 'Lisa Norwat'],
               'Match_Flag':["yes", "no", "yes", "yes", "no"],
               'Match_List':[["Tom Hilley"], [], ["Mr. J McGregor"], ["Ebbstein Jack","Mr. Jack Ebbstein"], []]} 

df3 = pd.DataFrame(data_df3)

print(df3)

     ID           Name Match_Flag                          Match_List
0  56382     Tom Hilley        yes                        [Tom Hilley]
1  34732  Andreas Puthz         no                                  []
2  12423  Jeff McGregor        yes                    [Mr. J McGregor]
3  29574  Jack Ebbstein        yes  [Ebbstein Jack, Mr. Jack Ebbstein]
4  76532    Lisa Norwat         no                                  []

我的方法:

# import libraries
import pandas as pd
from fuzzywuzzy import fuzz  

# create matching
for i in df1["Name"].unique().tolist():

    # initialize matching list
    matching_list = []

    for j in df2["Name"].unique().tolist():

        # create matching score
        if fuzz.token_set_ratio(i, j) >= 90:
            matching_list.append(j)

    # create red flags
    if matching_list:
        df1.loc[df1['Name'] == i,'Match_Flag'] = 'yes'
        df1.loc[df1['Name'] == i,'Match_List'] = matching_list
    else:
        df1.loc[df1['Name'] == i,'Match_Flag'] = 'no'
        df1.loc[df1['Name'] == i,'Match_List'] = ["-"]

我的方法的输出:

line 611, in _setitem_with_indexer
    raise ValueError('Must have equal len keys and value '

ValueError: Must have equal len keys and value when setting with an iterable

由于我的方法是 1. 不工作和 2. 对于数百万行来说太慢了,我请你帮助我,并找到一个更有效和工作的方法。 p>

【问题讨论】:

    标签: python-3.x pandas list loops fuzzy-comparison


    【解决方案1】:

    基于this topic 我相信合并这两个数据框比遍历整个数据更有效。 既然你想要匹配的名字,你应该使用内连接。

    【讨论】:

    • 模糊匹配给出了匹配的近似值,而不是 精确 匹配,因此连接不起作用。通常我可能只是交叉连接两个表(不匹配任何东西),然后 .apply 函数,但是数百万行数据框会创建一个万亿+行数据框,这是行不通的。
    • 这有点不同,你可以在主题中看到有一些预处理使这个连接模糊
    • 哦,很有趣。我没看到链接。这可能行得通!
    【解决方案2】:

    这个答案可能需要一段时间才能运行,但应该可以。

    我导入了名称以使用随机名称创建更大的数据框。

    import pandas as pd
    from fuzzywuzzy import fuzz 
    import random
    import os
    import names
    
    
    
    id_col = range(10000)
    name_col = [names.get_full_name() for _ in range(10000)]
    df1 = pd.DataFrame({'ID':id_col, 'name_col':name_col})
    
    age = [random.randint(1, 95) for _ in range(10000)]
    name_col2 = [names.get_full_name() for _ in range(10000)]
    df2 = pd.DataFrame({'name_col2':name_col2, 'age':age})
    

    因为我们要遍历 df1,所以我删除了 name 列的重复项。我们要进行交叉连接,将数据帧的整行放入第二个数据帧,所以我分配了 v=1

    df1_deduped = df1.drop_duplicates('name_col')
    df2 = df2.assign(v=1)
    

    定义要在.apply中使用的模糊函数

    def func(row):
        return fuzz.token_set_ratio(row['name_col'], row['name_col2'])
    

    在这里,我们将遍历第一个数据帧的长度,并且对于每一行(唯一名称),我们将其连接到第二个数据帧。然后我们 .apply 将模糊函数添加到 tokenthresh 列,并按阈值 70 过滤数据帧。如果有任何匹配项,则将其写入 csv。这样,它并不是全部在内存中完成,这很可能对你来说是一个问题,两边都有数百万行数据帧。这会将它分成几块。或者,您可以在 5 秒或 10 秒内完成,而不是逐行进入一百万行数据帧,这可能会减慢速度,我不确定。

    for i in range(len(df1_deduped)):
        df3 = pd.merge(df1.assign(v=1).iloc[[i],:], df2, on='v').drop(['v'], axis=1)
        df3['tokenthresh'] = df3.apply(func, axis=1)
        df3 = df3[df3.tokenthresh > 70]
        print('there are', len(df3), 'records that exceeded the threshold')
        if len(df3) > 0:
            df3.to_csv(str(i)+'.csv', index=False)
    

    然后我们可以读入创建的文件:

    files = []
    for file in os.listdir():
        files.append(pd.read_csv(file))
    data = pd.concat(files)
    

    最后连接不同的答案:

    data['concat_group'] = data.groupby(['ID', 'name_col'])['name_col2'].transform(lambda x: ', '.join(x))
    data = data.drop_duplicates(['ID', 'name_col'])
    

    【讨论】:

    • 感谢您的解决方案!不幸的是,我收到一条错误消息:“ParserError: Error tokenizing data. C error: Expected 1 fields in line 7, saw 5”。此外,这会降低我的数据大小:/
    • 发布错误?对于创建的数据集,这至少可以工作。您查看 phalanx 的链接了吗?
    猜你喜欢
    • 2021-11-30
    • 2017-03-08
    • 1970-01-01
    • 2014-09-02
    • 1970-01-01
    • 2014-01-09
    • 1970-01-01
    • 2020-04-24
    • 1970-01-01
    相关资源
    最近更新 更多