【第8回】Python で競馬予想してみる ~ 今までのおさらい ~


#競馬予想のおさらい
思い立って、コードを書き始めた python での競馬予想
今は、競馬予想をしてますが、一口馬主の募集馬の※※を・・・・だったり、株式やFXの※※を・・・・だったり
試したいことはたくさんあるのだ

ここでは、いままでのおさらいを備忘録で書いておきます
また、コードを手直ししているときに判明した問題点と　その before after を試してみます

ところどころ、コードを載せてますが素人なのでご勘弁を
直した方が良いところがあれば、ご教授お願いします

#開発環境
Windows10
Jupyter Notebook

#データの取得
[TARGET frontier JV](https://jra-van.jp/target/index.html)のレース検索で、2000年からの全レースデータを検索
馬データおよび前走データも読込み、全データを CSV で出力し使用

#データの前処理
- 細かな修正
    - 斤量の減量マークを外す
    - 競争除外馬の着差'----'や着順が '0' になっているのをエラー値に変換
    - 配当の欠損値を 0 に変換
    - 血統の欠損値を '不明' に変換
- カテゴリ変数
    - 場所、芝・ダ、馬場状態、性別、父タイプ名、母父タイプ名･･･などをカテゴリ化して、ダミー変数化
- 予想しないレース・馬データ削除
    - 予想するレースが障害レースの場合、削除
    - 予想するレースが新馬戦および初出走馬（前走レースデータがない馬）のデータ削除
- 過去レースは5レース分連結
- 目的変数：着順が3着以内かどうかのカラムを追加
- トレーニングデータと検証データは、時系列で分割。最近の 3割を検証データに
- 不均衡データ修正：アンダーサンプリング　False,2 True,1 の割合に

#モデルの学習・評価
LightGBM 使用
学習結果・評価結果は以下の通り

```text:Out
train  roc_auc_score = 0.7891163706003093
eval  roc_auc_score = 0.7758619662995246
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/895631/70a43036-eec3-7e34-1220-f1f131a820e1.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/895631/341085fb-7807-8204-3bd8-a9c6a60205c7.png)

#モデルの運用
学習結果に基づいて、閾値を変化させたときの回収率を計算すると下のグラフの通り
購入数が、購入対象レース数の 1/3 以下になった時にシミュレーションストップしてるので、購入数が極端に少ないデータは除外
複勝はいい感じです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/895631/21e3e255-6b7b-c326-1e47-5c61e94cddb0.png)

#課題
- 学習データを精査してみると、競争除外馬も予測対象になっていることが判明。どの程度悪影響があるか不明だが修正したほうがよさそう
- 未出走馬を削除しているため、1~3着馬がいないレースでも予測していることが判明。レース毎に未出走馬の割合や数に応じて、予測レースから外した方がよさそう。
- これが一番問題: 今のモデルは未勝利場もG1馬も同じ土俵で学習・予測して結果で回収率をシミュレーションしている。未確認だが、クラスの低いレースでは、予測結果の確率の高い馬の割合が低く、G1などのクラスの高いレースでは予測結果の確立が高くなっている馬ばかりと推測。特徴量の修正などで対応するか？　馬券の買い方で対応するか？　

~~とりあえず投稿するが、対策結果は順次　追記　する予定:wave:~~

#競争除外馬と未出走馬の3頭以上出走レースを削除
###学習・検証データから競争除外馬を削除

```python:build.jpynd
    df_last = df_last.dropna(subset = ['前走レースID(新)']) # 前走レースID がないデータ削除
```
###3頭以上未出走馬が出走するレースを削除

3頭以上未出走馬が出走するレースを削除 

```python:build.jpynd
# 未出走馬が一定数以上のレースは除外
rouped = df_return_table.groupby('レースID(新/馬番無)')
num_horse = lambda x : x.count() - x.mean()
df_return_table['データ減'] = grouped['出走頭数'].transform(num_horse)
df_return_table = df_return_table[~(df_return_table['データ減'] <= -3)]
```

###proba の値を標準偏差化して　0~1に収まるように修正
レース内で Proba を標準偏差に直し（下表の`Proba_std`） その数値を 0~1 に収まるように修正してみた（下表の`Proba_std01`）
ついでにレース内で Proba を順位付けした列を追加（下表の`Proba順`）

```python:build.jpynd
# proba 順位付け   
df_return_table['Proba順'] = df_return_table.groupby('レースID(新/馬番無)').rank('dense', ascending = False)['Proba']
    
# proba のレース内標準偏差化
    
std_scaler = lambda x: (x-x.mean()) / x.std()
df_return_table['Proba_std'] = df_return_table.groupby('レースID(新/馬番無)')['Proba'].transform(std_scaler)
std01_scaler = lambda x: (x - x.min()) / (x.max() - x.min())
df_return_table['Proba_std01'] = df_return_table['Proba_std'].transform(std01_scaler)
```
回収率の計算に使うテーブルはこんな感じ
![スクリーンショット 2020-12-15 09.17.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/895631/fc5aeac0-7ee9-e6a3-771a-1d8c8a596d94.png)

##学習・検証結果
####競争除外馬と未出走馬が2頭以上のレースを削除　回収率シミュレーションは変更なし
Auc はほとんど変化なし

```text:Out
train  roc_auc_score = 0.7891023931796946
eval  roc_auc_score = 0.77596700811738
```

##回収率シミュレーション
今まで通り Proba の値を基に閾値を動かして検証
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/895631/56856b3a-6ca9-9b77-d683-4f14472f69ee.png)

```text:Out
WIN_回収率 81.82452193475815
PLACE_回収率 88.57142857142857
```
ちょっとは、良化すると思ったけどほとんど変化なしなのだ

####競争除外馬と未出走馬が2頭以上のレースを削除　回収率を Probaをレース毎に標準偏差し正規化したものでシミュレーション
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/895631/ad13f85b-4f69-3d27-5d6b-f21e30945414.png)

```text:Out
WIN_max回収率 81.76498653852708
PLACE_max回収率 85.47403863193337
```

####競争除外馬と未出走馬が 2頭以上のレースを削除　回収率をレース毎に Proba 上位 n頭を購入
n を 1~3頭に変化させても、単勝・複勝の回収率は 80-81％程度

色々試したけど、大して良化しないのだ

今回はここまで:wave: