🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

SARIMA 계절성을 고려한 시계열 예측 모델 - 슬라이드 1/9
A

AI Generated

2025. 12. 3. · 10 Views

SARIMA 계절성을 고려한 시계열 예측 모델

시계열 데이터에서 계절성 패턴을 포착하는 SARIMA 모델의 개념과 구현 방법을 알아봅니다. 여름에 아이스크림 판매가 늘고 겨울에 난방비가 오르는 것처럼, 반복되는 패턴을 예측 모델에 녹여내는 방법을 배워봅니다.


목차

  1. ARIMA의_한계와_SARIMA의_등장
  2. SARIMA_파라미터_완전_정복
  3. 자동_파라미터_탐색_auto_arima
  4. 모델_진단과_잔차_분석
  5. 예측과_신뢰구간_시각화
  6. 외생변수를_활용한_SARIMAX
  7. 계절성_분해로_데이터_이해하기
  8. 모델_성능_평가_지표

1. ARIMA의 한계와 SARIMA의 등장

김개발 씨는 쇼핑몰의 월별 매출 데이터를 분석하고 있었습니다. ARIMA 모델을 적용해봤지만, 예측 결과가 영 시원찮았습니다.

매년 12월이면 매출이 급증하는데, 모델은 이런 패턴을 전혀 잡아내지 못하고 있었습니다.

SARIMA는 Seasonal ARIMA의 약자로, 기존 ARIMA 모델에 계절성 요소를 추가한 모델입니다. 마치 날씨 예보관이 작년 같은 시기의 날씨를 참고하는 것처럼, 과거 같은 계절의 패턴을 활용하여 예측합니다.

이를 통해 주기적으로 반복되는 패턴이 있는 데이터를 훨씬 정확하게 예측할 수 있습니다.

다음 코드를 살펴봅시다.

import pandas as pd
from statsmodels.tsa.statespace.sarimax import SARIMAX

# 월별 매출 데이터 로드
sales_data = pd.read_csv('monthly_sales.csv', parse_dates=['date'], index_col='date')

# SARIMA 모델 정의: order=(p,d,q), seasonal_order=(P,D,Q,s)
# s=12는 12개월 주기의 계절성을 의미합니다
model = SARIMAX(sales_data['revenue'],
                order=(1, 1, 1),           # 비계절성 파라미터
                seasonal_order=(1, 1, 1, 12))  # 계절성 파라미터

# 모델 학습
fitted_model = model.fit(disp=False)

# 향후 12개월 예측
forecast = fitted_model.forecast(steps=12)
print(forecast)

김개발 씨는 입사 6개월 차 데이터 분석가입니다. 오늘 팀장님으로부터 긴급한 요청을 받았습니다.

"다음 분기 매출 예측 좀 해줄 수 있어요? 내일 경영진 회의에 필요해요." 부랴부랴 ARIMA 모델을 돌려봤지만, 결과가 이상했습니다.

12월은 항상 매출이 2배 이상 뛰는데, 모델은 그냥 밋밋한 직선을 그려놓았습니다. 김개발 씨는 머리를 긁적였습니다.

옆자리 선배 박시니어 씨가 화면을 슬쩍 보더니 말했습니다. "아, ARIMA만으로는 계절성을 못 잡아요.

SARIMA를 써봐요." 그렇다면 계절성이란 정확히 무엇일까요? 쉽게 비유하자면, 계절성은 마치 학교 매점의 판매 패턴과 같습니다.

시험 기간에는 에너지 드링크가 불티나게 팔리고, 여름에는 아이스크림 매출이 급증합니다. 이런 패턴은 매년 비슷한 시기에 반복됩니다.

시계열 데이터에서도 이와 똑같은 현상이 나타납니다. 기존 ARIMA 모델은 어떤 한계가 있었을까요?

ARIMA는 바로 직전 데이터들만 참고하여 예측합니다. 어제 매출, 그저께 매출, 그끄저께 매출...

이런 식으로요. 하지만 "작년 12월 매출이 어땠는지"는 전혀 고려하지 않습니다.

그래서 매년 반복되는 패턴을 놓치게 됩니다. SARIMA는 바로 이 문제를 해결합니다.

SARIMA는 ARIMA의 모든 기능을 가지면서, 추가로 계절성 파라미터를 도입합니다. 위 코드에서 seasonal_order=(1, 1, 1, 12)가 바로 그 부분입니다.

마지막 숫자 12는 "12개월 주기로 패턴이 반복된다"는 의미입니다. 코드를 자세히 살펴보겠습니다.

order=(1, 1, 1)은 기존 ARIMA의 파라미터입니다. 순서대로 p(자기회귀 차수), d(차분 횟수), q(이동평균 차수)를 의미합니다.

이것만으로는 단기적인 추세만 잡을 수 있습니다. 핵심은 seasonal_order=(1, 1, 1, 12) 부분입니다.

앞의 세 숫자 (P, D, Q)는 계절성 버전의 파라미터이고, 마지막 12는 계절 주기입니다. 월별 데이터라면 12, 분기별이라면 4를 넣으면 됩니다.

