【Python & SQLite】単勝1倍台の馬がいるレースの期待値分析してみた①

## 概要
競馬 × AIの話題を見かけることがここ数年で増えましたが、全レースを対象としたものも多く、
個人的に気になる事例に絞って分析してみたい！と思い、今回netkeiba-scraperを使用していじってみました。

今回絞るテーマは「単勝1倍台の馬が出走するレースの買い方」です。
2019年は日本ダービーや阪神JF、有馬記念など、単勝1倍台の馬が馬券外に沈むケースも多く見られました。
あくまで単勝馬券で分析は進めました。

※SQLをPythonでいじる練習も兼ねていますので、コードの書き方ご指導いただけると嬉しいです！


## netkeiba-scraperでデータ取得
競馬データのスクレイピングについては、偉大なる先人のコードを使用させていただきました。
[gitHubはこちら] (https://github.com/stockedge/netkeiba-scraper)

過去10年分のデータを取得しました。
特徴量抽出のための`genfeature`は、処理に40日くらいかかりそうだったため使用しませんでした。
またJRAのクラス呼称が変更された(ex. 500万下→1勝クラス)ため、Scalaコードを直さないと適切に`genfeature`できなさそうです。

DLしたrace.dbファイルをPythonで読み込み、データ分析を開始します。


## Jupyter NotebookでSQLiteを読み込む
```Python:dbconnect
import sqlite3 as sq

conn = sq.connect('race.db')
cur = conn.cursor()

cur.execute("SELECT r.race_id, i.race_name, r.order_of_finish, r.odds \
             FROM race_result r INNER JOIN race_info i on r.race_id=i.id \
             WHERE r.odds<2.0")

rows = cur.fetchall()
for row in rows:
    print(row)
```
Jupyter Notebookのipynbファイルと同じ階層にrace.dbファイルを移し、読み込みます。
race.dbのうちレースのデータが`race_info`テーブル、`race_result`テーブルに格納されているので、
欲しい情報を各テーブルから取得します。

※race_infoテーブル レースごとの情報が載っています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365854/8a4d387c-fc3e-74c2-b9bc-03dba6d282f4.png)


※race_resultテーブル 馬ごとの戦績が載っています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365854/72f7aad6-1895-914d-db87-a2373c242058.png)


## 1倍台の人気で勝利した馬の期待値

```Python:オッズ別期待値(全クラス)
# 勝ち数
cur.execute("SELECT r.odds, count(r.race_id) \
             FROM race_result r \
             WHERE r.odds<2.0 AND r.order_of_finish='1' \
             GROUP BY r.odds ORDER BY r.odds ASC")
rows = cur.fetchall()
# 全レース 
cur.execute("SELECT r.odds, count(r.race_id) \
             FROM race_result r \
             WHERE r.odds<2.0 \
             GROUP BY r.odds ORDER BY r.odds")
rows2 = cur.fetchall()

# 期待値計算 もっと他に書きようありそう
try:
    t11 = rows[0][0] * rows[0][1] / (rows2[0][0] * rows2[0][1]) * 100
    t12 = rows[1][0] * rows[1][1] / (rows2[1][0] * rows2[1][1]) * 100
    t13 = rows[2][0] * rows[2][1] / (rows2[2][0] * rows2[2][1]) * 100
    t14 = rows[3][0] * rows[3][1] / (rows2[3][0] * rows2[3][1]) * 100
    t15 = rows[4][0] * rows[4][1] / (rows2[4][0] * rows2[4][1]) * 100
    t16 = rows[5][0] * rows[5][1] / (rows2[5][0] * rows2[5][1]) * 100
    t17 = rows[6][0] * rows[6][1] / (rows2[6][0] * rows2[6][1]) * 100
    t18 = rows[7][0] * rows[7][1] / (rows2[7][0] * rows2[7][1]) * 100
    t19 = rows[8][0] * rows[8][1] / (rows2[8][0] * rows2[8][1]) * 100
except:
    None

print('単勝1.1倍の馬券期待値は %d' %t11)
print('単勝1.2倍の馬券期待値は %d' %t12)
print('単勝1.3倍の馬券期待値は %d' %t13)
print('単勝1.4倍の馬券期待値は %d' %t14)
print('単勝1.5倍の馬券期待値は %d' %t15)
print('単勝1.6倍の馬券期待値は %d' %t16)
print('単勝1.7倍の馬券期待値は %d' %t17)
print('単勝1.8倍の馬券期待値は %d' %t18)
print('単勝1.9倍の馬券期待値は %d' %t19)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365854/30b5384e-9ff0-69f7-05e8-d3c3d87569bd.png)
面白いことに、オッズが低いほど単勝期待値が高い(=1着になる可能性が高い)ことが分かりました。
やはり1.1倍ともなると歴史的名馬も多いからでしょうか。

似た条件で【オープンクラス以上(重賞含む)】に絞ると、1.1倍の期待値がさらに上昇しました。

```Python:OnlyOpenClass
cur.execute("SELECT r.odds, count(r.race_id) \
             FROM race_result r INNER JOIN race_info i on r.race_id=i.id \
             WHERE r.odds<2.0 AND r.order_of_finish='1' AND i.race_class like '%オープン%' \
             GROUP BY r.odds ORDER BY r.odds ASC")
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365854/759fb0a8-8709-06e1-6e2f-e4f518bf9895.png)


## 人気別の成績を調べる
単勝1倍台の馬が出走するレースでは、どの馬が勝利することが多いのでしょうか？
2番人気や3番人気に妙味はあるのでしょうか？

上までの分析と違い、【単勝1倍台の馬がいるレースに限定】しなければ計算できないため、
`WHERE ~ IN (SELECT ~~)`の記法でサブクエリを使用しました。

```Python:ResultByPopularity
# 単勝1倍台の馬がいるレースのうち人気別勝ち数
cur.execute("SELECT r.popularity, count(r.race_id) \
             FROM race_result r \
             WHERE r.order_of_finish='1' AND r.race_id IN (SELECT race_id from race_result WHERE odds<2.0 AND popularity='1') \
             GROUP BY r.popularity ORDER BY r.popularity ASC")
rows = cur.fetchall()
# 単勝1倍台の馬がいる全レース 
cur.execute("SELECT count(r.race_id) FROM race_result r WHERE r.odds<2.0 AND r.popularity='1'")
rows2 = cur.fetchall()

for row in rows:
    print(row)
print(rows2[0][0])
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365854/6529c2cc-276f-e7c5-0958-9b12e2d4feb2.png)
全7,619レース中、約半数を1番人気が勝利しています。
2番人気は単勝6倍以上、3番人気は10倍近くつかないと期待値100を超えません。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365854/b619277e-5eb9-537f-485e-ce623780714a.png)
オッズに頭数を掛け、レース数で割る形で期待値を計算しましたが、32と酷い有様でした。


## まとめ
競馬場やダート/芝、馬齢、馬番、馬の脚質を細分化しなければ、オッズを見ても儲からない、というのが今回の結果でした。
また競馬には多様な馬券の種類があるため、【単勝1倍台の馬が出走するレースで最も期待値の高い馬券種はどれか？】など、
もっと深く分析できそうです。

Python(Jupiter Notebook)でSQLを抽出する要領はつかめたので、引き続き分析してみようと思います。


## 参考記事
[netkeiba-scraperが2019年6月現在動くかの話(Ubuntu 18.04.2 LTS)](https://qiita.com/dk4130523/items/2c13dd713e8a84693aa9)