Day 58 经典时序模型2

@浙大疏锦行

今日任务:

  1. 时序建模的流程
  2. 时序任务经典单变量数据集
  3. ARIMA(p,d,q)模型实战
  4. ASRIMA摘要图的理解
  5. 处理不平稳的2种差分:趋势(n阶差分);季节性(季节性差分)

作业:对太阳黑子数量数据集用ARIMA完成流程

一、时序建模的流程

对于平稳时间序列,可以直接选择AR、MA、ARMA模型;对于不平稳数据(现实世界的大多数),采用ARIMA(p,d,q)。它在ARMA的基础上引入差分"I",从而处理不平稳性。

下面是ARIMA建模的流程,核心在于确定参数:

  • (1)数据可视化 :观察原始数据的时间序列图,判断是否存在不平稳倾向(趋势或季节性)
  • (2)平稳性检验ADF检验 ,若p值 > 0.05,说明序列不平稳,进行一阶差分
  • (3)确定差分次数d:一阶差分后,再次ADF检验;平稳则d=1,不平稳再次差分,直到平稳
  • (4)确定p和q :对平稳序列绘制ACF (定q)和PACF图(定p),根据截尾特征确定
  • (5)建立并训练ARIMA(p,d,q)模型
  • (6)评估与诊断 模型:查看模型的摘要信息,检查残差是否为白噪声 (完全提取出规律
  • (7)进行预测

时间序列建模的迭代过程:初步选择 -> 拟合检验 -> 模型修正 -> 数据还原。

二、经典数据集

最著名、最常用的单变量时间序列经典数据集。

下面使用的是url在线读取数据集 的方式:代码通过**pd.read_csv(url)**直接从网络 URL 读取数据,Pandas 会自动处理网络请求并加载数据到内存中,属于 "在线读取" 方式。

2.1 国际航空乘客数量(Airline Passengers)

python 复制代码
import pandas as pd
url = 'https://raw.githubusercontent.com/jbrownlee/Datasets/master/airline-passengers.csv'
df_air = pd.read_csv(url, header=0, index_col=0, parse_dates=True)
df_air.head()

2.2 太阳黑子数量(Sunspots)

python 复制代码
from statsmodels.datasets import sunspots
df_sun = sunspots.load_pandas().data['SUNACTIVITY']
df_sun.head()

2.3 加州每日女性出生数量(Daily Female Births)

python 复制代码
import pandas as pd
url = 'https://raw.githubusercontent.com/jbrownlee/Datasets/master/daily-total-female-births.csv'
df_births = pd.read_csv(url, header=0, index_col=0, parse_dates=True)
# df_births.plot()

注:这里直接使用df.plot() 绘图,因为Pandas 会隐式导入 Matplotlib(如果尚未导入),并使用其绘图接口生成图表。对于简单的可视化需求,无需显式导入 Matplotlib,减少代码量。

三、实战

3.1 Daily Female Births

python 复制代码
# 导入必要的库
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import adfuller
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from statsmodels.tsa.arima.model import ARIMA

# 设置matplotlib以正确显示中文
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 加载数据
url = 'https://raw.githubusercontent.com/jbrownlee/Datasets/master/daily-total-female-births.csv'
df = pd.read_csv(url, header=0, index_col=0, parse_dates=True)
df.columns = ['Births']
ts_data = df['Births']

print("--- 原始数据预览 ---")
print(ts_data.head())

3.1.1 初步可视化

python 复制代码
# 绘制时序图
plt.figure(figsize=(14, 7))
plt.plot(ts_data)
plt.title('1959年加州每日女性出生数量')
plt.xlabel('日期')
plt.ylabel('出生数量')
plt.show()

3.1.2 平稳性检验

python 复制代码
def adf_test(timeseries):
    print('--- ADF检验结果 ---')
    # H0: 序列非平稳; H1: 序列平稳
    result = adfuller(timeseries)
    print(f'ADF Statistic: {result[0]}')
    print(f'p-value: {result[1]}') # 重点关注这个值
    if result[1] <= 0.05:
        print("结论: 成功拒绝原假设,序列是平稳的。")
    else:
        print("结论: 未能拒绝原假设,序列是非平稳的。")

adf_test(ts_data)
python 复制代码
--- ADF检验结果 ---
ADF Statistic: -4.808291253559765
p-value: 5.2434129901498554e-05
结论: 成功拒绝原假设,序列是平稳的。

可以看到这个数据本身是平稳的,不要差分处理,故d=0。

3.1.3 确定p和q

绘制ACF和PACF图,确定p和q:

python 复制代码
# 因为数据是平稳的,我们直接对原始数据绘制ACF和PACF
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8))

