Как нечетко сопоставить названия фильмов с difflib и pandas?

У меня есть 2 списка потенциально перекрывающихся названий фильмов, но, возможно, написанных в другой форме.
Они находятся в 2 разных кадрах данных из панд. Итак, я попытался использовать функцию map() с библиотекой fuzzywuzzy следующим образом:

df1.title.map(lambda x: process.extractOne(x, choices=df2.title, score_cutoff=95))

Что дает хорошие качественные результаты. Но временная сложность такова, что я могу запустить его только на очень небольшом подмножестве обоих фреймов данных. Когда я пытаюсь увеличить размер фреймов данных, он быстро становится непригодным для использования.

Затем я попытался заменить fuzzywuzzy на difflib. И это намного быстрее. Но я не могу добиться желаемых результатов.

Сначала я попробовал:

df1.title.map(lambda x: difflib.get_close_matches(x, df2.title, n=1)

И это было быстро, но качество результатов было плохим. Даже отсутствуют некоторые простые изменения верхнего/нижнего регистра. Игра с cutoff не помогла.

Поэтому я подумал, что использую неправильный инструмент. В документах и ​​примерах я видел get_close_matches для отдельных слов. В названиях есть разные слова.

SequenceMatcher лучше?

И если да, то как мне вписать его в map(), чтобы он делал то же самое, что и вышеупомянутые функции: возвращал только лучший результат, и только если результат выше определенного соотношения?


person Bastian    schedule 13.07.2016    source источник


Ответы (2)


Я написал пакет Python, который призван решить эту проблему. Среди прочего, он решает проблему сложности n^2 (например, с двумя наборами данных длиной 100 вашему коду требуется 10 000 сравнений).

Вы можете установить его с помощью pip install fuzzymatcher

Вы можете найти репозиторий здесь и документы здесь.

Основное использование:

Имея два фрейма данных df_left и df_right, которые вы хотите объединить нечетко, вы можете написать следующее:

from fuzzymatcher import link_table, 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)
person RobinL    schedule 04.12.2017
comment
Очень нужный пакет. Но для этого требуются инструменты сборки Visual C++, которые занимают 4 ГБ на жестком диске. - person user2978216; 20.01.2018
comment
Интересно - я думаю, вы на Windows? У вас уже установлены pandas/numpy? Не могли бы вы предоставить подробную информацию о том, где/когда вы получаете сообщение об ошибке? - person RobinL; 21.01.2018
comment
Я на Windows, установлены pandas/numpy. Попытка установки выдает сообщение об ошибке - person user2978216; 21.01.2018
comment
Спасибо, не знал, что пакету Левенштейна это нужно. Я открыл вопрос, чтобы напомнить себе избавиться от этой зависимости в следующем выпуске. - person RobinL; 21.01.2018
comment
Примечание: теперь это исправлено в последнем коде на Github. - person RobinL; 14.10.2019

Чтобы исключить вероятность совпадения с низким баллом в результате различий в регистре, я бы предложил применить .upper() или .lower() к столбцам, которые вы сопоставляете. После настройки регистра можно было составить список всех заголовков в ThisList и применить следующую функцию (опираясь, как вы предложили, на SequenceMatcher) с заданным tolerance.

def fuzzy_group_list_elements(ThisList,Tolerance):
    from difflib import SequenceMatcher
    Groups = {}
    TempList = ThisList.copy()
    for Elmt in TempList:
        if Elmt not in Groups.keys():
            Groups[Elmt] = []
        for OtherElmt in TempList:
            if SequenceMatcher(None,Elmt,OtherElmt).quick_ratio() > Tolerance:
                Groups[Elmt] = Groups[Elmt] + [OtherElmt]
                TempList.remove(OtherElmt)
    Groups[Elmt] = list(set(Groups[Elmt]))
    return dict((v,k) for k in Groups for v in Groups[k])

Затем вы можете применить вышеуказанную функцию к столбцам фрейма данных, содержащим названия фильмов:

Mapping = fuzzy_group_list_elements(ThisList,0.85)
df['Matched Title'] = df['Title'].replace(Mapping)
person DrTRD    schedule 13.07.2016