基于ARIMA+SARIMA的航空公司 RPM 时间序列预测模型

 3f6a7ab0347a4af1a75e6ebadee63fc1.gif

🤵‍♂️ 个人主页:@艾派森的个人主页

✍🏻作者简介:Python学习者
🐋 希望大家多多支持,我们一起进步!😄
如果文章对你有帮助的话,
欢迎评论 💬点赞👍🏻 收藏 📂加关注+


目录

1.项目背景

2.数据集介绍

3.技术工具

4.实验过程

4.1导入数据

4.2数据预处理

4.3探索性分析

4.4特征工程

4.5构建模型


 

1.项目背景

        评估航空公司的运输量和运输需求。因此,对航空公司 RPM 的时间序列预测具有重要的商业意义,可以帮助航空公司进行运输规划、航班安排和市场营销决策。

        在航空业中,因素如季节性、节假日、燃油价格、宏观经济状况等都会对乘客需求产生影响,这些影响因素使得航空公司 RPM 的时间序列数据呈现出一定的复杂性和波动性。为了更准确地预测航空公司 RPM,传统的统计方法往往无法很好地处理这些复杂的时间序列特征,因此需要更加先进和灵活的预测模型。

        ARIMA (AutoRegressive Integrated Moving Average) 模型是一种经典的时间序列分析方法,可以用于捕捉时间序列数据的自相关和趋势性。SARIMA (Seasonal AutoRegressive Integrated Moving Average) 则是在 ARIMA 模型基础上加入季节性因素的模型,能够更好地处理具有季节性变动的时间序列数据。

        基于ARIMA+SARIMA的航空公司 RPM 时间序列预测模型,将结合ARIMA和SARIMA模型的优势,综合考虑航空公司 RPM 时间序列数据的趋势性和季节性变动,从而提高预测的准确性和可靠性。这种模型可以帮助航空公司更好地了解和预测未来乘客需求,有助于他们进行合理的运输资源配置和市场策略制定。

2.数据集介绍

        数据集来源于Kaggle,原始数据集共有249条,17个变量。

关于此文件

2003 年 1 月至 2023 年 9 月美国所有商业航空公司的非季节性调整每月航空交通数据。

注:

收入乘客里程 = 乘客数量和飞行距离,以千 (000) 为单位

可用座位里程 = 座位数和飞行距离,以千 (000) 为单位

负载系数 = 乘客里程占可用座位的比例- 英里数百分比 (%)

3.技术工具

Python版本:3.9

代码编辑器:jupyter notebook

4.实验过程

4.1导入数据

导入第三方库并加载数据集

import pandas as pd
import matplotlib.pyplot as plt 
import matplotlib.dates as mdates
import seaborn as sns
import numpy as np
import statsmodels.api as sm
import warnings
warnings.filterwarnings("ignore")

df= pd.read_csv('air traffic.csv')
df.head()

e2049a0fa26946f8ac297ba97235d731.png

查看数据大小

5210be6661eb4b4eb3c84919e36c3fe2.png

查看数据基本信息

ca0b3049180642efb14628fa0cebdaa4.png

4.2数据预处理

将非数值型变量转换为数值型变量

# 数据类型转换
for col in df.columns:
    if df[col].dtype == 'object': 
        df[col] = pd.to_numeric(df[col].str.replace(',', ''), errors='coerce')
df.info()

01d30b4a180b4234ba7da8b199742e30.png

# 由于我们正在做时间序列预测,现在让我们处理月份和年份列
# 这里我们将合并月份和年份,同时将日期设置为每月的第一天
df['Date'] = pd.to_datetime({'year': df['Year'], 'month': df['Month'], 'day': 1})
# 我们把日期设为索引
df.set_index('Date')

1f3c07804f0a4ce097d22ebc9c84be60.png

4.3探索性分析

# 让我们看看所有变量相对于日期的趋势
all_columns = [col for col in df.columns if col != 'Date']
for column in all_columns:
    plt.figure(figsize=(10, 4))  
    sns.lineplot(data=df, x='Date', y=column)
    plt.title(column)
    plt.xticks(rotation=45)
    plt.tight_layout()
    plt.show()

7a5a422c9e384f7bb54e6bb9924a2a50.png

94119447a22d431db8aca2adba7f33ee.png

9b152d6c7f5f490db5a77b085f89747e.png

49af798da4e04733ba3a1589d2100212.png

09b166c537d44d55b8b831f4ff93942c.png

c14e601e88f948e2a7e7b4dcba712d2f.png

2db5f1c18e4740bcb8fcb681cfb3ca95.png

ab5c087075ca49579f1142e78b0b1f6e.png

0fefbbfccabf4f80bb8cf7879002820c.png

4bf07547dad14bf4a63d2950ecb4cfea.png

f3116824bef346cda37b356321b464b8.png

22269a5200454831817f03c7c5a59109.png

新冠疫情的影响非常明显。

对于这个时间序列项目,我们将预测Dom_RPM:国内收入乘客里程数,这是航空公司流量的度量,计算付费乘客在国内航班上飞行的里程数。这是航空公司的一项关键绩效指标,显示了国内付费旅客的数量。

4.4特征工程

拆分数据集为训练集和测试集

# 我们来做一个新的更简单的df
new_df = df[['Date', 'Dom_RPM']].copy()
new_df['Date'] = pd.to_datetime(new_df['Date'])
new_df.set_index('Date', inplace=True)
# 将数据分成训练集和测试集
split_date = pd.to_datetime('2023-01-01')  
train = new_df.loc[:split_date]  
test = new_df.loc[split_date:]   
plt.figure(figsize=(12,6))
plt.plot(new_df.index, new_df['Dom_RPM'], label='Dom_RPM')
plt.axvline(x=split_date, color='red', linestyle='--', label='Train-Test Split')
plt.legend()
plt.show()

c121b3c9d48a44f4b2dd643d81f2f800.png

平稳性检验

# 平稳性检验
from statsmodels.tsa.stattools import adfuller
st_train= adfuller(train['Dom_RPM'], autolag='AIC')
print(st_train)

dfeabc1147f14ee4b947e3bc25cc4584.png

预差分结果表明,在5%显著性水平下,时间序列可能是平稳的,因为ADF统计量(-3.4227)更接近5%临界值(-2.8744),p值(0.0102)小于0.05,表明在该水平下可以拒绝单位根的原假设。

plt.figure(figsize=(12, 6))
plt.subplot(2, 1, 1)
pd.plotting.autocorrelation_plot(train['Dom_RPM'])
plt.title(f"Autocorrelation Plot for 'Training Set'")
plt.show()

3b21da16617f44db824ee9ee3c742f2f.png

# 差分训练数据
train['Dom_RPM_diff'] = train['Dom_RPM'].diff().shift(-1)
train.dropna(inplace=True)
# 检查差分后的ADF结果
diff_st_train= adfuller(train['Dom_RPM'], autolag='AIC')
print(diff_st_train)

5d572f420d4c4e6d9bcd38d4c57ab050.png

差异后,ADF统计量略负(-3.5346),p值进一步下降至0.0071,增强了非平稳性原假设的证据。差异后的ADF统计量仍然比5%临界值更负,并且非常接近1%临界值,这表明即使在更严格的显著性水平下,也有更强的平稳性证据。

from statsmodels.tsa.arima.model import ARIMA
import statsmodels.api as sm
plt.plot(train.index, train['Dom_RPM_diff'])
plt.xlabel('Date')
plt.ylabel('Domestic RPM')
plt.xticks(rotation=45)
plt.show()

27bcbb02840b42e181d8952e386e9596.png

4.5构建模型

这里我做了一个基本的ARIMA模型