plot_acf(ts_data, ax=ax1, lags=40)
ax1.set_title('自相关函数 (ACF)')

plot_pacf(ts_data, ax=ax2, lags=40)
ax2.set_title('偏自相关函数 (PACF)')

plt.tight_layout()
plt.show()

观察图形:发现PACF 在lag=2后,几乎所有相关性都落入了置信区间内(截尾 )→ p=2;而ACF ,主要呈现的是拖尾 → q=0。

3.1.4 建模并训练

确定p=2,d=0,q=0后,建立模型:ARIMA(2,0,0)

python 复制代码
import warnings
warnings.filterwarnings("ignore")
# 建立ARIMA(2, 0, 0)模型
model = ARIMA(ts_data, order=(2, 0, 0))
arima_result = model.fit()

# 让我们预测未来30天
forecast_steps = 30
forecast = arima_result.get_forecast(steps=forecast_steps)
pred_mean = forecast.predicted_mean
conf_int = forecast.conf_int()

# 绘制结果
plt.figure(figsize=(14, 7))
plt.plot(ts_data, label='原始数据')
# 绘制模型在历史数据上的拟合值
plt.plot(arima_result.fittedvalues, color='orange', label='模型拟合值')
# 绘制未来预测值
plt.plot(pred_mean, color='red', label='未来预测值')
# 绘制置信区间
plt.fill_between(conf_int.index,
                 conf_int.iloc[:, 0],
                 conf_int.iloc[:, 1], color='pink', alpha=0.5, label='95%置信区间')
plt.title('ARIMA(2,0,0)模型拟合与预测')
plt.legend()
plt.show()

注:时间序列分析的核心是将金子(可预测部分)和沙子(随机噪声)分离。所以一个好的结果应该是残差趋近于白噪声(反过来说明将可预测部分完全提取、学习),而不是完全复制原始规律(过拟合)。

目标:不是创造一个能"复制"历史的"复印机"(过拟合),而是要打造一个能"理解"历史规律的"侦探"(好模型)。这个"侦探"能区分出哪些是线索(模式),哪些是干扰项(噪音)。

3.1.5 评估与诊断

打印模型摘要,查看结果:

python 复制代码
# 打印模型摘要
print(arima_result.summary())

3.1.6 模型解读

在 statsmodels 库的现代版本中,ARIMA、SARIMA 和 SARIMAX 的后端实现被统一到了一个强大的 SARIMAX 类中。它包含三部分: ARIMA(p,d,q) 非季节性部分、Seasonal(P,D,Q,m) 季节性部分及eXogenous(X) 外部变量。

  • AIC和BIC:比较不同模型,选择"最佳"模型的重要依据。越小越好
  • sigma :模型残差的方差,越小说明拟合误差越小
  • std err:系数估计值的不确定性或"抖动幅度",越小越稳定
  • P > |z| :"该系数是否显著,不为0"。p < 0.05 则说明非常显著,不为0 ,等价于置信区间不包含0,说明模型参数合理
  • 残差判断 :好的残差应该是随机的白噪声 (无相关性、方差恒定及均值为0),如果为非白噪声,说明模型存在改进空间。另外,服从正态分布(增加说服力)将有利于构建准确的预测区间。

3.2 Airline Passengers

同理,按照上述的步骤进行

初步可视化

python 复制代码
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.tsa.statespace.sarimax import SARIMAX
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
import warnings

warnings.filterwarnings("ignore") # 忽略一些模型拟合时产生的警告信息
# 加载数据
url = 'https://raw.githubusercontent.com/jbrownlee/Datasets/master/airline-passengers.csv'
df_air = pd.read_csv(url, header=0, index_col=0, parse_dates=True)
df_air.rename(columns={'#Passengers': 'Passengers'}, inplace=True) # 列名简化

# 绘制原始数据
plt.figure(figsize=(12, 6))
plt.plot(df_air['Passengers'])
plt.title('Airline Passengers (1949-1960)')
plt.xlabel('Year')
plt.ylabel('Number of Passengers')
plt.grid(True)
plt.show()

平稳性

从图上可以看出,存在季节性。对于ARIMA模型只能处理趋势 (普通差分),因此需要先手动进行季节性差分 。此外,注意到,上升的幅度越来越大(乘性效应 ),使用对数处理加性效应),稳定方差。

python 复制代码
# 对数变换
df_air['log_passengers'] = np.log(df_air['Passengers'])

