基于时间序列的短期数据预测--ARMA模型的设计与实现(每个步骤附实现源码)

本文demo源码、实验数据:传送门

引言

前面我有分享两篇关于时间序列模型的文章,一篇是 Holt-Winters模型原理分析及代码实现(python)一篇是 LSTM模型分析及对时序数据预测的具体实现(python实现)。holt-winters是典型的平滑法,一般用于趋势分析和预测。LSTM是一种时间递归神经网络,适合于处理和预测时间序列中间隔和延迟相对较长的重要事件。最近实验室师兄的毕设论文需要用到某景区共享交通的租用量预测的算法,我帮忙协助实现了下,其中用到了一种新的时间序列模型,叫做ARMA模型,特总结如下。(本文涉及一些数理统计的概念及知识点,遇到不懂的请小伙伴们自行google)

AR、MA、ARMA模型简介

ARMA模型的全称是自回归移动平均模型,它是目前最常用的弥合平稳序列的模型。它又可以细分为AR模型、MA模型和ARMA三大类。都可以看作是多元线性回归模型。

AR

AR模型: 
具有如下结构的模型成为p阶自回归模型,简记为AR(p)。 

xt=ϕ0+ϕ1xt1+ϕ2xt2+...+ϕpxtp+εtxt=ϕ0+ϕ1xt−1+ϕ2xt−2+...+ϕpxt−p+εt

随机变量 XtXt的取值 xtxt是前p期 xt1,xt2,...,xtpxt−1,xt−2,...,xt−p的多元线性回归,认为 xtxt主要受过去p期的序列值影响。误差项是当前的随机干扰 εtεt,为零均值白噪声序列。

MA

MA模型: 
具有如下结构的模型称为q阶自回归模型,简记为MA(q)。 

xt=μ+εtθ1εt1θ2εt2...θqεtqxt=μ+εt−θ1εt−1−θ2εt−2−...−θqεt−q

即在t时刻的随机变量 XtXt的取值 xtxt是前q期的随机扰动 εt1,εt2,...,εtqεt−1,εt−2,...,εt−q的多元线性函数。误差项是当期的随机干扰 εtεt,为零均值白噪声序列, μμ是序列 {Xt}{Xt}的均值。认为 xtxt主要受过去q期的误差项影响。

ARMA

ARMA 
ARMA模型是最常用的平稳序列拟合模型,关于平稳序列的定义,请戳:平稳序列。ARMA模型的公式如下: 

xt=ϕ0+ϕ1xt1+ϕ2xt2+...+ϕpxtp+εtθ1εt1θ2εt2...θqεtqxt=ϕ0+ϕ1xt−1+ϕ2xt−2+...+ϕpxt−p+εt−θ1εt−1−θ2εt−2−...−−θqεt−q

随机变量 XtXt的取值 xtxt不仅与以前p期的序列值有关还与前q期的随机扰动有关。

基于时间序列的短期数据预测

接下来我将用数据挖掘的规范流程开启此次的探索之旅~

分析方法与过程

本文目的是预测某景区共享交通的租用情况,故可采用时间序列分析法来分析共享交通的历史租用情况,并预测未来几天内或几周内的租用情况,其挖掘建模的总体流程如下图所示。 
model_overview

数据抽取

我们从业务系统数据库抽取了时间段为2015-09-28~2017-10.16的数据,原始部分数据如下图所示: 
raw_data

数据探索与分析

原始数据转换成时序数据

原始数据是每发生一次租用行为就上传一天记录,为了生成每天的租用量,需要将数据按日做groupby,并求和,算出具体某一天的租用行为的次数,同时删除不必要的属性,代码如下:

def get_week_day(date):
  week_day_dict = {
    0 : '星期一',
    1 : '星期二',
    2 : '星期三',
    3 : '星期四',
    4 : '星期五',
    5 : '星期六',
    6 : '星期天',
  }
  day = date.weekday()
  return week_day_dict[day]
def loadDataSet(pathName):
    dataset = pd.read_excel(pathName)
    #to datetime
    dataset['开始时间'] = pd.to_datetime(dataset['开始时间'])
    dataset['开始时间'] = dataset['开始时间'].apply(lambda x: str(x)[:10])
    dataset['rentNumber'] = 1
    df = dataset.groupby('开始时间').aggregate('sum').reset_index()[['开始时间','rentNumber']]
    tmp = pd.to_datetime(df['开始时间'])
    week = [get_week_day(item) for item in tmp]
    df['week'] = list(week)
    return df
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

