商城首页欢迎来到中国正版软件门户

您的位置:首页 >如何基于另一数据框时间戳中心对 Pandas 数据框进行时间窗口滚动均值计算

如何基于另一数据框时间戳中心对 Pandas 数据框进行时间窗口滚动均值计算

  发布于2026-04-21 阅读(0)

扫一扫,手机访问

如何基于另一数据框时间戳中心对 Pandas 数据框进行时间窗口滚动均值计算

本文介绍如何将高频数据框(如每15秒采样)中以低频参考时间点(如每5分钟)为中心、按真实时间窗口(如±2.5分钟)计算滚动均值,并精准对齐到参考时间点。核心使用 rolling(..., on='time', center=True) 与 merge_asof 协同实现。

在时序数据分析中,我们常常会遇到一个经典难题:如何将高频采集的传感器数据(比如每15秒记录一次的车速),精准地聚合到另一组低频发生的事件时间点上(比如每5分钟的系统快照时间)?

这里的关键在于,我们需要的不是简单粗暴地按固定时间分桶。真正的需求是:以低频数据框df2中的每一个时间戳为“圆心”,在高频数据框df1中,划出一个前后对称的时间窗口(例如前后各2.5分钟,总宽度5分钟),然后计算这个窗口内所有样本的均值。这种“中心对齐的时间滑动窗口”操作,用常规的resample()groupby()很难直接实现,必须借助Pandas中时间感知的滚动操作与近似连接功能协同完成。

✅ 正确实现步骤

下面这套方法,可以说是处理这类问题的标准答案,兼顾了逻辑的严谨性和执行的效率。

  1. 打好基础:统一时间格式
    第一步永远是确保时间列的数据类型正确。将两个数据框的时间列都转换为Pandas可识别的datetime类型,这是后续所有时间操作的前提。

    df1['time'] = pd.to_datetime(df1['time'])
    df2['time'] = pd.to_datetime(df2['time'])
  2. 核心计算:在df1上生成中心滚动均值
    这是实现“中心对齐”窗口的关键一步。使用rolling(window="300s", on="time", center=True)方法:window="300s"定义了5分钟的总窗口宽度,而center=True这个参数至关重要,它确保了窗口是以当前行的时间戳为几何中心向两侧展开(即±150秒)。计算出的均值会自然地与原始时间戳对齐。

    df1_rolling = df1.rolling(window="300s", on="time", center=True).mean()
    df1 = df1.join(df1_rolling.add_suffix("_a vg"))  # 合并均值列,避免重复列名
  3. 精准匹配:用merge_asof进行时间近似连接
    现在,我们需要把df2中的每个参考时间点,与df1中计算好滚动均值的对应行匹配起来。这里就要请出merge_asof了。它的默认行为是向后查找,但为了获得真正意义上的“最近邻”匹配,有几个参数必须设置到位:首先确保两个表都按时间排序,然后设置direction='nearest'来寻找最近的时间点,再通过tolerance参数(例如15秒)控制匹配的最大允许误差。

    df1_sorted = df1.sort_values('time')
    df2_sorted = df2.sort_values('time')
    result = pd.merge_asof(
        df1_sorted,
        df2_sorted.rename(columns={'speed': 'speed_df2'}),
        on='time',
        tolerance=pd.Timedelta('15s'),
        allow_exact_matches=True,
        direction='nearest'  # 关键:改为 'nearest' 才能找最近时间点(而非仅向前/向后)
    )
  4. 清理结果:过滤未匹配成功的行(可选)
    匹配完成后,可能会存在一些未能成功找到对应时间窗口的行,其来自df2的列值为NaN。根据分析需求,可以选择将这些行过滤掉,保证结果集的整洁。

    result = result.dropna(subset=['speed_df2'])

⚠️ 几个必须留意的细节:

  • direction='nearest'参数是灵魂:这个参数在Pandas较新版本(≥1.1.0)中引入,是实现“寻找最近时间戳”功能的关键。如果使用旧版本,就需要手动实现复杂逻辑,非常不推荐。
  • 边界处的NaN是正常的:使用center=True进行滚动计算时,在数据序列的开头和结尾,由于无法构成完整的对称窗口,结果会是NaN。这是预期行为。如果一定要填充,可以用前向/后向填充,但需警惕这会引入数据偏差。
  • 时间类型是硬性要求:时间列必须是datetime64[ns]类型,否则rolling(on=...)会报错。用pd.to_datetime()进行转换是稳妥的做法。
  • 注意数据覆盖范围:如果df1的时间范围不足以覆盖df2中某个时间点所需的±2.5分钟窗口,那么该点的滚动均值就会是NaN,但merge_asof仍可能根据directiontolerance的设置匹配到最近的行。

✅ 完整可运行示例(精简版)

理论说再多,不如一段可运行的代码来得实在。下面是一个精简但完整的示例,涵盖了从数据构建到最终输出的全过程:

import pandas as pd
import numpy as np

# 示例数据构建(略去冗长原始数据,此处用前几行示意)
df1 = pd.DataFrame({
    'time': pd.date_range('2022-10-04 00:00:24', periods=80, freq='15S'),
    'speed': np.random.uniform(3.0, 4.6, 80)
})
df2 = pd.DataFrame({
    'time': pd.to_datetime(['2022-10-04 00:03:28', '2022-10-04 00:08:34',
                            '2022-10-04 00:13:25', '2022-10-04 00:18:27']),
    'speed': [4.646, 5.328, 5.895, 5.665]
})

# 步骤1:计算 df1 的中心滚动均值(5分钟窗)
df1['speed_a vg'] = df1.rolling(window='300s', on='time', center=True)['speed'].mean()

# 步骤2:近似时间对齐(关键:direction='nearest')
merged = pd.merge_asof(
    df1.sort_values('time'),
    df2.sort_values('time').rename(columns={'speed': 'speed_df2'}),
    on='time',
    direction='nearest',
    tolerance=pd.Timedelta('15s')
)

# 步骤3:保留匹配成功的行
final = merged.dropna(subset=['speed_df2']).reset_index(drop=True)
print(final[['time', 'speed', 'speed_a vg', 'speed_df2']])

这套方案经过大量实践检验,在保证计算精度的同时,也拥有出色的性能,即使面对百万级别的时间序列对齐任务也能从容应对,可以说是工业级时序特征工程中的一项标准实践了。

本文转载于:https://www.php.cn/faq/2345382.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注