1. 오늘 배운것.
시계열 분석 요약

1-1 잔차분석과 모델평가
- 잔차 분석이란?
- 잔차분석은 회귀 분석에서 관측값과 회귀 모형의 예측값 사이의 차이인 '잔차(residual)'를 분석하여 회귀 모형의 가정이 잘 만족되는지 확인하는 절차
- 시계열 자기상관함수와 Ljung-Box 검정방법을 이용해 확인
(백색소음 : 자기 상관성이 없는 시계열 데이터를 지칭하며 아무런 패턴이 남아있지 않은 무작위한 움직임을 보이는 데이터)
1) 잔차가 백색소음인지 확인하는 방법
1. ACF (자기상관함수)
- 잔차의 자기상관이 없는지 확인하기 위한 도구 ACF 그래프에서 모든 시차에서 유의미한 상관관계가 없을 때, 잔차가 백색소음임을 의미
- 잔차가 과거 값과 상관성이 없어야 좋은 모델
2. Ljung-Box Q 통계량
- Ljung-Box 검정은 잔차들이 서로 상관되어 있는지 여부를 테스트

2) AIC 와 BIC를 통한 모델 평가
AIC(Akaike Information Criterion)와 BIC(Bayesian Information Criterion)는 회귀모델에서 모델의 적합도와 모델의 복잡성을 동시에 고려하여, 적합한 모델을 돕는 지표입니다.
AIC
- 모델이 데이터를 얼마나 잘 설명하는지**(모델의 적합도)**와 모델의 복잡성 사이의 균형을 고려
- 모델의 복잡성을 고려하여, 적합도와 모델의 자유도를 조정한 지표
- AIC는 낮을수록 좋은 모델이며, 다음과 같이 계산

- AIC는 모델이 데이터를 얼마나 잘 설명하는 지와 동시에 모델의 복잡성을 페널티로 부여
+ 가능도란? : 가능도란, 주어진 모델이 데이터에 얼마나 적합(fitting)한지를 평가하는 지표입니다. 모델의 예측값과 실제 데이터가 일치할 가능성이 클수록 가능도는 높아집니다.

BIC : BIC는 AIC와 달리 샘플 수 n에 대한 로그값을 사용해 모델 복잡성에 더 큰 패널티를 부여합니다.

AIC와 BIC의 차이점
- AIC는 모델의 복잡성(k)과 적합도(L)와 사이의 균형을 중시하며, 적합도가 더 높은 모델을 선호
- BIC는 모델의 복잡성을 더 많이 고려하여 단순한 모델을 선호하며, 샘플 수가 클수록 더 엄격한 페널티를 부여
AIC와 BIC 평가 코드
- AIC와 BIC는 가장 낮은 값을 가진 모델을 선택하는 것이 원칙
- 두 지표 모두 낮을수록 더 좋은 모델로 간주하지만, BIC는 더 간소한 모델을 선호
- 특히, 샘플 수가 많을 때 BIC는 더 엄격한 기준을 제시
model_arima = ARIMA(data, order=(p, d, q))
model_fit = model_arima.fit()
# AIC, BIC확인
print(f"AIC: {model_fit.aic}")
print(f"BIC: {model_fit.bic}")
1-2. Auto-ARIMA
Auto-ARIMA는 ARIMA와 SARIMA 모델에서 사용되는 최적 차수(p, d, q, P, D, Q, s)를 자동으로 탐색해주는 도구입니다.
- Auto-ARIMA는 pmdarima 패키지에서 제공하는 강력한 자동화 도구로, 최소 AIC(아카이케 정보 기준) 또는 BIC(베이즈 정보 기준) 값을 찾는 방식으로 최적 차수를 결정
- Auto-ARIMA의 주요 기능
- p, d, q (비계절성 차수) 및 P, D, Q, s (계절성 차수)를 자동으로 선택
- AIC/BIC 기준으로 최적 모델 탐색
- 비계절적과 계절적 데이터를 모두 지원
- 수작업으로 차수 선택하는 번거로움을 줄여 시간 절약!
1) Auto-ARIMA 구현
Auto-ARIMA는 주로 복잡한 시계열 데이터를 처리할 때 유용하며, 데이터의 계절성 및 추세를 고려한 최적 모델을 제공합니다. 예를 들어, 월별 매출 데이터를 예측할 때 계절성을 자동으로 감지하여 월별 주기를 반영한 모델을 적합할 수 있습니다.
- Auto-ARIMA는 pmdarima 패키지에서 제공되며, 다양한 시계열 데이터에 적용 가능
- ex1
- m=12는 월별 데이터에서 계절 주기가 12개월인 것을 의미
- 월별 데이터의 1년 주기를 자동으로 감지하여 모델에 반영하게 됩니다.
- ex2
- 회사에서 최근 3년간의 월별 매출 데이터를 분석할 때, Auto-ARIMA는 데이터를 통해 최적의 p, d, q 값을 탐색
- 12개월 단위로 계절성을 반영해 미래 매출을 예측합니다.
- ex1
1-3. Prophet
Prophet은 Meta(구 Facebook)에서 개발한 시계열 예측 모델로, 추세(Trend), 계절성(Seasonality), 휴일 효과(Holiday Effects) 등을 고려하여 유연하고 강력한 모델링을 제공합니다. 특히 결측치 처리와 비정상적 데이터에 강하며, 대규모 데이터에서도 빠르게 작동하는 특성이 있습니다.
- Prophet은 기본적으로 추세를 직선이나 곡선으로 표현하며, 주기성은 일간, 주간, 연간 주기를 자동으로 감지
- 휴일이나 특별한 이벤트가 데이터에 미치는 영향을 따로 반영할 수도 있음
- Ex) 한국과 같이 명절이나 특정 휴일에 대한 소비 증가 같은 주기적 변화가 있는 경우
Prophet의 주요 특징

