시계열 모델링 2회차
1. 오늘 배운것.
1) 확률 보행의 정의
확률보행: 시간이 지남에 따라 무작위로 이동하는 경로를 설명하는 수학적 모델



2) 정상성과 비정상
정상성: 시계열의 통계적 성질이 시간에 따라 변하지 않는 상태

2-1) 정상성 시계열의 조건
① 시계열의 평균이 시간에 따라 변하지 않고 일정함

② 시계열의 분산이 시간에 따라 일정함

③ 시계열의 두 시점 간 공분산이 시간(t)에 의존하지 않고 시간의 차(h)에만 의존함

3) 정상성 확보를 위한 데이터 변환 기법
① 차분 : 시계열에서 추세, 계절성을 제거하기 위한 방법
특정 시점과 그 직전 시점 사이 발생하는 일련의 변화(△y(t))를 계산


② 이동평균 평활화 : 이동평균을 사용하면 시계열에서 단기적인 변동성을 제거하고, 전체적인 추세를 더 명확히 볼 수 있으며 평균을 안정화하는 데 도움이 됨



③ 분산 안전화
3-1) 로그 변환 : 시계열 데이터의 분산을 안정화하여 효과적, 큰 값을 축소시켜 분산의 변동을 줄임

3-2) 제곱근 변환 : 분산을 줄이는 방법으로, 양수 데이터를 대상으로 사용


3-3) z-score 표준화 : 데이터의 평균과 분산을 일정하게 만들기 위해 표준화를 사용


3-4) Box-Cox 변환 : 분산을 안정화 할 뿐만 아니라 데이터의 정규성을 보장하는 데도 유용


4) 정상 시계열 검정방법
단위근: 자기회귀 계수 Φ가 1인 경우
ADF 는 단위근의 존재 여부를 테스트하여 시계열의 정상성 여부를 판단.


import pandas as pd
from statsmodels.tsa.stattools import adfuller
# 1. 데이터 불러오기 또는 생성 (예시 데이터)
# 예시: 임의의 비정상 시계열 데이터 생성
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 분석할 시계열 데이터를 series_data 변수에 할당
series_data = pd.Series(data)
# 2. ADF 검정 수행 함수
def adf_test(series):
# ADF 검정을 수행하고 결과를 저장
result = adfuller(series, autolag='AIC')
# 결과 출력
print('--- ADF 검정 결과 ---')
print(f'ADF Statistic: {result[0]:.4f}')
print(f'p-value: {result[1]:.4f}')
print(f'Number of Lags Used: {result[2]}')
print(f'Number of Observations Used: {result[3]}')
print('Critical Values:')
for key, value in result[4].items():
print(f'\t{key}: {value:.4f}')
# 3. 결과 해석 (가설 검정)
alpha = 0.05 # 유의 수준 (일반적으로 5%)
if result[1] <= alpha:
# p-value가 유의 수준보다 작으면 귀무가설 기각
# 귀무가설(H0): 시계열에 단위근이 존재한다 (비정상이다)
# 대립가설(H1): 시계열에 단위근이 존재하지 않는다 (정상이다)
print('\n**결론: 귀무가설 기각. 시계열은 정상(Stationary)입니다.**')
else:
print('\n**결론: 귀무가설 채택. 시계열은 비정상(Non-stationary)입니다.**')
# 4. 함수 실행
adf_test(series_data)
2. 실습
Lesson 1: 공분산 이해하기
- x의 표준편차
- x와 y의 공분산
- x와 y의 상관관계
- 시점 t와 시점 t-1의 공분산
1.1. 표준편차
- 개념: 데이터가 평균으로부터 평균적으로 떨어진 거리입니다.
- xi: 각 데이터, x¯: 평균, n: 데이터 개수
- 결과: 생성된 임의 데이터 x의 표준편차는 약 1.82입니다.

1.2. x와 y의 공분산
- x와 y 두 변수가 같이 움직이는 패턴을 봅니다. 공분산은 각 데이터가 평균(x¯,y¯)을 기준으로 어떤 사분면에 위치하는지의 합입니다.

