python, numpy, pandas 날짜 타입 비교 및 정리

2021. 3. 16. 15:07IT, 데이터/Python(파이썬)

728x90

 

데이터를 정리하거나 분석할 때 날짜 시간을 기준으로 정렬하거나 비교할 일들이 많은데, 데이터 분석으로 많이 사용하고 있는 파이썬(python)은 날짜 타입이 여러 종류가 있어서 헷갈리기 쉽습니다.

 

그 이유는 파이썬에서 수학계산이나 데이터 분석을 위해서는 거의 필수로 활용되는 라이브러리인 numpy와 pandas가 자체 데이터 타입을 갖고 있기 때문인데요. 이것들은 기본 파이썬 데이터 타입과 용도는 비슷하지만 기능과 문법에 차이가 있기 때문에, 제대로 구분하지 않고 인터넷에서 검색해서 복사 붙여넣기하면 제대로 작동하지 않는 경우가 많습니다.

 

 

A. 파이썬 날짜 시간 타입 이해


 

먼저 파이썬에서 및 numpy, pandas에서 정의된 날짜타입은 아래와 같습니다.

 

 

라이브러리 날짜, 시간 클래스 타임델타 클래스
 datetime  datetime, date, time timedelta
numpy datetime64 timedelta64
pandas Timestamp Timedelta

 

먼저 datetime은 python 설치 시 기본적으로 내장된 라이브러리로, 날짜를 쓸 것인지, 시간을 쓸 것인지, 날짜시간을 합쳐쓸 것인지에 따라 클래스가 분화되어 있는 것이 특징입니다. 예를들어 '2021-3-16'을 표시하고 싶으면 date 클래스를 사용하고, '2021-3-16 12:34:21'를 표시하고 싶으면 'datetime' 클래스를 사용합니다. 반면 datetime64나 Timestamp는 각각 numpy, pandas 라이브러리에서 새로 정의한 날짜시간 클래스입니다. 이 둘은 numpy, pandas를 설치해야만 이용할 수 있고, 한 클래스로 날짜, 시간, 날짜시간을 모두 정의할 수 있는 것이 특징입니다.

 

그리고 날짜, 시간 타입은 일반적인 정수, 실수끼리 연산할 때와 달리 단위를 가지고 있는 점 때문에 시간의 차이를 저장하는 별도의 클래스가 있다는 특징이 있습니다. 이를 timedelta(타임델타)라고 하는데, 날짜, 시간은 특정 시점의 시각을 표시하는 용도이고(2021년 3월 16일), 타임델타는 기간 또는 시간 차이를 표시하는 용도(5일)라고 이해하시면 됩니다. 따라서 python에서 날짜, 또는 타임델타 변수 간 계산 시 출력되는 데이터 타입은 아래와 같은 구조를 갖습니다.

 

입력값의 타입 출력값의 타입 예시
 날짜 - 날짜 타임델타 (2021-03-16) - (2021-03-11) = 5일
날짜 - 타임델타 날짜 (2021-03-16) - 5일 = (2021-03-11)
타임델타 - 타임델타 타임델타 10일 - 7일 = 3일

 

이렇게 파이썬에서 날짜, 시간값을 활용하거나 계산하기 위해서 위와 같은 사항을 먼저 이해하시고 접근하시면 도움이 되며, 아래에서는 파이썬에서 활용되는 세 가지 날짜, 타임델타 타입의 정의, 그리고 스칼라값과 벡터값일 때 정의 및 연산 지원여부, 그리고 서로 다른 타입 간 연산 지원 여부 등을 정리해보도록 합니다. 이번 글에서는 편의상 시간은 제외하고 날짜인 경우만 다루도록 하겠습니다.

 

 

B. 파이썬 날짜 타입 및 변환 정리표


pandas, numpy, datetime 세 개 라이브러리의 날짜 및 타임델타 클래스 활용을 위해 필요한 기본적인 사항을 아래와 같이 표로 정리해보았습니다. 아래에 이어지는 내용은 모두 이 표에 대한 부연설명이기 때문에 이 표만 참고해도 되겠습니다.

 

먼저 1) datetime definition의 경우 숫자값으로 정의할 때, 문자열 스칼라값으로 정의할 때, 문자열 벡터값으로 정의할 때를 구분하여 표기하였습니다.

그리고 3)~7) 까지는 날짜 타입 - 타임델타 타입 간 연산 시 결과값 및 출력되는 데이터타입을 표기하였으며, 다른 타입간(예를 들어 Timestamp와 datetime64 간 계산) 연산 시 계산이 되는지 아니면 에러가 발생하는 지 여부도 함께 표시하였습니다.(빨간색 음영부분이 에러 발생하는 케이스)  

 

아래를 보시면 아시겠지만, pandas, numpy, datetime의 날짜와 타임델타 타입은 용도는 비슷해도 문법이나 지원되는 범위가 조금씩 다르기 때문에, 코드 작성 시에는 되도록 한 가지 타입으로 통일해서 사용하는 것이 좋으며, 특히 numpy와 pandas를 이용하는 경우에는 주로 사용하는 라이브러리의 날짜, 타임델타 타입을 이용하는 것이 호환성 면에서 좋다고 할 수 있습니다.

 

 

 

1. 날짜 데이터 정의


#1-1. 날짜 데이터 정의 - 숫자- pandas

import pandas as pd