order = (1, 1, 1)
arima_model = sm.tsa.arima.ARIMA(train['Dom_RPM_diff'].dropna(), order=order)  
fitted = arima_model.fit()
print(fitted.summary())

a74c2467b9114383b885a7121c7be328.png

        -4058.061的对数似然和AIC(8122.122)、BIC(8132.552)和HQIC(8126.325)等选择标准表明,该模型在捕获动态方面相对有效。自回归项(ar.L1)在-0.0813处没有统计学意义(p值:0.116),这意味着它可能对模型没有意义,而移动平均项(ma.L1)在-0.9967处非常显著(p值:0.000),表明对模型有很强的影响。误差方差(sigma2)很大,为4.056e+13,表明存在大量无法解释的变异。

        Ljung-Box检验(p值:0.92)表明不存在自相关问题,但Jarque-Bera检验表明残差不遵循正态分布(p值:0.00),异方差检验指向不同的残差方差(p值:0.00),表明潜在的模型规范问题。此外,残差的偏度(-0.82)和高峰度(7.85)进一步质疑了模型的假设。

# 分解时间序列以观察季节模式
decomposition = sm.tsa.seasonal_decompose(train['Dom_RPM'], model='additive', period=12)  
decomposition.plot()
plt.show()

# ACF和PACF图
fig, (ax1, ax2) = plt.subplots(2,1, figsize=(12,8))
sm.graphics.tsa.plot_acf(train['Dom_RPM'], lags=40, ax=ax1)  
sm.graphics.tsa.plot_pacf(train['Dom_RPM'], lags=40, ax=ax2)  
plt.show()

c25f1f1c793b440fb4db11d82c7555d1.png

e4c4405c7dd541d1a87a82adec0e515b.png

现在准备好运行SARIMA模型,我们必须指定季节性和非季节性组件。

# 非季节性组件 
p = 1 # number of lag observations in the model (AR term)
d = 1 # the number of times that the raw observations are differenced (I term)
q = 0  # the size of the moving average window (MA term)

# 季节性组件
P = 0  # seasonal autoregressive order
D = 1  # essasonal differencing order
Q = 1  # seasonal moving average order
s = 12 # the number of periods in a season
seasonal_order = (P, D, Q, s)

sarima_model = sm.tsa.statespace.SARIMAX(train['Dom_RPM'],
                                          order=(p, d, q),
                                          seasonal_order=seasonal_order,
                                          enforce_stationarity=False,
                                          enforce_invertibility=False)

fitted_sarima = sarima_model.fit()
print(fitted_sarima.summary())

fffbbb34c0ce4ec69ab1eabceab98c59.png

        使用L-BFGS-B算法对SARIMAX模型进行了成功的优化,经过8次迭代后,模型拟合得到了显著改善,最终函数值为14.78。该SARIMAX模型突出了数据中的非季节性和季节性动态,自回归项(0.3927)和季节移动平均项(-0.6053)的系数都很显著,表明存在显著的自回归效应和强烈的季节性影响。模型的拟合良好度指标,包括对数似然值为-3547.654,以及AIC(7101.307)、BIC(7111.405)和HQIC(7105.388)等标准,为评估模型性能提供了依据,值越低通常表示拟合越好。然而,诊断试验显示残差分布的正态性和异方差的证据。

检查模型的残差

residuals = fitted_sarima.resid

# 绘制残差
plt.figure(figsize=(10,6))
plt.plot(residuals)
plt.title('Residuals from SARIMA Model')
plt.axhline(y=0, color='black', linestyle='--')
plt.show() # 绘制残差的ACF和PACF
fig, ax = plt.subplots(1,2,figsize=(15,4))
sm.graphics.tsa.plot_acf(residuals, lags=40, ax=ax[0])
sm.graphics.tsa.plot_pacf(residuals, lags=40, ax=ax[1])
plt.show()

c149a1c2fdf94e6eaf1dfc78189f33a4.png

76247dc7d82e4d8599519bd3d18e4b72.png

