如何基于时间执行带滞后的滚动聚合操作(含分组大时间序列数据集场景)
实现带时间滞后的分组滚动聚合(适配非固定间隔时间戳的超大型DataFrame)
嘿,针对你这种带时间窗口滞后的滚动聚合需求——尤其是面对超大型、非固定时间间隔还需要分组的DataFrame,我刚好有个高效的解决方案,完全能解决shift或者merge_as_of搞不定的场景。
先看你的示例数据:
import pandas as pd import numpy as np df = pd.DataFrame( {'B': [0, 1, 2, np.nan, 4]}, index=[pd.Timestamp('20130101 09:00:00'), pd.Timestamp('20130101 09:00:02'), pd.Timestamp('20130101 09:00:03'), pd.Timestamp('20130101 09:00:05'), pd.Timestamp('20130101 09:00:06')] )
你想要的是每个时间点t,计算**过去3秒到过去1秒之间(左开右闭,即(-3s, -1s])**的最大值,这个需求用普通的rolling参数很难直接实现,但我们可以用Pandas的自定义窗口索引器来精确控制窗口范围,效率还很高。
核心方案:自定义窗口索引器
我们可以继承pd.api.indexers.BaseIndexer,自己实现窗口的起始和结束位置计算逻辑,这样不管时间戳间隔有多乱,都能精准定位每个时间点对应的聚合窗口:
class LaggedWindowIndexer(pd.api.indexers.BaseIndexer): def __init__(self, lag_seconds=1, window_seconds=3): self.lag = pd.Timedelta(seconds=lag_seconds) self.window = pd.Timedelta(seconds=window_seconds) def get_window_bounds(self, num_values, min_periods, center, closed): # 获取所有时间戳的数值形式 timestamps = self.index.values # 窗口结束时间:当前时间减去滞后的1秒 end_times = timestamps - self.lag # 窗口开始时间:结束时间再往前推3秒 start_times = end_times - self.window # 用二分查找找到每个窗口的起止索引 # 结束索引:小于等于end_time的最后一个位置 end_indices = np.searchsorted(timestamps, end_times, side='right') - 1 # 起始索引:大于start_time的第一个位置 start_indices = np.searchsorted(timestamps, start_times, side='left') # 返回窗口的起止索引对 return start_indices, end_indices
验证示例需求
用这个索引器来做滚动max聚合,完全匹配你的预期结果:
# 初始化索引器:对应窗口(-3s, -1s] indexer = LaggedWindowIndexer(lag_seconds=1, window_seconds=3) # 执行滚动max,min_periods=1表示至少有一个有效数据才返回结果 result = df.rolling(window=indexer, min_periods=1).max()
运行后得到的结果和你预期的一模一样:
B 2013-01-01 09:00:00 NaN 2013-01-01 09:00:02 0.0 2013-01-01 09:00:03 1.0 2013-01-01 09:00:05 2.0 2013-01-01 09:00:06 NaN
适配分组场景
如果你的DataFrame有id字段需要分组处理,直接在groupby之后用这个索引器就行,Pandas的分组滚动完全支持自定义索引器,而且效率对于大数据非常友好:
假设你的分组数据是这样的:
df_grouped = pd.DataFrame( {'id': ['A', 'A', 'B', 'B', 'A'], 'B': [0, 1, 2, np.nan, 4]}, index=[pd.Timestamp('20130101 09:00:00'), pd.Timestamp('20130101 09:00:02'), pd.Timestamp('20130101 09:00:03'), pd.Timestamp('20130101 09:00:05'), pd.Timestamp('20130101 09:00:06')] )
分组滚动聚合的代码:
grouped_result = df_grouped.groupby('id').rolling(window=indexer, min_periods=1).max()
为什么这个方案适合超大型DataFrame?
- 用
np.searchsorted做二分查找,时间复杂度是O(n log n),比逐行遍历快得多; - 不需要额外的merge或shift操作,避免了不必要的数据复制和内存开销;
- 兼容所有Pandas支持的聚合函数(max、min、mean、sum等);
- 不管时间戳间隔有多不规则,都能精准计算窗口范围,不会出现误差。
如果需要调整窗口的开闭区间,只需要修改np.searchsorted的side参数就行:比如要实现[-3s, -1s)的窗口,就把end_indices的side改成left,start_indices的side改成right。
内容的提问来源于stack exchange,提问作者hg628193hg
相关产品推荐
相关产品推荐