print(pd.Timestamp(2020,  7, 21))  # 2020-07-21 00:00:00
print(pd.Timestamp(year=2020, month=7, day=21, hour=12))  # 2020-07-21 12:00:00


#1-2. 날짜 데이터 정의 - 숫자- numpy

import numpy as np

print(np.datetime64(2020, 7, 21))   # TypeError: function takes at most 2 arguments (3 given)


#1-3. 날짜 데이터 정의 - 숫자 - python

import datetime

print(datetime.date(2020, 7, 21))  # 2020-07-21
print(datetime.datetime(2020, 7, 21, 15, 12, 40))  # 2020-07-21 15:12:40

 

1) pandas - Timestamp()

날짜 형식을 정의하는 가장 기본적인 방법으로 연도, 월, 일, 시, 분, 초 등 날짜와 시간의 각 구성요소를 숫자로 입력하는 방법이 있습니다. pandas에서는 Timestamp() 함수로 날짜와 시간을 정의할 수 있으며, 매개변수는 맨 앞은 연도, 그 다음 부터는 월, 일, 시, 분, 초 순서로 입력됩니다. 또는 각 매개변수명을 'year=2020'와 같이 명시적으로 표시해서 정의도 가능합니다.

 

2) numpy - 없음

numpy의 datetime64 타입은 위와 같이 숫자 형태로 날짜를 정의하는 기능을 제공하고 있지 않습니다. 숫자형태로 넣으면 TypeError가 발생합니다.

 

3) datetime - date() 또는 datetime()

python의 기본 날짜 라이브러리인 datetime는 클래스 생성자를 통해 pandas와 유사하게 날짜와 시간을 정의할 수 있습니다. pandas와 다른 점은 Timestamp는 날짜와 시간이 통합되어 있는데 반해, datetime 라이브러리는 날짜만 담고 있는 date, 시간만 담고 있는 time, 날짜-시간을 모두 담고 있는 datetime 클래스가 구분되어 있어 용도에 따라 구분하여 사용합니다.

  

 

2. 날짜 텍스트 → 날짜 데이터 변환(스칼라 값)


#2-1. 날짜 텍스트 → 날짜 변환(스칼라값)- pandas

import pandas as pd

print(pd.to_datetime('2020-07-21'))  # 2020-07-21 00:00:00
print(pd.to_datetime('07-21-2020'))  # 2020-07-21 00:00:00
print(pd.to_datetime('2020/07/21'))  # 2020-07-21 00:00:00
print(pd.to_datetime('07/21/2020'))  # 2020-07-21 00:00:00


#2-2. 날짜 텍스트 → 날짜 변환(스칼라값)- numpy

import numpy as np

print(np.datetime64('2020-07-21'))   # 2020-07-21
print(np.datetime64('07-21-2020'))  # ValueError: Month out of range in datetime string "07-21-2020"
print(np.datetime64('2020/07/21'))  # ValueError: Error parsing datetime string "2020/07/21" at position 4
print(np.datetime64('07/21/2020'))  # ValueError: Error parsing datetime string "07/21/2020" at position 2


#2-3. 날짜 텍스트 → 날짜 변환(스칼라값) - python

import datetime

print(datetime.date.fromisoformat('2020-07-21'))  # 2020-07-21
print(datetime.datetime.fromisoformat('2020-07-21'))  # 2020-07-21 00:00:00
print(datetime.time.fromisoformat('07:01:45'))  # 07:01:45

 

1) pandas.to_datetime()

날짜를 표현하는 다양한 방식을 별다른 처리없이 그대로 입력해도 알아서 연-월-일 시:분:초 형식으로 변환하여 인식합니다. 날짜만 입력해도 시간까지 표시되는 특징이 있습니다.

 

날짜를 표시할 때 연월일을 하이픈(-)으로 구분하거나 슬래시(/)로 구분하는 경우가 있는데, pandas.to_datetime은 이 두 가지 케이스를 모두 인식합니다. 또한 우리나라처럼 연-월-일로 표시하는 경우 외에 미국에서 사용되는 월-일-년으로 입력되는 경우에도 추가적인 텍스트 처리를 하지 않아도 알아서 연-월-일로 변환해서 저장됩니다.

 

2) numpy.datetime64()

반면, numpy.datetime64()는 항상 '연-월-일' 형식으로 입력해야 하며, 그렇지 않은 경우 아래와 같이 각기 다른 에러메시지가 발생하게 됩니다. 

 

3) datetime.(date / datetime / time).fromisoformat()

datetime의 date, datetime, time 클래스는 fromisoformat() 함수를 통해서 날짜 텍스트를 날짜 데이터 타입으로 변환할 수 있습니다. numpy.datetime64()와 비슷하게 날짜는 항상 '연-월-일' 형식으로, 시간은 '시:분:초' 형태로 입력해야 합니다. 

 

 

3. 날짜 텍스트 → 날짜 데이터 변환(벡터 값)


import pandas as pd
import numpy as np
from datetime import  date

date_list = ['2020-07-21', '2020-07-22', '2020-08-21']  # list 타입
date_array = np.array(['2020-07-21', '2020-07-22', '2020-08-21'])  # ndarray 타입
date_series = pd.Series(['2020-07-21', '2020-07-22', '2020-08-21'])  # Series 타입

#3-1. 날짜 텍스트 → 날짜 변환(벡터값) - pandas