- Prophet의 장점
- 비정상 데이터 및 결측치 처리: 결측치가 있더라도 데이터를 손쉽게 처리
- 빠른 계산 속도: 대규모 데이터에서도 빠르게 작동
- 직관적인 파라미터 조정: 추세와 주기성의 강도를 쉽게 조절
- 휴일 효과 반영: 사용자가 직접 정의한 휴일이나 명절 효과를 반영
- Prophet의 단점
- 복잡한 상호작용 반영 어려움: ARIMA/SARIMA처럼 세부 조정을 할 수 없다는 한계
- 비선형성 추세 반영 한계: 복잡한 비선형 추세를 반영하는 데 한계
1) 조건부 계절성(Conditional Seasonality)
Prophet 모델은 기본적으로 계절성(Seasonality)을 일정한 패턴으로 처리하지만, 실제 데이터에서는 계절성이 특정 시기나 조건에 따라 달라질 수 있습니다.
예를 들어, 여름철 주간 패턴이 다르거나 평일과 주말의 일일 패턴이 상이할 수 있습니다. 이러한 복잡한 계절 패턴을 모델링할 때 조건부 계절성을 활용하면 유용합니다.
2) 시계열 데이터의 교차검증
시계열 데이터의 교차 검증은 일반적인 회귀 및 분류 문제에서 사용하는 k-겹 교차 검증과 다르게 시간의 흐름을 보존하면서 모델의 예측 성능을 평가하는 방법입니다.
시계열 데이터는 시간이 중요한 요소이기 때문에, 미래를 예측하는 데 과거 데이터를 사용하고, 그 결과를 평가할 때는 반드시 시간 순서를 유지해야 합니다
시계열 데이터에서의 교차 검증 원리
- 과거 데이터를 기반으로 모델을 학습한 후, 특정 시점 이후의 데이터를 예측하여 그 예측 결과를 실제 값과 비교하는 방식
- 일반적으로 사용되는 방식은 롤링 예측(rolling forecast) 교차 검증 또는 확장 창(expanding window) 방법
① 롤링 예측(rolling/sliding forecast)
- 고정된 훈련 기간을 사용하고, 그 이후의 데이터로 예측을 반복하는 방식
- 훈련 데이터가 고정된 기간을 유지한 채 앞으로 이동하며 예측이 진행

② 확장 창(expanding window)
- 훈련 세트를 점차 확장하여, 훈련 데이터의 범위가 커지면서 미래 데이터를 예측하는 방식
- 새로운 데이터를 예측할 때마다 이전 데이터를 훈련 세트에 포함

