お馬の写真 別館

「お馬の写真」管理者による徒然なるブログ

DataLab.を使ってサイアーラインを可視化してみる(その2)

   

a0002_009631

ちょっと前回から間があいてしまいました。
とりあえず今回は「JRAの重賞を勝っている馬の父馬からサイアーラインを遡る。ただし血統データが整備されている馬に限る」という条件に当てはまる種牡馬を列挙するところまでやりたいと思います。

開発環境はPython3.4。これまでPythonを使った記事は2.7にしていましたが、新規にプログラム書くならPython3系にしたほうがいいかな、と思い直した次第。
それに伴いMySQLへのアクセスはMySQLdbからMySQL Connector/Pythonに変更。
どうもMySQLdbはPython3.4環境にはインストールできなかったため。(MySQL Connector/Pythonの方はwithを使った使用ができないっぽいですが…)

また、いつもと同じように、「PC-KEIBA Database for JRA-VAN Data Lab.」を使ってMySQLに格納したデータを使ってデータを見ていきます。

全体の流れ

  1. スタートはJRAの重賞を勝っている馬の父馬を列挙。
  2. 上記から種牡馬情報を取得。
  3. 取得した種牡馬情報からそれぞれの父馬を列挙。
  4. 列挙した父馬の中でまだ種牡馬情報を取得していないものを列挙。
  5. 列挙した種牡馬情報を取得して、リストに追加。3に戻る。
  6. それ以上父を辿れない状況になった時点で終了。
  7. とりあえずCSVで一覧出力。

なお、DBへのアクセスはデザインパターンのDaoパターン的なやり方でおこなうことにします。これは今回MySQLdb→MySQL Connector/Pythonへ入れ替えを行ったように、DBアクセスするためのドライバを入れ替えることが今後発生しうるため、入れ替えをしやすくしたいということで。

メイン

まずプログラムメインはこんな感じ。

import sys
import csv
from daofactory import DaoFactory
if __name__ == '__main__':
dao = DaoFactory().create_sire_info_dao()
sire_infos = []
unknown_numbers = dao.get_initial_fathers()
while len(unknown_numbers) > 0:
new_sire_infos = dao.get_sire_infos(unknown_numbers)
sire_infos.extend(new_sire_infos)
sire_numbers = {x.toroku_bango for x in sire_infos}
father_numbers = {x.f_toroku_bango for x in sire_infos}
unknown_numbers = father_numbers - sire_numbers - {'00000000'}
writer = csv.writer(sys.stdout)
writer.writerows(sire_infos)

DaoFactory、daoに関しては後で書くとして。
流れとしては、sire_infosというリストに種牡馬情報を追加。
dao.get_initial_fathers()でJRAの重賞馬の繁殖登録番号一覧を取得。
繁殖登録番号からdao.get_sire_infosで種牡馬情報を取得してsire_infosに追加。
sire_infosに登録済みの種牡馬一覧とその父馬一覧を列挙し、父馬の中に種牡馬情報を取得していないものを列挙。
…というのを繰り返す形。

最後に暫定でcsvで出力するという流れです。

データアクセスオブジェクト

まずデータアクセスオブジェクトのインターフェイスを定義。Pythonには明確なインターフェイスという考えがないようなので、オーバーライドしないで使ったら例外が発生する、というクラスで代用します。

class SireInfoDao:
def __init__(self):
pass
def get_initial_fathers(self):
raise NotImplementedError
def get_sire_infos(self, sire_numbers):
raise NotImplementedError

データアクセスオブジェクトの実装側。SireInfoDaoのサブクラスとして作っています。

from collections import namedtuple
from sireinfodao import SireInfoDao
class MysqlSireInfoDao(SireInfoDao):
def __init__(self, con):
super(MysqlSireInfoDao, self).__init__()
self._con = con
self._create_temporary_table()
def get_initial_fathers(self):
c = self._con.cursor()
sql = "SELECT DISTINCT c.ketto1_hanshoku_toroku_bango " \
+ "FROM jvd_umagoto_race_joho a " \
+ "JOIN jvd_race_shosai b ON a.race_code=b.race_code " \
+ "JOIN jvd_kyosoba_master c " \
+ "ON a.ketto_toroku_bango=c.ketto_toroku_bango " \
+ "WHERE a.kakutei_chakujun=1 " \
+ "AND b.grade_code NOT IN (' ','E') AND b.data_kubun='7' " \
+ "AND c.ketto1_hanshoku_toroku_bango<>'00000000'"
c.execute(sql)
return {x[0] for x in c.fetchall()}
def get_sire_infos(self, sire_numbers):
self._set_temporary_table(sire_numbers)
c = self._con.cursor()
sql = "SELECT a.hanshoku_toroku_bango, bamei, seinen, " \
+ "chichi_hanshoku_toroku_bango " \
+ "FROM jvd_hanshokuba_master a " \
+ "JOIN temp_sire b " \
+ "ON a.hanshoku_toroku_bango=b.hanshoku_toroku_bango"
c.execute(sql)
SireInfo = namedtuple('SireInfo',
'toroku_bango bamei seinen f_toroku_bango')
return [SireInfo._make(x) for x in c.fetchall()]
def _create_temporary_table(self):
c = self._con.cursor()
sql = "CREATE TEMPORARY TABLE temp_sire " \
+ "(hanshoku_toroku_bango CHAR(8))"
c.execute(sql)
def _set_temporary_table(self, sire_numbers):
c = self._con.cursor()
sql = "DELETE FROM temp_sire"
c.execute(sql)
sql = "INSERT INTO temp_sire (hanshoku_toroku_bango) VALUES(%s)"
numberlist = [(x,) for x in sire_numbers]
c.executemany(sql, numberlist)

DAO生成用のファクトリ。これを使って生成するようにしないと、ロジック内に特定のDAOに依存するコードが入っていまうので。

import mysql.connector
from mysqlsireinfodao import MysqlSireInfoDao
class DaoFactory:
def __init__(self):
pass
def create_sire_info_dao(self):
return MysqlSireInfoDao(self._get_connection())
def _get_connection(self):
con = mysql.connector.connect(host='localhost', db='pckeiba',
user='XXXX', passwd='XXXX')
return con

大体これで約1000頭ほどの情報が取得できるはず。ここから可視化する部分に関してはまた別途。

 - プログラミング