Python - функции прокрутки для объекта GroupBy

У меня есть объект временного ряда grouped типа <pandas.core.groupby.SeriesGroupBy object at 0x03F1A9F0>. grouped.sum() дает желаемый результат, но я не могу заставить Rolling_sum работать с объектом groupby. Есть ли способ применить скользящие функции к groupby объектам? Например:

x = range(0, 6)
id = ['a', 'a', 'a', 'b', 'b', 'b']
df = DataFrame(zip(id, x), columns = ['id', 'x'])
df.groupby('id').sum()
id    x
a    3
b   12

Однако хотелось бы иметь что-то вроде:

  id  x
0  a  0
1  a  1
2  a  3
3  b  3
4  b  7
5  b  12

person Community    schedule 21.12.2012    source источник
comment
Как именно вы ожидаете, что функция прокрутки будет работать с сгруппированными объектами (я имею в виду записывать математику, которую вы хотите выполнить, в символах)?   -  person tacaswell    schedule 22.12.2012
comment
Извините, я должен был быть более ясным.   -  person    schedule 22.12.2012
comment
Итак, вы хотите сделать cumsum для каждой из групп, а затем сшить все это обратно в один фрейм данных?   -  person tacaswell    schedule 22.12.2012
comment
Да, в идеале cumsum и любая функция прокатки (mean, sum, std).   -  person    schedule 22.12.2012


Ответы (5)


совокупная сумма

Чтобы напрямую ответить на вопрос, метод cumsum произвел бы желаемую серию:

In [17]: df
Out[17]:
  id  x
0  a  0
1  a  1
2  a  2
3  b  3
4  b  4
5  b  5

In [18]: df.groupby('id').x.cumsum()
Out[18]:
0     0
1     1
2     3
3     3
4     7
5    12
Name: x, dtype: int64

функции прокрутки панд для каждой группы

В более общем смысле, любую функцию прокрутки можно применить к каждой группе следующим образом (с использованием нового метода .rolling, прокомментированного @kekert). Обратите внимание, что тип возвращаемого значения - это многоиндексированная серия, которая отличается от предыдущих (устаревших) методов pd.rolling_ *.

In [10]: df.groupby('id')['x'].rolling(2, min_periods=1).sum()
Out[10]:
id
a   0   0.00
    1   1.00
    2   3.00
b   3   3.00
    4   7.00
    5   9.00
Name: x, dtype: float64

Чтобы применить функцию прокрутки для каждой группы и получить результат в исходном порядке фреймов данных, вместо этого следует использовать преобразование:

In [16]: df.groupby('id')['x'].transform(lambda s: s.rolling(2, min_periods=1).sum())
Out[16]:
0    0
1    1
2    3
3    3
4    7
5    9
Name: x, dtype: int64

устаревший подход

Для справки, вот как вел себя устаревший pandas.rolling_mean:

In [16]: df.groupby('id')['x'].apply(pd.rolling_mean, 2, min_periods=1)
Out[16]: 
0    0.0
1    0.5
2    1.5
3    3.0
4    3.5
5    4.5
person Garrett    schedule 21.12.2012
comment
pd.rolling_mean теперь не рекомендуется для серии и будет удален, вместо этого используйте df.groupby('id')['x'].rolling(2).mean() - person kekert; 12.10.2016

Для гуглеров, которые задаются этим старым вопросом:

Что касается комментария @ kekert к ответу @ Garrett на использование нового

df.groupby('id')['x'].rolling(2).mean()

вместо устаревшего

df.groupby('id')['x'].apply(pd.rolling_mean, 2, min_periods=1)

Любопытно, что новый подход .rolling (). mean () возвращает многоиндексированный ряд, сначала индексированный столбцом group_by, а затем индексом. Принимая во внимание, что старый подход просто возвращал бы серию, проиндексированную по отдельности исходным индексом df, что, возможно, имеет меньше смысла, но делает его очень удобным для добавления этой серии в качестве нового столбца в исходный фрейм данных.

Итак, я думаю, что нашел решение, которое использует новый метод Rolling () и по-прежнему работает так же:

df.groupby('id')['x'].rolling(2).mean().reset_index(0,drop=True)

что должно дать вам серию

0    0.0
1    0.5
2    1.5
3    3.0
4    3.5
5    4.5

который вы можете добавить в виде столбца:

df['x'] = df.groupby('id')['x'].rolling(2).mean().reset_index(0,drop=True)
person Kevin Wang    schedule 16.12.2016
comment
Думаю, можно использовать .transform вместо reset_index? - person TMrtSmith; 17.11.2017
comment
На самом деле это не удается, если вы группируете по нескольким столбцам. Удаление первого аргумента (уровней) решает эту проблему, поскольку по умолчанию удаляет все уровни. Таким образом, строка становится df['x'] = df.groupby('id')['x'].rolling(2).mean().reset_index(drop=True) - person Kartik Sreenivasan; 22.01.2018
comment
В качестве еще одного раздражающего нюанса используйте groupby(..., sort=False), если переменная вашей группы еще не отсортирована. Я получал действительно странные результаты при добавлении этого скользящего среднего в качестве нового столбца, потому что порядок не соответствовал исходному df. - person Hendy; 24.02.2019
comment
Очень полезная информация. а) Они должны добавить это в свою Поваренную книгу pandas б) Можете ли вы поднять какие-то ошибки pandas при изменении функциональности? Им следует лучше обдумать последствия, прежде чем отказываться от них. - person smci; 29.06.2019
comment
Не могли бы вы пояснить, почему мы должны указать здесь .rolling(2), т.е. почему window=2? Это потому, что есть 2 группы «а» и «б»? - person uniquegino; 02.02.2020
comment
@uniquegino я давно не смотрел на это, но я думаю, что 2 - это просто пример. Был задан вопрос о скользящей сумме, и приведенный пример был скользящей суммой 2. Скользящая сумма с окном 2 просто означает, что скользящая сумма является суммой двух значений (текущего значения и предыдущего значения). Таким образом, скользящая сумма для 5-й строки = значение 5-й строки + значение 4-й строки. (если window = 3, то это будет 5-й + 4-й + 3-й). - person Kevin Wang; 04.02.2020
comment
@KevinWang да, мне известно о применении window, но я чувствую, что последняя строка в исходном вопросе представляет собой сумму всех значений (12 = 3 + 4 + 5) в группе b, то есть window=3, что меня больше всего смутило - как .rolling(2).mean() волшебным образом служила потребностям кончины в каждой группе. Я, должно быть, упускаю что-то базовое, но не могу правильно продумать - person uniquegino; 04.02.2020
comment
@uniquegino о, понял. Вы правы, этот исходный пример был cumsum, но также был задан вопрос о функциях прокрутки (в частности, в комментариях: Yes, ideally cumsum and any rolling function(mean, sum, std).), примером которых был Rolling (2). - person Kevin Wang; 04.02.2020

Вот еще один способ, который хорошо обобщается и использует pandas 'расширяющийся метод.

Он очень эффективен и отлично работает для расчет скользящего окна с фиксированными окнами, например для временных рядов.

# Import pandas library
import pandas as pd

# Prepare columns
x = range(0, 6)
id = ['a', 'a', 'a', 'b', 'b', 'b']

# Create dataframe from columns above
df = pd.DataFrame({'id':id, 'x':x})

# Calculate rolling sum with infinite window size (i.e. all rows in group) using "expanding"
df['rolling_sum'] = df.groupby('id')['x'].transform(lambda x: x.expanding().sum())

# Output as desired by original poster
print(df)
  id  x  rolling_sum
0  a  0            0
1  a  1            1
2  a  2            3
3  b  3            3
4  b  4            7
5  b  5           12
person Sean McCarthy    schedule 27.09.2018
comment
У вас есть какие-либо доказательства того, что это очень эффективно? Как правило, с pandas выполнение любой итерации (например, преобразование или применение) является серьезным ударом по производительности по сравнению с выполнением того же самого с векторными операциями (которые все встроенные функции .sum, .rolling и т. Д. Будут ). Я знаю, что Pandas проводит предварительную проверку итерационных циклов, чтобы узнать, может ли он оптимизировать их для вас, но в целом итераций следует избегать, если производительность вызывает беспокойство. - person bwest87; 05.12.2019
comment
Мне очень жаль, что я могу дать вам только один голос, я подумываю о создании новых учетных записей, чтобы повысить ценность этого ответа. Это единственное, что сработало для меня с группировкой по нескольким столбцам, спасибо! - person sousben; 22.03.2020
comment
Прохладный. Это может применяться экспоненциальная скользящая средняя. q['exponential_ave'] = q.groupby('id')['x'].transform(lambda x: x.ewm(com=0.2).mean()) - person Darkhan; 20.04.2020
comment
В чем разница между использованием expanding и rolling? - person liang; 26.06.2021
comment
@liang эта статья Объясняет это лучше, чем я могу. В функциях прокрутки размер окна остается постоянным, тогда как при расширении функций он изменяется. См. Также этот ответ. - person Sean McCarthy; 26.06.2021

Я не уверен в механике, но это работает. Обратите внимание, что возвращаемое значение - это просто ndarray. Я думаю, что вы можете применить любую кумулятивную или «скользящую» функцию таким образом, и она должна дать тот же результат.

Я тестировал его с cumprod, cummax и cummin, и все они вернули ndarray. Я думаю, что pandas достаточно умен, чтобы знать, что эти функции возвращают серию, и поэтому функция применяется как преобразование, а не агрегация.

In [35]: df.groupby('id')['x'].cumsum()
Out[35]:
0     0
1     1
2     3
3     3
4     7
5    12

Изменить: мне показалось любопытным, что этот синтаксис действительно возвращает серию:

In [54]: df.groupby('id')['x'].transform('cumsum')
Out[54]:
0     0
1     1
2     3
3     3
4     7
5    12
Name: x
person Zelazny7    schedule 21.12.2012

Если вам нужно переназначить групповую функцию отката назад исходному фрейму данных, сохраняя порядок и группы, вы можете использовать функцию transform.

df.sort_values(by='date', inplace=True)
grpd = df.groupby('group_key')
#using center=false to assign values on window's last row
df['val_rolling_7_mean'] = grpd['val'].transform(lambda x: x.rolling(7, center=False).mean())
person yoav_aaa    schedule 01.10.2020