9
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

日時でラグ特徴量を作る(日付でシフトと集計)

Last updated at Posted at 2021-03-17

テーブルデータにおける時系列予測では, ラグ特徴量 (shift & rolling)を作る事がよくある.
これまでは, 単純に行数でシフトすればよい場合のみを扱ってきたが, 日付でシフトして日付で集計する需要が出てきた.
これが案外苦労したのでメモとして残しておく.

次のデータを例に説明する.

df = pd.DataFrame({
        "date": ["2021-04-01 10:04", "2021-04-01 12:03", "2021-04-02 14:40", "2021-04-03 2:01"
                , "2021-04-05 5:02", "2021-04-07 1:16", "2021-04-10 11:20", "2021-04-14 23:30"],
        "target": [1,5,7,2,3,7,9,3],
})
df["date"]=pd.to_datetime(df["date"])

table.png

これまでよく使ってきたのは, df["target_shift_rows"]=df["target"].shift(2).rolling(3).mean()みたいな行単位での処理だった.
行ごとのシフトであるため, indexが共通であり代入が簡単である.

しかしここでやりたいのは, 2日シフト, 3日間集計だ.

1. 日付でシフトして日付で集計する

shift_rolling = df.set_index("date").shift(freq="2D").rolling("3D")[["target"]].mean()

shift_rolling.png

  • indexを日付にすると.shift(freq="2D")で2日シフトできる
    • 通常の行のシフトのイメージと異なり, 単純に日付が2日ずれるだけ
    • 事前にpd.Timedelta(days=2)を引いておいても同じことを実現できる
  • .rolling("3D")で3日間の単位で集計できる
    • indexが日付のときのみ可能
    • indexが日付でない場合, .rolling("3D", on="date")のようにonで指定も可能

2. 無理やりマージする
先程のテーブルを元のテーブルにマージしたいのだが, 日付がバラバラなので完全一致することはなく, 通常のマージではどうにもならない.
merge_asofという関数を使うと, これを解消できる.

df = pd.merge_asof(df, shift_rolling, on="date", direction="backward", suffixes=("","_shift"))

merge.png

  • direction
    • "backward": 後ろ方向の近い値でマージ
    • "forward": 前方向の近い値でマージ
    • "nearest": 最も近い値でマージする

今回の場合, 未来情報を参照しないように"backward"を使う.

See https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.merge_asof.html

groupbyで使いたい場合

ユーザーIDや商品IDごとに日付で処理したい場合, さらに複雑になるのでメモ.

df = pd.DataFrame({
        "date": ["2021-04-01 10:04", "2021-04-01 12:03", "2021-04-02 14:40", "2021-04-03 2:01"
                , "2021-04-05 5:02", "2021-04-07 1:16", "2021-04-10 11:20", "2021-04-14 23:30"],
        "group": ["a","a","b","a","b","a","b","b"],
        "target": [1,5,7,2,3,7,9,3],
})
df["date"]=pd.to_datetime(df["date"])

table.png

今回は, groupごとに, 1日シフト, 2日平均を取りたいとする.

tmp = df.set_index("date").groupby("group")[["target"]].apply(
    lambda x: pd.merge_asof(pd.DataFrame(x.index), 
                            x.shift(freq="1D").rolling("2D").mean(),
                            on="date",
                            direction="backward",
                           )
)

table.png

pd.merge_asofが複数列でのマージに対応していないため, 事前にマージ用にシフトされた"date"列を, 元の"date"列に無理やりマージしておく.
追記:byという引数でonの前に通常マージする項目を設定できるっぽい.

df = df.merge(tmp, on=["group","date"], suffixes=("","_shift"))

table.png

最後に, 複数列でマージすればOK.

まとめ

  • pd.merge_asofの存在を知っているかどうかという問題
9
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?