항공사 승객수요 데이터 모델링
# 라이브러리 호출
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import statsmodels.api as sm
%reload_ext autoreload
%autoreload 2
from module import stationarity_adf_test, stationarity_kpss_test
# 데이터 준비
data = sm.datasets.get_rdataset("AirPassengers")
raw = data.data.copy()
# 데이터 전처리
## 시간 인덱싱
if 'time' in raw.columns:
raw.index = pd.date_range(start='1/1/1949', periods=len(raw['time']), freq='M')
del raw['time']
## 정상성 테스트
### 미변환
candidate_none = raw.copy()
display(stationarity_adf_test(candidate_none.values.flatten(), []))
display(stationarity_kpss_test(candidate_none.values.flatten(), []))
sm.graphics.tsa.plot_acf(candidate_none, lags=100, use_vlines=True)
plt.tight_layout()
plt.show()
### 로그 변환
candidate_trend = np.log(raw).copy()
display(stationarity_adf_test(candidate_trend.values.flatten(), []))
display(stationarity_kpss_test(candidate_trend.values.flatten(), []))
sm.graphics.tsa.plot_acf(candidate_trend, lags=100, use_vlines=True)
plt.tight_layout()
plt.show()
trend_diff_order_initial = 0
result = stationarity_adf_test(candidate_trend.values.flatten(), []).T
if result['p-value'].values.flatten() < 0.1:
trend_diff_order = trend_diff_order_initial
else:
trend_diff_order = trend_diff_order_initial + 1
print('Trend Difference: ', trend_diff_order)
### 로그+추세차분 변환
candidate_seasonal = candidate_trend.diff(trend_diff_order).dropna().copy()
display(stationarity_adf_test(candidate_seasonal.values.flatten(), []))
display(stationarity_kpss_test(candidate_seasonal.values.flatten(), []))
sm.graphics.tsa.plot_acf(candidate_seasonal, lags=100, use_vlines=True)
plt.tight_layout()
plt.show()
seasonal_diff_order = sm.tsa.acf(candidate_seasonal)[1:].argmax() + 1
print('Seasonal Difference: ', seasonal_diff_order)
### 로그+추세차분+계절차분 변환
candidate_final = candidate_seasonal.diff(seasonal_diff_order).dropna().copy()
display(stationarity_adf_test(candidate_final.values.flatten(), []))
display(stationarity_kpss_test(candidate_final.values.flatten(), []))
sm.graphics.tsa.plot_acf(candidate_final, lags=100, use_vlines=True)
plt.tight_layout()
plt.show()
## 데이터 로그 변환전
- ADF 테스트 결과 p-value 가 0.05보다 크므로 시간의존 구조 -> 비정상상태
- KPSS 테스트 결과 p-value가 0.05보다 작으므로 시간의존 구조 -> 비정상상태
- ACF 플롯도 추세와 계절성이 존재하고, 유의하지 않은 값들이 많다.
## 데이터 로그 변환 후
- ADF 테스트 결과 p-value 가 0.05보다 크므로 시간의존 구조 -> 비정상상태
- KPSS 테스트 결과 p-value가 0.05보다 작으므로 시간의존 구조 -> 비정상상태
- 하지만 p-value가 절반정도 감소하였다. -> 조금더 유의해졌음
- ACF 플롯도 추세와 계절성이 존재하고, 유의하지 않은 값들이 많다.
## 데이터 로그 변환 후 1회 추세 차분.
- ADF 테스트 결과 p-value 가 0.05보다 크므로 시간의존 구조 -> 비정상상태
- KPSS 테스트 결과 p-value가 0.05보다 크므로 시간의존 구조가 아님 -> 정상상태
- acf 플롯을 보면 여전히 특정한 차수가 반복되고 있음 -> 계절성이 제거가 안되었음
- 단순 로그변환 때보다 많이 개선되었다.
## 데이터 로그 변환 후 1회 추세 차분 후 계절 차분
- ADF 테스트 결과 p-value 가 0.05보다 작으므로 시간의존 구조가 아님 -> 비정상상태
- KPSS 테스트 결과 p-value가 0.05보다 크므로 시간의존 구조가 아님 -> 정상상태
- acf 플롯을 보면 값들이 대체로 유의하고, 추세성과 계절성 패턴이 나타나지 않는다.
항공사 승객수요 Auro-ARIMA 모델링
## 최종 타겟 선정 및 Train/Test 데이터 분리
candidate = candidate_trend.copy()
split_date = '1958-01-01'
Y_train = candidate[candidate.index < split_date]
Y_test = candidate[candidate.index >= split_date]
## 시각화 및 모수추론(p=1, q=0, d=1, P=1, Q=1, D(m)=12)
plt.figure(figsize=(14,4))
sm.tsa.graphics.plot_acf(Y_train, lags=50, alpha=0.05, use_vlines=True, ax=plt.subplot(121))
sm.tsa.graphics.plot_pacf(Y_train, lags=50, alpha=0.05, use_vlines=True, ax=plt.subplot(122))
plt.show()
# 모델링
## SARIMAX
logarithm, differencing = True, False
## 아래 부분을 돌려가면서 실행해본다.
# fit_ts_sarimax = sm.tsa.SARIMAX(Y_train, order=(1,trend_diff_order,0), trend='ct').fit()
# fit_ts_sarimax = sm.tsa.SARIMAX(Y_train, order=(1,trend_diff_order,0),
# seasonal_order=(0,0,1,seasonal_diff_order), trend='c').fit()
# fit_ts_sarimax = sm.tsa.SARIMAX(Y_train, order=(1,trend_diff_order,0),
# seasonal_order=(1,0,0,seasonal_diff_order), trend='c').fit()
fit_ts_sarimax = sm.tsa.SARIMAX(Y_train, order=(1,trend_diff_order,0),
seasonal_order=(1,0,1,seasonal_diff_order), trend='c').fit()
display(fit_ts_sarimax.summary())
pred_tr_ts_sarimax = fit_ts_sarimax.predict() ## 점추정값
pred_te_ts_sarimax = fit_ts_sarimax.get_forecast(len(Y_test)).predicted_mean ## 점추정 + 구간추정
pred_te_ts_sarimax_ci = fit_ts_sarimax.get_forecast(len(Y_test)).conf_int()
## 비정상성으로 변환
if logarithm: ##로그를 취한것을 원래 값으로 변환
Y_train = np.exp(Y_train).copy()
Y_test = np.exp(Y_test).copy()
pred_tr_ts_sarimax = np.exp(pred_tr_ts_sarimax).copy()
pred_te_ts_sarimax = np.exp(pred_te_ts_sarimax).copy()
pred_te_ts_sarimax_ci = np.exp(pred_te_ts_sarimax_ci).copy()
if differencing: ## 차분한 것을 원래 값으로 변환
pred_tr_ts_sarimax = np.cumsum(pred_tr_ts_sarimax).copy()
# 검증
%reload_ext autoreload
%autoreload 2
from module import *
Score_ts_sarimax, Resid_tr_ts_sarimax, Resid_te_ts_sarimax = evaluation_trte(Y_train, pred_tr_ts_sarimax,
Y_test, pred_te_ts_sarimax, graph_on=True)
display(Score_ts_sarimax)
ax = pd.DataFrame(Y_test).plot(figsize=(12,4))
## 점추정
pd.DataFrame(pred_te_ts_sarimax, index=Y_test.index, columns=['prediction']).plot(kind='line',
xlim=(Y_test.index.min(),Y_test.index.max()),
linewidth=3, fontsize=20, ax=ax)
## 구간추정
ax.fill_between(pd.DataFrame(pred_te_ts_sarimax_ci, index=Y_test.index).index,
pd.DataFrame(pred_te_ts_sarimax_ci, index=Y_test.index).iloc[:,0],
pd.DataFrame(pred_te_ts_sarimax_ci, index=Y_test.index).iloc[:,1], color='k', alpha=0.15)
plt.show()
- JB 테스트 결과 잔차는 정규분포에 가깝다고 본다.
- ljung-box 테스트 결과 자기상관이 없다고 한다
- 등분산성 테스트 결과 등분산이 아니라고 본다
- 예측과 실제값이 거의 비슷하게 나온다.
해당 포스팅은 패스트캠퍼스의 <파이썬을 활용한 시계열 데이터 분석 A-Z 올인원 패키지> 강의를 듣고 정리한 내용입니다
728x90
반응형
'Data Analysis & ML > 시계열분석' 카테고리의 다른 글
[시계열 분석] 정확도를 높이기 위한 Prophet 파라미터 활용 (0) | 2022.02.13 |
---|---|
[시계열 분석] Prophet (0) | 2021.12.19 |
[시계열분석] 시계열 알고리즘 - 선형확률과정의 분석사이클 자동화 (Auto ARIMA) (0) | 2021.09.26 |
[시계열분석] 시계열 알고리즘 - 선형확률과정의 분석사이클 (0) | 2021.09.26 |
[시계열분석] 시계열 알고리즘 - 적분 선형확률 과정(3) - SARIMA 모델링 해석 (1) | 2021.09.26 |