Возможно ли слияние нечетких совпадений с python pandas?
У меня есть два DataFrames, которые я хочу объединить на основе столбца. Однако из-за чередующихся написаний, разного количества пробелов, отсутствия/наличия диакритических знаков я хотел бы иметь возможность сливаться, пока они похожи друг на друга.
Будет выполняться любой алгоритм подобия (soundex, Levenshtein, difflib's).
Скажем, что один DataFrame имеет следующие данные:
df1 = DataFrame([[1],[2],[3],[4],[5]], index=['one','two','three','four','five'], columns=['number'])
number
one 1
two 2
three 3
four 4
five 5
df2 = DataFrame([['a'],['b'],['c'],['d'],['e']], index=['one','too','three','fours','five'], columns=['letter'])
letter
one a
too b
three c
fours d
five e
Затем я хочу получить полученный DataFrame
number letter
one 1 a
two 2 b
three 3 c
four 4 d
five 5 e
Ответы
Ответ 1
Подобно предложению @locojay, вы можете применить difflib
get_close_matches
к индексу df2
а затем применить join
:
In [23]: import difflib
In [24]: difflib.get_close_matches
Out[24]: <function difflib.get_close_matches>
In [25]: df2.index = df2.index.map(lambda x: difflib.get_close_matches(x, df1.index)[0])
In [26]: df2
Out[26]:
letter
one a
two b
three c
four d
five e
In [31]: df1.join(df2)
Out[31]:
number letter
one 1 a
two 2 b
three 3 c
four 4 d
five 5 e
,
Если бы это были столбцы, в том же духе вы могли бы применить к столбцу, а затем merge
:
df1 = DataFrame([[1,'one'],[2,'two'],[3,'three'],[4,'four'],[5,'five']], columns=['number', 'name'])
df2 = DataFrame([['a','one'],['b','too'],['c','three'],['d','fours'],['e','five']], columns=['letter', 'name'])
df2['name'] = df2['name'].apply(lambda x: difflib.get_close_matches(x, df1['name'])[0])
df1.merge(df2)
Ответ 2
Я написал пакет Python, который призван решить эту проблему:
pip install fuzzymatcher
Вы можете найти репо здесь и документы здесь.
Основное использование:
Имея два df_left
и df_right
, которые вы хотите соединить нечетко, вы можете написать следующее:
from fuzzymatcher import link_table, fuzzy_left_join
# Columns to match on from df_left
left_on = ["fname", "mname", "lname", "dob"]
# Columns to match on from df_right
right_on = ["name", "middlename", "surname", "date"]
# The link table potentially contains several matches for each record
fuzzymatcher.link_table(df_left, df_right, left_on, right_on)
Или, если вы просто хотите дать ссылку на ближайший матч:
fuzzymatcher.fuzzy_left_join(df_left, df_right, left_on, right_on)
Ответ 3
Я бы использовал Jaro-Winkler, потому что это один из наиболее эффективных и точных приближенных алгоритмов сопоставления строк, доступных в настоящее время [Cohen, et al.], [Winkler].
Вот как я сделал бы это с Jaro-Winkler из пакета jellyfish:
def get_closest_match(x, list_strings):
best_match = None
highest_jw = 0
for current_string in list_strings:
current_score = jellyfish.jaro_winkler(x, current_string)
if(current_score > highest_jw):
highest_jw = current_score
best_match = current_string
return best_match
df1 = pandas.DataFrame([[1],[2],[3],[4],[5]], index=['one','two','three','four','five'], columns=['number'])
df2 = pandas.DataFrame([['a'],['b'],['c'],['d'],['e']], index=['one','too','three','fours','five'], columns=['letter'])
df2.index = df2.index.map(lambda x: get_closest_match(x, df1.index))
df1.join(df2)
Вывод:
number letter
one 1 a
two 2 b
three 3 c
four 4 d
five 5 e
Ответ 4
Использование fuzzywuzzy
Ответ 2019
Поскольку в пакете fuzzywuzzy
нет примеров, здесь я написал функцию, которая будет возвращать все совпадения на основе порога, который вы можете установить как пользователь:
Пример кадра данных
df1 = pd.DataFrame({'Key':['Apple', 'Banana', 'Orange', 'Strawberry']})
df2 = pd.DataFrame({'Key':['Aple', 'Mango', 'Orag', 'Straw', 'Bannanna', 'Berry']})
# df1
Key
0 Apple
1 Banana
2 Orange
3 Strawberry
# df2
Key
0 Aple
1 Mango
2 Orag
3 Straw
4 Bannanna
5 Berry
Функция нечеткого соответствия
def fuzzy_merge(df_1, df_2, key1, key2, threshold=90, limit=2):
"""
df_1 is the left table to join
df_2 is the right table to join
key1 is the key column of the left table
key2 is the key column of the right table
threshold is how close the matches should be to return a match, based on Levenshtein distance
limit is the amount of matches that will get returned, these are sorted high to low
"""
s = df_2[key2].tolist()
m = df_1[key1].apply(lambda x: process.extract(x, s, limit=limit))
df_1['matches'] = m
m2 = df_1['matches'].apply(lambda x: ', '.join([i[0] for i in x if i[1] >= threshold]))
df_1['matches'] = m2
return df_1
Используя нашу функцию на фреймах данных: # 1
from fuzzywuzzy import fuzz
from fuzzywuzzy import process
fuzzy_merge(df1, df2, 'Key', 'Key', threshold=80)
Key matches
0 Apple Aple
1 Banana Bannanna
2 Orange Orag
3 Strawberry Straw, Berry
Используя нашу функцию на фреймах данных: # 2
df1 = pd.DataFrame({'Col1':['Microsoft', 'Google', 'Amazon', 'IBM']})
df2 = pd.DataFrame({'Col2':['Mcrsoft', 'gogle', 'Amason', 'BIM']})
fuzzy_merge(df1, df2, 'Col1', 'Col2', 80)
Col1 matches
0 Microsoft Mcrsoft
1 Google gogle
2 Amazon Amason
3 IBM
Установка:
Pip
pip install fuzzywuzzy
Anaconda
conda install -c conda-forge fuzzywuzzy
Ответ 5
http://pandas.pydata.org/pandas-docs/dev/merging.html не имеет функции подключения, чтобы сделать это на лету. Было бы хорошо, хотя...
Я просто сделал бы отдельный шаг и использовал бы difflib getclosest_matches, чтобы создать новый столбец в одном из 2 фреймов данных и объединить/объединить столбец с нечетким соответствием.
Ответ 6
Как хэдз-ап, это в основном работает, за исключением случаев, когда совпадение не найдено, или если у вас есть NaNs в любом столбце. Вместо прямого применения get_close_matches
мне было проще применить следующую функцию. Выбор заменителей NaN будет сильно зависеть от вашего набора данных.
def fuzzy_match(a, b):
left = '1' if pd.isnull(a) else a
right = b.fillna('2')
out = difflib.get_close_matches(left, right)
return out[0] if out else np.NaN
Ответ 7
Вы можете использовать d6tjoin для этого
import d6tjoin.top1
d6tjoin.top1.MergeTop1(df1.reset_index(),df2.reset_index(),
fuzzy_left_on=['index'],fuzzy_right_on=['index']).merge()['merged']
index number index_right letter 0 one 1 one a 1 two 2 too b 2 three 3 three c 3 four 4 fours d 4 five 5 five e
Он имеет множество дополнительных функций, таких как:
- проверить качество соединения, до и после соединения
- настроить функцию подобия, например, расстояние редактирования и расстояние от хамминга
- указать максимальное расстояние
- многоядерные вычисления
Подробнее см.
Ответ 8
Я использовал пакет Fuzzymatcher, и это хорошо сработало для меня. Посетите эту ссылку для более подробной информации об этом.
используйте команду ниже для установки
pip install fuzzymatcher
Ниже приведен пример кода (уже представлен RobinL выше)
from fuzzymatcher import link_table, fuzzy_left_join
# Columns to match on from df_left
left_on = ["fname", "mname", "lname", "dob"]
# Columns to match on from df_right
right_on = ["name", "middlename", "surname", "date"]
# The link table potentially contains several matches for each record
fuzzymatcher.link_table(df_left, df_right, left_on, right_on)
Ошибки, которые вы можете получить
- ZeroDivisionError: деление с плавающей точкой на zero---> Для устранения этой проблемы воспользуйтесь этой ссылкой
- OperationalError: Нет такого модуля: fts4 → downlaod в sqlite3.dll из здесь и заменить файл DLL в папке питона или анаконды библиотек DLL.
Плюсы:
- Работает быстрее В моем случае я сравнил один фрейм данных с 3000 строк с другим фреймом данных с 170 000 записей. Это также использует SQLite3 поиск по тексту. Так быстрее, чем многие
- Может проверять несколько столбцов и 2 кадра данных. В моем случае я искал наиболее близкое совпадение по адресу и названию компании. Иногда, название компании может быть таким же, но адрес тоже стоит проверить.
- Дает вам балл за все ближайшие совпадения для одной и той же записи. Вы выбираете, какой счет отсечки.
минусы:
- Оригинальная установка пакета глючит
- Требуется C++ и визуальные студии тоже установлены
- Не будет работать для 64-битной анаконды /Python