실제 현업에서는 어떻게 활용할까요? 전력 회사에서는 SARIMA로 계절별 전력 수요를 예측합니다.

여름 에어컨 사용량, 겨울 난방 수요를 미리 파악해야 발전 계획을 세울 수 있기 때문입니다. 항공사는 휴가철 승객 수 예측에, 소매업체는 연말 대목 재고 계획에 활용합니다.

하지만 주의할 점도 있습니다. 초보자들이 흔히 하는 실수는 계절 주기를 잘못 설정하는 것입니다.

일별 데이터인데 s=12를 넣으면 안 됩니다. 또한 최소 2-3개 주기 이상의 데이터가 있어야 계절성을 제대로 학습할 수 있습니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 조언대로 SARIMA를 적용하자, 12월 매출 급증 패턴이 예측 그래프에 고스란히 반영되었습니다.

팀장님께 보고한 예측 결과는 경영진 회의에서 좋은 반응을 얻었습니다.

실전 팁

💡 - 계절 주기(s)는 데이터 특성에 맞게 설정하세요. 월별=12, 분기별=4, 주별=52

  • 최소 2-3개 계절 주기 이상의 데이터가 필요합니다

2. SARIMA 파라미터 완전 정복

김개발 씨는 SARIMA를 적용해봤지만, 파라미터 설정에서 막혔습니다. (p, d, q)와 (P, D, Q, s)...

이 숫자들은 대체 어떻게 정해야 하는 걸까요? 무작정 1,1,1을 넣었더니 결과가 좋지 않았습니다.

SARIMA의 파라미터는 크게 비계절성 파라미터 (p, d, q)와 계절성 파라미터 (P, D, Q, s)로 나뉩니다. 마치 요리 레시피에서 기본 양념과 특별 양념을 구분하는 것처럼, 각 파라미터는 모델의 서로 다른 측면을 조절합니다.

올바른 파라미터 선택이 예측 성능을 좌우합니다.

다음 코드를 살펴봅시다.

import pandas as pd
from statsmodels.tsa.statespace.sarimax import SARIMAX
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
import matplotlib.pyplot as plt

# 데이터 로드
data = pd.read_csv('airline_passengers.csv', parse_dates=['month'], index_col='month')

# ACF, PACF 플롯으로 파라미터 추정
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
plot_acf(data['passengers'].diff().dropna(), ax=axes[0], lags=40)
plot_pacf(data['passengers'].diff().dropna(), ax=axes[1], lags=40)
plt.tight_layout()
plt.savefig('acf_pacf_plot.png')

# 파라미터 의미:
# p: AR 차수 (과거 값 몇 개를 참고할지)
# d: 차분 횟수 (정상성 확보를 위해)
# q: MA 차수 (과거 오차 몇 개를 참고할지)
# P, D, Q: 계절성 버전의 p, d, q
# s: 계절 주기 (월별=12)

model = SARIMAX(data['passengers'], order=(2, 1, 1), seasonal_order=(1, 1, 1, 12))

김개발 씨는 SARIMA 공식 문서를 뚫어져라 쳐다봤습니다. order, seasonal_order...

파라미터가 너무 많았습니다. 대체 이 숫자들은 어떻게 정하는 걸까요?

박시니어 씨가 화이트보드 앞으로 김개발 씨를 불렀습니다. "파라미터가 복잡해 보이지만, 하나씩 뜯어보면 별거 아니에요." 먼저 비계절성 파라미터 (p, d, q)를 살펴봅시다.

p는 AR(Auto-Regressive) 차수입니다. 쉽게 말해 "과거 며칠치 데이터를 참고할지"입니다.

마치 주식 투자자가 최근 3일간의 주가 흐름을 보고 내일을 예측하는 것과 같습니다. p=2라면 이틀 전, 어제 데이터를 참고합니다.

d는 차분(Differencing) 횟수입니다. 시계열 데이터가 정상성을 갖도록 변환하는 과정입니다.

정상성이란 평균과 분산이 시간에 따라 변하지 않는 상태를 말합니다. 대부분의 실제 데이터는 상승 추세나 하락 추세가 있어서 d=1로 한 번 차분해줍니다.

q는 MA(Moving Average) 차수입니다. 과거 예측 오차를 얼마나 참고할지 결정합니다.

"어제 예측이 100 빗나갔으니, 오늘은 그걸 보정해야겠다"는 식입니다. 이제 계절성 파라미터 (P, D, Q, s)를 살펴봅시다.

P, D, Q는 각각 p, d, q의 계절성 버전입니다. 차이점은 바로 직전이 아니라 한 계절 전을 참고한다는 것입니다.

P=1이고 s=12라면, 1개월 전이 아니라 12개월 전(작년 같은 달) 데이터를 참고합니다. s는 계절 주기를 나타냅니다.

월별 데이터는 12, 분기별은 4, 주별은 52가 됩니다. 이 값이 잘못되면 계절성을 제대로 포착하지 못합니다.

그렇다면 이 파라미터들을 어떻게 찾을까요? 코드에서 보이는 ACFPACF 그래프가 핵심입니다.