교차 검증 시 유의할 점
- 시간 순서 유지
- 교차 검증 시 반드시 과거 데이터를 사용하여 미래 데이터를 예측
- 미래 데이터를 기반으로 과거를 예측하는 모순을 방지
- 초기 훈련 기간 설정:
- initl 매개변수를 통해 처음 훈련에 사용할 데이터의 기간을 지정
- 충분한 데이터를 훈련 세트로 사용해야 모델이 적절히 학습
- cutoff 설정 주기
- period 매개변수로 각 교차 검증 단계 사이의 간격을 설정
- 너무 짧은 주기로 설정하면 모델의 평가가 과도하게 이루어질 수 있으므로 적절한 주기를 설정하는 것이 중요
- 예측 기간(horizon)
- horizon매개변수로 예측할 미래의 기간을 설정
- 예측 기간이 길어질수록 예측의 불확실성이 커질 수 있으므로 적절한 기간을 선택
2. 실습
Lesson 1: Auto-ARIMA 모델
AutoARIMA는 ARIMA 모델의 차수(p,d,q)와 계절성 차수(P,D,Q,m)를 자동으로 선택해주는 모델입니다.
수동 ARIMA와 SARIMA 모델링에서 우리가 직접 ADF/KPSS 테스트를 통해 시계열의 정상성 여부를 확인하고, ACF/PACF 그래프를 통해 최적의 차수를 찾았다면, AutoARIMA는 이러한 과정을 자동으로 처리하여 가장 적합한 차수를 찾아냅니다.
우리가 이번에 실습할 데이터는 서울시의 5년간의 일별 평균 기온 데이터를 주별로 평균을 낸 주간 평균기온데이터입니다.
AutoARIMA 학습 과정
1. 데이터 로드 및 전처리
2. AutoARIMA 모델 학습
3. 예측 및 성능 평가
4. 잔차 분석 및 시각화
평균 기온에 대한 데이터 시각화
# 날짜 주기(frequency)를 명시적으로 지정해 경고 제거
ts.index = pd.to_datetime(ts.index, format='%Y-%m-%d')
ts = ts.asfreq('W') # 주별 데이터로 주기 설정
ts.plot(figsize=(10,4)) #시각화

모델 학습
!pip install pmdarima -q
!pip install 명령어에 -q 옵션을 추가하여 조용한(quiet) 모드로 실행할 수 있습니다.
-q 옵션은 불필요한 출력 메시지를 줄여줍니다.
※ train_data가 아닌, ts(전체데이터)로 auto_arima 모델에 적합하는 이유?
- 일반적으로 모델 학습 전에 데이터를 훈련 데이터와 테스트 데이터로 분할하는 것이 맞습니다.
- 하지만 AutoARIMA의 경우, 먼저 전체 데이터를 사용하여 최적의 p,d,q를 찾고, 그 다음에 훈련 데이터를 기반으로 모델을 다시 학습하는 과정을 진행할 수 있습니다.
- 즉, AutoARIMA가 전체 데이터에서 차수 선택을 자동으로 최적화하는 데 필요한 정보를 얻고, 그 후 실제로 예측할 때는 훈련 데이터만 사용합니다
from pmdarima import auto_arima
auto_arima_model = auto_arima(
ts,
seasonal=True, # 계절성 모델 적용
m=52, # 주간 데이터에서는 계절 주기를 52로 설정 (1년 = 52주)
stepwise=True, # 단계별 탐색으로 빠른 계산
trace=True, # 모델 피팅 과정 출력
error_action='ignore', # 모델 피팅 중 에러 무시
suppress_warnings=True, # 경고 메시지 숨김
max_order=10, # 최대 차수 제한
start_p=1, start_q=1, # p와 q의 초기 값
max_p=3, max_q=3, # p와 q의 최대 값
start_P=0, start_Q=0, # 계절적 P와 Q의 초기 값
max_P=2, max_Q=2, # 계절적 P와 Q의 최대 값
d=None, D=1, # 차분(d)은 자동 선택, 계절 차분(D)은 1
)
auto_arima_model.summary()
출력)

출력 요약)
- 최적 모델: SARIMAX(0, 0, 1)x(1, 1, 1)[52] 이며, AIC 기준 가장 적합합니다.
- 유의한 계수: 비계절 MA(1차)와 계절 AR(52차) 계수는 통계적으로 매우 유의합니다 (p-값 0.05 미만).
- 모델 적합성: Ljung-Box, Jarque-Bera, Heteroskedasticity 테스트의 p-값이 모두 높게 나와, 잔차가 백색 잡음 조건을 잘 충족하며 모델이 적절하게 피팅되었다고 판단할 수 있습니다.
훈련,평가데이터 분할
# 전체 데이터의 길이 확인
total_len = len(ts)
# 80:20 비율로 훈련/평가 데이터 분할
train_size_n = int(total_len * 0.8)
train_data = ts[:train_size_n]
test_data = ts[train_size_n:]
print(total_len, len(train_data), len(test_data))
훈련데이터로 학습
auto_arima_model.fit(train_data)

예측
forecast = auto_arima_model.predict(n_periods=len(test_data))
forecast.tail()

성능확인
from sklearn.metrics import mean_squared_error
import numpy as np
def get_metrics(test_data, forecast):
# MSE (Mean Squared Error) 계산
mse = mean_squared_error(test_data, forecast)
print(f'MSE: {mse:.3f}')
# RMSE (Root Mean Squared Error) 계산
rmse = np.sqrt(mse)
print(f'RMSE: {rmse:.3f}')
# MAPE (Mean Absolute Percentage Error) 계산 (분모가 0인 경우 무시)
epsilon = 1e-10 # 0으로 나누면 분모에 0이 들어가면서 inf 또는 NaN이 발생할 수 있음.
#이를 방지하기 위해 작은 값 추가!
mape = np.mean(np.abs((test_data - forecast) / (test_data + epsilon))) * 100
print(f'MAPE: {mape:.3f}%')
get_metrics(test_data, forecast)

