note.nkmk.me

pandasの時系列データのタイムゾーンを処理(tz_convert, tz_localize)

Date: 2018-08-14 / Modified: 2020-04-09 / tags: Python, pandas, 時系列データ

pandasにおいて時系列データのタイムゾーンを処理するにはtz_convert(), tz_localize()を使う。

タイムゾーン情報が設定されている(awareな)データのタイムゾーンを別のタイムゾーンに変換(変更)するのがtz_convert()で、タイムゾーン情報が設定されていない(naiveな)データに新たにタイムゾーンを設定するのがtz_localize()

ここでは、まず単独の要素を例にtz_convert(), tz_localize()の処理内容を説明し、その後にpandas.DataFrame, Seriesのデータ列やインデックスに対する扱いを説明する。

  • 文字列からTimestamp型に変換: pd.to_datetime()
  • タイムゾーンを変換: tz_convert()
  • タイムゾーンを新たに設定: tz_localize()
  • タイムゾーンを削除する際の注意点
  • pandas.DataFrame, Seriesのデータ列・インデックスに対する処理
    • データ列でのtz_convert(), tz_localize()
    • DatetimeIndexでのtz_convert(), tz_localize()
    • pandas.DataFrame, Seriesでのtz_convert(), tz_localize()
    • タイムゾーン情報を含む文字列の場合

pandas.DataFrame, Seriesを時系列データとして扱う方法については以下の記事を参照。

Pythonにおけるタイムゾーンの処理については以下の記事を参照。

スポンサーリンク

文字列からTimestamp型に変換: pd.to_datetime()

文字列をTimestamp型に変換するにはpd.to_datetime()関数を使う。

タイムゾーンが含まれる日付の文字列

以下のようにTのあとに時刻、さらにそのあとに+09:00(+9時間)というタイムゾーンの情報が含まれる文字列を例とする。このフォーマットはISO 8601で規定されている。

import pandas as pd

s = '2018-01-01T12:00+09:00'
print(s)
# 2018-01-01T12:00+09:00

print(type(s))
# <class 'str'>

これをpd.to_datetime()に渡すと、tz属性にタイムゾーン情報が含まれる(awareな)Timestamp型オブジェクトが返される。この例のtz属性のFixedOffset(540)は540分(= 9時間)のオフセットを表す。

ts = pd.to_datetime(s)
print(ts)
# 2018-01-01 12:00:00+09:00

print(type(ts))
# <class 'pandas._libs.tslibs.timestamps.Timestamp'>

print(ts.tz)
# pytz.FixedOffset(540)

pd.to_datetime()の引数utcTrueとするとタイムゾーンがUTC(協定世界時)に設定される。

ts_utc = pd.to_datetime(s, utc=True)
print(ts_utc)
# 2018-01-01 03:00:00+00:00

print(ts_utc.tz)
# UTC

なお、デフォルト(utc=False)でtz属性が設定されるのはpandas0.24.0から。それより前のバージョンでは、UTCに変換され、tz属性がNoneとなる(naiveな)Timestamp型オブジェクトが返される。

タイムゾーンが含まれない日付の文字列

タイムゾーンの情報が含まれない文字列の場合、デフォルトではtz属性がNoneとなる(naiveな)Timestamp型オブジェクトが返される。

s_without_tz = '2018-01-01T12:00'

ts_naive = pd.to_datetime(s_without_tz)
print(ts_naive)
# 2018-01-01 12:00:00

print(ts_naive.tz)
# None

utc=Trueとすると、タイムゾーンがUTCに設定される。

ts_set_utc = pd.to_datetime(s_without_tz, utc=True)
print(ts_set_utc)
# 2018-01-01 12:00:00+00:00

print(ts_set_utc.tz)
# UTC

タイムゾーンを変換: tz_convert()

タイムゾーンを変換するにはtz_convert()メソッドを使う。

第一引数にタイムゾーン名を指定する。

print(ts_utc)
# 2018-01-01 03:00:00+00:00

print(ts_utc.tz)
# UTC

ts_jst = ts_utc.tz_convert('Asia/Tokyo')
print(ts_jst)
# 2018-01-01 12:00:00+09:00

print(ts_jst.tz)
# Asia/Tokyo

タイムゾーンが変換されても同時刻を指しているので、エポック秒(UNIX time)はtz_convert()前後で変わらない。Timestamp型オブジェクトのエポック秒はvalue属性で取得できる。

