ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Chapter 12-2 사례별 시계열 데이터 계산하기_2
    Do 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.0

     

    2. 뒤쪽의 데이터도 마찬가지입니다. 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                          NaN

     

    3. 다음은 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                  NaN

     

    3. 그래프를 그리기 위한 데이터프레임 준비하기

    다음은 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                           NaN

     

    4. 다음은 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                           NaN

     

    7. 각 나라의 에볼라 발병일 옮기기

    다음은 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] = shifted

     

    10. 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( )

     

     

     

     

     

    마무리하며

    판다스 라이브러리는 시간을 다룰 수 있는 다양한 기능을 제공합니다. 이 장에서는 시계열 데이터와 깊은 연관성이 있는 에볼라 데이터 및 주식 데이터를 주로 다루었습니다. 우리 주변의 상당수의 데이터는 시간과 깊은 연관성이 있는 경우가 많습니다. 시계열 데이터를 능숙하게 다루는 것은 데이터 분석가의 기본 소양이므로 이 장의 내용을 반드시 익혀두기 바랍니다.

     

     

     

     

     

    출처 : "판다스 입문"

Designed by Tistory.