maruyama
Nishika開催された同様のコンペ (※1) では、予測値として 前年同月 or 同年前月 の価格を使うだけ (※2) で4位/1046人でした。本コンペでも同様の方法が使えるのではないかと思い試してみましたので、ご紹介します。
※1 https://competition.nishika.com/competitions/yasai/summary
※2 https://competition.nishika.com/competitions/yasai/topics/353
(2023-07-25更新) RMSLEの定義がsklearnと異なっていたため修正しました。また、予測方法に前年翌月を追加しました。その他、不要なコードを一部削りました。
import numpy as np
import pandas as pd
train_data = pd.read_csv('data/train_data.csv', index_col=0, parse_dates=[0])
sample_submission = pd.read_csv('data/submission.csv')
あらかじめ価格を対数変換しておきます。
train_data = np.log1p(train_data)
また、この後の処理を書きやすくするため、行と列に分かりやすい名前を付けておきます。
train_data = train_data.rename_axis(index='date', columns='vegetable')
train_data.head()
vegetable | えのきだけ_中国 | えのきだけ_九州 | えのきだけ_北海道 | えのきだけ_北陸 | えのきだけ_四国 | えのきだけ_東北 | えのきだけ_東海 | えのきだけ_近畿 | えのきだけ_関東 | かぶ_北海道 | ... | レタス_関東 | 生しいたけ_中国 | 生しいたけ_九州 | 生しいたけ_北海道 | 生しいたけ_北陸 | 生しいたけ_四国 | 生しいたけ_東北 | 生しいたけ_東海 | 生しいたけ_近畿 | 生しいたけ_関東 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
date | |||||||||||||||||||||
2016-01-01 | 5.752573 | 5.710427 | 5.720312 | 5.616771 | 5.590987 | 5.365976 | 5.631212 | 5.655992 | 5.634790 | 4.828314 | ... | 5.605802 | 7.002156 | 6.955593 | 6.603944 | 7.116394 | 6.929517 | 6.896694 | 7.021084 | 7.015712 | 7.020191 |
2016-02-01 | 5.726848 | 5.758902 | 5.736572 | 5.666427 | 5.683580 | 5.631212 | 5.659482 | 5.690359 | 5.676754 | 5.062595 | ... | 5.743003 | 6.985642 | 6.863803 | 6.621406 | 7.124478 | 6.945051 | 6.872128 | 7.038784 | 7.028201 | 7.018402 |
2016-03-01 | 5.505332 | 5.384495 | 5.762051 | 5.393628 | 5.429346 | 5.105945 | 5.351858 | 5.365976 | 5.323010 | 5.225747 | ... | 5.631212 | 6.853299 | 6.606650 | 6.591674 | 7.092574 | 6.787845 | 6.848005 | 6.897705 | 6.843750 | 6.951772 |
2016-04-01 | 5.429346 | 5.209486 | 5.720312 | 5.278115 | 5.313206 | 5.036953 | 5.327876 | 5.318120 | 5.247024 | 5.247024 | ... | 5.347108 | 6.775366 | 6.620073 | 6.599870 | 6.922644 | 6.719013 | 6.788972 | 6.829794 | 6.783325 | 6.890609 |
2016-05-01 | 5.451038 | 5.313206 | 5.726848 | 5.332719 | 5.375278 | 5.141664 | 5.402677 | 5.398163 | 5.332719 | 5.017280 | ... | 5.111988 | 6.787845 | 6.711740 | 6.566672 | 6.962243 | 6.794587 | 6.766192 | 6.849066 | 6.853299 | 6.898715 |
5 rows × 340 columns
まず、予測値として前年翌月/前年同月/同年前月の価格を出力する方法を使って学習データに対して予測します。
train_data_pred = pd.concat(
# 前年翌月の価格を予測値として使う。
[
pd.DataFrame({
'id': train_data.columns,
'year': year,
'method': 'lynm',
'y': train_data.loc[f'{year}-12-01', :],
'y_pred': train_data.loc[f'{year - 1}-01-01', :]
})
for year in [2018, 2017]
] +
# 前年同月の価格を予測値として使う。
[
pd.DataFrame({
'id': train_data.columns,
'year': year,
'method': 'lycm',
'y': train_data.loc[f'{year}-12-01', :],
'y_pred': train_data.loc[f'{year - 1}-12-01', :]
})
for year in [2018, 2017]
] +
# 同年前月の価格を予測値として使う。
[
pd.DataFrame({
'id': train_data.columns,
'year': year,
'method': 'cylm',
'y': train_data.loc[f'{year}-12-01', :],
'y_pred': train_data.loc[f'{year}-11-01', :]
})
for year in [2018, 2017, 2016]
],
ignore_index=True
)
train_data_pred.head()
id | year | method | y | y_pred | |
---|---|---|---|---|---|
0 | えのきだけ_中国 | 2018 | lynm | 5.683580 | 5.700444 |
1 | えのきだけ_九州 | 2018 | lynm | 5.641907 | 5.590987 |
2 | えのきだけ_北海道 | 2018 | lynm | 5.556828 | 5.730100 |
3 | えのきだけ_北陸 | 2018 | lynm | 5.513429 | 5.609472 |
4 | えのきだけ_四国 | 2018 | lynm | 5.587249 | 5.627621 |
次に、野菜と地域の組み合わせそれぞれについて、最もRMLSEが小さくなる方を最適な予測方法として選びます。
best_pred_method = (
train_data_pred
.assign(rmsle = lambda df: df['y'] - df['y_pred'])
.groupby(['id', 'method'], as_index=False)['rmsle'].agg(lambda s: np.sqrt(np.mean(s ** 2)))
.loc[lambda df: df['rmsle'] == df.groupby('id')['rmsle'].transform(np.min), ['id', 'method']]
.reset_index(drop = True)
)
best_pred_method.head()
id | method | |
---|---|---|
0 | えのきだけ_中国 | cylm |
1 | えのきだけ_九州 | cylm |
2 | えのきだけ_北海道 | cylm |
3 | えのきだけ_北陸 | cylm |
4 | えのきだけ_四国 | cylm |
最適な予測方法を使った場合の、学習データに対する予測精度を確認します。
(
train_data_pred
.loc[lambda df: df['year'] >= 2017, :]
.merge(best_pred_method, how='inner', on=['id', 'method'])
.assign(rmsle = lambda df: df['y'] - df['y_pred'])
.groupby(['year'], as_index=False)['rmsle'].agg(lambda s: np.sqrt(np.mean(s ** 2)))
)
year | rmsle | |
---|---|---|
0 | 2017 | 0.209963 |
1 | 2018 | 0.170055 |
最後に、2019年12月の価格を予測し、提出ファイルを作ります。
submission = (
pd.concat(
[
# 前年翌月の価格を予測値として使う。
pd.DataFrame({
'id': train_data.columns,
'method': 'lynm',
'y': train_data.loc[f'2018-01-01', :]
}),
# 前年同月の価格を予測値として使う。
pd.DataFrame({
'id': train_data.columns,
'method': 'lycm',
'y': train_data.loc[f'2018-12-01', :]
}),
# 同年前月の価格を予測値として使う。
pd.DataFrame({
'id': train_data.columns,
'method': 'cylm',
'y': train_data.loc[f'2019-11-01', :],
})
],
ignore_index=True
)
# 最適な予測方法で予測した結果だけ抽出する。
.merge(best_pred_method, how='inner', on=['id', 'method'])
.drop(columns=['method'])
# 対数変換してあるので、元に戻す。
.assign(y = lambda df: np.expm1(df['y']))
# 予測結果を指定された順序に並び替える。
.merge(sample_submission[['id']], how='right', on='id')
)
submission.to_csv('submission/submission_20230725_01.csv', index=False, header=True)