4、时间序列基础:时间序列概念、时间戳处理、频率转换、滑动窗口
时间序列,说白了就是带时间戳的数据。你想想看,股票价格、传感器读数、服务器日志……这些东西都有一个共同点:它们按时间顺序排列。我刚开始做数据回放时,总觉得时间序列不就是多了一列时间嘛,有什么难的?后来被坑了几次才明白——时间序列处理,是整个数据回放系统的地基。
核心观点:时间序列分析的本质,是从时间维度上理解数据的变化规律。没有扎实的时间序列基础,数据回放就是空中楼阁。
4.1 时间序列概念
时间序列,就是按时间顺序排列的观测值集合。每个观测值都对应一个时间点。我习惯把时间序列拆成三个要素:
- 时间戳:记录数据产生的时间点
- 观测值:在某个时间点记录的数据
- 频率:数据点之间的时间间隔
举个例子。假设你在监控服务器的CPU使用率,每5秒采集一次。那么你得到的就是一个时间序列:
时间戳 CPU使用率(%)
2024-01-15 10:00:00 45.2
2024-01-15 10:00:05 47.8
2024-01-15 10:00:10 52.1
2024-01-15 10:00:15 49.6
嗯,这里要注意:时间序列不要求数据严格等间隔。但在实际项目中,等间隔的数据处理起来会顺手很多。我曾经处理过一个传感器数据集,采样间隔从1秒到30秒不等,那叫一个头疼。
4.2 时间戳处理
时间戳处理,是时间序列分析的第一步。也是我见过最多人翻车的地方。
常见的坑有哪些?
- 时区问题:服务器用UTC,数据库存本地时间,前端展示又转一次。我曾经因为时区没处理好,导致回放的数据差了8个小时,排查了一整天。
- 格式混乱:有的系统用"2024-01-15 10:00:00",有的用"2024/01/15 10:00:00",还有用时间戳的。统一格式是第一步。
- 精度问题:秒级够用还是需要毫秒级?这取决于你的业务场景。高频交易数据可能需要微秒级,而日级别的业务数据用秒级就够了。
我的建议:在系统内部统一使用UTC时间,只在展示层做时区转换。这样可以避免很多时区相关的bug。
Python里处理时间戳,我常用pandas的to_datetime()函数:
import pandas as pd
# 字符串转时间戳
df['timestamp'] = pd.to_datetime(df['timestamp_str'])
# 设置时间索引
df.set_index('timestamp', inplace=True)
# 处理不同格式
df['timestamp'] = pd.to_datetime(df['timestamp_str'], format='%Y/%m/%d %H:%M:%S')
4.3 频率转换
频率转换,说白了就是把数据从一个时间间隔转换到另一个时间间隔。比如把秒级数据聚合成分钟级数据。
为什么要做频率转换?
- 降低数据量:高频数据存储成本高,回放时也慢
- 匹配业务需求:业务分析可能只需要分钟级或小时级数据
- 对齐不同数据源:两个数据源采样频率不同,需要统一
频率转换有两种方式:
| 转换方式 | 说明 | 适用场景 |
|---|---|---|
| 降采样 | 从高频到低频 | 数据聚合、压缩 |
| 升采样 | 从低频到高频 | 数据插值、填充 |
降采样时,你需要指定聚合函数。比如把秒级数据聚合成分钟级,可以用均值、最大值、最小值等。我习惯用resample()方法:
# 降采样:秒级 -> 分钟级,取均值
df_minute = df.resample('1T').mean()
# 降采样:秒级 -> 小时级,取最大值
df_hour = df.resample('1H').max()
升采样时,你需要处理缺失值。常用的方法有前向填充、后向填充、线性插值等。我曾经在处理传感器数据时,因为升采样用了错误的填充方式,导致回放的数据出现了明显的阶梯状跳变。嗯,后来改用线性插值才解决问题。
# 升采样:分钟级 -> 秒级,前向填充
df_second = df_minute.resample('1S').ffill()
# 线性插值
df_second = df_minute.resample('1S').interpolate()
注意:升采样会引入人为构造的数据点。在数据回放时,如果对数据真实性要求极高,建议谨慎使用升采样。
4.4 滑动窗口
滑动窗口,是时间序列分析里最常用的技术之一。它就像一个固定大小的窗口,在时间轴上滑动,每次计算窗口内的统计量。
滑动窗口能做什么?
- 平滑数据:去除噪声,看到趋势
- 计算移动平均:比如5日移动平均线
- 检测异常:窗口内的数据如果偏离均值太多,可能就是异常
- 特征工程:为机器学习模型生成特征
我画了一张图,帮你理解滑动窗口的原理:
你看,窗口在时间轴上滑动,每次覆盖固定数量的数据点。窗口1覆盖t1到t3,窗口2覆盖t2到t4,窗口3覆盖t3到t5。每个窗口内可以计算均值、标准差、最大值等统计量。
代码实现也很简单:
# 滑动窗口均值,窗口大小为3
df['rolling_mean'] = df['value'].rolling(window=3).mean()
# 滑动窗口标准差
df['rolling_std'] = df['value'].rolling(window=3).std()
# 滑动窗口最大值
df['rolling_max'] = df['value'].rolling(window=3).max()
小技巧:滑动窗口的窗口大小选择很关键。窗口太小,噪声过滤不干净;窗口太大,会丢失细节。我一般会尝试3-5个不同的窗口大小,对比效果后再确定。
滑动窗口还有一个变种——指数加权滑动窗口。它给近期的数据更高的权重,对远期的数据权重逐渐衰减。这在金融数据分析中很常用:
# 指数加权滑动平均
df['ewm_mean'] = df['value'].ewm(span=3).mean()
为什么会用指数加权?因为在实际业务中,近期的数据往往比远期的数据更有参考价值。比如股票分析,今天的价格比一周前的价格更能反映当前趋势。
好了,时间序列基础就讲到这里。记住:时间戳处理要严谨,频率转换要明确业务需求,滑动窗口要选对窗口大小。这些基本功扎实了,后面的数据回放才能玩得转。