Я пытаюсь проверить нечеткое совпадение между строковым столбцом и списком ссылок. Серия строк содержит более 1 млн строк, а список ссылок содержит более 10 тыс. записей.
Например:
df['NAMES'] = pd.Series(['ALEXANDERS', 'NOVA XANDER', 'SALA MANDER', 'PARIS HILTON', 'THE HARIS DOWNTOWN', 'APARISIAN', 'PARIS', 'MARIN XO']) # 1mil rows
ref_df['REF_NAMES'] = pd.Series(['XANDER','PARIS']) #10 k rows
###Output should look like
df['MATCH'] = pd.Series([Nan, 'XANDER', 'MANDER', 'PARIS', 'HARIS', Nan, 'PARIS', Nan])
Он должен сгенерировать совпадение, если слово появляется отдельно в строке (и внутри него разрешена замена до 1 символа)
Например, «PARIS» может соответствовать «PARIS HILTON», «THE HARIS DOWNTOWN», но не «APARISIAN».
Точно так же «XANDER» соответствует «NOVA XANDER» и «SALA MANDER» (MANDER отличается от XANDER на 1 символ), но не создает совпадения с «ALEXANDERS».
На данный момент мы написали логику для того же самого (показанного ниже), хотя матч занимает более 4 часов. Нужно сделать это менее чем за 30 минут.
Текущий код -
tags_regex = ref_df['REF_NAMES'].tolist()
tags_ptn_regex = '|'.join([f'\s+{tag}\s+|^{tag}\s+|\s+{tag}$' for tag in tags_regex])
def search_it(partyname):
m = regex.search("("+tags_ptn_regex+ ")"+"{s<=1:[A-Z]}",partyname):
if m is not None:
return m.group()
else:
return None
df['MATCH'] = df['NAMES'].str.apply(search_it)
Кроме того, поможет ли многопроцессорность с регулярным выражением? Спасибо заранее!
Да, допускается до 1 замены, удаления или добавления. Спасибо!
Прежде всего, вы можете упростить регулярное выражение: \s+{tag}\s+|^{tag}\s+|\s+{tag}$
эквивалентно \b{tag}\b
, где \b
— граница слова. 2) почему вы успешно запускаете regex.search во второй раз? Хотя я думаю, что оба улучшения имеют относительно небольшой эффект
2) Я не думаю, что ваше регулярное выражение вообще работает - попробовал его на «АЛЕКСАНДЕ», и оно совпадает. Значит, у вас проблемы с корректностью в первую очередь. Я не думаю, что вы можете легко сделать «до 1 замены, удаления или добавления» с регулярным выражением.
На данный момент работает до 1 замены. Добавление/удаление приятно иметь. Я отредактировал скрипт так, чтобы regex.search был только один раз, .. как вы сказали .. не так много улучшений, хотя
По поводу исправления - думаю нужно сделать так tags_ptn_regex = '|'.join(tags_regex)
а потом regex.search("\\b("+tags_ptn_regex+ ")"+"{s<=1:[A-Z]}\\b",partyname)
иначе как я уже сказал он находит совпадение на "Александре" игнорируя \s+
, к тому же это немного проще и возможно быстрее. В искровом мире я бы предложил разбить ваши ИМЕНА на слова, затем взорвать, а затем сопоставить отдельные слова, но я не уверен, принесет ли это какую-либо пользу в пандах - в конце концов, ваши критерии соответствия сложны.
@YaroslavFyodorov .. Спасибо за исправление! К сожалению... это на выделенном сервере Py :(
По общему правилу, многопоточность кажется применимой здесь либо путем разделения вашего фрейма данных на несколько фрагментов и выполнения их независимо, затем объединения результатов, либо путем разделения ваших REF_NAMES и запуска их по отдельности. Не уверен, конкретно о питоне. Можете ли вы запустить несколько процессов Python и передать каждому из них часть вашего df?
Я бы попытался оценить нижнюю границу времени выполнения, запустив без замен (и, возможно, только часть REF_NAMES). Если это превышает ваш 30-минутный лимит...
Без нечеткого бита это намного меньше 10 минут, но нечеткая часть помогает с точностью
Ваш шаблон довольно неэффективен, так как вы повторяете шаблон tag
трижды в регулярном выражении. Вам просто нужно создать шаблон с так называемыми границами пробелов, (?<!\S)
и (?!\S)
, и вам понадобится только один шаблон tag
.
Далее, если у вас есть несколько тысяч альтернатив, даже регулярное выражение с одним шаблоном tag
будет чрезвычайно медленным, потому что могут появиться такие альтернативы, которые совпадают в одном и том же месте в строке, и, таким образом, будет слишком много возвратов.
Чтобы уменьшить этот возврат, вам понадобится regex trie.
Вот рабочий фрагмент:
import regex
import pandas as pd
## Class to build a regex trie, see https://stackoverflow.com/a/42789508/3832970
class Trie():
"""Regex::Trie in Python. Creates a Trie out of a list of words. The trie can be exported to a Regex pattern.
The corresponding Regex should match much faster than a simple Regex union."""
def __init__(self):
self.data = {}
def add(self, word):
ref = self.data
for char in word:
ref[char] = char in ref and ref[char] or {}
ref = ref[char]
ref[''] = 1
def dump(self):
return self.data
def quote(self, char):
return regex.escape(char)
def _pattern(self, pData):
data = pData
if "" in data and len(data.keys()) == 1:
return None
alt = []
cc = []
q = 0
for char in sorted(data.keys()):
if isinstance(data[char], dict):
try:
recurse = self._pattern(data[char])
alt.append(self.quote(char) + recurse)
except:
cc.append(self.quote(char))
else:
q = 1
cconly = not len(alt) > 0
if len(cc) > 0:
if len(cc) == 1:
alt.append(cc[0])
else:
alt.append('[' + ''.join(cc) + ']')
if len(alt) == 1:
result = alt[0]
else:
result = "(?:" + "|".join(alt) + ")"
if q:
if cconly:
result += "?"
else:
result = "(?:%s)?" % result
return result
def pattern(self):
return self._pattern(self.dump())
## Start of main code
df = pd.DataFrame()
df['NAMES'] = pd.Series(['ALEXANDERS', 'NOVA XANDER', 'SALA MANDER', 'PARIS HILTON', 'THE HARIS DOWNTOWN', 'APARISIAN', 'PARIS', 'MARIN XO']) # 1mil rows
ref_df = pd.DataFrame()
ref_df['REF_NAMES'] = pd.Series(['XANDER','PARIS']) #10 k row
trie = Trie()
for word in ref_df['REF_NAMES'].tolist():
trie.add(word)
tags_ptn_regex = regex.compile(r"(?:(?<!\S)(?:{})(?!\S)){{s<=1:[A-Z]}}".format(trie.pattern()), regex.IGNORECASE)
def search_it(partyname):
m = tags_ptn_regex.search(partyname)
if m is not None:
return m.group()
else:
return None
df['MATCH'] = df['NAMES'].apply(search_it)
Впечатляющий. Я думал предложить построить конечный автомат, но потом решил, что это слишком сложно и в любом случае не будет эффективно. Это хороший способ сделать это. Интересно, насколько быстро это на этих конкретных данных.
@YaroslavFyodorov Конечно, нечеткий квантификатор замедляет работу, но доказано, что сам подход хорошо работает с поисковыми фразами разной длины до 50 000.
Как насчет "НОВА XNDER"? Подходит ли «XANDER»?