# 直方图和核密度估计
residuals.plot(kind='hist', density=True, bins=30, alpha=0.5)
residuals.plot(kind='kde')
plt.title('Histogram and Density Plot of Residuals')
plt.show()

59df1432748d434a8130238c15eda332.png

# Q-Q图检查正态性
sm.qqplot(residuals, line='s')
plt.title('Q-Q Plot of Residuals')
plt.show()

ce7a4479d91d4e6b94e376927382c5d2.png

# 正态性的统计检验
from scipy import stats
jb_test = stats.jarque_bera(residuals)
print(f'Jarque-Bera test statistics: {jb_test[0]}, p-value: {jb_test[1]}')

62c033a427214ce4b81d90f8d9f2529b.png

JB检验统计量非常高,p值为0,说明数据不服从正态分布。

# 零均值的统计检验
mean_test = stats.ttest_1samp(residuals, 0)
print(f'Test Statistic: {mean_test.statistic}, p-value: {mean_test.pvalue}')

0f51c6274bff4ce4899d43159763f836.png

        由于检验统计量非常接近于0,p值非常高(0.919),因此有强有力的证据表明残差的平均值与0没有显著差异。实际上,这意味着在模型的残差中没有显著的偏差;也就是说,模型没有系统地高估或低估观测值。

# 自相关的统计检验
ljung_box_test = sm.stats.acorr_ljungbox(residuals, lags=[40], return_df=True)
print(ljung_box_test)

41c4620ce7774d4383ae655dcc446e88.png

Ljung-Box检验检查所有滞后至40的自相关,并检验数据中没有自相关的零假设。相对较低的统计量和较高的p值表明,在40个滞后的模型残差中没有显著的自相关证据。

将假数据纳入SARIMA模型,以考虑COVID-19大流行对时间序列数据的影响。

covid_start = '2020-03-01'  
covid_end = '2021-09-01'    

# 为covid周期创建虚拟变量
train['covid_dummy'] = np.where((train.index >= covid_start) & (train.index <= covid_end), 1, 0)
# 带有外生变量(哑变量)的SARIMA模型
sarimax_model1 = sm.tsa.statespace.SARIMAX(train['Dom_RPM'],
                                           exog=train['covid_dummy'],
                                           order=(p, d, q),
                                           seasonal_order=(P, D, Q, s),
                                           enforce_stationarity=False,
                                           enforce_invertibility=False)

fitted_sarimax1 = sarimax_model1.fit()
print(fitted_sarimax1.summary())

a522552dda254c65ad3d241c6c0a7ed7.png

        采用L-BFGS-B算法进行优化,经过9次迭代后呈现收敛性,表明疫情对数据的负面影响显著,如covid dummy系数(-1.936e+07)。自回归分量和季节移动平均分量均具有显著的统计学意义,表明存在重要的时间动态。尽管各种统计指标(如AIC、BIC和对数似然)显示了成功的拟合,但诊断显示残差中没有显著的自相关,但突出了非正态分布和异方差的问题。

        这两个代码片段一起工作,构建了一种更复杂的方式来理解covid对数据的影响。在第一个代码片段中,我们计算了两件事:covid dummy,它告诉我们在特定时间是否发生了covid,以及covid impact,它衡量covid -19的影响如何随着时间的推移而减少。

        然后,在第二个代码片段中,我们使用这些计算来构建SARIMAX模型。该模型不仅考虑了COVID-19是否存在,还考虑了其影响如何随时间变化。

