Pandas: достижение скорости «встроенных» методов (например, mean, std) для групповых манипуляций

#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/6496

3. Звучит круто, спасибо, в обоих случаях мне нужны несколько уроков по перестановке индексов… Не могли бы вы, пожалуйста, дать мне представление о том, как » zip » получить результаты первой и второй операции? т. Е. Как я могу получить размер каждой группы и среднее значение? (возможно, в индексах могут быть неоднородности, так как в первом случае у меня может быть меньше групп, чем во втором)…

4. на самом деле вы можете использовать df.groupby(...).tranform('count') для получения скорости cythonized

5. это то, что transform делает