본문 바로가기

Data Diary

2021-03-24(시계열데이터 심화8_Y의 정상성변환)

반응형

*본 내용은 시계열데이터 강의 내용 중 일부분을 요약한 내용입니다

 

 

 

지금까지는 feature들을 대상으로 진행했다면 이번 기록에는 Y을 대상으로 정상성 변환 part를 요약해봤습니다.

Y는 target이자 종속변수입니다. 현실의 데이터는 대~~부분 target은 비정상성입니다. 즉 시간의 흐름에 따라 뭔가 변화가 있고 분산도 왔다갔다 한 형태를 띕니다. 

 

*정상성으로 변환하면 좋은 점은 무엇인가??

예측하기가 쉬워집니다. 전에 올린 포스팅에서 매출과 점유율을 말씀드렸습니다. 매출은 마이너스부터 플러스까지 예측해야 될 범위가 넓어서 틀릴 확률이 비교적 높습니다. 이를 점유율로 바꾸면 0~1까지의 범위로 축소가 되고 예측이 맞을 경우도 맞아지게 됩니다. 이와 똑같은 원리입니다. 하지만 반드시 정상성으로 변환한다고 좋은 결과를 가져오는 것 아닙니다. 오히려 비정상성일때 성능이 더 좋을 수도 있다고 하니까 둘다 직접 해봐야 합니다. 다만 정상성으로 하게되면 성능이 좋아질 확률이 높아질 뿐입니다

 

정상성 사용할때 얻는 장점 정리

1. 예측성능 증가

2. 모델 복잡성 감소-> 복잡성이 줄어들면 당연히 정확성(bias,점추정)은 감소 될지언정 분산도 감소하므로 과적화될 확률이 낮아집니다. 가장 올바른 방향은 train의 성능이 좀 나빠져도 test 성능이 좋아지는 것입니다.

3. 전통적인 시계열 알고리즘은 사실 정상성을 만족하지 않으면 계수추정이 어렵다.

 

 

변환하는 방법

1.로그변환

-시간의 흐름에 따라 분산이 증가하는 Y를 로그를 해줌으로써 감소&일정하게 만들어준다(스케일 효과) 

 

2.차분

-특정 시점 또는 시점들의 데이터가 발산할 경우 시점간 차분(변화량)으로 정상성 변환 가능

-차분은 계절성과 추세를 제거하는 방법이다

-추세 제거 확인 measure ->ADF (계절성이 있어도 추세만 없다면 정상으로 판별함)

-계절성 제거 확인 measure -> KPSS(추세가 있어도 계절성만 없다면 정상으로 판별함)

 

1) 계절성를 제거하는 차분 방법1

계절성 추정(f(t))후 계절성 제거를 통한 정상성 확보(어떤 함수를 통해 계절성을 추정한 후 차분으로 제거)

이 방법의 단점은 추정 된 계절성이 과연 계절성을 잘 반영했는지다. 사람에 따라 계절성의 의미가 달라지고 애매한 부분이 있기때문에 아래 차분 방법2 처럼 일반적이고 쉬운 방법이 주로 쓰인다.

확률과정의 계절변수 더미화를 통해 기댓값 함수를 알아내는 낸다.

 

 

 

2) 계절성를 제거하는 차분 방법2

차분 적용 (1−𝐿^𝑑)𝑌𝑡 후 계절성 제거를 통한 정상성 확보

위 공식을 결론적으로 만 쓰게 되면 d가 1일때 Y(t)-Y(t-1)이다. 예를들어서 이해한다면

어떤 데이터를 봤을때 freq가 월 단위입니다. 데이터의 패턴은 12개월 마다 반복됩니다. 그러면 계절성이 12개월마다 반복되므로 d는 12입니다. 이때 차분하는 방법은 Y(t)-Y(t-12)입니다. 이렇게 뺄셈을 하면 계절성 패턴이 사라지게 됩니다.

 

 

3) 추세을 제거하는 차분 방법1

계절성를 제거하는 차분 방법1과 동일합니다.

 

4) 추세을 제거하는 차분 방법2

차분 적용 (1−𝐿1)^𝑑 *𝑌𝑡 후 추세 제거를 통한 정상성 확보

계절성 차분 방법2와 다른점 d가 괄호밖에 있다는 것입니다.

추세차분2

계절성 d와 추세의 d는 의미가 다릅니다

*계절성 d라는 것은 반복되는 패턴의 시점을 얘기합니다

*추세의 d는 뺄셈을 몇번 했는지에 대한 값입니다. 

 추세는 추세 패턴이 없어 질때까지 1씩 늘려서 뺄셈을 합니다. d=1에서 추세 패턴이 있다면 d=2로 빼고, d=3, 4, 5, ...

 주의할게, 뺀다고 그냥 빼는게 아니라 d=1일때 lag값을 취해서 뺄셈했습니다. 그리고 이 lag값을 그대로 또 lag를 취해   줍니다. 아래 이미지처럼 lag 값을 전달,전달,전달 해줍니다. 

