엑셀_파이썬_기타 코딩

롤링 PCA로 경제 인덱스 만들기

2023. 7. 27. 18:13

이전글 : https://oikon-mundi.tistory.com/448

 

파이썬: PCA로 종합 경제 지표 만들기 (hard data index construction)

주성분분석(PCA)는 여러 차원의 데이터를 하나의 차원으로 압축하는데 종종 쓰이는 기법이다. PCA를 통해 여러 경제 지표를 하나의 종합 인덱스로 압축하는 것도 가능하다. 방법론과 배경은 나중

oikon-mundi.tistory.com

 

위 글에서는 PCA로 경제지표를 만들었는데, 기간이 추가되면 기존의 경제지표수치도 변화한다는 점이 문제였다. 즉 작년에 기록한 2013년도의 지표수치가 -2였어도, 올해 기록한 2013년도 지표 수치는 3이 될 수 있다.

이러한 부분을 보완하기 위해 롤링 PCA(혹은 moving window PCA)를 써보기로 했다. 즉 일정 기간의 창(window)를 정해두고, 개별 창마다 PCA를 수행해서 값을 따오는 것이다. 예를 들면 2017.01~2019.01의 24개월 데이터를 하나의 창으로 두고 PCA를 수행해서 2019.01에 대응되는 PC1값을 형성한다. 그 다음 2017.02~2019.02의 24개월 데이터를 창으로 두고 다시 PCA를 수행하고, 2019.02의 PC1값을 채워넣어가는 방식이다.


코랩에 엑셀 데이터를 업로드하는 파트는 생략한다.

1.로우df에 인덱스 붙이기  

df = pd.read_excel(excel_direc, header =0, engine= 'openpyxl')
display(df)

df.set_index('observation date', drop= True, inplace =True )
df.sort_index(ascending = False, inplace= True )

display(df)

df 디스플레이 값

