如何在熊猫中按对象分组应用滚动功能

Posted

技术标签:

【中文标题】如何在熊猫中按对象分组应用滚动功能【英文标题】:How to apply rolling functions in a group by object in pandas 【发布时间】:2015-04-22 22:20:14 【问题描述】:

我很难解决 dataframe 或 groupby 中的回顾或翻转问题。

以下是我拥有的数据框的一个简单示例:

              fruit    amount    
   20140101   apple     3
   20140102   apple     5
   20140102   orange    10
   20140104   banana    2
   20140104   apple     10
   20140104   orange    4
   20140105   orange    6
   20140105   grape     1
   …
   20141231   apple     3
   20141231   grape     2

我需要每天计算前3天每种水果的“数量”平均值,并创建以下数据框:

              fruit     average_in_last 3 days
   20140104   apple      4
   20140104   orange     10
   ...

例如20140104,前3天分别是20140101、20140102和20140103(注意数据框中的日期不连续,20140103不存在),平均苹果数量为(3+5)/2 = 4,橙色为 10/1=10,其余为 0。

示例数据框非常简单,但实际数据框要复杂得多且更大。希望有人能对此有所了解,提前谢谢!

【问题讨论】:

这是两个问题:分别查看水果和对不规则数据进行滚动平均。第一个由 `groupby' 函数处理,如 ***.com/questions/22615288/…,第二个在 pandas 中还不能自动工作:请参阅 ***.com/questions/15771472/… 中的一些方法。 对于示例来说,苹果应该是 (3+5+0)/3 = 2.67。这样做会让事情变得更容易。 不规则采样是我遇到的一个问题,我确实需要通过 (3+5)/2=4 来计算。 【参考方案1】:

假设我们一开始有一个这样的数据框,

>>> df
             fruit  amount
2017-06-01   apple       1
2017-06-03   apple      16
2017-06-04   apple      12
2017-06-05   apple       8
2017-06-06   apple      14
2017-06-08   apple       1
2017-06-09   apple       4
2017-06-02  orange      13
2017-06-03  orange       9
2017-06-04  orange       9
2017-06-05  orange       2
2017-06-06  orange      11
2017-06-07  orange       6
2017-06-08  orange       3
2017-06-09  orange       3
2017-06-10  orange      13
2017-06-02   grape      14
2017-06-03   grape      16
2017-06-07   grape       4
2017-06-09   grape      15
2017-06-10   grape       5

>>> dates = [i.date() for i in pd.date_range('2017-06-01', '2017-06-10')]

>>> temp = (df.groupby('fruit')['amount']
    .apply(lambda x: x.reindex(dates)  # fill in the missing dates for each group)
                      .fillna(0)   # fill each missing group with 0
                      .rolling(3)
                      .sum()) # do a rolling sum
    .reset_index()
    .rename(columns='amount': 'sum_of_3_days', 
                     'level_1': 'date'))  # rename date index to date col


>>> temp.head()
   fruit        date  amount
0  apple  2017-06-01     NaN
1  apple  2017-06-02     NaN
2  apple  2017-06-03    17.0
3  apple  2017-06-04    28.0
4  apple  2017-06-05    36.0

# converts the date index into date column 
>>> df = df.reset_index().rename(columns='index': 'date')  
>>> df.merge(temp, on=['fruit', 'date'])
>>> df
          date   fruit  amount  sum_of_3_days
0   2017-06-01   apple       1                NaN
1   2017-06-03   apple      16               17.0
2   2017-06-04   apple      12               28.0
3   2017-06-05   apple       8               36.0
4   2017-06-06   apple      14               34.0
5   2017-06-08   apple       1               15.0
6   2017-06-09   apple       4                5.0
7   2017-06-02  orange      13                NaN
8   2017-06-03  orange       9               22.0
9   2017-06-04  orange       9               31.0
10  2017-06-05  orange       2               20.0
11  2017-06-06  orange      11               22.0
12  2017-06-07  orange       6               19.0
13  2017-06-08  orange       3               20.0
14  2017-06-09  orange       3               12.0
15  2017-06-10  orange      13               19.0
16  2017-06-02   grape      14                NaN
17  2017-06-03   grape      16               30.0
18  2017-06-07   grape       4                4.0
19  2017-06-09   grape      15               19.0
20  2017-06-10   grape       5               20.0

【讨论】:

不错的答案,+1。我认为如果你能使它更简洁,你更有可能获得赞成票。如果不出意外,只需使示例数据更紧凑。【参考方案2】:

我也想用 groupby 滚动,这就是我登陆这个页面的原因,但我相信我有一个比以前的建议更好的解决方法。

您可以执行以下操作:

pivoted_df = pd.pivot_table(df, index='date', columns='fruits', values='amount')
average_fruits = pivoted_df.rolling(window=3).mean().stack().reset_index()

.stack() 不是必需的,但会将您的数据透视表转换回常规 df

【讨论】:

【参考方案3】:

你可以这样做:

>>> df
>>>
           fruit  amount
20140101   apple       3
20140102   apple       5
20140102  orange      10
20140104  banana       2
20140104   apple      10
20140104  orange       4
20140105  orange       6
20140105   grape       1

>>> g= df.set_index('fruit', append=True).groupby(level=1)
>>> res = g['amount'].apply(pd.rolling_mean, 3, 1).reset_index('fruit')
>>> res

           fruit          0
20140101   apple   3.000000
20140102   apple   4.000000
20140102  orange  10.000000
20140104  banana   2.000000
20140104   apple   6.000000
20140104  orange   7.000000
20140105  orange   6.666667
20140105   grape   1.000000

更新

好吧,正如@cphlewis 在 cmets 中提到的,我的代码不会给出你想要的结果。我检查了不同的方法,到目前为止我发现的方法是这样的(但不确定性能):

>>> df.index = [pd.to_datetime(str(x), format='%Y%m%d') for x in df.index]
>>> df.reset_index(inplace=True)
>>> def avg_3_days(x):
        return df[(df['index'] >= x['index'] - pd.DateOffset(3)) & (df['index'] < x['index']) & (df['fruit'] == x['fruit'])].amount.mean()

>>> df['res'] = df.apply(avg_3_days, axis=1)
>>> df

       index   fruit  amount  res
0 2014-01-01   apple       3  NaN
1 2014-01-02   apple       5    3
2 2014-01-02  orange      10  NaN
3 2014-01-04  banana       2  NaN
4 2014-01-04   apple      10    4
5 2014-01-04  orange       4   10
6 2014-01-05  orange       6    7
7 2014-01-05   grape       1  NaN

【讨论】:

20140104 apple 得到的结果与指定的问题不同。因为不规则抽样,我想。 (虽然我同意上面保罗的观点,结果应该是 2.66 才能保持一致。) 啊我明白了,谢谢你的注意,我会检查我能做什么 您好 Roman,感谢您的帮助。我尝试了类似的东西,在大数据帧上速度不快,并且缺少一些项目,例如,在2014-01-05,没有苹果,但应该从2014-01开始有平均数量的苹果-02 至 2014 年 1 月 4 日。

以上是关于如何在熊猫中按对象分组应用滚动功能的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Pandas 数据框中按列值分组

在熊猫中按周分组

在熊猫中按多个条件分组[重复]

在熊猫数据框中按日期和计数值分组

在熊猫数据框中按行应用时如何保留数据类型?

当日期不唯一时,在熊猫中按日期分组后计数观察值