print(pd.to_datetime(date_list))  
print(pd.to_datetime(date_array))
print(pd.to_datetime(date_series))  

>>> DatetimeIndex(['2020-07-21', '2020-07-22', '2020-08-21'], dtype='datetime64[ns]', freq=None)
>>> DatetimeIndex(['2020-07-21', '2020-07-22', '2020-08-21'], dtype='datetime64[ns]', freq=None)
>>> 0   2020-07-21
1   2020-07-22
2   2020-08-21
dtype: datetime64[ns]


#3-2. 날짜 텍스트 → 날짜 변환(벡터값) - numpy

print(np.array(date_list, dtype='datetime64'))  # ['2020-07-21' '2020-07-22' '2020-08-21']
print(np.array(date_array, dtype='datetime64'))  # ['2020-07-21' '2020-07-22' '2020-08-21']
print(np.array(date_series, dtype='datetime64'))  # ['2020-07-21' '2020-07-22' '2020-08-21']
print(np.datetime64(date_list))  # ValueError: Could not convert object to NumPy datetime


#3-3. 날짜 텍스트 → 날짜 변환(벡터값) - python

print([ date.fromisoformat(d) for d in date_list ] )
print([ date.fromisoformat(d) for d in date_array ] )
print([ date.fromisoformat(d) for d in date_series ] )

>>> [datetime.date(2020, 7, 21), datetime.date(2020, 7, 22), datetime.date(2020, 8, 21)]
>>> [datetime.date(2020, 7, 21), datetime.date(2020, 7, 22), datetime.date(2020, 8, 21)]
>>> [datetime.date(2020, 7, 21), datetime.date(2020, 7, 22), datetime.date(2020, 8, 21)]

python에서 1차원 벡터를 정의하는 방법은 크게 3가지가 있는데, 이 중 list는 Python의 기본 데이터 타입이고, ndarray는 Numpy에서, Series는 Pandas에서 각각 새로 정의한 데이터 타입입니다.

 

이 세 가지 벡터로 저장된 날짜 텍스트를 pandas, numpy, python 기본 날짜 타입으로 변환할 수 있는 지 확인해봅니다. 

 

 

1) pandas.to_datetime()

pandas에서는 스칼라 값과 동일하게 to_datetime() 함수를 이용합니다. 이 때 list와 ndarray 타입 벡터를 변환할 경우, 각 원소 뿐만 아니라 벡터 구조 또한 pandas의 DatetimeIndex 라는 새로운 데이터 타입으로 변경됩니다. 반면 Series 타입 벡터를 변환할 때는 그대로 Series 벡터 구조는 유지하면서 각 원소들만 날짜 데이터로 변환됩니다. 하지만 각 원소의 타입은 모두 Timestamp로 동일합니다.

 

DatetimeIndex는 Series와 동일한 1차원 벡터 구조이지만 용도가 다른데, Series는 pandas의 2차원 테이블 구조인 Dataframe의 구성요소로 활용될 수 있어 Dataframe 변경 및 가공 시 밀접하게 연관되나, DatetimeIndex는 좀 더 기본적인 ndarray에 가까운 타입으로 Dataframe 변경 및 가공에 직접 파라미터로 들어갈 수 없는 차이가 있다고 볼 수 있습니다.

 

2) numpy.array( array, dtype='datetime64')

numpy에서 날짜 텍스트 벡터의 원소들을 날짜 타입으로 변경은 ndarray를 정의할 때 타입을 'datetime64'로 지정하는 방법으로 수행됩니다. 이 때 array() 안에는 list, ndarray, Series 어떤 것을 입력해도 출력되는 벡터는 항상 ndarray 타입이 되며, 각 원소는 datetime64 타입이 됩니다. 만약 스칼라 값일 경우와 동일하게 np.datetime64(벡터) 형태로 실행하면, 'ValueError: Could not convert object to NumPy datetime' 에러가 발생하게 됩니다.

 

3) [ date.fromisoformat(x) for x in list ]

datetime의 date, datetime, time 클래스는 벡터를 입력받아 각 원소를 날짜 데이터로 변환해주는 함수가 없습니다. 따라서 datetime을 활용할 경우에는 반복문을 통해 각 원소별로 fromisoformat() 함수를 적용하여 하나씩 변환해야 합니다.

 

 

 

4. 날짜(스칼라) - 날짜(스칼라) 간 계산


 

날짜 데이터 끼리의 사칙연산은 더하기, 빼기, 곱하기, 나누기 중에 두 날짜의 차이를 구하는 -(빼기) 연산만 가능합니다.

예를 들어 2020년 7월 22일과 2020년 7월 20일의 차이는 2일이라고 할 수 있는데, 유의할 점은 '2일'이 그냥 정수 2가 아니라 연, 월, 일과 같은 날짜 단위가 붙기 때문에 int나 float 데이터 타입이 아닌 timedelta라고 하는 별도의 데이터 타입으로 저장된다는 점입니다.

 

import pandas as pd
import numpy as np
from datetime import  date

date_pd1 = pd.to_datetime('2020-07-22')
date_pd2 = pd.to_datetime('2020-07-20')
date_np1 = np.datetime64('2020-07-22')
date_np2 = np.datetime64('2020-07-20')
date_py1 = date.fromisoformat('2020-07-22')
date_py2 = date.fromisoformat('2020-07-20')

