Я видел много ответов на вопросы о переполнении стека, связанные с использованием метода Pandas apply
. Я также видел, как пользователи комментируют под ними, говоря, что « apply
работает медленно, и этого следует избегать».
Я прочитал много статей на тему производительности, которые объясняют apply
медленно. Я также видел заявление об отказе от ответственности в документации о том, что apply
это просто удобная функция для передачи UDF (сейчас не могу найти этого). Итак, по общему мнению, этого apply
следует избегать по возможности. Однако здесь возникают следующие вопросы:
- Если
apply
так плохо, то почему это в API? - Как и когда мне следует освободить свой код от кода
apply
? - Есть ли когда-нибудь ситуации, когда
apply
это хорошо (лучше других возможных решений)?
python
pandas
performance
apply
cs95
источник
источник
returns.add(1).apply(np.log)
vs.np.log(returns.add(1)
- это случай, когдаapply
обычно будет немного быстрее, что показано в правом нижнем зеленом поле на диаграмме jpp ниже.Ответы:
apply
, функция комфорта, которая вам никогда не нужнаМы начнем с рассмотрения вопросов в OP, один за другим.
DataFrame.apply
иSeries.apply
- вспомогательные функции, определенные для объекта DataFrame и Series соответственно.apply
принимает любую определяемую пользователем функцию, которая применяет преобразование / агрегирование к DataFrame.apply
По сути, это серебряная пуля, которая делает то, что не может сделать любая существующая функция pandas.Некоторые из вещей
apply
могут:axis=1
) или по столбцам (axis=0
) к DataFrameagg
илиtransform
)result_type
Аргумент).... Среди прочего. Дополнительные сведения см. В разделе « Применение функций для строк или столбцов» в документации.
Итак, со всеми этими функциями, почему это
apply
плохо? Это потому, чтоapply
идет медленно . Pandas не делает никаких предположений о природе вашей функции и поэтому итеративно применяет вашу функцию к каждой строке / столбцу по мере необходимости. Кроме того, обработка всех вышеперечисленных ситуацийapply
требует значительных накладных расходов на каждой итерации. Кроме того,apply
потребляет намного больше памяти, что является проблемой для приложений с ограничением памяти.Есть очень мало ситуаций, в которых
apply
можно использовать (подробнее об этом ниже). Если вы не уверены, следует ли вам использоватьapply
, вероятно, не стоит.Обратимся к следующему вопросу.
Перефразируя, вот несколько распространенных ситуаций, в которых вы захотите избавиться от любых вызовов
apply
.Числовые данные
Если вы работаете с числовыми данными, вероятно, уже существует векторизованная функция cython, которая делает именно то, что вы пытаетесь сделать (если нет, задайте вопрос о переполнении стека или откройте запрос функции на GitHub).
Сравните производительность
apply
для простой операции сложения.С точки зрения производительности, сравнения нет, цитонизированный эквивалент намного быстрее. В графике нет необходимости, потому что разница очевидна даже для игрушечных данных.
Даже если вы разрешите передачу необработанных массивов с
raw
аргументом, это все равно вдвое медленнее.Другой пример:
В общем, искать векторизованные альтернативы , если это возможно.
Строка / регулярное выражение
Pandas предоставляет "векторизованные" строковые функции в большинстве ситуаций, но есть редкие случаи, когда эти функции не ... "применяются", так сказать.
Распространенная проблема - проверить, присутствует ли значение в столбце в другом столбце той же строки.
Это должно вернуть вторую и третью строки, поскольку «дональд» и «минни» присутствуют в своих соответствующих столбцах «Заголовок».
Используя apply, это можно сделать с помощью
Однако существует лучшее решение, использующее понимание списков.
Здесь следует отметить, что итеративные процедуры оказываются быстрее, чем
apply
из-за меньших накладных расходов. Если вам нужно обрабатывать NaN и недопустимые типы данных, вы можете использовать это с помощью специальной функции, которую затем можно вызвать с аргументами внутри понимания списка.Для получения дополнительной информации о том, когда составление списков следует считать хорошим вариантом, см. Мою рецензию: Для циклов с пандами - когда мне это нужно? .
Распространенная ошибка: растущие столбцы списков
Люди испытывают искушение использовать
apply(pd.Series)
. Это ужасно с точки зрения производительности.Лучше всего просмотреть столбец и передать его в pd.DataFrame.
Наконец,
Apply - это удобная функция, поэтому бывают ситуации, когда накладные расходы достаточно незначительны, чтобы простить. Это действительно зависит от того, сколько раз вызывается функция.
Функции, которые векторизованы для Series, но не DataFrames
Что делать, если вы хотите применить строковую операцию к нескольким столбцам? Что, если вы хотите преобразовать несколько столбцов в datetime? Эти функции векторизованы только для серии, поэтому они должны применяться к каждому столбцу, который вы хотите преобразовать / обработать.
Это допустимый случай для
apply
:Обратите внимание, что также имеет смысл
stack
или просто использовать явный цикл. Все эти параметры немного быстрее, чем при использованииapply
, но разница достаточно мала, чтобы простить.Вы можете сделать аналогичный случай для других операций, таких как строковые операции или преобразование в категорию.
v / s
И так далее...
Преобразование серии в
str
:astype
противapply
Это похоже на идиосинкразию API. Использование
apply
для преобразования целых чисел в серии в строку сопоставимо (а иногда и быстрее), чем использованиеastype
.График построен с использованием
perfplot
библиотеки.Что касается поплавков, я вижу, что
astype
он всегда так же или немного быстрее, чемapply
. Это связано с тем, что данные в тесте имеют целочисленный тип.GroupBy
операции с цепными преобразованиямиGroupBy.apply
не обсуждался до сих пор, ноGroupBy.apply
это также итеративная удобная функция для обработки всего, что существующиеGroupBy
чего не делают функции.Одним из распространенных требований является выполнение GroupBy, а затем двух простых операций, таких как «запаздывающий cumsum»:
Здесь вам понадобятся два последовательных вызова groupby:
Используя
apply
, вы можете сократить это до одного вызова.Оценить производительность очень сложно, потому что она зависит от данных. Но в целом
apply
это приемлемое решение, если цель состоит в том, чтобы уменьшить количествоgroupby
звонков (потому чтоgroupby
это тоже довольно дорого).Другие предостережения
Помимо упомянутых выше оговорок, также стоит упомянуть, что
apply
работает с первой строкой (или столбцом) дважды. Это делается для того, чтобы определить, есть ли у функции какие-либо побочные эффекты. Если нет,apply
можно использовать быстрый путь для оценки результата, иначе он вернется к медленной реализации.Это поведение также наблюдается в
GroupBy.apply
версиях pandas <0.25 (оно было исправлено для 0.25, см. Здесь для получения дополнительной информации ).источник
%timeit for c in df.columns: df[c] = pd.to_datetime(df[c], errors='coerce')
конечно же, после первой итерации это будет намного быстрее, так как вы конвертируетеdatetime
в ...datetime
?to_datetime
строк такой же быстрый, как и ...datetime
объекты" .. правда? Я включил создание фрейма данных (фиксированная стоимость) в тайминги циклаapply
vs,for
и разница намного меньше.Не все
apply
одинаковыВ приведенной ниже таблице показано, когда следует учитывать
apply
1 . Зеленый означает, возможно, эффективный; красный избегать.Кое-что из этого интуитивно понятно:
pd.Series.apply
это построчный цикл на уровне Python, то же самое, что и построчныйpd.DataFrame.apply
(axis=1
). Их злоупотребления многочисленны и разнообразны. В другом посте они рассматриваются более подробно. Популярные решения - использовать векторизованные методы, списки (предполагает чистые данные) или эффективные инструменты, такие какpd.DataFrame
конструктор (например, чтобы избежатьapply(pd.Series)
).Если вы используете
pd.DataFrame.apply
построчно,raw=True
часто бывает полезно указать (где возможно). На этом этапеnumba
это обычно лучший выбор.GroupBy.apply
: в целом одобреноПовторение
groupby
операций, которых следует избегатьapply
, снизит производительность.GroupBy.apply
здесь обычно хорошо, при условии, что методы, которые вы используете в своей пользовательской функции, сами векторизованы. Иногда нет собственного метода Pandas для групповой агрегации, которую вы хотите применить. В этом случае для небольшого количества группapply
с настраиваемой функцией все же может быть предложена приемлемая производительность.pd.DataFrame.apply
по столбцам: смешанная сумкаpd.DataFrame.apply
column -wise (axis=0
) - интересный случай. Для небольшого количества строк по сравнению с большим количеством столбцов это почти всегда дорого. Для большого количества строк относительно столбцов, что является более распространенным случаем, вы иногда можете увидеть значительное улучшение производительности, используяapply
:1 Есть исключения, но обычно они незначительны или редки. Пара примеров:
df['col'].apply(str)
может немного превзойтиdf['col'].astype(str)
.df.apply(pd.to_datetime)
работа со строками плохо масштабируется со строками по сравнению с обычнымfor
циклом.источник
apply
значительно быстрее , чем мое решение сany
. Есть мысли по этому поводу?any
примерно в 100 раз быстрее, чемapply
. Он провел мои первые тесты с 2000 строк на 1000 столбцов, и здесь онapply
был в два раза быстрее, чемany
Для
axis=1
(то есть строковых функций) вы можете просто использовать следующую функцию вместоapply
. Интересно, почему это не такpandas
? (Не тестировалось с составными индексами, но, похоже, намного быстрее, чемapply
)источник
zip(df, row[1:])
здесь достаточно; действительно, на этом этапе подумайте, являетсяnumba
ли func числовым вычислением. См. Этот ответ для объяснения.numba
, быстрее,faster_df_apply
предназначено для людей, которым просто нужно что-то эквивалентное, но быстрее, чемDataFrame.apply
(что до странности медленнее)..apply
реализовано, но делает одну вещь, которая значительно замедляет его, по сути, делает:row = pd.Series({f:v for f,v in zip(cols, row[1:])})
что добавляет много сопротивления. Я написал ответ, в котором описывалась реализация, хотя я думаю, что она устарела, в последних версиях пытались использовать Cython.apply
, я полагаю (неБывают ли ситуации, когда
apply
хорошо? Иногда да.Задача: расшифровать строки Unicode.
Обновление
Я ни в коем случае не выступал за использование
apply
, просто подумал, что, поскольку онNumPy
не может справиться с вышеуказанной ситуацией, он мог бы быть хорошим кандидатомpandas apply
. Но я забыл о простом понимании списка благодаря напоминанию @jpp.источник
[unidecode.unidecode(x) for x in s]
илиlist(map(unidecode.unidecode, s))
?apply
, просто подумал, что это могло быть хорошо. вариант использования.