Pandasのto_datetime
関数を徹底解説!日付・時刻変換の基本と応用
データ分析において、日付と時刻は非常に重要な情報源となります。時系列データの分析はもちろんのこと、異なるデータセットを結合したり、特定の期間でデータをフィルタリングしたり、集計を行ったりする際にも、日付・時刻データは中心的な役割を果たします。しかし、日付・時刻データの形式は多岐にわたり、文字列、数値、あるいは異なるフォーマットの日付文字列などが混在していることも少なくありません。
Pandasライブラリは、Pythonでデータ分析を行う上で不可欠なツールですが、日付・時刻データの扱いにおいても非常に強力な機能を提供しています。その中でも、最も基本的でありながら、データ前処理において最も頻繁に利用される関数の一つが pandas.to_datetime
です。
この記事では、Pandasの to_datetime
関数について、その基本的な使い方から、様々な引数を駆使した応用的な変換方法、よくある問題とその解決策まで、徹底的に解説します。この記事を読めば、to_datetime
関数をマスターし、日付・時刻データを自在に操るための強力な一歩を踏み出すことができるでしょう。
なぜ日付・時刻データの変換が必要なのか?
CSVファイルやデータベースからデータを読み込むと、日付や時刻の情報が文字列(object
型)として扱われていることがよくあります。例えば、'2023-10-27'
, '10/27/2023'
, '27 OCT 2023'
, '20231027'
, '1698374400'
(Unixエポック秒) など、様々な形式が考えられます。
これらのデータが文字列型や単なる数値型である場合、Pandasはそれらを日付や時刻として特別に扱うことができません。例えば、以下のような操作ができません。
- 日付に基づいた並べ替え
- 日付範囲によるデータのフィルタリング(例: 2023年内のデータのみ抽出)
- 特定の期間(月、四半期、年など)でのデータの集計
- 曜日や月、年などの要素を取り出す
- 日時の差(期間)を計算する
- タイムゾーンを考慮した処理
これらの操作を効率的かつ正確に行うためには、データをPandasが認識する日付・時刻型に変換する必要があります。Pandasにおける主要な日付・時刻型は Timestamp
(単一の時点)と DateTimeIndex
(複数の時点のインデックス/Series)です。そして、この変換を行うための中心的な関数が to_datetime
なのです。
pandas.to_datetime
関数の基本
pandas.to_datetime
関数は、様々な入力形式(文字列、数値、リスト、Series、DataFrameなど)を受け取り、Pandasが扱う日付・時刻型である Timestamp
オブジェクトまたは DateTimeIndex
オブジェクトに変換します。
最も基本的な使い方は、変換したい単一の値や、Series/リストなどのコレクションを引数として渡すことです。
“`python
import pandas as pd
単一の文字列を変換
date_str = ‘2023-10-27’
timestamp = pd.to_datetime(date_str)
print(timestamp)
print(type(timestamp))
文字列のリストを変換
date_list = [‘2023-10-27’, ‘2023-10-28’, ‘2023-10-29’]
datetime_index = pd.to_datetime(date_list)
print(datetime_index)
print(type(datetime_index))
文字列のSeriesを変換
date_series = pd.Series([‘2023/11/01 10:00’, ‘2023/11/02 11:30’, ‘2023/11/03 14:45’])
datetime_series = pd.to_datetime(date_series)
print(datetime_series)
print(type(datetime_series))
“`
実行結果:
2023-10-27 00:00:00
<class 'pandas._libs.tslibs.timestamps.Timestamp'>
DatetimeIndex(['2023-10-27', '2023-10-28', '2023-10-29'], dtype='datetime64[ns]', freq=None)
<class 'pandas.core.indexes.datetimes.DatetimeIndex'>
0 2023-11-01 10:00:00
1 2023-11-02 11:30:00
2 2023-11-03 14:45:00
dtype: datetime64[ns]
<class 'pandas.core.series.Series'>
このように、単一の値の場合は Timestamp
、複数の値(リストやSeries)の場合は DateTimeIndex
または datetime64[ns]
型のSeriesに変換されます。datetime64[ns]
というのは、PandasやNumPyで日付・時刻を扱うためのデータ型で、「ナノ秒精度の日時」を意味します。
基本的な変換であれば、to_datetime
は自動的に様々な標準的な日付・時刻形式を解釈してくれます。しかし、データが標準的でない形式であったり、パフォーマンスを最適化したい場合には、様々な引数を活用する必要があります。
to_datetime
関数の主な引数
pandas.to_datetime
関数は多くの引数を持ちますが、特に重要なのは以下のものです。
arg
: 変換対象のデータ(文字列、リスト、Series、DataFrame、数値など)。必須引数です。errors
: 変換エラーが発生した場合の挙動を指定します。'raise'
,'ignore'
,'coerce'
のいずれかを指定します。デフォルトは'raise'
です。dayfirst
: 日付の曖昧さがある場合に、日を先に解釈するかどうかを指定します(例: “01/02/2023” を 1月2日と解釈するか、2月1日と解釈するか)。ブール値を指定します。デフォルトはFalse
です。yearfirst
: 日付の曖昧さがある場合に、年を先に解釈するかどうかを指定します(例: “01/02/03” を 2001年2月3日と解釈するか、2003年1月2日と解釈するか)。ブール値を指定します。デフォルトはFalse
です。format
: 入力文字列の厳密な書式を指定します。これにより、変換処理を高速化し、曖昧さをなくすことができます。文字列で書式コード(例:'%Y-%m-%d'
)を指定します。unit
: 数値データを変換する場合の単位を指定します。'D'
,'h'
,'m'
,'s'
,'ms'
,'us'
,'ns'
など。Unixエポック秒などを変換する際に使用します。infer_datetime_format
: 入力文字列の書式を自動的に推測するかどうかを指定します。ブール値を指定します。デフォルトはFalse
ですが、書式が統一されている場合はTrue
にすることでパフォーマンスが向上する可能性があります(ただし、複雑な書式の場合は推測に時間がかかることもあります)。origin
: 数値データを変換する際の基準となる日時を指定します。デフォルトは'unix'
(1970年1月1日 00:00:00 UTC) です。cache
: 重複する入力値を変換する際に、変換結果をキャッシュするかどうかを指定します。ブール値を指定します。Seriesやリストに同じ日時が多く含まれる場合にパフォーマンスが向上する可能性があります。
これらの引数を使いこなすことで、様々な状況に対応した柔軟で効率的な変換が可能になります。
以降では、これらの主要な引数について、詳細な使用方法と具体的な例を交えて解説していきます。
errors
引数の詳細
データに含まれる日付・時刻文字列がすべて正しい形式であるとは限りません。中には無効な日付(例: ‘2023-02-30’)や、日付として解釈できない全く異なる文字列が含まれていることもあります。このような場合に to_datetime
がどのように振る舞うかを制御するのが errors
引数です。
errors
引数には以下の3つの値を指定できます。
-
'raise'
(デフォルト):- 変換できない値があった場合、即座に
ValueError
などの例外を発生させ、処理を中断します。 - データの品質が重要であり、無効な値が一つでもあれば許容できない場合に適しています。
python
date_series_with_error = pd.Series(['2023-10-27', 'invalid-date', '2023-10-29'])
try:
pd.to_datetime(date_series_with_error, errors='raise')
except ValueError as e:
print(f"エラーが発生しました: {e}")実行結果:
エラーが発生しました: Could not convert 'invalid-date' with format none.
- 変換できない値があった場合、即座に
-
'ignore'
:- 変換できない値があった場合でも例外は発生せず、その値は変換されずに元の形式(通常は文字列)のまま残ります。
- データの一部に無効な値が含まれていても処理を続行したいが、無効な値が何であるかをそのまま残しておきたい場合に便利です。
python
date_series_with_error = pd.Series(['2023-10-27', 'invalid-date', '2023-10-29'])
result_ignore = pd.to_datetime(date_series_with_error, errors='ignore')
print(result_ignore)
print(result_ignore.dtype)実行結果:
0 2023-10-27
1 invalid-date
2 2023-10-29
dtype: object
この場合、結果のSeriesのデータ型はobject
のままになり、日付/時刻としての特殊な操作はできません。有効な値も文字列として扱われます。 -
'coerce'
:- 変換できない値があった場合でも例外は発生せず、その値はPandasにおける日付・時刻の欠損値である
NaT
(Not a Time) に置き換えられます。 - データに無効な値が含まれていることが想定され、それらを欠損値として扱い、有効な値のみを日付・時刻型に変換したい場合に最もよく使われます。これはデータクレンジングにおいて非常に強力なオプションです。
python
date_series_with_error = pd.Series(['2023-10-27', 'invalid-date', '2023-10-29', '2023-02-30']) # 無効な日付も追加
result_coerce = pd.to_datetime(date_series_with_error, errors='coerce')
print(result_coerce)
print(result_coerce.dtype)
print(result_coerce.isna()) # NaTになった箇所を確認実行結果:
0 2023-10-27
1 NaT
2 2023-10-29
3 NaT
dtype: datetime64[ns]
bool
0 False
1 True
2 False
3 True
dtype: bool
'coerce'
を使うと、結果のSeriesはdatetime64[ns]
型になり、有効な値は正しく変換され、無効な値はNaT
になります。NaT
は.isna()
メソッドなどで欠損値として検出できるため、その後の処理(欠損値の補完や削除など)が容易になります。 - 変換できない値があった場合でも例外は発生せず、その値はPandasにおける日付・時刻の欠損値である
まとめ:
- 厳密に正しいデータのみを扱いたい、エラーを即座に検出したい →
'raise'
- エラー値を元のまま残し、処理を続行したい →
'ignore'
- エラー値を欠損値として扱い、有効な値だけを変換したい →
'coerce'
(データクレンジングで最も頻繁に利用)
データ分析の初期段階では、errors='coerce'
を使用してまず日付列を変換し、NaT
がいくつ含まれているかを確認し、それらをどう処理するか(削除するか、補完するかなど)を検討するワークフローが一般的です。
format
引数の徹底解説
to_datetime
はデフォルトでもある程度の書式を自動的に解釈してくれますが、入力データが標準的でない書式である場合や、変換処理のパフォーマンスを向上させたい場合は、format
引数を使用して入力文字列の書式を明示的に指定することが強く推奨されます。
format
引数には、入力文字列の構成を表す書式コードを含む文字列を指定します。これにより、Pandasは書式を自動推測する手間を省き、指定された書式に従って直接パース(解析)を行うため、特に大量のデータを変換する際にパフォーマンスが劇的に向上することがあります。また、'01/02/2023'
のような曖昧な書式であっても、'%m/%d/%Y'
や '%d/%m/%Y'
のように明示的に指定することで、意図しない解釈を防ぐことができます。
よく使われる書式コード一覧
書式コードは、Pythonの標準ライブラリ datetime
モジュールの strftime()
および strptime()
関数で使用されるものと同じです。以下に代表的な書式コードを示します。
コード | 説明 | 例 (2023年10月27日 15時9分4秒 123456マイクロ秒, 日本標準時 (UTC+09:00), 金曜日) |
---|---|---|
%Y |
4桁の年 | 2023 |
%y |
2桁の年 (00-99) | 23 |
%m |
ゼロ詰めした月 (01-12) | 10 |
%B |
月名 (英語、大文字小文字区別あり) | October |
%b |
月名の省略形 (英語、大文字小文字区別あり) | Oct |
%d |
ゼロ詰めした日 (01-31) | 27 |
%A |
曜日名 (英語、大文字小文字区別あり) | Friday |
%a |
曜日の省略形 (英語、大文字小文字区別あり) | Fri |
%H |
24時間表記の時 (00-23) | 15 |
%I |
12時間表記の時 (01-12) | 03 |
%p |
午前/午後 (AM/PM) | PM |
%M |
ゼロ詰めした分 (00-59) | 09 |
%S |
ゼロ詰めした秒 (00-61) | 04 (60 , 61 は閏秒) |
%f |
マイクロ秒 (6桁) | 123456 |
%Z |
タイムゾーン名 | JST |
%z |
UTCからのオフセット (形式: +HHMM or -HHMM) | +0900 |
%j |
年間の通算日 (001-366) | 300 (2023年10月27日は年の300日目) |
%U |
週番号 (年の最初の日曜日を週の始まりとする、00-53) | 43 |
%W |
週番号 (年の最初の月曜日を週の始まりとする、00-53) | 43 |
%c |
ロケールの日時表現 (例: ‘Fri Oct 27 15:09:04 2023’) | 例: Fri Oct 27 15:09:04 2023 |
%x |
ロケールの日付表現 (例: ’10/27/23′) | 例: 10/27/23 |
%X |
ロケールの時間表現 (例: ’15:09:04′) | 例: 15:09:04 |
区切り文字(-
, /
, ,
:
, .
, ,
など)や、時刻のAM/PMを表す文字列なども、書式文字列の中にそのまま含めます。
format
引数の使用例
様々な書式を持つ文字列を to_datetime
で変換する例を見てみましょう。
例1: YYYY-MM-DD HH:MM:SS
最も一般的な形式の一つです。format
を指定しなくても多くの場合正しく解釈されますが、明示的に指定する方が安全で高速です。
python
date_str = '2023-10-27 15:09:04'
datetime_obj = pd.to_datetime(date_str, format='%Y-%m-%d %H:%M:%S')
print(datetime_obj)
例2: MM/DD/YYYY
アメリカなどでよく使われる形式です。dayfirst=True
を指定しない限り、デフォルトでは DD/MM/YYYY
と解釈される可能性があるため、format
で明示することが重要です。
python
date_str = '10/27/2023'
datetime_obj = pd.to_datetime(date_str, format='%m/%d/%Y')
print(datetime_obj)
例3: DD-MM-YYYY
ヨーロッパなどでよく使われる形式です。
python
date_str = '27-10-2023'
datetime_obj = pd.to_datetime(date_str, format='%d-%m-%Y')
print(datetime_obj)
例4: YYYY年MM月DD日
(日本語)
日本語の区切り文字を含む場合も、format
で正確に記述します。
python
date_str = '2023年10月27日'
datetime_obj = pd.to_datetime(date_str, format='%Y年%m月%d日')
print(datetime_obj)
例5: 月名を含む形式
'27 Oct 2023'
のような形式の場合、月名を表す %b
を使用します。
“`python
date_str = ’27 Oct 2023′
datetime_obj = pd.to_datetime(date_str, format=’%d %b %Y’)
print(datetime_obj)
date_str_full = ‘October 27, 2023′
datetime_obj_full = pd.to_datetime(date_str_full, format=’%B %d, %Y’)
print(datetime_obj_full)
“`
例6: マイクロ秒を含む形式
高精度な時間情報を含む場合は %f
を使用します。
python
date_str = '2023-10-27 15:09:04.123456'
datetime_obj = pd.to_datetime(date_str, format='%Y-%m-%d %H:%M:%S.%f')
print(datetime_obj)
例7: タイムゾーン情報を含む形式
タイムゾーン情報を含む場合は %Z
や %z
を使用します。ただし、to_datetime
はデフォルトではタイムゾーン情報を保持せず、UTCに変換してawareな(タイムゾーン認識の)Timestamp/DateTimeIndexを作成します。Naiveな(タイムゾーン非認識の)オブジェクトが必要な場合は注意が必要です。タイムゾーンを明示的に扱う場合は、変換後に .tz_localize()
や .tz_convert()
を使用するのが一般的です。
“`python
UTCからのオフセット (+HHMM 形式)
date_str = ‘2023-10-27 15:09:04+0900′
datetime_obj = pd.to_datetime(date_str, format=’%Y-%m-%d %H:%M:%S%z’)
print(datetime_obj)
結果はタイムゾーン情報を持つ Timestamp になります
タイムゾーン名を含む場合(注意:システムによって認識されるタイムゾーン名が異なる場合があります)
date_str_tzname = ‘2023-10-27 15:09:04 JST’
formatに %Z を含めても、to_datetime自体はタイムゾーン名をパースするだけで、
そのタイムゾーン情報を持つawareな日時オブジェクトを直接生成するわけではありません。
通常は %z
を使うか、naiveな日時を生成してから後でtz_localizeします。
この例では、%Z を含めても naive な日時が生成されることが多いです。
より確実なのは、tz_localize を後で適用することです。
datetime_obj_tzname_naive = pd.to_datetime(date_str_tzname, format=’%Y-%m-%d %H:%M:%S %Z’)
print(datetime_obj_tzname_naive) # 多くの場合 naive になる
aware な日時が必要な場合は、tz_localize を使う
例: pd.to_datetime(‘2023-10-27 15:09:04’).tz_localize(‘JST’)
“`
タイムゾーンに関しては、to_datetime
で文字列をパースする際に %z
を使うと、そのオフセット情報を持つawareな日時オブジェクトが生成されます。%Z
はタイムゾーン名のパースを試みますが、結果がnaiveになるかawareになるかは入力や環境に依存する場合があるため、タイムゾーンを正確に扱いたい場合は、一旦naiveな日時としてパースし、後から tz_localize
や tz_convert
を適用するのが最も安全で柔軟な方法です。
format
指定の注意点
- 入力と完全に一致させる:
format
文字列は、入力文字列の正確な構造と一致している必要があります。少しでも異なると変換に失敗します。例えば、入力が'2023/10/27'
なのにformat='%Y-%m-%d'
と指定するとエラーになります。 - 大文字小文字の区別: 月名や曜日名の書式コード (
%B
,%b
,%A
,%a
) は大文字小文字を区別することがあります。入力文字列の大文字小文字に合わせて書式コードを記述してください。 - 曖昧さの排除:
format
を指定することで、dayfirst
やyearfirst
を指定するよりも強力に日付の解釈の曖昧さを排除できます。例えば'01/02/2023'
に対してformat='%m/%d/%Y'
と指定すれば必ず1月2日と解釈されます。 - パフォーマンス: 大量のデータを変換する場合、
format
を正確に指定することで、自動推測 (infer_datetime_format=True
) よりもはるかに高速になることが一般的です。
もしデータの中で複数の異なる書式が混在している場合、to_datetime
を一度に適用することはできません。その場合は、データを書式ごとに分割して変換するか、正規表現などを使って書式をある程度統一してから変換を試みる必要があります。あるいは、errors='coerce'
を利用して、変換できなかったものを特定し、個別に処理することも考えられます。
数値からの変換 (unit
, origin
)
to_datetime
関数は、日付や時刻を表す数値(整数または浮動小数点数)をPandasの日付・時刻型に変換することもできます。これは、Unixエポックタイム(1970年1月1日 00:00:00 UTCからの経過時間)や、特定の日付からの経過日数などで日時が表現されている場合によく利用されます。
数値からの変換には主に unit
と origin
の引数を使用します。
unit
: 入力数値の単位を指定します。デフォルトは'ns'
(ナノ秒) ですが、Unixエポックタイムでは秒 ('s'
)、ミリ秒 ('ms'
)、マイクロ秒 ('us'
) がよく使われます。その他の単位として、日 ('D'
), 時 ('h'
), 分 ('m'
) なども指定可能です。origin
: 数値0
がどの時点を表すかを指定します。デフォルトは'unix'
(1970年1月1日 00:00:00 UTC) です。Unixエポックタイム以外の基準日を使用する場合は、ここに基準となる日時を指定します(例:'2000-01-01'
やpd.Timestamp('2000-01-01')
)。
Unixエポックタイムからの変換
多くのシステムやAPIでは、日時をUnixエポックからの経過秒数、ミリ秒数、マイクロ秒数などで表現しています。
例1: Unixエポック秒 (unit='s'
)
“`python
2023年10月27日 15:09:04 UTC に対応するUnixエポック秒
unix_timestamp_s = 1698419344
datetime_obj_s = pd.to_datetime(unix_timestamp_s, unit=’s’)
print(datetime_obj_s)
Seriesでの変換
unix_series_s = pd.Series([1698419344, 1698419345, 1698419346])
datetime_series_s = pd.to_datetime(unix_series_s, unit=’s’)
print(datetime_series_s)
``
origin=’unix’
デフォルトのはUTCを基準としているため、結果もUTCとして解釈されることが多いですが、Pandasの内部表現はnaiveなdatetime64[ns]です。タイムゾーンを意識した処理を行う場合は、変換後に
.tz_localize(‘UTC’)` などで明示的にタイムゾーンを指定するのが安全です。
例2: Unixエポックミリ秒 (unit='ms'
)
“`python
2023年10月27日 15:09:04.123 UTC に対応するUnixエポックミリ秒
unix_timestamp_ms = 1698419344123
datetime_obj_ms = pd.to_datetime(unix_timestamp_ms, unit=’ms’)
print(datetime_obj_ms)
“`
例3: Unixエポックマイクロ秒 (unit='us'
)
“`python
2023年10月27日 15:09:04.123456 UTC に対応するUnixエポックマイクロ秒
unix_timestamp_us = 1698419344123456
datetime_obj_us = pd.to_datetime(unix_timestamp_us, unit=’us’)
print(datetime_obj_us)
“`
特定の基準日からの経過時間
origin
引数を使用すると、Unixエポックタイム以外の基準日から数値を変換できます。例えば、あるプロジェクトが開始された日付から経過した日数を日時として解釈する場合などです。
例: 2000年1月1日からの経過日数 (unit='D'
, origin='2000-01-01'
)
数値 0
が ‘2000-01-01’ を表し、数値 1
が ‘2000-01-02’ を表す、といったケースを想定します。
“`python
2000年1月1日からの経過日数
elapsed_days = pd.Series([0, 10, 100, 8765]) # 2000/01/01, 2000/01/11, 2000/04/10, 2023/12/15 に対応
origin_date = ‘2000-01-01’ # または pd.Timestamp(‘2000-01-01’)
datetime_series_days = pd.to_datetime(elapsed_days, unit=’D’, origin=origin_date)
print(datetime_series_days)
“`
このように、unit
と origin
を適切に指定することで、数値データから柔軟に日付・時刻オブジェクトを作成できます。
dayfirst
, yearfirst
引数
これらの引数は、'01/02/03'
や '01/02/2023'
のように、日付の要素の順番が曖昧な文字列を変換する際に、Pandasがどのように解釈するかを制御します。ただし、これらの引数を使用するよりも、format
引数で明示的に書式を指定する方が、意図しない変換を防ぎ、コードの可読性も高まるため推奨されます。format
を指定する場合は、これらの引数は無視されます。
dayfirst
(bool
, デフォルトFalse
):True
にすると、'01/02/2023'
は日/月/年 (2023年2月1日) として解釈されます。False
の場合、月/日/年 (2023年1月2日) または年/月/日 (2023年1月2日) と解釈されます(他の要素の有無に依存)。yearfirst
(bool
, デフォルトFalse
):True
にすると、'01/02/03'
は年/月/日 (2001年2月3日) または年/日/月 (2001年3月2日) として解釈されます。False
の場合、月/日/年 (2001年2月3日) や日/月/年 (2003年1月2日) などと解釈される可能性があります。
例: 曖昧な日付の解釈
“`python
ambiguous_date = ’01/02/2023′
デフォルト (dayfirst=False, yearfirst=False) – 多くの場合 MM/DD/YYYY または YYYY/MM/DD と解釈される
datetime_default = pd.to_datetime(ambiguous_date)
print(f”デフォルト: {datetime_default}”) # 多くの環境で 2023-01-02 となる
dayfirst=True を指定
datetime_dayfirst = pd.to_datetime(ambiguous_date, dayfirst=True)
print(f”dayfirst=True: {datetime_dayfirst}”) # 2023-02-01 となる
“`
この例からわかるように、dayfirst
を True
にすることで解釈が変わります。しかし、このような曖昧さを避けるには、やはり format
を使うのがベストです。
“`python
format=’%m/%d/%Y’ で明確に指定
datetime_format_mdy = pd.to_datetime(ambiguous_date, format=’%m/%d/%Y’)
print(f”format=’%m/%d/%Y’: {datetime_format_mdy}”) # 2023-01-02
format=’%d/%m/%Y’ で明確に指定
datetime_format_dmy = pd.to_datetime(ambiguous_date, format=’%d/%m/%Y’)
print(f”format=’%d/%m/%Y’: {datetime_format_dmy}”) # 2023-02-01
“`
このように、format
を使うことで解釈が明確になります。したがって、可能な限り format
引数の使用を検討し、それが難しい場合に補助的に dayfirst
や yearfirst
を使うと良いでしょう。
infer_datetime_format
とパフォーマンス
infer_datetime_format
はブール値(デフォルト False
)をとる引数です。True
に設定すると、Pandasは入力された最初のいくつかの非欠損値から日付の書式を推測しようとします。推測が成功し、データセット全体でその書式が統一されている場合、format
引数を手動で指定するのと同程度、あるいはそれ以上のパフォーマンス向上をもたらす可能性があります。これは、PandasがC拡張機能を使って高速なパースを行うためです。
しかし、以下の点に注意が必要です。
- 推測の失敗: 書式が複雑であったり、データ中に複数の書式が混在していたりする場合、推測が失敗することがあります。推測が失敗した場合、パフォーマンスは向上せず、かえって遅くなることもあります。
- 書式が統一されていない場合: 推測された書式と異なるデータが含まれていた場合、変換エラーが発生する可能性があります(
errors
引数の設定に依存)。 - 推測自体のコスト: 推測処理自体にもコストがかかります。データ量が非常に少ない場合は、手動で
format
を指定する方が速いこともあります。 - 保証されないパフォーマンス:
infer_datetime_format=True
は常にパフォーマンス向上を保証するものではありません。データの性質に依存します。
いつ使うか:
- 入力データが単一の明確な書式で統一されていることが分かっているが、その書式を手動で調べるのが面倒な場合。
- まずは試してみて、パフォーマンスが向上するかどうかを確認したい場合。
推奨:
- 可能であれば、入力データの書式を事前に把握し、
format
引数で明示的に指定する方が、最も信頼性が高く、多くの場合で安定した高いパフォーマンスが得られます。特に、大規模なデータを扱う場合は、手動でのformat
指定を検討すべきです。
“`python
infer_datetime_format=True を使用する場合
例: YYYY/MM/DD HH:MM:SS 形式が統一されている場合
date_series_slash = pd.Series([‘2023/10/27 10:00:00’, ‘2023/10/28 11:00:00’, ‘2023/10/29 12:00:00’])
format 指定なしより速くなる可能性があるが、format=’%Y/%m/%d %H:%M:%S’ と同じくらい速くなることも
datetime_series_infer = pd.to_datetime(date_series_slash, infer_datetime_format=True)
print(datetime_series_infer)
大量のデータに対してパフォーマンスを比較してみるのが良い
import timeit
setup = “””
import pandas as pd
date_series = pd.Series([‘2023-10-27 10:00:00’] * 100000)
“””
print(“Without format:”, timeit.timeit(“pd.to_datetime(date_series)”, setup=setup, number=10))
print(“With format:”, timeit.timeit(“pd.to_datetime(date_series, format=’%Y-%m-%d %H:%M:%S’)”, setup=setup, number=10))
print(“With infer:”, timeit.timeit(“pd.to_datetime(date_series, infer_datetime_format=True)”, setup=setup, number=10))
``
timeit
上記のコメントアウトされたコードのように、簡単なを使って異なる設定でのパフォーマンスを比較してみると、データや環境による違いが確認できます。一般的には、
format` を指定した場合が最も安定して高速です。
cache
引数
cache
はブール値(デフォルト False
)をとる引数です。True
に設定すると、to_datetime
は入力 Series の重複する文字列に対する変換結果を内部的にキャッシュします。これにより、同じ日付文字列が何度も出現する場合、2回目以降の変換がキャッシュされた結果から取得されるため、パフォーマンスが向上する可能性があります。
“`python
重複が多いSeries
date_series_cached = pd.Series([‘2023-10-27’] * 50000 + [‘2023-10-28’] * 50000)
cache=False (デフォルト)
pd.to_datetime(date_series_cached, cache=False) # 各要素を個別に変換
cache=True
datetime_series_cached = pd.to_datetime(date_series_cached, cache=True)
‘2023-10-27’ と ‘2023-10-28’ の変換結果がキャッシュされ、再利用される
print(datetime_series_cached)
“`
注意点:
cache=True
は、入力 Series に重複する値が多く含まれている場合に効果を発揮します。すべての値が一意である場合は、キャッシュのオーバーヘッドの方が大きくなる可能性があります。- キャッシュはメモリを使用します。非常に多数の一意な文字列を変換する場合、
cache=True
によってメモリ使用量が増加する可能性があるため注意が必要です。 - 文字列以外の型(数値など)を変換する場合は、
cache
引数は効果がありません。
重複が多いカテゴリカルな日付文字列などを変換する場合には、cache=True
を試してみる価値があります。
NaT
(Not a Time) について
errors='coerce'
を使用した際に登場した NaT
は、Pandasにおける日付・時刻型の欠損値を表す特別な値です。数値データにおける NaN
(Not a Number) やオブジェクトデータにおける None
と同様の役割を果たします。
NaT
の特徴:
datetime64[ns]
型のSeriesやDateTimeIndexに含まれることができます。NaT
と比較を行うと、多くの場合結果はFalse
になります(例:pd.NaT == pd.NaT
はFalse
)。欠損値の比較はpd.isna()
や.isnull()
メソッドを使用します。NaT
を含む Series に対して日付・時刻固有の操作(例:.dt
アクセサを使った要素抽出)を行うと、NaT
の箇所は欠損値として扱われます。
“`python
NaT を含む Series の例
datetime_series_with_nat = pd.Series([pd.Timestamp(‘2023-10-27’), pd.NaT, pd.Timestamp(‘2023-10-29’)])
print(datetime_series_with_nat)
NaT の検出
print(datetime_series_with_nat.isna())
NaT を含む Series で月を抽出
print(datetime_series_with_nat.dt.month)
“`
実行結果:
0 2023-10-27
1 NaT
2 2023-10-29
dtype: datetime64[ns]
0 False
1 True
2 False
dtype: bool
0 10.0
1 NaN
2 10.0
dtype: float64
NaT
の箇所の .dt.month
の結果が NaN
になっていることがわかります。これは、Pandasが NaT
を日付・時刻の欠損値として適切に処理しているためです。
errors='coerce'
で NaT
を生成した後、これらの欠損値をどのように扱うかは分析の目的によって異なります。
- 欠損値を含む行/列を削除:
df.dropna(subset=['date_column'])
- 欠損値を補完:
df['date_column'].fillna(...)
(例: 前後の値で補完、特定の値で補完など) - 欠損値を特定のフラグとして扱う:
df['is_valid_date'] = df['date_column'].notna()
のようなフラグ列を作成する。
NaT
は to_datetime
を使ったデータクレンジングプロセスで重要な役割を果たします。
実践的な例: CSVからの日付列変換
実際のデータ分析では、CSVファイルなどからデータを読み込み、その中の日付列を変換することがよくあります。
以下のような sales.csv
ファイルがあると仮定します。
csv
OrderID,OrderDate,ProductName,Price
1001,2023-10-27,Laptop,1200
1002,2023/10/28,Keyboard,75
1003,2023年10月29日,Mouse,25
1004,invalid-date,Monitor,300
1005,2023-11-01,Webcam,50
1006,2023-02-30,Speaker,150
このデータを読み込み、OrderDate
列を日付・時刻型に変換する例を考えます。
“`python
import pandas as pd
import io
サンプルCSVデータを文字列として用意
csv_data = “””OrderID,OrderDate,ProductName,Price
1001,2023-10-27,Laptop,1200
1002,2023/10/28,Keyboard,75
1003,2023年10月29日,Mouse,25
1004,invalid-date,Monitor,300
1005,2023-11-01,Webcam,50
1006,2023-02-30,Speaker,150
“””
StringIOを使って仮想的にファイルから読み込む
実際のファイルの場合は pd.read_csv(‘sales.csv’)
df = pd.read_csv(io.StringIO(csv_data))
print(“読み込み直後のDataFrame:”)
print(df)
print(“\nデータ型:”)
print(df.dtypes)
OrderDate列をto_datetimeで変換
エラー値は NaT に変換
format は指定せず、to_datetime の自動解釈と infer_datetime_format=True に頼る
ただし、この例では複数の書式が混在しているため、format指定なし or infer=True だけでは全てを正しく変換できない可能性がある
errors=’coerce’ を使うことで、変換できなかったものが NaT になることを確認できる
df[‘OrderDate’] = pd.to_datetime(df[‘OrderDate’], errors=’coerce’, infer_datetime_format=True)
print(“\n変換後のDataFrame:”)
print(df)
print(“\n変換後のデータ型:”)
print(df.dtypes)
print(“\nNaT (欠損値) の確認:”)
print(df[‘OrderDate’].isna())
print(f”\n欠損値の数: {df[‘OrderDate’].isna().sum()}”)
有効な日付のみ抽出して表示
print(“\n有効な日付の行のみ:”)
print(df.dropna(subset=[‘OrderDate’]))
日付に基づいた操作の例:月を取り出す (NaTの箇所は NaN になる)
print(“\n月の抽出:”)
print(df[‘OrderDate’].dt.month)
日付でソート
print(“\nOrderDateでソート:”)
print(df.sort_values(‘OrderDate’))
特定の日付以降でフィルタリング
print(“\n2023年11月1日以降の注文:”)
print(df[df[‘OrderDate’] >= ‘2023-11-01’]) # 文字列での比較もto_datetimeが自動変換して比較してくれる場合があるが、Timestampで比較するのが安全
print(df[df[‘OrderDate’] >= pd.Timestamp(‘2023-11-01’)]) # こちらがより推奨される
“`
実行結果 (環境によって細かい出力は変わる可能性があります):
“`
読み込み直後のDataFrame:
OrderID OrderDate ProductName Price
0 1001 2023-10-27 Laptop 1200
1 1002 2023/10/28 Keyboard 75
2 1003 2023年10月29日 Mouse 25
3 1004 invalid-date Monitor 300
4 1005 2023-11-01 Webcam 50
5 1006 2023-02-30 Speaker 150
データ型:
OrderID int64
OrderDate object
ProductName object
Price int64
dtype: object
変換後のDataFrame:
OrderID OrderDate ProductName Price
0 1001 2023-10-27 Laptop 1200
1 1002 2023-10-28 Keyboard 75
2 1003 NaT Mouse 25
3 1004 NaT Monitor 300
4 1005 2023-11-01 Webcam 50
5 1006 NaT Speaker 150
変換後のデータ型:
OrderID int64
OrderDate datetime64[ns]
ProductName object
Price int64
dtype: object
NaT (欠損値) の確認:
0 False
1 False
2 True
3 True
4 False
5 True
Name: OrderDate, dtype: bool
欠損値の数: 3
有効な日付の行のみ:
OrderID OrderDate ProductName Price
0 1001 2023-10-27 Laptop 1200
1 1002 2023-10-28 Keyboard 75
4 1005 2023-11-01 Webcam 50
月の抽出:
0 10.0
1 10.0
2 NaN
3 NaN
4 11.0
5 NaN
Name: OrderDate, dtype: float64
OrderDateでソート:
OrderID OrderDate ProductName Price
0 1001 2023-10-27 Laptop 1200
1 1002 2023-10-28 Keyboard 75
4 1005 2023-11-01 Webcam 50
2 1003 NaT Mouse 25
3 1004 NaT Monitor 300
5 1006 NaT Speaker 150
2023年11月1日以降の注文:
OrderID OrderDate ProductName Price
4 1005 2023-11-01 Webcam 50
“`
この例では、errors='coerce'
を使用したため、'invalid-date'
、'2023年10月29日'
(infer_datetime_format=True
では日本語書式がパースできない可能性がある)、'2023-02-30'
(無効な日付) の3つが NaT
に変換されました。このように、errors='coerce'
はデータクレンジングの第一歩として非常に有効です。
もし、特定の書式で変換したい場合は、複数の to_datetime
呼び出しを組み合わせたり、事前に文字列処理で書式を統一したりする必要があります。例えば、'2023年10月29日'
を変換したい場合は、format='%Y年%m月%d日'
を指定した変換を試みる必要があります。
“`python
複数の書式が混在する場合の対処例(より複雑な処理が必要になることが多い)
まず特定の書式でパースを試み、失敗したら別の書式を試みる、などのロジックが必要
これはto_datetime単体では難しく、カスタム関数やapplyなどを使う必要が出てくる
簡単な例として、特定の書式だけを指定してerrors=’coerce’を使ってみる
この場合、指定したフォーマットに合わないものはすべてNaTになります
df[‘OrderDate_strict’] = pd.to_datetime(df[‘OrderDate’], format=’%Y-%m-%d’, errors=’coerce’)
print(“\nformat=’%Y-%m-%d’ で変換(失敗はNaTへ):”)
print(df[‘OrderDate_strict’])
実行結果:
format=’%Y-%m-%d’ で変換(失敗はNaTへ):
0 2023-10-27
1 NaT
2 NaT
3 NaT
4 2023-11-01
5 NaT
Name: OrderDate_strict, dtype: datetime64[ns]
``
format
このように、を指定すると厳密になりますが、それ以外の書式は変換できなくなります。実務では、混在する書式を特定し、それぞれに対応する
format` で変換するロジックを組むか、データソース側で書式を統一するなどの対応が必要になります。
トラブルシューティング:よくあるエラーと解決策
to_datetime
を使用する際に遭遇しやすいエラーとその解決策について説明します。
1. ValueError: Unknown string format:
または ValueError: time data '...' does not match format '%...'
原因: 入力文字列が、to_datetime
が自動的に認識できる標準的な書式ではないか、format
引数で指定した書式と入力文字列の実際の書式が一致していない場合に発生します。
解決策:
- 入力文字列の正確な書式を確認します。
format
引数を使用して、入力文字列の書式と完全に一致する書式コードを指定します。- 複数の書式が混在している場合は、
errors='coerce'
を使用して、変換できなかった値(NaT
になる)を確認し、問題のあるデータを特定します。その後、問題のあるデータの書式を修正するか、複数のto_datetime
呼び出しやカスタム関数で対応します。 infer_datetime_format=True
を試してみる(ただし、必ず成功するわけではない)。
2. ValueError: day is out of range for month
原因: 入力文字列が日付として無効な値を含んでいる場合に発生します。例: '2023-02-30'
(2月は30日がない), '2023-04-31'
(4月は31日がない), '2023-13-01'
(13月はない)。
解決策:
errors='coerce'
を使用します。これにより、無効な日付はNaT
に変換され、エラーは発生しなくなります。その後、NaT
となったデータを確認し、修正や削除などの対応を行います。- 元のデータソースを確認し、日付データが無効になっている原因を特定して修正します。
3. TypeError: unconvertible type
原因: to_datetime
がサポートしていない型のデータが入力として渡された場合に発生します。
解決策: 入力データが文字列、数値、リスト、Series、DataFrameのいずれかであることを確認します。異なる型の場合は、適切な前処理を行ってから to_datetime
に渡します。
4. ValueError: Tz-aware datetime cannot be converted to naive datetime without specifying ascii=True or utc=True
(バージョンによっては異なるメッセージ)
原因: タイムゾーン情報を持つ aware な日時オブジェクトを、タイムゾーン情報を持たない naive なオブジェクトに変換しようとした場合に発生します。to_datetime
の出力がデフォルトで naive (datetime64[ns]) であることと関連して、タイムゾーン付きの文字列をパースする際に発生しやすいです。
解決策:
to_datetime
の引数でタイムゾーン情報(%z
など)を正しくパースできるformat
を指定すると、出力が aware になることがあります。- または、一旦 naive な日時として
to_datetime
でパースした後、.tz_localize()
や.tz_convert()
メソッドを使用してタイムゾーンを適切に扱うようにします。タイムゾーン付きのデータを扱う場合は、to_datetime
だけでなくこれらのメソッドと組み合わせて使用するのが一般的です。
5. パフォーマンスが遅い
原因: 大量のデータを変換しているにも関わらず、format
引数を指定していないか、infer_datetime_format=True
がうまく機能していない可能性があります。
解決策:
- 入力文字列の書式を特定し、
format
引数で明示的に指定します。これが最も確実なパフォーマンス向上策です。 - 入力データに重複する値が多い場合は、
cache=True
を試してみます。
これらのトラブルシューティングの知識は、to_datetime
をスムーズに使いこなす上で非常に役立ちます。エラーメッセージをよく読み、原因に応じた解決策を試すことが重要です。
まとめ
Pandasの to_datetime
関数は、データ分析における日付・時刻データの扱いの基礎となる非常に重要なツールです。文字列や数値など、様々な形式で格納された日付・時刻データを、Pandasが効率的に処理できる datetime64[ns]
型(Timestamp
または DateTimeIndex
)に変換することで、強力な時系列分析機能や日付に基づいたデータ操作を活用できるようになります。
この記事では、to_datetime
の基本的な使い方から、以下の重要な引数とその活用方法について詳しく解説しました。
arg
: 変換対象を指定errors
: 変換エラー発生時の挙動 ('raise'
,'ignore'
,'coerce'
) を制御。特に'coerce'
はデータクレンジングで非常に有用。format
: 入力文字列の書式を明示的に指定。パフォーマンス向上と曖昧さ解消に不可欠。様々な書式コードを解説しました。unit
,origin
: 数値データ(Unixエポックタイムや基準日からの経過時間)を変換。dayfirst
,yearfirst
: 曖昧な日付の解釈を制御(format
指定が優先される)。infer_datetime_format
: 書式自動推測(パフォーマンス向上につながる場合がある)。cache
: 重複値の変換結果をキャッシュ(重複が多い場合に有効)。
また、日付・時刻データの欠損値である NaT
についても触れ、errors='coerce'
を使った際の NaT
の生成と、その後のデータ処理における NaT
の扱いについて説明しました。
実践的な例として、CSVファイルから読み込んだ複数の書式を含む日付列を変換するケースを取り上げ、errors='coerce'
を使った簡単なデータクレンジングのワークフローを示しました。
最後に、to_datetime
を使用する際によく発生するエラーとその解決策についても解説しました。エラーメッセージの意味を理解し、適切な引数や前処理方法を選択することで、多くの問題を解決できるはずです。
データ分析の現場では、日付・時刻データの形式は常にきれいであるとは限りません。本記事で解説した to_datetime
の様々な機能と引数を理解し、データの特性に合わせて適切に使い分けることで、効率的かつ正確なデータ前処理が可能になります。ぜひ、実際のデータでこれらの機能を試してみてください。