未记录事实核验 · 状态:维护中
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 本文适用范围与待维护项
本文的双11会议情境及销量数据为教学模拟,不代表真实企业业绩。练习覆盖时间序列探索、分解和基础预测比较;分解绘图、ADF结果输出及Prophet绘图的已知问题已在本文代码中修正,但修正后的完整流程尚未核验运行,不保证在所有环境中直接运行。所示模型比较仅使用末尾30天的一次留出验证,Prophet展示未提供样本外评估,也未显式建模模拟数据中的促销事件;结果不能直接作为库存、预算或增长率预测依据,真实应用需补充滚动验证、预测区间与业务事件建模。
时间序列分析 - 掌握时间数据的预测艺术
abstract 📋 本节概览
📈 "看得见趋势,算不准未来"的预测难题
双11前的预测会议现场:
CEO急切地问:"根据往年数据,今年双11销量会比去年增长多少?我们需要准备多少库存?"
数据分析师小王看着图表:"从图上看,每年双11确实都在增长,但今年具体会增长多少...这个..."
运营总监不耐烦地打断:"你这个预测有什么用?就说增长30%还是50%?"
小王为难地说:"我只能说大概率会增长,但准确数字真的很难说..."
CFO质疑:"那我怎么制定预算?怎么安排物流?你这不是等于没预测吗?"
现场陷入尴尬的沉默...
这种"会看图,不会算"的预测困境:
很多数据分析师都遇到过这种尴尬:
- 📊 看得见规律,算不准数值:能观察到趋势但无法量化预测
- 📈 历史很清楚,未来很模糊:过去的数据一目了然,未来预测却心虚
- 🎯 老板要结果,分析师给不出:业务需要具体数字,只能给出模糊判断
- 💸 预测不准确,决策有风险:错误预测导致库存、预算、资源配置失误
时间序列分析就是要让你从"看图说话"升级为"精准预测",用科学的方法把时间数据中的规律转化为可靠的未来预测。
💡 核心概念解释
abstract 🎯 什么是时间序列?
时间序列是按时间顺序排列的数据点序列,每个数据点都对应特定的时间戳。就像记录体温的温度计,每小时测量一次,形成了体温随时间变化的序列。
时间序列的基本组成
1. 趋势(Trend)
- 长期的上升或下降模式
- 如公司营收的长期增长趋势
2. 季节性(Seasonality)
- 固定周期的重复模式
- 如零售业的年度销售季节性波动
3. 周期性(Cyclical)
- 不固定周期的波动模式
- 如经济周期的起伏
4. 随机性(Random/Noise)
- 无法预测的随机波动
- 如突发事件对销量的影响
时间序列分析方法
经典方法:
- ARIMA模型:自回归整合移动平均模型
- 指数平滑:ExponentialSmoothing、Holt-Winters
- 季节分解:STL分解、Classical分解
现代方法:
- Prophet:Facebook开源的预测工具
- LSTM:长短期记忆神经网络
📊 应用场景与能力要求
基础能力(数据分析师)
- 理解时间序列的基本概念
- 掌握简单的预测方法(移动平均、指数平滑)
- 能够进行基础的趋势和季节性分析
高级能力(时间序列专家)
- 深入理解ARIMA、Prophet等高级模型
- 能够处理复杂的多元时间序列
- 具备解决复杂预测问题的能力
🔍 技术实现
1. 时间序列数据准备与探索
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime, timedelta
import warnings
warnings.filterwarnings('ignore')
# 时间序列分析库
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.holtwinters import ExponentialSmoothing
from statsmodels.tsa.stattools import adfuller
from sklearn.metrics import mean_squared_error, mean_absolute_error
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
class TimeSeriesAnalyzer:
"""时间序列分析框架"""
def __init__(self):
self.data = None
self.models = {}
def generate_sample_data(self):
"""生成示例时间序列数据"""
np.random.seed(42)
# 生成2年的日销售数据
dates = pd.date_range('2022-01-01', '2023-12-31', freq='D')
n_days = len(dates)
# 基础趋势:两年区间内从100线性增长到115
trend = np.linspace(100, 115, n_days)
# 年度季节性:年初附近高峰,年中附近低谷
yearly_season = 20 * np.sin(2 * np.pi * np.arange(n_days) / 365.25 + np.pi/2)
# 周度季节性:以7天为周期的正弦波动
weekly_season = 10 * np.sin(2 * np.pi * np.arange(n_days) / 7 + np.pi)
# 特殊事件
special_events = np.zeros(n_days)
for i, date in enumerate(dates):
if date.month == 11 and 8 <= date.day <= 15: # 双11
special_events[i] = 80
elif date.month == 12 and date.day >= 20: # 圣诞
special_events[i] = 30
# 随机噪声
noise = np.random.normal(0, 8, n_days)
# 合成最终销量
sales = trend + yearly_season + weekly_season + special_events + noise
sales = np.maximum(sales, 20) # 确保最小销量
ts_data = pd.DataFrame({
'date': dates,
'sales': sales
})
ts_data.set_index('date', inplace=True)
return ts_data
def explore_time_series(self, data):
"""时间序列数据探索"""
print("📈 时间序列数据探索")
print("=" * 50)
# 基础统计信息
print("数据基础信息:")
print(f"时间范围: {data.index.min()} 到 {data.index.max()}")
print(f"数据点数量: {len(data)}")
print(f"平均销量: {data['sales'].mean():.1f}")
print(f"销量标准差: {data['sales'].std():.1f}")
# 可视化分析
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
# 1. 原始时间序列
axes[0, 0].plot(data.index, data['sales'])
axes[0, 0].set_title('原始销量时间序列')
axes[0, 0].grid(True, alpha=0.3)
# 2. 销量分布
axes[0, 1].hist(data['sales'], bins=50, alpha=0.7)
axes[0, 1].set_title('销量分布')
# 3. 月度平均销量
monthly_avg = data.groupby(data.index.month)['sales'].mean()
axes[1, 0].bar(range(1, 13), monthly_avg.values)
axes[1, 0].set_title('月度平均销量')
axes[1, 0].set_xticks(range(1, 13))
# 4. 星期效应
weekly_avg = data.groupby(data.index.dayofweek)['sales'].mean()
weekday_names = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
axes[1, 1].bar(range(7), weekly_avg.values)
axes[1, 1].set_xticks(range(7))
axes[1, 1].set_xticklabels(weekday_names)
axes[1, 1].set_title('星期效应')
plt.tight_layout()
plt.show()
self.data = data
return data
def decompose_time_series(self, data):
"""时间序列分解"""
print(f"\n🔍 时间序列分解分析")
print("=" * 50)
# 进行季节分解
decomposition = seasonal_decompose(data['sales'],
model='additive',
period=365)
# 可视化分解结果
fig, axes = plt.subplots(4, 1, figsize=(15, 12))
decomposition.observed.plot(ax=axes[0], title='原始数据')
decomposition.trend.plot(ax=axes[1], title='趋势')
decomposition.seasonal.plot(ax=axes[2], title='季节性')
decomposition.resid.plot(ax=axes[3], title='残差')
for ax in axes:
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
return decomposition
def test_stationarity(self, series):
"""平稳性检验"""
print(f"\n📊 平稳性检验")
print("=" * 50)
# ADF检验
adf_result = adfuller(series.dropna())
print("ADF平稳性检验结果:")
print(f" ADF统计量: {adf_result[0]:.4f}")
print(f" p值: {adf_result[1]:.4f}")
if adf_result[1] <= 0.05:
print(" 结论: 在5%显著性水平下拒绝单位根原假设 (p <= 0.05)")
else:
print(" 结论: 在5%显著性水平下不能拒绝单位根原假设 (p > 0.05)")
return adf_result
def build_prediction_models(self, data, test_size=30):
"""构建预测模型"""
print(f"\n🤖 构建时间序列预测模型")
print("=" * 50)
# 分割数据
train_data = data['sales'][:-test_size]
test_data = data['sales'][-test_size:]
model_results = {}
# 1. 移动平均
print("\n训练 移动平均模型...")
ma_predictions = [train_data.rolling(window=7).mean().iloc[-1]] * test_size
ma_mse = mean_squared_error(test_data, ma_predictions)
model_results['移动平均'] = {
'predictions': ma_predictions,
'rmse': np.sqrt(ma_mse)
}
print(f" RMSE: {np.sqrt(ma_mse):.2f}")
# 2. 指数平滑
print("\n训练 指数平滑模型...")
exp_model = ExponentialSmoothing(train_data,
seasonal='add',
seasonal_periods=7).fit()
exp_predictions = exp_model.forecast(steps=test_size)
exp_mse = mean_squared_error(test_data, exp_predictions)
model_results['指数平滑'] = {
'model': exp_model,
'predictions': exp_predictions.values,
'rmse': np.sqrt(exp_mse)
}
print(f" RMSE: {np.sqrt(exp_mse):.2f}")
# 3. ARIMA模型
print("\n训练 ARIMA模型...")
try:
arima_model = ARIMA(train_data, order=(1, 1, 1)).fit()
arima_predictions = arima_model.forecast(steps=test_size)
arima_mse = mean_squared_error(test_data, arima_predictions)
model_results['ARIMA'] = {
'model': arima_model,
'predictions': arima_predictions,
'rmse': np.sqrt(arima_mse)
}
print(f" RMSE: {np.sqrt(arima_mse):.2f}")
except:
print(" ARIMA模型训练失败")
return model_results, test_data
def evaluate_predictions(self, model_results, test_data):
"""评估预测结果"""
print(f"\n📈 预测模型评估")
print("=" * 50)
# 可视化预测结果
plt.figure(figsize=(15, 6))
test_dates = test_data.index
plt.plot(test_dates, test_data.values, 'o-', label='实际值', linewidth=2)
for name, results in model_results.items():
plt.plot(test_dates, results['predictions'],
'--', label=f'{name}预测', alpha=0.8)
plt.title('预测结果对比')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
# 性能对比
print("模型性能对比:")
for name, results in model_results.items():
print(f" {name}: RMSE = {results['rmse']:.2f}")
# 示例使用
analyzer = TimeSeriesAnalyzer()
# 1. 生成和探索数据
sample_ts_data = analyzer.generate_sample_data()
explored_data = analyzer.explore_time_series(sample_ts_data)
# 2. 时间序列分解
decomposition_result = analyzer.decompose_time_series(explored_data)
# 3. 平稳性检验
stationarity_test = analyzer.test_stationarity(explored_data['sales'])
# 4. 构建预测模型
model_results, test_data = analyzer.build_prediction_models(explored_data)
# 5. 评估预测结果
analyzer.evaluate_predictions(model_results, test_data)
2. Prophet预测模型
# Prophet模型实现(需要安装:pip install prophet)
class ProphetAnalyzer:
"""Prophet时间序列预测"""
def __init__(self):
self.model = None
def build_prophet_model(self, data):
"""构建Prophet模型"""
try:
from prophet import Prophet
# 准备数据格式
prophet_data = pd.DataFrame({
'ds': data.index,
'y': data['sales'].values
})
# 创建和训练模型
model = Prophet(yearly_seasonality=True,
weekly_seasonality=True,
daily_seasonality=False)
model.fit(prophet_data)
# 预测未来30天
future = model.make_future_dataframe(periods=30)
forecast = model.predict(future)
# 可视化结果
fig, ax = plt.subplots(figsize=(15, 6))
# 预测结果
model.plot(forecast, ax=ax)
ax.set_title('Prophet预测结果')
# 组件分解
model.plot_components(forecast)
plt.tight_layout()
plt.show()
self.model = model
return forecast
except ImportError:
print("请安装Prophet: pip install prophet")
return None
# 使用Prophet
prophet_analyzer = ProphetAnalyzer()
prophet_forecast = prophet_analyzer.build_prophet_model(explored_data)
📋 最佳实践
1. 数据准备
- 数据质量检查:处理缺失值、异常值、重复值
- 频率统一:确保时间间隔一致
- 特征工程:创建滞后变量、移动平均、季节性特征
2. 模型选择策略
- 数据量小:简单方法(移动平均、指数平滑)
- 明显季节性:Holt-Winters、Prophet
- 复杂模式:ARIMA、机器学习方法
3. 评估策略
- 样本外验证:时间序列交叉验证
- 多指标评估:RMSE、MAE、MAPE
- 业务指标:预测准确率对业务的实际价值
⚠️ 常见误区
-
忽视季节性
- 误区:用简单趋势模型处理季节性数据
- 正解:识别并建模季节性模式
-
数据泄露
- 误区:使用未来信息预测过去
- 正解:严格按时间顺序划分数据
-
过度拟合
- 误区:追求训练集上的完美表现
- 正解:关注样本外预测性能
✅ 掌握检查
- [ ] 理解时间序列的基本组成要素
- [ ] 掌握时间序列分解和平稳性检验
- [ ] 能够使用多种预测方法
- [ ] 理解模型评估和选择策略
- [ ] 能够解决实际业务预测问题
🔗 学习连接
前置知识: 预测模型 | 统计分析基本概念 下一步: 机器学习 | 数据挖掘 相关概念: 统计分析 | 机器学习 | 模型评估
tip 💡 实践建议
- 从数据探索开始,充分理解时间序列的特征
- 选择合适的预测方法,简单模型往往效果不错
- 重视季节性和趋势的建模
- 建立完整的评估体系,关注业务价值