2014年7月31日木曜日

距離による時間差(2)

距離による時間差の記事で、2013年の京都の芝1200mと2400mの上位3頭平均タイムを示したが、クラスが上なのに遅いタイムの部分があった。これは、馬場が不良のときのデータも含んでいたのが理由のように思われるので、良馬場だけのデータにする。

2013年の京都の芝1200mの上位3頭の平均タイムは、以下である。
新馬(1レース) 1:10:1
未勝利(5レース) 1:09:4
500万下(4レース) 1:08:7
1000万下(7レース) 1:08:5
1600万下(2レース) 1:08:7
オープン(4レース) 1:07:8
GIII (2レース) 1:08:0
で、2400mは以下である。
未勝利(1レース) 2:26:2
500万下(4レース) 2:27:4
1000万下(4レース) 2:27:2
1600万下(3レース) 2:25:1
GII(2レース) 2:24:0

未勝利と500万下の差は、0.7秒と-1.2秒、
500万下と1000万下の差は、0.2秒と0.2秒、
1000万下と1600万下の差は、-0.2秒と2.1秒
である。少しはましになったが、やっぱり、きれいな差にはなっていない。スピード指数で比較するのは、難しいのではないか。

2014年7月30日水曜日

距離による時間差

同じ秒差でも、距離によって異なる価値がある。ということで、スピード指数では、以下のように距離指数というのを使って、距離による時間差を吸収している。
この指数は 1秒÷基準タイム×1000 の計算式から求められたものです。
実際のところ、どれぐらい差があるのだろうか。2013年の京都の芝1200mの上位3頭の平均タイムは、以下である。
新馬(2レース) 1:10:2
未勝利(5レース) 1:09:4
500万下(5レース) 1:08:9
1000万下(7レース) 1:08:5
1600万下(4レース) 1:09:1
オープン(5レース) 1:08:3
GIII (2レース) 1:08:0
で、2400mは以下である。
未勝利(1レース) 2:26:2
500万下(4レース) 2:27:4
1000万下(5レース) 2:27:6
1600万下(3レース) 2:25:1
GII(2レース) 2:24:0

未勝利と500万下の差は、0.5秒と-1.2秒、
500万下と1000万下の差は、0.4秒と-0.2秒、
1000万下と1600万下の差は、-0.6秒と2.5秒
である。きれいな差にはなっていない。2400mは未勝利のほうが500万下、1000万下より速いやんか。結局、データ数がそれほどないため、ほとんど誤差と言えるのだろう。

2014年7月29日火曜日

オープンにしかない距離

基準タイムを計算しようとしたとき、500万下、1000万下にない距離はどうすればいいのか。とりあえず、オープン以上しかないレースというのがどれだけあるのだろうか。2013年で見てみると(障害はのぞく)、

京都 芝3000m オープン平場と、GIのみ
京都 芝3200m GIのみ
阪神 芝3000m GIIのみ
中山 芝3600m GIIのみ
東京 芝2500m GIIのみ
東京 芝3400m GIIIのみ

これだけあるようだ。これらの距離は、オープンのタイムをもとに基準タイムを作るしかない。逆に言えば、これらのレースタイムは誤差が大きくなるだろうな。

2014年7月28日月曜日

例外的なデータ(2)

どんな距離のレースがあるんだろうとデータを見ていて気付いた。23600になっているデータがある。何じゃこりゃと思うと、これはダイヤモンドステークス(3600m)から取得したデータのよう。Yahooのページで確認してみると