잔차분석
import matplotlib.pyplot as plt
auto_arima_model.plot_diagnostics(figsize=(10, 6))
plt.subplots_adjust(hspace=0.5)
for ax in plt.gcf().axes: # 현재 figure의 모든 axes에 적용
plt.sca(ax) # 해당 서브플롯 선택
plt.xticks(rotation=45) # x축 라벨 기울이기
plt.show()

시각화 자료 해석)
- Standardized Residuals
- 시간에 따른 모델의 잔차를 시각화
- 잔차가 일정한 패턴 없이 평균 0을 중심으로 무작위로 분포해 있다면, 모델이 데이터에 잘 적합된 것입니다.
- 이 그래프에서는 잔차가 평균 0을 중심으로 일정한 패턴 없이 분포하고 있어, 모델이 데이터에 전반적으로 잘 맞고 있다고 볼 수 있습니다.
- 하지만 일부잔차가 -3~3 사이 극단 값을 가지며 이는 특정 시점에서 모델이 데이터의 변동성을 충분히 설명하지 못함의 의미합니다.
- Histogram + Estimated Density
- 잔차가 정규 분포를 따르는지를 확인하기 위한 히스토그램과 밀도 함수
- 초록색 선은 N(0,1)정규분포를 나타내고, 주황색 선은 잔차의 추정밀도입니다.
- 잔차가 완벽하게 정규성을 띄지않지만 대체로 정상성에 가까운 분포를 따릅니다.
- Normal Q-Q 플롯
- 잔차가 정규 분포를 따르는지를 시각적으로 평가하기 위한 플롯입니다. 점들이 빨간색 직선에 가까울수록 잔차가 정규성을 만족한다는 뜻입니다.
- 대다수의 점이 빨간색 직선 근처에 위치해 있으며, 이는 잔차가 전반적으로 정규성을 만족하고 있음을 보여줍니다.
- 그러나 양 끝단에서 일부 점들이 직선에서 벗어나는 모습을 보여주고 있으며, 이는 극단적인 이상치가 존재할 수 있음을 시사합니다. 잔차의 비정규성은 모델이 데이터의 모든 패턴을 충분히 설명하지 못할 가능성을 나타냅니다.
- Correlogram(잔차의 자기상관도)
- 잔차의 자기 상관 관계를 시각적으로 나타낸 플롯입니다. 잔차가 무작위로 분포하고 자기 상관성이 없다면, 모델이 데이터를 잘 설명했다고 할 수 있습니다.
- 대부분의 지점에서 잔차의 자기 상관성이 0에 가깝고, 이는 모델이 데이터 패턴을 잘 설명하고 있음을 나타냅니다.
잔차 자기상관성 확인
residuals = test_data - forecast
residuals.tail()

from statsmodels.stats.diagnostic import acorr_ljungbox
# Ljung-Box 검정 (잔차의 독립성 검정)
ljung_box_test = acorr_ljungbox(residuals, return_df=True) #10번째 지연(lag)까지 잔차의 자기상관성을 확인
print(ljung_box_test)

결과해석) p-값이 0.05보다 크기 때문에, 잔차에 자기상관성이 없다는 귀무가설을 기각하지 못합니다. 즉, 잔차는 독립적이며, 모델이 데이터를 잘 설명하고 있다고 볼 수 있습니다.
예측 시각화
# 시각화
def get_figure(test_data, forecast,conf_int):
plt.figure(figsize=(12, 6))
# 실제값
plt.plot(test_data.index, test_data, label='Actual', color='blue')
# 예측값
plt.plot(test_data.index, forecast, label='Forecast', color='red')
# 신뢰구간
plt.fill_between(test_data.index, conf_int.iloc[:, 0], conf_int.iloc[:, 1], color='pink', alpha=0.3, label='95% Confidence Interval')
plt.title('Actual vs Forecast with Confidence Intervals')
plt.xlabel('Date')
plt.ylabel('Temperature(°C)')
plt.legend()
plt.show()
# AutoARIMA 모델로 예측과 신뢰구간 계산
forecast, conf_int = auto_arima_model.predict(n_periods=len(test_data), return_conf_int=True)
# conf_int를 DataFrame으로 변환(신뢰구간 하한선과 상한선!)
conf_int = pd.DataFrame(conf_int, index=test_data.index, columns=['Lower', 'Upper'])
get_figure(test_data, forecast,conf_int)

