Какой самый быстрый способ сопоставления, замены и извлечения подстрок из кадра данных pandas с несколькими критериями? - PullRequest
0 голосов
/ 30 января 2019

У меня есть приблизительно 1 миллион строк данных о пандах, содержащих данные, проанализированные на основании федерального апелляционного суда.Мне нужно извлечь имена судей, рассматривающих дела.Данные имеют неизвестное количество судей по делу (одна строка), которые содержатся в строке.Эта строка (в настоящее время хранится в одном столбце) содержит много лишнего текста, а также имеет несовместимое форматирование и использование заглавных букв.Я использую разные словари имен судей (с возможностью использования 2575 ключей регулярных выражений) для сопоставления судей, перечисленных на основе нескольких критериев, описанных ниже.Сначала я использую словарь с самыми строгими критериями соответствия и постепенно ослабляю критерии.Я также удаляю соответствующую строку из исходного столбца.Нынешние методы, которые я пробовал, слишком медленны (занимают дни, недели или даже месяцы).Причина существования нескольких возможных словарей в том, что многие судьи имеют одинаковые (фамилии) имена.Строки обычно не содержат полных имен.Я использую данные, содержащиеся в двух других столбцах, чтобы получить правильное соответствие: год, когда дело было решено, и суд, рассматривающий дело (оба целых числа).У меня также есть более и менее качественные условия поиска подстроки.Словари, которые я использую, могут быть созданы по желанию в разных форматах, кроме регулярных выражений, если это необходимо.

Самое быстрое решение, которое я пробовал, было грубым и непифоничным.При первоначальном разборе данных (извлечении разделов и ключевых слов из необработанных текстовых файлов), который происходит в каждом конкретном случае, я сделал следующее: 1) удалил лишний текст в максимально возможной степени, 2) отсортировал оставшиесятекст в список, хранящийся в столбце панд, 3) объединяет в виде строк год и суд для каждого элемента в этом списке, и 4) сопоставляет эту объединенную строку со словарем, который я подготовил аналогичным образом.Этот словарь не использовал регулярные выражения и имел приблизительно 800 000 ключей.Этот процесс занял около суток (с учетом всех других разборов) и был не столь точным, как мне бы хотелось (потому что в нем отсутствовали определенные перестановки формата имени).Приведенный ниже код содержит мою последнюю попытку (которая в данный момент выполняется и выглядит одной из самых медленных).Он создает подмножества словарей на лету и все равно в конечном итоге перебирает эти меньшие словари с ключами регулярных выражений.Я прочитал и попытался применить решения из многих вопросов stackoverflow, но не смог найти работоспособное решение.Я открыт для любой идеи на основе Python.Данные - это реальные данные, которые я очистил с помощью предыдущей функции.

import numpy as np
import pandas as pd

test_data = {'panel_judges' : ['CHAGARES, VANASKIE, SCHWARTZ', 
                               'Sidney R. Thomas, Barry G. Silverman, Raymond C. Fisher, Opinion by Thomas'],
             'court_num' : [3, 9],
             'date_year' : [2014, 2014]}
test_df = pd.DataFrame(data = test_data)

name_dict = {'full_name' : ['Chagares, Michael A.', 
                            'Vanaskie, Thomas Ignatius',
                            'Schwartz, Charles, Jr.',
                            'Schwartz, Edward Joseph',
                            'Schwartz, Milton Lewis',
                            'Schwartz, Murray Merle'],
             'court_num' : [3, 3, 1061, 1097, 1058, 1013],
             'circuit_num' : [3, 3, 5, 9, 9, 3],
             'start_year' : [2006, 2010, 1976, 1968, 1979, 1974],
             'end_year' : [2016, 2019, 2012, 2000, 2005, 2013],
             'hq_match' : ['M(?=ICHAEL)? ?A?(?=\.)? ?CHAGARES',
                           'T(?=HOMAS)? ?I?(?=GNATIUS)? ?VANASKIE',
                           'C(?=HARLES)? SCHWARTZ',
                           'E(?=DWARD)? ?J?(?=OSEPH)? ?SCHWARTZ',
                           'M(?=ILTON)? ?L?(?=EWIS)? ?SCHWARTZ',
                           'M(?=URRAY)? ?M?(?=ERLE)? ?SCHWARTZ'],
             'lq_match' : ['CHAGARES',
                           'VANASKIE',
                           'SCHWARTZ', 
                           'SCHWARTZ', 
                           'SCHWARTZ', 
                           'SCHWARTZ']}
names = pd.DataFrame(data = name_dict)

in_col = 'panel_judges'
year_col = 'date_year'
out_col = 'fixed_panel'
court_num_col = 'court_num'

test_df[out_col] = ''
test_df[out_col].astype(list, inplace = True)

