Pandas按组进行时间累积和

5 投票
2 回答
6766 浏览
提问于 2025-04-18 07:26

我有一个数据框,每个ID记录了一次或多次事件。对于每个事件,记录了ID、一个指标x和一个日期。大概是这样的:

import pandas as pd
import datetime as dt
import numpy as np
x = range(0, 6)
id = ['a', 'a', 'b', 'a', 'b', 'b']
dates = [dt.datetime(2012, 5, 2),dt.datetime(2012, 4, 2),dt.datetime(2012, 6, 2),
         dt.datetime(2012, 7, 30),dt.datetime(2012, 4, 1),dt.datetime(2012, 5, 9)]

df =pd.DataFrame(np.column_stack((id,x,dates)), columns = ['id', 'x', 'dates'])

我想设置一个回顾期(比如70天),然后计算数据集中每一行的x的累计和,这个累计和是针对该ID在回顾期内之前发生的事件(不包括正在计算的这一行的x)。最后的结果应该是这样的:

  id  x                dates    want
0  a  0  2012-05-02 00:00:00    1
1  a  1  2012-04-02 00:00:00    0
2  b  2  2012-06-02 00:00:00    9
3  a  3  2012-07-30 00:00:00    0
4  b  4  2012-04-01 00:00:00    0
5  b  5  2012-05-09 00:00:00    4

2 个回答

5

我需要做一些类似的事情,所以我查了一下,发现了pandas的食谱(我非常推荐给任何想了解这个工具强大功能的人)中的这一页:Pandas: 按时间间隔计算滚动平均。在最新版本的pandas中,你可以给rolling()函数传递一个额外的参数,这个参数会根据一个日期时间类型的列来计算窗口。因此,这个例子变得更加简单明了:

# First, convert the dates to date time to make sure it's compatible
df['dates'] = pd.to_datetime(df['dates'])

# Then, sort the time series so that it is monotonic
df.sort_values(['id', 'dates'], inplace=True)

# '70d' corresponds to the the time window we are considering
# The 'closed' parameter indicates whether to include the interval bounds
# 'yearfirst' indicates to pandas the format of your time series
df['want'] = df.groupby('id').rolling('70d', on='dates', closed='neither'
    )['x'].sum().to_numpy()

df['want'] = np.where(df['want'].isnull(), 0, df['want']).astype(int)
df.sort_index() # to dispay it in the same order as the example provided
  id  x      dates  want
0  a  0 2012-05-02     1
1  a  1 2012-04-02     0
2  b  2 2012-06-02     9
3  a  3 2012-07-30     0
4  b  4 2012-04-01     0
5  b  5 2012-05-09     4
2

好吧,有一种方法是这样的:(1) 首先用 'id' 作为分组变量进行 groupby/apply 操作。(2) 在这个操作中,使用 resample 将每个组的数据转换为每日时间序列。(3) 然后使用 rolling_sum(并且要移动一下,这样就不会把当前行的 'x' 值算进去)来计算你想要的70天回顾期的总和。(4) 最后,把分组的数据减少到只有原始的观察值:

In [12]: df = df.sort(['id','dates'])
In [13]: df
Out[13]: 
  id  x      dates
1  a  1 2012-04-02
0  a  0 2012-05-02
3  a  3 2012-07-30
4  b  4 2012-04-01
5  b  5 2012-05-09
2  b  2 2012-06-02

你需要先把数据按 ['id','dates'] 排序。现在我们可以进行 groupby/apply 操作:

In [15]: def past70(g):
             g = g.set_index('dates').resample('D','last')
             g['want'] = pd.rolling_sum(g['x'],70,0).shift(1)
             return g[g.x.notnull()]            

In [16]: df = df.groupby('id').apply(past70).drop('id',axis=1)
In [17]: df
Out[17]: 
               x  want
id dates              
a  2012-04-02  1   NaN
   2012-05-02  0     1
   2012-07-30  3     0
b  2012-04-01  4   NaN
   2012-05-09  5     4
   2012-06-02  2     9

如果你不想要 NaN 值,那就直接这样做:

In [28]: df.fillna(0)
Out[28]: 
               x  want
id dates              
a  2012-04-02  1     0
   2012-05-02  0     1
   2012-07-30  3     0
b  2012-04-01  4     0
   2012-05-09  5     4
   2012-06-02  2     9

编辑: 如果你想把回顾窗口设置为一个参数,可以这样做:

def past_window(g,win=70):
    g = g.set_index('dates').resample('D','last')
    g['want'] = pd.rolling_sum(g['x'],win,0).shift(1)
    return g[g.x.notnull()]            

df = df.groupby('id').apply(past_window,win=10)
print df.fillna(0)

撰写回答