初仔の成績の分析

現3歳世代、ジェンティルドンナヴィルシーナといった名牝の初仔はPOG的には不発でした..(秋や古馬になって活躍するかもしれませんが)

初仔は走らないのでしょうか.. ??

そういうことどこかで聞いたことあるよなぁ、、と思いつつ調べてみると、

どうやら「初仔の牝馬は走らない」というのが定説となっているようです

手元のデータでも検証してみます。

データ取得

  • 3世代(2016-2017、2017-2018、2018-2019)の成績から2勝馬以上の母を抽出
  • ただし、産駒の1頭以上がN勝以上(年度に応じて2〜4)をあげた母馬に絞ります
  • 各産駒のPOG期間の勝利数をカウント
  • ただし、[地]や(地)といった地方所属馬は勝利数が多めに出てしまうので除外します

分析

産駒の平均勝利数(x軸)と初仔の勝利数(y軸)をプロットしてみます。

以下のようなグラフが得られました。

f:id:sanshonoki:20190615182743p:plain

直線は  y=x なので

  • この直線より下にプロットされた点は 初仔はその母馬の全産駒の平均より成績が悪い
  • 逆に直線より上にプロットされた点は 初仔はその母馬の全産駒の平均より成績が良い

ということを表しています。

これを見ると大半のプロットは直線の下に位置しており、初仔の成績はその母馬の産駒の平均的な成績に比べるとあまり奮ってないということになります。

牡馬と牝馬の比較

ここから性別ごとの比較をしていきます。

さきほどのグラフを牡馬、牝馬で分けて出すと次のようになります。

初仔の勝利数 割合(牡馬) 割合(牝馬
0 0.727 0.816
1 0.236 0.122
2 0.036 0.061

これを見ると、牝馬は牡馬に比べて0勝馬に終わる可能性が高いということは言えそうです

デビュー時の体重の比較

初仔の成績があまり良くない理由としては生物学的な要因以外にもいくつか考えられます

  • その産駒の育成のノウハウがたまっていない
  • 小柄に生まれる(?)

など。

育成に関してはデータのとりようがないので 2番目の体重説に関して、初仔のデビュー体重と2年目以降の産駒のデビュー体重の分布を出して検証してみました

牝馬の場合は、初仔のデビュー時の体重が2年目以降の産駒のデビュー体重と比べて明らかに小さいことが分かります。 おそらくこれも一因でしょう

平均勝利数が上位の母馬

データ分析の副産物として得られたPOG期間の産駒平均勝利数が上位の母馬のリストです 対象産駒数3以上だと信頼度としてはかなり高いと思います

母名 平均勝利数 対象産駒数
ヴィアメディチ 3.500000 2
スタセリタ 2.333333 3
ラドラーダ 2.250000 4
グロッタアズーラ 2.000000 3
ミスアンコール 2.000000 5
スターアイル 1.800000 5
ナスケンアイリス 1.666667 3
ラヴズオンリーミー 1.666667 6
エアトゥーレ 1.500000 8
ドバイマジェスティ 1.500000 4
ライラックアンドレース 1.500000 4
ナイスカット 1.400000 5
シンハリーズ 1.375000 8
ミスチフ 1.333333 3
ポールポジションII 1.250000 4
シーザリオ 1.222222 9
チャチャリーノ 1.200000 5
スパイオブラヴ 1.200000 5
フサイチパンドラ 1.142857 7

その他、興味深い記事

特別に初仔に着目した分析ではないですが興味深い記事を見つけました。

これによると、

繁殖牝馬は第2仔~第6仔において最高の産駒を送り出す可能性が非常に高い

そして、      

概して、第7仔以降の牝馬において競走能力と同様に繁殖能力にも衰えが見られ始める。

だそうです。また、

牝馬が繁殖入りしたばかりのときは、その牝馬を見て身体的に補完すると考えられる種牡馬と交配させます。その結果生まれてきた産駒が、予想とまったく異なることが時々あります。多くの場合、繁殖牝馬が初仔を出産する前に2年目の種牡馬はすでに予約されています。そのため、ほとんどの場合、種牡馬選びに磨きをかける機会があるのは、3年目の種牡馬を選ぶときなのです」。

3番目はなるほどなと思いました

来年以降の作戦にぜひ活かしたいと思います

厩舎のコメントからキーワード分析する

POGの本には冒頭に厩舎コメントつきの2歳馬の写真ページがあります。

f:id:sanshonoki:20190520232638p:plain:w300

この例で言えば、「厩舎の一番馬ですね。来たときから均整の取れた馬体をしていました。(以下、略)」が厩舎のコメントにあたります。

このコメントからその後の活躍を予感させるサインとなるキーワード、活躍に結びつきにくいサインとなるキーワードを抽出できないかという試みです。

サンプルデータ数が少ないこともあり今回は機械学習や統計のアルゴリズムを使わない人力によるデータ分析です。

データ

過去3年分のPOG本のコメントから分析します。

年度 データ数
2018〜2019年 POGの王道(黄本) 83
2016〜2017年 最強のPOG青本 38
2015〜2016年 最強のPOG青本 49

2017〜2018年のPOG(アーモンドアイ3冠の年)は都合により本を購入しておらず残念ながらデータがありません..

たった3年分でも手打ちによるデータ入力作業があまりにも大変だったので一部の種牡馬ディープインパクトキングカメハメハ + α)に絞ってコメント入力しました。

