'년별 공휴일이 아닌 마지막 날짜 business year end'에 해당되는 글 1건

  1. 2019.12.26 [Python pandas] 시계열 데이터 빈도/주기와 날짜 Offsets (Frequencies and Date Offsets)

지난번 포스팅에서는 numpy와 pandas를 이용해서 차수 m인 단순 이동평균 구하는 방법 (https://rfriend.tistory.com/502) 를 소개하였습니다. 


이번 포스팅에서는 Python pandas에서 시계열 데이터를 생성할 때 유용하게 사용할 수 있는 빈도와 날짜 Offsets (pandas Frequencies and Date Offsets)에 대해서 알아보겠습니다.  2020년 달력을 가지고 Offset Type 별로 Alias 사용해가면서 결과값 확인해보도록 하겠습니다. 



[ Python pandas Base Time Series Frequencies and Date Offsets ]




(* 'Frequency'를 '빈도'라고 번역하는게 좋을지, 아니면 '주기'라고 번역하는게 좋을지 고민스럽습니다. 저는 의미상으로는 '주기'라고 번역하는게 더 적합할 것 같다고 생각하는데요, 이미 '빈도'라고 번역이 되어서 사용되고 있네요. Offset은 뭐라고 번역하는게 좋을지 잘 모르겠네요.)



  (1) 3일 주기의 날짜 데이터 생성하기 (generate dates with 3 days frequency)


pandas 의 Frequencies는 'base frequency'와 'multiplier'로 구성되어 있으며, base frequency는 Alias 문자열(alias string)를 사용하여 호출해서 이용합니다. 아래의 예는 'Day'의 Alias인 'd'(or 'D')에 '3'을 곱하여(multiplier) '3 Days' Frequency (빈도, 주기)의 날짜 범위를 8개 (periods = 8) 생성한 것입니다. 



import pandas as pd


pd.date_range('2019-12-01', periods = 8, freq = '3d') # or freq = '3D'

[Out]:

DatetimeIndex(['2019-12-01', '2019-12-04', '2019-12-07', '2019-12-10', '2019-12-13', '2019-12-16', '2019-12-19', '2019-12-22'], dtype='datetime64[ns]', freq='3D')

 



freq = 3 * '1D' 과 같이 명시적으로 곱하기 3을 밖으로 빼어서 표기해도 freq = '3D'와 결과 값은 동일합니다.  



pd.date_range('2019-12-01', periods = 8, freq = 3 * '1d') # or freq = 3 * '1D'

[Out]:
DatetimeIndex(['2019-12-01', '2019-12-04', '2019-12-07', '2019-12-10',
               '2019-12-13', '2019-12-16', '2019-12-19', '2019-12-22'],
              dtype='datetime64[ns]', freq='3D')

 



그리고 base frequency는 'date offset' 이라는 클래스 객체(class object)를 가지고 있습니다. 아래에 pandas.tseries.offsets 으로부터 일(Day), 시간(Hour), 분(Minute), 초(Minute) date offsets을 불어와서, freq = Day(3)과 같이 Day(3)의 date offset으로 위의 freq = '3d'와 동일한 결과를 얻었습니다.  



from pandas.tseries.offsets import Day, Hour, Minute, Second


pd.date_range('2019-12-01', periods = 8, freq = Day(3))

[Out]:
DatetimeIndex(['2019-12-01', '2019-12-04', '2019-12-07', '2019-12-10',
               '2019-12-13', '2019-12-16', '2019-12-19', '2019-12-22'],
              dtype='datetime64[ns]', freq='3D')




2일(2 Days) + 23시간 (23 Hours) + 59분 (59 Minutes) + 60초 (60 Seconds) = 3 일 (Days)  이므로 아래의 myfreq = Day(2) + Hour(23) + Minute(59) + Second(60) 으로 freq = myfreq 를 사용하여 날짜를 생성하면 위와 동일한 결과를 반환합니다. (3일 주기의 8개 날짜 생성)



from pandas.tseries.offsets import Day, Hour, Minute, Second


myfreq = Day(2) + Hour(23) + Minute(59) + Second(60) # 3 days

myfreq

[Out]: <3 * Days>


pd.date_range('2019-12-01', periods = 8, freq = myfreq)

[Out]:
DatetimeIndex(['2019-12-01', '2019-12-04', '2019-12-07', '2019-12-10',
               '2019-12-13', '2019-12-16', '2019-12-19', '2019-12-22'],
              dtype='datetime64[ns]', freq='3D')

 



물론, freq = '2D23H59min60S' (혹은 freq = '2d23h59T60s') 로 Frequency 의 Alias를 사용해도 결과는 동일합니다. 



pd.date_range('2019-12-01', periods = 8, freq = '2D23H59min60S') # or freq = '2d23h59T60s'

[Out]:
DatetimeIndex(['2019-12-01', '2019-12-04', '2019-12-07', '2019-12-10',
               '2019-12-13', '2019-12-16', '2019-12-19', '2019-12-22'],
              dtype='datetime64[ns]', freq='3D')





각 주/월/분기별 (a) 시작 날짜와 마지막 날짜, (b) 공휴일이 아닌(business day) 시작 날짜와 마지막 날짜를 가져올 수 있는 Data Offset, Alias를 살펴보기 위해 아래의 2020년 달력을 봐가면서 예를 들어보겠습니다.  






  (2) Month End vs. Business Month End, Month Begin vs. Business Month Begin


(2-1) (a) 월의 마지막 날짜(Month End) vs. (b) 월의 공휴일이 아닌 마지막 날짜 (Business Month End)


아래는 (a) 2020년 1월 ~ 8월의 각 월별 마지막 날짜 (offset type: MonthEnd, alias: 'M')와, (b) 각 월별 공휴일이 아닌 마지막 날짜(offset type: Business Month End, alias: 'BM')로 DatetimeIndex 를 생성해보았습니다. 2020년 2월달과 5월달의 'Month End'와 'Business Month End'가 서로 다르게 정확하게 생성되었다는 것을 위의 2020년 달력과 아래의 날짜 생성결과로 확인할 수 있습니다. 



# (a) Month End: 'M'

pd.date_range('2020-01-01', periods = 8, freq = 'M')

[Out]:
DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31', '2020-04-30',
               '2020-05-31', '2020-06-30', '2020-07-31', '2020-08-31'],
              dtype='datetime64[ns]', freq='M')

 



