055 ARIMA 기초
키워드: ARIMA, 자기회귀, 이동평균
개요
ARIMA(AutoRegressive Integrated Moving Average)는 시계열 예측의 고전적이면서도 강력한 방법입니다. 자기회귀(AR), 차분(I), 이동평균(MA)을 결합한 모델입니다.
실습 환경
- Python 버전: 3.11 권장
- 필요 패키지:
statsmodels, pmdarima, pandas
pip install statsmodels pmdarima pandas numpy matplotlib
ARIMA 구성요소
AR, I, MA 이해하기
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
# 055 데이터 생성
np.random.seed(42)
n = 300
# 055 AR(1) 프로세스: Y_t = 0.7 * Y_{t-1} + ε_t
ar_data = [0]
for i in range(1, n):
ar_data.append(0.7 * ar_data[-1] + np.random.randn())
# 055 MA(1) 프로세스: Y_t = ε_t + 0.7 * ε_{t-1}
ma_data = [np.random.randn()]
noise = [ma_data[0]]
for i in range(1, n):
epsilon = np.random.randn()
noise.append(epsilon)
ma_data.append(epsilon + 0.7 * noise[-2])
# 055 시각화
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
axes[0, 0].plot(ar_data)
axes[0, 0].set_title('AR(1) Process: Y_t = 0.7·Y_{t-1} + ε')
axes[0, 1].plot(ma_data)
axes[0, 1].set_title('MA(1) Process: Y_t = ε_t + 0.7·ε_{t-1}')
# 055 ACF
plot_acf(ar_data, ax=axes[1, 0], lags=20)
axes[1, 0].set_title('ACF of AR(1)')
plot_acf(ma_data, ax=axes[1, 1], lags=20)
axes[1, 1].set_title('ACF of MA(1)')
plt.tight_layout()
plt.show()
# 055 ARIMA 구성요소 설명
components = {
'구성요소': ['AR (p)', 'I (d)', 'MA (q)'],
'이름': ['AutoRegressive', 'Integrated', 'Moving Average'],
'의미': [
'과거 값들의 선형 조합',
'차분 횟수 (정상화)',
'과거 오차의 선형 조합'
],
'수식': [
'φ₁Y_{t-1} + φ₂Y_{t-2} + ...',
'차분: Y_t - Y_{t-1}',
'θ₁ε_{t-1} + θ₂ε_{t-2} + ...'
]
}
print("ARIMA(p, d, q) 구성요소:")
print(pd.DataFrame(components).to_string(index=False))
ACF와 PACF
# 055 ACF: 자기상관함수 (AutoCorrelation Function)
# 055 PACF: 부분자기상관함수 (Partial AutoCorrelation Function)
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 055 AR(1)
plot_acf(ar_data, ax=axes[0, 0], lags=20, title='AR(1) - ACF')
plot_pacf(ar_data, ax=axes[0, 1], lags=20, title='AR(1) - PACF')
# 055 MA(1)
plot_acf(ma_data, ax=axes[1, 0], lags=20, title='MA(1) - ACF')
plot_pacf(ma_data, ax=axes[1, 1], lags=20, title='MA(1) - PACF')
plt.tight_layout()
plt.show()
# 055 패턴 해석
patterns = {
'모델': ['AR(p)', 'MA(q)', 'ARMA(p,q)'],
'ACF': ['점진적 감소', 'lag q 이후 절단', '점진적 감소'],
'PACF': ['lag p 이후 절단', '점진적 감소', '점진적 감소']
}
print("\nACF/PACF 패턴으로 모델 식별:")
print(pd.DataFrame(patterns).to_string(index=False))
ARIMA 모델 적합
실제 데이터 준비
# 055 항공 승객 데이터 시뮬레이션
np.random.seed(42)
dates = pd.date_range('2010-01', periods=144, freq='M')
trend = np.linspace(100, 400, 144)
seasonality = 50 * np.sin(np.arange(144) * 2 * np.pi / 12)
noise = np.random.randn(144) * 20
passengers = trend + seasonality + noise
ts = pd.Series(passengers, index=dates, name='passengers')
# 055 학습/테스트 분할
train = ts[:-12]
test = ts[-12:]
print("데이터 정보:")
print(f" 전체: {len(ts)} 개월")
print(f" 학습: {len(train)} 개월")
print(f" 테스트: {len(test)} 개월")
# 055 시각화
plt.figure(figsize=(14, 6))
plt.plot(train, label='Train')
plt.plot(test, label='Test')
plt.title('Monthly Passengers')
plt.legend()
plt.show()
수동 ARIMA 적합
from statsmodels.tsa.stattools import adfuller
# 1. 정상성 검정
result = adfuller(train)
print(f"ADF p-value: {result[1]:.4f}")
# 2. 차분
train_diff = train.diff().dropna()
result_diff = adfuller(train_diff)
print(f"1차 차분 후 ADF p-value: {result_diff[1]:.4f}")
# 3. ACF/PACF로 p, q 결정
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
plot_acf(train_diff, ax=axes[0], lags=24, title='ACF (after differencing)')
plot_pacf(train_diff, ax=axes[1], lags=24, title='PACF (after differencing)')
plt.tight_layout()
plt.show()
# 4. ARIMA 적합
model = ARIMA(train, order=(2, 1, 2))
fitted = model.fit()
print("\nARIMA(2,1,2) 결과:")
print(fitted.summary().tables[1])
Auto ARIMA
import pmdarima as pm
# 055 Auto ARIMA
auto_model = pm.auto_arima(
train,
start_p=0, start_q=0,
max_p=5, max_q=5,
d=None, # 자동 결정
seasonal=True,
m=12, # 계절 주기
start_P=0, start_Q=0,
max_P=2, max_Q=2,
trace=True,
error_action='ignore',
suppress_warnings=True,
stepwise=True
)
print("\n최적 ARIMA:")
print(auto_model.summary())
예측
미래 예측
# 055 예측
forecast_arima = fitted.forecast(steps=12)
forecast_auto = auto_model.predict(n_periods=12)
# 055 신뢰 구간
conf_int = fitted.get_forecast(steps=12).conf_int()
# 055 시각화
plt.figure(figsize=(14, 6))
plt.plot(train, label='Train')
plt.plot(test, label='Actual')
plt.plot(test.index, forecast_arima, 'r--', label='ARIMA(2,1,2)')
plt.plot(test.index, forecast_auto, 'g--', label='Auto ARIMA')
plt.fill_between(test.index, conf_int.iloc[:, 0], conf_int.iloc[:, 1], alpha=0.2)
plt.title('ARIMA Forecast')
plt.legend()
plt.show()
# 055 성능 평가
from sklearn.metrics import mean_absolute_error, mean_squared_error
mae_arima = mean_absolute_error(test, forecast_arima)
mae_auto = mean_absolute_error(test, forecast_auto)
rmse_arima = np.sqrt(mean_squared_error(test, forecast_arima))
rmse_auto = np.sqrt(mean_squared_error(test, forecast_auto))
print("\n예측 성능:")
print(f" ARIMA(2,1,2) - MAE: {mae_arima:.2f}, RMSE: {rmse_arima:.2f}")
print(f" Auto ARIMA - MAE: {mae_auto:.2f}, RMSE: {rmse_auto:.2f}")
SARIMA (Seasonal ARIMA)
계절성 포함
from statsmodels.tsa.statespace.sarimax import SARIMAX
# 055 SARIMA 모델
# 055 order=(p, d, q), seasonal_order=(P, D, Q, m)
sarima_model = SARIMAX(
train,
order=(1, 1, 1),
seasonal_order=(1, 1, 1, 12)
)
sarima_fitted = sarima_model.fit(disp=False)
print("SARIMA(1,1,1)(1,1,1,12) 결과:")
print(sarima_fitted.summary().tables[1])
# 055 예측
forecast_sarima = sarima_fitted.forecast(steps=12)
# 055 비교
plt.figure(figsize=(14, 6))
plt.plot(train[-36:], label='Train (last 3 years)')
plt.plot(test, 'b-', label='Actual', linewidth=2)
plt.plot(test.index, forecast_arima, 'r--', label='ARIMA')
plt.plot(test.index, forecast_sarima, 'g--', label='SARIMA')
plt.title('ARIMA vs SARIMA')
plt.legend()
plt.show()
mae_sarima = mean_absolute_error(test, forecast_sarima)
print(f"\nSARIMA MAE: {mae_sarima:.2f}")
모델 진단
# 055 잔차 분석
residuals = sarima_fitted.resid
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 055 잔차 플롯
axes[0, 0].plot(residuals)
axes[0, 0].set_title('Residuals')
axes[0, 0].axhline(y=0, color='r', linestyle='--')
# 055 히스토그램
axes[0, 1].hist(residuals, bins=30, edgecolor='black')
axes[0, 1].set_title('Residual Distribution')
# 055 ACF
plot_acf(residuals, ax=axes[1, 0], lags=20)
axes[1, 0].set_title('ACF of Residuals')
# 055 Q-Q Plot
from scipy import stats
stats.probplot(residuals, dist="norm", plot=axes[1, 1])
axes[1, 1].set_title('Q-Q Plot')
plt.tight_layout()
plt.show()
# 055 Ljung-Box 검정
from statsmodels.stats.diagnostic import acorr_ljungbox
lb_test = acorr_ljungbox(residuals, lags=[10], return_df=True)
print("\nLjung-Box 검정:")
print(f" p-value: {lb_test['lb_pvalue'].values[0]:.4f}")
print(f" → {'잔차에 자기상관 없음 (좋음)' if lb_test['lb_pvalue'].values[0] > 0.05 else '잔차에 자기상관 존재'}")
ARIMA vs FLAML
# 055 ARIMA는 전통적 통계 모델
# 055 FLAML은 ML 기반 접근
comparison = {
'특성': ['접근 방식', '장점', '단점', '적합한 경우'],
'ARIMA': [
'통계적 모델',
'해석 가능, 신뢰 구간',
'비선형 패턴 어려움',
'단변량, 선형 관계'
],
'FLAML': [
'ML 모델',
'비선형, 다변량 지원',
'블랙박스, 더 많은 데이터 필요',
'복잡한 패턴, 외부 변수'
]
}
print("\nARIMA vs FLAML:")
print(pd.DataFrame(comparison).to_string(index=False))
정리
- ARIMA(p,d,q): AR(p) + 차분(d) + MA(q)
- p 결정: PACF 절단점
- d 결정: ADF 검정으로 정상화에 필요한 차분 횟수
- q 결정: ACF 절단점
- SARIMA: 계절성 포함 (P, D, Q, m)
- auto_arima: 자동 파라미터 선택
- 잔차 분석으로 모델 적합성 확인
다음 글 예고
다음 글에서는 FLAML로 시계열 예측에 대해 알아보겠습니다. FLAML의 ML 기반 접근법으로 시계열을 예측하는 방법을 다룹니다.
FLAML AutoML 마스터 시리즈 #055