分析方法

POG期間の獲得賞金のデータを取得し、それをマージしたテーブルを賞金でソートします。 そして目で上位で見かけるキーワードと下位でよく見かけるキーワードを抽出するというものです。

f:id:sanshonoki:20190520233953p:plain皐月賞後時点でのデータ)

ちなみに、賞金等の馬のデータの取得はこちらのお手製のscraperを使っています。

github.com

結果

頑張ってポジティブ(活躍を予感させる)キーワードとネガティブ(活躍に結びつきにくい)キーワードを抽出した結果、次のようになりました。

Positiveキーワード
  • デビューが早い(6~7月、6月東京、6月阪神、夏のデビュー、7月中京、早めの移動)
  • 完成度が高い
  • スタッフの評判が高い、厩舎内で一番、トップクラス
  • 動きが抜群、身体能力が高い、相当良い、すごくいい
  • 我慢が利く
  • 緩くない(ルーラーシップ産駒)
  • 柔らかい(キングカメハメハ産駒)


キーワード 代表例
デビューが早い (たくさん)
完成度が高い グランアレグリア
ダノンファンタジー
スタッフの評判が高い、厩舎内で一番、トップクラス サトノダイヤモンド
アドマイヤジャスタ
動きが抜群、身体能力が高い、相当良い、すごくいい サートゥルナーリア
アルアイン
アドミラブル
クラージュゲリエ
我慢が利く サートゥルナーリア
緩くない(ルーラーシップ産駒) ディアンドル
柔らかい(キングカメハメハ産駒) レイデオロ
リオンディーズ

デビューが早いのは言わずもがなのここ数年のトレンドですね(POGをやる上で逆らえないノーザンファーム系のクラブ馬の傾向) 厩舎コメントがどうだったか分かりませんがアーモンドアイもデビューは夏でした。

デビュー時期以外に関しては、活躍した馬のコメントなのでそりゃそうだろうという内容ですが後で取り上げるNeutralキーワードより重視したいワードとなります。

逆に、上記のコメントがありながらPOG期間で期待ほど奮わなかった馬をリストアップすると、、

  • サトノソロモン:"厩舎の一番馬"
  • プランドラー:"スタッフの評価も高い"

になります。

Negativeキーワード
  • 成長を促しながら、焦らずいく
  • 成長がゆっくり、成長途上
  • 幼さを残す、気性面が幼い、併せるとガツン
  • 小柄、細い、華奢な面がある
  • 非力さがある
  • デビューは秋以降

基本的に完成度が低いということを意味しています。 血統が良くてもこれらのキーワードが含まれているとリスク増ということになります。要注意です。 あとは当然ながら気性が悪いと能力を発揮できないリスクが高まるということですね。

Neutralキーワード

他に、PositiveでもなくNegativeでもない上位の馬でも下位の馬でも見られるNeutralキーワードとして以下がピックアップされました

  • スピードがある
  • バネ、キレがある
  • 乗り味がいい
  • 動きが柔らかい
  • 素質の高さ
  • 期待している
  • 大物感
  • ディープらしさ(ディープインパクト産駒)

乗り手の主観ぽいのはあまり当てにならないということですかね。。


