11. Time Series (시계열 분석)
ARIMA, 계절성, 예측
학습 목표
이 튜토리얼을 완료하면 다음을 할 수 있습니다:
- 시계열 데이터의 특성 이해 (추세, 계절성, 정상성)
- 시계열 분해 (Decomposition) 수행
- 정상성 검정 (ADF Test) 실행 및 해석
- ARIMA 모델 이해 및 적용
- ACF/PACF를 활용한 파라미터 선택
- 예측 성능 평가 (RMSE, MAE, MAPE)
핵심 개념
1. 시계열 데이터란?
시간 순서로 정렬된 데이터로, 일반 데이터와는 다른 특별한 특성을 가집니다.
| 특성 | 설명 |
|---|---|
| 순서 중요 | 데이터 순서에 의미가 있음 |
| 자기상관 | 과거 값이 미래에 영향 |
시계열의 3가지 구성 요소
| 구성 요소 | 설명 |
|---|---|
| Trend (추세) | 장기적 증가/감소 패턴 |
| Seasonality (계절성) | 주기적 반복 패턴 |
| Noise (노이즈) | 불규칙한 변동 |
# 시계열 데이터 생성 예시
import numpy as np
import pandas as pd
np.random.seed(42)
dates = pd.date_range(start='2020-01-01', periods=730, freq='D')
# 구성 요소
trend = np.linspace(100, 200, 730) # 선형 추세
seasonality = 30 * np.sin(2 * np.pi * np.arange(730) / 365) # 연간 계절성
weekly = 10 * np.sin(2 * np.pi * np.arange(730) / 7) # 주간 패턴
noise = np.random.normal(0, 10, 730) # 노이즈
# 최종 시계열
values = trend + seasonality + weekly + noise
df = pd.DataFrame({'date': dates, 'sales': values})
df.set_index('date', inplace=True)2. 시계열 분해 (Decomposition)
시계열을 추세, 계절성, 잔차로 분해하여 각 구성 요소를 분석합니다.
가법 모델 vs 승법 모델
| 모델 | 수식 | 사용 시점 |
|---|---|---|
| 가법 (Additive) | Yt = Tt + St + Rt | 계절 변동이 일정할 때 |
| 승법 (Multiplicative) | Yt = Tt × St × Rt | 추세에 따라 계절 변동이 커질 때 |
- T: Trend (추세)
- S: Seasonal (계절성)
- R: Residual (잔차)
from statsmodels.tsa.seasonal import seasonal_decompose
# 가법 모델로 분해
decomposition = seasonal_decompose(df['sales'], model='additive', period=365)
# 각 구성 요소 접근
print(f'추세 시작: {decomposition.trend.dropna().iloc[0]:.2f}')
print(f'계절성 범위: [{decomposition.seasonal.min():.2f}, {decomposition.seasonal.max():.2f}]')
print(f'잔차 표준편차: {decomposition.resid.std():.2f}')항공 승객 데이터처럼 추세가 증가하면서 계절 변동도 커지는 경우 승법 모델을 사용합니다.
3. 정상성 (Stationarity)
시계열 분석의 핵심 가정: 통계적 특성이 시간에 따라 일정
| 조건 | 설명 |
|---|---|
| 일정한 평균 | E[Yt] = μ |
| 일정한 분산 | Var(Yt) = σ² |
| 자기공분산 | Cov(Yt, Yt-k) = γk (시차에만 의존) |
왜 정상성이 중요한가? 대부분의 시계열 모델(ARIMA 등)은 정상성을 가정합니다. 비정상 시계열은 변환이 필요합니다.
정상성 검정 (ADF Test)
ADF(Augmented Dickey-Fuller) 검정으로 정상성을 확인합니다.
from statsmodels.tsa.stattools import adfuller
def adf_test(series, name=''):
result = adfuller(series.dropna(), autolag='AIC')
print(f'=== ADF Test: {name} ===')
print(f'ADF Statistic: {result[0]:.4f}')
print(f'p-value: {result[1]:.4f}')
print(f'Critical Values:')
for key, value in result[4].items():
print(f' {key}: {value:.4f}')
if result[1] < 0.05:
print('\n→ 정상 시계열 (Stationary)')
else:
print('\n→ 비정상 시계열 (Non-stationary)')
adf_test(df['sales'], '원본 시계열')
# p-value < 0.05 → 정상성비정상 → 정상 변환
# 1차 차분
series_diff = series.diff().dropna()
# 로그 변환 + 차분 (분산이 커지는 경우)
series_log_diff = np.log(series).diff().dropna()4. ACF와 PACF
ARIMA 모델의 파라미터를 결정하는 핵심 도구입니다.
| 지표 | 의미 | 수식 | ARIMA 적용 |
|---|---|---|---|
| ACF | 시차별 상관관계 | Corr(Yt, Yt-k) | MA(q) 차수 결정 |
| PACF | 순수 시차 상관관계 | 중간 시차 영향 제거 | AR(p) 차수 결정 |
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
plot_acf(series_diff, ax=axes[0], lags=50, alpha=0.05)
plot_pacf(series_diff, ax=axes[1], lags=50, alpha=0.05)
plt.tight_layout()
plt.show()ACF/PACF 해석 가이드
| 패턴 | ACF | PACF | 모델 |
|---|---|---|---|
| AR(p) | 지수적 감소 | p에서 절단 | ARIMA(p,d,0) |
| MA(q) | q에서 절단 | 지수적 감소 | ARIMA(0,d,q) |
| ARMA | 지수적 감소 | 지수적 감소 | ARIMA(p,d,q) |
5. ARIMA 모델
AutoRegressive Integrated Moving Average
ARIMA(p, d, q):
- p: AR (자기회귀) 차수 - PACF로 결정
- d: 차분 횟수 - 정상성 확보에 필요한 차분 수
- q: MA (이동평균) 차수 - ACF로 결정
from statsmodels.tsa.arima.model import ARIMA
# Train/Test 분할 (시간 순서 유지!)
train_size = int(len(df) * 0.8)
train = df['sales'][:train_size]
test = df['sales'][train_size:]
# 모델 생성 및 학습
model = ARIMA(train, order=(2, 1, 2))
model_fit = model.fit()
# 예측
forecast = model_fit.forecast(steps=len(test))
# 요약
print(model_fit.summary())주의: 시계열 데이터는 반드시 시간 순서대로 분할해야 합니다. 무작위 분할은 미래 정보 누출(Data Leakage)을 일으킵니다!
파라미터 자동 선택 (Grid Search)
from itertools import product
p_values = range(0, 4)
d_values = range(0, 2)
q_values = range(0, 4)
results = []
for p, d, q in product(p_values, d_values, q_values):
try:
model = ARIMA(train, order=(p, d, q))
model_fit = model.fit()
aic = model_fit.aic
results.append({'Order': f'({p},{d},{q})', 'AIC': aic})
except:
continue
results_df = pd.DataFrame(results)
print(results_df.nsmallest(5, 'AIC')) # AIC가 낮을수록 좋음6. 계절성 ARIMA (SARIMA)
계절성이 있는 데이터에는 SARIMA를 사용합니다.
SARIMA(p, d, q)(P, D, Q, s):
- (p, d, q): 비계절 파라미터
- (P, D, Q, s): 계절 파라미터, s=주기
from statsmodels.tsa.statespace.sarimax import SARIMAX
model = SARIMAX(train,
order=(1, 1, 1),
seasonal_order=(1, 1, 1, 12)) # 월별 데이터
model_fit = model.fit(disp=False)7. 자동 ARIMA
pmdarima 라이브러리로 최적 파라미터를 자동으로 찾습니다.
from pmdarima import auto_arima
auto_model = auto_arima(
train,
seasonal=True,
m=12, # 계절 주기
trace=True,
error_action='ignore',
suppress_warnings=True
)
print(auto_model.summary())8. 잔차 진단
좋은 모델은 잔차가 백색 잡음(White Noise)이어야 합니다.
from statsmodels.stats.diagnostic import acorr_ljungbox
residuals = model_fit.resid
# Ljung-Box 검정
lb_result = acorr_ljungbox(residuals, lags=[10, 20, 30], return_df=True)
print(lb_result)
# p-value > 0.05면 잔차에 자기상관 없음 (Good!)잔차 진단 체크리스트:
- 잔차가 0 주위에서 무작위로 분포
- 잔차의 ACF가 유의하지 않음
- 잔차가 정규분포를 따름 (Q-Q Plot 확인)
9. 이동평균 기반 예측
간단하지만 효과적인 베이스라인 모델입니다.
# 단순 이동평균 (SMA)
df['SMA_7'] = df['sales'].rolling(window=7).mean()
df['SMA_30'] = df['sales'].rolling(window=30).mean()
# 지수 이동평균 (EMA) - 최근 값에 더 큰 가중치
df['EMA_7'] = df['sales'].ewm(span=7).mean()
df['EMA_30'] = df['sales'].ewm(span=30).mean()10. 시계열 Cross-Validation
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
for train_idx, test_idx in tscv.split(X):
X_train, X_test = X[train_idx], X[test_idx]
y_train, y_test = y[train_idx], y[test_idx]코드 요약
import pandas as pd
import numpy as np
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.stattools import adfuller
from sklearn.metrics import mean_squared_error, mean_absolute_error
# 1. 정상성 검정
result = adfuller(series.dropna())
print(f'ADF p-value: {result[1]:.4f}')
# 2. 필요시 차분
series_diff = series.diff().dropna()
# 3. 데이터 분할 (시간 순서 유지)
train_size = int(len(series) * 0.8)
train, test = series[:train_size], series[train_size:]
# 4. ARIMA 모델
model = ARIMA(train, order=(2, 1, 2))
model_fit = model.fit()
# 5. 예측
forecast = model_fit.forecast(steps=len(test))
# 6. 평가
rmse = np.sqrt(mean_squared_error(test, forecast))
mae = mean_absolute_error(test, forecast)
mape = np.mean(np.abs((test - forecast) / test)) * 100
print(f"RMSE: {rmse:.4f}")
print(f"MAE: {mae:.4f}")
print(f"MAPE: {mape:.2f}%")평가 지표
| 지표 | 공식 | 특징 |
|---|---|---|
| RMSE | √(MSE) | 큰 오차에 민감 |
| MAE | `Mean( | error |
| MAPE | `Mean( | error/y |
시계열 예측 Best Practices
체크리스트
- 데이터 탐색: 추세, 계절성, 이상치 확인 및 시계열 분해
- 정상성 확보: ADF 검정 후 필요시 차분/로그 변환
- 모델 선택: ACF/PACF 분석, AIC/BIC 기반 파라미터 선택
- 잔차 진단: 잔차 자기상관 확인, 정규성 검정
- 예측 평가: 시간 기준 분할, Rolling window cross-validation
자주하는 실수
| 실수 | 올바른 방법 |
|---|---|
| 무작위 Train/Test 분할 | 시간 순서 기준 분할 |
| 미래 정보 사용 | 과거 데이터만 사용 |
| 정상성 검정 생략 | ADF 검정 필수 |
| 단일 평가 지표 | RMSE, MAE, MAPE 종합 평가 |
면접 질문 맛보기
- 정상성이란 무엇이고 왜 중요한가요?
- ARIMA의 p, d, q는 어떻게 결정하나요?
- 시계열 데이터의 Train/Test Split 주의점은?
- ACF와 PACF의 차이점은 무엇인가요?
- 가법 모델과 승법 모델은 언제 각각 사용하나요?
더 많은 면접 질문은 Premium Interviews (opens in a new tab)에서 확인하세요.
실습 노트북
노트북에서는 합성 데이터와 실제 항공 승객 데이터를 사용한 실습, 다양한 이동평균 비교, 잔차 진단 시각화, 그리고 연습 문제를 추가로 다룹니다.
이전: 10. Imbalanced Data | 다음: 12. Neural Network