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()
相关推荐
2301_795167201 小时前
Python 高手编程系列一十五:使用 __new __()方法覆写实例创建过程
开发语言·网络·python
轻竹办公PPT1 小时前
AI一键生成年终总结PPT
人工智能·python·powerpoint
是Dream呀1 小时前
昇腾平台 PyTorch 迁移实操:从环境搭建到精度达标的完整步骤
人工智能·pytorch·python·昇腾
lxmyzzs1 小时前
【图像算法 - 36】医疗应用:基于 YOLOv12 与 OpenCV 的高精度脑肿瘤检测系统实现
python·深度学习·opencv·yolo·计算机视觉·脑肿瘤检测
工藤学编程1 小时前
零基础学AI大模型之Milvus实战:Attu可视化安装+Python整合全案例
人工智能·python·milvus
学不完了是吧1 小时前
“小白专属”python字符串处理文档
开发语言·python
☆光之梦☆1 小时前
《openGauss全密态与防篡改账本数据库:云上数据安全与可信的新范式》
数据库·python
Maya动画技术1 小时前
python的py转pyd方法(cython)
开发语言·python·spring
找了一圈尾巴1 小时前
Python 学习-深入理解 Python 进程、线程与协程(上)
python·学习·并发