plt.figure(figsize=(12, 5))
plt.plot(df_air['log_passengers'])
plt.title('Log-Transformed Airline Passengers')
plt.grid(True)
plt.show()

# 季节性差分 (m=12)
df_air['log_seasonal_diff'] = df_air['log_passengers'].diff(12)

# 丢掉因差分产生的NaN值
deseasonalized_data = df_air['log_seasonal_diff'].dropna()

plt.figure(figsize=(12, 5))
plt.plot(deseasonalized_data)
plt.title('Deseasonalized and Log-Transformed Data')
plt.grid(True)
plt.show()

确定参数

  • d:一阶差分
  • p:PACF,滞后一阶后截尾,p=1
  • q:ACF,滞后一阶后截尾,q=1

注:lag=12的尖峰,是残余的季节性信号

python 复制代码
# 对去季节性的数据再进行一阶差分,使其平稳
stationary_data = deseasonalized_data.diff(1).dropna()

# 绘制ACF和PACF图
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8))
plot_acf(stationary_data, lags=24, ax=ax1)
plot_pacf(stationary_data, lags=24, ax=ax2)
plt.show()

建模与评估

python 复制代码
from statsmodels.tsa.arima.model import ARIMA

# 准备训练数据(经过预处理的)
# 注意:我们是对去季节性的数据进行建模
train_data = deseasonalized_data[:'1959']

# 定义并拟合ARIMA(1,1,1)模型
model = ARIMA(train_data, order=(1, 1, 1))
model_fit = model.fit()

print(model_fit.summary())

# 预测未来12个点(1960年全年)
# 模型预测的是经过变换后的数据
forecast_diff = model_fit.forecast(steps=12)

首先尝试前面推断的模型ARIMA(1,1,1),发现ma.L1的系数不显著(0.399>0.05),此时AIC为 -407.797。更换模型为ARIMA(1,1,0)尝试,系数显著,AIC为-409.622,小于前者。

python 复制代码
# 重新拟合一个更简单的模型 ARIMA(1, 1, 0)
model_simplified = ARIMA(train_data, order=(1, 1, 0))
model_fit_simplified = model_simplified.fit()
print(model_fit_simplified.summary())

# 比较两个模型的AIC
# 原模型 ARIMA(1,1,1) 的 AIC: -407.797
# 简化模型 ARIMA(1,1,0) 的 AIC 会是多少?
# (运行代码后,ARIMA(1,1,0)的AIC大约是-409.622,比-407.797更小)

数据还原

针对经过对数变换、季节性差分和一阶差分"后的值,一步步把它还原回去

python 复制代码
# 让我们给变量取个更准确的名字
# forecast_deseasonalized 是对"去季节性"序列的直接预测
forecast_deseasonalized = model_fit.forecast(steps=12)

# 逆向变换过程
predictions = []

# 逐步还原预测值
for i in range(len(forecast_deseasonalized)):
    # 1. 还原季节性差分 (直接使用预测值)
    # log(y_t) = y'_t + log(y_{t-12})
    # y'_t 就是 forecast_deseasonalized[i]
    
    # 获取12个月前的历史对数值
    last_year_log_val = df_air['log_passengers']['1959'].iloc[i]
    
    # 加上历史值,得到预测的对数值
    pred_log = forecast_deseasonalized.iloc[i] + last_year_log_val
    
    # 2. 还原对数变换 (exp)
    pred_original_scale = np.exp(pred_log)
    
    predictions.append(pred_original_scale)

# 将预测结果转换为Series,方便绘图
# 注意:forecast_deseasonalized 自带了正确的日期索引,可以直接使用
predictions_series = pd.Series(predictions, index=forecast_deseasonalized.index)

# --- 重新绘图 ---
plt.figure(figsize=(14, 7))
# 原始数据
plt.plot(df_air['Passengers'], label='Original Data')
# 我们的预测
plt.plot(predictions_series, color='red', linestyle='--', label='ARIMA Forecast (Corrected)')
plt.title('Airline Passengers Forecast using Manual Preprocessing + ARIMA')
plt.xlabel('Year')
plt.ylabel('Number of Passengers')
plt.legend()
plt.grid(True)
plt.show()

四、作业

对太阳黑子数量数据集进行建模

python 复制代码
from statsmodels.datasets import sunspots
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import adfuller
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from statsmodels.tsa.arima.model import ARIMA

# 设置matplotlib以正确显示中文
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

df_sun = sunspots.load_pandas().data['SUNACTIVITY']
years = range(1700, 1700 + len(df_sun))
df_sun = pd.Series(df_sun.values,index=years) # 1700-2008
df_sun.head()

