RubyとRで競馬分析

データの検索クラス

用意したDataLab.のデータを利用するためのRubyスクリプト「DataLab.rb」です。

インデックス情報の利用にはいろいろな方法がありますが、私は、.idxファイルを使って.datファイルを検索するDataLabクラスを作って利用しています。
また、比較的よく行なう、馬毎レース情報(SE)の検索ルーチンも定義しています。

DataLabクラスの使い方

DataLabクラスの一般的な使い方は、次のようになります。

  1. コンストラクタに、開くデータベースのレコード種別名('RA'など)を指定する。
  2. dat_openメソッドを実行して、datファイルを開く。
  3. dat_readメソッドやdat_regexpメソッドに検索するインデックス値を渡して、該当するデータを読み込む。
    この処理を必要なだけ繰り返す。
  4. すべての読み込み処理が終わったら、dat_closeメソッドを実行して、datファイルを閉じる。

以下、具体的な使い方をいくつか紹介します。

(1) 文字列でインデックス値を指定して、該当する1データを取得する

インデックス値に完全一致するデータを取得するには、dat_readメソッドを使います。
指定したインデックスのデータが無い場合は、dat_readメソッドから長さがゼロの文字列が返されます。

たとえば、「レース詳細」(RA)を開き、指定するデータ(2006年1月28日の箱根特別のデータ)を読み込むコード例は、次のようになります。

#DataLabクラスを使う
require 'DataLab.rb'

#レース詳細(RA)を読み込むDataLabクラスのオブジェクトを作る
RA = DataLab.new("RA")

#datファイルを開く
RA.dat_open

#「2006012805010110」(2006年1月28日の箱根特別)のデータを読み込む
ra_record = RA.dat_read("2006012805010110")

#datファイルを閉じる
RA.dat_close

#データを表示する
print ra_record, "\n"

このメソッドは、データを最も効率よく検索できます。
ただし、datファイルを開いたときに、idxファイルのデータをすべて読み込むため、その分の時間とメモリが必要です。

(2) インデックス値を正規表現で指定して、該当するデータの配列を取得する

dat_readメソッドでは、指定したインデックスと完全一致するデータだけが返されます。
これに対して、dat_regexpメソッドは、正規表現で指定したインデックスと一致するデータの配列を返します。

たとえば、「2006年1月15日」に開催された全レースのデータを検索するには、次のようにします。

#DataLabクラスを使う
require 'DataLab.rb'

#レース詳細(RA)を読み込むDataLabクラスのオブジェクトを作る
RA = DataLab.new("RA")

#datファイルを開く
RA.dat_open

#正規表現で「2006年1月15日」のデータを指定し、読み込む
ra_record = RA.dat_regexp(/\A20060115/)

#datファイルを閉じる
RA.dat_close

#データ配列を表示する
ra_record.each { |data| print data }

dat_regexpメソッドは、全インデックスを列挙して検索しているので、効率は良くありません。
本当によく行なう検索であれば、そのためのインデックス情報を作るのがよいのでしょう。とはいえ、ここで扱っている程度のデータ量であれば、最近のパソコンであれば総当りで検索しても実用になると感じています。

(3) インデックスのない部分のデータを検索する

dat_readメソッドとdat_regexpメソッドは、メモリに読み込んでいるインデックス情報を検索しています。
これに対して、インデックスにない、データ内の情報を検索するには、dat_regexp_allメソッドを使います。

たとえば、馬名に「ナリタ」がつく競走馬を列挙するコード例は、次のようになります。

#DataLabクラスを使う
require 'DataLab.rb'

#競走馬マスタ(UM)を読み込むDataLabクラスのオブジェクトを作る
UM = DataLab.new("UM")

#datファイルを開く
UM.dat_open

#正規表現で「ナリタ」のデータを指定し、読み込む
um_record = UM.dat_regexp_all(46, 81, /ナリタ/s)

#datファイルを閉じる
UM.dat_close

#データ配列を表示する
um_record.each { |data| print data[11..20], ":", data[46..81], "\n" }

「dat_regexp_all(46, 81, /ナリタ/s)」というのは、データのfirstバイト目(46)からlastバイト目(81)までの部分文字列と正規表現「/ナリタ/s」の一致を調べるという意味です。
JVA-VANのデータは、日本語文字コードにShift-JISを使っています。そのため、正規表現の「s」オプションを指定してShift-JISを強制しています。

dat_regexp_allメソッドは、インデックスのある全データを1データずつ読み込んで検索しているので、非常に効率の悪い処理です。

(4) インデックス情報のハッシュを取得する

