즉, 안정적(Stationary) 데이터에 대해서만 미래 예측 가능
A. 시계열 예측이 맞다고 생각.
그 예로, 위성 사진의 경우에도 일정 주기로 촬영되는 특성이 있기 때문에 연속적 데이터셋이 만들어질 것이고 변화를 관찰하여 사용할 수 있기 때문에 시계열 예측이 맞음.
답변 : 과거 유가 변동 데이터만 이용할 경우 시계열 예측이 맞으나, 유가가 아닌 다른 성격의 데이터가 활용될경우 엄밀하게 봤을 때 시계열 예측이 아님
A. 유가의 변화에 영향이 미치게 되는 다른 밀접한 변화 요인들로 인해 발생된 결과 지표들도 있을 수 있기 때문이 아닐까.
답변
- 이전 유가만 활용
- 임의로 결정된 전제 -> "원유 시장이 외부 영향 없이 자체적으로 유가를 결정하는 안정적 프레세스가 있을 것"
- 국제 유가는 국가 간 분쟁, 경제 호황/불활 등 외부 요소가 많고 가격을 결정하는 수요-공급의 균형점이 끊임없이 변화함
- 즉, 외부 요인을 고려할 수 있는 보조 데이터 활용이 필요한 것
👉 참고: 현재 노드에서의 Stationary Time-Series, Stationary, 안정적 시계열, 안정성 == 정상성과 같은 의미
평균
분산
공분산
참고) 상관관계(Correlation)
- 공분산을 각 분산으로 다시 나눔 -> 공분산의 (-1, 1) 범위 스케일링
- 양의 상관관계 : 1
- 음의 상관관계 : -1
- 무관 : 0
A. 시계열 데이터의 통계적 특성이 변하지 않아야 하는 것을 의미
Daily Minimum Temperatures in Melbourne
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
import warnings
warnings.filterwarnings('ignore')
dataset_filepath = os.getenv('HOME')+'/aiffel/stock_prediction/data/daily-min-temperatures.csv'
df = pd.read_csv(dataset_filepath)
print(type(df))
df.head()
df = pd.read_csv(dataset_filepath, index_col='Date', parse_dates=True)
print(type(df))
df.head()
df['Temp']
현재 df는 데이터프레임 형태로 데이터셋을 저장하고 있고, df[Temp]는 Series 형태로 데이터셋을 저장하고 있음
from matplotlib.pylab import rcParams
rcParams['figure.figsize'] = 13, 6
plt.plot(ts1)
ts1[ts1.isna()]
참고) 결측치 처리법
- drop : 결측치 데이터 삭제
- interpolate(보간법) : 결측치 양옆 값들로 보간
- extrapolation(보외법) : 범위 밖의 값(외부값)을 예측하여 채우는 것 즉, 주어진 데이터 집합 범위를 넘어가는 영역에서의 값 추정을 위해 사용되는 통계기법(데이터 패턴 및 경향성을 통해 결측치 처리)
ts1=ts1.interpolate(method='time')
print(ts1[ts1.isna()])
plt.plot(ts1)
직관적으로 봤을 경우, 평균, 분산, 자기공분산 패턴이 있는 것으로 보임
def plot_rolling_statistics(timeseries, window=12):
rolmean = timeseries.rolling(window=window).mean() # 이동평균 시계열
rolstd = timeseries.rolling(window=window).std() # 이동표준편차 시계열
# 원본시계열, 이동평균, 이동표준편차 시각화
orig = plt.plot(timeseries, color='blue',label='Original')
mean = plt.plot(rolmean, color='red', label='Rolling Mean')
std = plt.plot(rolstd, color='black', label='Rolling Std')
plt.legend(loc='best')
plt.title('Rolling Mean & Standard Deviation')
plt.show(block=False)
plot_rolling_statistics(ts1, window=12)
안정성을 보임
International airline passengers
ts2 = df['Passengers']
plt.plot(ts2)
t-p
시점까지의 차분 경향성 파악 ➡️ 주기 데이터의 정상성 여부를 판별할 수 있게됨
- 참고) P-value?
- 귀무가설 가정 시의 확률분포 상에서 현재 관측보다 더 극단적인 관측이 나올 확률
- 귀무가설이 틀렸다고 볼 수 있기도 함
- 0.05 미만으로 낮게 나오면 -> p-value만큼의 오류 가능성 전제로, 귀무 가설의 기각 및 대립가설의 채택 근거가 될 수 있음
statsmodels
의 adfuller
이용from statsmodels.tsa.stattools import adfuller
def augmented_dickey_fuller_test(timeseries):
dftest = adfuller(timeseries, autolag='AIC')
print('Results of Dickey-Fuller Test:')
dfoutput = pd.Series(dftest[0:4], index=['Test Statistic','p-value','#Lags Used','Number of Observations Used'])
for key,value in dftest[4].items():
dfoutput['Critical Value (%s)' % key] = value
print(dfoutput)
대립가설 : 안정적 시계열이다
augmented_dickey_fuller_test(ts1)
augmented_dickey_fuller_test(ts2)
그러나, p-value가 직접적인 근거가 될 수는 없다는 점은 유의해야 함
ts_log = np.log(ts2)
plt.plot(ts_log)
augmented_dickey_fuller_test(ts_log)
moving_avg = ts_log.rolling(window=12).mean()
plt.plot(ts_log)
plt.plot(moving_avg, color='red')
ts_log_moving_avg = ts_log - moving_avg
ts_log_moving_avg.head(15)
NaN 값은 뭐지?
- Moving Average 계산(windows size=12일 경우) 시 앞의 11개 데이터는 계산이 되지 않음 -> 결측치가 됨
- DF Test시 에러가 되니 제거
ts_log_moving_avg.dropna(inplace=True)
ts_log_moving_avg.head(15)
plot_rolling_statistics(ts_log_moving_avg)
augmented_dickey_fuller_test(ts_log_moving_avg)
moving_avg_6 = ts_log.rolling(window=6).mean()
ts_log_moving_avg_6 = ts_log - moving_avg_6
ts_log_moving_avg_6.dropna(inplace=True)
plot_rolling_statistics(ts_log_moving_avg_6)
augmented_dickey_fuller_test(ts_log_moving_avg_6)
12개월 단위 주기성이 있어 그렇게 판단할 수도 있겠지만, 중요한 부분은 moving average 고려 시 rolling mean을 구하기 위해 window 크기를 결정해야만 한다는 것
추세(Trend) vs 계절성(Seasonality) vs 주기성(Cycle)
- 추세 (Trend)
- 장기적 증가 or 감소 경향성
- 기울기의 증가 or 감소로 관찰
- 일정 시간 발생(유지되지 않음)
- 계절성(Seasonality)
- 계절 요인 영향으로 1년(일정 기간)안에 반복적으로 나타남 -> 패턴
- 빈도(항상 일정함)
- 주기성(Cycle)
- 정해지지 않은 빈도, 기간동안 일어나는 상승 or 하락
현재 스텝 값 - 직전 스텝 값
-> 이번 스텝의 변화량이 됨ts_log_moving_avg_shift = ts_log_moving_avg.shift(-1)
plt.plot(ts_log_moving_avg, color='blue')
plt.plot(ts_log_moving_avg_shift, color='green')
원본 시계열 - 시프트 시계열
그래프ts_log_moving_avg_diff = ts_log_moving_avg - ts_log_moving_avg_shift
ts_log_moving_avg_diff.dropna(inplace=True)
plt.plot(ts_log_moving_avg_diff)
plot_rolling_statistics(ts_log_moving_avg_diff)
augmented_dickey_fuller_test(ts_log_moving_avg_diff)
from statsmodels.tsa.seasonal import seasonal_decompose
decomposition = seasonal_decompose(ts_log)
trend = decomposition.trend # 추세
seasonal = decomposition.seasonal # 계절성
residual = decomposition.resid # 로그변환 - 추세 - 계절성
plt.rcParams["figure.figsize"] = (11,6)
plt.subplot(411)
plt.plot(ts_log, label='Original')
plt.legend(loc='best')
plt.subplot(412)
plt.plot(trend, label='Trend')
plt.legend(loc='best')
plt.subplot(413)
plt.plot(seasonal,label='Seasonality')
plt.legend(loc='best')
plt.subplot(414)
plt.plot(residual, label='Residuals')
plt.legend(loc='best')
plt.tight_layout()
plt.rcParams["figure.figsize"] = (13,6)
plot_rolling_statistics(residual)
residual.dropna(inplace=True)
augmented_dickey_fuller_test(residual)
Residual
부분을 모델링Trend
부분을 모델링Seasonality
부분을 모델링AR 형태의 기대와 MA 형태의 우려 속에서 적정 수준을 찾는 것이 ARIMA
파라미터 선정 방법
A. 시계열 데이터 상관관계 패턴 파악, 유의한 시차의 식별 및 파라미터의 결정에 도움을 줌
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
plot_acf(ts_log)
plot_pacf(ts_log)
plt.show()
p=1
이 적합해 보임(∵ p가 2 이상인 구간에서 pacf는 0에 가까워지고 있음)참고) AR/MA 모형과 ACF/PACF 관계
AR(p) MA(q) ACF 점차 감소 시차 q 이후 0 PACF 시차 p 이후 0 점차 감소
diff_1 = ts_log.diff(periods=1).iloc[1:]
diff_1.plot(title='Difference 1st')
augmented_dickey_fuller_test(diff_1)
diff_2 = diff_1.diff(periods=1).iloc[1:]
diff_2.plot(title='Difference 2nd')
augmented_dickey_fuller_test(diff_2)
train_data, test_data = ts_log[:int(len(ts_log)*0.9)], ts_log[int(len(ts_log)*0.9):]
plt.figure(figsize=(10,6))
plt.grid(True)
plt.plot(ts_log, c='r', label='training dataset')
plt.plot(test_data, c='b', label='test dataset')
plt.legend()
print(ts_log[:2])
print(train_data.shape)
print(test_data.shape)
import warnings
warnings.filterwarnings('ignore')
from statsmodels.tsa.arima.model import ARIMA
model = ARIMA(train_data, order=(14, 1, 0))
fitted_m = model.fit()
print(fitted_m.summary())
fitted_m = fitted_m.predict()
fitted_m = fitted_m.drop(fitted_m.index[0])
plt.plot(fitted_m, label='predict')
plt.plot(train_data, label='train_data')
plt.legend()
forecast()
model = ARIMA(train_data, order=(14, 1, 0))
fitted_m = model.fit()
fc= fitted_m.forecast(len(test_data), alpha=0.05)
fc_series = pd.Series(fc, index=test_data.index)
plt.figure(figsize=(9,5), dpi=100)
plt.plot(train_data, label='training')
plt.plot(test_data, c='b', label='actual price')
plt.plot(fc_series, c='r',label='predicted price')
plt.legend()
plt.show()
from sklearn.metrics import mean_squared_error, mean_absolute_error
import math
mse = mean_squared_error(np.exp(test_data), np.exp(fc))
print('MSE: ', mse)
mae = mean_absolute_error(np.exp(test_data), np.exp(fc))
print('MAE: ', mae)
rmse = math.sqrt(mean_squared_error(np.exp(test_data), np.exp(fc)))
print('RMSE: ', rmse)
mape = np.mean(np.abs(np.exp(fc) - np.exp(test_data))/np.abs(np.exp(test_data)))
print('MAPE: {:.2f}%'.format(mape*100))
MAPE 기준 5% 이내이기 때문에 매우 좋은 모델로 평가됨