결론
이는 AutoARIMA가 데이터를 자동으로 분석하여 최적의 파라미터를 선택하고, 계절성을 포함하지 않은 시계열 데이터에 특히 유용한 모델임을 보여줍니다.
하지만, AutoARIMA는 복잡한 계절성과 비선형 패턴을 충분히 반영하지 못할 수 있습니다. 이를 보완하기 위해 이제 Prophet 모델을 사용하여 예측을 수행해 보겠습니다. Prophet은 Facebook에서 개발한 모델로, 계절성과 추세를 자동으로 처리하며, 특히 비선형적인 시계열 데이터를 다루는 데 강점을 보입니다.
Lesson2 Prophet 모델
데이터 로드
import pandas as pd
file_path = '/content/ta_20240920220735.csv'
# CSV 파일을 8행부터 읽어오기 (skiprows=7)
df = pd.read_csv(file_path, skiprows=6,
encoding='CP949',
# index_col=0, #날짜열을 인덱스로 지정해야합니다!
parse_dates=True
)
df.index.name = None #인덱스명 제거
ts = df.iloc[-1825:, [0,2]] #'평균기온' 컬럼만 선택, 최근 약 5년치 데이터만 선택
ts

전처리
컬럼명 수정
- 페이스북의 예측 라이브러리가 작동하는 방식은, 데이터셋의 열 부분이 ds와 y로 지정되어 있어야 합니다.
- 날짜열이 ds, 예측하려는 데이터 레이블은 y로 컬럼명 수정
ts.columns = ['ds', 'y']
ts['ds'] = ts['ds'].str[1:]
ts

datetime 형변환
ts['ds'] = pd.to_datetime(ts['ds'])
주간데이터로 변환
ts = ts.resample('W-Sun', on='ds').mean().reset_index()
시각화
ts.plot(x='ds', y='y', figsize=(20,6))

- 매년 여름에 기온이 상승하고, 겨울에 기온이 하락하는 계절적인 변화를 확인
- 계절성은 Prophet 모델이 잘 처리할 수 있는 부분입니다. 연간 주기적인 패턴을 예측하는 모델을 구현해봅시다.
모델설치
!pip install cmdstanpy
!pip install prophet
학습, 평가데이터 분할
# 전체 데이터의 길이 확인
total_len = len(ts)
# 80:20 비율로 훈련/평가 데이터 분할
train_size_n = int(total_len * 0.8)
train_data = ts[:train_size_n]
test_data = ts[train_size_n:]
print(total_len, len(train_data), len(test_data))
전체 데이터수 : 262, 트레인 데이터 수 : 209, 테스트 데이터 수 : 53
- 전체 데이터셋을 80:20 비율로 훈련 데이터(train)와 평가 데이터(test)로 나눕니다.
- 이 경우, 약 5년치 데이터를 사용했으며, 훈련 데이터는 약 4년치, 평가 데이터는 약 1년치로 설정됩니다.
- Prophet은 훈련 데이터를 기반으로 패턴을 학습한 뒤, 평가 데이터를 예측하게 됩니다.
모델 학습
#계절성 모드는 곱셈 모델을 사용해야 시간에 따라 증가하는 것을 학습가능
m = Prophet(changepoint_prior_scale=0.1,
yearly_seasonality=True)#seasonality_mode='multiplicative')
m.fit(train_data)
계절성 반영
- 현재는 additive (기본값) 모드로 모델링합니다.
- 만약 기온이 시간이 지나면서 계절성이 더 증폭되는 패턴이 보인다면, 곱셈 모드를 고려할 수 있습니다.
- 최근 5년간 기온 변동폭이 일정하게 유지되므로 기본형인 additive 모드를 사용합니다. 즉, 계절성 패턴이 시간이 지남에 따라 일정한 크기로 변동하는 경우에는 additive 모드가 적합합니다.
- 그 외에도 Prophet 정의 시 다양한 파라미터가 있습니다.
- seasonality_mode
- 'additive' 또는 'multiplicative'로 계절성을 설정합니다. multiplicative는 데이터의 변동성이 시간이 지남에 따라 더 커지거나 작아지는 경우에 사용합니다.
- Prophet에서 기본적으로 계절성을 자동으로 학습하지만, 계절성이 뚜렷하고 그 변동폭이 시간에 따라 비례적으로 커지거나 작아지는 경우 곱셈 모드를 사용하는 것이 적합합니다.
- 예를 들어, 기온 데이터가 시간이 지남에 따라 계절적인 기온 차이가 더 크게 벌어지면 곱셈 모드가 유용할 수 있습니다.
- changepoint_prior_scale
- 추세 변화점(Changepoints)에 대한 민감도를 조정하는 파라미터입니다. 기본값은 0.05로, 값이 클수록 모델이 더 민감하게 추세 변화를 감지합니다. 만약 기온 데이터에서 큰 변동이 적다면, 이 값을 더 낮출 수 있습니다.
- yearly_seasonality
- 연간 계절성을 학습할지를 설정합니다. Prophet은 데이터의 길이에 따라 자동으로 이를 감지하지만, 계절성을 강제로 적용하거나 해제할 수도 있습니다. 예를 들어, yearly_seasonality=True로 설정하면 연간 주기 패턴을 강제할 수 있습니다.
- holidays
- 휴일 데이터를 추가할 수 있습니다. 휴일이 기온에 영향을 미친다고 생각된다면, 특정 휴일에 대해 기온의 변화 패턴을 학습시킬 수 있습니다.
- seasonality_mode
모델 예측
future = m.make_future_dataframe(periods=52, freq='W') # freq='W'로 주간 데이터 생성
future