def judge_matcher(df, in_col, out_col, year_col, court_num_col, 
                  size_column = None):
    general_cols = ['start_year', 'end_year', 'full_name']
    court_cols = ['court_num', 'circuit_num']
    match_cols = ['hq_match', 'lq_match']
    for match_col in match_cols:
        for court_col in court_cols:
            lookup_cols = general_cols + [court_col] + [match_col]
            judge_df = names[lookup_cols]
            for year in range(df[year_col].min(),
                              df[year_col].max() + 1):
                for court in range(df[court_num_col].min(),
                                   df[court_num_col].max() + 1):
                    lookup_subset = ((judge_df['start_year'] <= year)
                                     & (year < (judge_df['end_year'] + 2))
                                     & (judge_df[court_col] == court))  
                    new_names = names.loc[lookup_subset]
                    df_subset = ((df[year_col] == year) 
                                  & (df[court_num_col] == court))
                    df.loc[df_subset] = matcher(df.loc[df_subset], 
                          in_col, out_col, new_names, match_col)  
    return df   

def matcher(df, in_col, out_col, lookup, keys):
    patterns = dict(zip(lookup[keys], lookup['full_name']))
    for key, value in patterns.items():
        df[out_col] = ( 
             np.where(df[in_col].astype(str).str.upper().str.contains(key), 
                                  df[out_col] + value + ', ', df[out_col]))
        df[in_col] = df[in_col].astype(str).str.upper().str.replace(key, '')    
    return df 

df = judge_matcher(test_df, in_col, out_col, year_col, court_num_col) 

Вывод, который я получаю в настоящее время, по существу правильный (хотя имена должны быть отсортированы и в списке).Правильный «Шварц» выбран, и все совпадения верны.Проблема в скорости.Моя цель - иметь дедуплицированный, отсортированный (в алфавитном порядке) список судей на каждой панели, либо храниться в одном столбце, либо разбиваться на 15 отдельных столбцов (в настоящее время я делаю это в отдельной векторизованной функции).Затем я проведу другие поиски этих судей на основе другой демографической и биографической информации.Полученные данные будут доступны для исследователей в этой области, и код станет частью бесплатной общедоступной платформы, которую можно будет использовать и для изучения других судов.Таким образом, точность и скорость являются важными факторами для пользователей на разных машинах.

1 Ответ

0 голосов
/ 07 февраля 2019

Для тех, кто сталкивается с этим вопросом и сталкивается с аналогичной сложной проблемой сопоставления строк в пандах, это решение, которое я нашел, является самым быстрым.

Оно не полностью векторизовано, как я хотел, но яиспользуется df.apply с этим методом в классе:

def judge_matcher(self, row, in_col, out_col, year_col, court_num_col, 
                  size_col = None):
    final_list = []
    raw_list = row[in_col]
    cleaned_list = [x for x in raw_list if x]
    cleaned_list = [x.strip() for x in cleaned_list]
    for name in cleaned_list:
        name1 = self.convert_judge_name(row[year_col],
                                        row[court_num_col], name, 1)
        name2 = self.convert_judge_name(row[year_col],
                                        row[court_num_col], name, 2)
        if name1 in self.names_dict_list[0]:
            final_list.append(self.names_dict_list[0].get(name1))
        elif name1 in self.names_dict_list[1]:
            final_list.append(self.names_dict_list[1].get(name1))
        elif name2 in self.names_dict_list[2]:
            final_list.append(self.names_dict_list[2].get(name2))
        elif name2 in self.names_dict_list[3]:
            final_list.append(self.names_dict_list[3].get(name2))
        elif name in self.names_dict_list[4]:
            final_list.append(self.names_dict_list[4].get(name)) 
    final_list = list(unique_everseen(final_list))
    final_list.sort()
    row[out_col] = final_list
    if size_col and final_list:
        row[size_col] = len(final_list)
    return row 

@staticmethod
def convert_judge_name(year, court, name, dict_type):
    if dict_type == 1:
        return str(int(court) * 10000 + int(year)) + name
    elif dict_type == 2:
        return str(int(year)) + name
    else:
        return name

По сути, он объединяет три столбца вместе и выполняет поиск в хешированном словаре (вместо регулярных выражений) с объединенными строками.Умножение используется для эффективной конкатенации двух чисел, которые должны располагаться рядом как строки.В словарях были одинаково подготовленные ключи (а значения - нужные строки).Используя списки и затем дедуплицируя, мне не пришлось удалять соответствующие строки.Я не определял время этой конкретной функции, но весь модуль занял чуть более 10 часов, чтобы обработать ~ 1 миллион строк.При повторном запуске я постараюсь запомнить время этой прикладной функции и опубликовать результаты здесь.Метод уродлив, но довольно эффективен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...