#python #performance #pandas
#python #Производительность #pandas
Вопрос:
Я работаю с относительно большим DataFrame
(~ 4 млн строк x 11 столбцов, числовое ctypes
значение).
Мне нужны манипуляции на groupby
основе выполнения, особенно transform
s и aggregate
s. Примерно я работаю с O(1M)
группами.
На моей машине (i7 2600k, 8 ГБ оперативной памяти, Fedora20x64) я заметил, что практически невозможно выполнить какие-либо groupby
манипуляции, кроме «встроенных».
Например.
df.groupby('key').Acol.mean()
занимает доли секунды, в то время как
df.groupby('key').Acol.aggregate(pd.Series.mean)
может занять минуты, а потребление памяти резко возрастает.
Любая другая манипуляция, указанная через a lambda
, даже если она написана в терминах pd.Series
векторизации, занимает гораздо больше времени, чем я могу позволить себе ждать, или достигает сумасшедших уровней замены.
В: есть ли у вас какие-либо предложения приблизиться к производительности «встроенных» методов?
Могу ли я как-то написать свои методы по мере написания встроенных функций? Может ли cython помочь?
Ситуация каким-то образом ухудшается при переходе от aggregate
к transform
.
Таким образом, я не смог найти «встроенные» функции (я что-то пропустил ??)
Что я делаю для решения проблемы:
Я разделяю df
их на несколько частей (безопасным для данных способом), сохраняю их в hdf5
файлах, загружаю их с 4 клиентов, параллельно обрабатываю данные, повторно сохраняю 'hdf5'
и, наконец, объединяю результат в финале df
. Это решение либо работает, либо генерирует экстремальную замену от клиентов.
Ответ №1:
Это использует постоянную память и равно O (количество групп).
Встроенная функция значительно ускоряет работу по двум причинам.
- Они не создают хороший объект для вычисления встроенного, поскольку индекс не нужен
- Им не нужно переходить из пространства cython в пространство python (и обратно).
Таким образом, при нетривиальных вычислениях важно, чтобы вы использовали встроенные функции. Использование (apply / aggregate) подходит для оценки обобщенной функции, но pandas не может делать слишком много предположений о том, что происходит в пользовательской функции, и они оцениваются в пространстве python.
In [28]: df = DataFrame(np.random.randn(4000000,11))
In [29]: df.groupby(df.index//4).ngroups
Out[29]: 1000000
In [30]: %timeit df.groupby(df.index//4).mean()
1 loops, best of 3: 412 ms per loop
In [31]: %timeit -n 1 df.groupby(df.index//4).apply(lambda x: x.mean())
1 loops, best of 3: 1min 22s per loop
Передача .aggregates(pd.Series.mean)
происходит де-факто .apply(lambda x: x.mean())
и влечет за собой тот же штраф за выполнение.
Вы всегда должны использовать векторизованные встроенные модули, когда это возможно, особенно с большим количеством групп.
Вот пример чего-то, что не встроено, но может быть легко достигнуто:
Я хочу вычислить max-min, поэтому наивно, что вы должны сделать:
df.groupby(...).apply(lambda x: x.max()-x.min())
Гораздо быстрее сделать:
g = df.groupby(...)
g.max()-g.min()
Комментарии:
1. спасибо за ваш ответ. Не поймите меня неправильно, но в вашем последнем примере вы могли бы легко преобразовать свое выражение в векторизованную форму, созданную с помощью встроенных функций. Моя точка зрения заключалась в следующем: могу ли я использовать ту же стратегию реализации встроенных функций и создать мою? Рассмотрим, например:
df.groupby(['Day_id','Frame']).apply(lambda x : x[x.Avg_U > 0].Speed.mean())
илиdf.groupby(['Day_id','Frame']).transform(lambda x :len(x))
я еще не смог найти более разумную векторизованную форму.2. сначала фильтруйте
df[df.Avg_U>0].groupby(...).Speed.mean()
; для 2-го сделайте это: github.com/pydata/pandas/issues/64963. Звучит круто, спасибо, в обоих случаях мне нужны несколько уроков по перестановке индексов… Не могли бы вы, пожалуйста, дать мне представление о том, как »
zip
» получить результаты первой и второй операции? т. Е. Как я могу получить размер каждой группы и среднее значение? (возможно, в индексах могут быть неоднородности, так как в первом случае у меня может быть меньше групп, чем во втором)…4. на самом деле вы можете использовать
df.groupby(...).tranform('count')
для получения скорости cythonized5. это то, что
transform
делает