#4-1. 날짜(스칼라) - 날짜(스칼라) 계산 (동일 타입일 때)

print(date_pd1 - date_pd2)   # 2 days 00:00:00
print(date_np1 - date_np2)   # 2 days 
print(date_py1 - date_py2)   # 2 days, 0:00:00


#4-2. 날짜(스칼라) - 날짜(스칼라) 계산 (다른 타입일 때)

print(date_pd1 - date_np2)   # 2 days 00:00:00
print(date_pd1 - date_py2)   # TypeError: unsupported operand type(s) for -: 'Timestamp' and 'datetime.date'
print(date_np1 - date_py2)   # UFuncTypeError: ufunc 'subtract' cannot use operands with types dtype('<M8[D]') and dtype('O')


#4-3. 날짜(스칼라) - 날짜(스칼라) 계산 결과 타입
print(type(date_pd1 - date_pd2))   # <class 'pandas._libs.tslibs.timedeltas.Timedelta'>
print(type(date_np1 - date_np2))  # <class 'numpy.timedelta64'>
print(type(date_py1 - date_py2))   # <class 'datetime.timedelta'>
print(type(date_pd1 - date_np2))   # <class 'pandas._libs.tslibs.timedeltas.Timedelta'>

두 날짜가 스칼라 값일 때 빼기 연산 결과가 어떻게 출력되는지 테스트 결과는 위와 같습니다.

 

먼저 4-3의 위에 세 줄을 보시면 각 날짜 간 차이를 구하면 그 값은 기존의 Timestamp, datetime64, date가 아닌 Timedelta라는 새로운 타입의 값이 출력됩니다. 4-1의 결과값을 보면 출력값도 기존의 날짜와는 다른 형태를 갖고 있는 것을 알 수 있습니다. 날짜, 시간 타입이 일반 숫자 타입과 다른 결정적인 요소이니 꼭 알고가야 합니다.  

 

Timestamp, datetime64, date 타입 간 연산에 따른 출력값의 타입은 아래와 같습니다. pandas 기반이냐, numpy나 기본 python 기반이냐에 따라 타입 명칭이 Timedelta, timedelta64, timedelta로 각기 다르지만 같은 역할을 한다고 보면 됩니다. 출력값의 형태도 미세하게 다른데, Timedelta와 timedelta의 경우 시간이 없는 날짜끼리만 연산을 해도 '2 days 00:00:00'처럼 시:분:초 정보가 포함되는데, 반면 timedelta64는 날짜끼리 연산 시 '2 days'로 순수하게 날짜만 표기됩니다.

 

1) Timestamp - Timestamp  →  pandas._libs.tslibs.timedeltas.Timedelta 타입

2) datetime64 - datetime64  →  numpy.timedelta64 타입

3) date - date  →  datetime.timedelta 타입

 

4-2 코드를 보시면 Timestamp와 datetime64는 서로 타입은 다르지만 날짜 차이를 구할 때 서로 호환이 되는 것을 알 수 있습니다. 이 때 출력값은 pandas 계열의 Timedelta 타입으로 맞춰집니다.

 

4) Timestamp - datetime64  → pandas._libs.tslibs.timedeltas.Timedelta 타입

 

반면 Timestamp 또는 datetime64 타입을 date 타입과 연산할 경우 아래와 같은 에러가 발생합니다. 즉 python 기본 날짜 형식은 numpy, pandas 계열 날짜 형식과 직접 호환이 되지 않습니다.

 

  TypeError: unsupported operand type(s) for -: 'Timestamp' and 'datetime.date' 또는

  UFuncTypeError: ufunc 'subtract' cannot use operands with types dtype('<M8[D]') and dtype('O')

 

5) Timestamp 또는 datetime64 - date  →  에러 발생

 

 

 

5. 날짜(벡터) - 날짜(스칼라) 간 계산


다음으로는 날짜 데이터 벡터에서 날짜 스칼라 값의 차이를 구할 때 나타나는 결과를 알아봅니다.

 

import pandas as pd
import numpy as np
from datetime import  date

date_list = [ date.fromisoformat(d) for d in ['2020-07-21', '2020-07-22', '2020-08-21']]
date_array = np.array(['2020-07-21', '2020-07-22', '2020-08-21'], dtype='datetime64')
date_series = pd.to_datetime(pd.Series(['2020-07-21', '2020-07-22', '2020-08-21']))

date_pd = pd.to_datetime('2020-07-22')
date_np = np.datetime64('2020-07-22')
date_py = date.fromisoformat('2020-07-22')


#5-1. 날짜 (벡터)- 날짜 계산(스칼라) - 동일 타입일 때

print(date_series - date_pd)
# 0   -1 days
# 1    0 days
# 2   30 days
print(date_array - date_np)   # [-1  0 30]
print(date_list - date_py)   # TypeError: unsupported operand type(s) for -: 'list' and 'datetime.date'
print([date - date_py for date in date_list])   # [datetime.timedelta(days=-1), datetime.timedelta(0), datetime.timedelta(days=30)]


#5-2. 날짜 (벡터)- 날짜 계산(스칼라) - 다른 타입일 때

