본문으로 건너뛰기

073 시계열 예측의 이해

키워드: 시계열, time series

개요

시계열 예측(Time Series Forecasting)은 과거 데이터의 패턴을 분석하여 미래 값을 예측하는 기법입니다. 판매량, 주가, 수요, 날씨 등 시간에 따라 변하는 데이터 예측에 활용됩니다.

실습 환경

  • Python 버전: 3.11 권장
  • 필요 패키지: pycaret[full]>=3.0

시계열이란?

시계열 데이터:
시간 순서대로 기록된 데이터

t₁ → t₂ → t₃ → t₄ → t₅ → ... → tₙ → ?
y₁ y₂ y₃ y₄ y₅ yₙ ?

특징:
- 순서가 중요 (셔플 불가)
- 시간 의존성 (과거가 미래에 영향)
- 자기 상관 (autocorrelation)

시계열 데이터 예시

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# 073 시계열 데이터 생성
np.random.seed(42)
dates = pd.date_range('2020-01-01', periods=365*3, freq='D') # 3년

# 073 구성 요소
trend = np.linspace(100, 200, len(dates)) # 상승 추세
seasonality = 30 * np.sin(2 * np.pi * np.arange(len(dates)) / 365) # 연간 계절성
weekly = 10 * np.sin(2 * np.pi * np.arange(len(dates)) / 7) # 주간 패턴
noise = np.random.normal(0, 10, len(dates)) # 노이즈

# 073 결합
values = trend + seasonality + weekly + noise

# 073 데이터프레임
df = pd.DataFrame({
'date': dates,
'value': values
})
df.set_index('date', inplace=True)

print(f"데이터 기간: {df.index.min()} ~ {df.index.max()}")
print(f"데이터 수: {len(df)}")
print(df.head())

시계열의 구성 요소

import matplotlib.pyplot as plt
from statsmodels.tsa.seasonal import seasonal_decompose

# 073 분해
decomposition = seasonal_decompose(df['value'], model='additive', period=365)

fig, axes = plt.subplots(4, 1, figsize=(14, 12))

axes[0].plot(df.index, df['value'])
axes[0].set_title('Original')
axes[0].set_ylabel('Value')

axes[1].plot(df.index, decomposition.trend)
axes[1].set_title('Trend')
axes[1].set_ylabel('Value')

axes[2].plot(df.index, decomposition.seasonal)
axes[2].set_title('Seasonality')
axes[2].set_ylabel('Value')

axes[3].plot(df.index, decomposition.resid)
axes[3].set_title('Residual')
axes[3].set_ylabel('Value')

plt.tight_layout()
plt.savefig('time_series_decomposition.png', dpi=150)

구성 요소 설명

1. 추세 (Trend)
- 장기적인 상승/하락 경향
- 선형 or 비선형

2. 계절성 (Seasonality)
- 일정 주기로 반복되는 패턴
- 일별, 주별, 월별, 연별

3. 순환 (Cycle)
- 불규칙한 주기의 변동
- 경기 순환 등

4. 잔차 (Residual)
- 설명되지 않는 변동
- 노이즈

정상성 (Stationarity)

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from statsmodels.tsa.stattools import adfuller

# 073 정상성 테스트 (ADF 테스트)
def check_stationarity(series, name):
result = adfuller(series.dropna())
print(f"\n{name}")
print(f"ADF Statistic: {result[0]:.4f}")
print(f"p-value: {result[1]:.4f}")
print("정상성:", "Yes" if result[1] < 0.05 else "No")
return result[1] < 0.05

# 073 원본 데이터
is_stationary = check_stationarity(df['value'], "Original Series")

# 073 차분 (Differencing)
df['diff1'] = df['value'].diff()
is_stationary_diff1 = check_stationarity(df['diff1'], "First Difference")

# 073 시각화
fig, axes = plt.subplots(2, 1, figsize=(14, 8))

axes[0].plot(df.index, df['value'])
axes[0].set_title('Original (Non-stationary)')

axes[1].plot(df.index, df['diff1'])
axes[1].set_title('First Difference (Stationary)')

plt.tight_layout()
plt.savefig('stationarity.png', dpi=150)

정상성이 중요한 이유

정상 시계열:
- 평균과 분산이 시간에 따라 일정
- 많은 통계 모델의 가정
- 예측 안정성 향상

비정상 → 정상 변환:
- 차분 (Differencing)
- 로그 변환
- 계절 차분

자기상관 (Autocorrelation)

from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
import matplotlib.pyplot as plt

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 073 ACF (자기상관함수)
plot_acf(df['value'].dropna(), lags=50, ax=axes[0])
axes[0].set_title('Autocorrelation Function (ACF)')

# 073 PACF (편자기상관함수)
plot_pacf(df['value'].dropna(), lags=50, ax=axes[1])
axes[1].set_title('Partial Autocorrelation Function (PACF)')

plt.tight_layout()
plt.savefig('acf_pacf.png', dpi=150)

ACF와 PACF 해석

ACF (Autocorrelation Function):
- lag k에서의 상관관계
- 간접 영향 포함

PACF (Partial ACF):
- 중간 lag 영향 제거 후 상관관계
- 직접 영향만

모델 선택:
- ACF 지수 감소, PACF p에서 절단 → AR(p)
- ACF q에서 절단, PACF 지수 감소 → MA(q)
- 둘 다 지수 감소 → ARMA(p, q)