写真ページに掲載される馬はそもそも素質を見込まれた馬たちですがこれらのキーワードを参考にすると活躍馬と出会える確率がより高まると思います。あと、分析作業をやってみた感想として走る馬を見分けるのは難しいが走らない馬を見つけるのはそこまで難しくないという印象を持ちました。 守りを固めるには良い情報になるかなと思います

種牡馬の年齢と産駒成績との関係

先日、ディープインパクトキングカメハメハの種付け中止 😱という衝撃のニュースが流れました

両馬とも高齢になってきて健康面での問題からの種付け中止だそうです。

ということで種牡馬の年齢と産駒成績との関係も気になってきて調べてみました。

分析方法

データソース

netkeibaの種牡馬リーディングのデータを使って分析します。 このデータは産駒全体での成績なので移動平均を取ったようなイメージです。

本来は世代(年齢)ごとの成績を集計するのが一番よいのですが集計作業が大変なのでひとまず、このスムージングされたデータを使って分析してみます。

期間

期間は 2010年~2018年 の 9年分です。

対象種牡馬

各年でTop10に入った種牡馬をピックアップし、それらを対象にします。 全部で20頭リストアップされました。

評価項目

そして、年齢と勝ち馬率やアーニングインデックスの関係をグラフにプロットします。

結果

左が勝ち馬率のグラフで右がアーニングインデックスのグラフです。 (横軸が種牡馬の年齢です)

f:id:sanshonoki:20190511080329p:plain

ゴチャゴチャして分からないので強引に回帰直線を引いてみます。

f:id:sanshonoki:20190511081151p:plain

おお、

右下がりになりました

やはり馬も年齢には勝てないのでしょうか。。

興味深いのは勝ち馬率の減少トレンドが顕著なのに対し、アーニングインデックスの減少はそうでもないということです。

これはどういうことかと言うと、勝ち馬率は下がってきつつも馬の平均獲得賞金はそれなりに維持できているということなので 大きなレースで産駒がいい成績を出しているということを意味しています。


次に、何頭かずつ取り出して見ていきます。

まずはビッグ5です。

f:id:sanshonoki:20190511082308p:plain

こちらも回帰直線を引いちゃいます。

f:id:sanshonoki:20190511082653p:plain

やはりビッグ5と言えども年齢とともにスコアが下がるようです

しかし、ステイゴールドはすごいです。晩年でも勝ち馬率とアーニングインデックスが盛り返しています

オルフェーヴルが3冠を取ったのが2011年でその4年後の2015年に勝ち馬率が急上昇しています。 オルフェーヴルの活躍を見て翌年の2012年に種付けすると2015年に2歳馬となってデビューしてきます。これは偶然の一致ではないでしょう

すごい活躍馬が出た数年後はウォッチしておいたほうがよいかもしれません

ちなみに、4、5年前の2015年、2014年のダービー馬、オークス馬はというと、

となっています。

続きまして、ブレイク中の若手の3頭です。

f:id:sanshonoki:20190511085043p:plain

明らかな上昇トレンドであり、引き続き期待できそうです。

最後に、ビッグ5に続くベテラン7頭です。

f:id:sanshonoki:20190511085128p:plain

年齢とともにスコアが下がっています。 アーニングインデックスの下がりがビッグ5よりも大きいのが気になります。

大きなレースで勝てなくなってきているということですね.. この辺がスーパーサイアーとの違いなんでしょうか

ただ、クロフネマンハッタンカフェは下がってないので注目しておきたいと思います。

母馬年齢と産駒の成績との関係

また今年も日本ダービーが近づいてきました。 というわけで、POGのデータ分析を少しやってみました。

今回は母馬年齢と産駒の成績の関係を調べてみました。

調査は以下の2シーズンのデータを使って行います。


やったことは単純に母馬年齢と産駒の獲得賞金をプロットしただけです

結果は以下のようになりました。

f:id:sanshonoki:20190502092211p:plain

f:id:sanshonoki:20190502092139p:plain

母年齢20歳以上になってくるとさすがにちょっと狙いづらくなってきますね

5000万以上を稼ぐ馬は重賞勝ちレベルの馬になってきますが重賞級のボリュームゾーンは母年齢~13歳ぐらいまでになるでしょうか


ちなみに母年齢20歳以上で産駒が5000万円以上稼いだ馬は以下の2頭でした

活躍した産駒が出たので狙いたくなりますがデータ的には狙いづらいですね。。

arXivの論文をチェックするbot