print(date_series - date_np)   # 2 days 00:00:00
print(date_series - date_py)   # TypeError: unsupported operand type(s) for -: 'DatetimeArray' and 'datetime.date'
print(date_array - date_pd)   # TypeError: unsupported operand type(s) for -: 'numpy.ndarray' and 'Timestamp'
print(date_array - date_py)   # UFuncTypeError: ufunc 'subtract' cannot use operands with types dtype('<M8[D]') and dtype('O')
print(date_list - date_pd)   # TypeError: unsupported operand type(s) for -: 'list' and 'Timestamp'
print(date_list - date_np)   # UFuncTypeError: ufunc 'subtract' cannot use operands with types dtype('O') and dtype('<M8[D]')


#5-3  date타입 array, series 벡터 - date 타입 날짜(스칼라) 계산 

date_array_date = np.array(date_list)
date_series_date = pd.Series(date_list)

print(date_array_date - date_py)   # [datetime.timedelta(days=-1) datetime.timedelta(0) datetime.timedelta(days=30)]
print(date_series_date - date_py)
# 0   -1 days
# 1    0 days
# 2   30 days
# dtype: timedelta64[ns]

 

먼저 동일한 날짜타입을 가진 벡터와 스칼라 간 빼기 연산(5-1)의 경우입니다.

pandas 기반의 Timestamp와 numpy 기반의 datetime64 타입 날짜의 경우 series 또는 array 타입 벡터의 원소와 스칼라 값 간 연산이 잘 작동하는 것으로 나타납니다. 반면 list 타입 벡터를 스칼라와 계산 시 'unsupported operand type(s) for -: 'list' and 'datetime.date' 에러가 발생합니다. 이 에러는 list 타입과 date 타입 간에 -(빼기) 연산이 지원되지 않는다는 뜻인데요.

list 타입은 여러 값들을 묶어서 저장하는 구조에 충실하기 때문에, 계산을 위해 만들어진 numpy나 pandas의 벡터 타입과 다르게 연산자를 통한 계산이 불가능합니다. 실제로 list 안에 날짜 타입이 아니라 일반적인 정수나 실수가 들어있어도 list를 바로 사칙연산시킬 수 없습니다. 따라서 list 타입 데이터의 원소 간 연산을 위해서는 for 반복문을 활용해서 원소 하나씩 계산해야 합니다. 반면 Timestamp와 datetime64는 벡터와 스칼라값 간 연산이 가능한데, 이는 이 두 데이터타입이 브로드캐스팅(broadcasting)을 지원하기 때문입니다. 보통 수학이나 과학, 통계 계산을 목적으로 하는 라이브러리가 이러한 기능을 지원하고 있습니다.

 

1) Timestamp(series 벡터) - Timestamp(스칼라)  → Timedelta값 series 벡터

2) datetime64(array 벡터) - datetime64(스칼라)  →  timedelta64값 array 벡터

3) date(list 벡터) - date(스칼라)  →  에러 발생

4) [date(list 벡터) - date(스칼라) for date in date(list 벡터)]   →  timedelta값 list 벡터

 

5-2 코드와 같이 서로 다른 타입의 날짜 데이터 간 계산의 경우 Timestamp 타입의 날짜에 대한 series 벡터와 스칼라값 간 연산만 정상적으로 작동하고 나머지는 모두 에러가 발생되는 것으로 나타났습니다. 

 

5-3 코드는 date 타입 날짜를 list가 아닌 array 또는 Series 타입 벡터로 저장했을 때 date 타입 스칼라값과 계산이 되는지를 확인해 보았습니다. 이 경우에는 둘 다 잘 작동하는 것으로 나타납니다. 

 

이렇게 서로 다른 타입 간에 날짜 벡터와 스칼라 값 데이터 계산 시 에러 발생 케이스가 제각각이기 때문에 되도록 같은 타입의 데이터끼리 연산을 하는 것이 좋고, 벡터-스칼라 간 연산을 지원하는 브로드캐스팅 기능이 있는 Pandas나 numpy 라이브러리의 데이터 타입을 사용하는 것이 좋습니다.

 

  

6. 날짜(벡터) - 타임델타(스칼라) 간 계산


날짜 데이터를 활용할 때 특정 날짜에서 몇 일 전, 몇 일 후 이런 식으로 날짜 데이터를 변경할 일들이 있는데, 이 때 날짜 데이터 타입(Timestamp, datetime64, date)과 타임델타 데이터 타입(Timedelta, timedelta64, timedelta) 간 연산 결과를 확인해봅니다.

 

import pandas as pd
import numpy as np
import datetime
from datetime import  date

date_list = [ date.fromisoformat(d) for d in ['2020-07-21', '2020-07-22', '2020-08-21']]
date_array = np.array(['2020-07-21', '2020-07-22', '2020-08-21'], dtype='datetime64')
date_series = pd.to_datetime(pd.Series(['2020-07-21', '2020-07-22', '2020-08-21']))

td_pd = pd.Timedelta(5, unit='days')
td_np = np.timedelta64(5, 'D')
td_py = datetime.timedelta(days=5)

#6-1. 날짜 (벡터)- 타임델타(스칼라) 계산 - 동일 라이브러리일 때

print(date_series - td_pd)
#0   2020-07-16
#1   2020-07-17
#2   2020-08-16
#dtype: datetime64[ns]
print(date_array - td_np)   # ['2020-07-16' '2020-07-17' '2020-08-16']
print(date_list - td_py)   #  TypeError: unsupported operand type(s) for -: 'list' and 'datetime.timedelta'

#6-2. 날짜 (벡터)- 타임델타(스칼라) 계산 - 다른 라이브러리일 때

