시계열 데이터 분석

Author

Gabriel Yang

KOSPI주가 정보를 저장한 시계열 데이터를 이용해서 정보를 분석하는 연습을 합니다. 시계열데이터 분석을 위해서 시간에 따라 이동시키거나 일정 주기로 추출하는 작업을 수행을 합니다. 일정 기간동안의 통계적 특성을 분석하기 위해서 window을 만들고 이동할 수도 있습니다.

우선 연습을 위해서 KOSPI주가 정보를 가져옵니다. KOSPI주가 정보를 가져오기 위해 FinanceDataReader 라이브러리를 사용합니다. DataReader함수에 시계열 정보를 가져올 주식 종목 기호 KS11과 시작 시점을 전달합니다.

import pandas as pd
import numpy as np
import FinanceDataReader as fdr

stock_name = 'KS11'
df = fdr.DataReader(stock_name, start = '2022-1-1')
display(df.head(5))
Open High Low Close Adj Close Volume
Date
2022-01-04 2991.969971 2995.250000 2973.080078 2989.239990 2989.239990 621200
2022-01-05 2984.050049 2986.199951 2936.729980 2953.969971 2953.969971 786900
2022-01-06 2925.399902 2952.540039 2915.379883 2920.530029 2920.530029 785500
2022-01-07 2933.780029 2959.030029 2933.100098 2954.889893 2954.889893 545800
2022-01-10 2947.370117 2951.120117 2910.899902 2926.719971 2926.719971 477000
Figure 1: KOPI 시계열 데이터

리턴된 데이터를 간단하게 살펴봅니다. 시간 정보로 Index가 표시되고 주가 정보는 각 컬럼에 표시됩니다. 해당 시점의 고점, 저점, 종가, 수정 종가, 시가 총액이 각각 컬럼으로 있습니다.

Figure 1 의 index의 데이터 형식은 DatetimeIndex로 출력됩니다. 따라서 시계열 데이터 분석에 사용할 수 있는 Pandas 기능을 사용할 수 있습니다.

shift()

시간을 이동시키는 명령어인 shift()함수를 알아봅니다. shift()함수는 지정한 수만큼 인덱스를 이동 시키는 명령입니다. Figure 1 데이터 프레임은 한국 주식 시장이 운영기간을 하루 단위로 측정된 시계열 데이터 프레임입니다. shift()명령을 이용하여 Close컬럼 데이터를 1개 Index크기 만큼 앞으로 이동합니다.

df['Close+1'] = df['Close'].shift(1)
display(df[['Close', 'Close+1']].head(5))
Close Close+1
Date
2022-01-04 2989.239990 NaN
2022-01-05 2953.969971 2989.239990
2022-01-06 2920.530029 2953.969971
2022-01-07 2954.889893 2920.530029
2022-01-10 2926.719971 2954.889893

비교를 쉽게하기 위해서 CloseClose+1 컬럼만 표시하여 결과를 확인합니다. 새롭게 생성한 Close+1 컬럼은 Close 컬럼이 이동되어 저장되었습니다. 정보를 가져올 수 없는 2023-01-02 인덱스 정보는 NaN으로 표시됩니다.

asfreq()

시계열 데이터 프레임을 새로운 빈도로 변환하기 위해서 as_freq()함수를 사용합니다. KOSPI주가 데이터의 월별 종가 정보를 확인할 수 있도록 이 함수를 사용해 봅니다.

df_monthly = df.asfreq('M')
display(df_monthly.head(5))
Open High Low Close Adj Close Volume Close+1
Date
2022-01-31 NaN NaN NaN NaN NaN NaN NaN
2022-02-28 2663.000000 2699.179932 2658.250000 2699.179932 2699.179932 613300.0 2676.760010
2022-03-31 2743.239990 2765.199951 2743.199951 2757.649902 2757.649902 1029500.0 2746.739990
2022-04-30 NaN NaN NaN NaN NaN NaN NaN
2022-05-31 2666.879883 2685.899902 2654.320068 2685.899902 2685.899902 670700.0 2669.659912

결과를 보면 NaN으로 데이터가 처리된 부분이 있습니다. 2020년 1월 31일과 2022년 4월 30일 데이터는 왜 없는 걸까요?

start_day = pd.Timestamp('2022-01-28 00:00:00')
end_day = start_day + pd.DateOffset(days = 10)
df.loc[start_day : end_day, :]
Open High Low Close Adj Close Volume Close+1
Date
2022-01-28 2617.870117 2668.590088 2591.530029 2663.340088 2663.340088 433700 2614.489990
2022-02-03 2706.340088 2735.340088 2702.780029 2707.820068 2707.820068 435300 2663.340088
2022-02-04 2714.830078 2751.800049 2712.870117 2750.260010 2750.260010 535900 2707.820068
2022-02-07 2750.699951 2750.699951 2718.939941 2745.060059 2745.060059 417600 2750.260010

시작일을 2022-01-28일로 설정하고 시작일로 부터 10일 간격 뒤의 시간을 종료일로 설정 후 해당 위치의 데이터 프레임 정보를 확인합니다. 1월의 마지막은 1월 31일이지만 주식시장은 1월 28일 거래로 폐장 후 2월에 개장하기 때문에 데이터가 없는 상태입니다.

asfreq()로 간격을 설정한 기준은 월말을 기준으로 했기 때문에 해당 시점에 데이터가 없어 NaN으로 결과가 출력 되었습니다.

Tip

