-
Chapter 12-2 사례별 시계열 데이터 계산하기_2Do it! 판다스 입문 2022. 6. 26. 19:07
시간 범위와 인덱스
앞에서 사용한 주식 데이터는 특정 일에 누락된 데이터가 없었습니다. 하지만 가끔은 데이터를 수집하지 못한 날도 있을 수 있겠죠. 만약 특정 일에 누락된 데이터도 포함시켜 데이터를 살펴보려면 어떻게 해야 할까요? 이런 경우에는 임의로 시간 범위를 생성하여 인덱스로 지정해야 합니다.
시간 범위 생성해 인덱스로 지정하기
1. 테슬라 주식 데이터는 특정 일에 누락된 데이터가 없습니다. 그래서 이번에는 에볼라 데이터 집합을 사용하겠습니다. 가장 앞쪽의 데이터를 살펴보면 2015년 01월 01일의 데이터가 누락된 것을 알 수 있습니다.
ebola = pd.read_csv('../data/country_timeseries.csv', parse_dates=[0])
print(ebola.iloc[:5, :5])
Date Day Cases_Guinea Cases_Liberia Cases_SierraLeone
0 2015-01-05 289 2776.0 NaN 10030.0
1 2015-01-04 288 2775.0 NaN 9780.0
2 2015-01-03 287 2769.0 8166.0 9722.0
3 2015-01-02 286 NaN 8157.0 NaN
4 2014-12-31 284 2730.0 8115.0 9633.02. 뒤쪽의 데이터도 마찬가지입니다. 2014년 03월 23일의 데이터가 누락되었습니다.
print(ebola.iloc[-5:, :5])
Date Day Cases_Guinea Cases_Liberia Cases_SierraLeone
117 2014-03-27 5 103.0 8.0 6.0
118 2014-03-26 4 86.0 NaN NaN
119 2014-03-25 3 86.0 NaN NaN
120 2014-03-24 2 86.0 NaN NaN
121 2014-03-22 0 49.0 NaN NaN3. 다음은 date_range 메서드를 사용하여 2014년 12월 31일부터 2015년 01월 05일 사이의 시간 인덱스(DatetimeIndex)를 생성한 것입니다.
head_range = pd.date_range(start='2014-12-31', end='2015-01-05')
print(head_range)
DatetimeIndex(['2014-12-31', '2015-01-01', '2015-01-02', '2015-01-03',
'2015-01-04', '2015-01-05'],
dtype='datetime64[ns]', freq='D')4. 다음은 원본 데이터를 손상시키는 것을 방지하기 위해 ebola 데이터프레임의 앞쪽 5개의 데이터를 추출하여 새로운 데이터프레임을 만든 것입니다. 이때 Date 열을 인덱스로 먼저 지정하지 않으면 오류가 발생합니다. 반드시 Date 열을 인덱스로 지정한 다음 과정 3에서 생성한 시간 범위를 인덱스로 지정해야 합니다.
ebola_5 = ebola.head()
ebola_5.index = ebola_5['Date']
ebola_5.reindex(head_range)
print(ebola_5.iloc[:5, :5])
Date Day Cases_Guinea Cases_Liberia Cases_SierraLeone
Date
2015-01-05 2015-01-05 289 2776.0 NaN 10030.0
2015-01-04 2015-01-04 288 2775.0 NaN 9780.0
2015-01-03 2015-01-03 287 2769.0 8166.0 9722.0
2015-01-02 2015-01-02 286 NaN 8157.0 NaN
2014-12-31 2014-12-31 284 2730.0 8115.0 9633.0* 시간 범위의 주기 설정하기
시간 범위를 인덱스로 지정하면 DatetimeIndex 자료형이 만들어집니다. 그리고 DatetimeIndex에는 freq 속성이 포함되어 있죠. freq 속성값을 지정하면 시간 간격을 조절하여 DatetimeIndex를 만들 수 있습니다. 아래에 freq 속성값으로 사용할 수 있는 시간 주기를 표로 정리했습니다.
freq 속성값으로 사용할 수 있는 시간 주기
시간 주기 설명 B 평일만 포함 C 사용자가 정의한 평일만 포함 D 달력 일자 단위 W 주간 단위 M 월 마지막 날만 포함 SM 15일과 월 마지막 날만 포함 BM M 주기의 값이 휴일이면 제외하고 평일만 포함 CBM BM에 사용자 정의 평일을 적용 MS 월 시작일만 포함 SMS 월 시작일과 15일만 포함 BMS MS 주기의 값이 휴일이면 제외하고 평일만 포함 CBMS BMS에 사용자 정의 평일을 적용 Q 3, 6, 9, 12월 분기 마지막 날만 포함 BQ 3, 6, 9, 12월 분기 마지막 날이 휴일이면 제외하고 평일만 포함 QS 3, 6, 9, 12월 분기 시작일만 포함 BQS 3, 6, 9, 12월의 분기 시작일이 휴일이면 제외하고 평일만 포함 A 년의 마지막 날만 포함 BA 년의 마지막 날이 휴일이면 제외하고 평일만 포함 AS 년의 시작일만 포함 BAS 년의 시작일이 휴일이면 제외하고 평일만 포함 BH 평일을 시간 단위로 포함(09:00 ~ 16:00) H 시간 단위로 포함(00:00 ~ 00:00) T 분 단위 포함 S 초 단위 포함 L 밀리초 단위 포함 U 마이크로초 단위 포함 N 나노초 단위 포함 다음은 date_range 메서드의 freq 인잣값을 B로 설정하여 평일만 포함시긴 DatetimeIndex를 만든 것입니다.
print(pd.date_range('2017-01-01', '2017-01-07', freq='B'))
DatetimeIndex(['2017-01-02', '2017-01-03', '2017-01-04', '2017-01-05',
'2017-01-06'],
dtype='datetime64[ns]', freq='B')시간 범위 수정하고 데이터 밀어내기 - shift 메서드
만약 나라별로 에볼라의 확산 속도를 비교하려면 발생하기 시작한 날짜를 옮기는 것이 좋습니다. 왜 그럴까요? 일단 ebola 데이터프레임으로 그래프를 그려보고 에볼라의 호가산 속도를 비교하는 데 어떤 문제가 있는지 그리고 해결 방법은 무엇인지 알아보겠습니다.
에볼라의 확산 속도 비교하기
1. 다음은 ebola 데이터프레임의 Date 열을 인덱스로 지정한 다음 x축을 Date 열로, y축을 사망자 수로 지정하여 그린 그래프입니다.
import matplotlib.pyplot as plt
ebola.index = ebola['Date']
fig, ax = plt.subplots()
ax = ebola.iloc[0:, 1:].plot(ax=ax)
ax.legend(fontsize=7, loc=2, borderaxespad=0.)
plt.show()
2. 그런데 과정 1의 그래프는 각 나라의 에볼라 발병일이 달라 그래프가 그려지기 시작한 지점도 다릅니다. 달리기 속도를 비교하려면 같은 출발선에서 출발하여 시간을 측정해야겠죠? 에볼라의 확산 속도도 같은 방법으로 측정해야 합니다. 즉, 각 나라의 발병일을 가장 처음 에볼라가 발병한 Guinea와 동일한 위치로 옮겨야 나라별 에볼라의 확산 속도를 제대로 비교할 수 있습니다.
ebola_sub = ebola[['Day', 'Cases_Guinea', 'Cases_Liberia']]
print(ebola_sub.tail(10))
Day Cases_Guinea Cases_Liberia
Date
2014-04-04 13 143.0 18.0
2014-04-01 10 127.0 8.0
2014-03-31 9 122.0 8.0
2014-03-29 7 112.0 7.0
2014-03-28 6 112.0 3.0
2014-03-27 5 103.0 8.0
2014-03-26 4 86.0 NaN
2014-03-25 3 86.0 NaN
2014-03-24 2 86.0 NaN
2014-03-22 0 49.0 NaN3. 그래프를 그리기 위한 데이터프레임 준비하기
다음은 Date 열의 자료형을 datetime 오브젝트로 변환하여 ebola 데이터프레임을 다시 생성한 것입니다. 그런데 중간에 아예 날짜가 없는 데이터(2015년 01월 01일)도 있습니다. 이 데이터도 포함시켜야 확산 속도를 제대로 비교할 수 있습니다.
ebola = pd.read_csv('../data/country_timeseries.csv', parse_dates=['Date'])
print(ebola.head( ).iloc[:, :5])
Date Day Cases_Guinea Cases_Liberia Cases_SierraLeone
0 2015-01-05 289 2776.0 NaN 10030.0
1 2015-01-04 288 2775.0 NaN 9780.0
2 2015-01-03 287 2769.0 8166.0 9722.0
3 2015-01-02 286 NaN 8157.0 NaN
4 2014-12-31 284 2730.0 8115.0 9633.0
print(ebola.tail( ).iloc[:, :5])
Date Day Cases_Guinea Cases_Liberia Cases_SierraLeone
117 2014-03-27 5 103.0 8.0 6.0
118 2014-03-26 4 86.0 NaN NaN
119 2014-03-25 3 86.0 NaN NaN
120 2014-03-24 2 86.0 NaN NaN
121 2014-03-22 0 49.0 NaN NaN4. 다음은 Date 열을 인덱스로 지정한 다음 ebola 데이터프레임의 Date 열의 최댓값과 최솟값으로 시간 범위를 생성하여 new_idx에 저장한 것입니다. 이렇게 하면 날짜가 아예 없었던 데이터의 인덱스를 생성할 수 있습니다.
ebola.index = ebola['Date']
new_idx = pd.date_range(ebola.index.min( ), ebola.index.max( ))5. 그런데 new_idx를 살펴보면 ebola 데이터 집합에 있는 시간 순서와 반대로 생성되어 있습니다. 다음은 시간 순서를 맞추기 위해 reversed 메서드를 사용하여 인덱스를 반대로 뒤집은 것입니다.
print(new_idx)
DatetimeIndex(['2014-03-22', '2014-03-23', '2014-03-24', '2014-03-25',
'2014-03-26', '2014-03-27', '2014-03-28', '2014-03-29',
'2014-03-30', '2014-03-31',
...
'2014-12-27', '2014-12-28', '2014-12-29', '2014-12-30',
'2014-12-31', '2015-01-01', '2015-01-02', '2015-01-03',
'2015-01-04', '2015-01-05'],
dtype='datetime64[ns]', length=290, freq='D')
new_idx = reversed(new_idx)6. 다음은 reindex 메서드를 사용하여 새로 생성한 인덱스(new_idx)를 새로운 인덱스로 지정한 것입니다. 그러면 2015년 01월 01일 데이터와 같은 ebola 데이터프레임에 아예 없었던 날짜가 추가됩니다. 이제 그래프를 그리기 위한 데이터프레임이 준비되었습니다.
ebola = ebola.reindex(new_idx)
print(ebola.head( ).iloc[:, :5])
Date Day Cases_Guinea Cases_Liberia Cases_SierraLeone
Date
2015-01-05 2015-01-05 289.0 2776.0 NaN 10030.0
2015-01-04 2015-01-04 288.0 2775.0 NaN 9780.0
2015-01-03 2015-01-03 287.0 2769.0 8166.0 9722.0
2015-01-02 2015-01-02 286.0 NaN 8157.0 NaN
2015-01-01 NaT NaN NaN NaN NaN
print(ebola.tail( ).iloc[:, :5])
Date Day Cases_Guinea Cases_Liberia Cases_SierraLeone
Date
2014-03-26 2014-03-26 4.0 86.0 NaN NaN
2014-03-25 2014-03-25 3.0 86.0 NaN NaN
2014-03-24 2014-03-24 2.0 86.0 NaN NaN
2014-03-23 NaT NaN NaN NaN NaN
2014-03-22 2014-03-22 0.0 49.0 NaN NaN7. 각 나라의 에볼라 발병일 옮기기
다음은 last_valid_index, first_valid_index 메서드를 사용하여 각 나라의 에볼라 발병일을 구한 것입니다. 각각의 메서드는 유효한 값이 있는 첫 번째와 마지막 인덱스를 반환합니다. 다음을 입력하고 결과를 확인해 보세요.
last_valid = ebola.apply(pd.Series.last_valid_index)
print(last_valid)
Date 2014-03-22
Day 2014-03-22
Cases_Guinea 2014-03-22
...
Deaths_UnitedStates 2014-10-01
Deaths_Spain 2014-10-08
Deaths_Mali 2014-10-22
dtype: datetime64[ns]
first_valid = ebola.apply(pd.Series.first_valid_index)
print(first_valid)
Date 2015-01-05
Day 2015-01-05
Cases_Guinea 2015-01-05
...
Deaths_UnitedStates 2014-12-07
Deaths_Spain 2014-12-07
Deaths_Mali 2014-12-07
dtype: datetime64[ns]8. 각 나라의 에볼라 발병일을 동일한 출발선으로 옮기려면 에볼라가 가장 처음 발병한 날(earliest_date)에서 각 나라의 에볼라 발병일을 뺀 만큼(shift_values)만 옮기면 됩니다.
earliest_date = ebola.index.min( )
print(earliest_date)
2014-03-22 00:00:00
shift_values = last_valid - earliest_date
print(shift_values)
Date 0 days
Day 0 days
Cases_Guinea 0 days
...
Deaths_UnitedStates 193 days
Deaths_Spain 200 days
Deaths_Mali 214 days
dtype: timedelta64[ns]9. 이제 각 나라의 에볼라 발병일을 옮기면 됩니다. 다음은 shift 메서드를 사용하여 모든 열의 값을 shift_values 값만큼 옮긴 것입니다. shift 메서드는 인잣값만큼 데이터를 밀어내는 메서드입니다.
ebola_dict = {}
for idx, col in enumerate(ebola):
d = shift_values[idx].days
shifted = ebola[col].shift(d)
ebola_dict[col] = shifted10. ebola_dict에는 시간을 다시 설정한 데이터가 딕셔너리 형태로 저장되어 있습니다. 다음은 DataFrame 메서드를 사용하여 ebola_dict의 값을 데이터프레임으로 변환한 것입니다.
ebola_shift = pd.DataFrame(ebola_dict) 11. 이제 에볼라의 최초 발병일(2014-03-22)을 기준으로 모든 열의 데이터가 옮겨졌습니다.
print(ebola_shift.tail( ))
Date Day Cases_Guinea Cases_Liberia Cases_SierraLeone \
Date
2014-03-26 2014-03-26 4.0 86.0 8.0 2.0
2014-03-25 2014-03-25 3.0 86.0 NaN NaN
2014-03-24 2014-03-24 2.0 86.0 7.0 NaN
2014-03-23 NaT NaN NaN 3.0 2.0
2014-03-22 2014-03-22 0.0 49.0 8.0 6.0
...12. 마지막으로 인덱스를 Day 열로 지정하고 그래프에 필요 없는 Date, Day 열은 삭제하면 그래프를 그리기 위한 데이터프레임이 완성됩니다.
ebola_shift.index = ebola_shift['Day']
ebola_shift = ebola_shift.drop(['Date', 'Day'], axis=1)
print(ebola_shift.tail( ))
Cases_Guinea Cases_Liberia Cases_SierraLeone Cases_Nigeria \
Day
4.0 86.0 8.0 2.0 1.0
3.0 86.0 NaN NaN NaN
2.0 86.0 7.0 NaN NaN
NaN NaN 3.0 2.0 NaN
0.0 49.0 8.0 6.0 0.0
...13. 다음은 지금까지 만든 데이터프레임으로 다시 그린 그래프입니다.
fig, ax = plt.subplots( )
ax = ebola_shift.iloc[:, :].plot(ax=ax)
ax.legend(fontsize=7, loc=2, borderaxespad=0.)
plt.show( )
마무리하며
판다스 라이브러리는 시간을 다룰 수 있는 다양한 기능을 제공합니다. 이 장에서는 시계열 데이터와 깊은 연관성이 있는 에볼라 데이터 및 주식 데이터를 주로 다루었습니다. 우리 주변의 상당수의 데이터는 시간과 깊은 연관성이 있는 경우가 많습니다. 시계열 데이터를 능숙하게 다루는 것은 데이터 분석가의 기본 소양이므로 이 장의 내용을 반드시 익혀두기 바랍니다.
출처 : "판다스 입문"
'Do it! 판다스 입문' 카테고리의 다른 글
[Pandas] DataFrame 다루기 메모 (0) 2022.11.02 Chapter 12-2 사례별 시계열 데이터 계산하기_1 (0) 2022.06.26 Chapter 12-1 datetime 오브젝트 (0) 2022.06.26 Chapter 11-3~11-4 데이터 필터링, 그룹 오브젝트 (0) 2022.06.25 Chapter 11-1~11-2 데이터 집계, 데이터 변환 (0) 2022.06.25