本文demo源码、实验数据:传送门
引言
前面我有分享两篇关于时间序列模型的文章,一篇是 Holt-Winters模型原理分析及代码实现(python),一篇是 LSTM模型分析及对时序数据预测的具体实现(python实现)。holt-winters是典型的平滑法,一般用于趋势分析和预测。LSTM是一种时间递归神经网络,适合于处理和预测时间序列中间隔和延迟相对较长的重要事件。最近实验室师兄的毕设论文需要用到某景区共享交通的租用量预测的算法,我帮忙协助实现了下,其中用到了一种新的时间序列模型,叫做ARMA模型,特总结如下。(本文涉及一些数理统计的概念及知识点,遇到不懂的请小伙伴们自行google)
AR、MA、ARMA模型简介
ARMA模型的全称是自回归移动平均模型,它是目前最常用的弥合平稳序列的模型。它又可以细分为AR模型、MA模型和ARMA三大类。都可以看作是多元线性回归模型。
AR
AR模型:
具有如下结构的模型成为p阶自回归模型,简记为AR§。
x t = ϕ 0 + ϕ 1 x t − 1 + ϕ 2 x t − 2 + . . . + ϕ p x t − p + ε t x_t = \phi_0+\phi_1x_{t-1}+\phi_2x_{t-2}+...+\phi_px_{t-p}+\varepsilon_t xt=ϕ0+ϕ1xt−1+ϕ2xt−2+...+ϕpxt−p+εt
随机变量 X t X_t Xt的取值 x t x_t xt是前p期 x t − 1 , x t − 2 , . . . , x t − p x_{t-1},x_{t-2},...,x_{t-p} xt−1,xt−2,...,xt−p的多元线性回归,认为 x t x_t xt主要受过去p期的序列值影响。误差项是当前的随机干扰 ε t \varepsilon_t εt,为零均值白噪声序列。
MA
MA模型:
具有如下结构的模型称为q阶自回归模型,简记为MA(q)。
x t = μ + ε t − θ 1 ε t − 1 − θ 2 ε t − 2 − . . . − θ q ε t − q x_t=\mu+\varepsilon_t-\theta_1\varepsilon_{t-1}-\theta_2\varepsilon_{t-2}-...-\theta_q\varepsilon_{t-q} xt=μ+εt−θ1εt−1−θ2εt−2−...−θqεt−q
即在t时刻的随机变量 X t X_t Xt的取值 x t x_t xt是前q期的随机扰动 ε t − 1 , ε t − 2 , . . . , ε t − q \varepsilon_{t-1},\varepsilon_{t-2},...,\varepsilon_{t-q} εt−1,εt−2,...,εt−q的多元线性函数。误差项是当期的随机干扰 ε t \varepsilon_t εt,为零均值白噪声序列, μ \mu μ是序列 { X t } \{X_t\} {Xt}的均值。认为 x t x_t xt主要受过去q期的误差项影响。
ARMA
ARMA
ARMA模型是最常用的平稳序列拟合模型,关于平稳序列的定义,请戳:平稳序列。ARMA模型的公式如下:
x t = ϕ 0 + ϕ 1 x t − 1 + ϕ 2 x t − 2 + . . . + ϕ p x t − p + ε t − θ 1 ε t − 1 − θ 2 ε t − 2 − . . . − − θ q ε t − q x_t = \phi_0+\phi_1x_{t-1}+\phi_2x_{t-2}+...+\phi_px_{t-p}+\varepsilon_t-\theta_1\varepsilon_{t-1}-\theta_2\varepsilon_{t-2}-...--\theta_q\varepsilon_{t-q} xt=ϕ0+ϕ1xt−1+ϕ2xt−2+...+ϕpxt−p+εt−θ1εt−1−θ2εt−2−...−−θqεt−q
随机变量 X t X_t Xt的取值 x t x_t xt不仅与以前p期的序列值有关还与前q期的随机扰动有关。
基于时间序列的短期数据预测
接下来我将用数据挖掘的规范流程开启此次的探索之旅~
分析方法与过程
本文目的是预测某景区共享交通的租用情况,故可采用时间序列分析法来分析共享交通的历史租用情况,并预测未来几天内或几周内的租用情况,其挖掘建模的总体流程如下图所示。
数据抽取
我们从业务系统数据库抽取了时间段为2015-09-28~2017-10.16的数据,原始部分数据如下图所示:
数据探索与分析
####原始数据转换成时序数据
原始数据是每发生一次租用行为就上传一天记录,为了生成每天的租用量,需要将数据按日做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
原始数据转换成时间序列数据如下所示:
空缺值分析
我们分析数据库的数据,发现数据库里面空缺值并不会标记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
空缺值实验结果如下图所示:
其中一共有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()
按天统计次数
def plot_monthdata(df):
df.rentNumber.plot(kind='bar',figsize=(16,4))
plt.show()
当我分析数据,画出上面两个图形时,我震惊了!!!租用量看上去只有周期趋势,没有总体上升趋势!!!而且越往后,峰值越来越小。从我一开始的认知中,我想像租用量,销售量什么的,周期性变化我是赞同的,一般来说周末租用的多,节假日租用的人多,因为景区相应的人多;但是从一个企业的发展来说,租用量不是应该是一种总体上升的趋势么,至少应该像下面的这张图:
不仅有周期性,而且还有总体上升的趋势。。。
分析到这里我心里就开始估计这个时序序列估计是个平稳时序即时间序列在某一常数附近波动且波动范围有限。
模型构建
本文共享交通租用量预测模型的建模流程如下图所示:
首先需要对观测值序列进行平稳性检测,如果不平稳,则对其进行差分运算直到差分后的数据平稳。在数据平稳后则对其进行白噪声检验,如果没有通过白噪声检验就进行模型识别,识别该模型属于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]))
检验结果:
这个说明数据是平稳的时许序列,与我们前面的猜测一致!
白噪声检验
为了验证序列中是否是白噪声,如果是白噪声,那么此序列即都是随机扰动,无法进行预测和使用。
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)
检验结果:
检验结果显示原始序列非白噪声。
模型识别
由上面平稳性检测与白噪声检验我们确定了原始序列是平稳非白噪声序列。接下来我们要做的就是计算平稳非白噪声序列的自相关系数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()
如下图所示:
acf:
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
对比图形如下图所示:
误差分析
这里的均方误差为:
在共享单车租用业务中,算是允许的误差范围,对未来的租用投放量有一定的指导意义。
总结
本文主要采用了ARMA模型对某景区的共享单车投放量做了预测,其中详细描述了数据挖掘建模的一般性流程,并结合实际业务对数据进行了分析,该模型及分析问题的方法也可以应用到金融数据预测,销售数据预测等实际场景中。最后,独学而无友则孤陋而寡闻,欢迎读者提出问题,一起讨论哈~