시각화로 보기)
# 1. 데이터 생성 (앞 단계와 동일)
np.random.seed(42)
x = np.random.normal(loc=10, scale=2, size=100)
# y는 x와 양의 관계 + 노이즈
y = 2 * x + np.random.normal(loc=0, scale=3, size=100)
# 2. 공분산 계산
# rowvar=False로 설정하지 않으면 기본적으로 각 행을 변수로 봅니다.
# 여기서는 1차원 배열이므로 np.cov(x, y)를 하면 2x2 행렬이 나옵니다.
cov_matrix = np.cov(x, y)
# [0, 1] 또는 [1, 0] 위치가 x와 y의 공분산입니다.
cov_xy = cov_matrix[0, 1]
print(f"x와 y의 공분산: {cov_xy:.2f}")
# 3. 시각화 (사분면 강조)
plt.figure(figsize=(6, 6))
sns.scatterplot(x=x, y=y)
# 평균선 그리기 (사분면 기준)
plt.axvline(np.mean(x), color='red', linestyle='--', label='Mean X')
plt.axhline(np.mean(y), color='black', linestyle='--', label='Mean Y')
plt.title(f'Covariance Structure\nCov: {cov_xy:.2f}')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.show()


1.3. x와 y의 상관계수
- 공분산은 데이터의 단위(cm, m 등)에 따라 값이 달라지므로, 이를 표준화하여 -1과 1 사이의 값으로 바꿉니다

시각화)
# 1. Data Generation (same as previous steps)
np.random.seed(42)
x = np.random.normal(loc=10, scale=2, size=100)
y = 2 * x + np.random.normal(loc=0, scale=3, size=100)
# 2. Standardization for Correlation Plot
x_std = (x - np.mean(x)) / np.std(x, ddof=1)
y_std = (y - np.mean(y)) / np.std(y, ddof=1)
cov_xy = np.cov(x, y)[0, 1]
corr_xy = np.corrcoef(x, y)[0, 1]
# 3. Plotting side-by-side
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Plot 1: Covariance (Original Scale)
sns.scatterplot(x=x, y=y, ax=axes[0], s=60, alpha=0.7)
axes[0].axvline(np.mean(x), color='black', linestyle='--', label=f'Mean X ({np.mean(x):.1f})')
axes[0].axhline(np.mean(y), color='black', linestyle='--', label=f'Mean Y ({np.mean(y):.1f})')
axes[0].set_title(f'2. Covariance (Original Scale)\nCov = {cov_xy:.2f}')
axes[0].set_xlabel('x')
axes[0].set_ylabel('y')
axes[0].legend()
# Plot 2: Correlation (Standardized Scale)
sns.scatterplot(x=x_std, y=y_std, ax=axes[1], color='orange', s=60, alpha=0.7)
axes[1].axvline(0, color='grey', linestyle='--')
axes[1].axhline(0, color='grey', linestyle='--')
axes[1].set_title(f'3. Correlation (Standardized Scale)\nCorr = {corr_xy:.2f}')
axes[1].set_xlabel('Standardized x (Z-score)')
axes[1].set_ylabel('Standardized y (Z-score)')
axes[1].axis('equal') # Important to show the scaling effect
plt.tight_layout()
plt.savefig('cov_corr_comparison.png')

- 왼쪽과 오른쪽 그래프는 완전히 동일한 데이터를 사용. 차이점은 축의 스케일(단위) 뿐
- 왼쪽: 공분산 (Covariance)데이터
- 원래 데이터 x, y 그대로 사용.
- 중심: 데이터의 평균 (x¯,y¯) 인 약 (10, 20) 지점에 십자선
- 의미: x,y가 평균으로부터 같은 방향(1, 3사분면)으로 움직이는 경향이 강해서 공분산이 양수(5.89)가 나옵니다. 하지만 이 값(5.89)만 봐서는 관계가 얼마나 강한지 알기 어렵습니다. (단위에 따라 값이 변함)
- 오른쪽: 상관관계 (Correlation)데이터
- 평균을 0, 분산을 1로 맞춘 표준화된 데이터 사용.중심: 무조건 (0, 0)이 중심입니다.
- 의미: 단위를 제거했기 때문에 기울기와 퍼짐 정도가 정규화. 여기서 구해진 상관계수(0.75)는 절대적인 척도로 사용할 수 있음 (1에 가까울수록 강한 양의 관계)이 비교를 통해 "상관관계는 공분산을 표준화하여 보기 좋게 만든 버전"임을 이해할 수 있음
1.4. 시점 t와 시점 t-h의 간의 공분산
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# 1. Load Data
df = pd.read_csv('/content/TimeSeriesForecastingInPython/data/jj.csv')
# --- Part 1: Lag 4 (h=4) Data Construction and Visualization ---
# Create Lag 4 Data
df['t'] = df['data']
df['t-4'] = df['data'].shift(4)
# 데이터가 4개씩 밀려있는것 확인
df
df['t-4']컬럼을 만들어 주어 4시간의 시차를 만들어줍니다.


