12
10

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 5 years have passed since last update.

Opt TechnologiesAdvent Calendar 2017

Day 18

有馬記念を予想する

Last updated at Posted at 2017-12-23

8の付く日でした。
今日は2017年12月23日です。間も無く24日になります。 発走前ならセーフ

有馬記念とは

クリスマスにサンタが乗る馬を速さで決める大会です 。 違うらしいです。。
競馬の中でも古馬G1の中で1年の最後に行われるG1競争です。
中山競馬場 芝2500mコースで行われ優勝賞金3億円~~(前後賞あわせて4.5億)~~!!
人ももっさり集まって投票券の発売機にすらたどり着けないくらいものすごい大規模なお祭りでもあります。徹夜しないと入場できないとか。
ネットで投票するからいいけどね!

仮説

ぶっちゃけデータの分析ってのは”俺が考えた方法がすごいぜ実証してやるぜ!”みたいな世界です。
競馬の予想も同じですね。

騎手の強さで結構決まる

競馬には 馬七人三 という言葉があります。馬だけの強さだけ考えてもだめってことですね。
競馬マンガとかだと騎手は馬の首掴んで漕いで勝ったりしますね。
普段はあまり見かけないですが、日本でも重賞に出てる外人騎手とかグイグイやってますね。
あとは姿勢とか進路の取り方というのは騎手の仕事です。ムーア騎手とかやばいですね。背中が地面と平行なくらい真っ直ぐです。たぶん空気抵抗ゼロですね。
この騎手の強さ、勝率や獲得賞金額でもいいんですが、こちらをオリジナルの方法で決めます。

数値化(レーティング)

Elo rating というものがあります。読んで字のごとくどれくらいエロいかを示したレートです。もともとはチェスなどで使われその他ゲームやスポーツにも活用されています。
残念ながら競馬は1vs1のスポーツではないので工夫が必要です。

麻雀ゲームでレーティング計算式を見つけました。
cf. http://www.maru-jan.com/game/rating_system.html

レーティングの計算式
(順位点+対局者による変動値) ×0.1※東南戦は2倍

順位点:	四麻 1位 +50 2位 +10 3位 -20 4位 -40
三麻 1位 +50 2位 -10 3位 -40
対局者による変動値:	(同卓者の平均Rt - 自分のRt)÷80
例:	同卓者の平均Rt2000、自分のRt1600の東南戦で1位 の場合
{50+(2000-1600)÷80}×0.1=5.5
東南戦は2倍=11
●Rtが1611に上昇します。
※小数点以下まで計算されますが、表示は整数までです
※Rtが高くなると2位でも減少する場合があります

今回は4位以下は全部4位の計算するって感じにしてこれを使います。

馬が主役である

馬の強さも獲得賞金で近似ってことにすることもできるんですが、どんなにメンバーが弱くても1着賞金は同じっていうのはどうなんだろうってなりますね。
近走2、3着ばかりの未勝利戦を勝った馬と12Rの500万下みたいに掲示板にぜんぜん載ってこなかった馬ばかりのレースの勝ち馬どっちが強そうですか?っていう問題です。
これを現すのもレートってことで騎手と同様に計算することにします。

データを集める

https://github.com/stockedge/netkeiba-scraper
こちらを使用させてもらいます。
2年分のデータを作り、データをsqliteからmysqlに移行したりしました。

レートを計算する

僕も人の子なので難解なSQLを作ることには自信があります。

require 'mysql2'

def connect
  Mysql2::Client.new(:host => 'localhost', :user => 'root', :password => 'root', :database => 'race')
end

=begin
create table jockey_rate
(
id integer primary key auto_increment,
date text not null,
race_number integer not null,
race_id integer not null,
jockey_id text not null,
before_rate integer,
race_rate integer,
after_rate integer
);

create table horse_rate
(
id integer primary key auto_increment,
date text not null,
race_number integer not null,
race_id integer not null,
horse_id text not null,
before_rate integer,
race_rate integer,
after_rate integer
);
=end

def set_rate(race_id, client)
  set_before_rate = "