print(ts_utc.value)
# 1514775600000000000

print(ts_jst.value)
# 1514775600000000000

Timestamp==<, >などの比較演算子で比較できる。同じ時刻か、どちらの時刻が早いか、などを判定できる。

print(ts_utc == ts_jst)
# True

他のタイムゾーンへの変換も同じ。

ts_pst = ts_utc.tz_convert('US/Pacific')
print(ts_pst)
# 2017-12-31 19:00:00-08:00

print(ts_pst.tz)
# US/Pacific

タイムゾーン名の一覧は以下のページなどを参照。

同じ時差を表すタイムゾーン名は一つだけでなく複数存在する。

print(ts_utc.tz_convert('America/Los_Angeles'))
# 2017-12-31 19:00:00-08:00

print(ts_utc.tz_convert('America/Vancouver'))
# 2017-12-31 19:00:00-08:00

タイムゾーンを新たに設定: tz_localize()

タイムゾーン情報が設定されていないnaiveなTimestamp型オブジェクトからtz_convert()を呼ぶとエラーになる。

print(ts_naive)
# 2018-01-01 12:00:00

print(ts_naive.tz)
# None

# print(ts_naive.tz_convert('Asia/Tokyo'))
# TypeError: Cannot convert tz-naive Timestamp, use tz_localize to localize

naiveなデータに新たにタイムゾーン情報を設定する場合はtz_localize()を使う。

ts_jst_localize = ts_naive.tz_localize('Asia/Tokyo')
print(ts_jst_localize)
# 2018-01-01 12:00:00+09:00

print(ts_jst_localize.tz)
# Asia/Tokyo

tz_localize()では、元の時刻はそのままでタイムゾーンが新たに設定されるので、同じオブジェクトに異なるタイムゾーンを設定すると違う時刻を示すオブジェクトとなる。

print(ts_naive.tz_localize('US/Pacific'))
# 2018-01-01 12:00:00-08:00

print(ts_naive.tz_localize('Asia/Tokyo') == ts_naive.tz_localize('US/Pacific'))
# False

すでにタイムゾーンが設定されているオブジェクトに対してtz_localize()で新たなタイムゾーンに上書きすることはできない。次に示すようにタイムゾーン情報を削除する必要がある。

print(ts_jst)
# 2018-01-01 12:00:00+09:00

print(ts_jst.tz)
# Asia/Tokyo

# print(ts_jst.tz_localize('US/Pacific'))
# TypeError: Cannot localize tz-aware Timestamp, use tz_convert for conversions

タイムゾーンを削除する際の注意点

タイムゾーン情報を削除したい場合は、tz_convert()またはtz_localize()の第一引数にNoneを指定すればよいが、それぞれ結果が異なるので注意。

tz_convert()の第一引数にNoneを指定すると、UTCに変換された上でタイムゾーン情報が削除され、tz_localize()の第一引数にNoneを指定すると、現地時刻のままタイムゾーン情報が削除される。

print(ts_jst)
# 2018-01-01 12:00:00+09:00

print(ts_jst.tz)
# Asia/Tokyo

print(ts_jst.tz_convert(None))
# 2018-01-01 03:00:00

print(ts_jst.tz_localize(None))
# 2018-01-01 12:00:00

pandas.DataFrame, Seriesのデータ列・インデックスに対する処理

これまでの例は単体の要素(Timestamp型)に対する処理だったが、pandas.DataFramepandas.Seriesに対する場合は注意が必要。

tz_convert(), tz_localize()が使えるのは、これまでの例のようなTimestamp型の要素(スカラー値)、DatetimeIndex、および、インデックスがDatetimeIndexであるpandas.DataFrame, Seriespandas.DataFrameのデータ列(pandas.Series)ではdtアクセサを介して使う。

データ列でのtz_convert(), tz_localize()

まず、タイムゾーン情報を含まない日時の文字列を要素とする列をもつpandas.DataFrameを例とする。

df = pd.DataFrame({'date': ['2018-01-01T12:00',
                            '2018-01-02T00:00',
                            '2018-01-03T10:00',
                            '2018-01-03T19:00'],
                   'value': ['A', 'B', 'C', 'D']})

print(df)
#                date value
# 0  2018-01-01T12:00     A
# 1  2018-01-02T00:00     B
# 2  2018-01-03T10:00     C
# 3  2018-01-03T19:00     D