芝・右・内2周 3600m [コースガイド

うーん、やられた。距離の部分を抜き出すために、このデータを、


Integer.parseInt(course.replaceAll("[^0-9]", ""));

として、数字の部分以外を削除していた。その結果、内2周の2も抜き出されてしまった。さて、どうやって切り出そうかな。[スペース]とmの間で抜き出すか。

String[] distances = course.split(" ");
for (String distance: distances) {
if (distance.endsWith("m")) {
return Integer.parseInt(distance.replaceAll("[^0-9]", ""));
}

}

上記のように、抜き出す部分を限定してみる。

2014年7月27日日曜日

基準タイムに何を使えばいいのか(2)

基準タイムに500万下、1000万下の平均タイムを使うとしても、そのクラスにはない距離というのがある。長距離、たとえば3200mなんてオープンにしかないし。ということは、その基準タイムは、オープンをもとに決めるしかない。したがって、オープンのタイムがこれぐらいだから、500万下もしくは1000万下だとこれぐらいと出すことになるのだろう。ということで、クラスごとにどれぐらいの差があるのかを調査中。たとえば、2013年の阪神競馬場芝1600mの平均(上位3頭)は以下。

新馬(8レース) 1:37:5
未勝利(13レース) 1:35:7
500万下(8レース) 1:34:8
1000万下(7レース) 1:34:5
1600万下(2レース) 1:33:8
オープン(4レース) 1:33:5
GIII(2レース) 1:35:1
GI(2レース) 1:34:5

これを見ると、何となく妥当なタイムが出ているように思うが、GIなどはレース数が少ない分、馬場の状態などで大きな違いが出る気がする。

2014年7月26日土曜日

基準タイムに何を使えばいいのか

500万下と1000万下の距離ごとの平均をとり、基準タイムとすることを書いた。しかし、実際に平均を取ってみると、不思議なデータもある。

2013年函館の芝2000mのデータ
500万下 8レース上位3頭の平均 2:03:8
1000万下 4レース上位3頭の平均 2:05:8

500万下のほうが2秒も速い。本来は、1000万下のほうがクラスが上なのだから速いはずである。しかし、たった4レースの平均だとすると、これぐらい差が出てもしかたないのかもしれない。もう少し調べてみよう。で、上記の対象になっているレースは、以下。
1回函館1日12レース 500万下 (混合)(特指) 定量 馬場:良 タイム: 2.00.6

1回函館3日8レース 500万下 [指定] 定量 馬場:良 タイム: 2.01.1
1回函館5日12レース 1000万下 (混合)(特指) 定量 馬場:良 タイム: 2.03.4
2回函館2日10レース 500万下 (混合)(特指) 定量 馬場:良 タイム: 2.02.1
2回函館3日10レース 500万下 (混合)(特指) 定量 馬場:良 タイム: 2.01.6
2回函館6日10レース 1000万下 (混合)(特指) 定量 馬場:良 タイム: 2.01.7
3回函館1日10レース 500万下 (混合)(特指) 定量 馬場:良 タイム: 2.02.0
3回函館3日10レース 500万下 (混合)(特指) 定量 馬場:良 タイム: 2.01.8
3回函館6日10レース 1000万下 (混合) 定量 馬場:良 タイム: 2.02.5
4回函館2日9レース 500万下 (混合)(特指) 定量 馬場:重 タイム: 2.06.9
4回函館5日12レース 1000万下 (混合)[指定] ハンデ  馬場:不良 タイム: 2.15.2
4回函館6日8レース 500万下 (混合)(特指) 定量 馬場:重 タイム: 2.12.5
これを見ると、500万下は、開催の最初のほうで行われていて、1000万下は後半だ。芝が荒れていたせいかもしれない。また、馬場不良で行われた4回函館5日12レースの非常に遅いタイムが効いているとも言える。

過去のデータはというと、
2012年
500万下 3レース上位3頭の平均 2:02:3
1000万下 2レース上位3頭の平均 2:02:4
2011年
500万下 3レース上位3頭の平均 2:02:4
1000万下 2レース上位3頭の平均 2:01:7

で安定しているように見える。馬場が悪いときのデータは外すべきか。

2014年7月25日金曜日

基準タイム

競馬でどの馬が勝つかはいろんな要因があるものの、速く走ったほうが勝ち、ということから考えると、やはり走破タイムが重要と言える。そして、競馬場によって走りやすさに差があることから、異なる競馬場での走破タイムをそのまま比較することは出来ないだろう。で、競馬場ごとの距離ごとに基準タイムを作って、比較しやすいようにするのが、第一ステップである。西田式スピード指数では、


各競馬場ごとに500万条件と1000万条件で行われたレースの1~3着に入着した馬のタイムの平均を求めてさらに平均したものです。
つまり各競馬場の各距離ごとに500万条件と1000万条件の中間に位置するモデル馬を想定して、これらが出すであろうタイムが基準タイムなのです。

になっている。同条件のレースは多くあることや、実力の拮抗ぐらいから考えて、この値を使うぐらいしかいい手はないかな、やっぱり。各距離と書かれているが、内回りと外回りは分けるべきかもしれない。分けようと思った場合、Yahoo競馬のページでは、
芝・右・外 1600m
のように外の場合のみ記述されているようだ。なので、このコース情報に"外"が含まれているかどうかを判定すればいいのだろう。また、500万、1000万条件を抜き出すには、
1000万下 (混合)[指定] 定量
のように書かれている条件に1000万下と書かれているかどうかで抜き出す。なお、2001年に900万下だったのが1000万下に変更になっている。Yahoo競馬のデータから見ると、変更になったのは、1回函館1日(2001/6/9)が最初のように見えるが、同時開催の東京・中京は900万のままだ。よくわからないな。とりあえず、900万下も1000万下も同じとしてマッチングすれば抜き出せるだろう。また、2歳の500万下と3歳以上の500万下には差がありそうな気がするが、平均だし、まずは気にせず同じものと取り扱うことにする。

この基準タイムは、予想するレースより、過去のレースのデータから求めないといけない。予想ソフトを作るのだから、当たり前だが、予想するレースが実施される前に手に入れられるデータのみを使うように制限する必要がある。ということで、基準タイムは、予想するレースの前年度のデータを使うことにするか。つまり、今(2014年)なら、2013年のデータを使うことになる。しかし、競馬場の改装工事があった場合、前年度データがないことも考えられるので、2年分の平均をとることにする。これで、データがないことを防げるだろう。

2014年7月24日木曜日

データからのタイムの抜き出し方

基準タイムを算出するため、500万下や1000万下の平均タイムを出してみると、1000mのタイムが変な値に。なぜか調べてみると、多くの場合、1分を切るからだ。Yahoo競馬では、タイムは、x.xx.xの形式で書かれる。そのため、"."でsplitして、それぞれを計算していたのだが、xx.xになるケースを考えていなかった。

String[] times = time.split("\\.");
if (times.length == 2) {
return Integer.parseInt(times[0]) * 10 + Integer.parseInt(times[1]);
} else if (times.length == 3) {
return Integer.parseInt(times[0]) * 600 + Integer.parseInt(times[1]) * 10 + Integer.parseInt(times[2]);
} else {
return -1;

}

のように場合分けすることで、おかしなデータはなくなった。

2014年7月23日水曜日

一つのレースだけが中止になることもある

Yahoo競馬からデータの取得を続けていると、なぜか、解析できないレースがあった。それにアクセスしてみると、出馬表にリダイレクトされる。転送先の出馬表を見てみると、「強風のため中止」とある。で、このレースは、「第126回中山大障害(J・GI)」。他のレースはできるが、このレースは障害レースであり、他の平地のレースは強風でもできたということだ。

HttpGet hg = new HttpGet(url);
List<Header> headers = new ArrayList<Header>();
headers.add(new BasicHeader("Accept-Charset","utf-8"));
headers.add(new BasicHeader("User-Agent", "HorseRaceAnalyzer"));
RequestConfig config = RequestConfig.custom().setRedirectsEnabled(false).build();
HttpClient hc = HttpClientBuilder.create()
.setDefaultRequestConfig(config)
.setDefaultHeaders(headers).build();

HttpResponse hr = hc.execute(hg);
int status = hr.getStatusLine().getStatusCode();
if (status != HttpStatus.SC_OK) {
return null;
}
return EntityUtils.toString(hr.getEntity(), "UTF-8");

Webページをhttp-getする際に、リダイレクトを止めることにした。そして、このレースのデータはエラーとして取り扱い、データとして取得しないことにする。その部分を含めて、http-getの部分のソースは上記。

2014年7月22日火曜日

例外的なデータ

Webページを解析するで、書いたように、Yahoo競馬のWebページをもとに競馬のデータを取得しようとしていて、前後の文字列を使用した。これで、データを取れるのだが、レースによってエラーが出る。すべてのデータを確認して、仕様を予測したのではないので、例外的なデータの場合があるからだ。まずややこしいのは払戻金の情報。昔は、三連単は2004年から三連複は2002年から始まったので、それより昔は当然ない。馬連は8頭立て以下では存在しないとか、競馬をわかっている人には当たり前のことではあるが、ややこしい。同着も存在する。それらを考慮してデータ構造を作った上で、取り出さなければならない。リスト型を使い、ない場合を含めていくつになることもありうるようにした。

他、競争中止になったときには、着順やタイム、上がり3ハロンなどの情報もなくなる。着順には数字が書かれるかわりに中止書かれている。競争除外になれば、それに加えて、オッズや馬体重、人気、通過順位もなくなることになる。値が空欄になったり、"-"になったりする。エラーが出るごと、これらを受け入れられるように修正した。

降着があるのも注意点。"5(3)"のように書かれている。こう書かれている場合は、もともとは3着だったのが5着に変更されたということのようだ。降着の取り扱いをデータ上どうするか悩んだが、そのレースの他の馬については降着で訂正される前の着順は書かれていない。なので、降着を反映した後のデータを取り扱うのがいいのだろう。なので、上記の例では、(3)の部分は捨てることになる。

 String[] strorders = strorder.split("\\(");
 horse.order = Integer.parseInt(strorders[0].replaceAll("[^0-9]", ""));

上記のようにして、括弧の前の部分だけを取り出し。"("でsplitするときは、"\\"を前につける必要がある。このことを最初知らずに少しだけはまった。

2014年7月21日月曜日

Webページを解析する(2)

Webページからデータを取得するということは、表示上の値を取り扱わないと行けないということである。払戻金は、たとえば、1,020円のように、3桁ごとに","が打たれている。これらは取り除かないと行けない。

 Integer.parseInt(money.replaceAll("[^0-9]", ""));

上記のようにString#replaceAllを使うことで、数字だけにした上で、intに変換できる。

また、タイムは1.09.6のような形で記述されている。最小の単位が0.1秒であるため、これをベースとした整数値として取り扱うことにし、以下のように変換した。

 String[] times = time.split("\\.");
 horse.time = Integer.parseInt(times[0]) * 600 + Integer.parseInt(times[1]) * 10 + Integer.parseInt(times[2]);

"."でsplitするときは、"\\"でエスケープする必要がある。ところで、Macでは、¥と\は区別される。"\"は[option]+[¥]で入力できる。

2014年7月20日日曜日

Webページを解析する

Yahoo競馬からデータを取得すると決めた。APIが用意されているわけではないので、HTMLを解析し、必要な部分を抜き出さなくてはならない。

public HtmlAnalyzer(String html) {
buf = new StringBuffer(html); 

}
public String getData(String begin, String end) {
int ibegin = buf.indexOf(begin);
if (ibegin == -1) return null;
ibegin += begin.length();
buf.delete(0, ibegin);
int iend = buf.indexOf(end);
if (iend == -1) return null;
String ret = buf.substring(0, iend);
buf.delete(0, iend + 1);
return ret;

}

上記のように、HTMLをStringBufferに入れて、解析した部分から消していくことにした。解析として、ある文字列とある文字列の間の文字列を取り出す関数を作った。そして、HTMLのソースファイルを見ながら、必要な部分の前後の文字列を切り出し、上記関数getDataの引数として与えるようにした。
<p id="raceTitDay" class="fntSS">2000年1月5日(水) <span>|</span> 1回中山1日 <span>|</span> 10:00発走</p> 
HTMLのソースファイルで、日付と開催日と場所の情報は上記のように書かれている。そこで、以下のようにgetDataを呼び出してデータを取得するようにした。

HtmlAnalyzer ha = new HtmlAnalyzer(html);
Race race = new Race();
race.horses = new ArrayList<Horse>();
String date = ha.getData("<p id=\"raceTitDay\" class=\"fntSS\">", "<span>");
SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日");
race.date = format.parse(date);
race.termplaceday = ha.getData("</span>", "<span>").trim();

多くのデータがあるため、かなり面倒だが、基本的には同じように書いて行けばよい。難しいのは、どこまで可変でどこまで固定なのかということだ。HTML自体は、データを埋め込んで自動生成しているのだろうから、基本的には同じ枠組みで作られているはずだ。しかし、どこが埋め込み部分なのか判断が難しい場合もあるので、試行錯誤が必要だろう。

2014年7月19日土曜日

どのプログラミング言語で作るか

久しぶりに、競馬予想ソフトを作ると決めたものの、どのプログラミングで作るのがいいのか。それを決めるための最も大きな要件は、どのデバイスで使うのか、ということになるだろう。後は、自分の得意・不得意もあるし、開発工数が少なくてすむというのもある。昔作ったときは、確か、C#で作ったのだが、そのときはWindowsで動かすことしか考えていなかったし、サーバー側で処理することも考えていなかった。今回の要件は以下のようになるだろうか。


  • 開発環境 MacBook Air + 無料の開発環境
  • Androidから見られるようにしたい
  • 最初はMac上でいろいろ試したい
  • 完成したもののロジックはクラウドで(Google App Engine?)
Macで使うことを考えると、Objective CもしくはSwiftという選択肢がある。速度的にはこれが一番早いだろう。他のデバイスで動かすことには適さない。マルチプラットフォームという話では、HTML/JavaScriptがいいのだろうな。無料で開発できるし。node.jsを動かしたようなクラウドサービスを使えば、これをクラウド側に展開することも可能だ。しかし、Google App Engineは対応していない。これからはHTML5だ、とか、口では言うのだが、あまり優秀な開発環境がないからか、書きにくくて好きではない。UI部分はまだしもロジックを書くのには向かないように思う。結局、Javaを使うか。これなら、Google App Engineも対応しているので最終的にロジック部分を全部移行することが可能だろうし、Eclipseで開発ができる。データベースをどうするかだが、めんどくさいので、最初のうちは、計算時は全部メモリ上において動作させるか、富豪プログラミング的に。最近はoracleとかのデータベースもインメモリが使われているしな。毎回、Yahooのページからデータを取るわけに行かないから、取ってきたデータをメモリ展開したものをファイルに保存することにする。

2014年7月18日金曜日

競馬のデータ

競馬の予想アプリを作ろうで書いたように、JRA-VANから生データは手に入れられないし、MAC用のSDKは現在はないようである。でも、データがないことには、どうもならん。ということで、何らか方法を考える。APIを提供してくれているところないかなー、と思ったが、さすがにそれはないようだ。しかし、競馬の結果を見ることができるWebサイトはいくつかある。そのデータを利用することにする。どこのサイトにしようかと思ったが、Yahooの競馬にすることに決めた。どこでも、大差ないと思うが、大手の安心感というやつか。で、当たり前だが、仕様書があるわけではない。したがって、仕様を推測しながら作ることになる。まずはURL。
結果のサイトは、

http://keiba.yahoo.co.jp/race/result/1402020101/

のようなURLになっている。どうもこれは、

http://keiba.yahoo.co.jp/race/result/[year - 2000][競馬場ID][x回][x日][xレース]/

という形式らしい。競馬場IDは、

01:札幌, 02:函館, 03:福島, 04:新潟, 05:東京, 06:中山, 07:中京, 08:京都, 09:阪神, 10:小倉

のようだ。昔、JRA-VANのデータを使ったときも同じだった気がするな。0はなくて10まで。ということで、この形式でURLを生成すれば、結果のページを取得できる。対応するレースが存在しない場合は、404エラーが帰ってくるので、404エラーが帰ってくるまで繰り返せば、すべてのデータを取ることが可能だ。

2014年7月17日木曜日

競馬の予想アプリを作ろう

完全にほったらかしになっていたのだが、少しずつ書いていきたい。

GWに友人に連れられてひさしぶりに競馬に行った。天皇賞含め、いくつかのレースを買ったのだが、惨敗。ひとつも取れず。昔は、自身で予想ソフトを作って、それをもとに予想していたが、新聞だけを読んで予想も楽しいものだった。

その後、競馬はしてないのだが、今、予想ソフトを作ってみると当時と違うことが出来るだろうか、と思い出した。で、作ってみることにした。

まず、データをどこからか取らなければ行けない。昔は、たしかnifty経由でJRA-VANからデータが取れたんだったっけ。しかし、今は違う。JRA-VANのサイトを見ると、JRA-VANデータラボの会員になる必要があるらしい。これは、月額2,052円(こっちのページには1,995円になっている、消費税が5%->8%であがってこちらは修正漏れか)。高いと言えば高いが、まあ、それで楽しめるのであればいいと思ったのだが、このサービスはデータをくれるのではなく、SDKベースでデータにアクセスできるようになるものらしい。

しかし、このSDKはMacで使えなさそうである。Q&Aでは、



Macintosh版のSDK(JV-Link)はありますか?
JRA-VAN SDKの中にMacintosh版も含まれております。
Macintosh版のJV-Linkは、Carbonインターフェースとして作成されており CodeWarriorやOS標準でバンドルされているXcodeで開発可能です。
と書かれているのだが、JV-Link質問箱では、

申し訳ございませんが、Mac版のJV-Link、SDKの更新は
現在行っておらず、Cocoaによる開発にも対応しておりません。


になっている。ということで、現在は使えないのだろう。どちらにしろ、Android版はなさそうだ。SDKベースではなくデータそのものを配布してくれればいいのに。