MATLABでボートレース解析をする5番目の記事です。
matlab_jp のツイッターの人から「いいねついた?」「競艇の解析まだ?」「何番目まで続くの?」って煽られるようになってしまった。。
「自分から投稿しますから!押さえつけないでください!後ろを張りついてきてください!」って返事しておこう。。
(という時期に書いております。)
ここまでの振り返り。
1番目の記事でダウンロードができて、2番目の記事で出走表を読み込んで、3番目の記事で結果を読み込んで、4番目の記事で配当データを取りました。
大体のデータは取れたので、学習をさせてみよう。
学習をさせる場合、まず何がほしい結果なのかを明確にしないとダメですね。今回はわかりやすく着順(1,2,3,4,5,6着)にしてみましょう。「今日の出走表を入れるだけで着順が出てきた!」というのが理想なので、理想に向かって進みましょう!
テーブルがバラけている問題
各記事毎に取ったデータだと、全部バラバラなのでテーブルを連結させちゃいましょう。T_Person っていうテーブルと T_Result の Finish があればいいよね。MATLAB だと innerjoin っていう超便利な関数があります!
今までのを全部ひっくるめたらこんなプログラムになる。
ファイルがダウンロードされてるんなら、こんなプログラムになるかな。
function T = boat_scan(since,to)
addpath('C:\work\boat')
tic
%% テーブルを作ろう
T = table;
%% ループ
for ix = datenum(since,'YYYYmmDD'):datenum(to,'YYYYmmDD')
%% 出走表の読み込み
fname = ['B',datestr(ix,'YYmmDD'),'.TXT'];
if exist(fname,'file') == 2 % 地震で 2011/3/12 から 3/31 あたりがない。
fid = fopen(fname);
S = textscan(fid,'%s','delimiter','\n');
B = S{1};
fclose(fid);
B = B(~cellfun(@isempty,(regexp(B,'^\d \d+'))));
Personal = cellfun(@scanB,B,'Unif',0);
Personal = unique(cell2mat(Personal),'rows');
T_Person = array2table(Personal,'VariableNames',{'NameNo','Age','Weight','Class','A_WinRate','A_DoubleWinRate','L_WinRate','L_DoubleWinRate','Motor','M_DoubleWinRate','Boat','B_DoubleWinRate'});
%% レース結果の読み込み
fname = ['K',datestr(ix,'YYmmDD'),'.TXT'];
fid = fopen(fname);
S = textscan(fid,'%s','delimiter','\n');
C = S{1};
fclose(fid);
%% BGN/ENDを探す
KBGN = find(contains(C,'BGN'));
KEND = find(contains(C,'END'));
%% BGN-END Loop
for BEloop = 1:length(KBGN)
T_Result = table;
%% BGN-ENDで切り取る
res = C(KBGN(BEloop)+1:KEND(BEloop)-1);
res(cellfun(@isempty,res)) = [];
% 場所
place = res{1}(1:3);
place(place == 12288) = [];
% 開催日と今何日目
ymd = datestr(ix,'YYYYmmDD');
idx = regexp(res{1},'第 \w日','match');
if ~isempty(idx) % 2018/01/16 の 2525行目にダメデータがあるのでスキップ
day = str2double(idx{1}(end-1));
start_idx = startsWith(res,'着');
x = find(start_idx)-1;
end_x = [x(2:end)-1;length(res)];
n = length(x);
% ここからレースのループ
for Rloop = 1:n
% レース番号
race = res(x(Rloop):end_x(Rloop));
oddsidx = find(contains(race,'単勝'));
if ~isempty(oddsidx)
race_detail = race(1:oddsidx-1);
odds_detail = race(oddsidx:end);
txt = textscan(race_detail{1},'%s');
R = txt{1}{1}; % レース番号
R = str2double(R(1:end-1));
R_NAME = txt{1}{2}; % レース名
R_NAME(R_NAME == 12288) = [];
midx = find(~cellfun(@isempty,regexp(txt{1},'H\d+m')));
LENGTH = txt{1}{midx};
LENGTH = str2double(LENGTH(2:end-1)); % H1800m の H/m を捨てる
WEATHER = txt{1}{midx+1};
WEATHER(WEATHER == 12288) = [];
midx = find(~cellfun(@isempty,regexp(txt{1},'風')));
WINDDIR = txt{1}{midx+1};
WINDDIR(WINDDIR == 12288) = [];
WINDPOW = txt{1}{midx+2};
WINDPOW(WINDPOW == 12288) = [];
WINDPOW(end) = [];
WINDPOW = str2double(WINDPOW);
midx = find(~cellfun(@isempty,regexp(txt{1},'波')));
WAVE = txt{1}{midx+1};
WAVE(WAVE == 12288) = [];
WAVE(end-1:end) = [];
WAVE = str2double(WAVE);
% 着順
FIN_idx = find(~cellfun(@isempty,regexp(race_detail,'^0\d')));
for iy = 1:length(FIN_idx)
rc = textscan(race_detail{FIN_idx(iy)},'%s');
FRAME = str2double(rc{1}{2});
NAME = str2double(rc{1}{3});
MOTOR = str2double(rc{1}{5});
BOAT = str2double(rc{1}{6});
EXTIME = str2double(rc{1}{7});
ENTRY = str2double(rc{1}{8}); % 進入コース(本番)
FINISH = str2double(rc{1}{1});
T_Result(end+1,:) = {place,ymd,day,R,R_NAME,LENGTH,WEATHER,WINDDIR,WINDPOW,WAVE,FRAME,NAME,MOTOR,BOAT,EXTIME,ENTRY,FINISH};
T_Result.Properties.VariableNames = {'Place','YYYYMMDD','Day','R','RaceName','Distance','Weather','WindDir','WindPow','Wave','Frame','NameNo','Motor','Boat','ExtTime','Entry','Finish',};
end
end
end
if isempty(T_Result)
elseif isempty(T)
T = innerjoin(T_Result,T_Person,'Keys','NameNo');
else
T = [T;innerjoin(T_Result,T_Person,'Keys','NameNo')];
end
end
end
end
t_toc = toc;
fprintf(1,[num2str(height(T)),'行処理済み [',datestr(ix,'YYYY/mm/DD'),']:',num2str(t_toc),'(秒)経過',10]);
end
T.Properties.VariableNames{13} = 'Motor';
T.Properties.VariableNames{14} = 'Boat';
T.Motor_T_Person = [];
T.Boat_T_Person = [];
T.Place = categorical(T.Place);
T.Day = categorical(T.Day);
T.R = categorical(T.R);
T.Weather = categorical(T.Weather);
T.WindDir = categorical(T.WindDir);
T.Frame = categorical(T.Frame);
T.NameNo = categorical(T.NameNo);
T.Motor = categorical(T.Motor);
T.Boat = categorical(T.Boat);
T.Entry = categorical(T.Entry);
T.Finish = categorical(T.Finish);
T.Class = categorical(T.Class);
T.Age = categorical(T.Age);
end
function sc = scanB(txt)
NAME = str2double(txt(3:6));
MOTOR = str2double(txt(41:43));
BOAT = str2double(txt(50:52));
AGE = str2double(txt(11:12));
WEIGHT = str2double(txt(15:16));
if strcmp(txt(17:18),'A1')
CLASS = 1;
elseif strcmp(txt(17:18),'A2')
CLASS = 2;
elseif strcmp(txt(17:18),'B1')
CLASS = 3;
else
CLASS = 4;
end
A_WINRATE = str2double(txt(19:23));
A_DOUBLEWINRATE = str2double(txt(24:29));
L_WINRATE = str2double(txt(30:34));
L_DOUBLEWINRATE = str2double(txt(35:40));
M_DOUBLEWINRATE = str2double(txt(44:49));
B_DOUBLEWINRATE = str2double(txt(53:58));
sc = [NAME,AGE,WEIGHT,CLASS,...
A_WINRATE,A_DOUBLEWINRATE,...
L_WINRATE,L_DOUBLEWINRATE,MOTOR,M_DOUBLEWINRATE,...
BOAT,B_DOUBLEWINRATE];
end
実行してみよう。
2019/8/1 から 8/3 までだったら、コマンドウィンドウでこう。
>> T = boat_scan("20190801","20190803");
932行処理済み [2019/08/01]:1.3405(秒)経過
1866行処理済み [2019/08/02]:2.6928(秒)経過
2854行処理済み [2019/08/03]:3.9852(秒)経過
念のため確認。
>> T
T =
2854×26 table
Place YYYYMMDD Day R RaceName Distance Weather WindDir WindPow Wave Frame NameNo Motor Boat ExtTime Entry Finish Age Weight Class A_WinRate A_DoubleWinRate L_WinRate L_DoubleWinRate M_DoubleWinRate B_DoubleWinRate
_____ __________ ___ __ ____________ ________ _______ _______ _______ ____ _____ ______ _____ ____ _______ _____ ______ ___ ______ _____ _________ _______________ _________ _______________ _______________ _______________
唐津 '20190801' 4 7 '一般' 1800 晴 北東 5 5 2 4224 21 59 6.81 2 1 37 48 3 3.95 23.16 4.65 27.03 30.84 27.03
唐津 '20190801' 4 10 '準優進出戦' 1800 晴 北東 4 4 3 4240 68 56 6.78 3 1 35 45 3 4.08 22.62 3.63 15.79 37.02 43.06
唐津 '20190801' 4 9 '準優進出戦' 1800 晴 北東 5 5 4 4286 55 63 6.81 4 4 35 50 3 5.25 31.93 5.73 54.55 33.91 39.73
唐津 '20190801' 4 4 '一般' 1800 晴 北 4 4 1 4289 34 53 6.79 1 5 34 48 2 5.98 44.65 5.83 50 39.64 26.58
唐津 '20190801' 4 11 '準優進出戦' 1800 晴 北東 4 4 4 4313 49 85 6.85 4 3 35 50 2 5.71 36.62 6.79 51.28 25.96 26.58
唐津 '20190801' 4 11 '準優進出戦' 1800 晴 北東 4 4 6 4338 30 81 6.76 6 6 35 45 4 3.59 12.96 0 0 33.48 39.19
唐津 '20190801' 4 10 '準優進出戦' 1800 晴 北東 4 4 2 4347 15 60 6.84 2 4 34 44 2 6.24 48 5.56 33.33 44.04 34.25
唐津 '20190801' 4 11 '準優進出戦' 1800 晴 北東 4 4 5 4349 35 82 6.85 5 5 33 49 3 5.08 24.53 4.46 17.86 32.88 25.93
唐津 '20190801' 4 6 '一般' 1800 晴 北 3 3 4 4372 13 46 6.75 5 2 31 47 1 5.46 36.47 0 0 31.72 23.29
順番がバラバラになっちゃったけど Finish も入ってるしよさそう!
ここからが学習。
MATLAB だとアプリを使えばいいよね。(便利!)
[アプリ] → [分類学習器] っていうのを起動して T を読み込ませよう。応答は Finish を選んでセッションの開始!
とりあえず試しに、[すべてのクイック学習] とかを選んで「学習」ボタンをクリック。
一番良くて精度 23.3%!
なんか低くない?
3日分しか取ってないからしょうがないとして、これを使って今日のレースを予想してみよう。
とりあえずエクスポートボタンを押しておきましょう。デフォルトだと trainedModel とかいう構造体が出てきますね。
あとは今日の出走表を取ってデータを入れればいいんだけど、出走表になくて結果にあるデータが入っちゃってるので(天気とか風とか)、適当に埋めましょう。
今日の出走表のダウンロードから始めるとして、こんなプログラム。
function T_today = boat_today
fname = ['B',datestr(now,'YYmmDD'),'.TXT'];
url = ['http://www1.mbrace.or.jp/od2/B/',datestr(now,'YYYYmm'),'/b',datestr(now,'YYmmDD'),'.lzh'];
dl = [getenv('USERPROFILE'),'\Downloads\'];
zipname = ['b',datestr(now,'YYmmDD'),'.lzh'];
if ~exist(fname)
% 出走表を取ってこよう。
if ~exist([dl,zipname])
web(url,'-browser')
pause(10) % 早すぎるとダウンロード中とかで見つからない。
end
dos(['"C:\Program Files (x86)\Lhaplus\Lhaplus.exe" /o:C:\work\boat ',dl,zipname,' &']);
end
pause(5) % 早すぎるとファイルがないとかで見つからない。
fid = fopen(fname);
S = textscan(fid,'%s','delimiter','\n');
C = S{1};
fid = fclose(fid);
%% BGN/ENDを探す
BBGN = find(contains(C,'BGN'));
BEND = find(contains(C,'END'));
%% BGN-END Loop
T_today = table;
ymd = datestr(now,'YYYYmmDD');
for BEloop = 1:length(BBGN)
%% BGN-ENDで切り取る
res = C(BBGN(BEloop)+1:BEND(BEloop)-1);
res(cellfun(@isempty,res)) = [];
% 場所
place = res{1}(7:9);
place(place == 12288) = [];
% 開催日と今何日目
idx = regexp(res{1},'第 \w日','match');
if idx{1}(end-1) > 60000 % 全角だったら
day = idx{1}(end-1) - 65296;
else
day = str2double(idx{1}(end-1));
end
% レース番号
idx = ~cellfun(@isempty, regexp(res,'[1234567890]+R'));
start_idx = find(~cellfun(@isempty, regexp(res,'1R')));
x = find(idx);
x_end = [x(2:end)-1 ; length(res)];
n = length(x);
% ここからレースのループ
for Rloop = 1:12
% レース番号
race = res(x(Rloop):x_end(Rloop));
R = Rloop; % レース番号
TR = textscan(race{1},'%s');
R_NAME = TR{1}{2}; % レース名
R_NAME(R_NAME == 12288) = [];
LENGTH = str2double(char(TR{1}{3}(2:end-1)-65248));
WEATHER = '晴';
WINDDIR = '北西';
WINDPOW = 0;
WAVE = 0;
% 舟ごと
for iy = 1:6
rc = race{5+iy};
FRAME = str2double(rc(1));
NAME = str2double(rc(3:6));
MOTOR = str2double(rc(41:43));
BOAT = str2double(rc(50:52));
EXTIME = 7.00; % 仮
ENTRY = FRAME; % 進入コース(本番)
AGE = str2double(rc(11:12));
WEIGHT = str2double(rc(15:16));
if strcmp(rc(17:18),'A1')
CLASS = 1;
elseif strcmp(rc(17:18),'A2')
CLASS = 2;
elseif strcmp(rc(17:18),'B1')
CLASS = 3;
else
CLASS = 4;
end
A_WINRATE = str2double(rc(19:23));
A_DOUBLEWINRATE = str2double(rc(24:29));
L_WINRATE = str2double(rc(30:34));
L_DOUBLEWINRATE = str2double(rc(35:40));
M_DOUBLEWINRATE = str2double(rc(44:49));
B_DOUBLEWINRATE = str2double(rc(53:58));
T_today(end+1,:) = {place,ymd,day,...
R,R_NAME,LENGTH,WEATHER,WINDDIR,WINDPOW,...
WAVE,FRAME,NAME,MOTOR,BOAT,EXTIME,ENTRY,...
AGE,WEIGHT,CLASS,A_WINRATE,A_DOUBLEWINRATE,...
L_WINRATE,L_DOUBLEWINRATE,M_DOUBLEWINRATE,...
B_DOUBLEWINRATE};
end
end
end
T_today.Properties.VariableNames = {'Place','YYYYMMDD','Day','R','RaceName','Distance','Weather','WindDir','WindPow','Wave','Frame','NameNo','Motor','Boat','ExtTime','Entry',...
'Age','Weight','Class','A_WinRate','A_DoubleWinRate','L_WinRate','L_DoubleWinRate','M_DoubleWinRate','B_DoubleWinRate'};
T_today.Place = categorical(T_today.Place);
T_today.Day = categorical(T_today.Day);
T_today.R = categorical(T_today.R);
T_today.Weather = categorical(T_today.Weather);
T_today.WindDir = categorical(T_today.WindDir);
T_today.Frame = categorical(T_today.Frame);
T_today.NameNo = categorical(T_today.NameNo);
T_today.Motor = categorical(T_today.Motor);
T_today.Boat = categorical(T_today.Boat);
T_today.Entry = categorical(T_today.Entry);
T_today.Age = categorical(T_today.Age);
T_today.Class = categorical(T_today.Class);
あとは適当なスクリプトを走らせる。
上で作ったプログラムと構造体の predictFcn を使えばいいね。今日は 2019/8/21 なので、尼崎12RのG3優勝戦 でも予想してみよう。
T_today = boat_today;
Place = '尼崎';
R = 12;
Tx = T_today(T_today.Place==Place & T_today.R==num2str(R),:);
Result = trainedModel.predictFcn(Tx);
disp([Tx.Frame, Result])
disp コマンドの出した結果↓↓。
1 1
2 4
3 3
4 1
5 4
6 3
雑な表示だけど、
- 1号艇1着(当たり)
- 2号艇4着
- 3号艇3着
- 4号艇1着
- 5号艇4着
- 6号艇3着
で、実際の結果が 135426 なので、1番だけ当たり。これで 16% なので、23% って大体このくらい+うっかりもう一個当たるかな、くらいよね。
デフォルトで、何も考えずに使うとイマイチ。
もう少し工夫しないと着順は被るし精度は低いし、いい結果は得られません、ということが分かりました!大発見ですね!(前向き)
ただ、ドッサリ書いたら疲れたのでまた今度。。
「いいね」が多かったら続きを書きますね!(いいねの亡者)