#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-кратное ускорение с этим изменением.