目录
一、前期准备
本次模型的构建与预测都是用的是python进行,其中涉及多个库:
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.graphics.api import qqplot
import warnings
import os
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.tsa.holtwinters import SimpleExpSmoothing,ExponentialSmoothing
from sklearn.metrics import mean_squared_error
以上各个库的作用介绍不是本文主要内容不过多解释,请自行了解。
本次实践通过三阶指数平滑的方法对十列金融数据集进行模型的拟合与预测。
二、数据来源与样式
本次实验数据为十列金融数据集,该数据集为时间序列,且相互之间独立,因此需要对十个时间序列分别进行拟合与预测。
图1 数据样式
三、数据的预处理
(一)表格处理
由于时间序列数据之间相互独立,为了便于建模预测在导入之前我在excel表格上对数据进行了简单的处理。
图2 处理后表格
我将十个时间序列数据分别放入十个sheet里,并且以年份命名,以便于代码导入数据。再者,由于原始时间序列数据没有时间列,因此我按照个人理解添加了时间列,每一个数据都来自每月的一月一日,因此其周期可分为12、6、4、3等。
(二)数据导入
通过pandas的相关方法导入excel数据。设置好相关参数,这样在pycharm里显示数据时就不会出现过多数据而省略部分数据的情况。
#处理warning
warnings.filterwarnings("ignore") #有时候代码处于某些原因会飘红却不影响正常的运行,为了美观使用该代码进行忽视处理
#作图显示中文字符
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False
#展示所有列表文件
pd.set_option('display.max_columns',1000)
pd.set_option("display.width",1000)
pd.set_option('display.max_colwidth',1000)
pd.set_option('display.max_rows',1000)
datax=pd.read_excel(r'D:\杂货\金融数据集合.xlsx',sheet_name=None)
在python中导入excel的方法在基础操作篇有介绍,不再赘述,详情可看本篇文章:
【Python处理EXCEL】基础操作篇:在Python中导入EXCEL数据
(三)数据处理
为方便后续选择数据以及代码参数的调整,我将“日期”列设置为行索引,通过以下代码实现。
#将excel中的“日期”一列设置为行索引
data=data.set_index('日期')
data.index=pd.to_datetime(data.index)
四、模型构建(指数平滑)
(一)数据作图
在此以1964年金融数据集为例。首先导入数据并作出原始数据的折线图。数据可视化的最基础的知识可以看我先前写的文章:
【Python数据分析】实践编写篇1:用Python程序完成描述性统计分析需求https://blog.csdn.net/Deng333333555/article/details/125697176?spm=1001.2014.3001.5501
该篇末尾对数据可视化作了简单的教学,但是只是冰山一角,想要深入学习数据可视化的朋友可以在网上查看其他的教程,也可以关注本人,后续会出使用的可视化教学篇。
#原始数据作图
fig=plt.figure(figsize=(15,8))#作图面积的大小
ax1=plt.subplot(1,1,1)#作图的位置
plt.xticks(fontsize=20)#设置x轴刻度的字体大小
plt.yticks(fontsize=20)#设置y轴刻度的字体大小
plt.xlabel('日期',fontsize=35,color='blue')#设置x轴的标签,蓝色部分字
plt.ylabel('金融数据',fontsize=35,color='blue')#设置y轴的标签,蓝色部分字
plt.title('原始数据1964',fontsize=35)#设置图的标题
x1=data.index #将先前设置的日期行索引设置为坐标轴的横轴
y=data.iloc[:,0] #利用提取excel列的方法,提取了金融数据集第一列的数据作为y轴
plt.plot(x1,y,linewidth=4.0,label='真实线',color='orange')#绘制坐标及数据,并设置一些参数
plt.legend(fontsize=20)#设置图例,即图中左上角那个
plt.show()#绘图
图3 1964年金融数据折线图
由上图可看到该年金融数据的基本趋势。
(二)观察季节性与趋势
通过程序作出以下季节性及趋势图,我将周期设置为12,原因在开篇提及,不再复述。
decomposition=seasonal_decompose(data.iloc[:,0],model='addictive',period=12)
decomposition.plot()
plt.show()
#关于seasonal_decompose()方法的参数
seasonal_decompose(x,model='additive',filt=None,period=None,two_sided=True,extrapolate_trend=0)
x | 时间序列。 如果是两维的,则单个Series应该在一列中。 x 必须包含 2 个完整的周期。 |
model | {“additive”, “multiplicative”} 时间序列分解的类型(加和 or 求乘积) 参数名称缩写是允许的 |
filt | 过滤掉季节性分量的过滤系数。 滤波中使用的具体移动平均法(单边or两侧)由 two_sided确定。 (个人理解是,计算滑动平均时,滑动平均阶数内各点所乘的那个系数) |
period | 时间序列的周期。 如果 x 不是 pandas 对象或 x 的索引没有频率,则必须使用。 如果 x 是具有时间序列索引的 pandas 对象,则覆盖 x 的默认周期。 |
two_sided | 滤波中使用的移动平均法。 如果为 True(默认),则使用 filt 计算居中移动平均值。 如果为 False,则滤波器系数filt仅适用于过去的值。 |
extrapolate_trend | 如果设置为 > 0,考虑到这么多 (+1) 个最近点,移动平均(卷积)产生的趋势是在两端外推的线性最小二乘法(如果 two_lateral 为 False,则为单侧外推)。如果设置为“freq”,则使用最近点。设置此参数会导致趋势或残差组件中没有 NaN 值。 |
(方法的介绍来源于网络)
图4 季节性及趋势图
从趋势图中看到,自1965到1967年期间数据有较大幅度的上涨,波动较大。而在1967到1971年间趋势波动小,变动较为平。1971年至1972年又出现了较大的波动。从季节性图来看,数据的变化在12期的情况下出现了周期波动的情况,说明该金融数据存在季节性趋。残差图则说明了数据中随机误差的产生是呈现正态分布的,并且期分布随机、不可预测。因此以该数据来构建预测模型是具有可行性的。
(三)一阶指数平滑
以下是一阶模型拟合数据的代码:需要注意的是,拟合的时候需要在最后加上 .fittedvalues 。
datasmooth1= SimpleExpSmoothing(data.iloc[:,0]).fit().fittedvalues
print(datasmooth1)
以下是一阶平滑拟合的可视化代码及图:
plt.figure(figsize=(15,8))
plt.title('一阶平滑1964',fontsize=40)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.xlabel('日期',fontsize=35,color='blue')
plt.ylabel('金融数据',fontsize=35,color='blue')
datasmooth1.plot(color='green',linewidth=3.0,label='拟合线') #将一阶平滑的结果画折线图
data.iloc[:,0].plot(color='black',linewidth=3.0,label='真实线')#将原始数据画折线图
plt.legend(fontsize=20)
plt.show()
图5 一阶平滑结果可视化
由图可以看出一阶指数平滑下模型的拟合效果很差,没有拟合出原始数据的波动与趋势,因此一阶指数平滑不可用。
(四)二阶指数平滑
以下是二阶模型拟合数据的代码:
datasmooth2= ExponentialSmoothing(data.iloc[:,0], trend="add", seasonal=None).fit().fittedvalues#添加trend效果
datasmooth2_2 = ExponentialSmoothing(data.iloc[:,0], trend="mul", seasonal=None).fit().fittedvalues#非添加trend效果
print(datasmooth2)
print(datasmooth2_2)
以下是二阶平滑拟合的可视化代码及图:
plt.figure(figsize=(15,8))
plt.title('二阶平滑1964',fontsize=40)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.xlabel('日期',fontsize=35,color='blue')
plt.ylabel('金融数据',fontsize=35,color='blue')
datasmooth2.plot(color='green',linewidth=3.0,label='line_add')#添加trend效果折线图
datasmooth2_2.plot(color='red',linewidth=3.0,label='line_mul')#非添加trend效果折线图
data.iloc[:,0].plot(color='black',linewidth=3.0,label='line_real')#真实线图
plt.legend(fontsize=20)
plt.show()
图6 二阶平滑结果可视化
由图看出,二阶平滑区分为真实线、加入趋势效应、未加入趋势效应三条线,其中加入趋势效应的曲线的拟合效果更加贴近真实线。但总的来看,二阶指数平滑的拟合效果仍然很差,无法拟合出原始数据的波动与趋势。
(五)三阶指数平滑
以下是三阶模型拟合数据的代码:
datasmooth3 = ExponentialSmoothing(data.iloc[:,0], trend="add", seasonal="add", seasonal_periods=12).fit().fittedvalues#添加seasonal效果
datasmooth3_2 = ExponentialSmoothing(data.iloc[:,0], trend="mul", seasonal="mul", seasonal_periods=12).fit().fittedvalues#非添加seasonal效果
print(datasmooth3)
print(datasmooth3_2)
以下是二阶平滑拟合的可视化代码及图:
plt.figure(figsize=(15,8))
plt.title('三阶平滑'+num,fontsize=40)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.xlabel('日期',fontsize=35,color='blue')
plt.ylabel('金融数据',fontsize=35,color='blue')
datasmooth3.plot(color='green',linewidth=3.0,label='line_add')#添加seasonal效果折线图
datasmooth3_2.plot(color='red',linewidth=3.0,label='line_mul')#非添加seasonal效果折线图
data.iloc[:,0].plot(color='black',linewidth=3.0,label='line_real')#原始数据折线图
plt.legend(fontsize=20)
plt.show()
图7 三阶平滑结果可视化
由图可以看出,三阶指数平滑后的结果与原始数据更为贴近,反映出了原始数据的波动及趋势。图中的三条线分别为原始数据线、加入季节效应线、未加入季节效应线,而加入了季节效应的曲线效果与原始数据更加接近。综合以上,三阶指数平滑的效果相对一阶指数平滑以及二阶指数平滑的表现更良好。
(六)均方误(MSE)比较
以上我做了三个模型,分别为一阶指数平滑、二阶指数平滑和三阶指数平滑,为了从这三者之间选择哪一个模型效果最优,那么需要有一个标准来进行比较,而通常会选择比较三个模型的均方误(MSE)来进行选择。代码如下:
from sklearn.metrics import mean_squared_error
datasmooth1= SimpleExpSmoothing(data.iloc[:,0]).fit().fittedvalues#一阶指数平滑拟合结果
datasmooth2= ExponentialSmoothing(data.iloc[:,0], trend="add", seasonal=None).fit().fittedvalues#二阶指数平滑拟合结果
datasmooth3 = ExponentialSmoothing(data.iloc[:,0], trend="add", seasonal="add", seasonal_periods=12).fit().fittedvalues#三阶指数平滑拟合结果
mse_1 = mean_squared_error(datasmooth1,data.iloc[:,0])#一阶指数平滑的均方误
mse_2 = mean_squared_error(datasmooth2,data.iloc[:,0])#二阶指数平滑的均方误
mse_3 = mean_squared_error(datasmooth3,data.iloc[:,0])#三阶指数平滑的均方误
print(mse_1)
print(mse_2)
print(mse_3)
表1 均方误计算结果 | ||
均方误(MSE) | ||
一阶指数平滑 | 二阶指数平滑 | 三阶指数平滑 |
140960.991 | 155824.614 | 47311.015 |
由上表可知,三界指数平滑的均方误是最小的,且都远小于一阶与二阶情况下的均方误,说明三界指数平滑的拟合效果要优于一阶指数平滑与二阶指数平滑。总体来看,三者的均方误都很大,在模型的拟合与预测中仍然存在较大的偏差,但相对于ARMA模型来说其拟合效果已经有很大的提升。
(七)正态性检验
正态性检验代码与图如下:
resid=model.resid#先计算出数据的残差
#以下再进行作图
fig=plt.figure(figsize=(8,6))
ax=fig.add_subplot(1,1,1)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.xlabel('Theoretical Quantiles',fontsize=35,color='blue')
plt.ylabel('Sample Quantiles',fontsize=35,color='blue')
plt.title('正态性检验1964',fontsize=20)
fig=qqplot(resid,line='q',ax=ax,fit=True)
plt.show()
图8 正态性检验qq图
由qq图来看,该模型数据通过正态性检验,符合正态性分布,说明我先前的判断合理。综合以上考虑,我决定使用三阶指数平滑方法来构建模型并预测未来18期的金融数据。
五、数据预测
预测数据的代码如下:
(需要注意的是:在进行预测时,第一行代码与拟合时不一样,最后部分没有 .fittedvalues 。)
通过forecast()方法进行未来十八期数据的预测,其中该方法中的数字18则是说明要预测未来18期的数据。
model = ExponentialSmoothing(data.iloc[:,0], trend="add", seasonal="add", seasonal_periods=12).fit()
pred = model.forecast(18)
print(pred)
表2 未来18期预测数据 | |
日期 | 预测数据 |
1973-01-01 | 7285.228568 |
1973-02-01 | 6915.147716 |
1973-03-01 | 7793.479060 |
1973-04-01 | 7143.713913 |
1973-05-01 | 7379.143971 |
1973-06-01 | 7104.064988 |
1973-07-01 | 6545.559722 |
1973-08-01 | 6991.558418 |
1973-09-01 | 6986.912414 |
1973-10-01 | 7799.023518 |
1973-11-01 | 7221.405211 |
1973-12-01 | 6921.526282 |
1974-01-01 | 7199.852791 |
1974-02-01 | 6829.771939 |
1974-03-01 | 7708.103282 |
1974-04-01 | 7058.338135 |
1974-05-01 | 7293.768194 |
1974-06-01 | 7018.689211 |
上表是由模型预测出的未来18期的金融数据。
预测图代码如下:
plt.figure(figsize=(15,8))
plt.title('最终预测结果1964',fontsize=40)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.xlabel('日期',fontsize=35,color='blue')
plt.ylabel('金融数据',fontsize=35,color='blue')
pred.plot(color='red',linewidth=3.0,label='预测线')#预测数据折线图
data.iloc[:,0].plot(color='black',linewidth=3.0,label='真实线')#实际数据折线图
plt.legend(fontsize=20)
plt.show()
图9 预测结果可视化
由上图看出,预测数据的波动以及变化趋势与原始数据较为吻合,认为其具有一定的合理性。
六、总结
以上的建模与预测只以1964年金融数据集为例,而其他数据集在我进行建模过程中表现出的特性与该例子的数据集相似,因此使用三阶指数平滑的方法在十个数据集中都能够行得通。
七、完整代码
由于总体需要预测的数据有10列,而我在数据处理时将十列数据分别放入同一个表格中的不同Sheet,在本文开头已有说明,因此完整代码中我加入了一个简单的循环与函数来遍历不同的sheet来分别预测不同的数据集·。同时也出于个人的需要,我要将代码输出的数据写入一个word文件中,因此在完整代码中可以看到print()函数里面会接一个file=wordfile参数,这个就是将输出print到我指定的word文件中,同时我还需要将作出的图片输出到指定的文件夹当中,因此在作图的最后我会添加一行plt.savefig()样式的代码,这个对于作图可有可无,纯看个人的需求。
其次,三阶指数平滑并不是该数据集的最优的拟合与预测方法,只是相对其他模型而言实现相对简单,若有更高的预测精度需求可自行尝试构建其他模型。
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.graphics.api import qqplot
import warnings
import os
#处理warning
warnings.filterwarnings("ignore")
#作图显示中文字符
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False
#展示所有列表文件
pd.set_option('display.max_columns',1000)
pd.set_option("display.width",1000)
pd.set_option('display.max_colwidth',1000)
pd.set_option('display.max_rows',1000)
datax=pd.read_excel(r'D:\杂货\金融数据集合.xlsx',sheet_name=None)#导入表格中的所有sheet
datasets=['1998','2001','1996','1981','1982','1974','1976','1972','1984','1964']#待循环的十个数据
def timeda_3(num):
data=pd.read_excel(r'D:\杂货\金融数据集合.xlsx',sheet_name=num)
data=data.set_index('日期')
data.index=pd.to_datetime(data.index)
#原始数据作图
fig=plt.figure(figsize=(15,8))
ax1=plt.subplot(1,1,1)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.xlabel('日期',fontsize=35,color='blue')
plt.ylabel('金融数据',fontsize=35,color='blue')
plt.title('原始数据'+num,fontsize=35)
x1=data.index
y=data.iloc[:,0]
plt.plot(x1,y,linewidth=4.0,label='真实线',color='orange')
plt.legend(fontsize=20)
plt.savefig(os.path.join(r'C:\Users\Lenovo\Desktop\商业数据挖掘大作业\金融数据集图片', '原始数据图'+num))#用于将图片保存到指定的文件夹中
plt.show()
#作季节性图
from statsmodels.tsa.seasonal import seasonal_decompose
decomposition=seasonal_decompose(data.iloc[:,0],model='addictive',period=12)
decomposition.plot()
plt.savefig(os.path.join(r'C:\Users\Lenovo\Desktop\商业数据挖掘大作业\金融数据集图片', '季节性图' + num))
plt.show()
#一阶平滑
from statsmodels.tsa.holtwinters import SimpleExpSmoothing,ExponentialSmoothing
datasmooth1= SimpleExpSmoothing(data.iloc[:,0]).fit().fittedvalues
print('一阶平滑结果:\n',datasmooth1,file=wordfile)
plt.figure(figsize=(15,8))
plt.title('一阶平滑'+num,fontsize=40)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.xlabel('日期',fontsize=35,color='blue')
plt.ylabel('金融数据',fontsize=35,color='blue')
datasmooth1.plot(color='green',linewidth=3.0,label='拟合线')
data.iloc[:,0].plot(color='black',linewidth=3.0,label='真实线')
plt.legend(fontsize=20)
plt.savefig(os.path.join(r'C:\Users\Lenovo\Desktop\商业数据挖掘大作业\金融数据集图片', '一阶平滑图' + num))
plt.show()
#一阶平滑均方误
datasmooth1= SimpleExpSmoothing(data.iloc[:,0]).fit().fittedvalues
from sklearn.metrics import mean_squared_error
mse_1 = mean_squared_error(datasmooth1,data.iloc[:,0])
print('一阶平滑均方误:',mse_1,file=wordfile)
#二阶平滑
datasmooth2= ExponentialSmoothing(data.iloc[:,0], trend="add", seasonal=None).fit().fittedvalues
datasmooth2_2 = ExponentialSmoothing(data.iloc[:,0], trend="mul", seasonal=None).fit().fittedvalues
print('二阶平滑结果(add):\n',datasmooth2,file=wordfile)
print('二阶平滑结果(mul):\n',datasmooth2_2,file=wordfile)
plt.figure(figsize=(15,8))
plt.title('二阶平滑'+num,fontsize=40)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.xlabel('日期',fontsize=35,color='blue')
plt.ylabel('金融数据',fontsize=35,color='blue')
datasmooth2.plot(color='green',linewidth=3.0,label='line_add')
datasmooth2_2.plot(color='red',linewidth=3.0,label='line_mul')
data.iloc[:,0].plot(color='black',linewidth=3.0,label='line_real')
plt.legend(fontsize=20)
plt.savefig(os.path.join(r'C:\Users\Lenovo\Desktop\商业数据挖掘大作业\金融数据集图片', '二阶平滑图' + num))
plt.show()
#二阶平滑均方误
datasmooth2= ExponentialSmoothing(data.iloc[:,0], trend="add", seasonal=None).fit().fittedvalues
from sklearn.metrics import mean_squared_error
mse_2 = mean_squared_error(datasmooth2,data.iloc[:,0])
print('二阶平滑均方误:',mse_2,file=wordfile)
#三阶平滑
datasmooth3 = ExponentialSmoothing(data.iloc[:,0], trend="add", seasonal="add", seasonal_periods=12).fit().fittedvalues
datasmooth3_2 = ExponentialSmoothing(data.iloc[:,0], trend="mul", seasonal="mul", seasonal_periods=12).fit().fittedvalues
print('三阶平滑结果(add):\n',datasmooth3,file=wordfile)
print('三阶平滑结果(mul):\n',datasmooth3_2,file=wordfile)
plt.figure(figsize=(15,8))
plt.title('三阶平滑'+num,fontsize=40)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.xlabel('日期',fontsize=35,color='blue')
plt.ylabel('金融数据',fontsize=35,color='blue')
datasmooth3.plot(color='green',linewidth=3.0,label='line_add')
datasmooth3_2.plot(color='red',linewidth=3.0,label='line_mul')
data.iloc[:,0].plot(color='black',linewidth=3.0,label='line_real')
plt.legend(fontsize=20)
plt.savefig(os.path.join(r'C:\Users\Lenovo\Desktop\商业数据挖掘大作业\金融数据集图片', '三阶平滑图' + num))
plt.show()
#三阶平滑均方误
datasmooth3 = ExponentialSmoothing(data.iloc[:,0], trend="add", seasonal="add", seasonal_periods=12).fit().fittedvalues
from sklearn.metrics import mean_squared_error
mse_3 = mean_squared_error(datasmooth3,data.iloc[:,0])
print('三阶平滑均方误:',mse_3,file=wordfile)
#预测三阶平滑模型数据
model = ExponentialSmoothing(data.iloc[:,0], trend="add", seasonal="add", seasonal_periods=12).fit()
pred = model.forecast(18)
print('三阶平滑预测结果数据:\n',pred,file=wordfile)
#qq图正态性检验
resid=model.resid
fig=plt.figure(figsize=(8,6))
ax=fig.add_subplot(1,1,1)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.xlabel('Theoretical Quantiles',fontsize=35,color='blue')
plt.ylabel('Sample Quantiles',fontsize=35,color='blue')
plt.title('正态性检验'+num,fontsize=20)
fig=qqplot(resid,line='q',ax=ax,fit=True)
plt.savefig(os.path.join(r'C:\Users\Lenovo\Desktop\商业数据挖掘大作业\金融数据集图片', '正态性检验qq图' + num))
plt.show()
#预测图
plt.figure(figsize=(15,8))
plt.title('最终预测结果'+num,fontsize=40)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.xlabel('日期',fontsize=35,color='blue')
plt.ylabel('金融数据',fontsize=35,color='blue')
pred.plot(color='red',linewidth=3.0,label='预测线')
data.iloc[:,0].plot(color='black',linewidth=3.0,label='真实线')
plt.legend(fontsize=20)
plt.savefig(os.path.join(r'C:\Users\Lenovo\Desktop\商业数据挖掘大作业\金融数据集图片', '预测数据图' + num))
plt.show()
print('-------------','以上为',num,'年的数据','------------',file=wordfile)
if __name__=='__main__':
wordfile = open(r'C:\Users\Lenovo\Desktop\商业数据挖掘大作业\数据输出.docx', 'w')#打开一个word文档,只有打开了才能写入
for num in datasets:
timeda_3(num)
wordfile.close()#写完word文档后要关闭,才能保存。
print('运行完毕')
文章出处登录后可见!