make_future_dataframe() → 예측 기간 설정
forecast = m.predict(future)
forecast[['ds', 'yhat_lower', 'yhat_upper', 'yhat']]
- forecast는 예측된 값들과 그에 해당하는 신뢰 구간을 포함하는 데이터프레임입니다.
- yhat은 예측된 기온 값이며, yhat_lower와 yhat_upper는 95% 신뢰 구간을 의미합니다. 즉, 예측된 값의 불확실성을 보여주는 값들입니다.

시각화
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
# Prophet 예측 시각화 (SARIMA 스타일에 맞게 수정)
plt.figure(figsize=(12, 6))
# 실제 값 (Actual)
plt.plot(test_data['ds'], test_data['y'], label='Actual', color='blue')
# 예측 값 (Forecast)
plt.plot(forecast['ds'], forecast['yhat'], label='Forecast', color='red')
# 신뢰 구간 (Confidence Interval)
plt.fill_between(forecast['ds'], forecast['yhat_lower'], forecast['yhat_upper'], color='pink', alpha=0.3, label='95% Confidence Interval')
# 그래프 설정
plt.title('Prophet Forecast vs Actuals with Confidence Interval')
plt.xlabel('Date')
plt.ylabel('Temperature (°C)')
plt.legend()
# X축 레이블 각도 조정
plt.xticks(rotation=45)
# 날짜 포맷 설정
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
# 그래프 출력
plt.show()

- ax=ax: 예측 그래프 위에 실제 데이터를 추가하여 두 그래프를 겹쳐서 비교할 수 있습니다.
- xlim: X축의 날짜 범위를 설정해 원하는 구간만을 확대해서 볼 수 있습니다.
- mdates.date2num(forecast['ds']): fill_between 함수에서 날짜 데이터를 숫자로 변환해 matplotlib가 날짜를 적절히 처리하도록 합니다.
- ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d')): X축의 날짜 형식을 사람이 읽을 수 있는 '년-월-일' 형식으로 설정합니다.
예측 시각화 해석
- Predictions: Prophet 모델이 예측한 값(기온)을 나타냅니다. 이 선은 Prophet이 학습한 데이터 패턴(계절성, 추세)을 바탕으로 미래 기온을 예측한 결과입니다.
- 신뢰구간: Prophet 모델의 예측값에 대한 95% 신뢰구간. 이 구간은 Prophet이 예측한 값이 얼마나 불확실한지를 나타내며, 모델이 예측한 값이 이 범위 내에 있을 확률이 95%임을 의미합니다.
평가데이터 기간(최근 1년) 만 시각화
# 평가데이터 기간(최근1년)만 시각화해보기!
# 테스트 데이터 구간에 해당하는 Prophet 예측 결과 필터링
forecast_test = forecast[forecast['ds'].isin(test_data['ds'])]
# Prophet 예측 시각화 (SARIMA 스타일에 맞게 수정)
plt.figure(figsize=(12, 6))
# 실제 값 (Actual)
plt.plot(test_data['ds'], test_data['y'], label='Actual', color='blue')
# 예측 값 (Forecast)
plt.plot(forecast_test['ds'], forecast_test['yhat'], label='Forecast', color='red')
# 신뢰 구간 (Confidence Interval)
plt.fill_between(forecast_test['ds'], forecast_test['yhat_lower'], forecast_test['yhat_upper'], color='pink', alpha=0.3, label='95% Confidence Interval')
# 그래프 설정
plt.title('Prophet Forecast vs Actuals (Test Data ONLY!) with Confidence Interval')
plt.xlabel('Date')
plt.ylabel('Temperature (°C)')
plt.legend()
# X축 레이블 각도 조정
plt.xticks(rotation=45)
# 날짜 포맷 설정
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
# 그래프 출력
plt.show()

