トップ 差分 一覧 ソース 検索 ヘルプ PDF RSS ログイン

Rと競馬データで学ぶ統計学 番外編 Rで有馬記念を当てましょう2016

キーワード

この記事はRと競馬データで学ぶ統計学シリーズの一部です。また、R Advent Calendar 2016の参加記事です。

はじめに

有馬記念ですよ、みなさん!


JRA公式 1984-2015有馬記念(サムネイルは93年、トウカイテイオーの年)

中央競馬(JRA)の1年を締めくくるグランプリ・有馬記念です。12月25日(日)15:25にドリームレースのゲートが開きます。この記事が投稿される12/21に、「ドルオタ馬券師ピッチマン」ことNYYの田中将大投手が抽選を行い、枠順が確定しました。

第61回有馬記念 出馬表


 思い返せば...(隙あらば自分語り)

今年は「競馬×データサイエンス」の普及活動にいそしんだ1年でした。2015年に楽天競馬ハッカソンに参加して、「この領域、盛り上げられれば、もっと楽しくなるんじゃないか」と、自分でも機械学習(主にランダムフォレスト)を使って中央・地方の馬券を買いつつ、同様に(というか、より高度に)競馬に対してデータサイエンス的なアプローチをしている方に声を掛けて、「ウマナリティクス(Uma + Analytics)」というイベントを開催しました。

ウマナリティクス グループページ

第54回Tokyo.Rの発表資料

筆者はもちろん競馬側からのアプローチですが、逆に「競馬を見たこともない、関心もなかった」というデータサイエンス側の方も、競馬の周辺にあるデータに興味を持って、予測にハマっていく人が多くいたようです。「ウマナリティクス」のイベントを通じて、いくつかの競馬人工知能プロジェクトが立ち上がり、回収率100%(=儲かる)を超える成果を出しています。筆者も、機械学習によって14万馬券を当てるなど、(ごく稀に)オイシい思いをしました。[1]


また、「競馬×データサイエンス」のリーチ拡大を狙い、このRと競馬データで学ぶ統計学シリーズを作成しています。グローバルなデータサイエンス・コンペであるKaggleでも競馬に関するお題があるように、競馬とデータサイエンスは相性の良い領域だと思います。



2017年も、「競馬×データサイエンス」の可能性を追求し、もっと多くの人に競馬の楽しさを伝えていければと思っています。

 ということで...

本日は、Rで有馬記念を当てましょう!という“意気込み”のもと、機械学習アルゴリズムを使い、現時点で得られるデータから、有馬記念を予測します。

そもそも有馬記念とは

競馬に強い関心がない方も、競馬と言えば「ダービー、有馬記念」というイメージがあるのではないでしょうか。有馬記念は、そのような一種の「お祭り」です。何年か前まで、“世界で最も多くの馬券が売れる”レースでした。

有馬記念は、船橋市の中山競馬場・芝2500mで開催されます。GIレースの中でも天皇賞・春(3200m)に次いで長い距離で行われ、各年代のトップホースが集結し、熱戦が繰り広げられます。

