왜 시계열 분석인가?
주가 예측, 매출 추정, 수요 예측, 기상 분석… 시간 흐름에 따라 데이터 패턴을 읽고 미래를 예측하는 시계열 분석은, 솔직히 데이터 사이언스에서 가장 "써먹기 좋은" 스킬 중 하나입니다. 처음 시계열 모델로 다음 달 매출을 맞춰봤을 때의 그 쾌감은 아직도 기억나네요.
Python은 statsmodels 0.14, Prophet 1.3, pmdarima 같은 강력한 라이브러리들을 갖추고 있어서, 전통적인 통계 모델부터 딥러닝 기반 예측까지 하나의 언어로 전부 커버할 수 있습니다.
이 가이드에서는 시계열의 기초 개념부터 ARIMA, SARIMA, SARIMAX, Prophet까지 핵심 모델을 직접 돌려볼 수 있는 코드와 함께 단계별로 다룹니다. 한국어 자료에서 종종 빠지는 모델 진단, 잔차 분석, 교차검증, 성능 비교까지 전부 포함했으니, 끝까지 따라오시면 실무에 바로 적용할 수 있을 겁니다.
환경 설정 및 라이브러리 설치
먼저, 필요한 라이브러리를 한번에 설치합시다. Python 3.11 이상에 pandas 3.0+, NumPy 2.x 환경을 권장합니다. (그 이하 버전에서도 대부분 동작하지만, 간혹 호환성 이슈가 생길 수 있어요.)
pip install pandas numpy matplotlib statsmodels pmdarima prophet scikit-learn
기본 임포트 세트입니다.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rcParams
# 한글 폰트 설정 (macOS 예시)
rcParams['font.family'] = 'AppleGothic'
rcParams['axes.unicode_minus'] = False
import warnings
warnings.filterwarnings('ignore')
시계열 데이터 기초 개념
시계열 데이터란?
시계열 데이터(Time Series Data)는 일정한 시간 간격으로 기록된 관측값의 순서 있는 집합입니다. 주식 가격, 월별 매출, 일일 기온, 시간별 웹 트래픽 같은 것들이 전형적인 예죠.
일반적인 회귀 분석과 뭐가 다르냐면, 시계열에서는 시간 순서 자체가 핵심 정보라는 점입니다. 관측값끼리 시간적 의존성(자기상관)이 있고, 이걸 제대로 모델링하는 게 시계열 예측의 본질이에요.
시계열의 4가지 구성 요소
- 추세(Trend): 장기적인 증가 또는 감소 방향. 예를 들어, 10년간 매출이 꾸준히 올라가는 패턴
- 계절성(Seasonality): 고정된 주기로 반복되는 패턴. 여름마다 아이스크림 매출이 뛰거나, 연말에 소매 매출이 급증하는 것
- 순환(Cyclicity): 고정되지 않은 장기 파동. 경기순환처럼 주기가 들쭉날쭉함
- 불규칙 변동(Noise): 위 세 가지로 설명 안 되는 랜덤한 변동
정상성(Stationarity)이란?
ARIMA 계열 모델을 쓰려면 데이터가 정상성을 만족해야 합니다. 쉽게 말해, 시계열의 통계적 특성(평균, 분산, 자기상관)이 시간이 흘러도 변하지 않아야 한다는 겁니다.
- 정상 시계열: 평균과 분산이 일정하고, 자기상관 구조가 시간에 의존하지 않음
- 비정상 시계열: 추세나 계절성 때문에 평균이나 분산이 시간에 따라 변함
비정상 시계열은 차분(differencing)으로 정상 시계열로 바꿀 수 있습니다. 1차 차분은 현재 값에서 이전 값을 빼는 것이고, 계절 차분은 현재 값에서 한 주기 전 값을 빼는 거예요. 개념 자체는 단순하죠?
Pandas로 시계열 데이터 다루기
pandas 3.0부터 PyArrow 기반 시간 타입이 기본값이 되면서 시계열 처리 성능이 꽤 좋아졌습니다. 자, 실전 데이터를 로드해서 전처리하는 과정을 같이 살펴봅시다.
# 항공 승객 데이터 로드 (클래식 시계열 벤치마크)
url = 'https://raw.githubusercontent.com/jbrownlee/Datasets/master/airline-passengers.csv'
df = pd.read_csv(url, parse_dates=['Month'], index_col='Month')
df.columns = ['Passengers']
print(f"기간: {df.index.min()} ~ {df.index.max()}")
print(f"데이터 수: {len(df)}")
print(f"결측값: {df.isnull().sum().values[0]}")
df.head()
시계열 시각화
데이터를 분석하기 전에 반드시 시각화부터 해야 합니다. 추세, 계절성, 이상치를 눈으로 먼저 확인하는 게 정말 중요해요. 숫자만 보고 모델부터 돌리면 나중에 후회합니다.
fig, axes = plt.subplots(2, 1, figsize=(12, 8))
# 원본 시계열
axes[0].plot(df.index, df['Passengers'], color='#2196F3', linewidth=1.5)
axes[0].set_title('월별 항공 승객 수 (1949-1960)', fontsize=14)
axes[0].set_ylabel('승객 수')
# 12개월 이동 평균
df['MA12'] = df['Passengers'].rolling(window=12).mean()
axes[1].plot(df.index, df['Passengers'], alpha=0.5, label='원본')
axes[1].plot(df.index, df['MA12'], color='red', linewidth=2, label='12개월 이동평균')
axes[1].set_title('이동평균으로 본 장기 추세', fontsize=14)
axes[1].legend()
plt.tight_layout()
plt.show()
시계열 분해(Decomposition)
statsmodels의 seasonal_decompose를 쓰면 시계열을 추세, 계절, 잔차로 깔끔하게 분해할 수 있습니다.
from statsmodels.tsa.seasonal import seasonal_decompose
# multiplicative 모델 — 계절 진폭이 추세에 비례하여 증가할 때 적합
result = seasonal_decompose(df['Passengers'], model='multiplicative', period=12)
fig = result.plot()
fig.set_size_inches(12, 10)
fig.suptitle('시계열 분해: 추세 × 계절성 × 잔차', fontsize=14, y=1.02)
plt.tight_layout()
plt.show()
팁: 계절 변동이 추세 수준에 비례해서 커지면 multiplicative, 일정하면 additive를 씁니다. 항공 승객 데이터는 승객이 늘수록 계절 변동 폭도 같이 커지니까 multiplicative가 맞습니다.
정상성 검정: ADF 테스트
눈으로 보는 것만으로는 확신하기 어려울 때가 많습니다. 이때 ADF(Augmented Dickey-Fuller) 검정을 써서 통계적으로 정상성을 판단할 수 있어요. 제 경험상, 이 과정을 건너뛰면 나중에 모델 성능이 왜 안 나오는지 한참 헤매게 됩니다.
from statsmodels.tsa.stattools import adfuller
def adf_test(series, title=''):
result = adfuller(series.dropna(), autolag='AIC')
print(f'=== ADF 검정: {title} ===')
print(f'검정 통계량: {result[0]:.4f}')
print(f'p-value: {result[1]:.6f}')
print(f'사용된 래그: {result[2]}')
print(f'관측치 수: {result[3]}')
for key, val in result[4].items():
print(f' 임계값 ({key}): {val:.4f}')
if result[1] < 0.05:
print("→ 귀무가설 기각: 정상 시계열입니다.")
else:
print("→ 귀무가설 채택 불가: 비정상 시계열입니다. 차분이 필요합니다.")
return result[1]
# 원본 데이터 검정
p_original = adf_test(df['Passengers'], '원본 데이터')
# 1차 차분 후 검정
df['diff1'] = df['Passengers'].diff()
p_diff1 = adf_test(df['diff1'], '1차 차분')
원본 항공 데이터의 p-value는 보통 0.05보다 높게 나와서 비정상 시계열로 판정됩니다. 1차 차분을 하면 p-value가 확 낮아져서 정상성을 확보할 수 있어요.
ACF와 PACF로 모델 파라미터 결정
ARIMA의 p(AR 차수)와 q(MA 차수)를 정하는 가장 전통적인 방법은 ACF(자기상관함수)와 PACF(편자기상관함수) 플롯을 분석하는 겁니다. 솔직히 처음에는 이 그래프를 해석하는 게 좀 어렵게 느껴질 수 있는데, 몇 번 해보면 감이 옵니다.
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 1차 차분 데이터의 ACF / PACF
plot_acf(df['diff1'].dropna(), lags=40, ax=axes[0], title='ACF (1차 차분)')
plot_pacf(df['diff1'].dropna(), lags=40, ax=axes[1], title='PACF (1차 차분)')
plt.tight_layout()
plt.show()
해석 가이드
- PACF가 lag p 이후 급격히 0으로 떨어지면 → AR(p) 모델이 적합, 이걸로 p 값을 결정
- ACF가 lag q 이후 급격히 0으로 떨어지면 → MA(q) 모델이 적합, q 값 결정
- ACF에서 lag 12, 24, 36에 유의미한 스파이크가 보이면 → 계절성 존재, SARIMA를 써야 함
항공 승객 데이터의 ACF를 보면 12개월 간격으로 반복 패턴이 뚜렷하게 나타납니다. 계절성을 반영한 SARIMA가 필요하다는 신호죠.
ARIMA 모델 구축
일단 비계절 ARIMA부터 시작해서 기본적인 모델링 흐름을 익혀봅시다.
학습/테스트 분할
# 마지막 24개월을 테스트셋으로 분리
train = df['Passengers'][:-24]
test = df['Passengers'][-24:]
print(f"학습 데이터: {train.index.min()} ~ {train.index.max()} ({len(train)}개)")
print(f"테스트 데이터: {test.index.min()} ~ {test.index.max()} ({len(test)}개)")
ARIMA 모델 학습
from statsmodels.tsa.arima.model import ARIMA
# ARIMA(2,1,2) 모델
model_arima = ARIMA(train, order=(2, 1, 2))
result_arima = model_arima.fit()
print(result_arima.summary())
예측 및 시각화
# 24개월 예측
forecast_arima = result_arima.forecast(steps=24)
plt.figure(figsize=(12, 6))
plt.plot(train.index, train, label='학습 데이터', color='#2196F3')
plt.plot(test.index, test, label='실제값', color='#4CAF50', linewidth=2)
plt.plot(test.index, forecast_arima, label='ARIMA(2,1,2) 예측',
color='#FF5722', linestyle='--', linewidth=2)
plt.title('ARIMA(2,1,2) 예측 결과', fontsize=14)
plt.xlabel('날짜')
plt.ylabel('승객 수')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
결과를 보면 금방 알 수 있지만, ARIMA는 계절성을 모델링하지 않기 때문에 항공 데이터처럼 강한 계절 패턴이 있으면 예측이 뚝 떨어집니다. 이래서 SARIMA가 필요한 거예요.
SARIMA/SARIMAX로 계절성 모델링
SARIMA는 ARIMA에 계절 성분 (P, D, Q, m)을 추가한 모델입니다. 여기서 m은 계절 주기인데, 월별 데이터라면 12가 됩니다.
pmdarima로 최적 파라미터 자동 탐색
ACF/PACF를 눈으로 해석하는 게 부담스럽다면(저도 처음엔 그랬습니다), pmdarima의 auto_arima를 쓰면 AIC 기준으로 최적 파라미터를 자동으로 찾아줍니다.
from pmdarima import auto_arima
# 계절성 ARIMA 자동 탐색 (m=12: 월별 주기)
auto_model = auto_arima(
train,
seasonal=True,
m=12,
stepwise=True, # 단계적 탐색 (속도 향상)
suppress_warnings=True,
trace=True, # 탐색 과정 출력
error_action='ignore',
max_p=3, max_q=3,
max_P=2, max_Q=2,
max_d=2, max_D=1,
information_criterion='aic'
)
print(auto_model.summary())
SARIMAX 수동 구축
from statsmodels.tsa.statespace.sarimax import SARIMAX
# auto_arima 결과를 참고하여 SARIMAX 모델 구축
model_sarima = SARIMAX(
train,
order=(1, 1, 1), # (p, d, q) — 비계절 성분
seasonal_order=(1, 1, 1, 12), # (P, D, Q, m) — 계절 성분
enforce_stationarity=False,
enforce_invertibility=False
)
result_sarima = model_sarima.fit(disp=False)
print(result_sarima.summary())
SARIMA 예측 및 신뢰 구간
# 예측 + 95% 신뢰 구간
pred = result_sarima.get_forecast(steps=24)
forecast_sarima = pred.predicted_mean
conf_int = pred.conf_int()
plt.figure(figsize=(12, 6))
plt.plot(train.index, train, label='학습 데이터', color='#2196F3')
plt.plot(test.index, test, label='실제값', color='#4CAF50', linewidth=2)
plt.plot(test.index, forecast_sarima, label='SARIMA 예측',
color='#FF5722', linestyle='--', linewidth=2)
plt.fill_between(test.index, conf_int.iloc[:, 0], conf_int.iloc[:, 1],
color='#FF5722', alpha=0.15, label='95% 신뢰구간')
plt.title('SARIMA 예측 결과 (계절성 반영)', fontsize=14)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
SARIMA는 12개월 주기의 계절 패턴을 직접 학습하기 때문에, ARIMA와 비교하면 예측 정확도가 확연히 다릅니다. 그래프를 보면 차이가 바로 느껴질 거예요.
외생 변수 추가 (SARIMAX)
SARIMAX에서 'X'는 외생 변수(exogenous variable)를 뜻합니다. 시계열에 영향을 주는 외부 요인—예를 들어 유가, 환율, 프로모션 여부 같은 것—을 모델에 직접 넣을 수 있죠.
# 외생 변수 예시: 연도별 GDP 성장률을 외생 변수로 추가
# exog_train = train_exog_data # (n_train, n_features) 형태
# exog_test = test_exog_data # (n_test, n_features) 형태
# model_sarimax = SARIMAX(
# train,
# exog=exog_train,
# order=(1, 1, 1),
# seasonal_order=(1, 1, 1, 12)
# )
# result_sarimax = model_sarimax.fit(disp=False)
# forecast_sarimax = result_sarimax.forecast(steps=24, exog=exog_test)
모델 진단: 잔차 분석
좋은 모델이란 뭘까요? 간단히 말하면, 잔차(residual)에 더 이상 쓸 만한 정보가 남아 있지 않은 모델입니다. 잔차가 백색 잡음(white noise)처럼 보이는지 확인하는 4가지 진단 플롯을 만들어봅시다.
# statsmodels 내장 진단 플롯
fig = result_sarima.plot_diagnostics(figsize=(14, 10))
fig.suptitle('SARIMA 잔차 진단', fontsize=14, y=1.02)
plt.tight_layout()
plt.show()
진단 해석 가이드
- Standardized Residuals: 잔차가 평균 0 주위에 랜덤하게 흩어져 있어야 합니다. 뭔가 패턴이 보이면 모델이 놓친 정보가 있다는 뜻이에요.
- Histogram + KDE: 잔차 분포가 정규분포(N(0,1))에 가까울수록 좋습니다.
- Q-Q Plot: 점들이 대각선 위에 깔끔하게 놓여야 정규성을 만족합니다.
- Correlogram (ACF): 모든 lag에서 신뢰 구간 안에 들어와야 자기상관이 없는 겁니다.
Ljung-Box 검정
from statsmodels.stats.diagnostic import acorr_ljungbox
lb_result = acorr_ljungbox(result_sarima.resid, lags=[12, 24], return_df=True)
print(lb_result)
# p-value > 0.05이면 잔차에 유의미한 자기상관 없음 → 모델 적합
Prophet으로 쉽고 강력한 예측
Prophet은 Meta(구 Facebook)가 만든 시계열 예측 라이브러리입니다. 2026년 1월에 버전 1.3.0이 나왔고, 솔직히 비전문가가 시계열 예측을 시작하기에 이것만큼 편한 도구가 없습니다.
Prophet의 핵심 장점
- 자동 계절성 탐지: 연간, 주간, 일간 계절성을 알아서 잡아줌
- 공휴일 효과: 한국 공휴일 같은 특별 이벤트를 반영 가능
- 결측값 내성: 데이터에 빈 구간이 있어도 예측 가능 (이거 은근 큰 장점입니다)
- 변화점 감지: 추세가 꺾이는 시점을 자동으로 찾아줌
- 직관적 API: 몇 줄이면 예측 완료
Prophet 데이터 형식
Prophet은 ds(날짜)와 y(값) 두 컬럼만 있으면 됩니다. 심플하죠.
from prophet import Prophet
# Prophet 형식으로 변환
df_prophet = df[['Passengers']].reset_index()
df_prophet.columns = ['ds', 'y']
# 학습/테스트 분할
train_prophet = df_prophet[:-24]
test_prophet = df_prophet[-24:]
# 모델 학습
model_prophet = Prophet(
yearly_seasonality=True,
weekly_seasonality=False, # 월별 데이터이므로 비활성화
daily_seasonality=False,
seasonality_mode='multiplicative', # 계절 변동이 추세에 비례
changepoint_prior_scale=0.05 # 추세 변화 유연성
)
model_prophet.fit(train_prophet)
예측 수행
# 미래 24개월 데이터프레임 생성
future = model_prophet.make_future_dataframe(periods=24, freq='MS')
# 예측
forecast = model_prophet.predict(future)
# 시각화
fig1 = model_prophet.plot(forecast)
plt.title('Prophet 예측 결과', fontsize=14)
plt.xlabel('날짜')
plt.ylabel('승객 수')
plt.show()
# 구성 요소 분해
fig2 = model_prophet.plot_components(forecast)
plt.show()
한국 공휴일 반영
Prophet은 한국 공휴일을 기본으로 지원합니다. 일별 데이터를 다룰 때 특히 유용해요.
# 한국 공휴일 추가
model_holidays = Prophet(yearly_seasonality=True)
model_holidays.add_country_holidays(country_name='KR')
model_holidays.fit(train_prophet)
# 어떤 공휴일이 포함되었는지 확인
print(model_holidays.train_holiday_names)
커스텀 계절성 추가
# 분기별 계절성 추가 예시
model_custom = Prophet()
model_custom.add_seasonality(
name='quarterly',
period=91.25, # 약 3개월
fourier_order=8 # 푸리에 급수 차수 (복잡도)
)
model_custom.fit(train_prophet)
모델 성능 비교
자, 이제 ARIMA, SARIMA, Prophet 세 모델의 예측 성능을 숫자로 비교해봅시다. 감으로 "이게 더 나은 것 같다"가 아니라 지표로 확인하는 게 중요합니다.
평가 지표
from sklearn.metrics import mean_absolute_error, mean_squared_error, mean_absolute_percentage_error
def evaluate_forecast(actual, predicted, model_name):
mae = mean_absolute_error(actual, predicted)
rmse = np.sqrt(mean_squared_error(actual, predicted))
mape = mean_absolute_percentage_error(actual, predicted) * 100
print(f"--- {model_name} ---")
print(f" MAE: {mae:.2f}")
print(f" RMSE: {rmse:.2f}")
print(f" MAPE: {mape:.2f}%")
return {'model': model_name, 'MAE': mae, 'RMSE': rmse, 'MAPE': mape}
# 각 모델 평가
results = []
results.append(evaluate_forecast(test, forecast_arima, 'ARIMA(2,1,2)'))
results.append(evaluate_forecast(test, forecast_sarima, 'SARIMA(1,1,1)(1,1,1,12)'))
# Prophet 예측값 추출
prophet_pred = forecast.set_index('ds').loc[test.index, 'yhat']
results.append(evaluate_forecast(test, prophet_pred, 'Prophet'))
# 결과 비교 테이블
comparison = pd.DataFrame(results).set_index('model')
print("\n=== 모델 성능 비교 ===")
print(comparison.round(2))
시각적 비교
plt.figure(figsize=(14, 7))
plt.plot(test.index, test, label='실제값', color='black', linewidth=2)
plt.plot(test.index, forecast_arima, label='ARIMA', linestyle='--', alpha=0.8)
plt.plot(test.index, forecast_sarima, label='SARIMA', linestyle='--', alpha=0.8)
plt.plot(test.index, prophet_pred, label='Prophet', linestyle='--', alpha=0.8)
plt.title('모델별 예측 성능 비교', fontsize=14)
plt.xlabel('날짜')
plt.ylabel('승객 수')
plt.legend(fontsize=12)
plt.grid(True, alpha=0.3)
plt.show()
모델 선택 가이드라인
| 상황 | 추천 모델 | 이유 |
|---|---|---|
| 계절성 없는 단순 시계열 | ARIMA | 가볍고 빠르며 해석이 쉬움 |
| 강한 계절성 + 추세 | SARIMA | 계절 패턴을 직접 모델링 |
| 외부 변수 영향 존재 | SARIMAX | 외생 변수 통합 가능 |
| 비전문가도 빠르게 적용 | Prophet | 자동 튜닝, 결측 내성 |
| 공휴일/이벤트 효과 중요 | Prophet | 공휴일 내장 지원 |
| 여러 시계열 동시 예측 | NeuralProphet | 글로벌 모델링 지원 |
교차검증으로 모델 안정성 확인
시계열에서는 일반적인 K-Fold 교차검증을 쓰면 안 됩니다. 시간 순서가 뒤섞이니까요. 대신 시계열 교차검증(Time Series Cross-Validation)으로 시간 순서를 유지하면서 검증해야 합니다.
Prophet 내장 교차검증
from prophet.diagnostics import cross_validation, performance_metrics
# Prophet 교차검증
# initial: 학습 기간, period: 슬라이딩 간격, horizon: 예측 기간
df_cv = cross_validation(
model_prophet,
initial='730 days', # 최소 2년 학습
period='180 days', # 6개월마다 검증
horizon='365 days' # 1년 예측
)
# 성능 지표 계산
df_metrics = performance_metrics(df_cv)
print(df_metrics[['horizon', 'mape', 'rmse', 'mae']].tail())
sklearn TimeSeriesSplit
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
scores = []
for fold, (train_idx, test_idx) in enumerate(tscv.split(df['Passengers'])):
train_fold = df['Passengers'].iloc[train_idx]
test_fold = df['Passengers'].iloc[test_idx]
model = SARIMAX(train_fold, order=(1,1,1), seasonal_order=(1,1,1,12),
enforce_stationarity=False, enforce_invertibility=False)
fit = model.fit(disp=False)
pred = fit.forecast(steps=len(test_fold))
mape = mean_absolute_percentage_error(test_fold, pred) * 100
scores.append(mape)
print(f"Fold {fold+1}: MAPE = {mape:.2f}%")
print(f"\n평균 MAPE: {np.mean(scores):.2f}% (±{np.std(scores):.2f}%)")
NeuralProphet: 딥러닝 기반 시계열 예측
NeuralProphet은 Prophet의 아이디어를 PyTorch 기반 신경망으로 확장한 라이브러리입니다(최신 버전 0.9.0). AR-Net 자기회귀, 지연 회귀 변수, 글로벌 모델링 같은 Prophet에 없는 기능들을 제공하는데, 개인적으로는 데이터가 충분할 때 한번 시도해볼 만한 옵션이라고 생각합니다.
pip install neuralprophet
from neuralprophet import NeuralProphet
# NeuralProphet 모델
model_np = NeuralProphet(
n_forecasts=24, # 24개월 예측
n_lags=12, # 과거 12개월 참조 (AR 성분)
yearly_seasonality=True,
weekly_seasonality=False,
daily_seasonality=False,
learning_rate=0.01,
epochs=200
)
# 학습
metrics = model_np.fit(train_prophet, freq='MS')
# 예측
future_np = model_np.make_future_dataframe(train_prophet, periods=24)
forecast_np = model_np.predict(future_np)
# 시각화
fig = model_np.plot(forecast_np)
plt.show()
NeuralProphet은 비선형 관계를 학습할 수 있어서 복잡한 시계열에서 전통 모델보다 나은 성능을 보일 수 있습니다. 하지만 데이터가 적으면 과적합 위험이 꽤 크니까, 무조건 딥러닝이 좋다는 생각은 버리는 게 좋습니다.
실전 팁과 주의사항
흔한 실수 5가지
- 미래 정보 누출(Data Leakage): 테스트 데이터로 정상성 검정이나 파라미터 선택을 하면 절대 안 됩니다. 반드시 학습 데이터만 써야 해요.
- 로그 변환 누락: 분산이 시간에 따라 커지면
np.log()로 변환 후 모델링하고, 예측 후np.exp()로 복원하세요. - 계절 차분 무시: 1차 차분만으로 정상성이 안 잡히면 계절 차분(D=1)도 꼭 적용해보세요.
- 과적합: ARIMA의 p, q를 너무 크게 잡으면 학습 데이터에만 들어맞는 모델이 됩니다. AIC/BIC 기준으로 고르는 습관을 들이세요.
- 잔차 진단 생략: 예측 정확도 수치만 보고 잔차 분석을 건너뛰는 분들이 많은데, 이러면 모델의 구조적 문제를 놓칩니다.
성능 향상 체크리스트
- ☑ 로그 변환 또는 Box-Cox 변환으로 분산 안정화
- ☑ 계절 차분 + 일반 차분 조합 시도
- ☑ auto_arima 결과를 출발점으로, 인접 파라미터도 테스트
- ☑ Prophet의
changepoint_prior_scale튜닝 (0.001 ~ 0.5) - ☑ 시계열 교차검증으로 모든 모델 비교
- ☑ 앙상블: 여러 모델 예측의 가중 평균 고려
자주 묻는 질문 (FAQ)
ARIMA와 SARIMA의 차이점은 무엇인가요?
ARIMA는 추세와 자기상관을 모델링하지만 계절성은 못 잡습니다. SARIMA는 여기에 계절 성분 (P, D, Q, m)을 얹어서 월별, 분기별 같은 반복 패턴까지 모델링합니다. 데이터에 계절성이 있으면 SARIMA, 없으면 ARIMA를 쓰면 됩니다.
Prophet과 ARIMA 중 뭘 먼저 써야 하나요?
빠른 프로토타이핑이 필요하면 Prophet부터 시작하세요. 설정 거의 없이 그럴듯한 결과를 내줍니다. 모델을 세밀하게 컨트롤하고 싶거나 통계적 해석이 중요한 상황이라면 ARIMA/SARIMA가 낫습니다. 사실 실무에서 가장 좋은 방법은 둘 다 돌려보고 교차검증으로 비교하는 겁니다.
시계열 데이터에 결측값이 있으면 어떻게 하나요?
ARIMA 계열은 결측값을 허용하지 않아서 미리 처리해야 합니다. 보간법(df.interpolate(method='time')), 전방 채움(df.ffill()), 혹은 계절 분해 후 추세 기반 보간 등을 쓸 수 있어요. Prophet은 결측값을 알아서 처리하니 별도 작업이 필요 없습니다.
auto_arima 결과를 그대로 써도 되나요?
좋은 출발점이긴 하지만, 반드시 잔차 진단은 해봐야 합니다. AIC가 가장 낮아도 잔차에 자기상관이 남아 있으면 파라미터를 수정해야 해요. auto_arima가 찾은 값 주변의 인접 파라미터도 같이 테스트하는 걸 추천합니다.
짧은 시계열(관측치 50개 미만)에서도 분석이 되나요?
되긴 하지만 제약이 있습니다. SARIMA는 최소 2~3 계절 주기(월별이면 24~36개)의 데이터가 필요해요. 데이터가 부족하면 단순 지수평활(Simple Exponential Smoothing)이나 Prophet(적은 데이터에서도 비교적 안정적)을 먼저 시도해보세요. 데이터가 적은데 복잡한 모델을 쓰면 과적합이 거의 확실합니다.