データ分析を行なっていると"2022-6-11"や"2022-06-11-12-30"のような日付/日時情報を扱うことがよくあります。この記事ではpythonで日付/時刻を扱う際に利用するdatetime型の使い方を紹介します。
datetime型
pythonでは基本的な日付型および時間型としてdatetimeが用意されており、datetimeを用いることで年・月・日・曜日の取得や、日時同士の足し算/引き算など基本的な処理が簡単に行えます。
datetime型への変換
datetime型への変換にはdateutilライブラリを利用することをおすすめします。組み込みライブラリのdatetimeを使った変換もできますが、フォーマット指定が必要だったり柔軟に変換できないことが多いためです。
組み込み v.s. サードパーティの議論について興味がある方は、英語になりますがこちらの記事を参照ください。サードパーティのライブラリを使うメリットや、日時データをプログラミングで扱うのがなぜ難しのかについて記載されています。
日時を表した文字列をdatetime型に変換するサンプルコードを記載します。
from dateutil.parser import parse
# 日時の文字列
yymmdd1 = "2022-6-11"
yymmdd2 = "2022/6/11"
yymmdd3 = "June 11th 2022" # Human-Readableな形式
# datetime型へ変換
yymmdd1 = parse(yymmdd1)
yymmdd2 = parse(yymmdd2)
yymmdd3 = parse(yymmdd3)
print(yymmdd1) # datetime.datetime(2022, 6, 11, 0, 0)
print(yymmdd2) # datetime.datetime(2022, 6, 11, 0, 0)
print(yymmdd3) # datetime.datetime(2022, 6, 11, 0, 0)
3つの表現形式で変換を試しましたが、どの形式でも正しく変換されているのがわかります。
次に、標準規格として定義されているISO8601の表現形式を変換してみます。
ISO8601では基本形式と、拡張形式が定義されており、
- 基本形式: 20210927T150810.78
- 拡張形式: 2021-09-27T15:08:10.78
のように日付と時刻を「T記号」で区切って表現します。
また、タイムゾーンも併せて表現することが可能で、協定世界時(UTC)は末尾に「Z記号」を付加することで表現できます。UTCからの差分は「+記号」で表現でき、日本時間を表すJSTの場合は+0900を末尾に付けます。
# ISO8601形式の文字列
iso1 = "2021-09-27T15:08:10.78" # 拡張形式
iso2 = "20210927T150810.78" # 基本形式
iso3 = "2021-09-27T15:08:10.78Z" # 拡張形式(UTC)
iso4 = "2021-09-27T15:08:10.78+9:00" # 拡張形式(JST)
# datetime型へ変換
iso1 = parse(iso1) # 2021-09-27 15:08:10.780000
iso2 = parse(iso2) # 2021-09-27 15:08:10.780000
iso3 = parse(iso3) # 2021-09-27 15:08:10.780000+00:00
iso4 = parse(iso4) # 2021-09-27 15:08:10.780000+09:00
# 型を確認
type(iso1) # datetime.datetime
datetime型への変換ができたので、年月日や時刻の情報を抽出する方法を見ていきます。
datetimeで値の取得: 年月日、時刻、曜日、現在時刻
datetime型に変換できれば年や月といった個別の情報を抽出するのは簡単です。
以下の例で、各情報の取得方法を紹介します。
# 日付の取得
print(iso1.date()) # 2021-09-27
# 年・月・日の取得
print(iso1.year) # 2021
print(iso1.month) # 9
print(iso1.day) # 27
# 時・分・秒の取得
print(iso1.hour) # 15
print(iso1.minute) # 8
print(iso1.second) # 10
# 曜日の取得
print(iso1.weekday()) # 月曜日を0, 日曜日を6で取得
print(iso1.isoweekday()) # 月曜日を1, 日曜日を7で取得
# ISOカレンダーの取得
print(iso1.isocalendar()) # datetime.IsoCalendarDate(year=2021, week=39, weekday=1)
# 現在時刻の取得
now = datetime.datetime.now()
print(now) # datetime.datetime(2022, 6, 12, 12, 53, 1, 266546)
タイムゾーンについて
国や地域ごとの時差を考慮した時刻設定を行うためにタイムゾーンが定義されています。先の例でも紹介しましたが、datetime型ではタイムゾーンを設定することができます。ここではdatetimeでタイムゾーンを確認/設定/削除する方法を紹介します。
タイムゾーンの確認/設定/削除: tzname()/astimezone/replace
タイムゾーンの確認/設定/削除はそれぞれ、tzname()、astimezone、replaceを用いて実現できます。サンプルコードを見ていきましょう。
# UTCタイムゾーンのdatetime
iso = parse("2021-09-27T15:08:10Z")
print(iso) # 2021-09-27 15:08:10+00:00
# timezoneを確認する
print(iso.tzname()) # UTC
# タイムゾーンを追加: astimezone
## UTCタイムゾーンを設定
iso = iso.astimezone(datetime.timezone.utc)
print(iso) # 2021-09-28 00:08:10+09:00
## JSTタイムゾーンを設定。時差が反映される。
iso = iso.astimezone(datetime.timezone(datetime.timedelta(hours=9)))
print(iso) # 2021-09-28 00:08:10+09:00
## 時差を反映したくない場合は、一度タイムゾーンを削除する
iso = parse("2021-09-27T15:08:10Z") # UTC
iso = iso.replace(tzinfo=None) # タイムゾーンを削除
iso = iso.astimezone(datetime.timezone(datetime.timedelta(hours=9)))
print(iso) # 2021-09-27 15:08:10+09:00
timezoneを確認/設定/削除する方法を紹介しました。注意点としてはastimezoneでタイムゾーンを変更する場合、変更前のdatetimeにタイムゾーンが設定されていると時差が考慮され時間が変更することです。それを避けたい場合はコードにある通り一度タイムゾーンを削除しましょう。
pytzを用いたタイムゾーンの設定
pytzを用いることで、'Asia/Tokyo'のように分かりやすい表現でタイムゾーンを設定できます。
iso = parse("2021-09-27T15:08:10Z") # UTC
jst = iso.astimezone(pytz.timezone('Asia/Tokyo'))
est = iso.astimezone(pytz.timezone('US/Eastern'))
print(jst) # 2021-09-28 00:08:10+09:00
print(est) # 2021-09-27 11:08:10-04:00
DataFrameでdatetimeを扱う
PandasのDataFrameでdatetime型へ変換する方法を2つ紹介します。
datetime型への変換: to_datetime
1つ目は、日付や日時を文字列として持つDataframeに対して変換する場合です。
import pandas as pd
# サンプルデータの用意
df_time = pd.DataFrame({'datetime':['2014-10-10', '2014-10-11', '2014-10-12']})
display(df_time)
# datetime
# 0 2014-10-10
# 1 2014-10-11
# 2 2014-10-12
# datetime型へ変換
df_time['datetime'] = pd.to_datetime(df_time['datetime'])
# dtypeを確認
print(df_time.dtypes)
# datetime datetime64[ns]
# dtype: object
pandas.to_datetimeに変換したいカラムを指定することで変換ができます。
2つ目が、year, month, dayのカラムをもつDataFrameに日付を追加する方法です。以下の例をご覧ください。
# サンプルデータ
display(df_date)
# year month day
# 0 2019 1 1
# 1 2019 1 2
# 2 2019 1 3
# 3 2019 1 4
# 4 2019 1 5
## year, month, dayカラムをもつDataFrameを変換する。
df_date['datetime'] = pd.to_datetime(df_date)
display(df_date)
# year month day datetime
# 0 2019 1 1 2019-01-01
# 1 2019 1 2 2019-01-02
# 2 2019 1 3 2019-01-03
# 3 2019 1 4 2019-01-04
# 4 2019 1 5 2019-01-05
pd.to_datetimeにyear, month, dayカラムを持つDataFrameを渡すと自動的に日付を作成してくれます。
DataFrameで年月日、時刻、曜日、現在時刻の取得: dt
dtアクセサを用いることで、DataFrame(厳密にはSeries)でも年月日や曜日などの情報を取得できます。
以下のサンプルコードでは曜日を取得し、元のDataFrameに追加しています。
# 曜日を追加
df_date['dayofweek'] = df_date['datetime'].dt.dayofweek # 月曜: 0, 日曜:6
# year month day datetime dayofweek
# 0 2019 1 1 2019-01-01 1
# 1 2019 1 2 2019-01-02 2
# 2 2019 1 3 2019-01-03 3
# 3 2019 1 4 2019-01-04 4
# 4 2019 1 5 2019-01-05 5
dtアクセサで取得できる情報はこちらのリファレンスから確認できます。
時系列特徴量を追加
ここまで紹介してきた内容をもとに時系列の特徴量を追加するコードを紹介します。
年/月/日のデータが与えられた際に、曜日/週末/季節/祝日といった時系列に依存した汎用的な特徴量を追加します。
曜日/週末/季節を追加
以下のサンプルコードを実行すると、曜日/週末/季節のカラムが追加されます。
# datetimeの追加
if 'datetime' not in df.keys():
df['datetime'] = pd.to_datetime(df)
# 曜日(dayofweek)の追加
# Monday: 0 - Sunday: 6
if 'dayofweek' not in df.keys():
df['dayofweek'] = df['datetime'].dt.dayofweek
# 週末(weekend)の追加
begin_weekend = 5 # 週末の定義。土曜日以降を週末とする
if 'weekend' not in df.keys():
df['weekend'] = np.where(df['dayofweek'] >= begin_weekend, 1, 0)
# 季節(season)の追加
spring = [3,4,5]
summer = [6,7,8]
fall = [9,10,11]
winter = [12,1,2]
seasons = [spring, summer, fall, winter]
def which_season(month):
for idx, season in enumerate(seasons):
if month in season:
return idx
raise Exception("No season is match")
if 'season' not in df.keys():
df['season'] = df['month'].apply(which_season)
国別の祝日を取得
国別の祝日情報を得るには、holidaysライブラリが便利です。pipで簡単にインストールできます。
ここではholidaysライブラリを使用して日本の祝日かどうか判定するカラム"national holiday"を追加します。
# holidays ライブラリを使って祝日を追加
import holidays
jp_holidays = holidays.JP() # 日本の祝日情報を取得
if 'national holiday' not in df.keys():
df['national holiday'] = df['datetime'].apply(lambda x: int(x in jp_holidays))
当然日本以外の祝日データも取得できます。
us_holidays = holidays.US() # 米国の祝日
uk_holidays = holidays.UK() # イギリスの祝日
cn_holidays = holidays.CN() # 中国の祝日
カスタムした祝日を追加
国民の祝日以外にも、正月三が日のように特別な日を祝日として追加したいことがあると思います。その場合はベースとなる国祝日にカスタムの祝日を追加できます。
以下のサンプルコードでは日本の祝日をベースにカスタムの祝日(1/2, 2/3)を追加しています。
# カスタム祝日を追加する方法
class CorporateHolidays(holidays.JP): # 日本の祝日をベース
def _populate(self, year):
# 祝日を追加
self[datetime.date(year, 1, 2)] = "Some Federal Holiday"
self[datetime.date(year, 2, 3)] = "Another Federal Holiday"
custom_holiday = CorporateHolidays()
if 'custom holiday' not in df.keys():
df['custom holiday'] = df['datetime'].apply(lambda x: int(x in custom_holiday))
ここまで実行すると曜日/週末/季節/祝日を追加したデータができます。
特徴量を追加するコードまとめ
最後に特徴量を追加するコードをパイプラインとしてまとめておきました。
class CorporateHolidays(holidays.JP):
def _populate(self, year):
# Populate the holiday list with the default US holidays
# holidays.JP._populate(self, year)
# 祝日を追加
self[datetime.date(year, 1, 2)] = "Some Federal Holiday"
self[datetime.date(year, 2, 3)] = "Another Federal Holiday"
def which_season(month):
spring = [3,4,5]
summer = [6,7,8]
fall = [9,10,11]
winter = [12,1,2]
seasons = [spring, summer, fall, winter]
for idx, season in enumerate(seasons):
if month in season:
return idx
raise Exception("No season is match")
def pipeline_date_feature(df:pd.DataFrame) -> pd.DataFrame:
""" 時系列の特徴量(曜日/週末/季節/祝日)を追加
Args:
df (pd.DataFrame): year, month, dayカラムをもつDataFrame
Returns:
pd.DataFrame: datetime, dayofweek, weekend, season, national holiday, custom holiday カラムを追加したDataFrame
"""
if 'datetime' not in df.keys():
df['datetime'] = pd.to_datetime(df)
# 曜日(dayofweek)の追加
# Monday: 0 - Sunday: 6
if 'dayofweek' not in df.keys():
df['dayofweek'] = df['datetime'].dt.dayofweek
# 週末(weekend)の追加
begin_weekend = 5 # 週末の定義。土曜日以降を週末とする
if 'weekend' not in df.keys():
df['weekend'] = np.where(df['dayofweek'] >= begin_weekend, 1, 0)
# 季節(season)の追加
if 'season' not in df.keys():
df['season'] = df['month'].apply(which_season)
# 日本の祝日(national holiday)を追加
jp_holidays = holidays.JP()
if 'national holiday' not in df.keys():
df['national holiday'] = df['datetime'].apply(lambda x: int(x in jp_holidays))
# カスタムの祝日(custom holiday)を追加
custom_holiday = CorporateHolidays()
if 'custom holiday' not in df.keys():
df['custom holiday'] = df['datetime'].apply(lambda x: int(x in custom_holiday))
return df
コメント