日時の文字列を要素とする列(pandas.Series)はpd.to_datetime()datetime64[ns]型に変換できる。各要素はTimestamp型。

デフォルト(utc=False)はタイムゾーン情報を含まない。

s_naive = pd.to_datetime(df['date'])
print(s_naive)
# 0   2018-01-01 12:00:00
# 1   2018-01-02 00:00:00
# 2   2018-01-03 10:00:00
# 3   2018-01-03 19:00:00
# Name: date, dtype: datetime64[ns]

print(s_naive[0])
# 2018-01-01 12:00:00

print(type(s_naive[0]))
# <class 'pandas._libs.tslibs.timestamps.Timestamp'>

print(s_naive[0].tz)
# None

utc=Trueとすると、タイムゾーンがUTCに設定される。

s_utc = pd.to_datetime(df['date'], utc=True)
print(s_utc)
# 0   2018-01-01 12:00:00+00:00
# 1   2018-01-02 00:00:00+00:00
# 2   2018-01-03 10:00:00+00:00
# 3   2018-01-03 19:00:00+00:00
# Name: date, dtype: datetime64[ns, UTC]

print(s_utc[0].tz)
# UTC

列(pandas.Series)からそのままtz_convert(), tz_localize()を呼び出すとエラーとなる。

# print(s_naive.tz_localize('Asia/Tokyo'))
# TypeError: index is not a valid DatetimeIndex or PeriodIndex

# print(s_utc.tz_convert('Asia/Tokyo'))
# TypeError: index is not a valid DatetimeIndex or PeriodIndex

dt.tz_convert(), dt.tz_localize()のように、dtアクセサを用いる。

print(s_naive.dt.tz_localize('Asia/Tokyo'))
# 0   2018-01-01 12:00:00+09:00
# 1   2018-01-02 00:00:00+09:00
# 2   2018-01-03 10:00:00+09:00
# 3   2018-01-03 19:00:00+09:00
# Name: date, dtype: datetime64[ns, Asia/Tokyo]

print(s_utc.dt.tz_convert('Asia/Tokyo'))
# 0   2018-01-01 21:00:00+09:00
# 1   2018-01-02 09:00:00+09:00
# 2   2018-01-03 19:00:00+09:00
# 3   2018-01-04 04:00:00+09:00
# Name: date, dtype: datetime64[ns, Asia/Tokyo]

上述のTimestamp型の要素の場合と同様に、タイムゾーンが設定されていないnaiveなデータ列に対してtz_convert()を使ったり、タイムゾーンが設定されているawareなデータ列に対してtz_localize()を使うことはできない。以降の例でも同じ。

# print(s_naive.dt.tz_convert('Asia/Tokyo'))
# TypeError: Cannot convert tz-naive timestamps, use tz_localize to localize

# print(s_utc.dt.tz_localize('Asia/Tokyo'))
# TypeError: Already tz-aware, use tz_convert to convert.

また、dtアクセサが使えるのは各要素の型がTimestampのような日時情報を表す場合のみ。文字列の列では使えない。上の例のようにあらかじめpd.to_datetime()で変換する必要がある。

# print(df['date'].dt.tz_localize('Asia/Tokyo'))
# AttributeError: Can only use .dt accessor with datetimelike values

DatetimeIndexでのtz_convert(), tz_localize()

set_index()index属性に代入するなどしてdatetime64[ns]の列をインデックスに設定すると、そのインデックスはDatetimeIndexとして扱われ、日時で行を指定したりできるようになる。

df['date'] = pd.to_datetime(df['date'])
df_ts = df.set_index('date')
print(df_ts)
#                     value
# date                     
# 2018-01-01 12:00:00     A
# 2018-01-02 00:00:00     B
# 2018-01-03 10:00:00     C
# 2018-01-03 19:00:00     D

print(df_ts.index)
# DatetimeIndex(['2018-01-01 12:00:00', '2018-01-02 00:00:00',
#                '2018-01-03 10:00:00', '2018-01-03 19:00:00'],
#               dtype='datetime64[ns]', name='date', freq=None)

print(type(df_ts.index))
# <class 'pandas.core.indexes.datetimes.DatetimeIndex'>

print(df_ts['2018-01-03'])
#                     value
# date                     
# 2018-01-03 10:00:00     C
# 2018-01-03 19:00:00     D

