記事の概要
pandasで時系列データを扱う時に長時間の欠損値や異常値があった場合、補間ではどうにもできないので、既にあるDataFrameから日付を指定して抽出したい事がありました。
本記事はDataFrameのカラムに時間情報がある時に日付(あるいは時刻)で範囲指定して時系列データを抽出する方法を備忘録がてら記述したものになります。
また扱うテーブルの情報によって変換の仕方が違うためそれに関しても言及しています。
DataFrameから日時データの抽出
データの下準備になります。長いので必要ない場合この節は飛ばしても大丈夫だと思います。
import pandas as pd
import numpy as np
import datetime as dt
本記事では上記の3つのライブラリを使用しています。
標準形式で記載された日付カラムが存在する場合
用いるDataFrame
df1 = pd.DataFrame({
'来店者数' : [120, 114, np.nan, 105, 128, 98],
'仕入れ数' : [140, np.nan, 100, 130, 120, np.nan],
'日付' : ['2019-05-01', '2019-05-02', '2019-05-03', '2019-05-04', '2019-05-05', '2019-05-06']},
)
来店者数 | 仕入れ数 | 日付 | |
---|---|---|---|
0 | 120.0 | 140.0 | 2019-05-01 |
1 | 114.0 | NaN | 2019-05-02 |
2 | NaN | 100.0 | 2019-05-03 |
3 | 105.0 | 130.0 | 2019-05-04 |
4 | 128.0 | 120.0 | 2019-05-05 |
5 | 98.0 | NaN | 2019-05-06 |
print(df1.dtypes)
来店者数 float64
仕入れ数 float64
日付 object
dtype: object
"日付"カラムののデータ型はobjectです。
通常csvやtsvファイルをpandasのread_csvで読み込んだ場合はこのようなDataFrameになっていると思います。
object型からdatetime型への変換
df1['日付'] = pd.to_datetime(df1['日付'])
print(df1.dtypes)
来店者数 float64
仕入れ数 float64
日付 datetime64[ns]
dtype: object
ちゃんと"日付"カラムがdatetime64[ns]型になっていますね。
datetimeの標準な形になっていればpandasのto_datetimeメソッドをそのまま使えばよいです。
標準形式でない日付カラムが存在する場合
用いるDataFrame
df2 = pd.DataFrame({
'乗車人数' : [150, 180, 140, 160, 161, 145],
'日時' : ['2019/5月1日-12:00', '2019/5月1日-12:50', '2019/5月1日-13:30', '2019/5月1日-13:59', '2019/5月1日-14:00', '2019/5月1日-14:30']},
)
乗車人数 | 日時 | |
---|---|---|
0 | 150 | 2019/5月1日-12:00 |
1 | 180 | 2019/5月1日-12:50 |
2 | 140 | 2019/5月1日-13:30 |
3 | 160 | 2019/5月1日-13:59 |
4 | 161 | 2019/5月1日-14:00 |
5 | 145 | 2019/5月1日-14:30 |
まずこんな形式の日時データはないと思いますが、"日時"カラムの中身の形式がぐちゃぐちゃの場合でも変換可能です。
object型からdatetime型への変換
df2['日時'] = pd.to_datetime(df2['日時'], format='%Y/%m月%d日-%H:%M')
print(df2.dtypes)
乗車人数 int64
日時 datetime64[ns]
dtype: object
フォーマットを指定して変換するにはto_datetimeメソッドのformat引数に対応する文字列を渡してあげればよいです。
主なDirective(指示語)は以下の表のとおりです。1
Directive | 意味 |
---|---|
%Y | 西暦4桁の10進法表記 |
%y | 西暦下2桁の10進法表記 |
%m | 月の10進法表記 |
%d | 日の10進法表記 |
%H | 時(24時間表記)の10進法表記 |
%I | 時(12時間表記)の10進法表記 |
%M | 分の10進法表記 |
%S | 秒の10進法表記 |
%p | 'AM'か'PM'かの文字列 |
%w | 曜日の10進法表記 [0(日曜), 6] |
%A | 曜日名の文字列を返す ['Sunday', 'Saturday'] |
%% | '%'を表す |
年月日等の情報が別々のカラムに保持されている場合
用いるDataFrame
df3 = pd.DataFrame({
'来店者数' : [120, 114, 123, 105, 128, 98],
'仕入れ数' : [140, 110, 100, 130, 120, 100],
'年' : [2019, 2019, 2019, 2019, 2019, 2019],
'月' : [5, 5 ,5 ,6, 6, 6],
'日' : [29, 30, 31, 1, 2, 3]}
)
来店者数 | 仕入れ数 | 年 | 月 | 日 | |
---|---|---|---|---|---|
0 | 120 | 140 | 2019 | 5 | 29 |
1 | 114 | 110 | 2019 | 5 | 30 |
2 | 123 | 100 | 2019 | 5 | 31 |
3 | 105 | 130 | 2019 | 6 | 1 |
4 | 128 | 120 | 2019 | 6 | 2 |
5 | 98 | 100 | 2019 | 6 | 3 |
こんな感じのデータは結構ありそう。
この例の場合"年","月","日"それぞれのカラムの型はint64になっているかと思います。実際
print(df3.dtypes)
来店者数 int64
仕入れ数 int64
年 int64
月 int64
日 int64
dtype: object
このような結果となりました。
object型からdatetime型への変換
データ型がint64なので、一旦string(object)に変換してからまとめてto_datetimeメソッドに引き渡してdatetime64型にします。
df3['日付'] = pd.to_datetime(df3['年'].astype(str)+'-'+ df3['月'].astype(str)+'-'+ df3['日'].astype(str))
来店者数 | 仕入れ数 | 年 | 月 | 日 | 日付 | |
---|---|---|---|---|---|---|
0 | 120 | 140 | 2019 | 5 | 29 | 2019-05-29 |
1 | 114 | 110 | 2019 | 5 | 30 | 2019-05-30 |
2 | 123 | 100 | 2019 | 5 | 31 | 2019-05-31 |
3 | 105 | 130 | 2019 | 6 | 1 | 2019-06-01 |
4 | 128 | 120 | 2019 | 6 | 2 | 2019-06-02 |
5 | 98 | 100 | 2019 | 6 | 3 | 2019-06-03 |
print(df3.dtypes)
来店者数 int64
仕入れ数 int64
年 int64
月 int64
日 int64
日付 datetime64[ns]
dtype: object
変換出来ました。新たに"日付"カラムが生成されているのがわかります。
データ型もちゃんとdatetime64[ns]型になっています。
データ分析において日時の情報を入力する場合はいいですが、もし使わないのであれば"年","月","日"の各カラムは削除してしまってもいいと思います。
df3 = df3.drop(columns=['年','月','日'])
来店者数 | 仕入れ数 | 日付 | |
---|---|---|---|
0 | 120 | 140 | 2019-05-29 |
1 | 114 | 110 | 2019-05-30 |
2 | 123 | 100 | 2019-05-31 |
3 | 105 | 130 | 2019-06-01 |
4 | 128 | 120 | 2019-06-02 |
5 | 98 | 100 | 2019-06-03 |
以上の処理でdrop可能です。
期間を範囲指定する
ここから本題となります。
df1から2019/5/3より最近のレコードだけ取り出す場合は
df1[df1['日付'] > dt.datetime(2019,5,3)]
のように記述します。抽出結果は以下の通りになります。
来店者数 | 仕入れ数 | 日付 | |
---|---|---|---|
3 | 105.0 | 130.0 | 2019-05-04 |
4 | 128.0 | 120.0 | 2019-05-05 |
5 | 98.0 | NaN | 2019-05-06 |
上界と下界を指定する場合は、それぞれの条件を括弧でくくり、'&'で繋げます。
たとえばdf1から2019/5/3以降かつ2019/5/6より前のレコードだけ取り出す場合は
df1[(df1['日付'] >= dt.datetime(2019,5,3)) & (df1['日付'] < dt.datetime(2019,5,6))]
のように記述すればよいです。抽出結果は以下の通りになります。
来店者数 | 仕入れ数 | 日付 | |
---|---|---|---|
2 | NaN | 100.0 | 2019-05-03 |
3 | 105.0 | 130.0 | 2019-05-04 |
4 | 128.0 | 120.0 | 2019-05-05 |
もちろん時間や分、秒まで指定可能です。
df2から2019/5/1の12:30より後から同日の14:00より前までのレコードだけ取り出す場合は
df2[(df2['日時'] > dt.datetime(2019,5,1,12,30)) & (df2['日時'] < dt.datetime(2019,5,1,14))]
のように書きます。抽出結果は以下の通りです。
乗車人数 | 日時 | |
---|---|---|
1 | 180 | 2019-05-01 12:50:00 |
2 | 140 | 2019-05-01 13:30:00 |
3 | 160 | 2019-05-01 13:59:00 |
他の方法(index)
これまではカラムに保存された日時のデータをdatetimeの型に変換してカラムの上書きをしてきました。
これらとは別の方法として、DataFrameのindexに変換後の日時を保持してインデックス参照するものがあります。
この時期間の範囲指定の方法が少し違います。
日時の情報を保持しつつカラムから削除したい場合にこの方法は有効かと思われます。
たとえばdf2において
df2.index = pd.to_datetime(df2['日時'], format='%Y/%m月%d日-%H:%M').values
df2 = df2.drop(columns='日時')
とすればindexに日時データが保持され、元の"日時"カラムは削除されます。
乗車人数 | |
---|---|
2019-05-01 12:00:00 | 150 |
2019-05-01 12:50:00 | 180 |
2019-05-01 13:30:00 | 140 |
2019-05-01 13:59:00 | 160 |
2019-05-01 14:00:00 | 161 |
2019-05-01 14:30:00 | 145 |
この時indexで機関の範囲指定をして抽出する方法は以下の通りです。
df2['2019-05-01 12:30' : '2019-05-01 14:00']
乗車人数 | |
---|---|
2019-05-01 12:50:00 | 180 |
2019-05-01 13:30:00 | 140 |
2019-05-01 13:59:00 | 160 |
2019-05-01 14:00:00 | 161 |
上記の方法を用いる場合、条件に境界値は含まれるので注意!