Python pandas で日時関連のデータ操作をカンタンに
概要
Python で日時/タイムスタンプ関連の操作をする場合は dateutil
や arrow
を使っている人が多いと思うが、 pandas
でもそういった処理がわかりやすく書けるよ、という話。
pandas
の本領は多次元データの蓄積/変形/集約処理にあるが、日時操作に関連した強力なメソッド / ユーティリティもいくつか持っている。今回は それらを使って日時操作を簡単に行う方法を書いてく。ということで DataFrame
も Series
もでてこない pandas
記事のはじまり。
※ ここでいう "日時/タイムスタンプ関連の操作" は文字列パース、日時加算/減算、タイムゾーン設定、条件に合致する日時のリスト生成などを想定。時系列補間/リサンプリングなんかはまた膨大になるので別途。
インストール
以下サンプルには 0.15での追加機能も含まれるため、0.15 以降が必要。
pip install pandas
準備
import pandas as pd
初期化/文字列パース
pd.to_datetime
が便利。返り値は Python 標準のdatetime.datetime
クラスを継承したpd.Timestamp
インスタンスになる。
dt = pd.to_datetime('2014-11-09 10:00') dt # Timestamp('2014-11-09 10:00:00') type(dt) # pandas.tslib.Timestamp import datetime isinstance(dt, datetime.datetime) # True
pd.to_datetime
は、まず pandas
独自の日時パース処理を行い、そこでパースできなければ dateutil.parser.parse
を呼び出す。そのため結構無茶なフォーマットもパースできる。
pd.to_datetime('141109 1005') # Timestamp('2014-11-09 10:05:00')
リスト-likeなものを渡すと DatetimeIndex
( pandas
を 普段 使ってない方は 日時のリストみたいなものだと思ってください) が返ってくる。
pd.to_datetime(['2014-11-09 10:00', '2014-11-10 10:00']) # <class 'pandas.tseries.index.DatetimeIndex'> # [2014-11-09 10:00:00, 2014-11-10 10:00:00] # Length: 2, Freq: None, Timezone: None
また、pd.to_datetime
は文字列以外の 日付-like なものも処理できる。自分は numpy.datetime64
を datetime.datetime
に変換するのに使ったりもする。
pd.to_datetime(datetime.datetime(2014, 11, 9)) # Timestamp('2014-11-09 00:00:00') import numpy as np pd.to_datetime(np.datetime64('2014-11-09 10:00Z')) # Timestamp('2014-11-09 10:00:00')
補足 Timestamp.to_datetime()
で Timestamp
-> datetime.datetime
へ変換できる
dt.to_datetime()
# datetime.datetime(2014, 11, 9, 10, 0)
日時の加算/減算
pd.Timestamp
に対して datetime.timedelta
, dateutil.relativedelta
を使って日時の加減算を行うこともできるが、
dt + datetime.timedelta(days=1) # Timestamp('2014-11-10 10:00:00') from dateutil.relativedelta import relativedelta dt + relativedelta(days=1) # Timestamp('2014-11-10 10:00:00')
一般的な時間単位は pandas
が offsets
として定義しているため、そちらを使ったほうが直感的だと思う。使えるクラスの一覧は こちら。
dt # Timestamp('2014-11-09 10:00:00') import pandas.tseries.offsets as offsets # 翌日 dt + offsets.Day() # Timestamp('2014-11-10 10:00:00') # 3日後 dt + offsets.Day(3) # Timestamp('2014-11-12 10:00:00')
pd.offsets
を使うメリットとして、例えば dateutil.relativedelta
では days
と day
を間違えるとまったく違う意味になってしまう。が、pd.offsets
ならより明瞭に書ける。
# relativedelta の場合 days なら 3日後 dt + relativedelta(days=3) # Timestamp('2014-11-12 10:00:00') # day なら月の 3日目 dt + relativedelta(day=3) # Timestamp('2014-11-03 10:00:00') # 月の3日目を取りたいならこっちのが明瞭 dt - offsets.MonthBegin() + offsets.Day(2) # Timestamp('2014-11-03 10:00:00')
時刻部分を丸めたければ normalize=True
。
dt + offsets.MonthEnd(normalize=True) # Timestamp('2014-11-30 00:00:00')
また、 pd.offsets
は datetime.datetime
, np.datetime64
にも適用できる。
datetime.datetime(2014, 11, 9, 10, 00) + offsets.Week(weekday=2) # Timestamp('2014-11-12 10:00:00')
※ np.datetime64
に対して適用する場合は 加算/減算オペレータではなく、pd.offsets
インスタンスの .apply
メソッドを使う。.apply
は Timestamp
/ datetime
にも使える。(というか加減算オペレータも裏側では .apply
を使っている)。
offsets.Week(weekday=2).apply(np.datetime64('2014-11-09 10:00:00Z')) # Timestamp('2014-11-12 10:00:00') offsets.Week(weekday=2).apply(dt) # Timestamp('2014-11-12 10:00:00')
ある日付が offsets
に乗っているかどうか調べる場合は .onOffset
。例えば ある日付が水曜日 ( weekday=2
) かどうか調べたければ、
# 11/09は日曜日 ( weekday=6 ) dt # Timestamp('2014-11-09 10:00:00') pd.offsets.Week(weekday=2).onOffset(dt) # False pd.offsets.Week(weekday=2).onOffset(dt + offsets.Day(3)) # True
タイムゾーン
タイムゾーン関連の処理は tz_localize
と tz_convert
二つのメソッドで行う。引数は 文字列、もしくは pytz
, dateutil.tz
インスタンス (後述)。
上でつくった Timestamp
について、現在の表示時刻 (10:00) をそのままにして タイムゾーンを付与する場合は tz_localize
。
dt # Timestamp('2014-11-09 10:00:00') dt.tz_localize('Asia/Tokyo') # Timestamp('2014-11-09 10:00:00+0900', tz='Asia/Tokyo')
現在の表示時刻を世界標準時 (GMTで10:00) とみて タイムゾーン付与する場合は まず GMT に tz_localize
してから tz_convert
。
dt.tz_localize('GMT').tz_convert('Asia/Tokyo') # Timestamp('2014-11-09 19:00:00+0900', tz='Asia/Tokyo')
一度 タイムゾーンを付与したあとは、tz_convert
でタイムゾーンを変更したり、
dttz = dt.tz_localize('Asia/Tokyo') dttz # Timestamp('2014-11-09 10:00:00+0900', tz='Asia/Tokyo') dttz.tz_convert('US/Eastern') # Timestamp('2014-11-08 20:00:00-0500', tz='US/Eastern')
タイムゾーンを削除したりできる。
tz_localize(None)
:Timestamp
のローカル時刻を引き継いで タイムゾーンを削除tz_convert(None)
:Timestamp
の GMT 時刻を引き継いで タイムゾーンを削除
dttz # Timestamp('2014-11-09 10:00:00+0900', tz='Asia/Tokyo') dttz.tz_localize(None) # Timestamp('2014-11-09 10:00:00') dttz.tz_convert(None) # Timestamp('2014-11-09 01:00:00')
また、tz_localize
, tz_convert
は pytz
, dateutil.tz
を区別なく扱える。標準の datetime.datetime
では pytz
と dateutil.tz
でタイムゾーンの初期化方法が違ったりするのでこれはうれしい。
import pytz ptz = pytz.timezone('Asia/Tokyo') dt.tz_localize(ptz) # Timestamp('2014-11-09 10:00:00+0900', tz='Asia/Tokyo') from dateutil.tz import gettz dtz = gettz('Asia/Tokyo') dt.tz_localize(dtz) # Timestamp('2014-11-09 10:00:00+0900', tz='dateutil//usr/share/zoneinfo/Asia/Tokyo')
条件に合致する日時のリスト生成
単純な条件での生成
pd.date_range
で以下で指定する引数の条件に合致した日時リストを一括生成できる。渡せるのは 下の4つのうち 3つ。例えば start
, periods
, freq
を渡せば end
は自動的に計算される。
start
: 開始時刻end
: 終端時刻periods
: 内部に含まれる要素の数freq
: 生成する要素ごとに変化させる時間単位/周期 ( 1時間ごと、1日ごとなど)。freq
として指定できる文字列は こちら。またpd.offsets
も使える (後述)。
返り値は上でも少しふれた DatetimeIndex
になる。print
された DatetimeIndex
の読み方として、表示結果の 2行目が 生成された日時の始点 (11/01 10:00) と終端 (11/04 09:00)、3行目が生成されたリストの長さ (72コ)と周期 ( H = 1時間ごと) になる。
dtidx = pd.date_range('2014-11-01 10:00', periods=72, freq='H') dtidx # <class 'pandas.tseries.index.DatetimeIndex'> # [2014-11-01 10:00:00, ..., 2014-11-04 09:00:00] # Length: 72, Freq: H, Timezone: None
生成された DatetimeIndex
から Timestamp
を取得する場合、ふつうに要素選択するか、 .asobject.tolist()
を使う。.asobject.tolist()
の意味は、
asobject
:DatetimeIndex
内の要素をTimestamp
オブジェクトに明示的に変換 (メソッドでなくプロパティなので注意)tolist()
:DatetimeIndex
自身と同じ中身のリストを生成
dtidx[1] # Timestamp('2014-11-01 11:00:00', offset='H') dtidx.asobject.tolist() # [Timestamp('2014-11-01 10:00:00', offset='H'), # Timestamp('2014-11-01 11:00:00', offset='H'), # ... # Timestamp('2014-11-04 08:00:00', offset='H'), # Timestamp('2014-11-04 09:00:00', offset='H')]
少し複雑 (n個おき) な条件での生成
また、freq
に offsets
を与えればもう少し複雑な条件でもリスト生成できる。ある日時から3時間ごとの Timestamp
を生成したい場合は以下のように書ける。
pd.date_range('2014-11-01 10:00', periods=72, freq=offsets.Hour(3)) # <class 'pandas.tseries.index.DatetimeIndex'> # [2014-11-01 10:00:00, ..., 2014-11-10 07:00:00] # Length: 72, Freq: 3H, Timezone: None
さらに複雑な条件 (任意の条件) での生成
さらに複雑な条件を使いたい場合は、一度単純な条件でリスト生成し、必要な部分をスライシングして取得するのがよい。例えば 深夜 0時から5時間ごとに1時間目/4時間目の時刻をリストとして取得したい場合は以下のようにする。
これで dateutil.rrule
でやるような複雑な処理も (大部分? すべて?) カバーできるはず。
dtidx = pd.date_range('2014-11-01 00:00', periods=20, freq='H') selector = [False, True, False, False, True] * 4 dtidx[selector].asobject.tolist() # [Timestamp('2014-11-01 01:00:00'), # Timestamp('2014-11-01 04:00:00'), # Timestamp('2014-11-01 06:00:00'), # Timestamp('2014-11-01 09:00:00'), # Timestamp('2014-11-01 11:00:00'), # Timestamp('2014-11-01 14:00:00'), # Timestamp('2014-11-01 16:00:00'), # Timestamp('2014-11-01 19:00:00')]
DatetimeIndex に対する一括処理
最後に、先のセクションで記載した 時刻 加減算, タイムゾーン処理の方法/メソッドは DatetimeIndex
に対しても利用できる。全体について 何かまとめて処理したい場合は DatetimeIndex
の時点で処理しておくと楽。
dtidx # <class 'pandas.tseries.index.DatetimeIndex'> # [2014-11-01 00:00:00, ..., 2014-11-01 19:00:00] # Length: 20, Freq: H, Timezone: None # 1日後にずらす dtidx + offsets.Day() # <class 'pandas.tseries.index.DatetimeIndex'> # [2014-11-02 00:00:00, ..., 2014-11-02 19:00:00] # Length: 20, Freq: H, Timezone: None # タイムゾーンを設定 dtidx.tz_localize('Asia/Tokyo') # <class 'pandas.tseries.index.DatetimeIndex'> # [2014-11-01 00:00:00+09:00, ..., 2014-11-01 19:00:00+09:00] # Length: 20, Freq: H, Timezone: Asia/Tokyo
まとめ
pandas
なら日時/タイムスタンプ関連の操作もカンタン。
Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理
- 作者: Wes McKinney,小林儀匡,鈴木宏尚,瀬戸山雅人,滝口開資,野上大介
- 出版社/メーカー: オライリージャパン
- 発売日: 2013/12/26
- メディア: 大型本
- この商品を含むブログ (9件) を見る