时间序列的本质:藏在波动里的规律密码
1854年8月,伦敦宽街的空气里飘着泰晤士河腐臭的腥气,没人知道这场突如其来的霍乱已经夺走了700多条人命。当时的医学界普遍相信“瘴气致病论”:腐烂的有机物散发的有毒气体是霍乱的源头,所有人都忙着往街道泼石灰水消毒,街头巷尾弥漫着恐慌与无力感。直到一个叫约翰·斯诺的医生盯上了手里那摞按死亡日期整理的时间序列数据——他把1848年到1854年伦敦霍乱死亡人数按天统计,把死亡时间标在时间轴上,突然发现了一个惊人的规律:所有死亡高峰都出现在宽街公共水泵取水的时段之后,死亡人数在取水高峰后48小时左右达到峰值,正好符合霍乱的潜伏期。
斯诺没有停留在数据表面,他立刻把水泵的位置和死亡地址标在地图上,发现死亡最密集的区域恰恰是距离宽街水泵最近的住户区,而距离水泵更远的啤酒厂工人因为喝的是啤酒而不是井水,几乎没有人感染。他拿着这张叠加了时间序列与空间位置的可视化图找到市政当局,说服他们拆掉了宽街水泵的把手。仅仅10天后,霍乱的死亡人数就从每天平均100多人降到了0。
这个被写入数据科学史册的案例,第一次向世人展示了时间序列的独特价值:它从来不是一堆冰冷的数字,而是记录系统演化过程的最直接载体。霍乱的传播路径、人口的流动规律、水源污染的扩散逻辑,全部藏在死亡人数随时间波动的规律里。如果我们把宽街的居民看作一个系统,霍乱是系统的外部扰动,那么死亡人数的时间序列就是这个系统对扰动的响应记录,所有隐藏的因果链条都 encoded 在波动的时序规律中。
一、时间序列的本质:系统状态的时序快照
要理解时间序列,首先要跳出“按时间排序的数字列表”这种浅层定义。从系统论的角度看,任何一个动态系统的状态都可以用一组变量描述,而时间序列就是这些变量在连续或离散时间点上的采样记录。你每天记录的体重、气象站每小时采集的温度、股票每分钟跳动的价格、工厂每小时上报的产能,本质上都是某个系统在对应时间点的状态快照。
和普通的表格数据最大的区别是,时间序列天然携带顺序性和依赖性:今天的销量不是凭空产生的,它和昨天的销量、上周的销量、去年同期的销量都有关系;今天的温度不会突然从20度跳到30度再跳回15度,它受大气环流、日照时长等连续变化的因素影响,具有平滑性。这种顺序性和依赖性,既是时间序列预测的难点,也是它蕴含规律的核心来源。
作为中级读者,你已经不需要再重复“什么是时间序列”这类入门级问题,我们需要深入理解的是:时间序列的波动从来不是随机的,它的每一次起伏都对应着系统内部演化规律和外部扰动的影响。而我们的核心任务,就是把这些隐藏在波动里的规律拆解出来,转化为可预测的信号。
---
二、时间序列的四大核心特性:波动的底层逻辑
所有的时间序列波动,本质上都是以下四种特性的叠加,你可以把任何一个时序数据都看作这四种成分的混合体:
1. 趋势性:系统的长期演化方向
趋势性是时间序列最直观的特性,它代表系统在长时间维度上的持续上升或下降的演化方向。比如过去10年中国新能源车的渗透率从不到1%涨到超过30%,对应的销量时间序列就有明显的上升趋势;而传统功能机的出货量从2016年开始持续下滑,对应的序列就有明显的下降趋势。
趋势性的本质是系统的累积效应:当系统存在正向的反馈循环时,趋势就会持续上升——比如销量增长带来规模效应,成本下降带来价格优势,进一步刺激销量增长,形成正向循环;反之,当系统存在负向反馈时,趋势就会持续下降。需要注意的是,趋势不一定是线性的,也可能是非线性的,比如指数增长、逻辑斯蒂增长(先快速增长后趋于饱和),甚至可能是分段变化的——比如某产品在上市初期快速增长,达到市场饱和后进入平稳期,趋势就会从上升转为水平。
> 💡 电子版交互提示:你可以打开本章配套的EDA Notebook,加载`retail_sales.csv`零售销量数据集,绘制10年销量的移动平均线(窗口设为365天),你会看到移动平均线清晰地勾勒出了销量的长期上升趋势,而原始序列的短期波动会被平滑掉。
2. 季节性:周期性外部扰动的烙印
季节性是时间序列在固定时间间隔内重复出现的规律性波动,它的核心特征是周期长度固定。最典型的就是气温的年周期:每年夏天温度升高、冬天温度降低,周期固定为12个月;电商销量的周周期:每周六日的销量明显高于工作日,周期固定为7天;零售销量的年周期:每年618、双十一、元旦的销量都会出现峰值,周期固定为12个月。
季节性的本质是系统受到固定周期的外部因素驱动:地球的公转导致日照时长、太阳辐射角度周期性变化,从而形成气温的年季节性;消费者的作息规律形成周度季节性;电商的促销节奏形成年度季节性。需要注意的是,季节性不一定是自然形成的,也可能是人为规则导致的——比如A股的交易日季节性,周末和节假日休市,对应的交易量序列就会有明显的周度季节性。
很多人会把季节性和周期性混淆,两者的核心区别就是周期是否固定:季节性的周期是固定的,比如每年12个月、每周7天;而周期性的周期长度是变化的,比如经济周期可能繁荣3年、衰退2年、复苏1年,下一次繁荣可能持续4年,没有固定的时间间隔,我们会在后续章节详细拆解两者的区别。
3. 周期性:系统内部反馈的循环
周期性是时间序列中围绕长期趋势上下波动的规律,它的核心特征是周期长度不固定,波动的成因是系统内部的反馈机制。最典型的就是宏观经济周期:经济的繁荣阶段,企业扩大生产、居民收入增加、消费上涨,进一步刺激生产,形成正向循环;当产能过剩、通胀高企时,央行加息、企业收缩生产、居民收入下降、消费下跌,进入衰退阶段;衰退到一定程度后,库存出清、政策刺激,经济重新进入复苏阶段,形成完整的周期循环。
和季节性不同,周期性的波动没有固定的时间表,它的长度、振幅都由系统内部的反馈强度决定。另一个典型的例子是库存周期:企业根据销量调整生产,生产调整导致库存变化,库存变化反过来又影响生产决策,形成2-3年的库存周期波动。如果你看到一个时间序列的波动没有固定的间隔,但明显围绕某个中心上下起伏,就可以判断它存在周期性。
4. 随机性:不可预测的微小扰动的叠加
没有任何时间序列是完美的规律体,它总会有一些无法解释的波动,这就是随机性,也常被称为“噪声”。随机性的本质是系统受到大量微小的、不可预测的外部因素的叠加影响:比如零售销量除了趋势、季节、周期外,还会受当天天气、突发新闻、竞争对手的促销活动等无数微小因素的影响,这些因素单独看影响很小,但叠加起来就会形成随机的波动。
根据中心极限定理,当这些微小因素的影响相互独立时,它们的叠加会近似服从正态分布,这也是为什么我们很多时候会把时间序列的残差项假设为正态分布。但需要注意的是,随机性不等于“完全无规律”:如果随机波动是独立的,那么它就是“白噪声”,当前时刻的波动和过去的波动没有任何关系;但如果随机波动存在“波动聚集性”——大涨之后往往跟着大跌,大跌之后往往跟着大涨,那么这种随机性本身就是有规律的,我们会在后续讲解GARCH模型时详细拆解。
我们可以用一个简单的公式把四种特性叠加起来,描述任意一个时间序列的生成逻辑: $$y_t = T_t + S_t + C_t + \varepsilon_t$$ 其中$y_t$是t时刻的观测值,$T_t$是趋势项,$S_t$是季节项,$C_t$是周期项,$\varepsilon_t$是随机噪声项。我们的核心任务,就是把这四个成分从$y_t$中拆解出来,分析每个成分的规律,进而实现预测。
---
三、三个真实场景的时序特性拆解
为了更直观地理解四种特性的叠加逻辑,我们来看三个不同领域的真实时间序列案例,你可以边看边打开配套的EDA Notebook跟随操作。
案例1:某连锁超市2019-2023年日销量数据
我们先加载数据集,绘制原始时序图: ```python import pandas as pd import matplotlib.pyplot as plt
加载数据,日期列转为时间格式
retail = pd.read_csv('retail_sales.csv', parse_dates=['date']) retail = retail.set_index('date')
绘制原始时序图
plt.figure(figsize=(12, 4)) plt.plot(retail['sales'], label='日销量', alpha=0.6) plt.title('某连锁超市2019-2023年日销量时序图') plt.xlabel('日期') plt.ylabel('销量(件)') plt.legend() plt.show() ``` 你会看到销量序列有明显的三个特征: 1. 长期上升趋势:销量从2019年的日均2000件左右,增长到2023年的日均5000件左右,整体保持上升态势,对应我们的趋势性特性; 2. 固定周期的季节性波动:每年12月、1月都会出现明显的销量峰值,对应双十一、元旦、春节的促销效应,周期固定为12个月;每周六日的销量比工作日高20%左右,对应周度季节性; 3. 随机波动:除了趋势和季节性之外,还有一些无法解释的起伏,比如2022年4月因为疫情封控销量突然暴跌,2022年6月因为618大促销量突然暴涨,这些属于随机噪声,部分是可以解释的异常值,部分是真正的随机波动。
如果我们用STL分解把四个成分拆解开,会得到更清晰的结果: ```python from statsmodels.tsa.seasonal import STL
STL分解,周期设为365天(年季节性)
stl = STL(retail['sales'], period=365) res = stl.fit()
绘制分解结果
fig, axes = plt.subplots(4, 1, figsize=(12, 10)) axes[0].plot(retail['sales'], label='原始销量') axes[0].legend() axes[1].plot(res.trend, label='趋势项', color='orange') axes[1].legend() axes[2].plot(res.seasonal, label='季节项', color='green') axes[2].legend() axes[3].plot(res.resid, label='残差项(随机噪声)', color='red') axes[3].legend() plt.tight_layout() plt.show() ``` 你会看到趋势项清晰地勾勒出销量的长期增长曲线,季节项每年重复出现相同的波动模式,残差项则是去掉趋势和季节之后剩下的随机波动。这个案例完美展示了四种特性的叠加逻辑。
案例2:某城市1990-2023年日平均气温数据
加载气象数据集,绘制时序图: ```python
加载气温数据
temp = pd.read_csv('temperature.csv', parse_dates=['date']) temp = temp.set_index('date')
plt.figure(figsize=(12, 4)) plt.plot(temp['temp'], label='日平均气温', alpha=0.6) plt.title('某城市1990-2023年日平均气温时序图') plt.xlabel('日期') plt.ylabel('气温(℃)') plt.legend() plt.show() ``` 这个序列的特性非常典型: 1. 长期上升趋势:过去30年该城市的平均气温每年上升约0.02℃,对应全球变暖的大趋势,属于非常平缓的非线性趋势; 2. 双重季节性:最明显的是年季节性,每年7月气温最高、1月气温最低,周期12个月;如果是小时级的气温数据,还会看到日季节性,每天下午2点气温最高、凌晨4点气温最低,周期24小时; 3. 随机波动:去掉趋势和季节之后,残差项的振幅非常小,说明气温的规律性极强,随机噪声的占比很低,这也是为什么气象预测的准确率可以做到很高。
这个案例告诉我们:不同领域的时间序列,四种特性的占比完全不同。气温序列的规律性极强,趋势和季节性的占比超过90%,随机噪声很少;而零售销量的规律性弱一些,随机噪声占比更高,预测难度也更大。
案例3:某新能源股票2018-2023年日收盘价数据
加载股票数据集,绘制时序图: ```python
加载股票数据
stock = pd.read_csv('stock_price.csv', parse_dates=['date']) stock = stock.set_index('date')
plt.figure(figsize=(12, 4)) plt.plot(stock['close'], label='日收盘价', alpha=0.6) plt.title('某新能源股票2018-2023年日收盘价时序图') plt.xlabel('日期') plt.ylabel('收盘价(元)') plt.legend() plt.show() ``` 这个序列的特性和前两个完全不同: 1. 几乎没有明显的趋势或季节性:股价的波动完全没有任何固定的上升或下降方向,也没有固定周期的波动规律; 2. 极强随机性+波动聚集性:股价的波动看起来完全是随机的,但你仔细观察会发现,大涨之后往往跟着大跌,大跌之后往往跟着大涨,也就是波动率聚集——大涨的时候波动率很高,大跌的时候波动率也很高,平稳的时候波动率很低,这种波动聚集性本身就是一种隐藏的规律; 3. 极弱的自相关性:我们计算股价的自相关系数,会发现只有 lag=1 的时候自相关系数稍微显著,lag≥2的时候自相关系数几乎为0,说明今天的股价和昨天的股价有一点点关系,再往前的历史和今天的股价几乎没有任何关系。
这个案例是很多新手预测者的“噩梦”:很多人试图用历史股价预测未来股价,但几乎都失败了,核心原因就是忽略了股价的随机性占比极高,可挖掘的规律极少的特性。我们会在后续章节讲解,对于这类随机性极强的序列,传统的时序模型效果很差,需要用到专门针对金融时序的模型,甚至需要结合基本面、情绪面等外部特征。
---
四、时间序列预处理全流程:从原始数据到可用数据集
原始的时序数据从来不是直接可以拿来建模的,它往往充满缺失值、异常值,甚至是非平稳的,直接建模只会得到完全错误的结果。时间序列的预处理和普通表格数据的预处理有本质区别:它不能打乱数据的顺序,不能使用随机采样的方法,所有的处理必须保留时间顺序的依赖性。一套标准的时序预处理流程如下:
1. 缺失值处理:不能随便用均值填充
时间序列的缺失值产生的原因有很多:传感器故障、数据上报失败、人工录入遗漏等,不同的缺失原因对应的处理方法完全不同。首先要把缺失值分成两类:
- **随机缺失**:缺失的发生和数值本身无关,比如传感器随机故障导致的缺测,这种缺失可以用插值法处理;
- **非随机缺失**:缺失的发生和数值本身有关,比如销量为0的时候不报销量,或者温度低于0度的时候传感器失效,这种缺失需要特殊处理。
常见的时间序列缺失值处理方法有: | 方法 | 适用场景 | 优缺点 | |------|----------|--------| | 前向填充(ffill) | 缺失值前后的值和缺失值差异很小,比如分钟级的交易数据,缺失1-2分钟 | 优点:简单快速,保留时序顺序;缺点:会引入滞后偏差 | | 后向填充(bfill) | 和前向填充类似,适合缺失值在序列末尾的场景 | 优点:简单;缺点:会引入未来信息泄露,不能用在训练集 | | 线性插值 | 缺失值前后的值有线性变化趋势,比如气温、流量等连续变化的序列 | 优点:平滑,符合序列的连续变化规律;缺点:不适合有突变的序列 | | 基于时序模型的插值 | 缺失值较多,或者序列有复杂的趋势、季节性 | 优点:能捕捉序列的规律,填充准确率高;缺点:计算成本高 |
> ⚠️ 实战注意点:时间序列的缺失值处理必须在拆分训练集和测试集之前完成,而且只能用训练集的统计量做填充,不能用整个序列的均值或中位数填充,否则会导致数据泄露,让测试集的评估结果虚高。比如你不能用2023年的销量均值填充2019年的缺失值,这相当于提前知道了未来的信息。
2. 异常值检测:先判断成因再处理
时间序列的异常值是指和序列整体规律明显不符的观测值,它可能是数据录入错误,也可能是真实的特殊事件。比如零售销量某一天突然翻了10倍,可能是把1000件录成了10000件(错误异常值),也可能是真的做了大型促销(真实异常值),处理方式完全不同。
针对时间序列的异常值检测,常用的方法有: 1. 3σ原则:假设序列服从正态分布,超出均值±3倍标准差的观测值判定为异常值,适合随机噪声服从正态分布的平稳序列; 2. 箱线图法(IQR):超出上四分位数+1.5倍四分位距,或者下四分位数-1.5倍四分位距的观测值为异常值,对噪声分布没有假设,鲁棒性更强; 3. STL分解残差检测:先用STL分解把序列的趋势、季节项去掉,剩下的残差项如果是平稳的,就可以用3σ或IQR检测残差里的异常值,这种方法能排除趋势、季节性的干扰,检测出的异常值更准确; 4. 时序专用检测算法:比如孤立森林、DBSCAN聚类,或者专门针对时序的异常检测算法如Twitter的AnomalyDetection、Facebook的Prophet异常检测模块,适合复杂的多季节性的序列。
处理异常值的原则是:先判断成因,再决定处理方式。如果是数据录入错误,可以直接删除或者用插值法修正;如果是真实事件导致的(比如疫情封控、大促),不能直接删除,最好加一个异常标记特征,让模型学习到这类特殊事件的影响。
3. 平稳性检验:建模前的必做步骤
在讲平稳性之前,我们先回到之前提到的汽车比喻:平稳的时间序列就像在平坦笔直的高速路上以恒定速度行驶的汽车,它的位置随时间的变化是线性的,速度(一阶差分)是恒定的,加速度(二阶差分)是0,均值、方差、自相关性都不会随时间突然跳变。而不平稳的序列就像突然踩油门(趋势性,速度越来越快,均值持续上升)、突然踩刹车(趋势性下降,均值持续下跌)、或者路上有坑洼(随机性波动,方差变大),你很难预测它接下来会开到哪里,因为你不知道驾驶员什么时候又会踩油门刹车。
从数学定义上,平稳性分为两种:
- **严平稳**:序列的任意有限维联合分布不随时间平移而变化,也就是不管你把时间轴往前提多久,序列的分布都和原来完全一样。严平稳是非常苛刻的条件,实际中几乎不可能满足;
- **宽平稳(弱平稳)**:只需要满足三个条件:① 均值不随时间变化;② 方差不随时间变化;③ 自协方差只和时间间隔有关,和具体时间点