Нечеткое соответствие с длинным предложением(предложениями)

#python-3.x #nlp #fuzzy-search #fuzzy #fuzzy-comparison

Вопрос:

предположим, у меня есть следующий фрейм данных:

 ID       CompanyName         JobDescription
1        Green Grass LLC     "In the centre of Green Grass area..."
2        Johnny Inc.          "Johnny is currently looking for data analist that..."
3        Liamloy             "LiamLoy Corp. is established in New York..."
4        KaasKan             "In the forest we are walking..."
 

Моя главная цель-исключить CompanyName то, что есть в каждом JobDescription . Желаемый результат был бы:

 ID       CompanyName         JobDescription
1        Green Grass LLC     "In the centre of area..."
2        Johnny Inc.          "is currently looking for data analist that..."
3        Liamloy             "is established in New York..."
4        KaasKan             "In the forest we are walking"
 

Я попытался word tokenize JobDescription (преобразовать предложение в слова) и применить fuzzymatching для обнаружения и удаления совпадений. Однако это оказалось не очень успешным. Например, при токенизации третьего JobDescription . « Liamloy » сравнивается с « LiamLoy » и « Corp .». Возможно, этот подход не идеален. На данный момент я понятия не имею. Интересно, не хотел бы кто-нибудь из вас поделиться своим мнением и просветить меня, как я могу успешно удалить CompanyName в каждом JobDescription из них .

Ответ №1:

Если вы не ожидаете, что слова в названиях компаний будут заменены, я предлагаю использовать встроенную библиотеку python difflib, чтобы найти общую часть двух строк и заменить ее маской.

 def find_matching_spans(a, b, min_match=3, max_mismatch=1):
    """ Find the spans in the string b that are similar to the string a"""
    prev_match = 0
    match = 0
    mismatch = 0
    i = 0
    span_start = 0
    prev_start = 0
    span_end = 0
    spans = []
    common = []
    
    def add_span():
        if prev_match > min_match:
            if spans and spans[-1][-1] >= prev_start - 2:
                spans[-1][-1] = span_end
            else:
                spans.append([prev_start, span_end])
    
    for item in difflib.ndiff(a.lower(), b.lower()):
        if item[0] == ' ' and item[2] != ' ':
            match  = 1
            mismatch = 0
            if match == 1:
                span_start = i
                common = []
            common.append(item[2])
        elif item[0] == ' ' or item[2] == ' ':
            if match > min_match:
                add_span()
                prev_start = span_start
                prev_match = match
                span_end = i
            match = 0
            mismatch  = 1
            if mismatch > max_mismatch:
                add_span()
                prev_match = 0
        elif item[0] == '-':
            pass
        if item[0] in {' ', ' '}:
            i  = 1
    return spans


def replace_spans(text, spans, replacement):
    spans = [[0, 0]]   spans   [[len(text), len(text)]]
    parts = []
    for i in range(1, len(spans)):
        parts.append(text[spans[i-1][1]:spans[i][0]])
        if i < len(spans) - 1:
            parts.append('XXX')
    return ''.join(parts)


def replace_name(a, b, replacement='XXX'):
    b_prev = None
    while b_prev != b:
        spans = find_matching_spans(a, b)
        b_prev = b
        b = replace_spans(b, spans, replacement)
    return b
 

Это будет работать так:

 print(replace_name("Green Grass LLC", "In the centre of Green Grass area..."))
print(replace_name("Johnny Inc.", "Johnny is currently looking for data analist that..."))
print(replace_name("Liamloy", "LiamLoy Corp. is established in New York..."))
print(replace_name("KaasKan", "In the forest we are walking..."))
 

И производить продукцию

 In the centre of XXX area...
XXX is currently looking for data analist that...
XXX Corp. is established in New York...
In the forest we are walking...
 

Комментарии:

1. Это работает только для коротких предложений. Почему-то это не работает для длинных предложений. Однако я ценю ваши усилия.

Ответ №2:

Почему бы не использовать регулярное выражение?

 import re


def replace_company_name(company_name, text):
    sanitized_text = re.sub(company_name, '', text)
    return sanitized_text
 

Похоже, вам также нужно рассмотреть исправления после названия компании, такие как corp, из-за примера с Лиамлоем.

Один из способов справиться с этим-обычный набор констант после исправления названия компании. Вы также должны отметить, что я использую флаг игнорировать регистр, потому что, глядя на строку для Liamloy, название компании-Liamloy, в то время как в описании должности это LiamLoy. Также, вероятно, существуют различия в том, как капитализируются исправления после исправления (INC, Inc, inc и т. Д. )

 COMPANY_NAME_POSTFIXES = '|'.join(['INC', 'CORP', 'LLC', 'LTD'])


def replace_company_name(company_name, text):

    # 1. replace any postfixes in the company name. E.G. Green Grass LLC. -> Green Grass 
    company_name_post_fixregex = rf'({COMPANY_NAME_POSTFIXES})?\.?'
    sanitized_company_name = re.sub(company_name_postfix_regex, '', company_name, flags=re.IGNORECASE)
    # 2. replace any instances of the sanitized company name followed optionally by both a space and a company name postfix
    search_string = rf'{sanitized_company_name}\s?{company_name_postfix_regex}'
    sanitized_text = re.sub(search_string, '', text, flags=re.IGNORECASE)
    return sanitized_text
 

Вышеуказанный подход приведет к побочному эффекту замены используемых слов, если они не являются названием компании. Например, для ООО «Зеленая трава» «В центре зоны зеленой травы много зеленой травы, за которой ухаживают» — > В центре области много того, за чем ухаживают

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