pandas.groupby 尊重时间的对象的移动平均线

Moving average on pandas.groupby object that respects time

给定一个 pandas 数据帧,格式如下:

toy = pd.DataFrame({
'id': [1,2,3,
       1,2,3,
       1,2,3],
'date': ['2015-05-13', '2015-05-13', '2015-05-13', 
         '2016-02-12', '2016-02-12', '2016-02-12', 
         '2018-07-23', '2018-07-23', '2018-07-23'],
'my_metric': [395, 634, 165, 
              144, 305, 293, 
              23, 395, 242]
})
# Make sure 'date' has datetime format
toy.date = pd.to_datetime(toy.date)

my_metric 列包含一些(随机)指标,我希望以列 id 为条件计算随时间变化的移动平均值 并在我自己指定的某个指定时间间隔内。我将此时间间隔称为 "lookback time";这可能是 5 分钟 或 2 年。为了确定要包含在回顾计算中的观察结果,我们使用 date 列(如果您愿意,可以是索引)。

令我沮丧的是,我发现使用 pandas 内置函数不容易执行这样的过程,因为我需要有条件地执行计算 在 id 上,同时应仅对回顾时间内的观察结果进行计算(使用 date 列进行检查)。因此,输出数据帧应由每个 id-date 组合的一行组成,my_metric 列现在是回顾时间内包含的所有观察值的平均值(例如 2年,包括今天的日期)。

为清楚起见,在使用 2 年回溯时间时,我提供了一个具有所需输出格式的图表(为过大的图表道歉):

我有一个解决方案,但它没有使用特定的 pandas 内置函数,并且可能不是最优的(列表理解和单个 for 循环的组合)。我正在寻找的解决方案不会使用 for 循环,因此更 scalable/efficient/fast.

谢谢!

计算回顾时间:(Current_year - 2 年)

from dateutil.relativedelta import relativedelta
from dateutil import parser
import datetime

In [1691]: dt = '2018-01-01'

In [1695]: dt = parser.parse(dt)

In [1696]: lookback_time = dt - relativedelta(years=2)

现在,根据回溯时间过滤数据帧并计算滚动平均值

In [1722]: toy['new_metric'] = ((toy.my_metric + toy[toy.date > lookback_time].groupby('id')['my_metric'].shift(1))/2).fillna(toy.my_metric)

In [1674]: toy.sort_values('id')
Out[1674]: 
        date  id  my_metric  new_metric
0 2015-05-13   1        395       395.0
3 2016-02-12   1        144       144.0
6 2018-07-23   1         23        83.5
1 2015-05-13   2        634       634.0
4 2016-02-12   2        305       305.0
7 2018-07-23   2        395       350.0
2 2015-05-13   3        165       165.0
5 2016-02-12   3        293       293.0
8 2018-07-23   3        242       267.5

因此,经过一番修改后,我找到了一个可以充分概括的答案。我使用了一个略有不同的 'toy' 数据框(与我的案例更相关)。为了完整起见,这里是数据:

现在考虑以下代码:

# Define a custom function which groups by time (using the index)
def rolling_average(x, dt):
    xt = x.sort_index().groupby(lambda x: x.time()).rolling(window=dt).mean()
    xt.index = xt.index.droplevel(0)
    return xt

dt='730D' # rolling average window: 730 days = 2 years

# Group by the 'id' column
g = toy.groupby('id')

# Apply the custom function
df = g.apply(rolling_average, dt=dt)

# Massage the data to appropriate format
df.index = df.index.droplevel(0)
df = df.reset_index().drop_duplicates(keep='last', subset=['id', 'date'])

结果符合预期: