MATLABで競艇の解析を始めよう（教師あり学習）

# MATLABでボートレース解析をする５番目の記事です。
　[matlab_jp のツイッターの人](https://twitter.com/matlab_jp)から「いいねついた？」「競艇の解析まだ？」「何番目まで続くの？」って煽られるようになってしまった。。

　「自分から投稿しますから！押さえつけないでください！後ろを張りついてきてください！」って返事しておこう。。


（という時期に書いております。）

## ここまでの振り返り。
　[１番目の記事](https://qiita.com/teruqii/items/11c21882613db088e339)でダウンロードができて、[２番目の記事](https://qiita.com/teruqii/items/9aede1165e7428d3f89c)で出走表を読み込んで、[３番目の記事](https://qiita.com/teruqii/items/14bdffe71ef9ff061e55)で結果を読み込んで、[４番目の記事](https://qiita.com/teruqii/items/986d4fba96544960f367)で配当データを取りました。

# 大体のデータは取れたので、学習をさせてみよう。
　学習をさせる場合、まず何がほしい結果なのかを明確にしないとダメですね。今回はわかりやすく着順（1,2,3,4,5,6着）にしてみましょう。「今日の出走表を入れるだけで着順が出てきた！」というのが理想なので、理想に向かって進みましょう！

## テーブルがバラけている問題
　各記事毎に取ったデータだと、全部バラバラなのでテーブルを連結させちゃいましょう。T_Person っていうテーブルと T_Result の Finish があればいいよね。MATLAB だと innerjoin っていう超便利な関数があります！

## 今までのを全部ひっくるめたらこんなプログラムになる。
　ファイルがダウンロードされてるんなら、こんなプログラムになるかな。

```matlab:boat_scan.m
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 までだったら、コマンドウィンドウでこう。

```matlab
>> T = boat_scan("20190801","20190803");

932行処理済み [2019/08/01]：1.3405(秒)経過
1866行処理済み [2019/08/02]：2.6928(秒)経過
2854行処理済み [2019/08/03]：3.9852(秒)経過
```

　念のため確認。

```matlab
>> 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 を選んでセッションの開始！
![TR_app1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475339/729d607f-e223-32c2-11a0-0efeb27096fe.png)

　とりあえず試しに、[すべてのクイック学習] とかを選んで「学習」ボタンをクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475339/ccd16230-4098-45ec-8fc5-6d6f54fcdaf7.png)

一番良くて精度 23.3%！
なんか低くない？

## ３日分しか取ってないからしょうがないとして、これを使って今日のレースを予想してみよう。
　とりあえずエクスポートボタンを押しておきましょう。デフォルトだと trainedModel とかいう構造体が出てきますね。

　あとは今日の出走表を取ってデータを入れればいいんだけど、出走表になくて結果にあるデータが入っちゃってるので（天気とか風とか）、適当に埋めましょう。

　今日の出走表のダウンロードから始めるとして、こんなプログラム。


```matlab:boat_today.m

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,'[１２３４５６７８９０]+Ｒ'));
    start_idx = find(~cellfun(@isempty, regexp(res,'１Ｒ')));
    
    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優勝戦](https://www.boatrace.jp/owpc/pc/race/racelist?rno=12&jcd=13&hd=20190821) でも予想してみよう。

```matlab
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 コマンドの出した結果↓↓。

```matlab
     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 ](https://www.boatrace.jp/owpc/pc/race/raceresult?rno=12&jcd=13&hd=20190821) なので、１番だけ当たり。これで 16% なので、23% って大体このくらい＋うっかりもう一個当たるかな、くらいよね。


# デフォルトで、何も考えずに使うとイマイチ。
　もう少し工夫しないと着順は被るし精度は低いし、いい結果は得られません、ということが分かりました！大発見ですね！（前向き）

　ただ、ドッサリ書いたら疲れたのでまた今度。。
　「いいね」が多かったら続きを書きますね！（いいねの亡者）