Ответ 1
Нет, я не думаю, что ты должен отказываться от панд. Там определенно лучшие способы сделать то, что вы пытаетесь. Хитрость заключается в том, чтобы избегать apply
/transform
в любой форме, насколько это возможно. Избегайте их, как чумы. Они в основном реализованы как для циклов, так что вы также можете напрямую использовать циклы python for
, которые работают на скорости C и обеспечивают более высокую производительность.
Реальное увеличение скорости - это то, где вы избавляетесь от циклов и используете функции панд, которые неявно векторизуют их операции. Например, ваша первая строка кода может быть значительно упрощена, как я скоро вам покажу.
В этом посте я описываю процесс настройки, а затем, для каждой строки в вашем вопросе, предлагаю улучшение, а также сравнение времени и правильности.
Настройка
data = {'pk' : np.random.choice(10, 1000)}
data.update({'Val{}'.format(i) : np.random.randn(1000) for i in range(100)})
df = pd.DataFrame(data)
g = df.groupby('pk')
c = ['Val{}'.format(i) for i in range(100)]
transform
+ sub
+ shift
→ diff
Ваша первая строка кода может быть заменена простым оператором diff
:
v1 = df.groupby('pk')[c].diff().fillna(0)
Проверка работоспособности
v2 = df.groupby('pk')[c].transform(lambda x: x - x.shift(1)).fillna(0)
np.allclose(v1, v2)
True
Performance
Performance%timeit df.groupby('pk')[c].transform(lambda x: x - x.shift(1)).fillna(0)
10 loops, best of 3: 44.3 ms per loop
%timeit df.groupby('pk')[c].diff(-1).fillna(0)
100 loops, best of 3: 9.63 ms per loop
Удаление избыточных операций индексирования
Что касается вашей второй строки кода, я не вижу особых возможностей для улучшения, хотя вы можете избавиться от вызова reset_index()
+ [val_cols]
, если ваш оператор groupby не рассматривает pk
в качестве индекса :
g = df.groupby('pk', as_index=False)
Затем вторая строка кода сокращается до:
v3 = g[c].rolling(4).mean().shift(1)
Проверка работоспособности
g2 = df.groupby('pk')
v4 = g2[c].rolling(4).mean().shift(1).reset_index()[c]
np.allclose(v3.fillna(0), v4.fillna(0))
True
Performance
Performance%timeit df.groupby('pk')[c].rolling(4).mean().shift(1).reset_index()[c]
10 loops, best of 3: 46.5 ms per loop
%timeit df.groupby('pk', as_index=False)[c].rolling(4).mean().shift(1)
10 loops, best of 3: 41.7 ms per loop
Обратите внимание, что время на разных компьютерах различается, поэтому обязательно тщательно протестируйте свой код, чтобы убедиться, что ваши данные действительно улучшаются.
Хотя в этот раз разница не так велика, вы можете оценить тот факт, что есть улучшения, которые вы можете сделать! Это может оказать гораздо большее влияние на большие данные.
Послесловие
В заключение, большинство операций выполняются медленно, поскольку их можно ускорить. Ключ должен избавиться от любого подхода, который не использует векторизацию.
Для этого иногда полезно выйти из пространства панд и ступить в пустышку. Операции над массивами numpy или использование numpy, как правило, выполняются намного быстрее, чем эквиваленты панд (например, np.sum
быстрее, чем pd.DataFrame.sum
, а np.where
быстрее, чем pd.DataFrame.where
и т.д.).
Иногда петель нельзя избежать. В этом случае вы можете создать базовую функцию зацикливания, которую затем можно векторизовать, используя numba или cython. Примеры этого можно найти здесь на Повышении производительности, прямо изо рта лошади.
В других случаях ваши данные слишком велики, чтобы их можно было разумно разместить в массивах. В этом случае пришло время сдаться и переключиться на dask
или spark
, которые предлагают высокопроизводительные распределенные вычислительные среды для работы с большими данными.