#11.3.3부터 복습
11장 시계열¶
11.1 날짜, 자료형, 도구¶
from datetime import datetime
now = datetime.now()
now
datetime.datetime(2023, 4, 11, 19, 17, 31, 981445)
now.year, now.month, now.day
(2023, 4, 11)
datetime은 날짜와 시간을 모두 저장하며 마이크로초까지 지원
datetime.timedelta는 두 datetime 객체 간의 시간적인 차이를 표현
delta = datetime(2011,1,7) - datetime(2008, 6, 24, 8, 15)
delta
datetime.timedelta(days=926, seconds=56700)
delta.days
926
delta.seconds
56700
timedelta를 더하거나 빼면 그만큼의 시간이 datetime 객체에 적용되어 새로운 객체를 만들 수 있다
from datetime import timedelta
start = datetime(2011, 1, 7)
start + timedelta(12)
datetime.datetime(2011, 1, 19, 0, 0)
start - 2*timedelta(12)
datetime.datetime(2010, 12, 14, 0, 0)
datetime 모듈형의 자료형¶
자료형 | 설명 |
---|---|
date | 그레고리안 달력을 사용해서 날짜(연,월,일) 저장 |
time | 하루의 시간을 시,분, 초, 마이크로초 단위로 저장 |
datetime | 날짜와 시간 저장 |
timedelta | 두 datetime 값 간의 차이(일, 초, 마이크로초) 표현 |
tzinfo | 지역시간대를 저장하기 위한 기본 자료형 |
11.1.1 문자열을 datetime으로 변환하기¶
str, strftime¶
datetime 객체와 나중에 소개할 pandas의 Timestamp 객체는 str 메서드나 strftime 메서드에 포맷 규칙을 넘겨서 문자열로 나타낼 수 있음
stamp = datetime(2011, 1, 3)
str(stamp)
'2011-01-03 00:00:00'
stamp.strftime('%Y-%m-%d')
'2011-01-03'
Datetime 포맷 규칙¶
426참고
datetime.strptime¶
이 포맷 코드는 datetime.strptime을 사용해 문자열을 날짜로 변환할 때 사용 가능
value = '2011-01-03'
datetime.strptime(value, '%Y-%m-%d')
datetime.datetime(2011, 1, 3, 0, 0)
datestrs = ['7/6/2011', '8/6/2011']
[datetime.strptime(x, '%m/%d/%Y') for x in datestrs]
[datetime.datetime(2011, 7, 6, 0, 0), datetime.datetime(2011, 8, 6, 0, 0)]
parser.parse¶
datetime.strptime은 알려진 형식의 날짜를 파싱하는 최적의 방법
하지만 매번 포맷 규칙을 써야 하는 건 귀찮은 일. 특히 흔히 쓰는 날짜 형식에 대해서는 더 그렇다
이 경우에는 서드파티 패키지인 dateutil에 포함된 parser.parse 메서드 사용(pandas 설치 시 자동으로 함께 설치)
from dateutil.parser import parse
parse('2011-01-03')
datetime.datetime(2011, 1, 3, 0, 0)
dayfirst=True¶
국제 로케일의 경우 날짜가 월 앞에 오는 경우가 매우 흔함
이런 경우 dayfirst=True를 넘겨주면 됨
parse('6/12/2011', dayfirst=True)
datetime.datetime(2011, 12, 6, 0, 0)
to_datetime¶
pandas는 일반적으로 DateFrame의 컬럼이나 축 색인으로 날짜가 담긴 배열 사용
to_datetime 메서드는 많은 종류의 날짜 표현 처리
ISO 8601 같은 표준 날짜 형식은 매우 빠르게 처리 가능
datestrs = ['2011-07-06 12:00:00', '2011-08-06 00:00:00']
pd.to_datetime(datestrs)
DatetimeIndex(['2011-07-06 12:00:00', '2011-08-06 00:00:00'], dtype='datetime64[ns]', freq=None)
또한 누락된 값(None, 빈 문자열 등)으로 간주되어야 할 값도 처리
idx = pd.to_datetime(datestrs + [None])
idx
DatetimeIndex(['2011-07-06 12:00:00', '2011-08-06 00:00:00', 'NaT'], dtype='datetime64[ns]', freq=None)
idx[2]
NaT
pd.isnull(idx)
array([False, False, True])
NaT(Not a Time)는 pandas에서 누락된 타임스탬프 데이터를 나타냄
로케일별 날짜 포맷¶
428쪽
11.2 시계열 기초¶
pandas에서 찾아볼 수 있는 가장 기본적인 시계열 객체의 종류는 파이썬 문자열이나 datetime 객체로 표현되는 타임스탬프로 색인된 Series
from datetime import datetime
dates = [datetime(2011, 1, 2), datetime(2011, 1, 5),
datetime(2011, 1, 7), datetime(2011, 1, 8),
datetime(2011, 1, 10), datetime(2011, 1, 12)]
dates
[datetime.datetime(2011, 1, 2, 0, 0), datetime.datetime(2011, 1, 5, 0, 0), datetime.datetime(2011, 1, 7, 0, 0), datetime.datetime(2011, 1, 8, 0, 0), datetime.datetime(2011, 1, 10, 0, 0), datetime.datetime(2011, 1, 12, 0, 0)]
ts = pd.Series(np.random.randn(6), index=dates)
ts
2011-01-02 -0.660452 2011-01-05 1.888484 2011-01-07 -1.080412 2011-01-08 -0.093792 2011-01-10 -0.388695 2011-01-12 0.744563 dtype: float64
내부적으로 보면 이들 datetime 객체는 DatetimeIndex에 들어 있으며 ts 변수의 타입은 TimeSeries
ts.index
DatetimeIndex(['2011-01-02', '2011-01-05', '2011-01-07', '2011-01-08', '2011-01-10', '2011-01-12'], dtype='datetime64[ns]', freq=None)
다른 Series와 마찬가지로 서로 다르게 색인된 시계열 객체 간의 산술 연산은 자동으로 날짜에 맞춰짐
ts + ts[::2]
2011-01-02 -1.320904 2011-01-05 NaN 2011-01-07 -2.160825 2011-01-08 NaN 2011-01-10 -0.777390 2011-01-12 NaN dtype: float64
ts[::2]는 ts에서 매 두 번째 항목 선택 [처음:끝:간격]
pandas는 NumPy의 datetime64 자료형을 사용해서 나노초의 정밀도를 가지는 타임스탬프를 저장
ts.index.dtype
dtype('<M8[ns]')
DatetimeIndex의 스칼라값은 pandas의 Timestamp 객체
stamp = ts.index[0]
stamp
Timestamp('2011-01-02 00:00:00')
Timestamp는 datetime 객체를 사용하는 어떤 곳에도 대체 사용 가능
11.2.1 색인, 선택, 부분 선택¶
시계열은 라벨에 기반해서 데이터를 선택하고 인덱싱할 때 pandas.Series와 동일하게 동작
stamp = ts.index[2]
ts[stamp]
-1.0804123566308572
해석할 수 있는 날짜를 문자열로 넘겨서 편리하게 사용
ts['1/10/2011']
-0.38869484568137774
ts['20110110']
-0.38869484568137774
긴 시계열에서는 연을 넘기거나 연, 월만 넘겨서 데이터의 일부 구간만 선택 가능
longer_ts = pd.Series(np.random.randn(1000),
index=pd.date_range('1/1/2000', periods=1000))
longer_ts
2000-01-01 -0.302320 2000-01-02 0.208258 2000-01-03 0.718211 2000-01-04 -1.750831 2000-01-05 1.594788 ... 2002-09-22 -1.751205 2002-09-23 -1.195382 2002-09-24 1.252427 2002-09-25 -0.277886 2002-09-26 1.395137 Freq: D, Length: 1000, dtype: float64
longer_ts['2001']
2001-01-01 0.324231 2001-01-02 -0.912948 2001-01-03 -1.302367 2001-01-04 0.277356 2001-01-05 -1.446043 ... 2001-12-27 -1.442742 2001-12-28 0.482156 2001-12-29 -1.825836 2001-12-30 -0.066086 2001-12-31 0.085874 Freq: D, Length: 365, dtype: float64
여기서 문자열 '2001'은 연도로 해석되어 해당 기간의 데이터 선택.
월에 대해서도 마찬가지
longer_ts['2001-05']
2001-05-01 1.399096 2001-05-02 1.247567 2001-05-03 -0.412225 2001-05-04 0.476326 2001-05-05 1.154219 2001-05-06 0.291118 2001-05-07 -0.281617 2001-05-08 0.324115 2001-05-09 1.808182 2001-05-10 0.056280 2001-05-11 -0.189004 2001-05-12 0.305886 2001-05-13 -0.811485 2001-05-14 0.664926 2001-05-15 2.217034 2001-05-16 -0.215491 2001-05-17 -0.715199 2001-05-18 1.391018 2001-05-19 -1.430651 2001-05-20 -0.188337 2001-05-21 -0.661401 2001-05-22 -0.502865 2001-05-23 -0.500264 2001-05-24 -1.155446 2001-05-25 -1.887830 2001-05-26 0.101240 2001-05-27 0.339684 2001-05-28 0.831127 2001-05-29 0.788567 2001-05-30 0.398429 2001-05-31 -0.235175 Freq: D, dtype: float64
datetime 객체로 데이터를 잘라내는 작업은 일반적인 Series와 동일한 방식
ts[datetime(2011,1,7):]
2011-01-07 -1.080412 2011-01-08 -0.093792 2011-01-10 -0.388695 2011-01-12 0.744563 dtype: float64
대부분의 시계열 데이터는 연대순으로 정렬되기 때문에 범위를 지정하기 위해 시계열에 포함하지 않고 타임스탬프를 이용해 Series를 나눌 수 있다
ts
2011-01-02 -0.660452 2011-01-05 1.888484 2011-01-07 -1.080412 2011-01-08 -0.093792 2011-01-10 -0.388695 2011-01-12 0.744563 dtype: float64
ts['1/6/2011': '1/11/2011']
2011-01-07 -1.080412 2011-01-08 -0.093792 2011-01-10 -0.388695 dtype: float64
앞서와 같이 날짜 문자열이나 datetime 혹은 타임스탬프를 넘길 수 있음
이런 방식으로 데이터를 나누면 NumPy 배열을 나누는 것처럼 원본 시계열에 대한 뷰를 생성
즉, 데이터 복사가 발생하지 않고 슬라이스에 대한 변경이 원본 데이터에도 반영
truncate¶
이와 동일한 인스턴스 메서드로 truncate가 있는데, 이 메서드는 TimeSeries를 두 개의 날짜로 나눔
ts.truncate(after='1/9/2011')
2011-01-02 -0.660452 2011-01-05 1.888484 2011-01-07 -1.080412 2011-01-08 -0.093792 dtype: float64
위 방식은 DataFrame에서도 동일하게 적용되며 로우에 인덱싱됨
dates = pd.date_range('1/1/2000', periods=100, freq='W-WED')
dates
DatetimeIndex(['2000-01-05', '2000-01-12', '2000-01-19', '2000-01-26', '2000-02-02', '2000-02-09', '2000-02-16', '2000-02-23', '2000-03-01', '2000-03-08', '2000-03-15', '2000-03-22', '2000-03-29', '2000-04-05', '2000-04-12', '2000-04-19', '2000-04-26', '2000-05-03', '2000-05-10', '2000-05-17', '2000-05-24', '2000-05-31', '2000-06-07', '2000-06-14', '2000-06-21', '2000-06-28', '2000-07-05', '2000-07-12', '2000-07-19', '2000-07-26', '2000-08-02', '2000-08-09', '2000-08-16', '2000-08-23', '2000-08-30', '2000-09-06', '2000-09-13', '2000-09-20', '2000-09-27', '2000-10-04', '2000-10-11', '2000-10-18', '2000-10-25', '2000-11-01', '2000-11-08', '2000-11-15', '2000-11-22', '2000-11-29', '2000-12-06', '2000-12-13', '2000-12-20', '2000-12-27', '2001-01-03', '2001-01-10', '2001-01-17', '2001-01-24', '2001-01-31', '2001-02-07', '2001-02-14', '2001-02-21', '2001-02-28', '2001-03-07', '2001-03-14', '2001-03-21', '2001-03-28', '2001-04-04', '2001-04-11', '2001-04-18', '2001-04-25', '2001-05-02', '2001-05-09', '2001-05-16', '2001-05-23', '2001-05-30', '2001-06-06', '2001-06-13', '2001-06-20', '2001-06-27', '2001-07-04', '2001-07-11', '2001-07-18', '2001-07-25', '2001-08-01', '2001-08-08', '2001-08-15', '2001-08-22', '2001-08-29', '2001-09-05', '2001-09-12', '2001-09-19', '2001-09-26', '2001-10-03', '2001-10-10', '2001-10-17', '2001-10-24', '2001-10-31', '2001-11-07', '2001-11-14', '2001-11-21', '2001-11-28'], dtype='datetime64[ns]', freq='W-WED')
long_df = pd.DataFrame(np.random.randn(100, 4),
index=dates,
columns=['Colorado', 'Texas', 'New York', 'Ohio'])
long_df
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000-01-05 | -0.010458 | 1.453430 | 0.933213 | -0.198765 |
2000-01-12 | 1.440750 | 1.077317 | 0.453703 | -1.385550 |
2000-01-19 | 0.545656 | -0.088485 | 0.228068 | -1.245183 |
2000-01-26 | 0.781862 | -0.341393 | 1.229398 | 0.712978 |
2000-02-02 | 0.340771 | -1.259383 | 0.289881 | 0.420280 |
... | ... | ... | ... | ... |
2001-10-31 | 1.062527 | 0.243646 | -1.920554 | -1.259347 |
2001-11-07 | 1.094378 | 0.723928 | -0.002725 | 0.233345 |
2001-11-14 | -0.576821 | 0.830371 | -1.132144 | -0.550131 |
2001-11-21 | 0.338640 | -0.025453 | -1.400102 | -1.212203 |
2001-11-28 | -0.113194 | -0.084810 | 2.740100 | -0.939532 |
100 rows × 4 columns
long_df.loc['5-2001']
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2001-05-02 | 2.244838 | -0.684860 | 2.062028 | 0.580022 |
2001-05-09 | 1.964933 | 0.085443 | -0.497791 | -1.794478 |
2001-05-16 | 1.162649 | 0.463409 | 1.580263 | 0.049227 |
2001-05-23 | -1.555454 | 0.761102 | -0.552816 | 1.388061 |
2001-05-30 | 1.100567 | -0.471734 | 0.099528 | 0.122781 |
11.2.2 중복된 색인을 갖는 시계열¶
어떤 애플리케이션에서는 여러 데이터가 특정 타임스탬프에 몰려 있다
dates = pd.DatetimeIndex(['1/1/2000', '1/2/2000', '1/2/2000',
'1/2/2000', '1/3/2000'])
dates
DatetimeIndex(['2000-01-01', '2000-01-02', '2000-01-02', '2000-01-02', '2000-01-03'], dtype='datetime64[ns]', freq=None)
dup_ts = pd.Series(np.arange(5), index=dates)
dup_ts
2000-01-01 0 2000-01-02 1 2000-01-02 2 2000-01-02 3 2000-01-03 4 dtype: int32
is_unique 속성을 통해 확인해보면 색인이 유일하지 X
dup_ts.index.is_unique
False
이 시계열 데이터를 인덱싱하면 타임스탬프의 중복 여부에 따라 스칼라값이나 슬라이스가 생성됨
dup_ts['1/3/2000'] # 중복없음
4
dup_ts['1/2/2000'] # 중복있음
2000-01-02 1 2000-01-02 2 2000-01-02 3 dtype: int32
level=0¶
유일하지 않은 타임스탬프를 가지는 데이터를 집계한다고 해보자
한 가지 방법은 groupby에 level=0(단일 단계 인덱싱)을 넘기는 것
grouped = dup_ts.groupby(level=0)
grouped.mean()
2000-01-01 0.0 2000-01-02 2.0 2000-01-03 4.0 dtype: float64
grouped.count()
2000-01-01 1 2000-01-02 3 2000-01-03 1 dtype: int64
11.3 날짜 범위, 빈도, 이동¶
pandas에서 일반적인 시계열은 불규칙적인 것으로 간주. 즉, 고정된 빈도를 갖지 X
하지만 시계열 안에서 누락된 값이 발생할지라도 일별, 월별, 혹은 매 15분 같은 상대적인 고정 빈도에서의 작업이 요구되는 경우가 종종 있음
다행스럽게도 pandas에는 리샘플링, 표준 시계열 빈도 모음, 빈도 추론 그리고 고정된 빈도의 날짜 범위를 위한 도구가 있음
예를 들어 아래 예제 시계열을 고정된 일 빈도로 변환하려면 resample메서드 사용
resample¶
ts
2011-01-02 -0.660452 2011-01-05 1.888484 2011-01-07 -1.080412 2011-01-08 -0.093792 2011-01-10 -0.388695 2011-01-12 0.744563 dtype: float64
resampler = ts.resample('D')
문자열 'D'는 일 빈도
11.3.1 날짜 범위 생성¶
pandas.daterange¶
pandas.daterange를 사용하면 특정 빈도에 따라 지정한 길이만큼의 Datetimeindex 생성
index = pd.date_range('2012-04-01', '2012-06-01')
index
DatetimeIndex(['2012-04-01', '2012-04-02', '2012-04-03', '2012-04-04', '2012-04-05', '2012-04-06', '2012-04-07', '2012-04-08', '2012-04-09', '2012-04-10', '2012-04-11', '2012-04-12', '2012-04-13', '2012-04-14', '2012-04-15', '2012-04-16', '2012-04-17', '2012-04-18', '2012-04-19', '2012-04-20', '2012-04-21', '2012-04-22', '2012-04-23', '2012-04-24', '2012-04-25', '2012-04-26', '2012-04-27', '2012-04-28', '2012-04-29', '2012-04-30', '2012-05-01', '2012-05-02', '2012-05-03', '2012-05-04', '2012-05-05', '2012-05-06', '2012-05-07', '2012-05-08', '2012-05-09', '2012-05-10', '2012-05-11', '2012-05-12', '2012-05-13', '2012-05-14', '2012-05-15', '2012-05-16', '2012-05-17', '2012-05-18', '2012-05-19', '2012-05-20', '2012-05-21', '2012-05-22', '2012-05-23', '2012-05-24', '2012-05-25', '2012-05-26', '2012-05-27', '2012-05-28', '2012-05-29', '2012-05-30', '2012-05-31', '2012-06-01'], dtype='datetime64[ns]', freq='D')
periods¶
기본적으로 date_range는 일별 타임스탬프 생성
만약 시작 날짜나 종료 날짜만 넘긴다면 생성할 기간의 숫자를 함께 전달
pd.date_range(start='2012-04-01', periods=20)
DatetimeIndex(['2012-04-01', '2012-04-02', '2012-04-03', '2012-04-04', '2012-04-05', '2012-04-06', '2012-04-07', '2012-04-08', '2012-04-09', '2012-04-10', '2012-04-11', '2012-04-12', '2012-04-13', '2012-04-14', '2012-04-15', '2012-04-16', '2012-04-17', '2012-04-18', '2012-04-19', '2012-04-20'], dtype='datetime64[ns]', freq='D')
pd.date_range(end='2012-06-01', periods=20)
DatetimeIndex(['2012-05-13', '2012-05-14', '2012-05-15', '2012-05-16', '2012-05-17', '2012-05-18', '2012-05-19', '2012-05-20', '2012-05-21', '2012-05-22', '2012-05-23', '2012-05-24', '2012-05-25', '2012-05-26', '2012-05-27', '2012-05-28', '2012-05-29', '2012-05-30', '2012-05-31', '2012-06-01'], dtype='datetime64[ns]', freq='D')
시작과 종료 날짜는 생성된 날짜 색인에 대해 엄격한 경계를 정의
예를 들어 날짜 색인이 각 월의 마지막 영업일을 포함하도록 하고 싶다면 빈도값으로 'BM' (월 영업마감일)을 전달
그러면 이 기간 안에 들어오는 날짜들만 포함
pd.date_range('2000-01-01', '2000-12-01', freq='BM')
DatetimeIndex(['2000-01-31', '2000-02-29', '2000-03-31', '2000-04-28', '2000-05-31', '2000-06-30', '2000-07-31', '2000-08-31', '2000-09-29', '2000-10-31', '2000-11-30'], dtype='datetime64[ns]', freq='BM')
기본 시계열 빈도¶
438쪽
date_range는 기본적으로 시작 시간이나 종료 시간의 타임스탬프(존재한다면)를 보존
pd.date_range('2012-05-02 12:56:31', periods=5)
DatetimeIndex(['2012-05-02 12:56:31', '2012-05-03 12:56:31', '2012-05-04 12:56:31', '2012-05-05 12:56:31', '2012-05-06 12:56:31'], dtype='datetime64[ns]', freq='D')
normalize¶
가끔은 시간 정보를 포함하여 시작 날짜와 종료 날짜를 갖고 있으나 관례에 따라 자정에 맞추어 타임스탬프를 *정규화*하고 싶을 때가 있음
이렇게 하려면 normalize 옵션 사용
pd.date_range('2012-05-12 12:56:31', periods=5, normalize=True)
DatetimeIndex(['2012-05-12', '2012-05-13', '2012-05-14', '2012-05-15', '2012-05-16'], dtype='datetime64[ns]', freq='D')
11.3.2 빈도와 날짜 오프셋¶
pandas에서 빈도는 *기본 빈도(base frequency)와 배수의 조합으로 이루어짐
기본 빈도는 보통 'M'(월별), 'H'(시간별)처럼 짧은 문자열로 참조
각 기본 빈도에는 일반적으로 날짜 오프셋*(date offset)이라고 불리는 객체 사용
예를 들어 시간별 빈도는 Hour 클래스를 사용해서 표현
from pandas.tseries.offsets import Hour, Minute
hour = Hour()
hour
<Hour>
이 오프셋의 곱은 정수를 넘겨서 구할 수 있음
four_hours = Hour(4)
four_hours
<4 * Hours>
대부분의 애플리케이션에서는 이런 객체들을 직접 만들어야 할 경우는 절대 없겠지만 대신 'H' 또는 '4H'처럼 문자열로 표현하게 될 것
기본 빈도 앞에 정수를 두면 해당 빈도의 곱을 생성
pd.date_range('2000-01-01', '2000-01-03 23:59', freq='4h')
DatetimeIndex(['2000-01-01 00:00:00', '2000-01-01 04:00:00', '2000-01-01 08:00:00', '2000-01-01 12:00:00', '2000-01-01 16:00:00', '2000-01-01 20:00:00', '2000-01-02 00:00:00', '2000-01-02 04:00:00', '2000-01-02 08:00:00', '2000-01-02 12:00:00', '2000-01-02 16:00:00', '2000-01-02 20:00:00', '2000-01-03 00:00:00', '2000-01-03 04:00:00', '2000-01-03 08:00:00', '2000-01-03 12:00:00', '2000-01-03 16:00:00', '2000-01-03 20:00:00'], dtype='datetime64[ns]', freq='4H')
여러 오프셋을 덧셈으로 합칠 수 있다
Hour(2) + Minute(30)
<150 * Minutes>
유사하게 빈도 문자열로 '1h30min'을 넘겨도 같은 표현으로 잘 해석
pd.date_range('2000-01-01', periods=10, freq='1h30min')
DatetimeIndex(['2000-01-01 00:00:00', '2000-01-01 01:30:00', '2000-01-01 03:00:00', '2000-01-01 04:30:00', '2000-01-01 06:00:00', '2000-01-01 07:30:00', '2000-01-01 09:00:00', '2000-01-01 10:30:00', '2000-01-01 12:00:00', '2000-01-01 13:30:00'], dtype='datetime64[ns]', freq='90T')
월별 주차(WOM)¶
한 가지 유용한 빈도 클래스는 WOM으로 시작하는 '월별 주차'
월별 주차를 사용하면 매월 3째주 금요일 같은 날자를 얻을 수 있음
rng = pd.date_range('2012-01-01', '2012-09-01', freq='WOM-3FRI')
list(rng)
[Timestamp('2012-01-20 00:00:00', freq='WOM-3FRI'), Timestamp('2012-02-17 00:00:00', freq='WOM-3FRI'), Timestamp('2012-03-16 00:00:00', freq='WOM-3FRI'), Timestamp('2012-04-20 00:00:00', freq='WOM-3FRI'), Timestamp('2012-05-18 00:00:00', freq='WOM-3FRI'), Timestamp('2012-06-15 00:00:00', freq='WOM-3FRI'), Timestamp('2012-07-20 00:00:00', freq='WOM-3FRI'), Timestamp('2012-08-17 00:00:00', freq='WOM-3FRI')]
11.3.3 데이터 시프트(shift)¶
시프트는 데이터를 시간 축에서 앞이나 뒤로 이동하는 것을 의미
Series와 DataFrame은 색인은 변경하지 않고 데이터를 앞이나 뒤로 느슨한 시프트를 수행하는 shift 메서드를 가짐
ts = pd.Series(np.random.randn(4),
index=pd.date_range('1/1/2000', periods=4, freq='M'))
ts
2000-01-31 -0.331971 2000-02-29 -1.161522 2000-03-31 -0.679998 2000-04-30 1.439298 Freq: M, dtype: float64
ts.shift(2)
2000-01-31 NaN 2000-02-29 NaN 2000-03-31 -0.331971 2000-04-30 -1.161522 Freq: M, dtype: float64
ts.shift(-2)
2000-01-31 -0.679998 2000-02-29 1.439298 2000-03-31 NaN 2000-04-30 NaN Freq: M, dtype: float64
이렇게 시프트를 하게 되면 시계열의 시작이나 끝에 결측치 발생
shift는 일반적으로 한 시계열 내에서, 혹은 DataFrame의 컬럼으로 표현할 수 있는 여러 시계열에서의 퍼센트 변화를 계산할 때 흔히 사용
코드로는 다음과 같이 표현
ts / ts.shift(1) - 1
2000-01-31 NaN 2000-02-29 2.498862 2000-03-31 -0.414563 2000-04-30 -3.116620 Freq: M, dtype: float64
느슨한 시프트는 색인을 바꾸지 않기 때문에 어떤 데이터는 버려지기도 함
그래서 만약 빈도를 알고 있다면 shift에 빈도를 넘겨서 타임스탬프가 확장되도록 할 수 있음
ts.shift(2, freq='M')
2000-03-31 -0.331971 2000-04-30 -1.161522 2000-05-31 -0.679998 2000-06-30 1.439298 Freq: M, dtype: float64
다른 빈도를 넘겨도 되는데, 이를 통해 아주 유연하게 데이터를 밀거나 당기는 작업 가능
ts.shift(3, freq='D')
2000-02-03 -0.331971 2000-03-03 -1.161522 2000-04-03 -0.679998 2000-05-03 1.439298 dtype: float64
ts.shift(1, freq='90T')
2000-01-31 01:30:00 -0.331971 2000-02-29 01:30:00 -1.161522 2000-03-31 01:30:00 -0.679998 2000-04-30 01:30:00 1.439298 dtype: float64
여기서 T는 분을 나타냄
오프셋만큼 날짜 시프트¶
pandas의 날짜 오프셋은 datetime이나 Timestamp 객체에서도 사용 가능
from pandas.tseries.offsets import Day, MonthEnd
now = datetime(2011, 11, 17)
now + 3* Day()
Timestamp('2011-11-20 00:00:00')
monthEnd¶
만일 MonthEnd 같은 앵커드 오프셋을 추가한다면 빈도 규칙의 다음 날짜로 롤 포워드(roll forward)됨
now + MonthEnd()
Timestamp('2011-11-30 00:00:00')
now + MonthEnd(2)
Timestamp('2011-12-31 00:00:00')
rollforward, rollback¶
앵커드 오프셋은 rollforward와 rollback 메서드를 사용해서 명시적으로 각각 날짜를 앞으로 밀거나 뒤로 당길 수 있음
now
datetime.datetime(2011, 11, 17, 0, 0)
offset = MonthEnd()
offset.rollforward(now)
Timestamp('2011-11-30 00:00:00')
offset.rollback(now)
Timestamp('2011-10-31 00:00:00')
이 메서드를 groupby와 함께 사용하면 날짜 오프셋을 영리하게 사용 가능
ts = pd.Series(np.random.randn(20),
index=pd.date_range('1/15/2000', periods=20, freq='4d'))
ts
2000-01-15 0.384681 2000-01-19 0.739548 2000-01-23 -0.273558 2000-01-27 -0.000211 2000-01-31 -0.711506 2000-02-04 -0.280505 2000-02-08 1.061804 2000-02-12 -0.599532 2000-02-16 2.097395 2000-02-20 -2.543772 2000-02-24 0.949874 2000-02-28 0.126215 2000-03-03 -0.607417 2000-03-07 -0.876065 2000-03-11 0.896403 2000-03-15 -1.084187 2000-03-19 2.338939 2000-03-23 0.788120 2000-03-27 1.486257 2000-03-31 -0.724399 Freq: 4D, dtype: float64
ts.groupby(offset.rollforward).mean()
2000-01-31 0.027791 2000-02-29 0.115926 2000-03-31 0.277206 dtype: float64
물론 가장 쉽고 빠른 방법은 resample 사용
ts.resample('M').mean()
2000-01-31 0.027791 2000-02-29 0.115926 2000-03-31 0.277206 Freq: M, dtype: float64
11.4 시간대 다루기¶
pytz.timezone¶
import pytz
pytz.common_timezones[-5:]
['US/Eastern', 'US/Hawaii', 'US/Mountain', 'US/Pacific', 'UTC']
pytz에서 시간대 객체를 얻으려면 pytz.timezone 사용
tz = pytz.timezone('America/New_York')
tz
<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>
pandas의 메서드에서는 시간대 이름이나 객체를 모두 사용가능하지만 시간대 이름을 사용하기 권장
11.4.1 시간대 지역화와 변환¶
기본적으로 pandas에서 시계열은 *시간대를 엄격히 다루지 X*
다음 시계열을 살펴보자
rng = pd.date_range('3/9/2012 9:30', periods=6, freq='D')
ts = pd.Series(np.random.randn(len(rng)), index=rng)
ts
2012-03-09 09:30:00 -0.303273 2012-03-10 09:30:00 -1.181472 2012-03-11 09:30:00 -1.532606 2012-03-12 09:30:00 -0.692163 2012-03-13 09:30:00 0.919303 2012-03-14 09:30:00 0.029354 Freq: D, dtype: float64
색인의 tz 필드는 None
print(ts.index.tz)
None
시간대를 지정해 날짜 범위 생성 가능
pd.date_range('3/9/2012 9:30', periods=10, freq='D', tz='UTC')
DatetimeIndex(['2012-03-09 09:30:00+00:00', '2012-03-10 09:30:00+00:00', '2012-03-11 09:30:00+00:00', '2012-03-12 09:30:00+00:00', '2012-03-13 09:30:00+00:00', '2012-03-14 09:30:00+00:00', '2012-03-15 09:30:00+00:00', '2012-03-16 09:30:00+00:00', '2012-03-17 09:30:00+00:00', '2012-03-18 09:30:00+00:00'], dtype='datetime64[ns, UTC]', freq='D')
tz_localize¶
*지역화* 시간으로의 변환은 tz_localize 메서드로 처리
ts
2012-03-09 09:30:00 -0.303273 2012-03-10 09:30:00 -1.181472 2012-03-11 09:30:00 -1.532606 2012-03-12 09:30:00 -0.692163 2012-03-13 09:30:00 0.919303 2012-03-14 09:30:00 0.029354 Freq: D, dtype: float64
ts_utc = ts.tz_localize('UTC')
ts_utc
2012-03-09 09:30:00+00:00 -0.303273 2012-03-10 09:30:00+00:00 -1.181472 2012-03-11 09:30:00+00:00 -1.532606 2012-03-12 09:30:00+00:00 -0.692163 2012-03-13 09:30:00+00:00 0.919303 2012-03-14 09:30:00+00:00 0.029354 Freq: D, dtype: float64
ts_utc.index
DatetimeIndex(['2012-03-09 09:30:00+00:00', '2012-03-10 09:30:00+00:00', '2012-03-11 09:30:00+00:00', '2012-03-12 09:30:00+00:00', '2012-03-13 09:30:00+00:00', '2012-03-14 09:30:00+00:00'], dtype='datetime64[ns, UTC]', freq='D')
tz_convert¶
시계열이 특정 시간대로 지역화되고 나면 tz_convert를 이용해 다른 시간대로 변환 가능
ts_utc.tz_convert('America/New_York')
2012-03-09 04:30:00-05:00 -0.303273 2012-03-10 04:30:00-05:00 -1.181472 2012-03-11 05:30:00-04:00 -1.532606 2012-03-12 05:30:00-04:00 -0.692163 2012-03-13 05:30:00-04:00 0.919303 2012-03-14 05:30:00-04:00 0.029354 Freq: D, dtype: float64
위 시계열의 경우에는 America/New_York 시간대에서 일광절약시간을 사용하고 있는데,
동부표준시(EST)로 맞춘 다음 UTC 혹은 베를린 시간으로 변환 가능
ts_eastern = ts.tz_localize('America/New_York')
ts_eastern.tz_convert('UTC')
2012-03-09 14:30:00+00:00 -0.303273 2012-03-10 14:30:00+00:00 -1.181472 2012-03-11 13:30:00+00:00 -1.532606 2012-03-12 13:30:00+00:00 -0.692163 2012-03-13 13:30:00+00:00 0.919303 2012-03-14 13:30:00+00:00 0.029354 dtype: float64
ts_eastern.tz_convert('Europe/Berlin')
2012-03-09 15:30:00+01:00 -0.303273 2012-03-10 15:30:00+01:00 -1.181472 2012-03-11 14:30:00+01:00 -1.532606 2012-03-12 14:30:00+01:00 -0.692163 2012-03-13 14:30:00+01:00 0.919303 2012-03-14 14:30:00+01:00 0.029354 dtype: float64
tz_localize와 tz_convert는 모두 DatetimeIndex의 인스턴스 메서드
ts.index.tz_localize('Asia/Shanghai')
DatetimeIndex(['2012-03-09 09:30:00+08:00', '2012-03-10 09:30:00+08:00', '2012-03-11 09:30:00+08:00', '2012-03-12 09:30:00+08:00', '2012-03-13 09:30:00+08:00', '2012-03-14 09:30:00+08:00'], dtype='datetime64[ns, Asia/Shanghai]', freq=None)
11.4.2 시간대를 고려해서 TimeStamp 객체 다루기¶
시계열이나 날짜 범위와 비슷하게 개별 TimeStamp 객체도 시간대를 고려한 형태로 변환 가능
stamp = pd.Timestamp('2011-03-12 04:00')
stamp_utc = stamp.tz_localize('utc')
stamp_utc.tz_convert('America/New_York')
Timestamp('2011-03-11 23:00:00-0500', tz='America/New_York')
Timestamp 객체 생성 시 시간대를 직접 넘겨주는 것도 가능
stamp_moscow = pd.Timestamp('2011-03-12 04:00', tz='Europe/Moscow')
stamp_moscow
Timestamp('2011-03-12 04:00:00+0300', tz='Europe/Moscow')
시간대를 고려한 Timestamp 객체는 내부적으로 UTC 타임스탬프 값을 유닉스 에포크(Unix epoch)(1970년 1월 1일)부터 현재까지의 나노초로 저장
이 UTC 값은 시간대 변환 과정에서 변하지 않고 유지됨
stamp_utc.value
1299902400000000000
stamp_utc.tz_convert('America/New_York').value
1299902400000000000
pandas의 DateOffset 객체를 이용해 시간 연산을 수행할 때는 가능하다면 일광절약시간 고려
DST로 전환되기 직전의 타임스탬프에 대한 예제를 살펴보자
먼저 DST 시행 30분 전의 Timestamp 생성
from pandas.tseries.offsets import Hour
stamp = pd.Timestamp('2012-03-12 01:30', tz='US/Eastern')
stamp
Timestamp('2012-03-12 01:30:00-0400', tz='US/Eastern')
stamp + Hour()
Timestamp('2012-03-12 02:30:00-0400', tz='US/Eastern')
그리고 DST 시행 90분 전의 Timestamp를 생성하자
stamp = pd.Timestamp('2012-11-04 00:30', tz='US/Eastern')
stamp
Timestamp('2012-11-04 00:30:00-0400', tz='US/Eastern')
stamp + 2*Hour()
Timestamp('2012-11-04 01:30:00-0500', tz='US/Eastern')
11.4.3 다른 시간대 간의 연산¶
서로 다른 시간대를 갖는 두 시계열이 하나로 합쳐지면 결과는 UTC가 됨
타임스탬프는 내부적으로 UTC로 저장되므로 추가적인 변환이 불필요한 명료한 연산
rng = pd.date_range('3/7/2012 9:30', periods=10, freq='B')
ts = pd.Series(np.random.randn(len(rng)), index=rng)
ts
2012-03-07 09:30:00 1.596012 2012-03-08 09:30:00 -0.055037 2012-03-09 09:30:00 -0.355602 2012-03-12 09:30:00 1.337220 2012-03-13 09:30:00 0.251376 2012-03-14 09:30:00 0.970821 2012-03-15 09:30:00 -1.236458 2012-03-16 09:30:00 -1.209916 2012-03-19 09:30:00 1.270191 2012-03-20 09:30:00 1.813012 Freq: B, dtype: float64
ts1 = ts[:7].tz_localize('Europe/London')
ts2 = ts1[2:].tz_convert('Europe/Moscow')
result = ts1 + ts2
result.index
DatetimeIndex(['2012-03-07 09:30:00+00:00', '2012-03-08 09:30:00+00:00', '2012-03-09 09:30:00+00:00', '2012-03-12 09:30:00+00:00', '2012-03-13 09:30:00+00:00', '2012-03-14 09:30:00+00:00', '2012-03-15 09:30:00+00:00'], dtype='datetime64[ns, UTC]', freq=None)
11.5 기간과 기간 연산¶
며칠, 몇 개월, 몇 분기, 몇 해 같은 기간은 Period 클래스로 표현 가능하며
문자열이나 정수 그리고 기본 시계열 빈도를 가지고 생성
p = pd.Period(2007, freq='A-DEC')
p
Period('2007', 'A-DEC')
여기서 Period 객체는 2007년 1월 1일부터 같은 해 12월 31일까지의 기간 표현
이 기간에 정수를 더하거나 빼서 편리하게 정해진 빈도에 따라 기간 이동 가능
p + 5
Period('2012', 'A-DEC')
p - 2
Period('2005', 'A-DEC')
만약 두 기간이 같은 빈도를 가진다면 두 기간의 차는 둘 사이의 간격
pd.Period('2014', freq='A-DEC') - p
<7 * YearEnds: month=12>
period_range¶
일반적인 기간 범위는 period_range 함수로 생성
rng = pd.period_range('2000-01-01', '2000-06-30', freq='M')
rng
PeriodIndex(['2000-01', '2000-02', '2000-03', '2000-04', '2000-05', '2000-06'], dtype='period[M]')
PeriodIndex 클래스는 순차적인 기간을 저장하며 다른 pandas 자료구조에서 축 색인과 마찬가지로 사용됨
pd.Series(np.random.randn(6), index=rng)
2000-01 1.846694 2000-02 -0.248803 2000-03 -0.374283 2000-04 1.815577 2000-05 0.798930 2000-06 0.109411 Freq: M, dtype: float64
다음과 같은 문자열 배열을 이용해 PeriodIndex 클래스를 생성하는 것도 가능
values = ['2001Q3', '2002Q2', '2003Q1']
index = pd.PeriodIndex(values, freq='Q-DEC')
index
PeriodIndex(['2001Q3', '2002Q2', '2003Q1'], dtype='period[Q-DEC]')
11.5.1 Period의 빈도 변환¶
asfreq¶
Period와 PeriodIndex 객체는 asfreq 메서드를 통해 다른 빈도로 변환 가능
새해 첫날부터 시작하는 연간 빈도를 월간 빈도로 변환해보자
p = pd.Period('2007', freq = 'A-DEC') #12월 마지막 일
p
Period('2007', 'A-DEC')
p.asfreq('M', how='start')
Period('2007-01', 'M')
p.asfreq('M', how='end')
Period('2007-12', 'M')
Period('2007', 'A-DEC')는 전체 기간에 대한 커서로 생각할 수 있고 월간으로 다시 나눌 수 있다
*회계연도* 마감이 12월이 아닌 경우 월간 빈도가 달라짐
p = pd.Period('2007', freq='A-JUN')
p
Period('2007', 'A-JUN')
p.asfreq('M', 'start')
Period('2006-07', 'M')
p.asfreq('M', 'end')
Period('2007-06', 'M')
빈도가 상위 단계에서 하위 단계로 변환되는 경우 상위 기간은 하위 기간이 어디에 속했는지에 따라 결정
예를 들어 A-JUN 빈도일 경우 2007년 8월은 실제로 2008년 기간에 속하게 됨
p = pd.Period('Aug-2007', 'M')
p.asfreq('A-JUN')
Period('2008', 'A-JUN')
모든 PeriodIndex 객체나 시계열은 지금까지 살펴본 내용과 같은 방식으로 변환 가능
rng = pd.period_range('2006', '2009', freq='A-DEC')
rng
PeriodIndex(['2006', '2007', '2008', '2009'], dtype='period[A-DEC]')
ts = pd.Series(np.random.randn(len(rng)), index=rng)
ts
2006 0.321144 2007 1.262563 2008 -0.656359 2009 -1.159757 Freq: A-DEC, dtype: float64
ts.asfreq('M', how='start')
2006-01 0.321144 2007-01 1.262563 2008-01 -0.656359 2009-01 -1.159757 Freq: M, dtype: float64
위 예에서 연 빈도는 해당 빈도의 시작 월부터 시작하는 월 빈도로 치환됨
만일 매 해의 마지막 영업일을 대신 사용하고 싶다면 'B' 빈도를 사용하고 해당 시간의 종료 지점을 지정해서 변환
ts.asfreq('B', how='end')
2006-12-29 0.321144 2007-12-31 1.262563 2008-12-31 -0.656359 2009-12-31 -1.159757 Freq: B, dtype: float64
11.5.2 분기 빈도¶
많은 분기 데이터는 일반적으로 *회계연도의 끝*인 12월의 마지막 날이나 마지막 업무일을 기준으로 보고하는데,
2012Q4는 회계연도의 끝이 어딘가에 따라 의미가 달라짐
pandas는 12가지 모든 경우의 수를 지원하며 분기 빈도는 Q-JAN부터 Q-DEC까지
p = pd.Period('2012Q4', freq='Q-JAN')
p
Period('2012Q4', 'Q-JAN')
회계연도 마감이 1월인 경우 2012Q4는 11월부터 1월까지가 되고 일간 빈도로 검사 가능
p.asfreq('D', 'start')
Period('2011-11-01', 'D')
p.asfreq('D', 'end')
Period('2012-01-31', 'D')
분기 영업마감일의 오후 4시를 가리키는 타임스탬프 구하기
p4pm = (p.asfreq('B', 'e') - 1).asfreq('T', 's') + 16*60
p4pm
Period('2012-01-30 16:00', 'T')
p4pm.to_timestamp()
Timestamp('2012-01-30 16:00:00')
period_range를 사용해 분기 범위 생성 가능
rng = pd.period_range('2011Q3', '2012Q4', freq='Q-JAN')
rng
PeriodIndex(['2011Q3', '2011Q4', '2012Q1', '2012Q2', '2012Q3', '2012Q4'], dtype='period[Q-JAN]')
ts = pd.Series(np.arange(len(rng)), index=rng)
ts
2011Q3 0 2011Q4 1 2012Q1 2 2012Q2 3 2012Q3 4 2012Q4 5 Freq: Q-JAN, dtype: int32
new_rng = (rng.asfreq('B', 'e') - 1).asfreq('T', 's') + 16*60
new_rng
PeriodIndex(['2010-10-28 16:00', '2011-01-28 16:00', '2011-04-28 16:00', '2011-07-28 16:00', '2011-10-28 16:00', '2012-01-30 16:00'], dtype='period[T]')
ts.index = new_rng.to_timestamp()
ts
2010-10-28 16:00:00 0 2011-01-28 16:00:00 1 2011-04-28 16:00:00 2 2011-07-28 16:00:00 3 2011-10-28 16:00:00 4 2012-01-30 16:00:00 5 dtype: int32
11.5.3 타임스탬프와 기간 서로 변환하기¶
to_period¶
타임스탬프로 색인된 Series와 DataFrame 객체는 to_period 메서드를 사용해서 기간(period)으로 변환 가능
rng = pd.date_range('2000-01-01', periods=3, freq='M')
ts = pd.Series(np.random.randn(3), index=rng)
ts
2000-01-31 0.619824 2000-02-29 1.041366 2000-03-31 -0.918987 Freq: M, dtype: float64
pts = ts.to_period()
pts
2000-01 0.619824 2000-02 1.041366 2000-03 -0.918987 Freq: M, dtype: float64
여기서 말하는 기간은 겹치지 않는 시간상의 간격을 뜻하므로 주어진 빈도에서 타임스탬프는 하나의 기간에만 속함
새로운 PeriodIndex의 빈도는 기본적으로 타임스탬프 값을 통해 추론되지만 원하는 빈도 직접 지정도 가능
결과에 중복되는 기간이 나오더라도 문제되지 X
rng = pd.date_range('1/29/2000', periods=6, freq='D')
rng
DatetimeIndex(['2000-01-29', '2000-01-30', '2000-01-31', '2000-02-01', '2000-02-02', '2000-02-03'], dtype='datetime64[ns]', freq='D')
ts2 = pd.Series(np.random.randn(6), index=rng)
ts2
2000-01-29 -0.286512 2000-01-30 -0.390444 2000-01-31 0.030516 2000-02-01 -0.845617 2000-02-02 0.270653 2000-02-03 -0.560364 Freq: D, dtype: float64
ts2.to_period('M')
2000-01 -0.286512 2000-01 -0.390444 2000-01 0.030516 2000-02 -0.845617 2000-02 0.270653 2000-02 -0.560364 Freq: M, dtype: float64
to_timestamp¶
기간을 타임스탬프로 변환하려면 to_timestamp 메서드 이용
pts = ts2.to_period()
pts
2000-01-29 -0.286512 2000-01-30 -0.390444 2000-01-31 0.030516 2000-02-01 -0.845617 2000-02-02 0.270653 2000-02-03 -0.560364 Freq: D, dtype: float64
pts.to_timestamp(how='end')
2000-01-29 23:59:59.999999999 -0.286512 2000-01-30 23:59:59.999999999 -0.390444 2000-01-31 23:59:59.999999999 0.030516 2000-02-01 23:59:59.999999999 -0.845617 2000-02-02 23:59:59.999999999 0.270653 2000-02-03 23:59:59.999999999 -0.560364 Freq: D, dtype: float64
11.5.4 배열로 PeriodIndex 생성하기¶
고정된 빈도를 갖는 데이터는 종종 여러 컬럼에 걸쳐 기간에 대한 정보를 함께 저장하기도 함
예를 들어 다음 거시경제학(매크로경제학macroeconomic) 데이터셋에는 연도와 분기가 구분된 컬럼에 존재
data = pd.read_csv('macrodata.csv')
data.head(5)
year | quarter | realgdp | realcons | realinv | realgovt | realdpi | cpi | m1 | tbilrate | unemp | pop | infl | realint | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1959.0 | 1.0 | 2710.349 | 1707.4 | 286.898 | 470.045 | 1886.9 | 28.98 | 139.7 | 2.82 | 5.8 | 177.146 | 0.00 | 0.00 |
1 | 1959.0 | 2.0 | 2778.801 | 1733.7 | 310.859 | 481.301 | 1919.7 | 29.15 | 141.7 | 3.08 | 5.1 | 177.830 | 2.34 | 0.74 |
2 | 1959.0 | 3.0 | 2775.488 | 1751.8 | 289.226 | 491.260 | 1916.4 | 29.35 | 140.5 | 3.82 | 5.3 | 178.657 | 2.74 | 1.09 |
3 | 1959.0 | 4.0 | 2785.204 | 1753.7 | 299.356 | 484.052 | 1931.3 | 29.37 | 140.0 | 4.33 | 5.6 | 179.386 | 0.27 | 4.06 |
4 | 1960.0 | 1.0 | 2847.699 | 1770.5 | 331.722 | 462.199 | 1955.5 | 29.54 | 139.6 | 3.50 | 5.2 | 180.007 | 2.31 | 1.19 |
data.year
0 1959.0 1 1959.0 2 1959.0 3 1959.0 4 1960.0 ... 198 2008.0 199 2008.0 200 2009.0 201 2009.0 202 2009.0 Name: year, Length: 203, dtype: float64
data.quarter
0 1.0 1 2.0 2 3.0 3 4.0 4 1.0 ... 198 3.0 199 4.0 200 1.0 201 2.0 202 3.0 Name: quarter, Length: 203, dtype: float64
이 배열을 PeriodIndex에 빈도값과 함께 전달하면 이를 조합해서 DataFrame에서 사용 가능한 색인을 만들어냄
index = pd.PeriodIndex(year=data.year, quarter=data.quarter,
freq='Q-DEC')
index
PeriodIndex(['1959Q1', '1959Q2', '1959Q3', '1959Q4', '1960Q1', '1960Q2', '1960Q3', '1960Q4', '1961Q1', '1961Q2', ... '2007Q2', '2007Q3', '2007Q4', '2008Q1', '2008Q2', '2008Q3', '2008Q4', '2009Q1', '2009Q2', '2009Q3'], dtype='period[Q-DEC]', length=203)
data.index = index
data.infl
1959Q1 0.00 1959Q2 2.34 1959Q3 2.74 1959Q4 0.27 1960Q1 2.31 ... 2008Q3 -3.16 2008Q4 -8.79 2009Q1 0.94 2009Q2 3.37 2009Q3 3.56 Freq: Q-DEC, Name: infl, Length: 203, dtype: float64
11.6 리샘플링과 빈도 변환¶
*리샘플링* : 시계열의 빈도를 변환하는 과정
*다운샘플링* : 상위 빈도의 데이터를 하위 빈도로 집계
*업샘플링* : 다운샘플링의 반대
resample¶
판다스가 가진 객체로 빈도 변환과 관련된 모든 작업에서 유용하게 사용되는 메서드
rng = pd.date_range('2000-01-01', periods=100, freq='D')
ts = pd.Series(np.random.randn(len(rng)), index=rng)
ts
2000-01-01 -1.005843 2000-01-02 -0.281913 2000-01-03 1.668201 2000-01-04 1.524664 2000-01-05 -1.535869 ... 2000-04-05 -0.263652 2000-04-06 1.268911 2000-04-07 -0.024712 2000-04-08 -1.270517 2000-04-09 -0.037249 Freq: D, Length: 100, dtype: float64
ts.resample('M').mean()
2000-01-31 -0.148666 2000-02-29 0.369455 2000-03-31 -0.356641 2000-04-30 0.294672 Freq: M, dtype: float64
ts.resample('M', kind='period').mean()
2000-01 -0.148666 2000-02 0.369455 2000-03 -0.356641 2000-04 0.294672 Freq: M, dtype: float64
resample 메서드 인자¶
464쪽
11.6.1 다운샘플링¶
resample을 사용해 데이터를 다운샘플링할 때 고려해야 할 사항
- 각 간격의 양끝 중에서 어느 쪽을 닫아둘 것인가
- 집계하려는 구간의 라벨을 간격의 시작으로 할지 끝으로 할지 여부
분 단위 데이터를 통해 좀 더 알아보자
rng = pd.date_range('2000-01-01', periods=12, freq='T') #T는 매분
ts = pd.Series(np.arange(12), index=rng)
ts
2000-01-01 00:00:00 0 2000-01-01 00:01:00 1 2000-01-01 00:02:00 2 2000-01-01 00:03:00 3 2000-01-01 00:04:00 4 2000-01-01 00:05:00 5 2000-01-01 00:06:00 6 2000-01-01 00:07:00 7 2000-01-01 00:08:00 8 2000-01-01 00:09:00 9 2000-01-01 00:10:00 10 2000-01-01 00:11:00 11 Freq: T, dtype: int32
각 데이터를 5분 단위로 묶어서 각 그룹의 합을 집계해보자
ts.resample('5min', closed='right').sum()
1999-12-31 23:55:00 0 2000-01-01 00:00:00 15 2000-01-01 00:05:00 40 2000-01-01 00:10:00 11 Freq: 5T, dtype: int32
인자로 넘긴 빈도는 5분 단위로 증가하는 그룹의 경계를 정의
기본적으로 시작값을 그룹의 *왼쪽*에 포함시키므로 00:00의 값은 첫 번째 그룹의 00:00부터 00:05까지의 값을 집계
closed='right'를 넘기면 시작값을 그룹의 오른쪽에 포함시킴
ts.resample('5min', closed='right').sum()
1999-12-31 23:55:00 0 2000-01-01 00:00:00 15 2000-01-01 00:05:00 40 2000-01-01 00:10:00 11 Freq: 5T, dtype: int32
label='right'¶
결과로 반환된 시계열은 각 그룹의 왼쪽 타임스탬프가 라벨로 지정
label='right'를 넘겨서 각 그룹의 오른쪽 값을 라벨로 사용 가능
ts.resample('5min', closed='right', label='right').sum()
2000-01-01 00:00:00 0 2000-01-01 00:05:00 15 2000-01-01 00:10:00 40 2000-01-01 00:15:00 11 Freq: 5T, dtype: int32
loffset¶
반환된 결과의 색인을 특정 크기만큼 이동시키고 싶은 경우, 즉 그룹의 오른쪽 끝에서 1초를 빼서 타임스탬프가 참조하는 간격을 좀 더 명확히 보여주고 싶은 경우 loffset 메서드에 문자열이나 날짜 오프셋을 넘김
ts.resample('5min', closed='right', label='right', loffset='-1s').sum()
C:\Users\user\AppData\Local\Temp\ipykernel_15216\649735751.py:1: FutureWarning: 'loffset' in .resample() and in Grouper() is deprecated. >>> df.resample(freq="3s", loffset="8H") becomes: >>> from pandas.tseries.frequencies import to_offset >>> df = df.resample(freq="3s").mean() >>> df.index = df.index.to_timestamp() + to_offset("8H") ts.resample('5min', closed='right', label='right', loffset='-1s').sum()
1999-12-31 23:59:59 0 2000-01-01 00:04:59 15 2000-01-01 00:09:59 40 2000-01-01 00:14:59 11 Freq: 5T, dtype: int32
loffset 대신 반환된 결과에 shift 메서드를 사용해도 같은 결과
OHLC 리샘플링 (how='ohlc')¶
금융 분야에서 시계열 데이터를 집계하는 아주 흔한 방식은 각 버킷에 대해 4가지 값 계산
이 4가지 값은 시가(open), 고가(high), 저가(low), 종가(close)이며, 이를 OHLC(Open-High-Low-Close)라고 함
how='ohlc'를 넘겨서 한 번에 이 값을 담고 있는 컬럼을 가지는 DataFrame 얻기 가능
ts.resample('5min').ohlc()
open | high | low | close | |
---|---|---|---|---|
2000-01-01 00:00:00 | 0 | 4 | 0 | 4 |
2000-01-01 00:05:00 | 5 | 9 | 5 | 9 |
2000-01-01 00:10:00 | 10 | 11 | 10 | 11 |
11.6.2 업샘플링과 보간¶
하위 빈도에서 상위 빈도로 변환할 때는 집계 필요 X
주간 데이터를 담고 있는 DataFrame을 살펴보자
frame = pd.DataFrame(np.random.randn(2,4),
index=pd.date_range('1/1/2000', periods=2,
freq='W-WED'),
columns=['Colorado', 'Texas', 'New York', 'Ohio'])
frame
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000-01-05 | 0.074284 | 0.132083 | -0.475665 | -0.118019 |
2000-01-12 | -0.270629 | 0.873430 | -0.010476 | 0.976725 |
이 데이터에 요약함수를 사용하면 그룹당 하나의 값이 들어가고 그 사이에 결측치가 들어감
asfreq 메서드를 이용해 어떤 요약함수도 사용하지 않고 상위 빈도로 리샘플링 해보자
df_daily = frame.resample('D').asfreq()
df_daily
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000-01-05 | 0.074284 | 0.132083 | -0.475665 | -0.118019 |
2000-01-06 | NaN | NaN | NaN | NaN |
2000-01-07 | NaN | NaN | NaN | NaN |
2000-01-08 | NaN | NaN | NaN | NaN |
2000-01-09 | NaN | NaN | NaN | NaN |
2000-01-10 | NaN | NaN | NaN | NaN |
2000-01-11 | NaN | NaN | NaN | NaN |
2000-01-12 | -0.270629 | 0.873430 | -0.010476 | 0.976725 |
수요일이 아닌 요일에는 이전 값을 채워서 보간을 수행한다고 가정
fillna와 reindex 메서드에서 사용했던 보간 메서드를 리샘플링에서도 사용 가능
frame.resample('D').ffill()
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000-01-05 | 0.074284 | 0.132083 | -0.475665 | -0.118019 |
2000-01-06 | 0.074284 | 0.132083 | -0.475665 | -0.118019 |
2000-01-07 | 0.074284 | 0.132083 | -0.475665 | -0.118019 |
2000-01-08 | 0.074284 | 0.132083 | -0.475665 | -0.118019 |
2000-01-09 | 0.074284 | 0.132083 | -0.475665 | -0.118019 |
2000-01-10 | 0.074284 | 0.132083 | -0.475665 | -0.118019 |
2000-01-11 | 0.074284 | 0.132083 | -0.475665 | -0.118019 |
2000-01-12 | -0.270629 | 0.873430 | -0.010476 | 0.976725 |
limit¶
limit 옵션을 사용해 보간법을 적용할 범위를 지정하는 것도 가능
frame.resample('D').ffill(limit=2)
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000-01-05 | 0.074284 | 0.132083 | -0.475665 | -0.118019 |
2000-01-06 | 0.074284 | 0.132083 | -0.475665 | -0.118019 |
2000-01-07 | 0.074284 | 0.132083 | -0.475665 | -0.118019 |
2000-01-08 | NaN | NaN | NaN | NaN |
2000-01-09 | NaN | NaN | NaN | NaN |
2000-01-10 | NaN | NaN | NaN | NaN |
2000-01-11 | NaN | NaN | NaN | NaN |
2000-01-12 | -0.270629 | 0.873430 | -0.010476 | 0.976725 |
특히 새로운 날짜 색인은 이전 색인과 겹쳐질 필요가 전혀 X
frame.resample('W_THU').ffill()
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) File ~\anaconda3\lib\site-packages\pandas\_libs\tslibs\offsets.pyx:3959, in pandas._libs.tslibs.offsets.to_offset() ValueError: separator must be spaces The above exception was the direct cause of the following exception: ValueError Traceback (most recent call last) Cell In[86], line 1 ----> 1 frame.resample('W_THU').ffill() File ~\anaconda3\lib\site-packages\pandas\core\frame.py:11392, in DataFrame.resample(self, rule, axis, closed, label, convention, kind, loffset, base, on, level, origin, offset, group_keys) 11375 @doc(NDFrame.resample, **_shared_doc_kwargs) 11376 def resample( 11377 self, (...) 11390 group_keys: bool | lib.NoDefault = no_default, 11391 ) -> Resampler: > 11392 return super().resample( 11393 rule=rule, 11394 axis=axis, 11395 closed=closed, 11396 label=label, 11397 convention=convention, 11398 kind=kind, 11399 loffset=loffset, 11400 base=base, 11401 on=on, 11402 level=level, 11403 origin=origin, 11404 offset=offset, 11405 group_keys=group_keys, 11406 ) File ~\anaconda3\lib\site-packages\pandas\core\generic.py:8858, in NDFrame.resample(self, rule, axis, closed, label, convention, kind, loffset, base, on, level, origin, offset, group_keys) 8855 from pandas.core.resample import get_resampler 8857 axis = self._get_axis_number(axis) -> 8858 return get_resampler( 8859 self, 8860 freq=rule, 8861 label=label, 8862 closed=closed, 8863 axis=axis, 8864 kind=kind, 8865 loffset=loffset, 8866 convention=convention, 8867 base=base, 8868 key=on, 8869 level=level, 8870 origin=origin, 8871 offset=offset, 8872 group_keys=group_keys, 8873 ) File ~\anaconda3\lib\site-packages\pandas\core\resample.py:1543, in get_resampler(obj, kind, **kwds) 1537 def get_resampler( 1538 obj, kind=None, **kwds 1539 ) -> DatetimeIndexResampler | PeriodIndexResampler | TimedeltaIndexResampler: 1540 """ 1541 Create a TimeGrouper and return our resampler. 1542 """ -> 1543 tg = TimeGrouper(**kwds) 1544 return tg._get_resampler(obj, kind=kind) File ~\anaconda3\lib\site-packages\pandas\core\resample.py:1613, in TimeGrouper.__init__(self, freq, closed, label, how, axis, fill_method, limit, loffset, kind, convention, base, origin, offset, group_keys, **kwargs) 1610 if convention not in {None, "start", "end", "e", "s"}: 1611 raise ValueError(f"Unsupported value {convention} for `convention`") -> 1613 freq = to_offset(freq) 1615 end_types = {"M", "A", "Q", "BM", "BA", "BQ", "W"} 1616 rule = freq.rule_code File ~\anaconda3\lib\site-packages\pandas\_libs\tslibs\offsets.pyx:3891, in pandas._libs.tslibs.offsets.to_offset() File ~\anaconda3\lib\site-packages\pandas\_libs\tslibs\offsets.pyx:3987, in pandas._libs.tslibs.offsets.to_offset() ValueError: Invalid frequency: W_THU
11.6.3 기간 리샘플링¶
기간으로 색인된 데이터를 리샘플링하는 것은 타임스탬프와 유사
frame = pd.DataFrame(np.random.randn(24, 4),
index=pd.period_range('1-2000', '12-2001',
freq='M'),
columns=['Colorado', 'Texas', 'New York', 'Ohio'])
frame[:5]
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000-01 | 0.503453 | -1.134415 | 1.494519 | 0.953009 |
2000-02 | -1.362463 | -0.077935 | 0.649666 | 0.850100 |
2000-03 | -0.347269 | -1.947039 | 0.777690 | -0.830139 |
2000-04 | -0.076448 | -1.436340 | 0.798756 | -0.592936 |
2000-05 | -0.368362 | 0.639689 | 0.660808 | 0.424828 |
annual_frame = frame.resample('A-DEC').mean()
annual_frame
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000 | -0.200521 | -0.206693 | 0.469497 | 0.260499 |
2001 | 0.192552 | 0.125546 | -0.142086 | -0.079427 |
convention¶
업샘플링은 asfreq 메서드처럼 리샘플링하기 전에 새로운 빈도에서 구간의 끝을 어느 쪽에 두어야 할지 미리 결정
convetion 인자의 기본값은 'start'지만 'end'로도 지정가능
#Q-DEC: 12월을 연도마감으로 하는 분위주기
annual_frame.resample('Q-DEC').ffill()
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000Q1 | -0.200521 | -0.206693 | 0.469497 | 0.260499 |
2000Q2 | -0.200521 | -0.206693 | 0.469497 | 0.260499 |
2000Q3 | -0.200521 | -0.206693 | 0.469497 | 0.260499 |
2000Q4 | -0.200521 | -0.206693 | 0.469497 | 0.260499 |
2001Q1 | 0.192552 | 0.125546 | -0.142086 | -0.079427 |
2001Q2 | 0.192552 | 0.125546 | -0.142086 | -0.079427 |
2001Q3 | 0.192552 | 0.125546 | -0.142086 | -0.079427 |
2001Q4 | 0.192552 | 0.125546 | -0.142086 | -0.079427 |
annual_frame.resample('Q-DEC', convention='end').ffill()
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000Q4 | -0.200521 | -0.206693 | 0.469497 | 0.260499 |
2001Q1 | -0.200521 | -0.206693 | 0.469497 | 0.260499 |
2001Q2 | -0.200521 | -0.206693 | 0.469497 | 0.260499 |
2001Q3 | -0.200521 | -0.206693 | 0.469497 | 0.260499 |
2001Q4 | 0.192552 | 0.125546 | -0.142086 | -0.079427 |
기간의 업샘플링과 다운샘플링은 좀 더 엄격
- 다운샘플링의 경우 대상 빈도는 반드시 원본 빈도의 *하위 기간*
- 업샘플링의 경우 대상 빈도는 반드시 원본 빈도의 *상위 기간*
위 조건을 만족하지 않으면 예외 발생
이 예외는 주로 분기, 연간, 주간 빈도에서 발생하는데,
예를 들어 Q-MAR로 정의된 기간은 A-MAR, A-JUN, A-SEP, A-DEC로만 이루어짐
annual_frame.resample('Q-MAR').ffill()
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2000Q4 | -0.200521 | -0.206693 | 0.469497 | 0.260499 |
2001Q1 | -0.200521 | -0.206693 | 0.469497 | 0.260499 |
2001Q2 | -0.200521 | -0.206693 | 0.469497 | 0.260499 |
2001Q3 | -0.200521 | -0.206693 | 0.469497 | 0.260499 |
2001Q4 | 0.192552 | 0.125546 | -0.142086 | -0.079427 |
2002Q1 | 0.192552 | 0.125546 | -0.142086 | -0.079427 |
2002Q2 | 0.192552 | 0.125546 | -0.142086 | -0.079427 |
2002Q3 | 0.192552 | 0.125546 | -0.142086 | -0.079427 |
11.7 이동창 함수¶
우선 시계열 데이터를 불러와서 영업일 빈도로 리샘플링
close_px_all = pd.read_csv('stock_px.csv', parse_dates=True, index_col=0)
close_px_all
AAPL | MSFT | XOM | SPX | |
---|---|---|---|---|
2003-01-02 | 7.40 | 21.11 | 29.22 | 909.03 |
2003-01-03 | 7.45 | 21.14 | 29.24 | 908.59 |
2003-01-06 | 7.45 | 21.52 | 29.96 | 929.01 |
2003-01-07 | 7.43 | 21.93 | 28.95 | 922.93 |
2003-01-08 | 7.28 | 21.31 | 28.83 | 909.93 |
... | ... | ... | ... | ... |
2011-10-10 | 388.81 | 26.94 | 76.28 | 1194.89 |
2011-10-11 | 400.29 | 27.00 | 76.27 | 1195.54 |
2011-10-12 | 402.19 | 26.96 | 77.16 | 1207.25 |
2011-10-13 | 408.43 | 27.18 | 76.37 | 1203.66 |
2011-10-14 | 422.00 | 27.27 | 78.11 | 1224.58 |
2214 rows × 4 columns
close_px = close_px_all[['AAPL', 'MSFT', 'XOM']]
close_px
AAPL | MSFT | XOM | |
---|---|---|---|
2003-01-02 | 7.40 | 21.11 | 29.22 |
2003-01-03 | 7.45 | 21.14 | 29.24 |
2003-01-06 | 7.45 | 21.52 | 29.96 |
2003-01-07 | 7.43 | 21.93 | 28.95 |
2003-01-08 | 7.28 | 21.31 | 28.83 |
... | ... | ... | ... |
2011-10-10 | 388.81 | 26.94 | 76.28 |
2011-10-11 | 400.29 | 27.00 | 76.27 |
2011-10-12 | 402.19 | 26.96 | 77.16 |
2011-10-13 | 408.43 | 27.18 | 76.37 |
2011-10-14 | 422.00 | 27.27 | 78.11 |
2214 rows × 3 columns
close_px = close_px.resample('B').ffill()
close_px
AAPL | MSFT | XOM | |
---|---|---|---|
2003-01-02 | 7.40 | 21.11 | 29.22 |
2003-01-03 | 7.45 | 21.14 | 29.24 |
2003-01-06 | 7.45 | 21.52 | 29.96 |
2003-01-07 | 7.43 | 21.93 | 28.95 |
2003-01-08 | 7.28 | 21.31 | 28.83 |
... | ... | ... | ... |
2011-10-10 | 388.81 | 26.94 | 76.28 |
2011-10-11 | 400.29 | 27.00 | 76.27 |
2011-10-12 | 402.19 | 26.96 | 77.16 |
2011-10-13 | 408.43 | 27.18 | 76.37 |
2011-10-14 | 422.00 | 27.27 | 78.11 |
2292 rows × 3 columns
rolling¶
이제 resample이나 groupby와 유사하게 동작하는 rolling 연산을 알아보자
이는 Series나 DataFrame에 대해 원하는 기간을 나타내는 window 값과 함께 호출
close_px.AAPL.plot()
<Axes: >
close_px.AAPL.rolling(250).mean().plot()
<Axes: >
rolling(250)이라는 표현은 groupby와 비슷해 보이지만 그룹을 생성하는 대신 250일 크기의 움직이는 창을 통해 그룹핑할 수 있는 객체 생성
appl_std250 = close_px.AAPL.rolling(250, min_periods=10).std()
appl_std250[5:12]
2003-01-09 NaN 2003-01-10 NaN 2003-01-13 NaN 2003-01-14 NaN 2003-01-15 0.077496 2003-01-16 0.074760 2003-01-17 0.112368 Freq: B, Name: AAPL, dtype: float64
appl_std250.plot()
<Axes: >
expanding¶
*확장창 평균*(expanding window mean)을 구하기 위해서는 rolling 대신 expanding을 사용
확장창 평균은 시계열의 시작 지점에서부터 창의 크기가 시계열의 전체 크기가 될 때까지 점점 창의 크기를 늘림
appl_std250 시계열의 확장창 평균은 아래처럼 구할 수 있음
expanding_mean = appl_std250.expanding().mean()
DataFrame에 대해 이동창 함수를 호출하면 각 컬럼에 적용됨
close_px.rolling(60).mean().plot(logy=True)
<Axes: >
rolling 함수는 고정 크기의 기간 지정 문자열을 넘겨서도 호출 가능
빈도가 불규칙한 시계열일 경우 유용하게 사용. resample 함수에서 사용하던 것과 같은 형식
예를 들어 20일 크기의 이동평균은 아래처럼 구함
close_px.rolling('20D').mean()
AAPL | MSFT | XOM | |
---|---|---|---|
2003-01-02 | 7.400000 | 21.110000 | 29.220000 |
2003-01-03 | 7.425000 | 21.125000 | 29.230000 |
2003-01-06 | 7.433333 | 21.256667 | 29.473333 |
2003-01-07 | 7.432500 | 21.425000 | 29.342500 |
2003-01-08 | 7.402000 | 21.402000 | 29.240000 |
... | ... | ... | ... |
2011-10-10 | 389.351429 | 25.602143 | 72.527857 |
2011-10-11 | 388.505000 | 25.674286 | 72.835000 |
2011-10-12 | 388.531429 | 25.810000 | 73.400714 |
2011-10-13 | 388.826429 | 25.961429 | 73.905000 |
2011-10-14 | 391.038000 | 26.048667 | 74.185333 |
2292 rows × 3 columns
475부터
'Python, Jupyter 🐍 > [python]파이썬 데이터분석' 카테고리의 다른 글
지도에 서울소재대학 위치 표시하기 (0) | 2023.04.18 |
---|---|
9장 (0) | 2023.04.18 |
10장 데이터 집계와 그룹 연산 (0) | 2023.04.17 |
7장 (0) | 2023.04.17 |
[오류] 'jupyter'은(는) 내부 또는 외부 명령, 실행할 수 있는 프로그램, 또는배치 파일이 아닙니다. (0) | 2023.04.09 |