print(date_series - td_np)   #  print(date_series - td_pd)와 동일
print(date_series - td_py)   # print(date_series - td_pd)와 동일
print(date_array - td_pd)   # ['2020-07-16T00:00:00.000000000' '2020-07-17T00:00:00.000000000' '2020-08-16T00:00:00.000000000']
print(date_array - td_py)   #  UFuncTypeError: ufunc 'subtract' cannot use operands with types dtype('<M8[D]') and dtype('O')
print(date_list - td_pd)   #  TypeError: unsupported operand type(s) for -: 'list' and 'Timedelta'
print(date_list - td_np)   # UFuncTypeError: ufunc 'subtract' cannot use operands with types dtype('O') and dtype('<m8[D]')

먼저 날짜와 타임델타 값 간 계산을 위해서 Timedelta 타입 변수를 정의합니다.

Timedelta, timedelta64, timedelta 모두 정의 방식이 약간씩 다른데, pandas의 Timedelta는 unit='단위'로, '시' 단위 경우에는 'hour' 등으로 사용하면 됩니다. 반면 numpy의 timedelta64는 'day' 대신 'D'로 축약된 알파벳을 사용하며, python의 timedelta는 수치와 단위를 별도의 파라미터가 아닌 'days=5'와 같이 하나의 표현으로 정의합니다.

 

6.1 코드를 살펴보면 기존 날짜 벡터가 7월 21일, 7월 22일, 8월 21일인데, 5일로 정의된 Timedelta를 뺀 결과 7월 16일, 7월 17일, 8월 16일로 나타납니다.

 

날짜 데이터 타입과 타임델타 데이터 타입이 서로 다른 라이브러리일 경우 연산이 되는지 확인한 결과,

Timestamp 데이터 타입은 numpy와 python의 timedelta64, timedelta 타입과 호환이 되며, datetime64 데이터 타입은 Timedelta와 호환이 되나, datetime64 - timedelta 간 연산, datetime - Timedelta / timedelta64는 에러가 발생합니다.

 

그리고 한 가지 유의사항은 datetime64와 timedelta 간 연산 시 결과가 날짜 뿐만 아니라 6.2 코드 셋째 줄과 같이 시간까지 포함되는 결과가 나타납니다.

 

 

7. 타임델타 - 타임델타(정수) 간 계산


import pandas as pd
import numpy as np
import datetime
from datetime import  date

td_pd1 = pd.Timedelta(5, unit='days')
td_np1 = np.timedelta64(5, 'D')
td_py1 = datetime.timedelta(days=5)

td_pd2 = pd.Timedelta(2, unit='days')
td_np2 = np.timedelta64(2, 'D')
td_py2 = datetime.timedelta(days=2)

#7-1. 타임델타(스칼라) - 타임델타(스칼라) 계산

print(td_pd1 + td_pd2)   #  7 days 00:00:00
print(td_pd1 + td_np2)   #  7 days 00:00:00
print(td_pd1 + td_py2)   #  7 days 00:00:00
print(td_np1 + td_np2)   #  7 days
print(td_np1 + td_py2)   # UFuncTypeError: ufunc 'add' cannot use operands with types dtype('<m8[D]') and dtype('O')
print(td_py1 + td_py2)   #  7 days, 00:00:00

#7-2. 타임델타(스칼라) - 정수 계산

print(td_pd1 + 2)   #  unsupported operand type(s) for +: 'Timedelta' and 'int'
print(td_np1 + 2)   #  7 days
print(td_py1 + 2)   #  TypeError: unsupported operand type(s) for +: 'datetime.timedelta' and 'int'

timedelta 타입 간 사칙연산은 곱하기(*)를 제외하고 +,-,/ 연산을 지원하며, 결과 타입은 동일하게 timedelta 타입이 됩니다. 7-1 코드와 같이 동일 timedelta 타입 및 다른 타입 간 계산 결과 pandas의 Timedelta 타입은 다른 라이브러리의 timedelta 타입과 연산을 지원하나, numpy의 timedelta64 타입은 다른 라이브러리 타입과 연산 시 'UFuncTypeError: ufunc 'add' cannot use operands with types dtype('<m8[D]') and dtype('O')' 에러가 발생합니다.

 

 

다음으로 timedelta 타입과 일반 정수 타입 간 연산의 지원 여부를 7-2 코드에서 확인할 수 있는데,

여기서는 오직 numpy의 timedelta64만이 정수와의 연산을 지원하며, pandas의 Timedelta와 datetime의 timedelta 타입은 'unsupported operand type' 에러를 발생시킵니다.

 

 

8. 타임델타 → 숫자 변환


import pandas as pd
import numpy as np
import datetime
from datetime import  date

td_pd = pd.Timedelta(5, unit='days')
td_np = np.timedelta64(5, 'D')
td_py = datetime.timedelta(days=5)

# 8-1 - 스칼라 값인 경우

print(td_pd.days)  # 5
print(td_np.astype(int))
print(td_py.days)   # 5


# 8-2 - 벡터일 경우

date_pd1 = pd.to_datetime(pd.Series(['2020-07-21', '2020-07-22', '2020-08-21']))
date_np1 = np.array(['2020-07-21', '2020-07-22', '2020-08-21'], dtype='datetime64')

