你手机里的每日步数、股票的实时价格、城市的月度降雨量……这些随时间变化的数据,藏着无数规律和趋势。但 raw 时间序列往往充斥着噪声(比如某天步数突然变低可能只是忘了带手机)、波动(股票的短期涨跌),直接分析就像在乱麻中找线索。
时间序列处理就是梳理这团“乱麻”的技术:通过平滑去噪、分解趋势、模型预测,让隐藏的规律浮出水面。今天从基础到实战,拆解时间序列处理的核心方法,附公式推导和完整代码,帮你从“看数据”到“懂数据”再到“预测数据”。
一、什么是时间序列?为什么它“不好惹”?
时间序列是“按时间顺序排列的数据”,比如:
- 小时级:某网站每小时的访问量;
- 日级:某店铺的每日销售额;
- 年度:某城市的年降雨量。
它的“特殊之处”在于时间依赖性:今天的销售额和昨天、上周同期可能高度相关(比如周末销量通常更高)。但同时,它又很“吵”:
- 短期噪声:比如某天因系统故障,访问量骤降;
- 季节性波动:比如冰淇淋销量在夏季飙升,冬季低迷;
- 长期趋势:比如随着用户增长,网站访问量整体上升。
直接用这种“带噪声、有波动”的数据做分析或预测,结果往往失真。时间序列处理的核心,就是剥离噪声、提取核心成分、捕捉时间规律。
二、时间序列处理3大核心技术:从“清洗”到“预测”
1. 平滑处理:给数据“去噪”,露出真面目
原始时间序列常像“锯齿”一样上下波动,平滑处理的作用是“磨平毛刺”,突出整体趋势。两种最常用的方法:
(1)简单移动平均(SMA):用“窗口均值”过滤噪声
原理:取连续n个数据的平均值作为当前点的平滑值,相当于用“局部平均”抵消短期波动。
公式:
SMAt=xt−n+1+xt−n+2+...+xtnSMA_t = \frac{x_{t-n+1} + x_{t-n+2} + ... + x_t}{n}SMAt=nxt−n+1+xt−n+2+...+xt
其中,nnn是窗口大小(比如n=7代表“7天移动平均”),xtx_txt是t时刻的原始数据。
举例:用7天移动平均平滑每日步数——某天步数1000步(可能忘了带手机),但前后6天平均8000步,平滑后的值会更接近8000,过滤掉异常噪声。
优缺点:简单易算,但对窗口内所有数据“一视同仁”(昨天和7天前的数据权重相同),对近期变化不敏感。
(2)指数加权移动平均(EWMA):给“近期数据”更高权重
原理:让越近的数据权重越大,既平滑噪声,又能快速响应新变化。
公式:
EWMAt=α⋅xt+(1−α)⋅EWMAt−1EWMA_t = \alpha \cdot x_t + (1-\alpha) \cdot EWMA_{t-1}EWMAt=α⋅xt+(1−α)⋅EWMAt−1
其中,α\alphaα是平滑系数(0<α<1),α越大,近期数据权重越高(比如α=0.9时,几乎只关注当前数据)。
举例:用EWMA平滑股票价格——最近3天股价连续上涨,α=0.7的EWMA会快速“跟上”上涨趋势,而SMA可能因包含更早的低价数据,反应滞后。
优缺点:更灵活,适合趋势变化较快的场景,但需要调参α(通常取0.1-0.3)。
2. 时间序列分解:把“复杂数据”拆成“简单成分”
任何时间序列都可以拆解为3个核心成分,就像一杯奶茶能拆成“奶+茶+糖”:
xt=Trendt+Seasonalityt+Residualtx_t = Trend_t + Seasonality_t + Residual_txt=Trendt+Seasonalityt+Residualt
- 趋势(Trend):长期变化方向(比如销售额逐年增长);
- 季节性(Seasonality):周期性重复的波动(比如春节前销量暴涨,节后回落);
- 残差(Residual):扣除趋势和季节性后剩下的“噪声”(无法解释的随机波动)。
分解作用:单独分析每个成分,比如通过趋势判断业务是否增长,通过季节性制定促销计划,通过残差识别异常(比如残差突然变大,可能是突发事件)。
3. 预测模型:用“历史”预测“未来”
最经典的预测模型是ARIMA(自回归积分移动平均),它通过3个部分捕捉时间依赖:
- AR(自回归):用过去的“自身数据”预测未来(比如用前3天的销售额预测今天);
- I(差分):通过差分让数据“平稳”(消除趋势,让均值和方差不随时间变化);
- MA(移动平均):用过去的“预测误差”修正当前预测(比如昨天预测多了100,今天就少预测100)。
ARIMA参数:记为ARIMA(p,d,q),其中:
- p:AR的阶数(用过去p个时刻的数据);
- d:差分次数(通常d=1或2,让数据平稳);
- q:MA的阶数(用过去q个误差)。
三、实战:从“生成数据”到“预测未来”的完整流程
我们用一个“带趋势+季节性+噪声”的虚拟时间序列(比如某产品的日销量),演示完整处理流程:平滑去噪→分解成分→ARIMA预测。
完整代码(可直接运行)
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf # 用于查看自相关性
# 设置中文显示
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
plt.rcParams["axes.unicode_minus"] = False # 负号正常显示
# ----------------------
# 1. 生成虚拟时间序列数据(带趋势+季节性+噪声)
# ----------------------
np.random.seed(42)
# 时间索引:200天(2024-01-01至2024-07-19)
time_index = pd.date_range(start='2024-01-01', periods=200, freq='D')
# 构建3个核心成分
trend = np.linspace(100, 300, 200) # 长期趋势:从100线性增长到300(比如用户增长带动销量)
seasonality = 50 * np.sin(2 * np.pi * np.arange(200) / 7) # 周度季节性:周期7天(周末销量高)
noise = np.random.normal(0, 15, 200) # 随机噪声:均值0,标准差15(模拟短期波动)
# 合成时间序列:趋势+季节性+噪声
series = pd.Series(trend + seasonality + noise, index=time_index)
series.name = "每日销量" # 给序列命名,方便绘图
# ----------------------
# 2. 平滑处理:用EWMA去噪
# ----------------------
# 指数加权移动平均(α=0.3,近期数据权重更高)
ewma = series.ewm(alpha=0.3).mean()
# 可视化:原始数据 vs 平滑后数据
plt.figure(figsize=(12, 5))
plt.plot(series, label='原始数据', color='blue', alpha=0.6)
plt.plot(ewma, label='EWMA平滑(α=0.3)', color='red', linewidth=2)
plt.title('原始数据与平滑后数据对比', fontsize=14)
plt.xlabel('日期')
plt.ylabel('销量')
plt.legend()
plt.show()
# ----------------------
# 3. 时间序列分解:拆出趋势、季节性、残差
# ----------------------
# 分解(周期=7,因为我们设置了周度季节性)
decompose_result = seasonal_decompose(series, model='additive', period=7)
trend_component = decompose_result.trend # 趋势成分
seasonal_component = decompose_result.seasonal # 季节性成分
residual_component = decompose_result.resid # 残差(噪声)
# 可视化3个成分
plt.figure(figsize=(12, 10))
plt.subplot(3, 1, 1)
plt.plot(series, label='原始数据', alpha=0.5)
plt.plot(trend_component, label='趋势成分', color='orange', linewidth=2)
plt.title('原始数据与趋势成分', fontsize=14)
plt.legend()
plt.subplot(3, 1, 2)
plt.plot(seasonal_component, label='季节性成分', color='green')
plt.title('季节性成分(周度周期)', fontsize=14)
plt.legend()
plt.subplot(3, 1, 3)
plt.plot(residual_component, label='残差(噪声)', color='purple')
plt.axhline(y=0, color='black', linestyle='--') # 参考线:0值
plt.title('残差成分(应接近随机分布)', fontsize=14)
plt.legend()
plt.tight_layout()
plt.show()
# ----------------------
# 4. ARIMA预测:用历史数据预测未来30天
# ----------------------
# 查看数据自相关性(帮助选择ARIMA参数)
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plot_acf(series.dropna(), lags=20, ax=plt.gca()) # 自相关图(MA参数q参考)
plt.title('自相关图(ACF)')
plt.subplot(1, 2, 2)
plot_pacf(series.dropna(), lags=20, ax=plt.gca()) # 偏自相关图(AR参数p参考)
plt.title('偏自相关图(PACF)')
plt.tight_layout()
plt.show()
# 拟合ARIMA模型(根据ACF/PACF,选择p=1, d=1, q=1)
model = ARIMA(series, order=(1, 1, 1)) # (p,d,q)
fit_result = model.fit()
# 预测未来30天
forecast_steps = 30
forecast = fit_result.forecast(steps=forecast_steps)
# 生成预测的时间索引(接在原始数据后面)
forecast_index = pd.date_range(start=series.index[-1] + pd.Timedelta(days=1), periods=forecast_steps, freq='D')
forecast_series = pd.Series(forecast, index=forecast_index, name='预测销量')
# 可视化预测结果
plt.figure(figsize=(12, 5))
plt.plot(series[-60:], label='最近60天原始数据', color='blue') # 只显示最近60天,方便对比
plt.plot(forecast_series, label=f'未来{forecast_steps}天预测', color='red', linestyle='--')
plt.title('原始数据与预测结果对比', fontsize=14)
plt.xlabel('日期')
plt.ylabel('销量')
plt.legend()
plt.show()
四、结果解读:从图中读懂时间的规律
1. 平滑处理:噪声去哪了?
左图是原始数据(蓝色,锯齿状),右图是EWMA平滑后的数据(红色,更平滑)。可以看到:
- 原始数据因噪声上下波动剧烈;
- 平滑后的数据保留了整体增长趋势和周度波动,却滤掉了突兀的“毛刺”(比如某几天的异常低值)。
2. 分解:趋势、季节、噪声,一个都跑不掉
- 趋势成分(橙色线):清晰显示“销量从100增长到300”的长期上升趋势,不受短期波动影响;
- 季节性成分(绿色线):呈现明显的7天周期(和我们设置的“周度季节性”一致),说明每周有固定的高低规律(比如周末高、工作日低);
- 残差成分(紫色线):围绕0值随机波动,说明趋势和季节性已被充分提取,剩下的是“纯噪声”。
3. 预测:未来30天会怎样?
ARIMA模型预测的未来30天销量(红色虚线):
- 整体延续了上升趋势(符合长期增长规律);
- 保留了小幅波动(符合周度季节性);
- 和最近的原始数据衔接自然,没有突然的跳变——说明模型捕捉到了时间依赖关系。
五、时间序列处理“避坑指南”
-
平滑窗口别乱选:
- 短期噪声多(如小时级数据):选小窗口(n=3-5),避免过度平滑丢失变化;
- 长期趋势明显(如年度数据):选大窗口(n=12,即年度移动平均),突出趋势。
-
ARIMA依赖“平稳性”:
ARIMA要求数据“平稳”(均值和方差不随时间变化)。如果原始数据有明显趋势,需要先做差分(d≥1)——代码中order=(1,1,1)
的d=1
就是1阶差分,用于消除趋势。 -
季节性强?用SARIMA:
若数据有固定周期(如月度数据的12个月周期),普通ARIMA效果有限,改用SARIMA(带季节性参数的ARIMA),专门处理强季节性数据。 -
异常值先处理:
极端异常值(如某天销量为0,明显是数据错误)会严重影响平滑和预测,建议先用箱线图或3σ法则识别并修正(比如用前后均值替换)。
总结:时间序列处理的“黄金流程”
处理时间序列,记住这3步:
- 平滑去噪:用EWMA或移动平均,过滤短期波动,让数据“变乖”;
- 分解成分:拆出趋势、季节性、残差,搞清楚数据“由什么组成”;
- 模型预测:用ARIMA(或SARIMA)捕捉时间依赖,预测未来趋势。
无论是销量预测、库存管理,还是异常检测(比如残差突然变大可能是故障),掌握这套流程,就能从“时间的乱麻”中理出清晰的线索。
你处理过哪些有趣的时间序列数据?评论区聊聊你的发现~