- 시차 4 (h=4)일 때의 데이터와 공분산이제 현재 시점 t와 4분기(1년) 전 시점 t-4의 관계를 봅니다. jj.csv는 분기별 데이터이므로, 시차 4는 정확히 '작년 이맘때'와의 비교를 의미합니다.
- 산점도 (왼쪽 그래프):
- 패턴: 직선에 더 가깝게 모여 있는 것을 볼 수 있습니다.
- 공분산 값: 약 16.19
- 해석: 1년 전 실적(t-4)과 현재 실적(t)은 매우 강한 양의 상관관계를 가집니다. 이는 계절성(Seasonality)이 뚜렷함을 시사합니다.
2. 시차별 공분산 그래프 (Autocovariance Function)
- 오른쪽 그래프에서 빨간색 점으로 표시된 부분이 Lag 4의 공분산 위치
- 그래프 해석
- Lag 4의 공분산(16.19)은 Lag 5(15.25)보다 약간 더 높음.보통 계절성이 있는 데이터는 주기(여기서는 4)마다 상관성이 튀어 오르는 경향이 있는데, 이 데이터는 전체적인 추세(우상향)가 너무 강해서 계절성보다는 추세의 영향이 지배적으로 나타나고 있음 또한, 모든 시차에서 공분산이 매우 높음
Lesson 2: 정상성 조건 3가지 실습 - 연평균 물가 상승률 데이터
# 연도별 평균 물가 상승률 계산
import statsmodels.api as sm
data = sm.datasets.macrodata.load_pandas().data
inflation_rate_df = pd.DataFrame({
'Year': data['year'],
'Inflation Rate': data['infl']
})
inflation_rate_yearly_avg = inflation_rate_df.groupby('Year').mean().reset_index()
# 데이터프레임 출력
display(inflation_rate_yearly_avg.loc[:5,:])
# 연도별 평균 물가 상승률 시각화
plt.figure(figsize=(10, 5))
plt.plot(inflation_rate_yearly_avg['Year'], inflation_rate_yearly_avg['Inflation Rate'], label='Average Inflation Rate')
plt.title('Average Inflation Rate per Year')
plt.xlabel('Year')
plt.ylabel('Inflation Rate')
plt.grid(True)
plt.show()


2.1. 시간에 따라 시계열의 평균이 일정
# 1. 평균이 일정한지 확인 (이동 평균 계산)
window_size = 10 # 이동 평균 및 이동 분산에 사용할 윈도우 크기
inflation_rate_df['Rolling Mean'] = inflation_rate_df['Inflation Rate'].rolling(window=window_size).mean()
plt.figure(figsize=(10, 5))
plt.plot(inflation_rate_df['Year'], inflation_rate_df['Inflation Rate'], label='Inflation Rate')
plt.plot(inflation_rate_df['Year'], inflation_rate_df['Rolling Mean'], label='Rolling Mean (window=10)', color='red')
plt.title('Inflation Rate and Rolling Mean')
plt.xlabel('Year')
plt.ylabel('Inflation Rate')
plt.legend()
plt.grid(True)
plt.show()

2.2. 시간에 따라 시계열의 분산이 일정
# 2. 분산이 일정한지 확인 (이동 분산 계산)
inflation_rate_df['Rolling Variance'] = inflation_rate_df['Inflation Rate'].rolling(window=window_size).var()
plt.figure(figsize=(10, 5))
plt.plot(inflation_rate_df['Year'], inflation_rate_df['Rolling Variance'], label='Rolling Variance (window=10)', color='green')
plt.title('Rolling Variance of Inflation Rate')
plt.xlabel('Year')
plt.ylabel('Variance')
plt.legend()
plt.grid(True)
plt.show()

2.3. 시간에 따라 공분산이 일정
import numpy as np
# 시차 h=10으로 데이터 생성
lag = 10
y_t = inflation_rate_yearly_avg['Inflation Rate'][:-lag].reset_index(drop=True)
y_t_plus_10 = inflation_rate_yearly_avg['Inflation Rate'][lag:].reset_index(drop=True)
# y_t와 y_t+10를 하나의 데이터프레임으로 결합
yt_yt_plus_10_df = pd.DataFrame({
'y_t': y_t,
'y_t+10': y_t_plus_10
})
# y_t와 y_t+10의 공분산 계산
covariance_10 = np.cov(yt_yt_plus_10_df['y_t'], yt_yt_plus_10_df['y_t+10'])[0, 1]
display(yt_yt_plus_10_df.head(15))
print(f'공분산은 {covariance_10:.2f} 입니다.')