http://www.jra.go.jp/facilities/race/nakayama/course/img/pic_course_3d.jpg
中山競馬場のコース見取り図(出典: JRAホームページ「コース紹介:中山競馬場」

レースとしては、コーナーを6回まわる、トリッキーなコース形態に加え、直線に急坂(高低差2.2m)があること、1年間大レースを戦ってきた中での体調などから、大逆転劇や人気薄の激走が見られます。過去5年の平均配当で見ても、馬連(1、2着を順不同で当てる)で53.9倍、三連単(1、2、3着を順番通り当てる)では686.42倍と、“荒れやすい”レースと言えます。とはいえ、1番人気は5年で3勝、3着1回と確実に馬券圏内に入っており、どちらかというと、人気馬+人気薄の組み合わせをうまく拾うことが的中のポイントになりそうです。[2]

2015年有馬記念・勝ち馬ゴールドアクター
出典: 第60回有馬記念優勝ゴールドアクター&吉田隼人【151227中山10R有馬記念】
ライセンス: クリエイティブ・コモンズ(表示 - 非営利 - 継承)


有馬記念(競馬)について語りだしたら、それだけで長大になってしまう[3]ので、このへんで。

使用するパッケージ

以下のパッケージ群を使用します。

library(ranger)
library(caret)
library(dplyr)
library(RcppRoll)

使用するデータ

有馬記念に限らず、競馬を予測するためのデータは、さまざまな場所から入手可能です。詳しくは、このシリーズ第1回で紹介していますので、ご参照ください。今回は、筆者が収集、公開しているデータを使用します。


GitHub上のデータは2013年1月から2016年11月までですが、何頭か、12月も出走している馬がいるため、手元で12月分のデータを追加して使います。データの構造についても、上記第1回の記事で紹介しています。

データはCSV形式なので、read.csv 関数で読み込みます。また、開催日を日付型(Date 型)に変換します。

jra_data <- read.csv("jra_race_result.csv", header=TRUE)
jra_data$開催日 <- as.Date(jra_data$開催日)

str(jra_data)
'data.frame':	193830 obs. of  30 variables:
 $ 開催日      : Date, format: "2013-01-05" "2013-01-05" ...
 $ 競馬場      : Factor w/ 10 levels "京都","阪神",..: 7 7 7 7 7 7 7 7 7 7 ...
 $ レース番号  : int  1 1 1 1 1 1 1 1 1 1 ...
 $ レース名    : Factor w/ 1130 levels "1932-1950sダービーメモリーズトキノミノルカップ",..: 152 152 152 ...
 $ コース      : Factor w/ 3 levels "ダート","芝",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ 周回        : Factor w/ 8 levels "右","右2周","左",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ 距離        : int  1200 1200 1200 1200 1200 1200 1200 1200 1200 1200 ...
 $ 馬場状態    : Factor w/ 4 levels "重","不良","良",..: 3 3 3 3 3 3 3 3 3 3 ...
 $ 賞金        : num  500 200 125 75 50 0 0 0 0 0 ...
 $ 頭数        : int  16 16 16 16 16 16 16 16 16 16 ...
 $ 着順        : int  1 2 3 4 5 6 7 8 9 10 ...
 $ 枠番        : int  6 2 7 1 5 5 3 2 4 3 ...
 $ 馬番        : int  12 4 13 1 10 9 6 3 7 5 ...
 $ 馬名        : Factor w/ 24373 levels "アーカイブ","アークアーセナル",..: 22934 350 2327 ...
 $ 性別        : Factor w/ 3 levels "セ","牡","牝": 2 3 3 2 2 2 2 2 3 3 ...
 $ 年齢        : int  3 3 3 3 3 3 3 3 3 3 ...
 $ 騎手        : Factor w/ 258 levels "A.アッゼニ","A.シュタルケ",..: 76 169 75 62 141 136 241 89 137 91 ...
 $ タイム      : num  73.6 73.6 73.7 74 74.3 74.3 74.3 74.4 74.6 74.8 ...
 $ 着差        : Factor w/ 22 levels "","1 1/2馬身",..: 1 20 20 4 4 20 19 11 3 3 ...
 $ 通過順      : Factor w/ 8699 levels "","01-01","01-01-01",..: 2 233 2279 1201 6227 4371 233 7300 4183 6227 ...
 $ 上り3F      : num  39.4 39.3 39.1 39.5 38.9 39.4 40 38.8 39.7 39.4 ...
 $ 斤量        : num  56 54 54 56 56 56 53 56 54 54 ...
 $ 馬体重      : int  484 454 396 484 462 474 506 448 448 476 ...
 $ 増減        : int  2 -2 0 6 2 8 -4 8 -4 -2 ...
 $ 人気        : int  1 7 5 2 3 6 10 14 11 4 ...
 $ オッズ      : num  1.9 44.1 11.8 5.3 6.1 ...
 $ ブリンカー  : Factor w/ 2 levels "","B": 1 1 1 1 1 1 1 1 1 1 ...
 $ 調教師      : Factor w/ 321 levels "C.チャン","C.ファウンズ",..: 120 204 57 27 133 64 256 77 221 282 ...
 $ 調教コメント: Factor w/ 181 levels "nan","いま一息",..: 46 102 46 74 41 9 57 180 95 13 ...
 $ 調教評価    : Factor w/ 5 levels "A","B","C","D",..: 2 3 2 2 2 3 3 3 3 3 ...

上記のデータが、いわゆる素データですが、ここに合成変数を追加します。

# 人気と実際の着順の差分(プラスだったら、人気薄の激走、マイナスだったら人気を裏切った)
jra_data$期待 <- jra_data$人気 - jra_data$着順

# 近5走の平均着順
jra_data <- jra_data %>% group_by(馬名) %>%
mutate(平均着順 = roll_meanr(着順, n = 3, na.rm = T), 間隔 = 開催日 - lag(開催日))

また、データにいくつかの加工を行います。

jra_data$間隔 <- as.numeric(jra_data$間隔)

# 新馬など出走間隔がない馬は0日とする
jra_data[is.na(jra_data$間隔),]$間隔 <- 0

# 何らかの欠損値があるレコードを除外する
jra_data <- na.omit(jra_data)

# 確実に出走間隔のデータがある2014年以降のデータを使用する
jra_data <- jra_data[jra_data$開催日 >= "2014-01-01",]

そして、予測において不要な(筆者が使いきれない)データを除外します。なお、今回は、本日(12/21)の段階で予測するため、レース直前に判明するデータ(馬体重、増減、人気、オッズ)も除外します。

jra_data <- jra_data[setdiff(colnames(jra_data),
c("馬体重", "増減", "人気", "オッズ", "周回", "着差", "通過順", "ブリンカー"))]

これで、予測モデルを作るための教師データができました。

有馬記念予測モデルの構築

次に、教師データを使い、未知(発走前)のレース結果を予測するためのモデルを作ります。

本当は、ここでいくつかの機械学習アルゴリズムを使い分けたかったのですが、いかんせん時間がなく、今回は普段使っているランダムフォレストだけを紹介します。すいません。

さて、Rでランダムフォレストを実行するためのパッケージもたくさんありますが、ここでは、「とにかく簡単」に使える ranger パッケージを採用します。

そして、いきなり雑になりますが、以下のようなコードを実行します。


「競馬を予測する」といったときには、基本的にレース結果から得られる何らかの値を回帰ないし判別することになります。レース結果から得られる値として、着順(1着、2着...)、タイム(2分30秒4...)、獲得賞金(1着3億円、2着1億2000万円...)などがあります。他に、馬券内(1着、2着、狙う券種により3着)に入るかどうか、という1/0のフラグも考えられます。

筆者も、この1年ほどいろんな目的変数を試した結果、「中央競馬では賞金を回帰したらいいんじゃないか」と思っています。一方で、地方競馬では着順を回帰したほうが精度はいいような気がします。ということで、ここでは以下の1行で、最終的に賞金額を他の変数で回帰しています。

result<-ranger(賞金~.,data=data,mtry=best_mtry,min.node.size=best_nodesize,
num.trees=800,write.forest=T,importance="impurity")

ranger パッケージにはモデルの評価をする機能もあり、上記のコードを実行して得られた result オブジェクトには以下のような情報が含まれています。

Ranger result

Call:
 ranger(賞金 ~ ., data = data, mtry = best_mtry, min.node.size = best_nodesize,
 num.trees = 800, write.forest = T, importance = "impurity") 

Type:                             Regression 
Number of trees:                  800 
Sample size:                      114136 
Number of independent variables:  24 
Mtry:                             16 
Target node size:                 1 
Variable importance mode:         impurity 
OOB prediction error:             53332.02 
R squared:                        0.7799033

rangerパッケージのマニュアル(PDF)によると、OOB prediction error は回帰の場合、Mean squared error(平均二乗誤差)だそうです。また、R squared は重相関係数の2乗です。単純には、まずまず当てはまりがよさそうです(パッケージをほぼデフォルトで動かしただけですが)。

実際の獲得賞金と予測値のプロット[4]


有馬記念の予測

ともあれ、モデルはできたので(現在23:30)、有馬記念を予測しましょう。また、雑ですが以下のコードを実行します。


さぁ、注目の予測結果は...


マ ル タ ー ズ ア ポ ジ ー

ないな、うん。


注釈

  • [1]そしてそれ以上に大変な額の負けを経験しました。
  • [2]こういうのを書くのは簡単なんですけどね。
  • [3]すでになってますが。
  • [4]自分自身を回帰してるので合って当たり前ではありますが