insert into jockey_rate (date, race_number, race_id, jockey_id, before_rate)
select ri.date, ri.race_number, jr.race_id, jr.jockey_id, jr.before_rate
from
(
  select rs.race_id, rs.jockey_id, COALESCE(before_rate, 1500) as before_rate
  from race_result rs left join (
    SELECT a.id, a.jockey_id, after_rate as before_rate
    FROM jockey_rate a, (SELECT jockey_id, MAX(id) AS id FROM jockey_rate GROUP BY jockey_id) b
    WHERE a.id = b.id and a.jockey_id in (select jockey_id from race_result where race_id = #{race_id})
  ) as j on rs.jockey_id = j.jockey_id where rs.race_id = #{race_id}
) as jr
left join race_info ri on jr.race_id = ri.id
"
  client.query(set_before_rate)

  set_before_rate = "
insert into horse_rate (date, race_number, race_id, horse_id, before_rate)
select ri.date, ri.race_number, jr.race_id, jr.horse_id, jr.before_rate
from
(
  select rs.race_id, rs.horse_id, COALESCE(before_rate, 1500) as before_rate
  from race_result rs left join (
    SELECT a.id, a.horse_id, after_rate as before_rate
    FROM horse_rate a, (SELECT horse_id, MAX(id) AS id FROM horse_rate GROUP BY horse_id) b
    WHERE a.id = b.id and a.horse_id in (select horse_id from race_result where race_id = #{race_id})
  ) as j on rs.horse_id = j.horse_id where rs.race_id = #{race_id}
) as jr
left join race_info ri on jr.race_id = ri.id
"
  client.query(set_before_rate)

  set_race_rate = "update jockey_rate
                   set race_rate = (select abr
                                    from (select avg(before_rate) abr
                                          from jockey_rate
                                          where race_id = #{race_id}) tmp)
                                    where race_id = #{race_id}"
  client.query(set_race_rate)

  set_race_rate = "update horse_rate
                   set race_rate = (select abr
                                    from (select avg(before_rate) abr
                                          from horse_rate
                                          where race_id = #{race_id}) tmp)
                                    where race_id = #{race_id}"
  client.query(set_race_rate)

#{50+(2000-1600)÷80}×0.1

  set_race_rate = "update jockey_rate jr
                   left join (
                   select jr.jockey_id, jr.race_id,
                   before_rate + (CASE WHEN order_of_finish='1' THEN (50 + (jr.race_rate - jr.before_rate) / 80) * 0.1
                         WHEN order_of_finish='2' THEN (10 + (jr.race_rate - jr.before_rate) / 80) * 0.1
                         WHEN order_of_finish='3' THEN (-10 + (jr.race_rate - jr.before_rate) / 80) * 0.1
                    ELSE (-40 + (jr.race_rate - jr.before_rate) / 80) * 0.1 END) as after_rate
                   from jockey_rate jr left join race_result rr on jr.race_id = rr.race_id and jr.jockey_id = rr.jockey_id
                   where jr.race_id = #{race_id}) as tmp
                   on jr.jockey_id = tmp.jockey_id and jr.race_id = tmp.race_id
                   set jr.after_rate = tmp.after_rate
                   where jr.race_id = #{race_id}"
  client.query(set_race_rate)

  set_race_rate = "update horse_rate jr
                   left join (
                   select jr.horse_id, jr.race_id,
                   before_rate + (CASE WHEN order_of_finish='1' THEN (50 + (jr.race_rate - jr.before_rate) / 80) * 0.1
                         WHEN order_of_finish='2' THEN (10 + (jr.race_rate - jr.before_rate) / 80) * 0.1
                         WHEN order_of_finish='3' THEN (-10 + (jr.race_rate - jr.before_rate) / 80) * 0.1
                    ELSE (-40 + (jr.race_rate - jr.before_rate) / 80) * 0.1 END) as after_rate
                   from horse_rate jr left join race_result rr on jr.race_id = rr.race_id and jr.horse_id = rr.horse_id
                   where jr.race_id = #{race_id}) as tmp
                   on jr.horse_id = tmp.horse_id and jr.race_id = tmp.race_id
                   set jr.after_rate = tmp.after_rate
                   where jr.race_id = #{race_id}"
  client.query(set_race_rate)
end

def race_main
  client = connect
  select_query = "select id from race_info order by date, race_number"
  race_id = client.query(select_query)
  race_id.map{|r|
    set_rate r['id'],client
    puts "#{r['id']}"
  }
end

確認

ジョッキーレートを確認しましょう

SELECT a.id, a.jockey_id, after_rate as rate
FROM jockey_rate a, (SELECT jockey_id, MAX(id) AS id FROM jockey_rate GROUP BY jockey_id) b
WHERE a.id = b.id and a.jockey_id in (
'01032',
'00666',
'05339',
'01122',
'01075',
'05386',
'01014',
'05203',
'00663',
'05529',
'01043',
'05366',
'01115',
'05212',
'01088',
'05473'
);
+--------+-----------+-------------+
| id     | jockey_id | rate               |
+--------+-----------+-------------+
| 154879 | 00663     |       -1710 |
| 155109 | 00666     |        -930 |
| 155115 | 01014     |        -961 |
| 155158 | 01032     |       -1320 |
| 155189 | 01043     |       -1569 |
| 155068 | 01075     |       -1451 |
| 155156 | 01088     |        -708 |
| 155151 | 01115     |       -1023 |
| 155193 | 01122     |        -742 |
| 155154 | 05203     |       -1753 |
| 155069 | 05212     |        -395 |
| 155162 | 05339     |        -399 |
| 151618 | 05366     |        1129 |
| 155192 | 05386     |       -1072 |
| 155113 | 05473     |         907 |
| 155190 | 05529     |        1272 |
+--------+-----------+-------------+

あれーーー????
競馬が基本的に負ける勝負だということを忘れてました
リーディングともなると年間数百と負けるのであっという間にマイナスです
これでは使えないですね。。。

馬レートを確認しましょう

SELECT a.id, a.horse_id, after_rate as before_rate
FROM horse_rate a, (SELECT horse_id, MAX(id) AS id FROM horse_rate GROUP BY horse_id) b
WHERE a.id = b.id and a.horse_id in
(
'2012102774',
'2012102013',
'2012104105',
'2014106404',
'2012104733',
'2014106038',
'2013105880',
'2013106183',
'2011102151',
'2012104759',
'2012104669',
'2012104668',
'2012104870',
'2014106083',
'2008103192',
'2011104377'
);
+--------+------------+-------------+
| id     | horse_id   | before_rate |
+--------+------------+-------------+
| 145597 | 2008103192 |        1481 |
| 144144 | 2011102151 |        1502 |
| 149954 | 2011104377 |        1478 |
| 149953 | 2012102013 |        1526 |
| 149965 | 2012102774 |        1481 |
| 147046 | 2012104105 |        1496 |
| 149961 | 2012104668 |        1500 |
| 147056 | 2012104669 |        1492 |
| 147042 | 2012104733 |        1488 |
| 149950 | 2012104759 |        1503 |
| 147049 | 2012104870 |        1496 |
| 149962 | 2013105880 |        1504 |
| 149958 | 2013106183 |        1471 |
| 150728 | 2014106038 |        1509 |
| 145593 | 2014106083 |        1514 |
| 150731 | 2014106404 |        1509 |
+--------+------------+-------------+

これはまぁ良さそうですね。思ったほどレート高くないのが気になりますが。
現役最強馬キタサンブラックのIDは2012102013です。レートは1526

ちょっとレート1520超えてる馬を確認しましょう

mysql> SELECT a.id, a.horse_id, after_rate     FROM horse_rate a, (SELECT horse_id, MAX(id) AS id FROM horse_rate GROUP BY horse_id) b     WHERE a.id = b.id and after_rate >= 1520;
+--------+------------+------------+
| id     | horse_id   | after_rate |
+--------+------------+------------+
|  68759 | 2010103225 |       1526 |
| 141181 | 2011101125 |       1536 |
| 149953 | 2012102013 |       1526 |
| 123554 | 2012105398 |       1521 |
| 111555 | 2013104508 |       1522 |
| 149940 | 2013104699 |       1522 |
| 118440 | 2013105415 |       1520 |
|  58435 | 2013105906 |       1521 |
| 107437 | 2013106101 |       1524 |
| 151618 | 2013106119 |       1522 |
| 145549 | 2013106148 |       1520 |
| 149951 | 2014106201 |       1522 |
+--------+------------+------------+
12 rows in set (3.29 sec)

少ない
一番強いことになってる馬はレート1536ですね。もっと2000とか行くの期待してました。
ちなみにこの馬
オジュウチョウサンです!!!
さすが絶対王者!王者は格が違ったっ!!

ちなみにキタサンブラックと同じ1526のレートの馬はニホンピロバロンです。去年(そして今年も)の最優秀障害馬オジュウチョウサン、その前年まで王者だったアップトゥデイトのどちらにも勝ったことがある馬です。かっこいい

予測する

先人の知恵を参考にランダムフォレストで予測します

訓練データ出力

難解なSQLなら任せて

select rr.race_id,frame_number,horse_number,sex,age,basis_weight,
jr.before_rate as jockey_before_rate,
jr.race_rate as jockey_race_rate,
hr.before_rate as horse_before_rate,
hr.race_rate as horse_race_rate,
hr.after_rate - hr.before_rate as result
from race_result rr
left join jockey_rate jr on rr.race_id = jr.race_id and rr.jockey_id = jr.jockey_id
left join horse_rate hr on rr.race_id = hr.race_id and rr.horse_id = hr.horse_id
  INTO OUTFILE 'horse.csv'
  FIELDS TERMINATED BY ','
  OPTIONALLY ENCLOSED BY '"';

ランダムフォレスト

# TBD
# 予測
pred_result<-predict(result,data=pred_data)$predictions

結果

> pred_result
 [1] -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4
Levels: -1 -4 \\N 1 5

あ、これはいけない
もっと派手に数字が動くレーティングをしないといけなそうですね。
悪くないと思ったんですが有馬記念の予想については時間切れです、残念・・・

有馬記念予想

せっかくなので馬レートを参考にしましょう

| 2012102774 |        1481 |
| 2012102013 |        1526 |
| 2012104105 |        1496 |
| 2014106404 |        1509 |
| 2012104733 |        1488 |
| 2014106038 |        1509 |
| 2013105880 |        1504 |
| 2013106183 |        1471 |
| 2011102151 |        1502 |
| 2012104759 |        1503 |
| 2012104669 |        1492 |
| 2012104668 |        1500 |
| 2012104870 |        1496 |
| 2014106083 |        1514 |
| 2008103192 |        1481 |
| 2011104377 |        1478 |

初期値1500を維持するのも大変ということがわかったので1500以上を中心にこんな感じで

◎ キタサンブラック 1526
○ スワーヴリチャード 1514
▲ ブレスジャーニー 1509
▲ サトノクロニクル 1509
△ シャケトラ 1504
△ サトノクラウン 1500

3連複キタサン軸流しで10点

12
10
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
12
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?