observation date	미국:개인가처분소득-SAAR	미국:자동차판매:트럭	미국:제조업 신규수주:내구재-SA	미국:실업률	미국:재고율(출하대비 재고):제조업-SA	미국:산업생산지수-SA	미국:심각한회수불량률(90일이상연체	미국:신규독주택판매	미국:건설기성액	미국:기업이익	미국:소매판매	미국:4주평균 신규 실업수당청구건수-NSA	미국:설비가동률:제조업-SA	미국:수입	미국:수출
0	2023-06-30	19853.44	547.0	288353.0	3.8	1.49	102.25	1.89	73	167.44	2688.02	613207.0	238110.50	77.97	915.19	738.87
1	2023-05-31	19853.44	566.0	288353.0	3.4	1.49	102.80	1.89	73	167.44	2688.02	631059.0	204279.25	78.32	915.19	738.87
2	2023-04-30	19766.76	538.0	283311.0	3.1	1.50	103.28	1.89	60	156.63	2688.02	588220.0	223385.60	78.49	915.19	738.87
3	2023-03-31	19708.94	486.0	279837.0	3.6	1.48	102.66	1.89	63	150.25	2688.02	604084.0	223530.00	77.79	915.19	738.87
4	2023-02-28	19619.11	516.0	270825.0	3.9	1.49	102.57	1.89	56	133.23	2721.29	529374.0	218125.50	78.45	969.73	766.70
...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...
709	1964-05-31	473.94	NaN	NaN	4.8	NaN	28.62	NaN	52	6.35	44.41	NaN	NaN	NaN	6.41	8.40
710	1964-04-30	471.41	NaN	NaN	5.3	NaN	28.46	NaN	49	6.00	44.41	NaN	NaN	NaN	6.41	8.40
711	1964-03-31	468.51	NaN	NaN	5.9	NaN	28.00	NaN	53	5.33	44.41	NaN	NaN	NaN	6.41	8.40
712	1964-02-29	457.38	NaN	NaN	6.2	NaN	28.00	NaN	46	4.51	40.69	NaN	NaN	NaN	6.66	8.38
713	1964-01-31	455.28	NaN	NaN	6.4	NaN	27.81	NaN	39	4.85	40.69	NaN	NaN	NaN	6.66	8.38
714 rows × 16 columns

미국:개인가처분소득-SAAR	미국:자동차판매:트럭	미국:제조업 신규수주:내구재-SA	미국:실업률	미국:재고율(출하대비 재고):제조업-SA	미국:산업생산지수-SA	미국:심각한회수불량률(90일이상연체	미국:신규독주택판매	미국:건설기성액	미국:기업이익	미국:소매판매	미국:4주평균 신규 실업수당청구건수-NSA	미국:설비가동률:제조업-SA	미국:수입	미국:수출
observation date															
2023-06-30	19853.44	547.0	288353.0	3.8	1.49	102.25	1.89	73	167.44	2688.02	613207.0	238110.50	77.97	915.19	738.87
2023-05-31	19853.44	566.0	288353.0	3.4	1.49	102.80	1.89	73	167.44	2688.02	631059.0	204279.25	78.32	915.19	738.87
2023-04-30	19766.76	538.0	283311.0	3.1	1.50	103.28	1.89	60	156.63	2688.02	588220.0	223385.60	78.49	915.19	738.87
2023-03-31	19708.94	486.0	279837.0	3.6	1.48	102.66	1.89	63	150.25	2688.02	604084.0	223530.00	77.79	915.19	738.87
2023-02-28	19619.11	516.0	270825.0	3.9	1.49	102.57	1.89	56	133.23	2721.29	529374.0	218125.50	78.45	969.73	766.70
...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...
1964-05-31	473.94	NaN	NaN	4.8	NaN	28.62	NaN	52	6.35	44.41	NaN	NaN	NaN	6.41	8.40
1964-04-30	471.41	NaN	NaN	5.3	NaN	28.46	NaN	49	6.00	44.41	NaN	NaN	NaN	6.41	8.40
1964-03-31	468.51	NaN	NaN	5.9	NaN	28.00	NaN	53	5.33	44.41	NaN	NaN	NaN	6.41	8.40
1964-02-29	457.38	NaN	NaN	6.2	NaN	28.00	NaN	46	4.51	40.69	NaN	NaN	NaN	6.66	8.38
1964-01-31	455.28	NaN	NaN	6.4	NaN	27.81	NaN	39	4.85	40.69	NaN	NaN	NaN	6.66	8.38

2.

#데이터 열에 대응되는 전처리 방법 딕셔너리 형성
transform_method_dic = {
'미국:개인가처분소득-SAAR': 'deltalog',
'미국:자동차판매:트럭': 'loglevel',
'미국:제조업 신규수주:내구재-SA':	'deltalog',
'미국:실업률':'loglevel',
'미국:재고율(출하대비 재고):제조업-SA' : 'loglevel',
'미국:산업생산지수-SA' : 'deltalog',
'미국:심각한회수불량률(90일이상연체': 'loglevel',
'미국:신규독주택판매' : 'deltalog',
'미국:건설기성액': 'deltalog',
'미국:기업이익' : 'deltalog',
'미국:소매판매' : 'deltalog',
'미국:4주평균 신규 실업수당청구건수-NSA' : 'deltalog', # 고민해봐야함
'미국:설비가동률:제조업-SA': 'loglevel',
'미국:수입' : 'deltalog',
'미국:수출' : 'deltalog',
 }

display(transform_method_dic )

3.데이터 열별로변환 

columns = list(df)
print(columns)

for i in range(0,len(columns)) :

  if  transform_method_dic[ columns[i] ] == 'loglevel' :
        df.iloc[:, i]  = np.log10( df.iloc[:, i] )

  elif  transform_method_dic[ columns[i] ] == 'deltalog' :
        df.iloc[:, i]  = np.log10( df.iloc[:, i] ) -  np.log10( df.iloc[:, i].shift(periods=-1))

  i=i+1

display(df)

4.

from sklearn.decomposition import PCA
pca = PCA(n_components=2) # 주성분을 몇개로 할지 결정

#Local PCA를 돌릴 때 시계 길이 (window_size = 24-> 24개월 )
window_size = 36

 #PC1을 모을 Global 데이터프레임 df_PC 형성
arr = np.zeros(( df.shape[0] -window_size, 2))
df_PC = pd.DataFrame(data = arr,columns = ['PC1','PC2'] )
df_PC.index = index_date = df.index.tolist()[:df.shape[0] -window_size ]  #df_PC의 인덱스는 로우데이터프레임 df의 인덱스를 리스트로 변환하고, 이 리스트를 다시 로우데이터 행수- 윈도우 사이즈 만큼만 잘라서 넣기

5. 반복문으로 개별 창마다 PCA를 수행하고 그 결과를 df_PC에 저장

-개별 창마다 PCA를 수행하면서 데이터가 없는 열은 제외한다. 압축하려는 경제지표들이 발표되기 시작한 시점이 다 다르므로, 시간 흐름에 따라 반영하는 경제지표들이 추가되는 식으로 PCA를 수행했다. 이런 방식이 얼마나 유의미한지는 논의 여지가 있다. 

#0부터 로우데이터 행수-윈도우사이즈를 반복해서 PCA를 돌림

try :
  for i  in range( 0, df.shape[0] - window_size) :
    df_w = df.iloc[i : i+ window_size ]

    df_w.dropna(axis=1, how= 'any', inplace = True)
    #df_w.dropna(axis=0)

    #정규화
    norm_df = (df_w - df_w.mean())/df_w.std()
    norm_df = norm_df.dropna()

    #PCA 수행
    PC = pca.fit_transform(norm_df)
    PCdf = pd.DataFrame(data=PC, columns = ['PC1','PC2']) # 개별 PCA의 결과인 Local PC1의 데이터프레임

    PCdf['PC1'] = PCdf['PC1'].apply(lambda x: x*-1)
    PCdf['PC2'] = PCdf['PC2'].apply(lambda x: x*-1)


    df_PC.iloc[i] = PCdf.iloc[0] # Local PCA 첫 값을 Global PC1 데이터프레임에 집어넣기

except: print(i)

#반복문 끝

display(df_PC)

6.그래프화 

#주성분1을 그래프화

import matplotlib.pyplot as plt
#import matplotlib.dates as dates
#import matplotlib.ticker as ticker
import matplotlib.dates as mdates

plt.figure(figsize=(19,6))
plt.plot(index_date, df_PC.iloc[:, 0], label= 'PC1 index')
plt.gca().xaxis.set_major_locator(mdates.YearLocator(base=2)) # gca로 축정보를 가져오고 축단위를 설정
plt.gca().xaxis.set_minor_locator(mdates.YearLocator(base=1))

plt.legend()
plt.grid(True)
plt.title('Hard data Index (Rolling PCA, window size={}m)'.format(window_size)) #format 함수로 {}에 window_size 변수를 이름으로 넣기

 

만들기는 했지만 유용성은 그다지 높지 않은 것같고, 가끔씩 재미삼아 참조해보려 한다. 적재계수까지 확인해보고 싶은데 당장은 그냥 두려 한다. 롤링 PCA 방법론에 대해 유의해야할 점을 좀 더 찾아보고싶지만 당장 눈에 띄는 것은 없다.