時系列コンペでよく使用されるsin, cosへの変換とは

塩コンペに関しては、大会1位を飾ったphalanxさんが記事を書いているのでぜひとも参照してください!自分もやったことは記事内の2ヶ月〜最終日以外はほぼ同じです。


本題

時系列のコンペでは、特徴量として各訓練例が取得された時間や日付を組み込むことがあります。

しかしhourを例に考えてみると、23時の次は0時ですがこの間には数値的なつながりがありません。そこで循環性を考慮できるように三角関数を各特徴量に適用させます。

%matplotlib inline

import pandas as pd
import numpy as np

dates = pd.date_range(start='2017-01-01', end='2017-12-31', freq='H')
df = pd.DataFrame(data=np.zeros(len(dates)), index=dates, columns=['test'])
df.head()

使用DF.PNG

これで2017年1月から12月まで24時間ごとの特徴量を作成することができました。

インデックスから時系列の情報を抽出します。

df['year']  = df.index.year

df['month'] = df.index.month
df['day'] = df.index.day
df['hour'] = df.index.hour
df['dow'] = df.index.dayofweek

これで時系列の情報を抽出できました。

df[df.index.month == 1]['dow'].plot(grid=True)

通常のdow.png

これで1月の曜日の情報を見てみると、日曜日(dow==6)の次が月曜日(dow==0)になっており、隣り合った日であっても数値的には隣り合っていません。

そこで循環性を持つように三角関数を使用して特徴量を変換します。

def encode(df, col):

# この方法だと場合によって最大値が変化するデータでは正確な値は出ない
# 例:月の日数が30日や31日の場合がある
df[col + '_cos'] = np.cos(2 * np.pi * df[col] / df[col].max())
df[col + '_sin'] = np.sin(2 * np.pi * df[col] / df[col].max())
return df

df = encode(df, 'dow')
df = encode(df, 'hour')
df = encode(df, 'day')
df = encode(df, 'month')

実際に高校でやったように三角関数を図に出力してみると、各特徴量が循環性を持っていることがわかります。

# 例:月の循環性

df.plot.scatter('month_sin', 'month_cos').set_aspect('equal')

通常のdowの円.png

なおよりデータを細かくしたいときは、一つ小さな時系列情報を組み込めば表現はできます。

df['dow+hour'] = df['dow'] + df['hour'] / 24

df = encode(df, 'dow+hour')
df.plot.scatter('dow+hour_sin', 'dow+hour_cos').set_aspect('equal')

通常のdowhourの円.png


参考文献