# 让我们再试一次,但要用衰变
covid_start = pd.to_datetime('2020-03-01')
covid_end = pd.to_datetime('2021-09-01')
# 计算自COVID-19开始以来的天数,并将其除以30转换为月份
train['months_since_covid'] = ((train.index - covid_start) / np.timedelta64(1, 'D') / 30).astype(int)
# 线性衰减
duration_in_months = ((covid_end - covid_start) / np.timedelta64(1, 'D') / 30)
train['covid_impact'] = train['months_since_covid'].apply(
    lambda x: max(1 - x / duration_in_months, 0) if x >= 0 else 0
)
sarimax_model_with_decay = sm.tsa.statespace.SARIMAX(
    train['Dom_RPM'],  
    exog=train[['covid_dummy', 'covid_impact']],
    order=(p, d, q),
    seasonal_order=(P, D, Q, s),
    enforce_stationarity=False,
    enforce_invertibility=False
)

fitted_sarimax_with_decay = sarimax_model_with_decay.fit()
print(fitted_sarimax_with_decay.summary())

0d54016cadb84ac9b4df48279619f889.png

        在解释中,带有这些系数的SARIMAX模型表明,covid -19时期对数据有很大的负面影响(显然……),covid - dummy表明显著降低。covid - 19影响系数表明,随着时间的推移,影响会减弱,这表明大流行的最初冲击正在逐步恢复。

        这是我们对未来12个月的预测。它首先生成一组未来日期,从训练数据中的最后一个日期开始,跨越未来12个月。然后,它创建两个数组,future_covid - dummy和future_covid - impact,来表示预测的外生变量。Future_covid_dummy假设未来不会有covid影响,因此在整个预测期内将所有值设置为0。而future_covid - impact则假设COVID-19的影响逐渐减弱,在12个月的预测期内从0.5下降到0。然后将这些数组组合成df future_exog,它将在进行预测时用作外生变量。

# 预测期数(未来12个月)
n_periods = 12
future_dates = pd.date_range(train.index[-1] + pd.offsets.MonthBegin(), periods=n_periods, freq='M')
# 假设新冠病毒开始消失,那么影响将从0.5降至0
future_covid_dummy = [0] * n_periods  
future_covid_impact = np.linspace(start=0.5, stop=0, num=n_periods)  #
# 组合成df
future_exog = pd.DataFrame({'covid_dummy': future_covid_dummy, 'covid_impact': future_covid_impact}, index=future_dates)
n_periods = 12  
forecast = fitted_sarimax_with_decay.get_forecast(steps=n_periods, exog=future_exog)
forecast_mean = forecast.predicted_mean
forecast_conf_int = forecast.conf_int()

下面是预测图

# 利用历史数据绘制预测图
plt.figure(figsize=(10, 6))
plt.plot(train.index, train['Dom_RPM'], label='Historical')
plt.plot(pd.date_range(train.index[-1], periods=n_periods, freq='M'), forecast_mean, label='Forecast')
plt.fill_between(pd.date_range(train.index[-1], periods=n_periods, freq='M'), forecast_conf_int.iloc[:, 0], forecast_conf_int.iloc[:, 1], color='red', alpha=0.3)
plt.legend()
plt.show()

76db9a82bb2b4af4afa17d4cace228bd.png

# 让我们把我们的预测和测试进行比较
plt.figure(figsize=(10, 6))
plt.plot(train.index, train['Dom_RPM'], label='Historical', color='blue')
plt.plot(forecast_mean.index, forecast_mean, label='Forecast', color='red')
plt.fill_between(forecast_conf_int.index, forecast_conf_int.iloc[:, 0], forecast_conf_int.iloc[:, 1], color='red', alpha=0.3)
plt.plot(test.index, test['Dom_RPM'], label='Actual Test Set', color='green')

plt.title('Comparison between the Test and Forecast')
plt.xlabel('Date')
plt.ylabel('Dom_RPM (in millions)')
plt.legend()
plt.show()

b30dcec26e8442d9892cd590d1a0322a.png

预测结果与实际测试数据有些接近。虽然与测试集相比,预测似乎被低估了。

资料获取,更多粉丝福利,关注下方公众号获取

a74f7d5d03234f7c8a635562034442a0.gif#pic_center

 

  • 91
    点赞
  • 78
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 71
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 71
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

艾派森

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值