keysメソッドで、インデックス情報を格納したハッシュを取得できます。

馬毎レース情報(SE)の検索

馬毎レース情報(SE)は、インデックス以外の情報、たとえば、競走馬マスタ(UM)のインデックス値(血統登録番号)や、レース詳細(RA)のインデックス値で検索し、一度に複数のデータを得たい場合も多いでしょう。

たとえば、ナリタブライアンの成績を列挙したい場合は、dat_regexpメソッドを使って、馬毎レース情報(SE)を次のように検索できます。

#DataLabクラスを使う
require 'DataLab.rb'

#馬毎レース情報(SE)を読み込むDataLabクラスのオブジェクトを作る
SE = DataLab.new("SE")

#datファイルを開く
SE.dat_open

#正規表現で「1991108889」のデータを指定し、読み込む
se_record = SE.dat_regexp(/1991108889\z/)

#datファイルを閉じる
SE.dat_close

#データ配列を表示する
se_record.each { |data| print data }

これで検索される情報は、レースの日付順になっていないので、Arrayクラスのsort!メソッドでソートします。
これにより、データが昇順にソートされます。

#データをソートする
se_record.sort! { |a, b| a[11..18].to_i - b[11..18].to_i }

また、過去の成績を取得する際には、「ある日よりも古いデータ」が欲しい場合も多くあります。
次のコードでは、指定した日よりも古いレースのうち、最も新しいレースのデータを調べて表示します。

#1994年02月14日より古いデータの中で、もっとも新しいデータを表示する
se_record_index = 0
while se_record_index < se_record.length
  break if (se_record[se_record_index][11..18].to_i >= 19940214)
  se_record_index += 1
end
se_record_index -= 1
print se_record[se_record_index] if (se_record_index >= 0)

馬毎レース情報(SE)検索用の補助インデックスを作る

以上の処理は、馬毎レース情報(SE)の全インデックスを検索するため、時間が掛かります。
また、1頭だけ検索するというよりも、複数の競走馬について連続して検索するほうが多いでしょう。

そこで、競走馬マスタ(UM)のインデックス値(血統登録番号)から、馬毎レース情報(SE)のインデックス値の配列を取得するためのハッシュを計算するse_umaid_seidxメソッドと、レース詳細(RA)のインデックス値から、馬毎レース情報(SE)のインデックス値の配列を取得するためのハッシュを計算するse_raid_seidxメソッドを、「DataLab.rb」に定義しています。

se_umaid_seidxメソッドとse_raid_seidxメソッドから返されるハッシュに記録されているのは、馬毎レース情報(SE)のインデックス値の配列なので、インデックス値の配列を元に該当するデータの配列を返すindex_recordメソッドを用意します。

また、ソートされたSEデータ配列の中から、指定した日付よりも古いデータの中で、もっとも新しいデータのインデックス値を返すse_umaid_findメソッドも用意します。このメソッドは、該当データがない場合は-1を返します。

(1) se_umaid_seidxメソッドとse_umaid_findメソッドの使用例

以上のメソッドを使って、ナリタブライアン(血統登録番号:1991108889)の成績を表示し、1994年02月14日より古いデータの中で、もっとも新しいデータを表示するコードは、次のようになります。

#DataLabクラスを使う
require 'DataLab.rb'

#馬毎レース情報(SE)を読み込むDataLabクラスのオブジェクトを作る
SE = DataLab.new("SE")

#datファイルを開く
SE.dat_open

#「血統登録番号」→「SEインデックスの配列」変換を行なうハッシュを計算
se_umaid = se_umaid_seidx(SE)

#ナリタブライアン(血統登録番号1991108889)の成績を取得
se_record = index_record(SE, se_umaid["1991108889"])

#datファイルを閉じる
SE.dat_close

#データ配列を表示する
se_record.each { |data| print data }

#1994年02月14日より古いデータの中で、もっとも新しいデータを表示する
idx = se_umaid_find(se_record, 19940214)
print "*1994-02-14\n", se_record[idx] if idx >= 0

(2) se_raid_seidxメソッドの使用例

また、2006年1月28日の箱根特別に出走した各馬の馬毎レース情報を表示するコードは、次のようになります。

#DataLabクラスを使う
require 'DataLab.rb'

#馬毎レース情報(SE)を読み込むDataLabクラスのオブジェクトを作る
SE = DataLab.new("SE")

#datファイルを開く
SE.dat_open

#「レース詳細(RA)のインデックス値」→「SEインデックスの配列」変換を行なうハッシュを計算
se_raid = se_raid_seidx(SE)