原始数据转换成时间序列数据如下所示: 
datalist

空缺值分析

我们分析数据库的数据,发现数据库里面空缺值并不会标记NaN或者-1,而是根本没有这条记录,于是我就生成了2015-09-28至2017-10-16的日期序列,两个序列对比,缺少的日期就是空缺的日期,代码如下:

def get_datelist(starttime,endtime):
  #get_datelist('20150811','20150922')
    startdate = datetime.datetime(int(starttime[0:4]),int(starttime[5:7]),int(starttime[8:10]))
    #now = datetime.datetime.now()
    delta = datetime.timedelta(days=1)
    # my_yestoday = startdate + delta
    # my_yes_time = my_yestoday.strftime('%Y%m%d')
    n = 0
    date_list = []
    while 1:
        if starttime<=endtime:
            days = (startdate  + delta*n).strftime('%Y-%m-%d')
            n = n+1
            date_list.append(days)
            if days == endtime:
                break
    data = pd.DataFrame({'开始时间':date_list})
    return data
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

空缺值实验结果如下图所示: 
空缺 
其中一共有32个空缺值,因为总共的日期有750天,32天只占其中一小部分,而且是分散在各个月,对实际预测影响不是很大,所以此处做丢弃处理。

历史数据时序图

这里我们画出了按天及按月统计租用次数的时序图 
按月统计次数

def month_data(dataset):
  dataset['开始时间']= dataset['开始时间'].apply(lambda x: str(x)[:7])
  df = dataset.groupby('开始时间').aggregate('sum').reset_index()[['开始时间','rentNumber']]
  return df
def plot_monthdata(df):
  df.rentNumber.plot(kind='bar',figsize=(16,4))
  plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

month_data
按天统计次数

def plot_monthdata(df):
  df.rentNumber.plot(kind='bar',figsize=(16,4))
  plt.show()
  • 1
  • 2
  • 3

day_data
当我分析数据,画出上面两个图形时,我震惊了!!!租用量看上去只有周期趋势,没有总体上升趋势!!!而且越往后,峰值越来越小。从我一开始的认知中,我想像租用量,销售量什么的,周期性变化我是赞同的,一般来说周末租用的多,节假日租用的人多,因为景区相应的人多;但是从一个企业的发展来说,租用量不是应该是一种总体上升的趋势么,至少应该像下面的这张图: 
gen
不仅有周期性,而且还有总体上升的趋势。。。 
分析到这里我心里就开始估计这个时序序列估计是个平稳时序即时间序列在某一常数附近波动且波动范围有限。

模型构建

本文共享交通租用量预测模型的建模流程如下图所示: 
model_predict
首先需要对观测值序列进行平稳性检测,如果不平稳,则对其进行差分运算直到差分后的数据平稳。在数据平稳后则对其进行白噪声检验,如果没有通过白噪声检验就进行模型识别,识别该模型属于AR,MA,ARMA的哪一种,如果是平稳非白噪声序列就计算ACF、PACF,进行ARMA模型识别,对已识别好的模型,确定模型参数,最后应用预测并进行误差分析。

平稳性检测

为了确定原始数据序列中有没有随机趋势或确定趋势,需要对数据进行平稳性检测,在检测之前,为了避免极大值的干扰,此时将租用量取一个对数。

def stationarity_test(dataset,number):
  data = dataset.copy()
  data = data.iloc[: len(data)-number] #不检测最后number个数据
  #平稳性检测
  from statsmodels.tsa.stattools import adfuller as ADF
  diff = 0
  adf = ADF(data['rentNumber'])
  while adf[1] > 0.05:
    diff = diff + 1
    adf = ADF(data['rentNumber'].diff(diff).dropna())
  print(u'原始序列经过%s阶差分后归于平稳,p值为%s' %(diff, adf[1]))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

检验结果: 
平稳性检测 
这个说明数据是平稳的时许序列,与我们前面的猜测一致!

白噪声检验

为了验证序列中是否是白噪声,如果是白噪声,那么此序列即都是随机扰动,无法进行预测和使用。

