使用pandas在滚动窗口中重新采样

11 投票
3 回答
11829 浏览
提问于 2025-04-18 14:10

假设我有一些每天的数据(不是规律间隔的),我想计算每个月过去5个月的移动标准差(或者其他任意的非线性函数)。比如说,对于2012年5月,我会计算从2012年1月到2012年5月这5个月的数据的标准差。对于2012年6月,计算的时间段从2012年2月开始,依此类推。最终的结果是一个包含每个月值的时间序列。

不能使用滚动窗口,因为这首先是按天计算的,其次我需要指定值的数量(滚动窗口并不是按时间段聚合的,有些帖子讨论了这个问题,但对我来说并不相关,因为滚动计算仍然是针对每一天的)。

不能进行重采样,因为那样的话样本会是每5个月一次,比如我只会得到2012年5月、2012年10月、2013年3月的数据……最后,由于这个函数是非线性的,我不能先进行按月采样再在其上应用5期的滚动窗口。

所以我需要一种将重采样功能应用于按时间间隔定义的滚动窗口(而不是按值的数量)的方式。

我该如何在pandas中实现这个呢?一种方法可能是将几个(在这个例子中是5个)重采样的(5个月)时间序列结合起来,每个序列有一个月的偏移,然后将这些序列对齐成一个……但我不知道该如何实现这个。

3 个回答

1

这是一个尝试,虽然不是特别干净,但可能有效。

这里有一些示例数据:

df = pd.DataFrame(data={'a': 1.}, 
                  index=pd.date_range(start='2001-1-1', periods=1000))

首先,定义一个函数,用来减少日期的月份,n表示减少的月份数。这个函数需要进一步整理,但对于n小于等于12的情况是可以工作的。

from datetime import datetime    
def decrease_month(date, n):
    assert(n <= 12)

    new_month = date.month - n
    year_offset = 0
    if new_month <= 0:
        year_offset = -1
        new_month = 12 + new_month

    return datetime(date.year + year_offset, new_month, 1)

接着,为每个日期添加5个新列,代表它会跨越的5个滚动周期。

for n in range(rolling_period):
    df['m_' + str(n)] = df.index.map(lambda x: decrease_month(x, n))

然后,使用melt函数把数据从宽格式转换为长格式,这样每个滚动周期就会有一个条目。

df_m = pd.melt(df, id_vars='a')

你应该能够根据新创建的列进行分组,这样每个日期就能代表正确的5个月滚动周期。

In [222]: df_m.groupby('value').sum()
Out[222]: 
              a
value          
2000-09-01   31
2000-10-01   59
2000-11-01   90
2000-12-01  120
2001-01-01  151
2001-02-01  150
2001-03-01  153
2001-04-01  153
2001-05-01  153
2001-06-01  153
2001-07-01  153
...
2

我用下面的代码解决了一个类似的问题:

interval = 5
frames = []
for base in range(interval):
  frame = data.resample(f"{interval}min", base=base).last()
  frames.append(frame)

pd.concat(frames, axis=0).sort_index()

在这里,我创建了5个数据框,它们在相同的时间间隔内重新采样,但有不同的偏移量(也就是基准参数)。然后我只需要把它们合并在一起并排序。这样做通常比滚动加重新采样要高效得多(唯一的额外开销就是排序)。

5

我遇到过类似的问题,涉及到一个时间差序列,我想计算移动平均值,然后重新采样。这里有一个例子,我有100秒的数据。我对每10秒的数据计算一个滚动平均值,然后每5秒重新采样一次,从每个重新采样的区间中取第一个数据。最终的结果应该是在5秒的间隔中显示之前10秒的平均值。如果你想的话,也可以用月份格式来做类似的事情,而不是用秒:

df = pd.DataFrame(range(0,100), index=pd.TimedeltaIndex(range(0,100),'s'))
df.rolling('10s').mean().resample('5s').first()

结果:

             0
00:00:00   0.0
00:00:05   2.5
00:00:10   5.5
00:00:15  10.5
00:00:20  15.5
00:00:25  20.5
00:00:30  25.5
00:00:35  30.5
00:00:40  35.5
00:00:45  40.5
00:00:50  45.5
00:00:55  50.5
00:01:00  55.5
00:01:05  60.5
00:01:10  65.5
00:01:15  70.5
00:01:20  75.5
00:01:25  80.5
00:01:30  85.5
00:01:35  90.5

撰写回答