Найти тему
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 минуты