# (b) Business Month End: 'BM'

pd.date_range('2020-01-01', periods = 8, freq = 'BM')

[Out]:
DatetimeIndex(['2020-01-31', '2020-02-28', '2020-03-31', '2020-04-30',
               '2020-05-29', '2020-06-30', '2020-07-31', '2020-08-31'],
              dtype='datetime64[ns]', freq='BM')





(2-2) (a) 월별 시작 날짜(Month Begin) vs. (b) 월별 공휴일이 아닌 시작 날짜(Business Month Begin)


아래는 (a) 2020년 1월 ~ 8월까지 각 월별 시작 날짜(offset type: MonthStart, alias: 'MS') 와, (b) 공휴일이 아닌 시작 날짜(offset type: BusinessMonthBegin, alias:'BMS') 로 DatetimeIndex 를 생성하였습니다. 



# (a) Month Start: 'MS'

pd.date_range('2020-01-01', periods = 8, freq = 'MS')

[Out]:
DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01', '2020-04-01',
               '2020-05-01', '2020-06-01', '2020-07-01', '2020-08-01'],
              dtype='datetime64[ns]', freq='MS')




# (b) Business Month Start: 'BMS'

pd.date_range('2020-01-01', periods = 8, freq = 'BMS')

[Out]:
DatetimeIndex(['2020-01-01', '2020-02-03', '2020-03-02', '2020-04-01',
               '2020-05-01', '2020-06-01', '2020-07-01', '2020-08-03'],
              dtype='datetime64[ns]', freq='BMS')

 




  (3) 주별 특정 요일 날짜 (Week)