강의 자료 이미지

 

 

 

3.box-cox변환

-정규분포가 아닌 자료를 정규분포로 변환하기 위해 사용

-모수(parameter) 𝜆를 가지며, 보통 여러가지 𝜆값을 시도하여 가장 정규성을 높여주는 값을 사용합니다.

 

실습: 대기중 CO2농도 추세 제거

import pandas as pd
from statsmodels import datasets
import matplotlib.pyplot as plt
import statsmodels.api as sm
%reload_ext autoreload
%autoreload 2
from module import stationarity_adf_test, stationarity_Kpss_test

raw_set =datasetsdet_rdataset('co2',package = 'datasets')
raw= raw_set.data
raw

 

# 데이터 확인 및 추세 추정 (선형)
plt.plot(raw.time, raw.value) 
plt.show()

#첫번째 방법: OLS를 사용한 추세 제거(추세를 추정하는 방식)
#y는 value로 쓰고 x는 time으로 쓴다는 걸 문자로 표현함
result = sm.OLS.from_formula(formula ='value~time', data=raw).fit()
display(result.summary())

#params를 사용해서 f(t) 만들기
#f(t)는 추세를 반영한 함수이다. 이 것을 원본 Y에서 뺄때 추세제거가 된다
#paramsp[0]: y절편
#paramsp[1]: time의 coef
# f(t) = a_0 + a_1 D_1 + a_2 D_2 + ... 와 같은 형태이다
trend = result.params[0]+result.params[1]* raw.time
plt.plot(raw.time, raw.value, raw.time, trend)
plt.show()
'''
맨 아래 그래프가 직선으로 표현 되어 있다. 중간부분에 약간의 구부러짐이 있으므로 더욱 
정교하게 만들기 위해서는 시간에 대한 비선형 term을 추가해준다(아래 실습코드)
'''

 

#I(time**2): 시간에 대한 비선형 추가
#I:identity matrix(단위행렬 or 항등행렬: 대각선이 1이고 나머지 0인 매트릭스)
result = sm.OLS.from_formula(formula='value ~time+I(time**2)', data=raw).fit()
display(result.summary())

trend= result.params[0] + result.params[1]*raw.time + result.params[2]*raw.time**2
plt.plot(raw.time, raw.value, raw.time, trend)
plt.show()

#정확성이 다소 증가했지만 자기상관성도 증가했다

 

#추세 제거 및 정상성 확인
#현재 trend라는 f(t)를 만들었다. 이를 원래 Y와 뺄셈하여 추세 제거를 한 결과가
#이미 resid안에 들어있다. 따라서 추세를 제거한 그래프를 시각화하여 확인해본다
plt.plot(raw.time, result.resid)
plt.show()
#심하진 않지만 약간의 곡선이 보인다. 그래도 추세는 상당히 제거된 것을 볼수 있다

#정상성인지 아닌지를 확인하기
#우리가 눈으로 본 겻과 통계량은 다를 수 있다.
display(stationarity_adf_test(result.resid, [])) #비정상 판별(추세가 남아 있다는 뜻)
display(stationarity_Kpass_test(result.resid, [])) #정상 판별(계절성은 없다는 뜻)

#시각화로 정상성 확인하기
sm.graphics.tsa.plot__acf(result.resid, lags=100, use_vlines=True)
plt.tight_layout()
plt.show()
#그래프에서 진동하는 것처럼 위 아래가 바복되는 걸로 보아서는 계절성이 있을 수 있다.
#또한 파란색 범위 안에 들어와야 계절성이 없는 걸로 판별 할수 있다.

 

#방법2_차분사용(회귀분석 할 필요 없이 diff 하나로 해결)
#nan 값은 빼고 계산한다.
plt.plot(raw.time[1:], raw.value.diff(1).dropna())
plt.show()
#첫번째 그래프보면 추세나 계절성이 이 전 실습 시각화 보다 없음을 확인할수있다.

#통계량으로 판별하기
display(stationarity_adf_test(raw.value.diff(1).dropna(), [])) #정상 판별
display(stationarity_kpss_test(raw.value.diff(1).dropna(), [])) #정상 판별
#회귀로 하는 것보다 판정결과가 일관성있다.

sm.grapics.tsa.plot_acf(raw.value.diff(1).dropna(), lags=100, use_vlines=True)# 계절성이 남아 있는걸로 보인다
plt.tight_layout()
plt.show()

 

 

실습: 호흡기질환 사망자수 계절성 제거

# 라이브러리 및 데이터 로딩
import pandas as pd
from statsmodels import datasets
import matplotlib.pyplot as plt
import statsmodels.api as sm
%reload_ext autoreload
%autoreload 2
from module import stationarity_adf_test, stationarity_kpss_test