#2006年1月28日の箱根特別(2006012805010110)の各馬の成績を取得
se_record = index_record(SE, se_raid["2006012805010110"])

#datファイルを閉じる
SE.dat_close

#データ配列を表示する
se_record.each { |data| print data }

DataLab.rb

# JRA-VAN DataLab.データファイル・サポートクラス

class DataLab
  #初期化(インデックス・ファイル読み込み)
  def initialize(fname)  # fname = レコード名
    @DATFileName = 'Download/' + fname + '.dat'  # データ・ファイル名
    @IDXFileName = 'Download/' + fname + '.idx'  # インデックス・ファイル名

    @Index = Hash.new  # インデックス情報

    print @IDXFileName + "読み込み中..."
    open(@IDXFileName, "r") { |idxfile|
      idxfile.each { |line|
        line.chop!
        idata = line.split(',')
        @Index[idata[0]] = idata[1].to_i
      }
    }
    print "終了\n"
  end

  #データ・ファイルを開く
  def dat_open
    @DATFile = open(@DATFileName, "r")
  end

  #データ・ファイルから指定された1データを読み込む
  def dat_read(index)
    if @Index.key?(index) then
      @DATFile.pos = @Index[index]
      return @DATFile.gets
    else
      return ""
    end
  end

  #データ・ファイルから正規表現で指定したデータ(複数)を読み込む
  def dat_regexp(regexp_index)
    #regexp_index :検索する正規表現を表わすRegExpクラスのオブジェクト
    data_array = Array.new
    @Index.each { |key, value|
      if (regexp_index === key) then
        @DATFile.pos = value
        data_array << @DATFile.gets
      end
    }
    return data_array
  end

  #データ・ファイルから有効な(インデックスのある)データを全文検索する
  def dat_regexp_all(first, last, regexp_index)
    #regexp_index :検索する正規表現を表わすRegExpクラスのオブジェクト
    data_array = Array.new
    @Index.each { |key, value|
      @DATFile.pos = value + first
      data = @DATFile.read(last - first + 1)
      if (regexp_index === data) then
        @DATFile.pos = value
        data_array << @DATFile.gets
      end
    }
    return data_array
  end

  #データファイルのデータを列挙し、メソッドを呼び出す
  #callbackfunc1(key) 有効なキーを判断するメソッド
  #callbackfunc2(key, record) 有効なキーのデータを受け取るメソッド
  def dat_read_all(callbackfunc1, callbackfunc2)
    @Index.each { |key, val|
      if callbackfunc1.call(key) then
        @DATFile.pos = val
        callbackfunc2.call(key, @DATFile.gets)
      end
    }
  end

  #データ・ファイルを閉じる
  def dat_close
    @DATFile.close
  end

  #インデックス情報の配列を返す
  def keys
    return @Index.keys
  end
end

#「血統登録番号」→「SEインデックスの配列」変換を行なうハッシュを計算
def se_umaid_seidx(se)
  hash = Hash.new

  se.keys.each { |key|
    umaid = key[18..27]
    unless hash.key?(umaid)
      hash[umaid] = Array.new
    end
    hash[umaid] << key
  }

  #日付をもとに昇順にソートする
  hash.each { |key, value|
    value.sort! { |a, b| a[0..7].to_i - b[0..7].to_i }
  }

  return hash
end

#「レース詳細(RA)のインデックス値」→「SEインデックスの配列」変換を行なうハッシュを計算
def se_raid_seidx(se)
  hash = Hash.new

  se.keys.each { |key|
    raid = key[0..15]
    unless hash.key?(raid)
      hash[raid] = Array.new
    end
    hash[raid] << key
  }

  #馬番をもとに昇順にソートする
  hash.each { |key, value|
    value.sort! { |a, b| a[16..17].to_i - b[16..17].to_i }
  }

  return hash
end

#インデックス値の配列から、該当するデータの配列を取得する
def index_record(data, index_array)
  record = Array.new
  index_array.each { |id|
    record << data.dat_read(id)
  }
  return record
end

#10進数形式で指定した日付よりも古いデータの中で、もっとも新しいデータへのインデックスを返す
#該当するデータがない場合は、-1を返す
def se_umaid_find(se_record, race_date)
  se_record_index = 0
  while se_record_index < se_record.length
    break if (se_record[se_record_index][11..18].to_i >= race_date)
    se_record_index += 1
  end
  return se_record_index - 1
end

Creative Commons License
この作品は、クリエイティブ・コモンズ・ライセンスの下でライセンスされています。

(C) 2006 NARITA, Takuro
E-Mail:narita@a1.mbn.or.jp