date_pd2 = pd.to_datetime(pd.Series(['2020-07-20', '2020-06-22', '2019-08-21']))
date_np2 =  np.array(['2020-07-20', '2020-06-22', '2019-08-21'], dtype='datetime64')

td_pd = date_pd1 - date_pd2
td_np = date_np1 - date_np2

#print(td_pd)
#print(td_np)

#print(td_pd.days)   # AttributeError: 'Series' object has no attribute 'days'
print(td_pd.dt.days)
# 0      1
#1     30
#2    366
#dtype: int64
print(td_np.astype(int))   # [  1  30 366]

timedelta 타입 스칼라값을 숫자로 변환하는 방법은 8.1 코드를 참고하면 됩니다. pandas와 datetime의 경우 둘 다 날짜를 숫자로 변환할 때는 변수명 다음 '.days' 를 붙이면 되지만, numpy의 경우 days 속성이 없기 때문에 astype(int)와 같이 형변환 함수를 이용합니다.

 

1) Timedelta - Timedelta.days

2) timedelta64 - timedelta64.astype(int)

3) timedelta - timedelta.days

 

timedelta 벡터값을 숫자 벡터로 변환하는 방법은 8.2 코드를 참고하면 됩니다.

 

1) Series - Series.dt.days

2) ndarray - ndarray.astype(int)

 

numpy의 timedelta64는 스칼라나 벡터나 동일하지만, pandas의 Series 타입은 그대로 .days로 변환할 경우 'AttriAttributeError: 'Series' object has no attribute 'days'' 라는 에러를 출력합니다. 그 이유는 Series 데이터 타입에는 days라는 속성이 존재하지 않기 때문입니다. 대신 Series는 날짜/시간 타입 변환을 지원하기 위해 dt라는 속성이 존재합니다. 따라서 '일' 단위로 변환할 때는 .dt.days를 이용하면 벡터 내 각 원소들이 int로 변환됩니다.

 

 

 

9. 날짜 → 문자열 변환


import pandas as pd
import numpy as np
import datetime
from datetime import  date


# 9.1 - 스칼라일 경우 
print(str(np.datetime64('2020-07-21')))
print(str(pd.to_datetime('2020-07-21')))
print(str(date.fromisoformat('2020-07-22')))

# 9.2 - 벡터일 경우

date_series = pd.to_datetime(pd.Series(['2020-07-21', '2020-07-22', '2020-08-21']))
date_array = np.array(['2020-07-21', '2020-07-22', '2020-08-21'], dtype='datetime64')

# 에러는 나지 않으나 잘못된 변환
str_date_series = str(date_series)
str_date_array = str(date_array)
print(str_date_series, type(str_date_series), type(str_date_series[0]))
# 0   2020-07-21
# 1   2020-07-22
# 2   2020-08-21
# dtype: datetime64[ns] <class 'str'> <class 'str'>
print(str_date_array, type(str_date_array), type(str_date_array[0]))  # ['2020-07-21' '2020-07-22' '2020-08-21'] <class 'str'> <class 'str'>

#  정확한 변환
str_date_series = date_series.astype(str)
str_date_array = date_array.astype(str)
print(str_date_series, type(str_date_series), type(str_date_series[0]))
# 0    2020-07-21
# 1    2020-07-22
# 2    2020-08-21
# dtype: object <class 'pandas.core.series.Series'> <class 'str'>
print(str_date_array, type(str_date_array), type(str_date_array[0])) #['2020-07-21' '2020-07-22' '2020-08-21'] <class 'numpy.ndarray'> <class 'numpy.str_'>

 

맨 처음에 문자열을 날짜 타입으로 변환하는 것을 했었는데, 이번에는 반대로 날짜 타입을 문자열로 변환합니다. 스칼라 값의 경우 파이썬 기본 함수인 str()를 활용하며, 9-1코드와 같이 str(날짜)로 문자열 타입으로 바로 변환됩니다.

 

그러나 날짜 벡터를 문자열로 변환할때는 9-2 코드와 같이 str()를 사용하면 에러가 나지는 않고 멀쩡하게 변환된 것 처럼 보입니다. 하지만 원소만 문자열로 바뀌는 게 아니라 Series, ndarray 같은 벡터 타입까지 싸그리 문자열로 바뀌어 버립니다. 즉 위에 출력되는 값 전체가 문자열이 되어 버려서, '['2020-07-21' '2020-07-22' '2020-08-21']'가 통째로 하나의 문자열 값으로 변환됩니다.

따라서 벡터 내 원소들만 문자열로 변환하려면 아래와 같이 벡터.astype(str)을 사용하면 됩니다.

 

 

10. Timestamp, datetime64, date 간 변환 (스칼라 값)


마지막으로 날짜 타입인 Timestamp, datetime64, date 간 변환 방법입니다. 

 

import pandas as pd
import numpy as np
import datetime
from datetime import  date
import time


# 10. 타입변환 (스칼라일 경우) 
date_pd = pd.to_datetime('2020-07-22')
date_np = np.datetime64('2020-07-22')
date_py = date.fromisoformat('2020-07-22')

# Timestamp → datetime64
date_pd_to_np = date_pd.to_numpy()
print(date_pd_to_np)   # 2020-07-22T00:00:00.000000000

date_pd_to_np = np.datetime64(date_pd, 'D')
print(date_pd_to_np)   # 2020-07-22

# Timestamp → date
date_pd_to_py = datetime.date(date_pd.year, date_pd.month, date_pd.day)
print(date_pd_to_py)   # 2020-07-22