ACF(자기상관함수)는 시차별 상관관계를 보여주고, PACF(편자기상관함수)는 다른 시차의 영향을 제거한 순수 상관관계를 보여줍니다. PACF 그래프에서 급격히 0으로 떨어지기 전까지의 유의미한 시차 개수가 p 값의 힌트가 됩니다.

ACF 그래프는 q 값을 추정하는 데 사용합니다. 12, 24 같은 계절 주기에서 스파이크가 보이면 계절성 파라미터 조정이 필요합니다.

실무에서는 어떻게 할까요? 솔직히 말하면, 완벽한 파라미터를 한 번에 찾기는 어렵습니다.

그래서 Grid Search를 자주 사용합니다. 여러 파라미터 조합을 시도해보고, AIC나 BIC 같은 정보 기준값이 가장 낮은 조합을 선택합니다.

김개발 씨는 고개를 끄덕였습니다. "그러니까 ACF, PACF로 대략적인 범위를 잡고, Grid Search로 최적값을 찾으면 되는 거군요!"

실전 팁

💡 - ACF, PACF 그래프를 먼저 그려서 대략적인 파라미터 범위를 파악하세요

  • 파라미터 값은 보통 0, 1, 2 범위에서 시작하는 것이 좋습니다

3. 자동 파라미터 탐색 auto arima

김개발 씨는 Grid Search로 파라미터를 찾아보려 했지만, 조합이 너무 많았습니다. (p, d, q)만 해도 27가지, 여기에 (P, D, Q)까지 더하면 수백 가지가 넘었습니다.

일일이 다 해볼 수는 없는 노릇이었습니다.

auto_arima는 최적의 SARIMA 파라미터를 자동으로 찾아주는 도구입니다. 마치 내비게이션이 여러 경로 중 최적 경로를 찾아주는 것처럼, 다양한 파라미터 조합을 체계적으로 탐색하여 가장 좋은 모델을 추천해줍니다.

pmdarima 라이브러리에서 제공하는 이 기능은 실무에서 시간을 크게 절약해줍니다.

다음 코드를 살펴봅시다.

from pmdarima import auto_arima
import pandas as pd

# 데이터 로드
data = pd.read_csv('monthly_sales.csv', parse_dates=['date'], index_col='date')

# auto_arima로 최적 파라미터 자동 탐색
# m=12: 월별 계절성, seasonal=True: 계절성 모델 사용
model = auto_arima(
    data['revenue'],
    start_p=0, max_p=3,      # p 탐색 범위
    start_q=0, max_q=3,      # q 탐색 범위
    d=None,                   # 자동으로 d 결정
    seasonal=True,            # 계절성 모델 사용
    m=12,                     # 계절 주기 (월별)
    start_P=0, max_P=2,      # P 탐색 범위
    start_Q=0, max_Q=2,      # Q 탐색 범위
    D=None,                   # 자동으로 D 결정
    trace=True,               # 탐색 과정 출력
    error_action='ignore',
    suppress_warnings=True,
    stepwise=True             # stepwise 알고리즘 사용
)

print(model.summary())
print(f"\n최적 파라미터: {model.order}, {model.seasonal_order}")

김개발 씨는 엑셀에 파라미터 조합을 적어보다가 한숨을 쉬었습니다. p, d, q 각각 0부터 2까지만 해도 27가지인데, 계절성 파라미터까지 더하면...

계산하기도 싫었습니다. 박시니어 씨가 웃으며 말했습니다.

"그렇게 노가다 하지 마세요. auto_arima가 다 해줄 거예요." auto_arima는 pmdarima 라이브러리에서 제공하는 기능입니다.

이름 그대로 ARIMA(혹은 SARIMA)의 파라미터를 자동으로 찾아줍니다. 어떻게 작동할까요?

내부적으로 auto_arima는 여러 파라미터 조합을 시도합니다. 각 조합마다 모델을 학습하고, AIC(Akaike Information Criterion)라는 점수를 계산합니다.

AIC가 낮을수록 좋은 모델입니다. 마치 여러 식당의 리뷰 점수를 비교해서 최고의 맛집을 찾는 것과 같습니다.

코드를 살펴보겠습니다. start_p=0, max_p=3은 p 값의 탐색 범위를 지정합니다.

0부터 3까지 시도해본다는 뜻입니다. q, P, Q도 마찬가지입니다.

d=None으로 설정하면 차분 횟수도 자동으로 결정합니다. m=12는 계절 주기를 지정합니다.

월별 데이터니까 12입니다. 이 값은 반드시 직접 설정해줘야 합니다.

auto_arima도 계절 주기까지 자동으로 알아내진 못하기 때문입니다. stepwise=True는 중요한 옵션입니다.

모든 조합을 다 시도하는 대신, 통계적 기법을 사용해 효율적으로 탐색합니다. 수백 번 시도할 것을 수십 번으로 줄여줍니다.

데이터가 크거나 시간이 부족할 때 유용합니다. trace=True로 설정하면 탐색 과정이 화면에 출력됩니다.

어떤 조합을 시도하고 있는지, AIC가 얼마인지 실시간으로 볼 수 있습니다. 디버깅할 때 매우 유용합니다.

