본문 바로가기
python/메모장

[python] datetime, pandas 날짜 계산

by GJ999 2022. 8. 28.

현재 금융권에서 종사하고 있다보니 기본적으로 모든 정보들이 날짜를 포함하게 된다.

게다가 시, 분, 초가 필요한 경우는 거의 없고, 일자 단위의 계산이 필요한 경우가 99%다.

따라서 파이썬 내에서 날짜 계산하는 방식을 기록으로 남기고자 한다.

 

파이썬에서는 기본적으로 datetime 라이브러리를 사용해서 해당 양식을 사용한다.

그런데 해당 포맷은 보통 시/분/초까지를 포함하므로, 이를 감안하여 전처리를 진행해야한다.

1. 문자열 → 날짜 (datetime.datetime.strptime : string parse time)

strptime('yyyymmdd', '%Y%m%d')

strptime을 이용해서 정해진 날짜형식(%Y%m%d)대로 해당 문자열을 인식해서 datetime 포맷으로 변형함

>>> import datetime
>>> d1_str = '20210607'
>>> d1_dt = datetime.datetime.strptime(d1_str,'%Y%m%d')

>>> d1_dt
datetime.datetime(2021, 6, 7, 0, 0)

>>> print(d1_dt)
2021-06-07 00:00:00

그런데 날짜형식(%Y%m%d) 중 연도에 해당하는 %Y 만 월(%m), 일(%d)과 다르게 대문자임을 알 수 있는데 만약 인식하고자 하는 문자열의 연도가 4자리인 경우는 대문자, 2자리인경우는 소문자를 사용한다.

>>> d2_dt = datetime.datetime.strptime(d1_str,'%y%m%d')
Traceback (most recent call last):
	~~~~
ValueError: unconverted data remains: 607

>>> d2_str = '210607'
>>> d3_dt = datetime.datetime.strptime(d2_str,'%y%m%d')
>>> print(d3_dt)
2021-06-07 00:00:00

해당 포맷에서는 시, 분, 초를 포함하고 있으므로 이를 제거하려면 일자함수로 한번 더 가공을 해줘야한다.

>>> d1_dt
datetime.datetime(2021, 6, 7, 0, 0)
>>> d1 = d1_dt.date()
>>> d1
datetime.date(2021, 6, 7)
>>> print(d1)
2021-06-07

2. 날짜 → 문자열 (datetime.datetime.strftime : string format time)

strftime(datetime(yyyy,mm,dd), '%Y%m%d')

strptime의 반대사용, datetime 날짜를 다시 문자형태(%Y%m%d)로 수정

>>> d1
datetime.date(2021, 6, 7)
>>> datetime.datetime.strftime(d1,'%Y%m%d')
'20210607'

3. Pandas에서 문자열 → 날짜 (pandas.to_datetime)

pd.to_datetime(날짜 Series)

아래와 같은 데이터프레임이 있다고 가정하자.

>>> import pandas as pd
>>> df = pd.DataFrame({'date1' : ['20160517','20220505','20181017'],
...                     'date2' : ['20190522','20220828','20200315'],
...                    'name' : ['jhon','kim','lee']})

>>> df
      date1     date2  name
0  20160517  20190522  jhon
1  20220505  20220828   kim
2  20181017  20200315   lee

판다스(pandas)에서 to_datetime을 통해 문자열을 날짜 형태로 쉽게 바꾸자

이때 날짜는 시/분/초를 포함하고 있다.

>>> df['date1_'] = pd.to_datetime(df['date1'])
>>> df['date2_'] = pd.to_datetime(df['date2'])
>>> df
      date1     date2  name     date1_     date2_
0  20160517  20190522  jhon 2016-05-17 2019-05-22
1  20220505  20220828   kim 2022-05-05 2022-08-28
2  20181017  20200315   lee 2018-10-17 2020-03-15
>>> df['date1_'][0]
Timestamp('2016-05-17 00:00:00')

4. Pandas에서 날짜 문자열 ( apply(lambda x : x.strftime() or x.dt.strftime())

Series.apply(lambda x : x.strftime('%Y-%m-%d'))

apply 함수를 통해 컬럼별로 strftime 을 적용해서 문자열로 변경한다.

>>> df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 5 columns):
 #   Column  Non-Null Count  Dtype
---  ------  --------------  -----
 0   date1   3 non-null      object
 1   date2   3 non-null      object
 2   name    3 non-null      object
 3   date1_  3 non-null      datetime64[ns]
 4   date2_  3 non-null      datetime64[ns]
dtypes: datetime64[ns](2), object(3)
memory usage: 148.0+ bytes

>>> df['date1_str'] = df['date1_'].apply(lambda x : x.strftime('%Y-%m-%d'))
>>> df['date2_str'] = df['date2_'].apply(lambda x : x.strftime('%Y-%m-%d'))

>>> df.head()
      date1     date2  name     date1_     date2_   date1_str   date2_str
