9848 подписчиков
➡️ Методы ускорения кода: Векторизация
Это один из методов, который необходимо знать при работе с pandas, а его игнорирование обычно приводит к проваленным собеседованиям и медленному коду.
Задача: необходимо применить некоторую функцию к каждой записи. Очевидный способ, который делают новички — цикл по строкам или конкретному столбцу. Однако это антипатерн в pandas, работающий неприлично медленно на больших датафреймах. Разберем другие способы с примерами.
Например, итерация по строкам с помощью метода .iterrows(). Это самый медленный способ, к тому же не сохраняет типы данных. Другие варианты — использовать .itertuples(), где на каждой итерации строка рассматривается как именованный tupple. Это во много раз быстрее, чем .iterrows(). Еще один аналог — .iteritems().
Любые итерации все равно на порядки медленнее векторизованного подхода, поэтому использовать их стоит только в редких случаях, например когда результат зависит от предыдущих строк.
Другой метод — использование функции .apply(). Она принимает на вход функцию и доп. параметры, и затем применяет ее к каждой строке. Это более предпочтительный способ, работающий в разы быстрее. Также, apply лаконичнее и удобнее, особенно если применять lambda-функции.
Однако, современные процессоры научились оптимизировать подобные задачи с помощью SIMD-инструкций, в которых операции производятся над вектором, а не одним значением (как это происходит когда мы итерируемся по строкам). Чтобы использовать эти инструкции, нужно явно вызвать их в пакете.
Поэтому pandas содержит собственные реализации простых операций (сумма, min/max и тд), выполняющиеся гораздо быстрее итерирования. Такие функции называют векторизированными. Прежде чем использовать apply или iter…, стоит поискать в документации соответствующие векторные функции.
Для строк и дат есть свои методы, например df['col'].str.contains('pat') и df['col'].dt.days.
Ниже сравнение времени работы методов выше для операции добавления столбца-логарифма. Результаты ошеломляющие, векторизация быстрее циклов и iterrows в тысячу раз! Похожее сравнение можно прочитать тут.
import numpy as np
import pandas as pd
import math
df = pd.DataFrame(data={'values':range(1,100_000)})
temp=[]
# -------------------------------------------------
# 1.15 секунды
for idx in range(0, df.shape[0], 1):
temp.append(math.log(df['values'].iloc[idx]))
# 7.18 секунд
for i,row in df.iterrows():
temp.append(math.log(row['values']))
# 156 миллисекунд
for row in df.itertuples():
temp.append(math.log(row.values))
# 84.6 миллисекунды
temp = df['values'].apply(lambda x: math.log(x))
# 3.38 миллисекунды
temp = np.log(df['values'])
# -------------------------------------------------
df['new_values'] = temp
2 минуты
9 июня 2023