[PIST6(競輪)を機械学習で儲かるか検証した話] Section 01-01 : データ整形

収集したデータを学習に利用しやすい形式に変換します。

データセットの形式

せっかくですから、競馬や一般競輪の予想ではできなかったデータ形式に変換したいと思います。

いままでのデータ形式

競馬や競輪に対して予測器を作りたいのであれば、シンプルに考えれば馬券1枚に対して、当たる確率を求めたいです。
しかし、すべての馬券に対して、予測確率を求める学習データを作ることは困難です。

例えば、競馬では一度のレースに参加する馬の数が最大18頭、一般競輪では最大9人でした。

連単の場合は順列、連複の場合は組み合わせとなるため、各馬券の数は次のとおりになります。

競馬(18頭)一般競輪(9人)
単勝(複勝)189
二連複(ワイド)15336
二連単30672
三連複81684
三連単4,896504

各馬券に対して予測をするデータセットを構築するとなると、単勝の場合はレース数の18倍程度のデータ数になりますが、三連単の場合は4896倍のデータ数になります。
これは多くの場合、一般のコンピュータではメモリに乗らないデータ数になります。
また、三連単の場合は4896倍となったデータの内、当たる馬券は1倍しかないため、かなり偏った不均衡データとなります。

そのため、各馬券に対して予測を行うことは困難でした。
(ですから、1位になる馬(競技者)を予測すること、単勝馬券を予測することを一般には行います)

今回のデータ形式

しかし、PIST6では最大競技参加人数が6人です。
その場合は、三連単でも120通りしか存在しません。レース数も2700件程度しかないので、三連単の各馬券の予測を行っても、30万件程度のデータになり、一般の計算機に乗るサイズです。

ですから、今回作成するデータセットの形式は、
[レースデータ, 参加者データ] + [馬券情報]
を1行とした表データになります。

使用ライブラリ

glogでファイルパスを取ってきて、jsonで読み込み、pandasの形式へと変換します。
joblibで並列処理をすると数分で実行できます。

import warnings
warnings.simplefilter('ignore', FutureWarning)
import pandas as pd
import json
import glob
import joblib
from datetime import date

生データからの変換

生データのパスとチケット(馬券のこと)を渡して、レースデータを学習テーブルデータへと変換します。

def to_df(path, ticket_type):
    with open(path, 'r') as f:
        race_info = json.load(f)

    race_id = race_info['race_id']
    race_date = date.fromisoformat(race_info['date'])
    df_rank = pd.DataFrame(race_info['rank_data'])
    df_shusso = pd.DataFrame(race_info['shusso_data'])
    df_players = pd.DataFrame(race_info['player_data'])
    df_rank['number'] = df_rank['number'].astype(int)
    df_shusso['number'] = df_shusso['number'].astype(int)
    df = pd.merge(df_shusso, df_rank, on='number')

    df_players = df_players.drop(columns=['time_trial_time'])
    df_players = df_players.reset_index().rename(columns={'index':'number'})
    df_players['number'] += 1
    df = pd.merge(df, df_players)
    race_learning_data = {
        'race_id':race_id,
        'race_date':race_date,
        'round':race_id[-2:],
        'year':race_date.year,
        'month':race_date.month,
        'day':race_date.day,
        'player_num':len(df)
    }
    for row in df.itertuples():
        i = row.Index
        race_learning_data[f'{i}_age'] = row.age[:-1]
        race_learning_data[f'{i}_gear_rate'] = row.gear_rate
        race_learning_data[f'{i}_odds'] = row.odds
        race_learning_data[f'{i}_time_trial_time'] = row.time_trial_time
        race_learning_data[f'{i}_time_trial_rank'] = row.time_trial_rank
        race_learning_data[f'{i}_time_trial_gear_rate'] = row.time_trial_gear_rate

        race_learning_data[f'{i}_pre_rap_time'] = row.pre_lap_time
        race_learning_data[f'{i}_pre_gear_rate'] = row.pre_gear_rate

        race_learning_data[f'{i}_rank'] = row.rank
        race_learning_data[f'{i}_time'] = row.time

    top3_numbers = df_rank.head(3)['number'].to_list()
    odds_data = race_info['odds_data'][ticket_type]
    payout_data = race_info['payout_data']

    #print(race_id)
    ret = []
    for ticket_data in odds_data:
        #data = {}
        data = {key:value for key, value in race_learning_data.items()}

        if type(ticket_data['ticket']) == list:
            for i, number in enumerate(ticket_data['ticket']):
                data[f'ticket_{i}'] = number
        else:
            data[f'ticket'] = ticket_data['ticket']
        data['ticket_odds'] = ticket_data['odds']
        data['ticket_hit'] = check_ticket_success(ticket_data['ticket'], top3_numbers, ticket_type)
        data['ticket_payout'] = get_payout_data(ticket_data['ticket'], payout_data, ticket_type)
        ret.append(data)

    df = pd.DataFrame(ret)
    return df