0  20160517  20190522  jhon 2016-05-17 2019-05-22  2016-05-17  2019-05-22
1  20220505  20220828   kim 2022-05-05 2022-08-28  2022-05-05  2022-08-28
2  20181017  20200315   lee 2018-10-17 2020-03-15  2018-10-17  2020-03-15

>>> df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 7 columns):
 #   Column     Non-Null Count  Dtype
---  ------     --------------  -----
 0   date1      3 non-null      object
 1   date2      3 non-null      object
 2   name       3 non-null      object
 3   date1_     3 non-null      datetime64[ns]
 4   date2_     3 non-null      datetime64[ns]
 5   date1_str  3 non-null      object
 6   date2_str  3 non-null      object
dtypes: datetime64[ns](2), object(5)
memory usage: 172.0+ bytes

DataFrame.apply(lambda x : x.dt.strftime('%Y-%m-%d'))

그런데 위 방식은 날짜 컬럼이 여러 개일 때 코드가 길어지는 단점이 있다.

그래서 컬럼별 변화가 아닌, 여러 컬럼을 선택 후 한번에 날짜형태를 문자열형태로 변화시키기 위해서는 아래와 같이 strftime함수 앞에 dt를 붙여야 가능하다.

>>> df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 5 columns):
 #   Column  Non-Null Count  Dtype
---  ------  --------------  -----
 0   date1   3 non-null      object
 1   date2   3 non-null      object
 2   name    3 non-null      object
 3   date1_  3 non-null      datetime64[ns]
 4   date2_  3 non-null      datetime64[ns]
dtypes: datetime64[ns](2), object(3)
memory usage: 148.0+ bytes

>>> df[['date1_str', 'date2_str']] = df[['date1_', 'date2_']].apply(lambda x : x.dt.strftime('%Y-%m-%d'))

>>> df.head()
      date1     date2  name     date1_     date2_   date1_str   date2_str
0  20160517  20190522  jhon 2016-05-17 2019-05-22  2016-05-17  2019-05-22
1  20220505  20220828   kim 2022-05-05 2022-08-28  2022-05-05  2022-08-28
2  20181017  20200315   lee 2018-10-17 2020-03-15  2018-10-17  2020-03-15

>>> df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 7 columns):
 #   Column     Non-Null Count  Dtype
---  ------     --------------  -----
 0   date1      3 non-null      object
 1   date2      3 non-null      object
 2   name       3 non-null      object
 3   date1_     3 non-null      datetime64[ns]
 4   date2_     3 non-null      datetime64[ns]
 5   date1_str  3 non-null      object
 6   date2_str  3 non-null      object
dtypes: datetime64[ns](2), object(5)
memory usage: 172.0+ bytes

만약 중간에 dt를 뺀다면 아래와 같은 에러가 발생한다.

>>> df[['date1_str', 'date2_str']] = df[['date1_', 'date2_']].apply(lambda x : x.strftime('%Y-%m-%d'))
Traceback (most recent call last):
	~~~~
AttributeError: 'Series' object has no attribute 'strftime'

5. Pandas에서 특정 경과일 후(datetime.timedelta)

날짜 Series + datetime.timedelta(days = 365)

날짜연산을 하기 위해서는 일단 날짜형태로 변환 후, 특정 기간을 더하거나 뺄 때는 datetime의 timedelta를 이용한다.

이때, timedelta는 인자로 받을 수 있는 단위가 주(weeks), 일(days), 시간(hours), 분(minutes), 초(seconds)이다.

>>> df['date1_1y'] = df['date1_'] + datetime.timedelta(days = 365)
>>> df
      date1     date2  name     date1_     date2_   date1_1y
0  20160517  20190522  jhon 2016-05-17 2019-05-22 2017-05-17
1  20220505  20220828   kim 2022-05-05 2022-08-28 2023-05-05
2  20181017  20200315   lee 2018-10-17 2020-03-15 2019-10-17

6. Pandas에서 경과기간 구하기(numpy.timedelta)

(날짜2 - 날짜1)/np.timedelta(1,'D')

(날짜2 - 날짜1)/np.timedelta(1,'M')

(날짜2 - 날짜1)/np.timedelta(1,'Y')

date1_부터 date2_ 까지의 경과기간을 구할때는 두 컬럼을 빼주면 된다.

이때 경과기간은 일(days)로 저장이 되며, 형식은 Timedelta를 가진다.

>>> df['diff_days'] = df['date2_'] - df['date1_']
>>> df
      date1     date2  name     date1_     date2_   date1_1y diff_days
0  20160517  20190522  jhon 2016-05-17 2019-05-22 2017-05-17 1100 days
1  20220505  20220828   kim 2022-05-05 2022-08-28 2023-05-05  115 days
2  20181017  20200315   lee 2018-10-17 2020-03-15 2019-10-17  515 days
>>> df['diff_days'][0]
Timedelta('1100 days 00:00:00')

