0 SARIMAX模型時間序列分析步驟
1. 用pandas處理時序數據
2. 檢驗時序數據的平穩性
3. 將時序數據平穩化
4. 確定order 的 p.d.q值
5. 確定season_order的四個值
6. 應用SARIMAX模型對時序數據進行預測
其實SARIMAX比ARIMA模型就多了個season_order參數的確定,但也是這裏最費時間的一個步驟
1 將數據轉化成爲時序數據
先一股腦導入一下工具包
import pandas as pd
import datetime
import matplotlib.pyplot as plt
from pylab import mpl
mpl.rcParams['font.sans-serif']=['SimHei']
import seaborn as sns
import statsmodels.tsa.stattools as ts
import statsmodels.api as sm
from statsmodels.tsa.arima_model import ARIMA
from statsmodels.stats.diagnostic import unitroot_adf
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
import itertools
import warnings
import numpy as np
from statsmodels.tsa.seasonal import seasonal_decompose
#讀取數據
data = pd.read_csv('factor.csv')
data.index = pd.to_datetime(data['date'])
data.drop(['date'], axis=1, inplace=True)
data = data.result
data.head()
#數據大致情況展示
data.plot(figsize=(12,8))
plt.legend(bbox_to_anchor=(1.25, 0.5))
plt.title('result')
sns.despine()
plt.show()
2 序列平穩性檢測
#數據平穩性檢測 因爲只有平穩數據才能做時間序列分析
def judge_stationarity(data_sanya_one):
dftest = ts.adfuller(data_sanya_one)
print(dftest)
dfoutput = pd.Series(dftest[0:4], index=['Test Statistic','p-value','#Lags Used','Number of Observations Used'])
stationarity = 1
for key, value in dftest[4].items():
dfoutput['Critical Value (%s)'%key] = value
if dftest[0] > value:
stationarity = 0
print(dfoutput)
print("是否平穩(1/0): %d" %(stationarity))
return stationarity
stationarity = judge_stationarity(data)
3 序列平穩化
#若不平穩進行一階差分
if stationarity == 0:
data_diff = data.diff()
data_diff = data_diff.dropna()
plt.figure()
plt.plot(data_diff)
plt.title('一階差分')
plt.show()
#再次進行平穩性檢測
stationarity = judge_stationarity(data_diff)
4 做一下季節性分解看看有沒有季節性
#季節性分解
decomposition = seasonal_decompose(data,freq=28)
trend = decomposition.trend
seasonal = decomposition.seasonal
residual = decomposition.resid
plt.figure(figsize=[15, 7])
decomposition.plot()
print("test: p={}".format(ts.adfuller(seasonal)[1]))
#季節平穩性檢測
stationarity = judge_stationarity(residual)
5 對order參數p、q定階,下面兩種都可以,但圖我還是看不來,下一種傻瓜式(稍微費時間)
#畫ACF圖和PACF圖來確定p、q值
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
def draw_acf_pacf(ts,lags):
f = plt.figure(facecolor='white')
ax1 = f.add_subplot(211)
plot_acf(ts,ax=ax1,lags=lags) #lags 表示滯後的階數,值爲30,顯示30階的圖像
ax2 = f.add_subplot(212)
plot_pacf(ts,ax=ax2,lags=lags)
plt.subplots_adjust(hspace=0.5)
plt.show()
draw_acf_pacf(myts_diff,30)
#對模型p,q進行定階
warnings.filterwarnings("ignore") # specify to ignore warning messages
from statsmodels.tsa.arima_model import ARIMA
pmax = int(5) #一般階數不超過 length /10
qmax = int(5)
bic_matrix = []
for p in range(pmax +1):
temp= []
for q in range(qmax+1):
try:
temp.append(ARIMA(data, (p, 1, q)).fit().bic)
except:
temp.append(None)
bic_matrix.append(temp)
bic_matrix = pd.DataFrame(bic_matrix) #將其轉換成Dataframe 數據結構
p,q = bic_matrix.stack().idxmin() #先使用stack 展平, 然後使用 idxmin 找出最小值的位置
print(u'BIC 最小的p值 和 q 值:%s,%s' %(p,q)) # BIC 最小的p值 和 q 值:0,1
6 通過網格搜索對seasonal_order進行定階
#通過網格搜索對seasonal_order進行定階,目前就是pdq=011,seasonal_order=2, 2, 1, 52效果比較好,RMSE=202.4582
def get_ARIMA_params(data, pdq, m=12):
p = d = q = range(0, 3)
seasonal_pdq = [(x[0], x[1], x[2], m) for x in list(itertools.product(p, d, q))]
score_aic = 1000000.0
warnings.filterwarnings("ignore") # specify to ignore warning messages
for param_seasonal in seasonal_pdq:
mod = sm.tsa.statespace.SARIMAX(data,
order=pdq,
seasonal_order=param_seasonal,
enforce_stationarity=False,
enforce_invertibility=False)
results = mod.fit()
print('x{}12 - AIC:{}'.format(param_seasonal, results.aic))
if results.aic < score_aic:
score_aic = results.aic
params = param_seasonal, results.aic
param_seasonal, results.aic = params
print('x{}12 - AIC:{}'.format(param_seasonal, results.aic))
pdq = [0, 1, 1]
get_ARIMA_params(data, pdq, m=52)
這上面最關鍵的是這個m值怎麼設定,我設置默認爲12。m是季節週期,參考別人代碼的時候,月度數據的m爲12,那我這裏以周爲單位,季節週期應該是52吧。一年12個月,52個星期,這個邏輯應該沒有問題。
7 根據定階參數進行模型擬合
mod = sm.tsa.statespace.SARIMAX(data,
order=(0, 1, 1),
seasonal_order=(2, 1, 2, 52),
enforce_stationarity=False,
enforce_invertibility=False)
results = mod.fit()
print(results.summary().tables[1])
results.plot_diagnostics(figsize=(15, 12))
plt.show()
這裏模型擬合的時候用的數據的原始數據,而不是差分後的數據,因爲order參數中已經設置了d爲1,在擬合的時候會自動進行一階差分,並在預測的時候對預測結果進行差分還原。
8 對預測值和真實值作圖,並計算RMSE值作爲評估參數
predict_ts = results.predict(tpy='levels') #tpy='levels'直接預測值,沒有的話預測的是差值
myts = data[predict_ts.index] # 過濾沒有預測的記錄
predict_ts.plot(color='blue', label='Predict',figsize=(12,8))
myts.plot(color='red', label='Original',figsize=(12,8))
plt.legend(loc='best')
plt.title('RMSE: %.4f'% np.sqrt(sum((predict_ts-myts)**2)/myts.size))
plt.show()
9 向後對forecast值作圖
steps = 20
start_time = myts.index[-1]
forecast_ts = results.forecast(steps)
fore = pd.DataFrame()
fore['date'] = pd.date_range(start=start_time ,periods=steps, freq='7D')
fore['result'] = pd.DataFrame(forecast_ts.values)
fore.index = pd.to_datetime(fore['date'])
predict_ts['2019/1/18':].plot(color='blue', label='Predict',figsize=(12,8))
myts['2019/1/18':].plot(color='red', label='Original',figsize=(12,8))
fore.result.plot(color='black', label='forecast',figsize=(12,8))
plt.legend(loc='best')
plt.show()
這裏有個函數pd.date_range是專門用於產生時間序列索引的,start = 開始時間,end = 結束時間,periods=時間索引的個數,freq=‘7D’表示7天爲一個時間索引間隔,也可以是'7W'七週,'M'一個月等等。
由於預測的數據沒有時間索引,只有序號所以我要在這給他生成時間索引,併合併到dataframe,這樣就可以和其他值一起在圖像上展示了。
最後forecast的效果還是可以的嘛,保存forecast文件
fore.to_csv('forecast_20steps.csv')