チケットのあたり判定とオッズの取得

チケットとトップ3の馬を渡されたら、そのチケットが当たっているか、およびオッズを返す関数を定義します。

def check_ticket_success(ticket, top3_numbers, ticket_type):
    if ticket_type == 'tansyo':
        return ticket == top3_numbers[0]
    if ticket_type == 'sanrentan':
        return ticket == top3_numbers
    if ticket_type == 'nirentan':
        return ticket == top3_numbers[:2]
    if ticket_type == 'sanrenhuku':
        return sorted(ticket) == sorted(top3_numbers)
    if ticket_type == 'nirenhuku':
        return sorted(ticket) == sorted(top3_numbers[:2])
    if ticket_type == 'wide':
        for number in ticket:
            if number not in top3_numbers:
                return False
        return True
def get_payout_data(ticket, pyaout_data, ticket_type):
    def yen_to_float(text):
        return float(text.replace(',', '').replace('円', ''))
    payout_info = pyaout_data[ticket_type]

    for _payout_info in payout_info:
        if _payout_info['ret'] == '特払':
            return None

    if ticket_type == 'tansyo':

        if int(payout_info[0]['ret']) == ticket:
            return yen_to_float(payout_info[0]['payout'])/100
    if ticket_type == 'sanrentan':
        hit_ticket = [int(n) for n in payout_info[0]['ret'].split('>')]
        if hit_ticket == ticket:
            return yen_to_float(payout_info[0]['payout'])/100
    if ticket_type == 'nirentan':
        hit_ticket = [int(n) for n in payout_info[0]['ret'].split('>')]
        if hit_ticket == ticket:
            return yen_to_float(payout_info[0]['payout'])/100
    if ticket_type == 'sanrenhuku':
        hit_ticket = [int(n) for n in payout_info[0]['ret'].split('-')]
        if sorted(hit_ticket) == sorted(ticket):
            return yen_to_float(payout_info[0]['payout'])/100
    if ticket_type == 'nirenhuku':
        hit_ticket = [int(n) for n in payout_info[0]['ret'].split('-')]
        if sorted(hit_ticket) == sorted(ticket):
            return yen_to_float(payout_info[0]['payout'])/100
    if ticket_type == 'wide':
        for _payout_info in payout_info:
            hit_ticket = [int(n) for n in _payout_info['ret'].split('-')]
            if sorted(hit_ticket) == sorted(ticket):
                return yen_to_float(_payout_info['payout'])/100
    return 0

データセットの作成

チケットの種類ごとに、データセットを作ります。

def make_dataset(ticket_type):
    files = glob.glob('souce_data/raw_data/*.json')
    ret_dfs = joblib.Parallel(n_jobs=-1, verbose=1)([joblib.delayed(to_df)(path, ticket_type) for path in files])
    df = pd.concat(ret_dfs, axis=0)

    save_name = f'learning_data/learning_01_{ticket_type}.pkl'
    df = df.sort_values(['race_date']).reset_index(drop=True)
    df.to_pickle(save_name)
if __name__ == '__main__':
    for ticket_type in ['tansyo', 'sanrentan', 'nirentan', 'sanrenhuku', 'nirenhuku', 'wide']:
        make_dataset(ticket_type)

実行

実行すると、learning_dataディレクトリ以下にデータが格納されます。

project
...
├── learning_data
|    ├── learning_01_nirenhuku.pkl
|    ├── learning_01_nirentan.pkl
|    ├── learning_01_sanrenhuku.pkl
|    ├── learning_01_sanrentan.pkl
|    ├── learning_01_tansyo.pkl
|    └── learning_01_wide.pkl
...
タイトルとURLをコピーしました