Подтвердить что ты не робот

Возможно ли слияние нечетких совпадений с 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
4b9b3361

Ответ 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)

Ошибки, которые вы можете получить

  1. ZeroDivisionError: деление с плавающей точкой на zero---> Для устранения этой проблемы воспользуйтесь этой ссылкой
  2. OperationalError: Нет такого модуля: fts4 → downlaod в sqlite3.dll из здесь и заменить файл DLL в папке питона или анаконды библиотек DLL.

Плюсы:

  1. Работает быстрее В моем случае я сравнил один фрейм данных с 3000 строк с другим фреймом данных с 170 000 записей. Это также использует SQLite3 поиск по тексту. Так быстрее, чем многие
  2. Может проверять несколько столбцов и 2 кадра данных. В моем случае я искал наиболее близкое совпадение по адресу и названию компании. Иногда, название компании может быть таким же, но адрес тоже стоит проверить.
  3. Дает вам балл за все ближайшие совпадения для одной и той же записи. Вы выбираете, какой счет отсечки.

минусы:

  1. Оригинальная установка пакета глючит
  2. Требуется C++ и визуальные студии тоже установлены
  3. Не будет работать для 64-битной анаконды /Python