raw_set = datasets.get_rdataset("deaths", package="MASS")
raw = raw_set.data

#매월 마다 집계 되어 있는 것같다
#한 년도에 12개씩
raw

#시간 변수 추출
raw.time = pd.data_range('1974-01-01', periods=len(raw), freq='M') #매월 마지막날이 추가됨
raw['month']=raw.time.dt.month
raw

plt.plot(raw.time, raw.value)
plt.show()
#그래프가 계절성 같은 패턴을 보인다

#아무것도 안하고 돌려보기
display(stationarity_adf_test(raw.value, [])) #비정상
display(stationarity_kpss_test(raw.value, [])) #비정상
sm.graphics.tsa.plot_acf(raw.value, lags=50, use_vlines=True, title='ACF') #계절성 있다
plt.tight_layout()
plt.show()

#추세가 아닌 계절성을 제거 해보도록 한다
'''
C를 붙이고 컬럼 명을 넣으면 month의 더미변수를 만들고 fit한다
-1: y절편을반영하지 않겠다
각 계절이 호흡기 사망자 수에 어떤 영향을 주는지를 본다
1월달의 사망자 수를 얘기하는게 coef이다.
'''
result = sm.OLS.from_formula(formula='value ~ C(month) - 1', data=raw).fit()
display(result.summary())

#fittedvalues: 실제 예측된 추정값
plt.plot(raw.time, raw.value, raw.time, result.fittedvalues)
plt.show()

# 계절성 제거 및 정상성 확인
## 방법1
plt.plot(raw.time, result.resid)
plt.show()

display(stationarity_adf_test(result.resid, [])) #정상
display(stationarity_kpss_test(result.resid, [])) #비정상(계절성이 있다는 뜻)
sm.graphics.tsa.plot_acf(result.resid, lags=50, use_vlines=True) #계절성은 없는 편으로 보인다.
plt.tight_layout()
plt.show()
'''
약간의 감소세는 보이는 같고 계절성은 보이지 않는 것같다
자기상관 그래프를 보니까 좀더 안정적으로 만들 여지가 있다. 값을 줄일 여지가 있어 보인다
'''

 

# 계절성 제거 및 정상성 확인
## 방법2
sm.graphics.tsa.plot_acf(raw.value, lags=50, use_vlines=True)
plt.show()

plt.plot(raw.time, raw.value)
plt.title('Raw')
plt.show()

#diff 값을 3,6,12로 나눠서 실행
'''
첫 그래프를 보면 12개씩 반복되는 패턴이 보인다.12개월 마다 같은 패턴이 반복되는 걸 유추할수 있는데 
lagged그래프를 보면 녹색 그래프에서 3,6 그래프와 달리 갑자기 전혀 다른 패턴을 보인다.
즉, 시차에 맞는 lag값을 차분해 주면 녹색 그래프 처럼 전혀 다른 패턴을 보인다는 걸 알수있다.
'''
seasonal_lag = 3
plt.plot(raw.time[seasonal_lag:], raw.value.diff(seasonal_lag).dropna(), label='Lag{}'.format(seasonal_lag))
seasonal_lag = 6
plt.plot(raw.time[seasonal_lag:], raw.value.diff(seasonal_lag).dropna(), label='Lag{}'.format(seasonal_lag))
seasonal_lag = 12
plt.plot(raw.time[seasonal_lag:], raw.value.diff(seasonal_lag).dropna(), label='Lag{}'.format(seasonal_lag))
plt.title('Lagged')
plt.legend()
plt.show()

#통계량
seasonal_lag = 6
display(stationarity_adf_test(raw.value.diff(seasonal_lag).dropna(), [])) #정상
display(stationarity_kpss_test(raw.value.diff(seasonal_lag).dropna(), [])) #정상
sm.graphics.tsa.plot_acf(raw.value.diff(seasonal_lag).dropna(), lags=50,  #계절성 존재함
                         use_vlines=True, title='ACF of Lag{}'.format(seasonal_lag))
plt.tight_layout()
plt.show()

#자기상관그래프를 보니까 raw의 ACF보다 값이 더 작아지고 안정적이다. 더 개선되었다
seasonal_lag = 12
display(stationarity_adf_test(raw.value.diff(seasonal_lag).dropna(), [])) #비정상
display(stationarity_kpss_test(raw.value.diff(seasonal_lag).dropna(), [])) #정상
sm.graphics.tsa.plot_acf(raw.value.diff(seasonal_lag).dropna(), lags=50,  #계절성 없음
                         use_vlines=True, title='ACF of Lag{}'.format(seasonal_lag))
plt.tight_layout()
plt.show()

12시점마다 패턴이 반복된다
녹색일때 계절성이 없어졌다

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형