日時型データの前処理#
日時型データとは?#
データ分析、とりわけ時系列データ解析において、日時型の取り扱いは避けては通れぬ道です。
日時型とは、その名の通り日付/時間についての情報を持っているデータであり、データ解析において無限の可能性を秘めている存在です。
例えば1つのデータから、年/月/日/時刻の1要素を取り出すことももちろん、月を季節に、日を月初/月中/月末に、時刻を朝/昼/夕/夜に、また年月日を平日/休日に変換するなど、さまざまなパターンでカテゴリ値に変換することもできます。
さらに、2つの日時型データについて、その時間差(日数差や週数差など)を加減算演算で求めることができます。
時系列データ解析では、データの季節性を考慮することが定石です。日時型データをうまく使いこなすことで、適切な特徴量が見えてくるかもしれません。まさに特徴量エンジニアリング。
データの読み込み#
import warnings
warnings.filterwarnings("ignore")
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
pd.set_option("display.max_rows", 10)
pd.set_option("display.max_columns", 10)
fpath = "../data/2020413_raw.csv"
df = pd.read_csv(fpath, index_col=0, parse_dates=True, encoding="shift-jis")
本項の説明では、日時が格納されているIndexと、任意の1列のみで十分なので、それ以外の列はdropします。
df = df.drop(columns=df.columns.difference(["気化器圧力_PV"]))
df
気化器圧力_PV | |
---|---|
2020-04-13 00:00:00 | 127.983294 |
2020-04-13 00:00:01 | 128.262272 |
2020-04-13 00:00:02 | 127.899077 |
2020-04-13 00:00:03 | 127.622218 |
2020-04-13 00:00:04 | 127.577243 |
... | ... |
2020-04-13 23:59:56 | 128.403376 |
2020-04-13 23:59:57 | 128.588067 |
2020-04-13 23:59:58 | 127.902731 |
2020-04-13 23:59:59 | 127.481159 |
2020-04-14 00:00:00 | 127.047655 |
86401 rows × 1 columns
日時型、日付型への変換#
日時型には Timestamp
型や DateTime
型などがあります。
データを読み込んだ時点で日時型になっている場合は問題ないですが、文字列やUNIXTIME(1970/1/1から何秒経過したか?)として保持されている場合は、日時型に変換する必要があります。
日時型以外に時間に関する代表的なデータ型として、日付型もあります。
これは文字通り、日付のみの情報を保持していて、平休日変換など日単位の変換に有用です。
今回読み込んだデータは、読み込んだ時点で日時型になっているようです。
df.index
DatetimeIndex(['2020-04-13 00:00:00', '2020-04-13 00:00:01',
'2020-04-13 00:00:02', '2020-04-13 00:00:03',
'2020-04-13 00:00:04', '2020-04-13 00:00:05',
'2020-04-13 00:00:06', '2020-04-13 00:00:07',
'2020-04-13 00:00:08', '2020-04-13 00:00:09',
...
'2020-04-13 23:59:51', '2020-04-13 23:59:52',
'2020-04-13 23:59:53', '2020-04-13 23:59:54',
'2020-04-13 23:59:55', '2020-04-13 23:59:56',
'2020-04-13 23:59:57', '2020-04-13 23:59:58',
'2020-04-13 23:59:59', '2020-04-14 00:00:00'],
dtype='datetime64[ns]', length=86401, freq=None)
後学のため、あえて文字型に変換し、その後日時型や日付型に変換してみます。
df_index_str = df.index.astype(str)
# to_datetime関数で、datetime64[ns]型に変換
df_index_datetime64 = pd.to_datetime(df_index_str, format="%Y-%m-%d %H:%M:%S")
# to_datetime関数で、date型に変換
df_index_date = pd.to_datetime(df_index_str, format="%Y-%m-%d %H:%M:%S").date
df_index_str
Index(['2020-04-13 00:00:00', '2020-04-13 00:00:01', '2020-04-13 00:00:02',
'2020-04-13 00:00:03', '2020-04-13 00:00:04', '2020-04-13 00:00:05',
'2020-04-13 00:00:06', '2020-04-13 00:00:07', '2020-04-13 00:00:08',
'2020-04-13 00:00:09',
...
'2020-04-13 23:59:51', '2020-04-13 23:59:52', '2020-04-13 23:59:53',
'2020-04-13 23:59:54', '2020-04-13 23:59:55', '2020-04-13 23:59:56',
'2020-04-13 23:59:57', '2020-04-13 23:59:58', '2020-04-13 23:59:59',
'2020-04-14 00:00:00'],
dtype='object', length=86401)
df_index_datetime64
DatetimeIndex(['2020-04-13 00:00:00', '2020-04-13 00:00:01',
'2020-04-13 00:00:02', '2020-04-13 00:00:03',
'2020-04-13 00:00:04', '2020-04-13 00:00:05',
'2020-04-13 00:00:06', '2020-04-13 00:00:07',
'2020-04-13 00:00:08', '2020-04-13 00:00:09',
...
'2020-04-13 23:59:51', '2020-04-13 23:59:52',
'2020-04-13 23:59:53', '2020-04-13 23:59:54',
'2020-04-13 23:59:55', '2020-04-13 23:59:56',
'2020-04-13 23:59:57', '2020-04-13 23:59:58',
'2020-04-13 23:59:59', '2020-04-14 00:00:00'],
dtype='datetime64[ns]', length=86401, freq=None)
df_index_date
array([datetime.date(2020, 4, 13), datetime.date(2020, 4, 13),
datetime.date(2020, 4, 13), ..., datetime.date(2020, 4, 13),
datetime.date(2020, 4, 13), datetime.date(2020, 4, 14)],
dtype=object)
なお、formatオプションはデータの日時表記に即した形式で記載する必要があります。
ここでは、よく使う形式をいくつか列挙するにとどめ、詳細は参考文献 8.1.8. strftime() と strptime() の振る舞い に譲ることとします。
ディレクティブ |
意味 |
---|---|
%y |
0埋めした10進数で表記した世紀無しの年 |
%Y |
西暦(4桁)を10進数で表記した年 |
%m |
0埋めした10進数で表記した月 |
%d |
0埋めした10進数で表記した月中の日 |
%H |
0埋めした10進数で表記した24時間表記の時 |
%I |
0埋めした10進数で表記した12時間表記の時 |
%M |
0埋めした10進数で表記した分 |
%S |
0埋めした10進数で表記した秒 |
年/月/日/時/分/秒/曜日への変換#
日時型から特定の日時要素を取り出す作業はよく利用されます。
例えば月ごとの売上を計算するために月要素を取り出す操作などです。
特定の日時要素は文字列から正規表現などを使って取り出すこともできますが、日時型に変換して取り出す方が簡単に記述できます。
# 年を取得
df.index.year
Int64Index([2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020,
...
2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020],
dtype='int64', length=86401)
# 月を取得
df.index.month
Int64Index([4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
...
4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
dtype='int64', length=86401)
# 日を取得
df.index.day
Int64Index([13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
...
13, 13, 13, 13, 13, 13, 13, 13, 13, 14],
dtype='int64', length=86401)
# 曜日{月:0,火:1,水:2,木:3,金:4,土:5,日:6}を数値で取得
df.index.dayofweek
Int64Index([0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
...
0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
dtype='int64', length=86401)
# 時刻の時を取得
df.index.hour
Int64Index([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
...
23, 23, 23, 23, 23, 23, 23, 23, 23, 0],
dtype='int64', length=86401)
# 時刻の分を取得
df.index.minute
Int64Index([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
...
59, 59, 59, 59, 59, 59, 59, 59, 59, 0],
dtype='int64', length=86401)
# 時刻の秒を取得
df.index.second
Int64Index([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
...
51, 52, 53, 54, 55, 56, 57, 58, 59, 0],
dtype='int64', length=86401)
# 指定したフォーマットの文字列に変換
df.index.strftime("%Y/%m/%d %Hh%Mm%Ss")
Index(['2020/04/13 00h00m00s', '2020/04/13 00h00m01s', '2020/04/13 00h00m02s',
'2020/04/13 00h00m03s', '2020/04/13 00h00m04s', '2020/04/13 00h00m05s',
'2020/04/13 00h00m06s', '2020/04/13 00h00m07s', '2020/04/13 00h00m08s',
'2020/04/13 00h00m09s',
...
'2020/04/13 23h59m51s', '2020/04/13 23h59m52s', '2020/04/13 23h59m53s',
'2020/04/13 23h59m54s', '2020/04/13 23h59m55s', '2020/04/13 23h59m56s',
'2020/04/13 23h59m57s', '2020/04/13 23h59m58s', '2020/04/13 23h59m59s',
'2020/04/14 00h00m00s'],
dtype='object', length=86401)
日時差への変換#
日時型データが複数ある場合は、日時型データ間の日時差(年数/月数/週数/日数/時間差)の取得が求められることはよくあります。
例えば、Webにアクセスしてから商品購入までにかかる時間や宿の予約日と宿泊日の日数差などを知りたい場合などです。
日時の差分といっても明確に定義を決めなければ、値の意味が分からなくなってしまいます。
例えば、12:45:59と12:46:00の分の差分といっても、秒以下を無視して1分と考えるべきなのか、秒以下を考慮して1/60=0.0166…分と考えるべきなのか、ケースによって慎重に検討しなければなりません。
しかし、一般に年と月は前者で扱うことがほとんどです。というのも、そもそも年と月は長さが一定ではなく、単位として厳密に使用するには不十分だからです。
本項では便宜的に日時の系列が1本しかないので、インデックスに保持されている日時型を適当に並び替え、もとの日時型との差を確認することとします。
また本項で読み込んだデータの範囲は 2020-04-13 00:00:00 〜 2019-04-14 00:00:00 であるため、当たり前ですが年/月の差は常に0となります。念のため。
# 適当にシャッフルしたDataFrameを用意
df_shuffle = df.sample(frac=1).copy()
# 年の差分を計算(月以下の日時要素は考慮しない)
df_shuffle.index.year - df.index.year
Int64Index([0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
...
0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
dtype='int64', length=86401)
# 月の差分を取得(日以下の日時要素は考慮しない)
(df_shuffle.index.year * 12 + df_shuffle.index.month) - (
df.index.year * 12 + df.index.month
)
Int64Index([0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
...
0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
dtype='int64', length=86401)
# 日単位で差分を計算
(df_shuffle.index - df.index).astype("timedelta64[D]")
Int64Index([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
...
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
dtype='int64', length=86401)
# 時単位で差分を計算
(df_shuffle.index - df.index).astype("timedelta64[h]")
Int64Index([ 23, 17, 20, 5, 17, 3, 1, 3, 0, 15,
...
-17, -2, -12, -14, -4, -3, -18, -1, -21, -9],
dtype='int64', length=86401)
# 分単位で差分を計算
(df_shuffle.index - df.index).astype("timedelta64[m]")
Int64Index([ 1421, 1077, 1223, 316, 1073, 213, 117, 210, 34,
904,
...
-1014, -81, -668, -805, -236, -129, -1051, -50, -1214,
-511],
dtype='int64', length=86401)
# 秒単位で差分を計算
(df_shuffle.index - df.index).astype("timedelta64[s]")
Int64Index([ 85270, 64649, 73400, 18981, 64390, 12839, 7062, 12619,
2047, 54253,
...
-60836, -4802, -40021, -48292, -14145, -7688, -63043, -2946,
-72829, -30660],
dtype='int64', length=86401)
日時型の加減#
分析においては、日時型のデータを特定の期間だけずらしたい場合はよくあります。
例えば、宿泊予約した日までの直近30日間に予約を何回したかを調べるためには、対象の日時データを予約した日から30日前までに絞り込み、予約回数をカウントする必要があります。
前節でも触れた様に、期間を指定する際に年/月を使用すると、条件によって長さが異なる場合があります。
これにより、データ値に依存して期間が変動する様な処理になってしまう恐れがあるため、年/月を使用する際には留意する必要があります。
pandas
では組み込みモジュールの datetime
を併用することで日時型の増減を簡潔に行うことができます。
# timedelta用にdatetimeライブラリを読み込み
import datetime
# 日時型データに1日加える
df.index + datetime.timedelta(days=1)
DatetimeIndex(['2020-04-14 00:00:00', '2020-04-14 00:00:01',
'2020-04-14 00:00:02', '2020-04-14 00:00:03',
'2020-04-14 00:00:04', '2020-04-14 00:00:05',
'2020-04-14 00:00:06', '2020-04-14 00:00:07',
'2020-04-14 00:00:08', '2020-04-14 00:00:09',
...
'2020-04-14 23:59:51', '2020-04-14 23:59:52',
'2020-04-14 23:59:53', '2020-04-14 23:59:54',
'2020-04-14 23:59:55', '2020-04-14 23:59:56',
'2020-04-14 23:59:57', '2020-04-14 23:59:58',
'2020-04-14 23:59:59', '2020-04-15 00:00:00'],
dtype='datetime64[ns]', length=86401, freq=None)
# 日時型データに1時間加える
df.index + datetime.timedelta(hours=1)
DatetimeIndex(['2020-04-13 01:00:00', '2020-04-13 01:00:01',
'2020-04-13 01:00:02', '2020-04-13 01:00:03',
'2020-04-13 01:00:04', '2020-04-13 01:00:05',
'2020-04-13 01:00:06', '2020-04-13 01:00:07',
'2020-04-13 01:00:08', '2020-04-13 01:00:09',
...
'2020-04-14 00:59:51', '2020-04-14 00:59:52',
'2020-04-14 00:59:53', '2020-04-14 00:59:54',
'2020-04-14 00:59:55', '2020-04-14 00:59:56',
'2020-04-14 00:59:57', '2020-04-14 00:59:58',
'2020-04-14 00:59:59', '2020-04-14 01:00:00'],
dtype='datetime64[ns]', length=86401, freq=None)
# 日時型データに1分加える
df.index + datetime.timedelta(minutes=1)
DatetimeIndex(['2020-04-13 00:01:00', '2020-04-13 00:01:01',
'2020-04-13 00:01:02', '2020-04-13 00:01:03',
'2020-04-13 00:01:04', '2020-04-13 00:01:05',
'2020-04-13 00:01:06', '2020-04-13 00:01:07',
'2020-04-13 00:01:08', '2020-04-13 00:01:09',
...
'2020-04-14 00:00:51', '2020-04-14 00:00:52',
'2020-04-14 00:00:53', '2020-04-14 00:00:54',
'2020-04-14 00:00:55', '2020-04-14 00:00:56',
'2020-04-14 00:00:57', '2020-04-14 00:00:58',
'2020-04-14 00:00:59', '2020-04-14 00:01:00'],
dtype='datetime64[ns]', length=86401, freq=None)
# 日時型データに1秒加える
df.index + datetime.timedelta(seconds=1)
DatetimeIndex(['2020-04-13 00:00:01', '2020-04-13 00:00:02',
'2020-04-13 00:00:03', '2020-04-13 00:00:04',
'2020-04-13 00:00:05', '2020-04-13 00:00:06',
'2020-04-13 00:00:07', '2020-04-13 00:00:08',
'2020-04-13 00:00:09', '2020-04-13 00:00:10',
...
'2020-04-13 23:59:52', '2020-04-13 23:59:53',
'2020-04-13 23:59:54', '2020-04-13 23:59:55',
'2020-04-13 23:59:56', '2020-04-13 23:59:57',
'2020-04-13 23:59:58', '2020-04-13 23:59:59',
'2020-04-14 00:00:00', '2020-04-14 00:00:01'],
dtype='datetime64[ns]', length=86401, freq=None)
日時型の範囲指定#
勘の良い読者はお気づきかもしれませんが、前節日時型の加減でみたように、日時型を用いることでいくつかの算術演算を直感的に行うことができます。
先ほどは直感的な加減算を取り扱いましたが、同様にして大小(新旧)比較を行うこともできます。
これを利用することで、特定の日時範囲内のデータを簡潔に取り出すことができます。
この際、通常の比較演算と同様の感覚で、基準日時を含みたい場合は不等号に=を付し、含みたくない場合は不等号に=を付さないことで実現できます。
また &
や |
、および ~
といった論理演算子も使用することができます。
# 2020-04-13 12:00:00以前のデータを抽出
df[df.index <= datetime.datetime(2020, 4, 13, 12, 0, 0)]
気化器圧力_PV | |
---|---|
2020-04-13 00:00:00 | 127.983294 |
2020-04-13 00:00:01 | 128.262272 |
2020-04-13 00:00:02 | 127.899077 |
2020-04-13 00:00:03 | 127.622218 |
2020-04-13 00:00:04 | 127.577243 |
... | ... |
2020-04-13 11:59:56 | 128.593731 |
2020-04-13 11:59:57 | 128.527277 |
2020-04-13 11:59:58 | 127.677524 |
2020-04-13 11:59:59 | 127.217226 |
2020-04-13 12:00:00 | 127.760473 |
43201 rows × 1 columns
# 2020-04-13 11:00:00(境界含む) 〜 2020-04-13 13:00:00(境界含まない)の範囲のデータを抽出
df[
(datetime.datetime(2020, 4, 13, 11, 0, 0) <= df.index)
& (df.index < datetime.datetime(2020, 4, 13, 13, 0, 0))
]
気化器圧力_PV | |
---|---|
2020-04-13 11:00:00 | 127.898747 |
2020-04-13 11:00:01 | 127.238603 |
2020-04-13 11:00:02 | 127.298646 |
2020-04-13 11:00:03 | 127.665471 |
2020-04-13 11:00:04 | 127.964458 |
... | ... |
2020-04-13 12:59:55 | 128.285090 |
2020-04-13 12:59:56 | 127.881101 |
2020-04-13 12:59:57 | 127.212825 |
2020-04-13 12:59:58 | 127.515741 |
2020-04-13 12:59:59 | 128.501433 |
7200 rows × 1 columns
また本項で読み込んだデータの様にインデックスに日時型を用いている場合、直感的にインデックスをスライスすることもできます。
なお、この方法を用いる場合は境界は常に含まれることに注意です。
# 2020-04-13 11:00:00 〜 2020-04-13 13:00:00の範囲のデータを抽出
df["2020-04-13 11:00:00":"2020-04-13 13:00:00"]
気化器圧力_PV | |
---|---|
2020-04-13 11:00:00 | 127.898747 |
2020-04-13 11:00:01 | 127.238603 |
2020-04-13 11:00:02 | 127.298646 |
2020-04-13 11:00:03 | 127.665471 |
2020-04-13 11:00:04 | 127.964458 |
... | ... |
2020-04-13 12:59:56 | 127.881101 |
2020-04-13 12:59:57 | 127.212825 |
2020-04-13 12:59:58 | 127.515741 |
2020-04-13 12:59:59 | 128.501433 |
2020-04-13 13:00:00 | 128.800778 |
7201 rows × 1 columns
日時型の生成#
データに新しく時刻を付したい場合など、日時型データを生成したいことは多々あります。
これも文字列をゴニョゴニョ操作して実現することも可能ですが、pandasの date_range
を用いると、より簡潔に記述することができます。
# 生成範囲の開始日時と終了日時を指定できる。デフォルト設定では1日間隔で生成される。
pd.date_range("2020-04-01 12:00:00", "2020-04-30 12:00:00")
DatetimeIndex(['2020-04-01 12:00:00', '2020-04-02 12:00:00',
'2020-04-03 12:00:00', '2020-04-04 12:00:00',
'2020-04-05 12:00:00', '2020-04-06 12:00:00',
'2020-04-07 12:00:00', '2020-04-08 12:00:00',
'2020-04-09 12:00:00', '2020-04-10 12:00:00',
'2020-04-11 12:00:00', '2020-04-12 12:00:00',
'2020-04-13 12:00:00', '2020-04-14 12:00:00',
'2020-04-15 12:00:00', '2020-04-16 12:00:00',
'2020-04-17 12:00:00', '2020-04-18 12:00:00',
'2020-04-19 12:00:00', '2020-04-20 12:00:00',
'2020-04-21 12:00:00', '2020-04-22 12:00:00',
'2020-04-23 12:00:00', '2020-04-24 12:00:00',
'2020-04-25 12:00:00', '2020-04-26 12:00:00',
'2020-04-27 12:00:00', '2020-04-28 12:00:00',
'2020-04-29 12:00:00', '2020-04-30 12:00:00'],
dtype='datetime64[ns]', freq='D')
# 開始日時から1時間おきに20個生成したいとき(終了日時は省略)
pd.date_range("2020-04-13 12:00:00", periods=20, freq="H")
DatetimeIndex(['2020-04-13 12:00:00', '2020-04-13 13:00:00',
'2020-04-13 14:00:00', '2020-04-13 15:00:00',
'2020-04-13 16:00:00', '2020-04-13 17:00:00',
'2020-04-13 18:00:00', '2020-04-13 19:00:00',
'2020-04-13 20:00:00', '2020-04-13 21:00:00',
'2020-04-13 22:00:00', '2020-04-13 23:00:00',
'2020-04-14 00:00:00', '2020-04-14 01:00:00',
'2020-04-14 02:00:00', '2020-04-14 03:00:00',
'2020-04-14 04:00:00', '2020-04-14 05:00:00',
'2020-04-14 06:00:00', '2020-04-14 07:00:00'],
dtype='datetime64[ns]', freq='H')
# 開始日時から終了日時まで1時間間隔で生成したいとき
pd.date_range("2020-04-01 12:00:00", "2020-04-30 12:00:00", freq="H")
DatetimeIndex(['2020-04-01 12:00:00', '2020-04-01 13:00:00',
'2020-04-01 14:00:00', '2020-04-01 15:00:00',
'2020-04-01 16:00:00', '2020-04-01 17:00:00',
'2020-04-01 18:00:00', '2020-04-01 19:00:00',
'2020-04-01 20:00:00', '2020-04-01 21:00:00',
...
'2020-04-30 03:00:00', '2020-04-30 04:00:00',
'2020-04-30 05:00:00', '2020-04-30 06:00:00',
'2020-04-30 07:00:00', '2020-04-30 08:00:00',
'2020-04-30 09:00:00', '2020-04-30 10:00:00',
'2020-04-30 11:00:00', '2020-04-30 12:00:00'],
dtype='datetime64[ns]', length=697, freq='H')
# 開始日時から終了日時まで2時間間隔で生成したいとき
pd.date_range("2020-04-01 12:00:00", "2020-04-30 12:00:00", freq="2H")
DatetimeIndex(['2020-04-01 12:00:00', '2020-04-01 14:00:00',
'2020-04-01 16:00:00', '2020-04-01 18:00:00',
'2020-04-01 20:00:00', '2020-04-01 22:00:00',
'2020-04-02 00:00:00', '2020-04-02 02:00:00',
'2020-04-02 04:00:00', '2020-04-02 06:00:00',
...
'2020-04-29 18:00:00', '2020-04-29 20:00:00',
'2020-04-29 22:00:00', '2020-04-30 00:00:00',
'2020-04-30 02:00:00', '2020-04-30 04:00:00',
'2020-04-30 06:00:00', '2020-04-30 08:00:00',
'2020-04-30 10:00:00', '2020-04-30 12:00:00'],
dtype='datetime64[ns]', length=349, freq='2H')
小括#
本項では日時型データの基本的な前処理について紹介しました。
この辺の操作を文字列として頑張ることも可能ですが、適切に日時型データに変換することで、簡潔な記述と直感的な操作理解ができるのでオススメです。
また、内容が脳筋してきたので執筆は不精しましたが、適切な関数を書いて apply
することで、季節(春/夏/秋/冬)、時間帯(朝/昼/夕/夜)、平休日といった情報を付与したり抽出したりすることもできます。
この辺の話はカテゴリカルデータ処理との親和性が高いので、合わせてご一読いただくと理解が深まると思います。