Функция векторизации хэширования в панд

#python #pandas #vectorization

Вопрос:

У меня есть следующий набор данных (с разными значениями, просто умноженными на одни и те же строки). Мне нужно объединить столбцы и хэшировать их, в частности, с помощью библиотеки hashlib и предоставленного алгоритма.

Проблема в том, что это занимает слишком много времени, и почему-то у меня такое чувство, что я мог бы векторизовать функцию, но я не эксперт.

Функция довольно проста, и я чувствую, что ее можно векторизовать, но ее трудно реализовать.

Я работаю с миллионами строк, и это занимает несколько часов, даже если хэшировать значения 4 столбцов.

 import pandas as pd
import hashlib

data = pd.DataFrame({'first_identifier':['ALP1x','RDX2b']* 100000,'second_identifier':['RED413','BLU031']* 100000})

def _mutate_hash(row):
    return hashlib.md5(row.sum().lower().encode()).hexdigest()

%timeit data['row_hash']=data.apply(_mutate_hash,axis=1)

 

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

1. Не полный ответ, просто делюсь: моим первым инстинктом было бы использовать map() , но swifter или numba может быть быстрее. Вот 12 способов сделать это: towardsdatascience.com/. … И удобный график сравнения ! здесь

2. @sh37211 Спасибо, что поделились, но я не могу использовать внешние фреймворки

3. Хорошо, тогда map это было бы хорошим началом. Но я даже не могу запустить ваш фрагмент кода, чтобы увидеть, насколько он медленный: data = pd.DataFrame строка дает ValueError: arrays must all be same length результат .

4. @sh37211 Мне очень жаль, я забыл добавить «* 100000» во вторую строку при создании фрейма данных, сейчас это должно быть workign, и спасибо за ответы до сих пор

Ответ №1:

Использование понимания списка значительно ускорит вашу работу.

Сначала ваш оригинал:

 import pandas as pd
import hashlib

n = 100000
data = pd.DataFrame({'first_identifier':['ALP1x','RDX2b']* n,'second_identifier':['RED413','BLU031']* n})

def _mutate_hash(row):
    return hashlib.md5(row.sum().lower().encode()).hexdigest()

%timeit data['row_hash']=data.apply(_mutate_hash,axis=1)
 

1 loop, best of 5: 26.1 s per loop

Затем в качестве понимания списка:

 data = pd.DataFrame({'first_identifier':['ALP1x','RDX2b']* n,'second_identifier':['RED413','BLU031']* n})

def list_comp(df):
    return pd.Series([ _mutate_hash(row) for row in df.to_numpy() ])

%timeit data['row_hash']=list_comp(data)
 

1 loop, best of 5: 872 ms per loop

…то есть, ускорение ~30 раз.

В качестве проверки: вы можете проверить, что эти два метода дают эквивалентные результаты, поместив первый в «data2», а второй в «data3», а затем проверьте, что они равны:

 data2, data3 = pd.DataFrame([]), pd.DataFrame([])
%timeit data2['row_hash']=data.apply(_mutate_hash,axis=1)
...
%timeit data3['row_hash']=list_comp(data)
...
data2.equals(data3)
True
 

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

1. @Marat Для получения дополнительных доказательств превосходства понимания списка .apply() см. Ссылку , которую я предоставил в комментарии выше «12 способов сделать это», и цифру масштабирования в конце: towardsdatascience.com/… Он показывает .apply() , что примерно в 25 раз медленнее, чем понимание списка для 10 000-1 000 000 строк.

2. Я получаю следующую ошибку: «Ошибка значения: ndarray не является непрерывным» после копирования вашего кода и запуска list_comp

3. Извините, я удалил этот комментарий, заметив, что он фактически применяется к 10 тысячам строк. (для тех, кто пропустил это, TLDR: ответ вводит в заблуждение, потому apply что имеет накладные расходы из-за оптимизации).

4. Однако ускорение здесь происходит не из — за понимания списка-это из-за .to_numpy() . Для справедливого сравнения попробуйте выбрать время: pd.Series(data.sum(axis=1)).str.lower().str.encode('utf8').apply(lambda v: hashlib.md5(v).hexdigest()) — это значительно быстрее, чем понимание списка

5. @AlejandroA … это сообщение об ошибке создано Cython. Не уверен, почему вы видите сообщение об ошибке Cython. Вот колаборатория, где я написал и запустил приведенный выше код: colab.research.google.com/drive/… Просто повторно запустил его после сброса настроек. Все еще работает.

Ответ №2:

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

 data = pd.DataFrame(
    {
        "first_identifier": ["ALP1x", "RDX2b"] * 1000000,
        "second_identifier": ["RED413", "BLU031"] * 1000000,
    }
)



def _mutate_hash(row):
    return hashlib.md5(row).hexdigest()


prepped_data = data.apply(lambda col: col.str.lower().str.encode("utf8")).sum(axis=1)

data["row_hash"] = prepped_data.map(_mutate_hash)
 

Я вижу ~25-кратное ускорение с этим изменением.