Pandas 시계열 자료(자습용)

pandas_time_series

Pandas 금융 데이터 추적하기

들어가는 말

빅데이터기술경영 수업을 듣고 있는 학생들, 수고 많습니다. 수업중에 말씀드린 바와 같이 Pandas는 금융 시계열 데이터를 다룰 때 편하게 하려고 만들어진 패키지입니다. 이것은 개발자인 웨스 맥키니가 직접 밝히기도 한 것입니다.

오늘은 시계열 데이터를 다루는 방법을 일부 살펴보려 합니다. 자세한 내용은 웨스의 블로그나 Python for Data Analysis를 참고하기를 바랍니다.

먼저 필요한 모듈들을 모두 불러보겠습니다.

In [78]:
import pandas as pd #무조건 하세요.
import numpy as np #무조건 하라고 했죠?
import fix_yahoo_finance as yf #Yhoo Finance API 사용에 필요함
from pandas_datareader import data #데이터 다운로더
from datetime import time #시계열 데이터 추출에 사용할 것임(시각)
import matplotlib.pyplot as plt #시계열 데이터 플롯

시계열 인덱스 만들기

시간을 인덱스로 하는 데이터를 만듭니다. 분 단위로 데이터를 추려보겠습니다. date_range()는 어디서부터 어디까지 시계열 인덱스를 생성합니다.

기호 설명 기호 설명 기호 설명
B 비즈니스 데이 D 캘린더 데이 W 주당
M 월당 BM 비즈니스 월당 Q 분기
BQ 비즈니스 분기 A 연간 Y 연간
H T S

적절한 것을 freq로 지정하면 됩니다.

In [20]:
rng=pd.date_range('2012-06-01 09:30','2012-06-01 15:59',freq='T')
In [24]:
rng[:10]
Out[24]:
DatetimeIndex(['2012-06-01 09:30:00', '2012-06-01 09:31:00',
               '2012-06-01 09:32:00', '2012-06-01 09:33:00',
               '2012-06-01 09:34:00', '2012-06-01 09:35:00',
               '2012-06-01 09:36:00', '2012-06-01 09:37:00',
               '2012-06-01 09:38:00', '2012-06-01 09:39:00'],
              dtype='datetime64[ns]', freq='T')

분 단위로 인덱스가 만들어졌습니다. DatetimeIndex() 개체로 지정된 것 보입니다.

Business day기준으로 데이터를 뽑는다. BDay()는 business day로 offset 시간대를 동일하게 유지하고 비즈니스 일을 기준으로 3일을 연장해봅시다. 연장하는 방법은 offsets 처리기를 쓰면 됩니다. 비즈니스 기준 날짜를 연장하는 기능은 BDay()가 제공합니다. 데이터를 연장하여 붙이려면 append()를 하면 됩니다. Padas의 좋은 점은 Python의 list나 dict과 자유롭게 결합된다는 것입니다. 수업시간이 말씀드렸지요.

In [25]:
rng=rng.append([rng+pd.offsets.BDay(i) for i in range(1,4)])

이제 시계열 데이터를 만들어서 인덱스를 붙여봅시다. 그냥 1부터 숫자를 주르륵 붙이겠습니다. 데이터는 np.arange()로 생성하고 숫자는 실수(float)로 가정합시다.

In [26]:
ts=pd.Series(np.arange(len(rng),dtype=float),index=rng)

일부만 살펴보죠.

In [28]:
ts.head()
Out[28]:
2012-06-01 09:30:00    0.0
2012-06-01 09:31:00    1.0
2012-06-01 09:32:00    2.0
2012-06-01 09:33:00    3.0
2012-06-01 09:34:00    4.0
dtype: float64

파이썬의 time()을 이용해서 시간대별 데이터를 추출하자.

시간 데이터 추출하기

datetime 패키지에서 가져온 time()을 써봅시다. 시, 분을 입력값으로 합니다. 10시 데이터를 뽑으려면 다음과 같이 합니다.

In [30]:
ts[time(10,0)]
Out[30]:
2012-06-01 10:00:00      30.0
2012-06-04 10:00:00     420.0
2012-06-05 10:00:00     810.0
2012-06-06 10:00:00    1200.0
dtype: float64

다음과 같이 해도 결과는 같습니다.

In [ ]:
ts.at_time(time(10,0))

두 시간 사이의 값을 구해봅시다.

In [31]:
ts.between_time(time(10,0),time(10,1))
Out[31]:
2012-06-01 10:00:00      30.0
2012-06-01 10:01:00      31.0
2012-06-04 10:00:00     420.0
2012-06-04 10:01:00     421.0
2012-06-05 10:00:00     810.0
2012-06-05 10:01:00     811.0
2012-06-06 10:00:00    1200.0
2012-06-06 10:01:00    1201.0
dtype: float64

이번에는 난수 순열을 만들어서 적당히 뽑은 다음 순서대로 정리해봅시다.