실무에서의 팁을 드리자면, auto_arima의 결과를 100% 신뢰하지는 마세요. 자동으로 찾은 파라미터는 좋은 출발점이지, 반드시 최적은 아닙니다.

결과를 검토하고, 필요하면 미세 조정을 해야 합니다. 김개발 씨는 auto_arima를 실행하고 커피를 한 잔 마셨습니다.

돌아와 보니 최적 파라미터가 깔끔하게 출력되어 있었습니다. "와, 진짜 편하네요!"

실전 팁

💡 - stepwise=True로 탐색 시간을 대폭 줄일 수 있습니다

  • auto_arima 결과는 출발점으로 삼고, 결과를 검토한 후 미세 조정하세요

4. 모델 진단과 잔차 분석

김개발 씨는 auto_arima로 찾은 파라미터로 모델을 학습했습니다. 그런데 이 모델이 정말 좋은 건지 어떻게 알 수 있을까요?

박시니어 씨는 "잔차를 확인해봐야 한다"고 했는데, 잔차가 대체 뭘까요?

잔차(Residual)는 실제 값과 모델 예측값의 차이입니다. 마치 시험에서 예상 점수와 실제 점수의 차이를 분석하는 것처럼, 잔차를 분석하면 모델이 얼마나 잘 작동하는지 진단할 수 있습니다.

좋은 모델의 잔차는 백색 잡음처럼 무작위해야 합니다.

다음 코드를 살펴봅시다.

from statsmodels.tsa.statespace.sarimax import SARIMAX
from statsmodels.stats.diagnostic import acorr_ljungbox
import matplotlib.pyplot as plt
import pandas as pd

# 모델 학습
data = pd.read_csv('monthly_sales.csv', parse_dates=['date'], index_col='date')
model = SARIMAX(data['revenue'], order=(1, 1, 1), seasonal_order=(1, 1, 1, 12))
fitted = model.fit(disp=False)

# 잔차 진단 플롯 생성
fig = fitted.plot_diagnostics(figsize=(12, 8))
plt.tight_layout()
plt.savefig('residual_diagnostics.png')

# Ljung-Box 검정: 잔차의 자기상관 검정
# p-value > 0.05면 잔차가 백색잡음 (좋은 모델)
lb_test = acorr_ljungbox(fitted.resid, lags=[10, 20, 30], return_df=True)
print("Ljung-Box 검정 결과:")
print(lb_test)

# 잔차 통계 확인
print(f"\n잔차 평균: {fitted.resid.mean():.4f}")
print(f"잔차 표준편차: {fitted.resid.std():.4f}")

김개발 씨는 모델을 학습시키고 뿌듯해했습니다. 그런데 박시니어 씨가 물었습니다.

"모델 진단은 해봤어요?" 진단이요? 김개발 씨는 어리둥절했습니다.

코드가 에러 없이 돌아가면 된 거 아닌가요? 박시니어 씨가 설명을 시작했습니다.

"모델이 돌아간다고 좋은 모델은 아니에요. 잔차 분석을 해봐야 해요." 잔차란 무엇일까요?

잔차는 실제 값 - 예측 값입니다. 모델이 "이번 달 매출은 1억일 거야"라고 예측했는데, 실제로는 1억 2천만 원이었다면 잔차는 2천만 원입니다.

이 잔차들을 모아서 분석하면 모델의 품질을 평가할 수 있습니다. 좋은 모델의 잔차는 어떤 특성을 가져야 할까요?

첫째, 평균이 0이어야 합니다. 잔차 평균이 양수면 모델이 계속 과소예측하고 있다는 뜻입니다.

음수면 과대예측이고요. 둘째, 무작위해야 합니다.

잔차에 패턴이 있다면, 모델이 아직 데이터의 어떤 구조를 잡아내지 못했다는 의미입니다. 코드의 plot_diagnostics()는 네 가지 그래프를 보여줍니다.

왼쪽 위는 잔차의 시계열 플롯입니다. 특별한 패턴 없이 0 주변에서 랜덤하게 흩어져 있어야 합니다.

오른쪽 위는 히스토그램으로, 정규분포 모양이어야 합니다. 왼쪽 아래는 Q-Q 플롯으로, 점들이 대각선 위에 있으면 정규성을 만족합니다.

오른쪽 아래는 ACF 플롯으로, 대부분의 시차에서 신뢰구간 안에 있어야 합니다. Ljung-Box 검정은 더 엄밀한 테스트입니다.

이 검정은 "잔차가 서로 독립인가?"를 통계적으로 테스트합니다. p-value가 0.05보다 크면 잔차가 백색잡음이라는 귀무가설을 기각하지 못합니다.

쉽게 말해, 잔차에 패턴이 없다는 뜻이니 좋은 겁니다. 만약 진단 결과가 좋지 않다면 어떻게 해야 할까요?

파라미터를 조정하거나, 데이터 전처리를 다시 검토해야 합니다. 이상치를 제거하거나, 로그 변환을 적용하는 것도 방법입니다.

모델링은 한 번에 끝나지 않습니다. 진단하고, 수정하고, 다시 진단하는 반복 과정입니다.