AIの最新の動向を追いたいならarXivをチェックしないわけにはいきません!

しかし、

自分の業界のキーワードにマッチする論文は数ヶ月に1回あるかないか...

毎日チェックするのは面倒

そこでボットを作ってチェックすることにしました

システム構成

システムは以下のような感じです。 今回も?Herokuを使います。無料バンザイです。

f:id:sanshonoki:20190302220149p:plain

arXivにはAPIが用意されているのでrubyスクリプトからarXivAPIを叩きます。

rubyスクリプトをHerokuにデプロイし、スケジューラアドオンを追加し定期実行させます。 通知済みの論文の履歴はDB(postgresql)に保存し、新しい論文だけを通知します。

arXivAPI

arXivAPIが用意されています。 GETだけでのシンプルなものなので使い方は簡単です。 Perl, Ruby, Python, PHPの簡単なサンプルも用意されています。 ただ、レスポンスがJSONではなくXMLなのでパースは若干面倒。

慣れないXMLに少しハマりかけましたがググって解決。

APIを叩いてパースするコアとなるコードはこちら

  def search(keywords, max_results = 3)
    query = build_query(keywords)
    url = URI.parse("http://export.arxiv.org/api/query?search_query=#{query}&start=0&max_results=#{max_results}&sortBy=submittedDate&sortOrder=descending")
    res = Net::HTTP.get_response(url)

    xml = res.body
    doc = REXML::Document.new(xml)

    # https://medium.com/tech-batoora/xml-50488ec69b20
    entries = REXML::XPath.match(doc, '//feed/entry').map do |entry|
      {
        id: entry.elements['id'].text,
        updated: Date.parse(entry.elements['updated'].text),
        published: Date.parse(entry.elements['published'].text),
        title: entry.elements['title'].text,
        summary: entry.elements['summary'].text
      }
    end
    entries
  end

  private

  def build_query(keywords)
    cat = 'all'
    keywords.inject('') do |param, kw|
      if param.empty?
        param = "#{cat}:#{kw}"
      elsif kw.start_with?('+')
        param = "#{param}+OR+#{cat}:#{kw[1..kw.size]}"
      elsif kw.start_with?('!')
        param = "#{param}+ANDNOT+#{cat}:#{kw[1..kw.size]}"
      else
        param = "#{param}+AND+#{cat}:#{kw}"
      end
      param
    end
  end

40行ちょっとのコードです。keywordのANDやORも指定できるので一部対応しました。 (自分の関心のある論文はキーワード1個で十分なので対応してなくもよかったけど)

通知

通知先はとりあえずとしてメールとSlackです。

Slackは前に使ったことのあるslack-notifierのgemを利用させてもらい、WebHook URL経由で送信します。

メールはGmailSMTPサーバーを使って送ります。

テストでGmailから送ってみると Net::SMTPAuthenticationError が出力されてしまいました..。

あれ?以前他のプログラムからメール送信していたときはこんなの出てなかった気がするけど

と思ったけどコードにコメントが残っていて実は対策をしていた。。

今回も同じ対策をします。

Gmail を使って Net::SMTPAuthenticationError が出力される場合の解決法 - 大学生からの Web 開発」の通りにやればok。

Googleアカウントのページにいって、セキュリティの変更をします

  • 二段階認証を設定し、その後アプリ用のパスワードを発行する
  • もしくは、「安全性の低いアプリのアクセスを許可」


1年ぶりのrubyのコーディングで少し手こずりましたが無事稼働するようになりました。 気長に論文を待とうと思います。

と思ってたら、こんな感じでいきなり通知がやってきました!

f:id:sanshonoki:20190301233651p:plain

やったー。想像してた以上に便利でうれしい

今回のコードはこちら。もし興味ある方がいたらのぞいてみてください github.com

今回までやったことなかったのですがHerokuはwebアプリじゃなくてもデプロイできちゃうんですね。 今後もお世話になろうと思います。

Flask + Flask-Scriptでgunicornをマニアックに起動する

HerokuでpythonのWebアプリを動かすとき

Flask-Scriptを使っている場合、

== manager.py ==

from flask import Flask
from flask_script import Manager

app = Flask(__name__)
manager = Manager(app)

if __name__ == "__main__":
    manager.run()

== Procfile ==

web: gunicorn manager:app

と書けば何ら問題なくデプロイできます。

しかし、