하지만 해당 경과일을 다른 계산에 이용하기 위해서는 timedelta가 아닌 숫자형태로 변환할 필요가 있다.

이를 위해 np.timedelta를 이용하는데, 이를 통해 경과일, 경과월, 경과연도를 구할 수 있다.

>>> df['diff_days2'] = (df['date2_'] - df['date1_'])/np.timedelta64(1,'D')
>>> df
      date1     date2  name     date1_     date2_   date1_1y diff_days  diff_days2  diff_months  diff_years        
0  20160517  20190522  jhon 2016-05-17 2019-05-22 2017-05-17 1100 days      1100.0    36.140372    3.011698        
1  20220505  20220828   kim 2022-05-05 2022-08-28 2023-05-05  115 days       115.0     3.778312    0.314859        
2  20181017  20200315   lee 2018-10-17 2020-03-15 2019-10-17  515 days       515.0    16.920265    1.410022        
>>> df['diff_days2'][0]
1100.0
>>> df['diff_months'] = (df['date2_'] - df['date1_'])/np.timedelta64(1,'M')
>>> df['diff_years'] = (df['date2_'] - df['date1_'])/np.timedelta64(1,'Y')
>>> df
      date1     date2  name     date1_     date2_   date1_1y diff_days  diff_days2  diff_months  diff_years        
0  20160517  20190522  jhon 2016-05-17 2019-05-22 2017-05-17 1100 days      1100.0    36.140372    3.011698        
1  20220505  20220828   kim 2022-05-05 2022-08-28 2023-05-05  115 days       115.0     3.778312    0.314859        
2  20181017  20200315   lee 2018-10-17 2020-03-15 2019-10-17  515 days       515.0    16.920265    1.410022

7. 윤년인지 판별하기(calendar.isleap)

calender.isleap()

calendar 라이브러리에서 isleap함수를 쓰면 해당연도가 윤년인지 알 수 있다.

참고로 윤년에 대한 정의는 다음과 같다.

1. 연수가 4로 나누어 떨어지는 해는 윤년

2. 연수가 100으로 나누어 떨어지는 해는 평년

3. 연수가 400으로 나누어 떨어지는 해는 윤년

>>> import calendar
>>> df['date1_']
0   2016-05-17
1   2022-05-05
2   2018-10-17
Name: date1_, dtype: datetime64[ns]
>>> df['date1_'][0].year
2016
>>> calendar.isleap(df['date1_'][0].year)
True

참고로 isleap함수는 시리즈, 리스트, 문자열을 인식 못하고 무조건 숫자형만을 인식한다.

>>> calendar.isleap(df['date1_'])
Traceback (most recent call last):
~~~
TypeError: cannot perform __mod__ with this index type: DatetimeIndex
>>> calendar.isleap([2022,2020])
Traceback (most recent call last):
~~~
TypeError: unsupported operand type(s) for %: 'list' and 'int'
>>> calendar.isleap('2022')
Traceback (most recent call last):
~~~
TypeError: not all arguments converted during string formatting
>>> calendar.isleap(2022)
False

8. pandas에서 몇년 후 구하기(pd.to_timedelta)

날짜Series + pd.to_timedelta(정수Series)

파이썬에서 날짜연산은 기본적으로 datetime포맷 ± timedelta 포맷 형태를 따르고 있기 때문에 더하고자 하는 숫자컬럼을 timedelta포맷으로 바꿔줘야할 필요가 있다.

아래와 같은 data가 있다고 가정하자.

>>> df2 = pd.DataFrame({'date' : ['20160517','20220505','20181017'],
...                     'interval_year' : [2,3,4]})
>>>
>>> df2['date_'] = pd.to_datetime(df2['date'])
>>> df2
       date  interval_year      date_
0  20160517              2 2016-05-17
1  20220505              3 2022-05-05
2  20181017              4 2018-10-17

목표는 date_컬럼에서 interval_year 연수 뒤의 날짜를 구하는 것이다.

예) 첫번째 행 → 2016년 5월 17일의 2년 뒤는 2018년 5월 17일

>>> df2['date_'] + pd.to_timedelta(df2['interval_year']*365)
0   2016-05-17 00:00:00.000000730
1   2022-05-05 00:00:00.000001095
2   2018-10-17 00:00:00.000001460
dtype: datetime64[ns]

이때, 정확한 날짜로 떨어지지 않고 약간의 소수점이 붙는 이유는 1년이 정확히 365일이 아니기 때문이다.

>>> df2['date_'][0]
Timestamp('2016-05-17 00:00:00')
>>> pd.to_timedelta(df2['interval_year']*365)[0]
Timedelta('0 days 00:00:00.000000')
>>> df2['date_'][0] + pd.to_timedelta(df2['interval_year']*365)[0]
Timestamp('2016-05-17 00:00:00.000000730')
반응형

댓글