김개발 씨는 진단 그래프를 살펴봤습니다. 다행히 잔차가 깔끔하게 무작위 패턴을 보였습니다.

"휴, 다행이네요. 모델이 괜찮은 것 같아요!"

실전 팁

💡 - plot_diagnostics()로 한눈에 잔차의 전체적인 상태를 파악하세요

  • Ljung-Box p-value가 0.05 미만이면 파라미터 재조정이 필요합니다

5. 예측과 신뢰구간 시각화

김개발 씨의 SARIMA 모델이 드디어 완성되었습니다. 이제 실제로 미래를 예측할 차례입니다.

그런데 예측값만 덜렁 내놓으면 경영진이 믿어줄까요? "이 예측이 얼마나 확실한 거예요?"라는 질문에 어떻게 대답해야 할까요?

신뢰구간(Confidence Interval)은 예측의 불확실성을 수치로 표현한 것입니다. 마치 일기예보에서 "강수 확률 70%"라고 말하는 것처럼, 신뢰구간을 함께 제시하면 예측의 신뢰도를 알 수 있습니다.

95% 신뢰구간은 "실제 값이 이 범위 안에 있을 확률이 95%"라는 의미입니다.

다음 코드를 살펴봅시다.

import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.statespace.sarimax import SARIMAX

# 데이터 로드 및 모델 학습
data = pd.read_csv('monthly_sales.csv', parse_dates=['date'], index_col='date')
model = SARIMAX(data['revenue'], order=(1, 1, 1), seasonal_order=(1, 1, 1, 12))
fitted = model.fit(disp=False)

# 미래 12개월 예측 (신뢰구간 포함)
forecast_result = fitted.get_forecast(steps=12)
forecast_mean = forecast_result.predicted_mean
forecast_ci = forecast_result.conf_int(alpha=0.05)  # 95% 신뢰구간

# 시각화
plt.figure(figsize=(12, 6))
plt.plot(data.index, data['revenue'], label='실제 데이터', color='blue')
plt.plot(forecast_mean.index, forecast_mean, label='예측', color='red')
plt.fill_between(forecast_ci.index,
                 forecast_ci.iloc[:, 0], forecast_ci.iloc[:, 1],
                 color='red', alpha=0.2, label='95% 신뢰구간')
plt.xlabel('날짜')
plt.ylabel('매출')
plt.title('SARIMA 매출 예측')
plt.legend()
plt.savefig('forecast_with_confidence.png')

김개발 씨는 경영진 회의에 참석했습니다. 발표 자료에 SARIMA 예측 결과를 넣어뒀습니다.

그런데 이사님이 날카로운 질문을 던졌습니다. "다음 달 매출이 10억이라고요?

그게 확실한 건가요?" 김개발 씨는 당황했습니다. 확실하다고 말하기엔...

아무리 좋은 모델이라도 미래를 100% 맞출 순 없으니까요. 이때 신뢰구간이 필요합니다.

신뢰구간은 "예측값이 이 범위 안에 있을 확률"을 나타냅니다. 95% 신뢰구간이 8억~12억이라면, "실제 매출이 8억에서 12억 사이일 확률이 95%입니다"라고 말할 수 있습니다.

훨씬 정직하고 신뢰할 수 있는 답변입니다. 코드를 살펴보겠습니다.

get_forecast(steps=12)는 향후 12개월을 예측합니다. 단순히 forecast() 대신 get_forecast()를 사용하면 예측 결과 객체를 얻을 수 있어서 더 많은 정보에 접근할 수 있습니다.

predicted_mean은 예측의 평균값입니다. 우리가 흔히 말하는 "예측값"이 바로 이것입니다.

conf_int(alpha=0.05)는 95% 신뢰구간을 반환합니다. alpha=0.05는 "양쪽 끝 5%를 제외한 구간"이라는 의미입니다.

시각화에서 fill_between은 핵심입니다. 이 함수는 두 선 사이의 영역을 색칠합니다.

신뢰구간의 상한선과 하한선 사이를 연한 빨간색으로 채워서, 예측의 불확실성을 시각적으로 보여줍니다. alpha=0.2는 투명도로, 뒤의 데이터가 비쳐 보이게 합니다.

한 가지 중요한 특성이 있습니다. 신뢰구간은 미래로 갈수록 넓어집니다.

내일 예측은 꽤 정확하지만, 1년 뒤 예측은 불확실성이 큽니다. 이건 당연한 겁니다.

마치 내일 날씨는 잘 맞추지만, 한 달 뒤 날씨는 잘 못 맞추는 것과 같습니다. 실무에서 이 그래프는 매우 강력한 커뮤니케이션 도구입니다.

경영진에게 "매출이 10억입니다"라고 말하면 반신반의합니다. 하지만 "매출이 10억으로 예상되며, 95% 확률로 8억~12억 사이입니다"라고 말하면 신뢰감을 줍니다.

불확실성을 숨기는 게 아니라 정직하게 드러내는 것이 오히려 전문성을 보여줍니다. 다시 회의실 장면으로 돌아갑시다.

김개발 씨는 신뢰구간이 포함된 그래프를 보여주며 설명했습니다. 이사님은 고개를 끄덕였습니다.