# datetime64 → Timestamp
date_np_to_pd = pd.to_datetime(date_np)
print(date_np_to_pd)   # 2020-07-22 00:00:00

# datetime64 → date
unix_epoch = np.datetime64(0, 's')
one_second = np.timedelta64(1, 's')
seconds_since_epoch = (date_np - unix_epoch) / one_second
date_np_to_py = datetime.datetime.utcfromtimestamp(seconds_since_epoch).date()
print(date_np_to_py)    # 2020-07-22

# date → Timestamp
date_py_to_pd = pd.to_datetime(date_py)
print(date_py_to_pd)   # 2020-07-22 00:00:00

# date → datetime64
date_py_to_np = np.datetime64(date_py)
print(date_py_to_np)   # 2020-07-22

Timestamp에서 datetime64로 변환하는 방법은 두 가지가 있는데, 하나는 .to_numpy() 함수를 사용하는 것이고, 다른 하나는 np.datetime64() 함수를 사용하는 것입니다. 둘의 차이점은 np.datetime64()의 경우 형식을 'D'로 하면 날짜만 깔끔하게 변환할 수 있으나, to_numpy()는 Timestamp가 자체적으로 날짜 시간을 모두 포함하고 있어 datetime64로 변환하면 시간까지 같이 딸려나오는 단점이 있습니다.

 

반대로 datetime64에서 Timestamp로 변환하는 것은 처음에 Timestamp 정의할 때와 같이 pd.to_datetime()을 사용하면 별다른 작업 없이도 잘 변환이 됩니다.

 

date에서 Timestamp나 datetime64로 변환하는 것도 기존에 활용했던 함수들을 사용하면 되어 어렵지 않으나, 반대로 Timestamp나 datetime64에서 date로 변환하는 것은 다소 복잡합니다.

 

왜냐하면 date는 날짜 정의 시 숫자로 정의하는 방법과 문자열로 정의하는 함수가 따로 있는 등 유연성이 부족하기 때문에, date가 알아들을 수 있는 형태로 값을 변환해주어야 합니다. Timestamp는 year, month, day를 추출할 수 있는 속성을 갖고 있기 때문에, 'datetime.date(year, month, day)' 형태로 연,월,일 값을 하나씩 넣어주는 방법으로 변환할 수 있습니다. 반면 datetime64는 연,월,일을 바로 추출할 수 있는 기능이 없기 때문에 날짜를 정수로 표시하는 timestamp로 변환한 다음 datetime으로 변환하고 이를 다시 date로 변환해야 합니다. 이 때 datetime에서 timestamp를 날짜로 변환하는 함수가 datetime.datetime.utcfromtimestamp()입니다. 이 함수를 통해 timestamp가 시간을 포함한 날짜 타입으로 변환되고, 여기서 날짜 타입만 취하고자 할 때는 다시 .date() 함수를 사용합니다.

 

 

11. Timestamp, datetime64, date 간 변환 (벡터 값)


마지막으로 날짜 타입인 Timestamp, datetime64, date 간 변환 방법입니다. 

 

import pandas as pd
import numpy as np
import datetime
from datetime import  date
import time


# 11. 타입 변환(벡터일 경우)
date_pd = pd.to_datetime(pd.Series(['2020-07-21', '2020-07-22', '2020-08-21']))
date_np = np.array(['2020-07-20', '2020-06-22', '2019-08-21'], dtype='datetime64')
date_py = [ date.fromisoformat(d) for d in ['2020-07-20', '2020-06-22', '2019-08-21']]

#Series-Timestamp → ndarray-datetime64 변환
date_pd_to_np = np.array(date_pd.astype(str), dtype='datetime64')
print(date_pd_to_np)   # ['2020-07-21' '2020-07-22' '2020-08-21']

#ndarray-datetime64 → Series-Timestamp 변환
date_np_to_pd = pd.to_datetime(date_np)
print(date_np_to_pd)   # DatetimeIndex(['2020-07-21', '2020-07-22', '2020-08-21'], dtype='datetime64[ns]', freq=None)

#list,date → DatetimeIndex-Timestamp 변환
date_py_to_pd = pd.to_datetime(date_py)
print(date_py_to_pd)   # DatetimeIndex(['2020-07-21', '2020-07-22', '2020-08-21'], dtype='datetime64[ns]', freq=None)

#list,date → ndarray-datetime64 변환
date_py_to_np = np.array(date_py, dtype='datetime64')
print(date_py_to_np)   # ['2020-07-21' '2020-07-22' '2020-08-21']

 

Timestamp Series나 date 타입 list 벡터를 datetime64로 변환할 때는 datetime64를 벡터로 정의할 때 처럼 np.array(, dtype='datetime64')를 활용합니다. 반면 datetime64 ndarray나 date 타입 list 벡터를 timestamp로 변환할 때는 스칼라와 동일하게 pd.to_datetime()를 이용하면 됩니다.

 

그리고 date 타입 list 벡터의 경우 맨 앞에서 날짜 list를 정의하기 위해서는 별도의 함수없이 for를 활용하여 원소 하나씩 정의하였으므로, 마찬가지로 여기서도 날짜 하나씩 스칼라값을 date 타입으로 변환하는 것을 for 문을 활용하여 반복문을 구성하는 식으로 코드를 짜면 됩니다.