튜토리얼
11. Time Series

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 해석 가이드

패턴ACFPACF모델
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!)
💡

잔차 진단 체크리스트:

  1. 잔차가 0 주위에서 무작위로 분포
  2. 잔차의 ACF가 유의하지 않음
  3. 잔차가 정규분포를 따름 (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

체크리스트

  1. 데이터 탐색: 추세, 계절성, 이상치 확인 및 시계열 분해
  2. 정상성 확보: ADF 검정 후 필요시 차분/로그 변환
  3. 모델 선택: ACF/PACF 분석, AIC/BIC 기반 파라미터 선택
  4. 잔차 진단: 잔차 자기상관 확인, 정규성 검정
  5. 예측 평가: 시간 기준 분할, Rolling window cross-validation

자주하는 실수

실수올바른 방법
무작위 Train/Test 분할시간 순서 기준 분할
미래 정보 사용과거 데이터만 사용
정상성 검정 생략ADF 검정 필수
단일 평가 지표RMSE, MAE, MAPE 종합 평가

면접 질문 맛보기

  1. 정상성이란 무엇이고 왜 중요한가요?
  2. ARIMA의 p, d, q는 어떻게 결정하나요?
  3. 시계열 데이터의 Train/Test Split 주의점은?
  4. ACF와 PACF의 차이점은 무엇인가요?
  5. 가법 모델과 승법 모델은 언제 각각 사용하나요?

더 많은 면접 질문은 Premium Interviews (opens in a new tab)에서 확인하세요.


실습 노트북

노트북에서는 합성 데이터와 실제 항공 승객 데이터를 사용한 실습, 다양한 이동평균 비교, 잔차 진단 시각화, 그리고 연습 문제를 추가로 다룹니다.


이전: 10. Imbalanced Data | 다음: 12. Neural Network