时间序列分析就是发现时间序列的变动规律并使用该规律来预测的统计技术。时间序列分析基于以下三个假设:
时间序列分析主要包括两方面内容:第一是序列分解;第二是序列预测。
代码及数据地址:https://github.com/SeafyLiang/machine_learning_study/tree/master/time_series
一个时间序列包含三种影响因素:
时间序列按照季节性来分类,分为季节性时间序列和非季节性时间序列。
移动平均是一种简单平滑技术,它通过在时间序列上逐项推移取一定项数的均值,来表现指标的长期变化和发展趋势。
简单移动平均将时间序列上前n个数值做简单的算术平均。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
data = pd.read_csv('data/非季节性时间序列.csv', encoding='utf8', engine='python')
original_data = data['公司A']
def sma_demo():
# 将非季节性的时间序列,分解为趋势部分和不规则部分
# SMA 简单移动平均
sma_data = original_data.rolling(3).mean()
plt.plot(
data.index, original_data, 'k-',
data.index, sma_data, 'g-'
)
plt.show()
# 平滑后的曲线,波动性基本被消除
# 加入随机误差
random_error = original_data - sma_data
plt.plot(
data.index, original_data, 'k-',
data.index, sma_data, 'g-',
data.index, random_error, 'r-'
)
plt.show()
# 随机误差在0附件波动,均值趋向于0
if __name__ == '__main__':
sma_demo()
加权移动平均在基于简单移动平均的基础上,对时间序列上前n期的每一期数值赋予对应的权重。
它的基本思想是,提升近期的数据并减弱远期数据对当前预测值的影响,使平滑值更贴近最近的变化趋势。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
data = pd.read_csv('data/非季节性时间序列.csv', encoding='utf8', engine='python')
original_data = data['公司A']
def wma_demo():
# WMA 加权移动平均
# 定义窗口大小
wl = 3
# 定义每个窗口值的权重
ww = [1 / 6, 2 / 6, 3 / 6]
def wma(window):
return np.sum(window * ww)
sma_data = original_data.rolling(wl).aggregate(wma)
random_error = original_data - sma_data
plt.plot(
data.index, original_data, 'k-',
data.index, sma_data, 'g-',
data.index, random_error, 'r-'
)
plt.show()
if __name__ == '__main__':
wma_demo()
季节性时间序列,是指在一个时间序列中,若经过n个时间间隔后,呈现出相似的波动,则称该序列具有以n为周期的季节性特性。
import pandas as pd
import statsmodels.api
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
data = pd.read_csv('data/季节性时间序列.csv', encoding='utf8', engine='python')
total_sales = data['总销量'].values
# 执行季节性时间序列分解
'''
statsmodels.api.tsa.seasonal_decompose(x, freq=None)
x:要分解的季节性时间序列
freq:时间序列的周期,需要自己根据业务来判断数据的周期
'''
tsr = statsmodels.api.tsa.seasonal_decompose(
total_sales, period=7
)
# 获取趋势部分
trend = tsr.trend
# 获取季节性部分
seasonal = tsr.seasonal
# 获取随机误差部分
random_error = tsr.resid
# 生成2*2的子图
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)
ax1.set_title('总销量')
ax1.plot(
data.index, total_sales, 'k-'
)
ax2.set_title('趋势部分')
ax2.plot(
data.index, trend, 'g-'
)
ax3.set_title('季节性部分')
ax3.plot(
data.index, seasonal, 'r-'
)
ax4.set_title('随机误差')
ax4.plot(
data.index, random_error, 'b-'
)
plt.show()
时间序列预测,是根据时间序列反映出来的发展过程、方向和趋势,进行类推或延伸,借以预测下一段时间或以后若干周期内可能达到的水平。
时间序列预测使用时间序列的均值、方差和协方差这三个统计量来刻画时间序列的本质特征。若这些统计量的值在过去和未来仍保持不变,则为平稳的时间序列。
如果时间序列不平稳,则需要通过差分等技术手段,把它转换成平稳的时间序列,然后再进行预测。
在pandas模块中,使用diff函数,即可对数据进行差分。
可以使用单位根检验法来检验一个时间序列的平稳性。其中的p-value代表有多大的概率不是一个平稳的时间序列。
import pandas as pd
import matplotlib.pyplot as plt
# 单位根检验法
import statsmodels.tsa.stattools as ts
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
'''
一个平稳的时间序列指的是,未来的时间序列,它的均值、方差和协方差必定与现在的时间序列相等。
若时间序列不平稳,则需要通过差分等技术手段,把它转换成平稳的时间序列,然后再进行预测。
'''
data = pd.read_csv('data/时间序列预测.csv', encoding='utf8', engine='python')
# 设置索引为时间格式
data.index = pd.to_datetime(data.date, format='%Y%m%d')
# 删除date列,已经保存到索引中了
del data['date']
plt.figure()
plt.plot(data, 'r')
plt.show()
# 使用单位根检验法检验是否是平稳的时间序列
# 封装一个方法,方便解读adfuller函数的结果
def tagADF(t):
result = pd.DataFrame(index=[
"Test Statistic Value",
"p-value", "Lags Used",
"Number of Observations Used",
"Critical Value(1%)",
"Critical Value(5%)",
"Critical Value(10%)"
], columns=['value']
)
result['value']['Test Statistic Value'] = t[0]
result['value']['p-value'] = t[1]
result['value']['Lags Used'] = t[2]
result['value']['Number of Observations Used'] = t[3]
result['value']['Critical Value(1%)'] = t[4]['1%']
result['value']['Critical Value(5%)'] = t[4]['5%']
result['value']['Critical Value(10%)'] = t[4]['10%']
return result
# 使用ADF单位根检验法,检验时间序列的稳定性
adf_data = ts.adfuller(data.value)
# 解读ADF单位根检验结果
adfResult = tagADF(adf_data)
print(adfResult)
# value
# Test Statistic Value -1.16364
# p-value 0.689038
# Lags Used 12
# Number of Observations Used 77
# Critical Value(1%) -3.518281
# Critical Value(5%) -2.899878
# Critical Value(10%) -2.587223
# 68%的概率不是一个平稳的时间序列,故通过差分转换成平稳的时间序列
# 对数据进行差分,删除第一个位置的空值
diff = data.value.diff(1).dropna()
plt.figure()
plt.plot(diff, 'r')
plt.show()
# 使用ADF单位根检验法,检验时间序列的稳定性
adf_diff = ts.adfuller(diff)
# 解读ADF单位根检验结果
adfResult = tagADF(adf_diff)
print(adfResult)
# value
# Test Statistic Value -4.993859
# p-value 0.000023
# Lags Used 12
# Number of Observations Used 76
# Critical Value(1%) -3.519481
# Critical Value(5%) -2.900395
# Critical Value(10%) -2.587498
# 0.000023<0.05,认为差分后的时间序列是平稳的时间序列
自回归模型本质是一个线性回归模型,“自”描述的是当前值与历史值之间的关系。通过一个超参数p,把时间序列格式的样本转换为线性回归模型的样本。
移动平均模型描述的是自回归模型部分的累计误差。
移动平均模型的意义在于,找到较少参数的移动平均模型,来替代较多参数的自回归模型。
ARMA模型就是AR模型和MA模型的组合,将因变量仅对它的滞后值以及随机误差项的现值和滞后值进行回归所建立的模型。
import statsmodels.api as sm
import pandas as pd
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings("ignore")
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
data = pd.read_csv('data/时间序列预测.csv', encoding='utf8', engine='python')
# 设置索引为时间格式
data.index = pd.to_datetime(data.date, format='%Y%m%d')
# 删除date列,已经保存到索引中了
del data['date']
diff = data.value.diff(1).dropna()
plt.figure()
plt.plot(diff, 'r')
plt.show()
def ar_demo():
'''
自回归
:return:
'''
# 使用 AR 建模
arModel = sm.tsa.AR(
diff
)
# 使用 AR 模型的 select_order 方法
# 自动根据 AIC 指标,选择最优的 p 值
p = arModel.select_order(
maxlag=30,
ic='aic'
)
print(p)
# 使用最优的 p 值进行建模
arModel = arModel.fit(maxlag=p)
# 对时间序列模型进行评估
delta = arModel.fittedvalues - diff
score = 1 - delta.var() / diff.var()
print(score)
def sma_demo():
'''
简单移动平均
:return:
'''
sma_data = diff.rolling(3).mean()
plt.plot(
diff.index, diff, 'k-',
diff.index, sma_data, 'g-'
)
plt.show()
def arma_demo():
# ARMA模型,使用AIC指标
# AR模型从1-15中选择最优的p
# MA模型从1-15中选择最优的q
# 执行时间非常长,作者执行了10个小时左右
# ic = sm.tsa.arma_order_select_ic(
# diff,
# max_ar=15,
# max_ma=15,
# ic='aic'
# )
# 选择最优参数
order = (6, 6)
armaModel = sm.tsa.ARMA(
diff, order
).fit()
# 评估模型得分
delta = armaModel.fittedvalues - diff
score = 1 - delta.var() / diff.var()
print(score)
# 预测接下来10天的值
p = armaModel.predict(
start='2016-03-31',
end='2016-04-10'
)
# 封装一个对差分的数据进行还原的函数
def revert(diffValues, *lastValue):
for i in range(len(lastValue)):
result = []
lv = lastValue[i]
for dv in diffValues:
lv = dv + lv
result.append(lv)
diffValues = result
return diffValues
# 对差分的数据进行还原
r = revert(p, 10395)
plt.figure()
plt.plot(
diff, 'r',
label='Raw'
)
plt.plot(
armaModel.fittedvalues, 'g',
label='ARMA Model'
)
plt.plot(
p, 'b', label='ARMA Predict'
)
plt.legend()
plt.show()
r = pd.DataFrame({
'value': r
}, index=p.index
)
plt.figure()
plt.plot(
data.value, c='r',
label='Raw'
)
plt.plot(
r, c='g',
label='ARMA Model'
)
plt.legend()
plt.show()
if __name__ == '__main__':
ar_demo()
sma_demo()
arma_demo()