DatetimeIndextz_convert(), tz_localize()を実行可能。タイムゾーンを変更したり追加したりできる。

print(df_ts.index.tz_localize('Asia/Tokyo'))
# DatetimeIndex(['2018-01-01 12:00:00+09:00', '2018-01-02 00:00:00+09:00',
#                '2018-01-03 10:00:00+09:00', '2018-01-03 19:00:00+09:00'],
#               dtype='datetime64[ns, Asia/Tokyo]', name='date', freq=None)

pandas.DataFrame, Seriesでのtz_convert(), tz_localize()

インデックスがDatetimeIndexであるpandas.DataFrame, Seriesからtz_convert(), tz_localize()を実行することも可能。

インデックスであるDatetimeIndexのタイムゾーンが変更・追加される。

print(df_ts.tz_localize('Asia/Tokyo'))
#                           value
# date                           
# 2018-01-01 12:00:00+09:00     A
# 2018-01-02 00:00:00+09:00     B
# 2018-01-03 10:00:00+09:00     C
# 2018-01-03 19:00:00+09:00     D

pandas.Seriesでも同様。

s_ts = df_ts['value']
print(s_ts)
# date
# 2018-01-01 12:00:00    A
# 2018-01-02 00:00:00    B
# 2018-01-03 10:00:00    C
# 2018-01-03 19:00:00    D
# Name: value, dtype: object

print(s_ts.tz_localize('Asia/Tokyo'))
# date
# 2018-01-01 12:00:00+09:00    A
# 2018-01-02 00:00:00+09:00    B
# 2018-01-03 10:00:00+09:00    C
# 2018-01-03 19:00:00+09:00    D
# Name: value, dtype: object

このように、pandas.DataFrame, Seriesから直接tz_convert(), tz_localize()を実行できるのはインデックスがDatetimeIndexである場合のみ。インデックスのタイムゾーンが処理される。

データ列(pandas.Series)を処理したい場合は上述のようにdtアクセサを使う。

タイムゾーン情報を含む文字列の場合

+09:00のようなタイムゾーン情報を含む文字列の場合。

タイムゾーンが全て共通

タイムゾーンが全て共通であれば、pd.to_datetime()でタイムゾーン情報を含むdatetime64[ns]に変換できる。

df = pd.DataFrame({'date': ['2018-01-01T12:00+09:00',
                            '2018-01-02T00:00+09:00',
                            '2018-01-03T10:00+09:00',
                            '2018-01-03T19:00+09:00'],
                   'value': ['A', 'B', 'C', 'D']})

print(df)
#                      date value
# 0  2018-01-01T12:00+09:00     A
# 1  2018-01-02T00:00+09:00     B
# 2  2018-01-03T10:00+09:00     C
# 3  2018-01-03T19:00+09:00     D

print(pd.to_datetime(df['date']))
# 0   2018-01-01 12:00:00+09:00
# 1   2018-01-02 00:00:00+09:00
# 2   2018-01-03 10:00:00+09:00
# 3   2018-01-03 19:00:00+09:00
# Name: date, dtype: datetime64[ns, pytz.FixedOffset(540)]

utc=TrueとしてUTCに変換することも可能。

print(pd.to_datetime(df['date'], utc=True))
# 0   2018-01-01 03:00:00+00:00
# 1   2018-01-01 15:00:00+00:00
# 2   2018-01-03 01:00:00+00:00
# 3   2018-01-03 10:00:00+00:00
# Name: date, dtype: datetime64[ns, UTC]

上の例と同様に、dtアクセサを利用したり、インデックスに設定してDatetimeIndexとして扱ったりできる。

print(pd.to_datetime(df['date']).dt.tz_convert('US/Pacific'))
# 0   2017-12-31 19:00:00-08:00
# 1   2018-01-01 07:00:00-08:00
# 2   2018-01-02 17:00:00-08:00
# 3   2018-01-03 02:00:00-08:00
# Name: date, dtype: datetime64[ns, US/Pacific]
df['date'] = pd.to_datetime(df['date'])
df_ts = df.set_index('date')
print(df_ts)
#                           value
# date                           
# 2018-01-01 12:00:00+09:00     A
# 2018-01-02 00:00:00+09:00     B
# 2018-01-03 10:00:00+09:00     C
# 2018-01-03 19:00:00+09:00     D

