본문으로 건너뛰기

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