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

## 概要
前回の投稿 [【Python & SQLite】単勝1倍台の馬がいるレースの期待値分析してみた①](https://qiita.com/gmnori/items/61a3fc2ff96044dabf29)　の続編となります。
前回はスクレイピングしたデータをSQLにまとめ、Python(Jupiter Notebook)で単勝期待値を計算しました。

前回に引き続き、テーマは「単勝1倍台の馬が出走するレースの買い方」です。

## 前回の反省点
競馬場や距離などの条件を絞らずに計算したため、単勝馬券で期待値100を超える買い方を見つけられませんでした。
今回は競馬場、距離、芝/ダート、券種を絞って詳しく分析していきましょう。

## 競馬場、券種を絞って期待値の高そうな条件を探す
今回は「東京競馬場」に絞った結果を書いておきます。
他の競馬場でもいいのですが、歴史ある競馬場ですし、強い馬が実力を発揮しやすいと思うため東京競馬場にしました。

また券種は馬連(1,2着のどちらかに選んだ2頭が入れば当たり)で分析します。
前回の投稿で分かったように、単勝1倍台の1番人気は50%近い確率で勝利していることから、
馬連が当たりやすさと配当妙味のバランスに優れているという仮説を立てました。
（三連系だと人気薄の馬のノイズもありそうという理由もあります）

```Python:TokyoRacecource
#　芝コース　馬連でおいしい馬券を探す
#  単勝1倍台の1番人気馬の連帯率を調べる
cur.execute("SELECT i.distance, 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 IN ('1','2') AND i.surface like '芝%' AND i.place_detail like '%東京%' \
AND r.race_id IN (SELECT race_id from race_result WHERE odds<2.0 AND popularity='1') \
GROUP BY i.distance ORDER BY i.distance")
rows = cur.fetchall()

print('連帯数(芝)')
for row in rows:
    print(row)
    
cur.execute("SELECT i.distance, 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 NOT IN ('1', '2') AND i.surface like '芝%' AND i.place_detail like '%東京%'\
AND r.race_id IN (SELECT race_id from race_result WHERE odds<2.0 AND popularity='1') \
GROUP BY i.distance ORDER BY i.distance")
rows2 = cur.fetchall()

print('3着以下数(芝)')
for row2 in rows2:
    print(row2)

rentai1800 = round(rows[2][1] / (rows[2][1] + rows2[2][1]) * 100, 2)
print('1800m連帯率: %f パーセント' %rentai1800)
rentai2000 = round(rows[3][1] / (rows[3][1] + rows2[3][1]) * 100, 2)
print('2000m連帯率: %f パーセント' %rentai2000)

----------結果----------
連帯数(芝)
(1400, 66)
(1600, 89)
(1800, 90)
(2000, 84)
(2300, 2)
(2400, 40)
(3400, 1)
3着以下数(芝)
(1400, 31)
(1600, 59)
(1800, 38)
(2000, 31)
(2300, 2)
(2400, 22)
1800m連帯率: 70.310000 パーセント
2000m連帯率: 73.040000 パーセント
```
1800m, 2000mの成績が特に良いですね。日本ダービーやオークス、ジャパンカップが行なわれる2400mは64%程度と微妙。
※ちなみに2500mが表示されていないのは、過去に単勝1倍台の馬がいなかったためです。

最も連帯率の高い芝2000mの条件を深堀りしてみましょう。

## 東京芝2000m 1番人気が連帯したときの条件を深堀りしてみる
### もう一頭の連帯馬の【単勝人気】
```Python:Another連帯馬
# 2000mで絞ってみる
# 1人気が連帯したときのもう一頭の人気

cur.execute("SELECT r.popularity, count(r.race_id) FROM race_result r \
INNER JOIN race_info i on r.race_id=i.id \
WHERE r.order_of_finish IN ('1','2') AND r.popularity != '1' \
AND r.race_id IN (SELECT race_id from race_result r INNER JOIN race_info i on r.race_id=i.id WHERE odds<2.0 AND popularity='1' \
AND order_of_finish IN ('1','2') AND i.surface like '芝%' AND i.place_detail like '%東京%' AND i.distance=2000) \
GROUP BY r.popularity ORDER BY r.popularity")
rows = cur.fetchall()

print('(人気, 回数)')
for row in rows:
    print(row)

----------結果----------
(人気, 回数)
(2, 21)
(3, 31)
(4, 12)
(5, 6)
(6, 7)
(7, 4)
(8, 2)
(11, 1)
```
驚くべきことに、1番人気-2番人気の連帯より、1番人気-3番人気の連帯のほうが多かったです。
抜けた人気の1番人気より、少しでも勝てそうな2番人気を他の馬がマークした結果でしょうか？

ちなみに、【単勝1倍台の1番人気馬が連帯しなかった場合】に【連帯する回数が最も多かった】のも3番人気でした。
1番人気が勝とうがコケようが、2番人気から買うのは妙味が薄そうです。

### もう一頭の連帯馬の【4コーナーでのポジション】
```Python:4cornerPosition
cur.execute("SELECT substr(r.pass, -2), count(r.race_id) FROM race_result r \
INNER JOIN race_info i on r.race_id=i.id \
WHERE r.order_of_finish IN ('1','2') AND r.popularity != '1' \
AND r.race_id IN (SELECT race_id from race_result r INNER JOIN race_info i on r.race_id=i.id WHERE odds<2.0 AND popularity='1' \
AND order_of_finish IN ('1','2') AND i.surface like '芝%' AND i.place_detail like '%東京%' AND i.distance=2000) \
GROUP BY substr(r.pass, -2) ORDER BY substr(r.pass, -2)")
rows = cur.fetchall()

print('(番手, 回数)')
for row in rows:
    print(row)

----------結果----------
(番手, 回数)
('-1', 14)
('-2', 15)
('-3', 14)
('-4', 4)
('-5', 10)
('-6', 6)
('-7', 4)
('-8', 4)
('-9', 2)
('10', 3)
('11', 3)
('12', 2)
('13', 3)
```

最後の直線入り口では、5番手以内にいた馬が連帯しやすいようです。
これは先行脚質の馬が粘り切る展開になること、そもそも余力があったから前のほうにいることなどが考えられます。

東京芝2000mに単勝1倍台の人気馬が出走していて、3番人気の馬が先行脚質なら期待値が高そうに見えます。
2019年の天皇賞秋を思い出しますね…！


## 馬連の期待値を計算してみる(3つのテーブルから情報取得)
ここからは、netkeiba-scraperで取得したテーブルを3つ活用します。
そのため【サブクエリを入れ子にして条件を絞る】ことをしました。

### 単勝1番人気 - 単勝2番人気の馬連
まずは期待値の低そうな 【1番人気-2番人気】の馬連を購入した場合の期待値です。

```Python:1-2人気の馬連期待値
# race_resultのrace_id とpayoffのrace_idを紐づけて期待値を計算する
# ticket_type 単勝0, 複勝1, 枠連2, 馬連3, ワイド4, 馬単5, 三連複6, 三連単7

# サブクエリを2重使用し、【1倍台の馬が連帯したレースのうち】【もう一頭連帯した馬が2番人気だったレース】の馬連払戻金を合計
cur.execute("SELECT DISTINCT p.race_id, p.payoff FROM payoff p \
INNER JOIN race_result r ON p.race_id=r.race_id INNER JOIN race_info i on p.race_id=i.id \
WHERE p.ticket_type=3 AND p.race_id IN \
        (SELECT r.race_id FROM race_result r \
        INNER JOIN race_info i on r.race_id=i.id \
        WHERE r.order_of_finish IN ('1','2') AND r.popularity='2' \
            AND r.race_id IN (SELECT race_id from race_result r INNER JOIN race_info i on r.race_id=i.id \
            WHERE odds<2.0 AND popularity='1' AND order_of_finish IN ('1','2') AND i.surface like '芝%' \
            AND i.place_detail like '%東京%' AND i.distance=2000))")
rows = cur.fetchall()
umaren_sum = 0
for row in rows:
    umaren_sum += row[1]

# 単勝1倍台の馬が出走したレース数を調べる
cur.execute("SELECT count(race_id) from race_result r INNER JOIN race_info i on r.race_id=i.id \
            WHERE odds<2.0 AND popularity='1' AND i.surface like '芝%' AND i.place_detail like '%東京%' \
            AND i.distance=2000")
rows2 = cur.fetchall()

print('単勝1倍台の馬が連帯したレースでの馬連合計:')
print(umaren_sum)

print('単勝1倍台の馬が東京芝2000mに出走したレース数:')
print(rows2[0][0])

print('馬連期待値は')
print(round(umaren_sum / rows2[0][0], 2))

-----結果-----
単勝1倍台の馬が連帯したレースでの馬連合計:
6740.0
単勝1倍台の馬が東京芝2000mに出走したレース数:
115
馬連期待値は
58.61
```

やはり低いです。ただでさえ1頭が堅そうなので、もう一頭も人気馬を買うのは妙味がありません。
これなら単勝1番人気の単勝を買ったほうがマシです。

### 単勝1番人気 - 単勝3番人気の馬連
次に、最も期待値の期待できる 【1番人気-3番人気】の馬連を購入した場合の期待値です。

```Python:1-3人気の馬連期待値
# race_resultのrace_id とpayoffのrace_idを紐づけて期待値を計算する
# ticket_type 単勝0, 複勝1, 枠連2, 馬連3, ワイド4, 馬単5, 三連複6, 三連単7

# サブクエリを2重使用し、【1倍台の馬が連帯したレースのうち】【もう一頭連帯した馬が3番人気だったレース】の馬連払戻金を合計
cur.execute("SELECT DISTINCT p.race_id, p.payoff FROM payoff p \
INNER JOIN race_result r ON p.race_id=r.race_id INNER JOIN race_info i on p.race_id=i.id \
WHERE p.ticket_type=3 AND p.race_id IN \
        (SELECT r.race_id FROM race_result r \
        INNER JOIN race_info i on r.race_id=i.id \
        WHERE r.order_of_finish IN ('1','2') AND r.popularity='3' \
            AND r.race_id IN (SELECT race_id from race_result r INNER JOIN race_info i on r.race_id=i.id \
            WHERE odds<2.0 AND popularity='1' AND order_of_finish IN ('1','2') AND i.surface like '芝%' \
            AND i.place_detail like '%東京%' AND i.distance=2000))")
rows = cur.fetchall()
umaren_sum = 0
for row in rows:
    umaren_sum += row[1]

# 単勝1倍台の馬が出走したレース数を調べる
cur.execute("SELECT count(race_id) from race_result r INNER JOIN race_info i on r.race_id=i.id \
            WHERE odds<2.0 AND popularity='1' AND i.surface like '芝%' AND i.place_detail like '%東京%' \
            AND i.distance=2000")
rows2 = cur.fetchall()

print('単勝1倍台の馬が連帯したレースでの馬連合計:')
print(umaren_sum)

print('単勝1倍台の馬が東京芝2000mに出走したレース数:')
print(rows2[0][0])

print('馬連期待値は')
print(round(umaren_sum / rows2[0][0], 2))

-----結果-----
単勝1倍台の馬が連帯したレースでの馬連合計:
18460.0
単勝1倍台の馬が東京芝2000mに出走したレース数:
115
馬連期待値は
160.52
```
やっと期待値が100を超えました！
このデータを知っておけば2019年の天皇賞秋で【アーモンドアイとダノンプレミアムの馬連】を買っていたことでしょう。

### 単勝1番人気 - 単勝4番人気の馬連
念のため 【1番人気-4番人気】の馬連を購入した場合の期待値も見てみましょう。

```Python:1-4人気の馬連期待値
# race_resultのrace_id とpayoffのrace_idを紐づけて期待値を計算する
# ticket_type 単勝0, 複勝1, 枠連2, 馬連3, ワイド4, 馬単5, 三連複6, 三連単7

# サブクエリを2重使用し、【1倍台の馬が連帯したレースのうち】【もう一頭連帯した馬が4番人気だったレース】の馬連払戻金を合計
cur.execute("SELECT DISTINCT p.race_id, p.payoff FROM payoff p \
INNER JOIN race_result r ON p.race_id=r.race_id INNER JOIN race_info i on p.race_id=i.id \
WHERE p.ticket_type=3 AND p.race_id IN \
        (SELECT r.race_id FROM race_result r \
        INNER JOIN race_info i on r.race_id=i.id \
        WHERE r.order_of_finish IN ('1','2') AND r.popularity='4' \
            AND r.race_id IN (SELECT race_id from race_result r INNER JOIN race_info i on r.race_id=i.id \
            WHERE odds<2.0 AND popularity='1' AND order_of_finish IN ('1','2') AND i.surface like '芝%' \
            AND i.place_detail like '%東京%' AND i.distance=2000))")
rows = cur.fetchall()
umaren_sum = 0
for row in rows:
    umaren_sum += row[1]

# 1倍台の馬が出走したレース数を調べる
cur.execute("SELECT count(race_id) from race_result r INNER JOIN race_info i on r.race_id=i.id \
            WHERE odds<2.0 AND popularity='1' AND i.surface like '芝%' AND i.place_detail like '%東京%' \
            AND i.distance=2000")
rows2 = cur.fetchall()

print('単勝1倍台の馬が連帯したレースでの馬連合計:')
print(umaren_sum)

print('単勝1倍台の馬が東京芝2000mに出走したレース数:')
print(rows2[0][0])

print('馬連期待値は')
print(round(umaren_sum / rows2[0][0], 2))

-----結果-----
単勝1倍台の馬が連帯したレースでの馬連合計:
10030.0
単勝1倍台の馬が東京芝2000mに出走したレース数:
115
馬連期待値は
87.22
```
こちらは100は超えないものの【1-2人気の馬連】よりは期待値が高いことが分かりました。


## まとめ
競馬場、コース、券種を絞ることで期待値計算に深みが出てきました。SQLの扱いにも慣れてきました。
条件さえ変えれば他の競馬場でも分析できるので、お住まいの近くの競馬場の期待値計算をやってみてはいかがでしょうか！

次の目標は、WIN5(設定された5レースの勝ち馬をすべて当てる馬券。最高で数億円の配当)の攻略です！
競馬の馬場読み・展開読みなども分析する必要があるため、難易度は上がりますが、挑戦してみようと思います！

ここまで読んでくださりありがとうございました。