In [32]:
indexer=np.sort(np.random.permutation(len(ts))[700:])
In [34]:
indexer[:10]
Out[34]:
array([ 1,  3,  6,  9, 11, 13, 14, 15, 16, 17])
In [35]:
irr_ts=ts.copy()
In [36]:
irr_ts[indexer]=np.nan

근사값 처리하는 모습을 이해하기 위해 일부러 값을 좀 비워봤습니다.

In [38]:
irr_ts.head()
Out[38]:
2012-06-01 09:30:00    0.0
2012-06-01 09:31:00    NaN
2012-06-01 09:32:00    2.0
2012-06-01 09:33:00    NaN
2012-06-01 09:34:00    4.0
dtype: float64
In [39]:
irr_ts['2012-06-01 09:50':'2012-06-01 10:00']
Out[39]:
2012-06-01 09:50:00    20.0
2012-06-01 09:51:00    21.0
2012-06-01 09:52:00     NaN
2012-06-01 09:53:00    23.0
2012-06-01 09:54:00    24.0
2012-06-01 09:55:00    25.0
2012-06-01 09:56:00    26.0
2012-06-01 09:57:00     NaN
2012-06-01 09:58:00     NaN
2012-06-01 09:59:00     NaN
2012-06-01 10:00:00     NaN
dtype: float64

값이 존재하지 않는 것들이 섞여 있네요. 이제 근사값을 구하는 asof와 date_range를 사용합시다.

In [42]:
selection=pd.date_range('2012-06-01 09:52',periods=4,freq='B')
In [43]:
selection
Out[43]:
DatetimeIndex(['2012-06-01 09:52:00', '2012-06-04 09:52:00',
               '2012-06-05 09:52:00', '2012-06-06 09:52:00'],
              dtype='datetime64[ns]', freq='B')

09:52데이터는 보다시피 NaN입니다.

In [44]:
irr_ts.asof(selection)
Out[44]:
2012-06-01 09:52:00      21.0
2012-06-04 09:52:00     412.0
2012-06-05 09:52:00     801.0
2012-06-06 09:52:00    1192.0
Freq: B, dtype: float64

그러나 asof가 근사값을 구했습니다.

시계열 결합

In [45]:
data1=pd.DataFrame(np.ones((6,3),dtype=float),columns=list('abc'),index=pd.date_range('6/12/2012',periods=6))
In [46]:
data1.head()
Out[46]:
a b c
2012-06-12 1.0 1.0 1.0
2012-06-13 1.0 1.0 1.0
2012-06-14 1.0 1.0 1.0
2012-06-15 1.0 1.0 1.0
2012-06-16 1.0 1.0 1.0
In [47]:
data2=pd.DataFrame(np.ones((6,3),dtype=float)*2,columns=list('abc'),index=pd.date_range('6/13/2012',periods=6))
In [48]:
data2.head()
Out[48]:
a b c
2012-06-13 2.0 2.0 2.0
2012-06-14 2.0 2.0 2.0
2012-06-15 2.0 2.0 2.0
2012-06-16 2.0 2.0 2.0
2012-06-17 2.0 2.0 2.0

위 시계열 두 개를 합치는 방법은 concat을 쓰면 됩니다.

In [49]:
sliced=pd.concat([data1.loc[:'2012-06-14'],data2.loc['2012-06-15':]])
In [50]:
sliced
Out[50]:
a b c
2012-06-12 1.0 1.0 1.0
2012-06-13 1.0 1.0 1.0
2012-06-14 1.0 1.0 1.0
2012-06-15 2.0 2.0 2.0
2012-06-16 2.0 2.0 2.0
2012-06-17 2.0 2.0 2.0
2012-06-18 2.0 2.0 2.0

결합한 이후 결측 데이터가 있는 경우

In [51]:
data2=pd.DataFrame(np.ones((6,4),dtype=float)*2,columns=list('abcd'),index=pd.date_range('6/13/2012',periods=6))
In [52]:
data2
Out[52]:
a b c d
2012-06-13 2.0 2.0 2.0 2.0
2012-06-14 2.0 2.0 2.0 2.0
2012-06-15 2.0 2.0 2.0 2.0
2012-06-16 2.0 2.0 2.0 2.0
2012-06-17 2.0 2.0 2.0 2.0
2012-06-18 2.0 2.0 2.0 2.0
In [53]:
sliced=pd.concat([data1.loc[:'2012-06-14'],data2.loc['2012-06-15':]],sort=False)
In [54]:
sliced
Out[54]:
a b c d
2012-06-12 1.0 1.0 1.0 NaN
2012-06-13 1.0 1.0 1.0 NaN
2012-06-14 1.0 1.0 1.0 NaN
2012-06-15 2.0 2.0 2.0 2.0
2012-06-16 2.0 2.0 2.0 2.0
2012-06-17 2.0 2.0 2.0 2.0
2012-06-18 2.0 2.0 2.0 2.0

결측 데이터인 NaN이 발생되었습니다. data1에 해당 데이터가 없기 때문입니다.

In [55]:
sliced_filled=sliced.combine_first(data2)
In [56]:
sliced_filled
Out[56]:
a b c d
2012-06-12 1.0 1.0 1.0 NaN
2012-06-13 1.0 1.0 1.0 2.0
2012-06-14 1.0 1.0 1.0 2.0
2012-06-15 2.0 2.0 2.0 2.0
2012-06-16 2.0 2.0 2.0 2.0
2012-06-17 2.0 2.0 2.0 2.0
2012-06-18 2.0 2.0 2.0 2.0

combine_first()를 사용하면 입력한 것으로 NaN을 다 채웁니다. 이보다 간편한 방법은 update()를 사용하는 것입니다. 비워져 있는 것만 채우려면 overwrite=False로 합니다.

In [57]:
sliced.update(data2,overwrite=False)
In [58]:
sliced
Out[58]:
a b c d
2012-06-12 1.0 1.0 1.0 NaN
2012-06-13 1.0 1.0 1.0 2.0
2012-06-14 1.0 1.0 1.0 2.0
2012-06-15 2.0 2.0 2.0 2.0
2012-06-16 2.0 2.0 2.0 2.0
2012-06-17 2.0 2.0 2.0 2.0
2012-06-18 2.0 2.0 2.0 2.0

수익률 추이 계산하기

수익률 추이를 계산해봅시다. Pandas나 R로는 아주 쉽게 할 수 있습니다.

한 가지 알려드릴 것은, Google과 Yahoo의 API가 변경이 되어서 교과서에 나오는 방법으로는 데이터를 구할 수가 없다는 점입니다. 많은 분들이 질문을 하시는데 아마 온라인 블로그 등에 업데이터가 늦어 혼란이 많은 것 같습니다. 그래서 앞서 fix_yahoo_finance가 필요한 것입니다. 혹시 설치하지 않았다면 Anaconda 콘솔을 관리자 계정으로 열어 다음과 같이 입력합니다.

pip install fix_yahoo_finance

또한 구 버전의 Anaconda를 사용하고 있다면 pandas에서 에러가 발생할 수 있습니다. 그럴 때는,

conda uninstall pandas
conda install pandas

로 새로 설치해주십시오.

아래 명령어를 실행하고 나면 data.get_data_yahoo()가 정상적으로 작동합니다. 애플(AAPL)의 2011년 1월 1일부터 2012년 12월 31일까지 데이터를 구해 오겠습니다. 종가 데이터만 사용하지요.

In [59]:
yf.pdr_override()
In [60]:
price=data.get_data_yahoo('AAPL',start='2011-01-01',end='2012-12-31')['Close']
[*********************100%***********************]  1 of 1 downloaded

2011년 10월 3일과 2011년 3월 1일의 수익률 변화를 계산하는 방법은 다음과 같습니다.

In [63]:
price['2011-10-03']/price['2011-03-01']-1
Out[63]:
0.0723446893787576

그렇지만 일변 변화를 보다 더 간편하게 계산하는 pct_change()가 제공됩니다.

In [64]:
returns=price.pct_change()
In [65]:
price.head()
Out[65]:
Date
2010-12-31    46.08
2011-01-03    47.08
2011-01-04    47.33
2011-01-05    47.71
2011-01-06    47.68
Name: Close, dtype: float64

이제 수익지수를 구해보겠습니다. 변화율을 계속 곱하면 됩니다.

In [66]:
ret_index=(1+returns).cumprod()
In [68]:
ret_index.head()
Out[68]:
Date
2010-12-31         NaN
2011-01-03    1.021701
2011-01-04    1.027127
2011-01-05    1.035373
2011-01-06    1.034722
Name: Close, dtype: float64

첫 결측값을 1로 하겠습니다.

In [69]:
ret_index[0]=1

월 기준으로 수익지수의 변화를 보려면 resample()로 데이터를 다시 정리하면 됩니다. 인덱스를 기준으로 데이터를 축약하는 기능입니다. 자료를 참고하시기를 바랍니다.

In [70]:
m_returns=ret_index.resample('BM').last().pct_change() #business day, monthly
In [71]:
m_returns['2012']
Out[71]:
Date
2012-01-31    0.127031
2012-02-29    0.188315
2012-03-30    0.105304
2012-04-30   -0.025919
2012-05-31   -0.010787
2012-06-29    0.010905
2012-07-31    0.045787
2012-08-31    0.089169
2012-09-28    0.002841
2012-10-31   -0.107555
2012-11-30   -0.016931
2012-12-31   -0.129291
Freq: BM, Name: Close, dtype: float64

2012년을 기준으로 월변화를 관찰했습니다.

In [83]:
%matplotlib inline
In [84]:
m_returns['2012'].plot()
Out[84]:
<matplotlib.axes._subplots.AxesSubplot at 0x1df4aa04da0>
In [ ]:
 

댓글

이 블로그의 인기 게시물

R에서 csv 파일 읽는 법

xlwings tutorial - 데이터 계산하여 붙여 넣기