print(df_ts.index)
# DatetimeIndex(['2018-01-01 12:00:00+09:00', '2018-01-02 00:00:00+09:00',
#                '2018-01-03 10:00:00+09:00', '2018-01-03 19:00:00+09:00'],
#               dtype='datetime64[ns, pytz.FixedOffset(540)]', name='date', freq=None)

print(df_ts.tz_convert('US/Pacific'))
#                           value
# date                           
# 2017-12-31 19:00:00-08:00     A
# 2018-01-01 07:00:00-08:00     B
# 2018-01-02 17:00:00-08:00     C
# 2018-01-03 02:00:00-08:00     D

タイムゾーンが共通でない

タイムゾーンがバラバラの場合。

df = pd.DataFrame({'date': ['2018-01-01T12:00+09:00',
                            '2018-01-02T00:00+09:00',
                            '2018-01-03T10:00-05:00',
                            '2018-01-03T19:00-08:00'],
                   'value': ['A', 'B', 'C', 'D']})

print(df)
#                      date value
# 0  2018-01-01T12:00+09:00     A
# 1  2018-01-02T00:00+09:00     B
# 2  2018-01-03T10:00-05:00     C
# 3  2018-01-03T19:00-08:00     D

pd.to_datetime()のデフォルトでは各要素がPythonのdatetimeモジュールのdatetime型に変換される。datetime.datetimeのタイムゾーン情報はtzinfo属性。元の文字列の通り、各要素が別々のタイムゾーンとなる。

print(pd.to_datetime(df['date']))
# 0    2018-01-01 12:00:00+09:00
# 1    2018-01-02 00:00:00+09:00
# 2    2018-01-03 10:00:00-05:00
# 3    2018-01-03 19:00:00-08:00
# Name: date, dtype: object

print(type(pd.to_datetime(df['date'])[0]))
# <class 'datetime.datetime'>

print(pd.to_datetime(df['date'])[0].tzinfo)
# tzoffset(None, 32400)

print(pd.to_datetime(df['date'])[2].tzinfo)
# tzoffset(None, -18000)

pd.to_datetime()の引数utcTrueとすると、各要素はTimestamp型、列(pandas.Series)としてのデータ型dtypeはUTC基準のdatetime64[ns]datetime64[ns, UTC])となる。すべての要素がUTCの時刻に変換される。

print(pd.to_datetime(df['date'], utc=True))
# 0   2018-01-01 03:00:00+00:00
# 1   2018-01-01 15:00:00+00:00
# 2   2018-01-03 15:00:00+00:00
# 3   2018-01-04 03:00:00+00:00
# Name: date, dtype: datetime64[ns, UTC]

print(type(pd.to_datetime(df['date'], utc=True)[0]))
# <class 'pandas._libs.tslibs.timestamps.Timestamp'>

後者(utc=True)はこれまでの例のとおりだが、前者(デフォルト、utc=False)はdtアクセサを使えない。

# print(pd.to_datetime(df['date']).dt.tz_convert('Asia/Tokyo'))
# ValueError: Tz-aware datetime.datetime cannot be converted to datetime64 unless utc=True

また、インデックスに設定してもDatatimeIndexではなくノーマルのIndexとして扱われるため、tz_convert(), tz_localize()は使えない。

df['date'] = pd.to_datetime(df['date'])
df_dt = df.set_index('date')
print(df_dt)
#                           value
# date                           
# 2018-01-01 12:00:00+09:00     A
# 2018-01-02 00:00:00+09:00     B
# 2018-01-03 10:00:00-05:00     C
# 2018-01-03 19:00:00-08:00     D

print(df_dt.index)
# Index([2018-01-01 12:00:00+09:00, 2018-01-02 00:00:00+09:00,
#        2018-01-03 10:00:00-05:00, 2018-01-03 19:00:00-08:00],
#       dtype='object', name='date')

# print(df_dt.tz_convert('Asia/Tokyo'))
# TypeError: index is not a valid DatetimeIndex or PeriodIndex

# print(df_dt.tz_localize('Asia/Tokyo'))
# TypeError: index is not a valid DatetimeIndex or PeriodIndex

このように、タイムゾーンがバラバラの文字列に対してtz_convert(), tz_localize()を使いたい場合は、はじめにutc=Truepd.to_datetime()で変換する必要がある。

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事