"예측의 범위까지 보여주니까 훨씬 이해가 잘 되네요."

실전 팁

💡 - 항상 신뢰구간과 함께 예측을 제시하세요. 단일 값만 제시하면 오해를 부를 수 있습니다

  • 신뢰구간이 너무 넓다면 모델을 개선하거나 예측 기간을 줄이세요

6. 외생변수를 활용한 SARIMAX

김개발 씨의 예측 모델은 잘 작동하고 있었습니다. 그런데 마케팅팀에서 요청이 왔습니다.

"다음 달에 대규모 프로모션을 할 건데, 그것도 예측에 반영해줄 수 있어요?" 프로모션 효과는 어떻게 모델에 넣을 수 있을까요?

SARIMAX의 X는 eXogenous, 즉 외생변수를 의미합니다. 외생변수는 시계열 자체가 아닌 외부 요인입니다.

마치 주가를 예측할 때 금리나 환율을 함께 고려하는 것처럼, 프로모션, 공휴일, 경제 지표 등 외부 요인을 모델에 포함시켜 예측 정확도를 높일 수 있습니다.

다음 코드를 살펴봅시다.

import pandas as pd
from statsmodels.tsa.statespace.sarimax import SARIMAX

# 데이터 로드 (매출 + 외생변수)
data = pd.read_csv('sales_with_features.csv', parse_dates=['date'], index_col='date')

# 외생변수: 프로모션 여부, 공휴일 수, 광고비
exog_vars = data[['promotion', 'holidays', 'ad_spend']]

# SARIMAX 모델 (외생변수 포함)
model = SARIMAX(
    data['revenue'],
    exog=exog_vars,
    order=(1, 1, 1),
    seasonal_order=(1, 1, 1, 12)
)

fitted = model.fit(disp=False)
print(fitted.summary())

# 미래 예측을 위한 외생변수 준비
future_exog = pd.DataFrame({
    'promotion': [1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1],  # 프로모션 계획
    'holidays': [1, 0, 0, 2, 1, 0, 0, 1, 0, 0, 0, 3],   # 공휴일 수
    'ad_spend': [5000, 3000, 3000, 6000, 4000, 3000, 3000, 4000, 3000, 3000, 3000, 7000]
})

# 외생변수와 함께 예측
forecast = fitted.forecast(steps=12, exog=future_exog)
print(forecast)

김개발 씨는 마케팅팀의 요청을 받고 고민에 빠졌습니다. 지금 모델은 과거 매출 패턴만 보고 예측합니다.

하지만 현실에서는 프로모션을 하면 매출이 확 뛰고, 공휴일이 많으면 또 달라집니다. 이런 걸 어떻게 반영할까요?

박시니어 씨가 말했습니다. "SARIMA 뒤에 X 하나만 붙이면 돼요.

SARIMAX요." 외생변수란 무엇일까요? 내생변수는 모델 내부에서 결정되는 변수입니다.

매출 예측에서 "어제 매출"은 내생변수입니다. 반면 외생변수는 외부에서 주어지는 변수입니다.

프로모션 여부, 공휴일 수, 광고비 등은 매출과 상관없이 외부에서 결정됩니다. 비유하자면, 체중 예측 모델을 만든다고 생각해봅시다.

어제 체중, 그저께 체중을 보고 내일 체중을 예측합니다(내생변수). 하지만 "내일 회식이 있다"거나 "이번 주 운동을 많이 했다"는 정보를 추가하면 예측이 더 정확해집니다(외생변수).

코드에서 핵심은 exog 파라미터입니다. exog=exog_vars로 외생변수 데이터프레임을 전달합니다.

이 예제에서는 프로모션 여부(0 또는 1), 공휴일 수, 광고비 세 가지를 사용합니다. 모델은 이 변수들과 매출의 관계를 학습합니다.

summary()를 출력하면 각 외생변수의 계수를 확인할 수 있습니다. 예를 들어 promotion 계수가 5000만이라면, 프로모션을 할 때 매출이 5000만 원 증가한다는 의미입니다.

이 계수가 통계적으로 유의미한지도 확인할 수 있습니다. p-value가 0.05 미만이면 해당 변수가 실제로 의미 있는 영향을 준다고 해석합니다.

중요한 점이 있습니다. 예측할 때도 외생변수가 필요합니다.

미래를 예측하려면 미래의 외생변수 값을 알아야 합니다. "다음 달 프로모션을 할 건지", "공휴일이 몇 개인지"는 미리 알 수 있습니다.

하지만 "다음 달 경쟁사 매출"처럼 미리 알 수 없는 변수는 외생변수로 쓰기 어렵습니다. 실무에서 자주 사용하는 외생변수들이 있습니다.

마케팅 분야에서는 프로모션, 광고비, 이벤트가 대표적입니다. 소매업에서는 공휴일, 날씨, 요일을 많이 씁니다.

금융에서는 금리, 환율, 경제 지표를 활용합니다. 도메인에 맞는 외생변수를 찾는 것이 모델링의 핵심입니다.

김개발 씨는 마케팅팀의 프로모션 계획표를 받아 외생변수로 추가했습니다. 예측 정확도가 눈에 띄게 올라갔습니다.