def whitenoise_test(dataset,number):
  data = dataset.copy()
  data = data.iloc[: len(data)-number] #不使用最后5个数据
#白噪声检测
  from statsmodels.stats.diagnostic import acorr_ljungbox
  [[lb], [p]] = acorr_ljungbox(data['rentNumber'], lags = 1)
  if p < 0.05:
    print(u'原始序列为非白噪声序列,对应的p值为:%s' %p)
  else:
    print(u'原始该序列为白噪声序列,对应的p值为:%s' %p)
  [[lb], [p]] = acorr_ljungbox(data['rentNumber'].diff().dropna(), lags = 1)
  if p < 0.05:
    print(u'一阶差分序列为非白噪声序列,对应的p值为:%s' %p)
  else:
    print(u'一阶差分该序列为白噪声序列,对应的p值为:%s' %p)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

检验结果: 
白噪声 
检验结果显示原始序列非白噪声。

模型识别

由上面平稳性检测与白噪声检验我们确定了原始序列是平稳非白噪声序列。接下来我们要做的就是计算平稳非白噪声序列的自相关系数ACF及偏自相关系数PACF(关于这两个统计量的说明,大家请自行google)。 
我这里给大家总结了AR模型、MA模型、ARMA模型的统计量识别性质,如下表所示:

模型 统计量 性质
AR模型 均值 常数均值
  方差 常数方差
  ACF 拖尾
  PACF p阶截尾
MA模型 均值 常数均值
  方差 常数方差
  ACF q阶截尾
  PACF 拖尾
ARMA模型 均值 常数均值
  方差 常数方差
  ACF p阶拖尾
  PACF q阶拖尾
ACF、PACF

这里画出原始序列的自相关图:

def plot_acfandpacf(dataset):
  D_data = dataset.rentNumber  
  from statsmodels.graphics.tsaplots import plot_acf
  from statsmodels.graphics.tsaplots import plot_pacf
  fig = plt.figure(figsize=(40,10))
  ax1=fig.add_subplot(211)
  plot_acf(D_data,lags=40,ax=ax1).show()
  ax2=fig.add_subplot(212)
  plot_pacf(D_data,lags=40,ax=ax2).show()
  plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

如下图所示: 
acf: 
acf
pacf: 
pacf
很明显自相关图显示出自相关图与偏自相关图均显示拖尾性。 
这里我们使用模型ARMA,并选取p=1,q=1。

拟合及预测

有何模型参数,我们直接fitting模型并预测未来21天的数据,并将预测数据与真实数据做对比,误差用RMSE表示。 
代码如下

def plot_results(predicted_data, true_data):
  fig = plt.figure(facecolor='white',figsize=(10,5))
  ax = fig.add_subplot(111)
  ax.plot(true_data, label='True Data')
  plt.plot(predicted_data, label='Prediction')
  plt.legend()
  plt.show()
def arma_predict(dataset,number):
  data = list(dataset.rentNumber)
  from statsmodels.tsa.arima_model import ARMA
  """
  import statsmodels.tsa.stattools as st
  order = st.arma_order_select_ic(data,max_ar=2,max_ma=2,ic=['aic', 'bic', 'hqic'])
  """
  model = ARMA(data, order=(1,1))
  result_arma = model.fit(disp=-1, method='css')
  predict = result_arma.predict(len(data)-number,len(data))
  RMSE = np.sqrt(((predict-data[len(data)-number-1:])**2).sum()/(number+1))
  plot_results(predict,data[len(data)-number-1:])
  return predict,RMSE
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

对比图形如下图所示: 
对比图形

误差分析

这里的均方误差为: 
RMSE 
在共享单车租用业务中,算是允许的误差范围,对未来的租用投放量有一定的指导意义。

总结

本文主要采用了ARMA模型对某景区的共享单车投放量做了预测,其中详细描述了数据挖掘建模的一般性流程,并结合实际业务对数据进行了分析,该模型及分析问题的方法也可以应用到金融数据预测,销售数据预测等实际场景中。最后,独学而无友则孤陋而寡闻,欢迎读者提出问题,一起讨论哈~

猜你喜欢

转载自blog.csdn.net/qq_36558948/article/details/79594999
今日推荐