추세 시각화
from prophet.plot import add_changepoints_to_plot
fig = m.plot(forecast)
#변수명이 a인 이유: 공식문서 참조
a = add_changepoints_to_plot(fig.gca(),m,forecast) #현재모델의 축과 모델, 예측결과
# add_changepoints_to_plot(fig.gca(),m,forecast)

add_changepoints_to_plot
- 이 코드는 Prophet 모델이 감지한 **변화점(Changepoints)**을 그래프에 표시하는 역할을 합니다. Prophet은 데이터에서 추세가 급격히 변하는 시점을 자동으로 감지하며, 이를 변화점이라고 부릅니다.
- 그래프에 표시된 여러 빨간 수직선
- Prophet이 민감하게 추세 변화를 탐지한 결과
- changepoint_prior_scale 파라미터 값
- 변화점이 많다고 해서 반드시 큰 추세 변화가 있는 것은 아닙니다. Prophet은 설정된 민감도에 따라 작은 변화도 감지할 수 있는데, changepoint_prior_scale 파라미터 값 굵은 텍스트을 조정하면 추세 변화 감지 민감도를 줄일 수 있습니다.
- 계절성이 데이터의 주요 패턴을 형성
- 즉, 계절적인 변동이 주된 특징이며, 추세 변화는 상대적으로 작습니다. 따라서 변화점의 수가 많더라도, 추세 자체에 큰 변화가 없는 경우도 있을 수 있습니다.
- 그래프에서 수평선이 조금씩 올라가는 현상
- Prophet이 감지한 **장기적인 추세(Trend)**를 의미합니다.
- 이는 시간이 지남에 따라 기온이 미세하게 상승하고 있음을 시사합니다. Prophet 모델은 추세와 계절성을 분리하여 분석하기 때문에, 계절적인 변동과 별개로 데이터가 장기적으로 상승하거나 하락하는지 확인할 수 있습니다.
m.plot_components(forecast); #추세선과 연간계절성 시각화

plot_components
이 함수는 Prophet 모델이 분석한 추세와 계절성을 개별적으로 시각화합니다. Prophet은 데이터를 추세와 계절성으로 분리하여 예측을 수행하므로, 이 그래프들은 모델이 각 요소를 어떻게 분석하고 있는지 보여줍니다.
- 추세(Trend)
- 이 그래프에서 보이는 상승 추세는 시간이 지남에 따라 기온이 장기적으로 상승하고 있음을 나타냅니다.
- 이 그래프를 add_changepoints_to_plot의 결과와 연결해보면, 빨간색 수직선이 표시된 구간이 바로 이 추세가 변하는 중요한 시점들임을 알 수 있습니다. Prophet은 이 변화를 감지하여 모델에 반영하고 있습니다.
- 연간 계절성(Yearly Seasonality)
- 1월에 기온이 가장 낮고, 8월에 기온이 최고점을 찍는 전형적인 기온 변동 패턴을 보여줍니다. 이 계절성은 기온 데이터의 주요 패턴 중 하나로, Prophet 모델이 이 정보를 학습하여 정확한 예측을 도출하는 데 기여합니다.
앞서 확인했던 add_changepoints_to_plot에서 감지된 변화점과 함께 이 연간 계절성 그래프를 보면, 추세와 계절성이 서로 어떻게 조화를 이루며 모델의 예측에 영향을 미치는지 알 수 있습니다.
Prophet 모델을 통해 우리는 데이터를 단순히 예측하는 것 이상의 정보를 얻을 수 있습니다.
add_changepoints_to_plot 함수는 데이터를 분석할 때 Prophet이 어디에서 추세 변화를 감지했는지를 시각적으로 보여주며, plot_components 함수는 추세와 계절성을 분리하여 각각의 패턴을 명확히 분석하는 데 도움을 줍니다.
잔차분석 및 시각화
- 잔차 분석을 통해, 모델이 예측하지 못한 변동성을 파악하고, 이를 시각화하여 더 명확히 평가할 수 있습니다.
- 예측된 값과 실제 값 사이의 차이인 잔차(residual)는 모델이 정확하게 추세와 계절성을 반영하고 있는지 확인하는 데 필수적입니다.
- 하지만 Prophet 모델은 ARIMA와 달리 자동으로 잔차를 계산하고 분석하는 기능을 기본 제공하지 않습니다. 따라서, Prophet을 사용한 잔차분석은 수동으로 계산해야 합니다.
# ts와 forecast 데이터를 날짜를 기준으로 병합
# 원래데이터인 ts에 예측값인 yhat을 ds기준(조인 기준)으로 inner 조인하기
merged_data = pd.merge(ts[['ds', 'y']], forecast[['ds', 'yhat']], on='ds', how='inner')
merged_data