마케팅팀도 만족했습니다. "프로모션 효과를 숫자로 볼 수 있어서 좋네요!"

실전 팁

💡 - 외생변수는 "미래에도 값을 알 수 있는 것"을 선택하세요

  • 너무 많은 외생변수를 넣으면 과적합 위험이 있습니다. 핵심적인 것만 선택하세요

7. 계절성 분해로 데이터 이해하기

후배 이주니어 씨가 김개발 씨에게 물었습니다. "선배님, 이 데이터에 계절성이 있는지 어떻게 알 수 있어요?

그냥 눈으로 보면 되나요?" 좋은 질문입니다. SARIMA를 적용하기 전에, 정말 계절성이 있는지 먼저 확인해야 합니다.

계절성 분해(Seasonal Decomposition)는 시계열 데이터를 추세, 계절성, 잔차 세 요소로 분리하는 기법입니다. 마치 음악에서 보컬, 베이스, 드럼을 분리해서 듣는 것처럼, 데이터에 숨겨진 패턴을 눈으로 직접 확인할 수 있습니다.

이를 통해 SARIMA 적용이 적절한지 판단할 수 있습니다.

다음 코드를 살펴봅시다.

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

# 데이터 로드
data = pd.read_csv('monthly_sales.csv', parse_dates=['date'], index_col='date')

# 계절성 분해 (가법 모델)
# period=12: 12개월 주기
decomposition = seasonal_decompose(data['revenue'], model='additive', period=12)

# 분해 결과 시각화
fig, axes = plt.subplots(4, 1, figsize=(12, 10))

decomposition.observed.plot(ax=axes[0], title='원본 데이터')
decomposition.trend.plot(ax=axes[1], title='추세 (Trend)')
decomposition.seasonal.plot(ax=axes[2], title='계절성 (Seasonal)')
decomposition.resid.plot(ax=axes[3], title='잔차 (Residual)')

plt.tight_layout()
plt.savefig('seasonal_decomposition.png')

# 계절성 강도 확인
seasonal_strength = decomposition.seasonal.std() / decomposition.observed.std()
print(f"계절성 강도: {seasonal_strength:.2%}")

이주니어 씨의 질문은 핵심을 찌르는 것이었습니다. SARIMA는 계절성이 있는 데이터에 쓰는 모델입니다.

계절성이 없는데 SARIMA를 쓰면 오히려 성능이 떨어질 수 있습니다. 그렇다면 계절성이 있는지 어떻게 확인할까요?

가장 직관적인 방법이 계절성 분해입니다. 이 기법은 시계열 데이터를 세 가지 구성 요소로 분리합니다.

첫째, 추세(Trend)입니다. 장기적인 상승이나 하락 패턴을 말합니다.

회사가 성장하면서 매출이 꾸준히 증가하는 것이 추세입니다. 계절과 관계없이 전반적인 방향성을 보여줍니다.

둘째, 계절성(Seasonal)입니다. 주기적으로 반복되는 패턴입니다.

12월마다 매출이 오르고, 2월마다 떨어지는 것이 계절성입니다. 같은 모양의 파동이 반복됩니다.

셋째, 잔차(Residual)입니다. 추세와 계절성으로 설명되지 않는 나머지입니다.

예측 불가능한 불규칙 변동, 노이즈라고 보면 됩니다. 코드의 model='additive'는 중요한 선택입니다.

가법 모델은 원본 = 추세 + 계절성 + 잔차입니다. 계절 변동폭이 일정할 때 사용합니다.

반면 승법 모델(multiplicative)은 원본 = 추세 x 계절성 x 잔차입니다. 추세가 커질수록 계절 변동도 커질 때 사용합니다.

어떤 것을 선택해야 할까요? 원본 데이터 그래프를 봅시다.

시간이 지나면서 위아래 폭이 비슷하면 가법 모델, 폭이 점점 커지면 승법 모델입니다. 계절성 강도도 확인할 수 있습니다.

코드 마지막의 seasonal_strength는 전체 변동 중 계절성이 차지하는 비율입니다. 이 값이 20% 이상이면 계절성이 꽤 있다고 볼 수 있습니다.

5% 미만이면 계절성이 거의 없으니 일반 ARIMA로도 충분합니다. 실무에서 이 분석은 SARIMA 적용 전 필수 단계입니다.

분해 그래프에서 계절성 플롯이 규칙적인 패턴을 보이면 SARIMA가 적합합니다. 패턴이 불규칙하거나 거의 평평하면 계절성 파라미터가 필요 없을 수 있습니다.

이주니어 씨는 분해 그래프를 보며 고개를 끄덕였습니다. "와, 진짜 12개월마다 같은 패턴이 반복되네요.

이러니까 SARIMA를 써야 하는 거군요!"

실전 팁

💡 - SARIMA 적용 전 반드시 계절성 분해로 데이터를 확인하세요

  • 추세가 커질수록 변동폭도 커지면 model='multiplicative'를 사용하세요

8. 모델 성능 평가 지표

