Сопоставление строк и сохранение результатов в виде списков в ячейках - PullRequest
0 голосов
/ 26 октября 2019

У меня есть две очень большие таблицы df1 и df2 (по несколько миллионов строк в каждой), относящиеся к человеку, и в каждой таблице есть столбец, содержащий имя человека (имя столбца: «Имя»). Имена одного и того же человека могут быть написаны по-разному (например, «Джефф МакГрегор» или «Мистер Дж. МакГрегор» и т. Д.) В двух таблицах, поэтому я хочу применить нечеткое сопоставление строк с пакетом fuzzywuzzy в Python(это просто сравнивает две строки и возвращает меру сходства).

В качестве выходных данных (см. Df3 для желаемой выходной таблицы), я хотел бы заполнить столбцы «Match_Flag» и «Match_List» в df1 в соответствии с записями в df2. Для каждого (уникального) человека в df1 я хочу проверить, есть ли совпадения (нечеткая строка) в df2. Если есть строка, столбец «Match_Flag» должен содержать «да», а если нет - «нет». Столбец «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. он будет слишком медленным для миллионов строк , прошу вас помочь мне и, пожалуйста, найти более эффективный и работающий подход.

Ответы [ 2 ]

0 голосов
/ 26 октября 2019

Этот ответ может занять некоторое время, но должен работать.

Я импортировал имена для создания больших фреймов данных со случайными именами.

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, я удалил дубликаты столбца имени. Мы собираемся выполнить перекрестное соединение, чтобы перенести всю строку информационного кадра во 2-й информационный кадр, поэтому я назначил 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'])
0 голосов
/ 26 октября 2019

на основе в этой теме Я считаю, что объединение этих двух фреймов данных намного эффективнее, чем итерация по всем данным. так как вам нужны совпадающие имена, вы должны использовать внутреннее соединение.

...