Flask-Script を使って開発サーバーを動かすときの

$ python manage.py runserver

のような python manage.py XXX という書式のマニアックな起動をしたいときはどうすればよいのでしょう?

python manage.py XXX 形式でのWebサーバーの起動方法

python - How to use Flask-Script and Gunicorn - Stack Overflow の記事を参考に所望の起動ができました

== manage.py ==

import os
from flask import Flask
from flask_script import Manager, Server, Command, Option

app = Flask(__name__)

class GunicornServer(Command):
    def __init__(self, host='127.0.0.1', port=8000, workers=1):
        self.port = port
        self.host = host
        self.workers = workers

    def get_options(self):
        return (
            Option('-H', '--host',
                   dest='host',
                   default=self.host),

            Option('-p', '--port',
                   dest='port',
                   type=int,
                   default=self.port),

            Option('-w', '--workers',
                   dest='workers',
                   type=int,
                   default=self.workers),
        )

    def __call__(self, app, host, port, workers):

        from gunicorn import version_info

        if version_info < (0, 9, 0):
            from gunicorn.arbiter import Arbiter
            from gunicorn.config import Config
            arbiter = Arbiter(Config({'bind': "%s:%d" % (host, int(port)),'workers': workers}), app)
            arbiter.run()
        else:
            from gunicorn.app.base import Application

            class FlaskApplication(Application):
                def init(self, parser, opts, args):
                    return {
                        'bind': '{0}:{1}'.format(host, port),
                        'workers': workers
                    }

                def load(self):
                    return app

            FlaskApplication().run()


manager = Manager(app)
manager.add_command('gunicorn', GunicornServer(host='0.0.0.0', port=os.environ.get('PORT', 5000)))


if __name__ == "__main__":
    manager.run()

== Procfile ==

python manage.py gunicorn

で無事にHeroku上で python manage.py XXX 書式でWebアプリが動きます。

あと、Herokuにデプロイするときはポート番号を環境変数PORTの値にしておかないといけません (PORTの値はデプロイするたびに変わるので)

とりあえず、マニアックなやり方でWebアプリを起動できましたが自己満足できるということ以外にこちらの方法を取るメリットが今のところ思いつかないです。。

キャンプ場レビューのWordCloud画像の検索サービスを作る

WordCloud画像は以前の記事で作れるようになったので今回、全キャンプ場分の画像を作成し検索できるようにしました。

こんな感じです。

https://campsite-wordcloud.herokuapp.com/

  1. 検索ボックスにキーワードを入力してキャンプ場を検索 f:id:sanshonoki:20190109224710p:plain

  2. 候補が複数あるときは候補を選択する f:id:sanshonoki:20190109224737p:plain

  3. 選択したキャンプ場のWordCloud画像が表示される f:id:sanshonoki:20190121222415p:plain

これだけなので検索サービスというのは少々大げさです。ただ、いい感じのタイトルがすぐに思いつかなかったのでお許しください。。

苦労したところ

苦労したことを一つあげるとすればストップワードの設定です。

ストップワードをどう設定するかによってキャンプ場同士の特徴の違いをうまく浮き出せられるかが変わってきます。

以下の3パターンを試しました

  1. 結果を見ながら除外する単語をストップワードに追加していく
  2. 頻度を算出し、カウント上位とカウント下位の単語をストップワードとする
  3. TF-IDFを計算し、閾値以下をストップワードとする

が、一番良さそうだったのは一番目の人力チューニングでした..。

TF-IDFを使っていい感じになってくれればブログの記事のネタが増えてくれて良かったのですが。。w

ちなみに、最終的なストップワードは以下になりました。

STOP_WORDS = ['あり', 'ある', 'いる', 'する', 'こと', 'それ', 'ない', 'の', 'し', 'さ', 'れ', 'い', 'サイト', 'キャンプ場', '思い', 'キャンプ', 'でき', 'よう', 'とても', 'ところ', '出来', 'なり', 'あっ', 'おり', 'なっ', 'テント', 'キャビン', 'さん', 'なく', 'られ', 'オートキャンプ', 'トイレ', '利用']

キャンプに行くときに利用しつつ、引き続きチューニングをしていこうと思います。

形態素解析(単語への分割)は通常のMeCabを使用しましたがmecab-ipadic-NEologdなど他の辞書を使えば結果もまた変わってくると思います。これも時間があれば試してみたい