아래의 예는 2020년 1월 1일 이후의 날 중에서 매주 월요일(freq = 'W-MON'에 해당하는 날짜 8개로 DatetimeIndex를 생성한 것입니다. 각 요일별 alias는 영문 요일의 앞에서부터 3번째 자리까지의 알파벳입니다. 



# -- Week 'Alias': Offset Type

# 'W-MON': Monday

# 'W-TUE': Tuesday

# 'W-WED': Wednesday

# 'W-THU': Thursday

# 'W-FRI': Friday

# 'W-SAT': Saturday

# 'W-SUN': Sunday

pd.date_range('2020-01-01', periods = 8, freq = 'W-MON')

[Out]:

DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27', '2020-02-03', '2020-02-10', '2020-02-17', '2020-02-24'], dtype='datetime64[ns]', freq='W-MON')

 




  (4) 월별 특정 순번째의 요일 날짜 (Week of Month)


아래의 예는 2020년 1월 1일 이후의 날 중에서 매월 첫번째 금요일(freq = 'WOM-1FRI')에 해당하는 날째 8개로 DatetimeIndex를 생성한 것입니다.  만약 이러한 요건에 해당하는 offset type이 없어서 매뉴얼하게 코딩을 해야 한다고 생각하면 골치가 좀 아플것 같은데요, 매우 편리한 기능입니다. 

(가령, 매월 2번째 화요일로 날짜를 생성하고 싶다면 freq = "WOM-2TUE" 로 해주면 됩니다)



pd.date_range('2020-01-01', periods = 8, freq = 'WOM-1FRI')

[Out]:

DatetimeIndex(['2020-01-03', '2020-02-07', '2020-03-06', '2020-04-03', '2020-05-01', '2020-06-05', '2020-07-03', '2020-08-07'], dtype='datetime64[ns]', freq='WOM-1FRI')





  (5-1) 분기별 마지막 날짜(Quarter End), 분기별 공휴일 아닌 마지막 날짜(Business Quarter End) 


아래의 예는 2020년 1월 1일 이후 날짜 중에서 2월 달을 분기 마지막인 달 기준으로 해서 매 분기별 마지막 날짜(freq = 'Q-FEB') 8개(periods=8)를 가져와서 DatetimeIndex를 생성한 예입니다. 


만약 3월 달(March) 혹은 12월 달(December)을 분기 말의 기준으로 해서 매 분기별 마지막 날짜(freq = 'Q-MAR')를 8개 성성하고 싶다면 아래의 두번째 예제를 참고하면 됩니다. 



# quarterly dates anchored on 'February' last calendar day of each month

pd.date_range('2020-01-01', periods = 8, freq = 'Q-FEB')

[Out]:
DatetimeIndex(['2020-02-29', '2020-05-31', '2020-08-31', '2020-11-30',
               '2021-02-28', '2021-05-31', '2021-08-31', '2021-11-30'],
              dtype='datetime64[ns]', freq='Q-FEB')

 

# quarterly dates anchored on 'March' last calendar day of each month

pd.date_range('2020-01-01', periods = 8, freq = 'Q-MAR')

[Out]:
DatetimeIndex(['2020-03-31', '2020-06-30', '2020-09-30', '2020-12-31',
               '2021-03-31', '2021-06-30', '2021-09-30', '2021-12-31'],
              dtype='datetime64[ns]', freq='Q-MAR')




공휴일이 아닌 Business day 기준의 분기별 마지막 날짜(Business Quarter End)를 2월 달을 분기 마지막인 달 기준으로 8개 생성하려면 아래의 예처럼 freq = 'BQ-FEB' 의 base time series frequency를 사용하면 됩니다. 



# quarterly dates anchored on last busines day of each month

pd.date_range('2020-01-01', periods = 8, freq = 'BQ-FEB')

[Out]:
DatetimeIndex(['2020-02-28', '2020-05-29', '2020-08-31', '2020-11-30',
               '2021-02-26', '2021-05-31', '2021-08-31', '2021-11-30'],
              dtype='datetime64[ns]', freq='BQ-FEB')

 




  (5-2) 분기별 시작 날짜(Quarter Begin), 분기별 공휴일 아닌 시작 날짜(Business Quarter Begin)


분기별 시작 날짜는 위의 (5-1) 번의 offset type alias 'Q'에 'S'를 붙여주어서 'QS', 'BQS' 를 사용합니다. 


아래의 예는 2020년 1월 1일 이후의 날짜 중에서 2월(February) 달을 분기 마지막 달 기준으로 해서 각 분기별 시작 날짜(freq = 'QS-FEB')를 8개 (periods=8) 생성한 것입니다. 



# quarterly dates anchored on first calendar day of each month

pd.date_range('2020-01-01', periods = 8, freq = 'QS-FEB')

[Out]:

DatetimeIndex(['2020-02-01', '2020-05-01', '2020-08-01', '2020-11-01', '2021-02-01', '2021-05-01', '2021-08-01', '2021-11-01'], dtype='datetime64[ns]', freq='QS-FEB')




아래의 예는 분기별 공휴일이 아닌, 즉 business day 기준으로 2월(February) 달을 분기 마지막 달 기준으로 해서 각 분기별 시작 날짜(freq = 'BQS-FEB')를 8개 DatetimeIndex로 생성한 것입니다. 



# quarterly dates anchored on first busines day of each month

pd.date_range('2020-01-01', periods = 8, freq = 'BQS-FEB')

[Out]:
DatetimeIndex(['2020-02-03', '2020-05-01', '2020-08-03', '2020-11-02',
               '2021-02-01', '2021-05-03', '2021-08-02', '2021-11-01'],
              dtype='datetime64[ns]', freq='BQS-FEB')

 




  (6) 년별 특정월 마지막 날짜(Year End), 년별 공휴일이 아닌 특정월 마지막 날짜(Business Year End)


아래의 예는 년도별로 2월(February) 마지막 날짜 (freq = 'A-FEB')를 8개 가져와서 DatetimeIndex를 만든 것입니다. (만약 매년 1월 마지막 날짜를 생성하고 싶으면 freq = 'A-JAN' 처럼 JANUARY의 앞 3개 알파벳을 입력해주면 됩니다)



# Year End

# Annual dates anchored on last calendar day of given month

pd.date_range('2020-01-01', periods = 8, freq = 'A-FEB')

[Out]:

DatetimeIndex(['2020-02-29', '2021-02-28', '2022-02-28', '2023-02-28', '2024-02-29', '2025-02-28', '2026-02-28', '2027-02-28'], dtype='datetime64[ns]', freq='A-FEB')




아래의 예는 매 년도별로 공휴일이 아닌(Business day) 2월(February) 마지막 날짜(freq = 'BA-FEB') 를 8개 (periods=8) 가져와서 DatetimeIndex를 만는 것입니다. 



# Business Year End

# Annual dates anchored on last business day of given month

pd.date_range('2020-01-01', periods = 8, freq = 'BA-FEB')

[Out]:
DatetimeIndex(['2020-02-28', '2021-02-26', '2022-02-28', '2023-02-28',
               '2024-02-29', '2025-02-28', '2026-02-27', '2027-02-26'],
              dtype='datetime64[ns]', freq='BA-FEB')





  (7) 년별 시작 날짜(Year Begin), 년별 공휴일이 아닌 시작 날짜(Business Year Begin)


아래의 예는 2020-01-01일 이후 날짜 중에서 매년 2월(February)의 시작 날짜 (freq = 'AS-FEb') 를 8개(periods=8) 가져와서 DatetimeIndex를 만든 것입니다. 위의 (6)번에 'A'에 'S'를 추가하였습니다. 



# Year Begin

pd.date_range('2020-01-01', periods = 8, freq = 'AS-FEB')

[Out]:
DatetimeIndex(['2020-02-01', '2021-02-01', '2022-02-01', '2023-02-01',
               '2024-02-01', '2025-02-01', '2026-02-01', '2027-02-01'],
              dtype='datetime64[ns]', freq='AS-FEB')




아래의 예는 2020-01-01일 이후이고 공휴일이 아닌(business day) 날짜 중에서 매년 2월(February)의 시작 날짜 (freq = 'BAS-FEB') 를 8개 (periods=8) 가져와서 DatetimeIndex를 만든 것입니다. 바로 위의 freq = 'AS-FEB'에서 'B'를 추가하여 freq = 'BAS-FEB'를 사용해서 만들었습니다. 



# Business Year Begin

pd.date_range('2020-01-01', periods = 8, freq = 'BAS-FEB')

[Out]:
DatetimeIndex(['2020-02-03', '2021-02-01', '2022-02-01', '2023-02-01',
               '2024-02-01', '2025-02-03', '2026-02-02', '2027-02-01'],
              dtype='datetime64[ns]', freq='BAS-FEB')




  (8) Offset 만큼 날짜 이동하기 (shifting dates with offsets)


위에서 Base time series frequencies와 offset types 에 대해서 알아보았습니다. 이 offset 객체를 가지고 다른 datetime 객체에 더하거나 뺄 수 있습니다. 



from datetime import datetime

from pandas.tseries.offsets import MonthEnd, MonthBegin


now = datetime.now()

now

[Out]: datetime.datetime(2019, 12, 21, 15, 30, 39, 654904)



now + MonthEnd()

[Out]: Timestamp('2019-12-31 15:30:39.654904')


now - MonthEnd()

[Out]: Timestamp('2019-11-30 15:30:39.654904')


now + MonthBegin()

[Out]: Timestamp('2020-01-01 15:30:39.654904')



혹은 offset 객체에  rollforward() 메소드를 사용해서 앞으로(미래로) 날짜를 굴리거나(이동시키거나), 아니면 rollback() 메소드를 사용해서 뒤로(과거로) 날짜를 굴릴(이동시킬) 수 있습니다. 재미있는 기능입니다. ^^



offset_me = MonthEnd()


offset_me.rollforward(now)

[Out]: Timestamp('2019-12-31 15:30:39.654904')



offset_me.rollback(now)

[Out]: Timestamp('2019-11-30 15:30:39.654904')

 




  (9) pandas.period_range() 로 날짜 기간(period of time) 만들기


(a) pd.data_range('2020-01-01', periods=10, freq='d')로 만든 DatetimeIndex를 index로 해서 pandas Series를 만들 수도 있으며, (b) pd.period_range('2020-01-01', '2020-01-10', freq='d')러 PeriodIndex 를 만들어서 이를 index로 해서 pandas Series를 만들어도 동일한 결과를 얻을 수 있습니다. 



import pandas as pd

import numpy as np


# (a) pandas.date_range('start_date', periods, freq)

dr = pd.date_range('2020-01-01', periods=10, freq='d')

dr

[Out]:
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
               '2020-01-05', '2020-01-06', '2020-01-07', '2020-01-08',
               '2020-01-09', '2020-01-10'],
              dtype='datetime64[ns]', freq='D')



pd.Series(range(10), index=dr)

[Out]:
2020-01-01    0
2020-01-02    1
2020-01-03    2
2020-01-04    3
2020-01-05    4
2020-01-06    5
2020-01-07    6
2020-01-08    7
2020-01-09    8
2020-01-10    9
Freq: D, dtype: int64





# (b) pandas.period_range('start_date', 'end_date', freq)

pr = pd.period_range('2020-01-01', '2020-01-10', freq='d')

pr

[Out]:
PeriodIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
             '2020-01-05', '2020-01-06', '2020-01-07', '2020-01-08',
             '2020-01-09', '2020-01-10'],
            dtype='period[D]', freq='D')



pd.Series(range(10), index=pr)

[Out]:
2020-01-01    0
2020-01-02    1
2020-01-03    2
2020-01-04    3
2020-01-05    4
2020-01-06    5
2020-01-07    6
2020-01-08    7
2020-01-09    8
2020-01-10    9
Freq: D, dtype: int64





많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 눌러주세요. :-)




728x90
반응형
Posted by Rfriend
,