새로운 시간 단위를 설정 시 새롭게 설정하는 시간간격에 맞는 원데이터가 없는 경우 NaN으로 데이터가 표시되기 때문에 결과에 유의 해야합니다.

주식정보를 갖는 이 데이터 프레임의 특성 상 해당 날짜에 주가 정보가 없다면 직전 날짜의 주가 정보를 유지하면 될 것 같습니다. 1월 28일의 마지막 주가 정보를 1월말 주가 정보로 대체해도 문제가 없는 데이터이기 때문에 as_freq()함수의 method인자를 이용합니다.

method인자에는 NaN으로 표시된 결측치를 어떤 정보로 채울지 결정할 정보를 전달합니다. API문서를 확인하면 bfillffill을 지원한다고 합니다. 우리의 경우 이전 시점의 데이터로 결측치를 채울 예정이니 ffill로 설정합니다.

df_monthly = df.asfreq('M', method='ffill')
display(df_monthly.head(5))
Open High Low Close Adj Close Volume Close+1
Date
2022-01-31 2617.870117 2668.590088 2591.530029 2663.340088 2663.340088 433700 2614.489990
2022-02-28 2663.000000 2699.179932 2658.250000 2699.179932 2699.179932 613300 2676.760010
2022-03-31 2743.239990 2765.199951 2743.199951 2757.649902 2757.649902 1029500 2746.739990
2022-04-30 2669.179932 2696.100098 2664.060059 2695.050049 2695.050049 975000 2667.489990
2022-05-31 2666.879883 2685.899902 2654.320068 2685.899902 2685.899902 670700 2669.659912

이번에는 월말인 2022년 1월 31일 데이터가 NaN이 아니고 이전 시점의 데이터로 업데이트 된 것을 알 수 있습니다.

resample()

resample()함수는 주어진 빈도로 리샘플링할 때 사용합니다. 데이터를 보간하여 빈 시간대에 대한 새로운 값을 생성합니다. 시간을 기반으로 한 데이터에 적용하는 groupby함수로 생각할 수 있습니다.

asfreq()함수는 주기를 변경하기 위해 사용되어 해당 시점에 원데이터가 없다면 NaN으로 표시됩니다.

df_monthly = df.resample('M').last()
df_monthly.head(5)
Open High Low Close Adj Close Volume Close+1
Date
2022-01-31 2617.870117 2668.590088 2591.530029 2663.340088 2663.340088 433700 2614.489990
2022-02-28 2663.000000 2699.179932 2658.250000 2699.179932 2699.179932 613300 2676.760010
2022-03-31 2743.239990 2765.199951 2743.199951 2757.649902 2757.649902 1029500 2746.739990
2022-04-30 2669.179932 2696.100098 2664.060059 2695.050049 2695.050049 975000 2667.489990
2022-05-31 2666.879883 2685.899902 2654.320068 2685.899902 2685.899902 670700 2669.659912


resample()함수에 시간 간격이 전달된 후 리턴되는 객체는 DatetimeIndexResampler입니다. 이 객체에 시간단위로 그룹된 데이터를 처리할 방식을 전달합니다. 월단위로 리샘플링된 그룹에 마지막 값을 취하기 위해서 last()를 수행하여 매월 마지막 주가정보로 저장되었습니다.

df_monthly = df.resample('M').mean()
df_monthly.head(5)
Open High Low Close Adj Close Volume Close+1
Date
2022-01-31 2872.972116 2886.611598 2842.880525 2859.066830 2859.066830 5.516632e+05 2869.940538
2022-02-28 2722.530002 2740.107788 2703.206665 2724.015015 2724.015015 5.617500e+05 2722.023912
2022-03-31 2699.054316 2710.955694 2684.923840 2698.716192 2698.716192 6.851857e+05 2695.931908
2022-04-30 2700.210007 2711.937163 2688.504790 2703.242850 2703.242850 1.048310e+06 2706.223796
2022-05-31 2628.057971 2642.537500 2613.529016 2629.215002 2629.215002 7.979600e+05 2629.672510


KOSPI주가 지수의 월별 평균값을 구해봅니다. 시간간격은 동일하게 월 단위이고 해당 시간 단위 데이터를 평균하기 위해 mean함수를 사용했습니다.

rolling()

시계열 정보에서는 여러가지 이동 통계를 사용합니다. 일반적으로 이동 평균, 이동 표준 편차등이 주로 사용됩니다. 이동 통계를 이용하면 시계열 데이터의 추세를 확인할 수 있으며 이상치 검출에도 사용할 수 있습니다. 이동 윈도우에 크기에 따라서 데이터를 부드럽게 만들 수 있기 때문에 데이터가 가지는 불필요한 고주파 노이즈를 제거할 수 있습니다.

Pandas에서는 rolling()함수로 롤링 윈도우를 지원합니다. 이 함수를 이용해서 KOSPI 지수의 60일 이동평균값을 확인해봅니다.

df_monthly = df.rolling('60d').mean()
df_monthly.head(5)
Open High Low Close Adj Close Volume Close+1
Date
2022-01-04 2991.969971 2995.250000 2973.080078 2989.239990 2989.239990 621200.0 NaN
2022-01-05 2988.010010 2990.724976 2954.905029 2971.604981 2971.604981 704050.0 2989.239990
2022-01-06 2967.139974 2977.996663 2941.729980 2954.579997 2954.579997 731200.0 2971.604981
2022-01-07 2958.799988 2973.255005 2939.572510 2954.657471 2954.657471 684850.0 2954.579997
2022-01-10 2956.514014 2968.828027 2933.837988 2949.069971 2949.069971 643280.0 2954.657471