시계열 예측 방법론

1. 통계적 방법

# 073 ARIMA, SARIMA, ETS 등

# 073 특징:
# 073 - 수학적 기반
# 073 - 해석 용이
# 073 - 적은 데이터로도 가능

2. 머신러닝 방법

# 073 Random Forest, XGBoost, LightGBM 등

# 073 특징:
# 073 - 비선형 패턴 학습
# 073 - 외부 변수 활용 용이
# 073 - 특성 엔지니어링 필요

3. 딥러닝 방법

# 073 LSTM, Transformer 등

# 073 특징:
# 073 - 복잡한 패턴 학습
# 073 - 많은 데이터 필요
# 073 - 긴 시퀀스 처리 가능

간단한 예측 예제

from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error, mean_squared_error
import numpy as np
import pandas as pd

# 073 특성 엔지니어링
df_features = df.copy()
df_features['year'] = df_features.index.year
df_features['month'] = df_features.index.month
df_features['day'] = df_features.index.day
df_features['dayofweek'] = df_features.index.dayofweek
df_features['dayofyear'] = df_features.index.dayofyear

# 073 래그 특성
for lag in [1, 7, 14, 30, 365]:
df_features[f'lag_{lag}'] = df_features['value'].shift(lag)

# 073 이동 평균
df_features['rolling_7'] = df_features['value'].shift(1).rolling(7).mean()
df_features['rolling_30'] = df_features['value'].shift(1).rolling(30).mean()

# 073 NaN 제거
df_features = df_features.dropna()

# 073 학습/테스트 분할 (시간 순서 유지)
train_size = int(len(df_features) * 0.8)
train = df_features[:train_size]
test = df_features[train_size:]

feature_cols = ['year', 'month', 'day', 'dayofweek', 'dayofyear',
'lag_1', 'lag_7', 'lag_14', 'lag_30', 'lag_365',
'rolling_7', 'rolling_30']

X_train = train[feature_cols]
y_train = train['value']
X_test = test[feature_cols]
y_test = test['value']

# 073 모델 학습
model = RandomForestRegressor(n_estimators=100, random_state=42)
model.fit(X_train, y_train)

# 073 예측
y_pred = model.predict(X_test)

# 073 평가
mae = mean_absolute_error(y_test, y_pred)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
mape = np.mean(np.abs((y_test - y_pred) / y_test)) * 100

print(f"MAE: {mae:.2f}")
print(f"RMSE: {rmse:.2f}")
print(f"MAPE: {mape:.2f}%")

# 073 시각화
import matplotlib.pyplot as plt

plt.figure(figsize=(14, 6))
plt.plot(test.index, y_test, label='Actual', alpha=0.7)
plt.plot(test.index, y_pred, label='Predicted', alpha=0.7)
plt.xlabel('Date')
plt.ylabel('Value')
plt.title('Time Series Prediction')
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig('simple_forecast.png', dpi=150)

예측 평가 지표

import numpy as np

def evaluate_forecast(y_true, y_pred):
"""시계열 예측 평가 지표"""

# MAE (Mean Absolute Error)
mae = np.mean(np.abs(y_true - y_pred))

# RMSE (Root Mean Squared Error)
rmse = np.sqrt(np.mean((y_true - y_pred) ** 2))

# MAPE (Mean Absolute Percentage Error)
mape = np.mean(np.abs((y_true - y_pred) / y_true)) * 100

# SMAPE (Symmetric MAPE)
smape = np.mean(2 * np.abs(y_true - y_pred) / (np.abs(y_true) + np.abs(y_pred))) * 100

# R² Score
ss_res = np.sum((y_true - y_pred) ** 2)
ss_tot = np.sum((y_true - np.mean(y_true)) ** 2)
r2 = 1 - (ss_res / ss_tot)

return {
'MAE': mae,
'RMSE': rmse,
'MAPE': mape,
'SMAPE': smape,
'R2': r2
}

metrics = evaluate_forecast(y_test.values, y_pred)
for name, value in metrics.items():
print(f"{name}: {value:.4f}")

지표별 특징

MAE:
- 해석 용이 (원본 단위)
- 이상치에 덜 민감

RMSE:
- 큰 오차에 페널티
- 최적화에 많이 사용

MAPE:
- 비율 기반 (% 단위)
- 0에 가까운 값에 불안정

SMAPE:
- MAPE 개선 버전
- 대칭적

R²:
- 설명력 (0~1)
- 1에 가까울수록 좋음

시계열 예측의 어려움

1. 데이터 누출 (Data Leakage)
- 미래 정보가 학습에 사용됨
- 시간 순서 교차 검증 필요

2. 컨셉 드리프트
- 시간에 따라 패턴 변화
- 모델 주기적 업데이트 필요

3. 외부 요인
- 휴일, 이벤트, 경제 상황
- 외생 변수 고려 필요

4. 다중 계절성
- 일별 + 주별 + 연별
- 복합 패턴 모델링

정리

  • 시계열은 시간 순서대로 기록된 데이터
  • 추세, 계절성, 잔차로 분해
  • 정상성 확인 필수
  • 시간 순서 유지하여 분할
  • MAE, RMSE, MAPE 등으로 평가

다음 글 예고

다음 글에서는 PyCaret 시계열 모듈 시작하기를 다룹니다.


PyCaret 머신러닝 마스터 시리즈 #073