収集したデータを学習に利用しやすい形式に変換します。
データセットの形式
せっかくですから、競馬や一般競輪の予想ではできなかったデータ形式に変換したいと思います。
いままでのデータ形式
競馬や競輪に対して予測器を作りたいのであれば、シンプルに考えれば馬券1枚に対して、当たる確率を求めたいです。
しかし、すべての馬券に対して、予測確率を求める学習データを作ることは困難です。
例えば、競馬では一度のレースに参加する馬の数が最大18頭、一般競輪では最大9人でした。
連単の場合は順列、連複の場合は組み合わせとなるため、各馬券の数は次のとおりになります。
競馬(18頭) | 一般競輪(9人) | |
---|---|---|
単勝(複勝) | 18 | 9 |
二連複(ワイド) | 153 | 36 |
二連単 | 306 | 72 |
三連複 | 816 | 84 |
三連単 | 4,896 | 504 |
各馬券に対して予測をするデータセットを構築するとなると、単勝の場合はレース数の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
...