김개발 씨는 여러 가지 파라미터 조합으로 SARIMA 모델을 만들어봤습니다. 어떤 모델이 더 좋은 건지 어떻게 비교할까요?

"느낌상 이게 좋아 보여요"라고 말하면 아무도 납득하지 않을 겁니다. 객관적인 평가 지표가 필요합니다.

시계열 모델의 성능은 다양한 평가 지표로 측정할 수 있습니다. MAE, RMSE, MAPE 등의 지표는 마치 학생의 시험 점수처럼, 모델이 얼마나 잘 예측했는지 수치로 보여줍니다.

여러 모델을 동일한 기준으로 비교하면 최선의 선택을 할 수 있습니다.

다음 코드를 살펴봅시다.

import pandas as pd
import numpy as np
from sklearn.metrics import mean_absolute_error, mean_squared_error
from statsmodels.tsa.statespace.sarimax import SARIMAX

# 데이터 분할: 학습 데이터와 테스트 데이터
data = pd.read_csv('monthly_sales.csv', parse_dates=['date'], index_col='date')
train = data[:-12]  # 마지막 12개월 제외
test = data[-12:]   # 마지막 12개월은 테스트용

# 모델 학습 및 예측
model = SARIMAX(train['revenue'], order=(1, 1, 1), seasonal_order=(1, 1, 1, 12))
fitted = model.fit(disp=False)
predictions = fitted.forecast(steps=12)

# 평가 지표 계산
mae = mean_absolute_error(test['revenue'], predictions)
rmse = np.sqrt(mean_squared_error(test['revenue'], predictions))
mape = np.mean(np.abs((test['revenue'] - predictions) / test['revenue'])) * 100

print(f"MAE (평균 절대 오차): {mae:,.0f}원")
print(f"RMSE (평균 제곱근 오차): {rmse:,.0f}원")
print(f"MAPE (평균 절대 백분율 오차): {mape:.2f}%")

# AIC, BIC로 모델 복잡도 평가
print(f"\nAIC: {fitted.aic:.2f}")
print(f"BIC: {fitted.bic:.2f}")

김개발 씨는 세 가지 SARIMA 모델을 만들었습니다. 파라미터가 각각 다릅니다.

어떤 걸 최종 모델로 선택해야 할까요? 박시니어 씨가 말했습니다.

"감으로 고르지 말고, 숫자로 비교해야죠." 가장 기본적인 평가 방법은 학습-테스트 분할입니다. 전체 데이터 중 일부(보통 마지막 10-20%)를 따로 빼둡니다.

나머지로 모델을 학습하고, 빼둔 데이터로 예측 성능을 테스트합니다. 마치 시험 문제를 풀기 전에 답을 보지 않는 것과 같습니다.

MAE(Mean Absolute Error)는 가장 직관적인 지표입니다. 예측과 실제의 차이(절대값)를 평균낸 것입니다.

MAE가 100만 원이면 "평균적으로 100만 원 정도 빗나간다"는 뜻입니다. 단위가 원본 데이터와 같아서 해석이 쉽습니다.

RMSE(Root Mean Squared Error)는 큰 오차에 민감합니다. 오차를 제곱해서 평균내고 루트를 씌웁니다.

큰 오차에 더 많은 패널티를 부여합니다. 어떤 달은 잘 맞추고 어떤 달은 크게 틀렸다면, MAE보다 RMSE가 더 나쁘게 나옵니다.

MAPE(Mean Absolute Percentage Error)는 백분율로 표현합니다. "평균적으로 몇 퍼센트 틀리는가"를 알려줍니다.

MAPE가 5%면 상당히 좋은 모델입니다. 10% 이하면 실무에서 쓸 만합니다.

다만 실제 값이 0에 가까운 경우 MAPE가 무한대로 튀는 문제가 있습니다. AICBIC는 조금 다른 관점의 지표입니다.

이 지표들은 "예측 오차"뿐 아니라 "모델 복잡도"도 고려합니다. 파라미터가 많으면 학습 데이터에는 잘 맞지만 새 데이터에는 못 맞출 수 있습니다(과적합).

AIC와 BIC는 이런 복잡한 모델에 패널티를 줍니다. 값이 낮을수록 좋습니다.

어떤 지표를 써야 할까요? 정답은 "상황에 따라 다르다"입니다.

비즈니스에서 큰 오차가 치명적이면 RMSE를 중시합니다. 비율로 보고해야 하면 MAPE를 씁니다.

모델 선택 단계에서는 AIC를 참고합니다. 하나만 보지 말고 여러 지표를 종합적으로 판단하세요.

김개발 씨는 세 모델의 지표를 표로 정리했습니다. 두 번째 모델이 MAE, RMSE, MAPE 모두 가장 좋았고, AIC도 가장 낮았습니다.

객관적인 근거를 가지고 모델을 선택할 수 있었습니다.

실전 팁

💡 - 단일 지표만 보지 말고 MAE, RMSE, MAPE를 함께 확인하세요

  • 모델 선택 시 AIC가 낮은 것을 우선 고려하되, 테스트 셋 성능도 반드시 확인하세요

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Python#SARIMA#TimeSeries#Forecasting#Seasonality#Data Science

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.