- 잔차 계산
Prophet에서 예측 값(yhat)과 실제 값(y) 간의 차이를 직접 계산해 잔차를 구합니다.
# 잔차 계산!
merged_data['residual'] = merged_data['y'] - merged_data['yhat']
merged_data

- 잔차 시각화
- 잔차를 그래프로 시각화하여 모델이 실제 데이터와 얼마나 차이가 나는지 확인해봅시다.
# ts와 forecast 데이터를 날짜를 기준으로 병합
merged_data = pd.merge(ts[['ds', 'y']], forecast[['ds', 'yhat']], on='ds', how='inner')
# 잔차 계산
merged_data['residual'] = merged_data['y'] - merged_data['yhat']
# 잔차 시각화
plt.figure(figsize=(10,6))
plt.plot(merged_data['ds'], merged_data['residual'], label='Residuals')
plt.axhline(y=0, color='r', linestyle='--')
plt.title('Prophet Residuals Over Time')
plt.xlabel('Date')
plt.ylabel('Residuals')
plt.xticks(rotation=45)
plt.legend()
plt.show()

- 잔차분포확인
# 잔차 히스토그램
plt.figure(figsize=(8,5))
plt.hist(merged_data['residual'], bins=30)
plt.title('Residuals Distribution')
plt.xlabel('Residual')
plt.ylabel('Frequency')
plt.show()

잔차 자기상관성 확인 : Ljung-box Test
Ljung-Box 테스트는 잔차에 자기 상관이 있는지 확인하는 검정입니다. 잔차가 백색잡음(부작위적)이어야 좋은 모델인데, 잔차에 자기상관이 남아있는지 즉 모델이 모든 패턴을 설명하짐 못하고 예측에 남은 정보가 있는지 확인합니다.
from statsmodels.stats.diagnostic import acorr_ljungbox
# Ljung-Box 테스트
ljung_test = acorr_ljungbox(merged_data['residual'], lags=[10], return_df=True)
print(ljung_test)

현재 Ljung-Box 테스트 결과에서 p-value가 0.05보다 큽니다.(0.4) 따라서 모델의 잔차가 무작위(백색소음)임을 알 수 있습니다
결과 확인
merged_data

get_metrics(merged_data['y'], merged_data['yhat'])

두 시계열 모형 성능 비교
성능 지표를 비교해 보면, Prophet 모형의 MSE와 RMSE가 눈에 띄게 개선된 것을 확인할 수 있습니다. Prophet은 계절성과 추세를 자동으로 분리하는 특징이 있어, 비선형적이거나 복잡한 패턴을 가진 시계열 데이터를 처리하는 데 강점을 보입니다. 특히, 이번 주간 평균 기온 데이터에서 Prophet이 상대적으로 더 나은 예측 성능을 보여주는 이유는, 계절성 패턴을 효과적으로 반영하면서도, 특정 변화점을 잘 감지했기 때문입니다.

Auto-ARIMA는 자동으로 최적의 차수를 찾아주지만, 비선형적 변화를 반영하는 데는 한계가 있습니다. 특히, Auto-ARIMA는 트렌드나 계절성의 급격한 변화에 덜 민감할 수 있어, Prophet에 비해 예측 오차가 크게 나타났습니다.
반면에 Prophet은 데이터의 추세와 계절성을 자동으로 분리하여 학습하며, **변화점(changepoints)**을 효과적으로 감지해 갑작스러운 패턴 변화를 잘 반영합니다. Prophet의 MSE와 RMSE가 다른 모델들에 비해 크게 낮은 이유는, 이러한 비선형적 변화를 더 잘 포착했기 때문입니다.
따라서, Prophet은 특히 복잡한 계절성이나 비선형적 트렌드를 가진 시계열 데이터에서 강력한 성능을 보여줄 수 있습니다. 다만, Prophet의 MAPE는 여전히 높은 수준을 유지하고 있어, 절대적인 오차율을 줄이기 위한 추가적인 데이터 전처리나 모형 튜닝도 고려해볼 수 있습니다.
3. 느낀점
시계열 모델링 진짜 너무 어렵습니다... 이전회차에서까지는 괜찮았는데 갑자기 어려워 지는 느낌... 그래도 세션을 듣고 이렇게 한땀한땀 정리해보니 어느정도 이해가 가는 것 같습니다. 내일 복습문제 풀어보고 그래도 어렵다면 다시 복습해볼 예정입니다.
너무 정확하게 아는 것보다 우리는 모델을 활용하는게 중요하니까요...