# 최대 시차 설정
max_lag = 20
lags = np.arange(1, max_lag + 1)
covariances = []
# 각 시차별 공분산 계산
for lag in lags:
y_t = inflation_rate_yearly_avg['Inflation Rate'][:-lag].reset_index(drop=True)
y_t_plus_h = inflation_rate_yearly_avg['Inflation Rate'][lag:].reset_index(drop=True)
covariance = np.cov(y_t, y_t_plus_h)[0, 1]
covariances.append(covariance)
# 공분산 그래프 그리기
plt.figure(figsize=(8, 5))
plt.plot(lags, covariances, marker='o', linestyle='-')
plt.xlabel("Lag")
plt.ylabel("Covariance")
plt.title("Covariance by Lag for Inflation Rate")
plt.grid(True)
plt.show()

이렇게 그래프로만 보면 정상성을 띄는지 확인하기가 어렵습니다.
Lesson 3. 정상성 데이터 변환 확보하기 - 로그변환

- 원본 데이터 (파란색):
- 시간이 지날수록 진폭(변동 폭)이 커짐
- 측정 지표: 이동 표준편차의 기울기가 0.02528로 양수 (변동성이 계속 증가함)
- Lag Plot: 점들이 오른쪽 위로 갈수록 넓게 퍼지는 나팔 모양(Cone Shape)
- 로그 변환 데이터 (주황색):
- 진폭이 처음부터 끝까지 일정하게 유지
- 측정 지표: 이동 표준편차의 기울기가 -0.00044로 거의 0에 가까움 (변동성이 일정함)
- Lag Plot: 점들이 일정한 폭을 가진 직선 형태(Tube Shape) 모임
4. ADF 테스트
import pandas as pd
import numpy as np
from statsmodels.tsa.stattools import adfuller
# 1. Load Data
df['log_data'] = np.log(df['data'])
# 2. Define a function to print ADF results nicely
def print_adf_results(series, name):
result = adfuller(series)
print(f"--- ADF Test Results for: {name} ---")
print(f"ADF Statistic: {result[0]:.4f}")
print(f"p-value: {result[1]:.4f}")
print("Critical Values:")
for key, value in result[4].items():
print(f"\t{key}: {value:.4f}")
if result[1] < 0.05:
print("Result: Reject Null Hypothesis (Stationary)")
else:
print("Result: Fail to Reject Null Hypothesis (Non-Stationary)")
print("\n")
# 3. Run ADF Test
print_adf_results(df['data'], "Original Data")
print_adf_results(df['log_data'], "Log Transformed Data")
# (Optional) Let's check differencing as well, since log alone usually doesn't remove trend
df['log_diff'] = df['log_data'].diff().dropna()
print_adf_results(df['log_diff'].dropna(), "Log Transformed + Differenced Data")
--- ADF Test Results for: Original Data ---
ADF Statistic: 2.7420
p-value: 1.0000
Critical Values:
1%: -3.5246
5%: -2.9026
10%: -2.5887
Result: Fail to Reject Null Hypothesis (Non-Stationary)
--- ADF Test Results for: Log Transformed Data ---
ADF Statistic: -0.8041
p-value: 0.8179
Critical Values:
1%: -3.5160
5%: -2.8989
10%: -2.5867
Result: Fail to Reject Null Hypothesis (Non-Stationary)
--- ADF Test Results for: Log Transformed + Differenced Data ---
ADF Statistic: -4.3170
p-value: 0.0004
Critical Values:
1%: -3.5183
5%: -2.8999
10%: -2.5872
Result: Reject Null Hypothesis (Stationary)
3. 느낀점
시계열 분석의 조건에 대해 학습할 수 있었습니다.
깊게 들어가면 어렵겠지만 튜터님께서 시각화를 통해 잘 설명해주셔서 아직까지는 이해는 되는것 같습니다.
머신러닝은 조금 바빠서 못따라갔지만 시계열 만큼은 시간을 더 내서라도 완벽히 이해하기 위해 노력해야겠습니다.