周期约等于11,进行差分处理

python 复制代码
# 平稳性检验
print('--- ADF检验结果 ---')
# H0: 序列非平稳; H1: 序列平稳
result = adfuller(df_sun)
p_value = result[1]
print(f'p-value:{p_value}')
if p_value <= 0.05:
    print('结论: 拒绝原假设,序列是平稳的。')
else:
    print('结论: 未能拒绝原假设,序列是非平稳的。')

# 差分处理:周期性,period=11
df_sun_period = df_sun.diff(periods=11).dropna()
result_period = adfuller(df_sun_period)
print(f"p-value:{result_period[1]}")
if result_period[1] <= 0.05:
    print('结论: 拒绝原假设,序列是平稳的。')
else:
    print('结论: 未能拒绝原假设,序列是非平稳的。')

# 绘制原始图像
plt.figure(figsize=(12,6))
plt.subplot(211)
plt.plot(df_sun)
plt.title("太阳黑子数量(原始)")
plt.xlabel('年')
plt.ylabel('观测数量')
    
plt.subplot(212)
plt.plot(df_sun_period)
plt.title("太阳黑子数量(差分后)")
plt.xlabel('年')
plt.ylabel('观测数量')
plt.tight_layout()
plt.show()

绘制PACF和ACF图确定p,q。

python 复制代码
# 确定参数
fig,(ax1,ax2) = plt.subplots(2,1,figsize=(12,6))

plot_acf(df_sun_period,lags=40,ax=ax1)
plot_pacf(df_sun_period,lags=40,ax=ax2)
plt.tight_layout()
plt.show()

建立ARIMA(1,0,2)模型,留下最后一个周期的数据作为测试集

python 复制代码
# 建模
#train_size = int(len(df_sun_period) * 0.8)  # 80%训练,20%测试
train_years_end = 1997
train_diff = df_sun_period[df_sun_period.index<= train_years_end] # 1711-1997
test_diff = df_sun_period.iloc[df_sun_period.index > train_years_end] # 1998-2008
model = ARIMA(train_diff,order=(1,0,2))
arima_result = model.fit() # 训练
# 打印摘要
print(arima_result.summary())

预测结果,同时还原差分数据,可视化

python 复制代码
# 预测
forecast = arima_result.forecast(steps=len(test_diff))
# 还原为原始序列
predictions_original = []
    
# 获取最后period年的数据作为基准
period = 11
last_period_values = df_sun.loc['1987':'1997']

for i in range(len(forecast)):
    # 获取对应位置的上一个周期值
    base_value = last_period_values.iloc[(i+1) % period]
    # 还原周期性差分: y_t = y'_t + y_{t-period}
    pred_original = forecast.iloc[i] + base_value
    predictions_original.append(pred_original)

pred_mean_original = pd.Series(predictions_original, index=forecast.index)


# 绘制结果
plt.figure(figsize=(12,6))
#plt.plot(df_sun,label="Original Data")
plt.plot(train_diff.index,train_diff,label="Train Data")
plt.plot(test_diff.index,test_diff,color='orange',label="Test Data")
plt.plot(pred_mean_original.index + 1700+period,pred_mean_original,color='red',label="Predicted Values")
plt.title("Fitting and Prediction of ARIMA(1,0,2) Model")
plt.legend()
plt.show()
相关推荐
乾元11 分钟前
LLM 自动生成安全基线与等保合规初稿——把“网络工程事实”转译为“可审计的制度语言”
运维·网络·人工智能·python·安全·架构
全栈陈序员13 分钟前
【Python】基础语法入门(二十四)——文件与目录操作进阶:安全、高效地处理本地数据
开发语言·人工智能·python·学习
是有头发的程序猿16 分钟前
Python爬虫实战:面向对象编程构建高可维护的1688商品数据采集系统
开发语言·爬虫·python
摸鱼仙人~19 分钟前
企业级 RAG 问答系统开发上线流程分析
后端·python·rag·检索
serve the people25 分钟前
tensorflow tf.nn.softmax 核心解析
人工智能·python·tensorflow
癫狂的兔子33 分钟前
【BUG】【Python】eval()报错
python·bug
啃火龙果的兔子34 分钟前
java语言基础
java·开发语言·python
masterqwer34 分钟前
day42打卡
python
不会飞的鲨鱼36 分钟前
抖音验证码滑动轨迹原理(很难审核通过)
javascript·python
我命由我1234536 分钟前
Python 开发问题:No Python interpreter configured for the project
开发语言·后端·python·学习·pycharm·学习方法·python3.11