渋谷駅前で働くデータサイエンティストのブログ

元祖「六本木で働くデータサイエンティスト」です / 道玄坂→銀座→東京→六本木→渋谷駅前

UCI機械学習リポジトリのデータで遊ぶ(1):2013年のテニス四大大会match stats

去る2月5日(木)にレバレジーズ様のお招きで渋谷でちょっとしたRハンズオンをやってきました。



この中で利用したのが、UCI Machine Learning Repositoryのオープンデータセット。恥ずかしながら僕は最近になってこのリポジトリの存在を知ったんですが、結構な老舗の割に今でも頻繁に更新されていて、つい最近もNoisyOfficeというデータセットが追加されたばかりのようです。


で、上記Rハンズオンの中で題材に取り上げたデータのひとつがTennis Major Tournament Match Statistics。これは2013年のプロテニスの四大大会のmatch statsをATP / WTA双方について集めたもので、おそらくIBMとかOMEGAとかRADO辺りが公開しているものではないかと思います。実業団リーグにも参加していたことのあるテニス好きの僕としてはこれほど面白いデータもないということで取り上げさせていただいたんですが、時間の都合もありUSオープンのデータしか用いませんでした。


ということで、その補遺も兼ねて4大会全てのデータを使って以下機械学習のお遊びということでやってみようと思います。


データセット


面倒なので4大会分男女別にまとめたものをGitHubに置いておきました。men.txtとwomen.txtをそれぞれDLしてきて、データフレームdm, dwとして読み込んでください。



いくつか変数を取捨選択してあるので全部が揃っているわけではないんですが、元のページを見ると変数について以下のように説明があります。

Player 1 Name of Player 1
Player 2 Name of Player 2
Result Result of the match (0/1) - Referenced on Player 1 is Result = 1 if Player 1 wins (FNL.1>FNL.2)
FSP.1 First Serve Percentage for player 1 (Real Number)
FSW.1 First Serve Won by player 1 (Real Number)
SSP.1 Second Serve Percentage for player 1 (Real Number)
SSW.1 Second Serve Won by player 1 (Real Number)
ACE.1 Aces won by player 1 (Numeric-Integer)
DBF.1 Double Faults committed by player 1 (Numeric-Integer)
WNR.1 Winners earned by player 1 (Numeric)
UFE.1 Unforced Errors committed by player 1 (Numeric)
BPC.1 Break Points Created by player 1 (Numeric)
BPW.1 Break Points Won by player 1 (Numeric)
NPA.1 Net Points Attempted by player 1 (Numeric)
NPW.1 Net Points Won by player 1 (Numeric)
TPW.1 Total Points Won by player 1 (Numeric)
ST1.1 Set 1 result for Player 1 (Numeric-Integer)
ST2.1 Set 2 Result for Player 1 (Numeric-Integer)
ST3.1 Set 3 Result for Player 1 (Numeric-Integer)
ST4.1 Set 4 Result for Player 1 (Numeric-Integer)
ST5.1 Set 5 Result for Player 1 (Numeric-Integer)
FNL.1 Final Number of Games Won by Player 1 (Numeric-Integer)
FSP.2 First Serve Percentage for player 2 (Real Number)
FSW.2 First Serve Won by player 2 (Real Number)
SSP.2 Second Serve Percentage for player 2 (Real Number)
SSW.2 Second Serve Won by player 2 (Real Number)
ACE.2 Aces won by player 2 (Numeric-Integer)
DBF.2 Double Faults committed by player 2 (Numeric-Integer)
WNR.2 Winners earned by player 2 (Numeric)
UFE.2 Unforced Errors committed by player 2 (Numeric)
BPC.2 Break Points Created by player 2 (Numeric)
BPW.2 Break Points Won by player 2 (Numeric)
NPA.2 Net Points Attempted by player 2 (Numeric)
NPW.2 Net Points Won by player 2 (Numeric)
TPW.2 Total Points Won by player 2 (Numeric)
ST1.2 Set 1 result for Player 2 (Numeric-Integer)
ST2.2 Set 2 Result for Player 2 (Numeric-Integer)
ST3.2 Set 3 Result for Player 2 (Numeric-Integer)
ST4.2 Set 4 Result for Player 2 (Numeric-Integer)
ST5.2 Set 5 Result for Player 2 (Numeric-Integer)
FNL.2 Final Number of Games Won by Player 2 (Numeric-Integer)
Round Round of the tournament at which game is played (Numeric-Integer)


いくつか欠測値というかNAがある*1ので、色々迷ったんですがひとまず全部0で置換して埋めてあります。また完全に欠測でNULLになっているところも0で埋めました。なので、厳密には正しくない前処理がかかっている点は悪しからずご了承あれ。


ここでは目的変数としてResult、すなわち勝敗の結果を選びます。つまり全ての変数をPlayer 1の勝利に貢献するかどうかで評価するという形になります。


とりあえずSVMとランダムフォレストで男子の結果に基づいてモデルを推定する


最終的に手元にあるdmとdwは、上記オリジナルデータからFNL.1とFNL.2を削除したものです。理由は簡単で、テニスというスポーツのルール上「勝った方の総ゲーム数は必ず負けた方を上回る」*2ため、勝敗を説明する変数としては不適切だからです。


また、プレイヤー名も男子の中でor女子の中でCVする時には使えるんですが、そもそも参加人数が限られることを考えるとあまり意味がないので、一旦この後の分析では削除します*3。先にResult列をfactor型に直すのも含めて以下のように行います。

dm$Result<-as.factor(dm$Result)
dw$Result<-as.factor(dw$Result)
dm2<-dm[,3:39]
dw2<-dw[,3:39]


その上でチューニングしつつ以下のようにそれぞれSVMとランダムフォレストで分類モデル推定を行います。

> library(e1071)
> library(randomForest)
> dm2.tune<-tune.svm(Result~.,data=dm2)
> dm2.tune$best.model

Call:
best.svm(x = Result ~ ., data = dm2)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  radial 
       cost:  1 
      gamma:  0.02777778 

Number of Support Vectors:  204

> dm2.svm<-svm(Result~.,dm2,cost=1,gamma=dm2.tune$best.model$gamma)
> dm2.svm

Call:
svm(formula = Result ~ ., data = dm2, cost = 1, gamma = dm2.tune$best.model$gamma)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  radial 
       cost:  1 
      gamma:  0.02777778 

Number of Support Vectors:  204

> tuneRF(dm2[,-1],dm2[,1],doBest=T)
mtry = 6  OOB error = 6.31% 
Searching left ...
mtry = 3 	OOB error = 6.52% 
-0.03225806 0.05 
Searching right ...
mtry = 12 	OOB error = 4.28% 
0.3225806 0.05 
mtry = 24 	OOB error = 5.7% 
-0.3333333 0.05 

Call:
 randomForest(x = x, y = y, mtry = res[which.min(res[, 2]), 1]) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 12

        OOB estimate of  error rate: 3.87%
Confusion matrix:
    0   1 class.error
0 242   9  0.03585657
1  10 230  0.04166667
> dm2.rf<-randomForest(Result~.,dm2,mtry=12)
> dm2.rf

Call:
 randomForest(formula = Result ~ ., data = dm2, mtry = 12) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 12

        OOB estimate of  error rate: 3.67%
Confusion matrix:
    0   1 class.error
0 243   8  0.03187251
1  10 230  0.04166667


randomForestのOOB errorは3.67%と、たかがこの程度のmatch statsでもなかなか正確なんだなーということが分かりますね。ちなみに自己予測をしてみると

> table(dm2$Result,predict(dm2.svm,newdata=dm2[,-1]))
   
      0   1
  0 250   1
  1   3 237
> table(dm2$Result,predict(dm2.rf,newdata=dm2[,-1]))
   
      0   1
  0 251   0
  1   0 240


ということで、まぁ悪くないですね。これでSVMとランダムフォレストの分類モデルが出揃いました。


変数重要度を見てみる


自分もテニスプレイヤーである身からすると、気になるのが「どの数字が重要なのか?」ということ。1stサーブが入ってる方が有利?それとも第1セットを大差で奪った方が有利?とか色々思うわけで。。。ということでimportance関数で見てみましょう。

> importance(dm2.rf)
      MeanDecreaseGini
FSP.1         1.326090
FSW.1         3.414751
SSP.1         1.375221
SSW.1         1.774569
ACE.1         2.131037
DBF.1         1.589312
WNR.1         1.532577
UFE.1         1.424780
BPC.1        28.085637
BPW.1         5.113764
NPA.1         1.058818
NPW.1         1.680666
TPW.1         3.227819
ST1.1         9.497543
ST2.1         9.333334
ST3.1        15.241856
ST4.1         9.465749
ST5.1        12.178140
FSP.2         1.550794
FSW.2         3.959450
SSP.2         1.453953
SSW.2         2.711236
ACE.2         1.936136
DBF.2         1.785843
WNR.2         2.226341
UFE.2         1.507302
BPC.2        34.822428
BPW.2         4.745056
NPA.2         1.233000
NPW.2         1.349217
TPW.2         4.884543
ST1.2         9.525584
ST2.2         6.925047
ST3.2        32.095133
ST4.2         9.768185
ST5.2        12.961211
> barplot(c(dm2.rf$importance),names.arg = rownames(dm2.rf$importance),horiz=F,cex.names = 0.7)

f:id:TJO:20150206115753p:plain


いくつか目立つ変数があるんですが、際立っているのがBPC.1とBPC.2。これ何だっけ?と思ったら、「ブレークポイント創出数(ブレーク機会数)」なんですね。


一般に、ATP(プロテニス男子)ツアーでは誰もが200km超のビッグサーブを打つのが当たり前で、リターンゲーム(相手のサービスゲーム)というのはなかなか取れないもの。なので、いかにしてリターンゲームを取る(ブレークする)かが重要とされます。


で、「ブレークポイント」というのは要はリターンゲームで先に40に達するか、アドバンテージ・レシーバーとなるポイントに達することを指します。レシーバーから見て「このポイントを取ればゲームが取れる」ポイントであり、言ってみれば勝つためのチャンスです。この機会を創り出す多ければ多いほど相手にプレッシャーをかけることができます。


ところが、面白かったのが「実際にブレークに成功した数(BPW.1 / BPW.2)」よりも「ブレーク機会数(BPC.1 / BPC.2)」の方が重要度が高いこと。結果的にブレークに成功したかどうかよりも、相手にそれだけプレッシャーをかけられた方が最終的に勝っている(そして恐らく負けたPlayer 2側でも高いところを見るとBPCが少ないと最終的に負ける)というわけです。これは実は個人的には意外で面白かったです。


男子のモデルで女子の結果を予測してみる


さて、モデルの中身は大体分かったので、予測してみましょう。でも男子内部でやってもその汎化ぶりが分からないので、一見掟破りに見えますが男子のSVM / ランダムフォレストの分類モデルで女子の結果を予測してみます。

> table(dw2$Result,predict(dm2.svm,newdata=dw2[,-1]))
   
      0   1
  0 217  10
  1  13 212

# 正答率94.9%

> table(dw2$Result,predict(dm2.rf,newdata=dw2[,-1]))
   
      0   1
  0 222   5
  1   5 220

# 正答率97.8%


意外にもかなりの高精度になりました。これまた個人的には結構意外な感があって、「へーーーーーWTAでもそんなにブレーク機会数の方が重要なくらいサービスキープ当たり前の世界が広がってるのか」という感じです。ちなみに女子の方の変数重要度が見てみたいのでrandomForestで推定してみると、

> tuneRF(dw2[,-1],dw2[,1],doBest=T)
mtry = 6  OOB error = 1.55% 
Searching left ...
mtry = 3 	OOB error = 3.1% 
-1 0.05 
Searching right ...
mtry = 12 	OOB error = 1.11% 
0.2857143 0.05 
mtry = 24 	OOB error = 1.11% 
0 0.05 

Call:
 randomForest(x = x, y = y, mtry = res[which.min(res[, 2]), 1]) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 12

        OOB estimate of  error rate: 0.88%
Confusion matrix:
    0   1 class.error
0 224   3 0.013215859
1   1 224 0.004444444
> dw2.rf<-randomForest(Result~.,dw2,mtry=12)
> importance(dw2.rf)
      MeanDecreaseGini
FSP.1        0.6028805
FSW.1        7.3351962
SSP.1        0.5618011
SSW.1        1.6264988
ACE.1        0.6920522
DBF.1        0.5292140
WNR.1        1.7804903
UFE.1        1.9209395
BPC.1       13.7550851
BPW.1        6.0022307
NPA.1        1.1241421
NPW.1        1.0004857
TPW.1        3.0881920
ST1.1       28.3050922
ST2.1       28.2379261
ST3.1       29.8653854
ST4.1        0.0000000
ST5.1        0.0000000
FSP.2        0.6475287
FSW.2        4.0541477
SSP.2        0.6163471
SSW.2        1.2344994
ACE.2        0.7510999
DBF.2        0.6723578
WNR.2        1.1824890
UFE.2        3.7529122
BPC.2        4.4327094
BPW.2        3.8838350
NPA.2        0.7812590
NPW.2        0.7722206
TPW.2        2.0182145
ST1.2       23.5909031
ST2.2       22.0260401
ST3.2       28.6395859
ST4.2        0.0000000
ST5.2        0.0000000
> barplot(c(dw2.rf$importance),names.arg = rownames(dw2.rf$importance),horiz=F,cex.names = 0.7)

f:id:TJO:20150206115928p:plain


打って変わって各セットでの獲得ゲーム数の影響が大きいですね。。。それだけ大差がついてる試合が多いんですかね? いや、そうだっけ? ってことは。。。


各セットの獲得ゲーム数を削除してもう一度やり直してみる


そう、最初に「総獲得ゲーム数」を削除したのに各セットの獲得ゲーム数を削除してなかったのでしたorz これはこれで重要といえば重要なんですが、結局これも結果と紐付いている変数なので正しくなさそうです。なので、各セットの獲得ゲーム数を削除してもう一度やってみましょう。

> dm3<-dm2
> dm3$ST1.1<-c()
> dm3$ST2.1<-c()
> dm3$ST3.1<-c()
> dm3$ST4.1<-c()
> dm3$ST5.1<-c()
> dm3$ST1.2<-c()
> dm3$ST2.2<-c()
> dm3$ST3.2<-c()
> dm3$ST4.2<-c()
> dm3$ST5.2<-c()
> dw3<-dw2
> dw3$ST1.1<-c()
> dw3$ST2.1<-c()
> dw3$ST3.1<-c()
> dw3$ST4.1<-c()
> dw3$ST5.1<-c()
> dw3$ST1.2<-c()
> dw3$ST2.2<-c()
> dw3$ST3.2<-c()
> dw3$ST4.2<-c()
> dw3$ST5.2<-c()


何だこの糞コードとか言わないでorz 後は普通に同じことやるだけです。

> dm3.tune<-tune.svm(Result~.,data=dm3)
> dm3.tune$best.model

Call:
best.svm(x = Result ~ ., data = dm3)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  radial 
       cost:  1 
      gamma:  0.03846154 

Number of Support Vectors:  231

> dm3.svm<-svm(Result~.,dm3,cost=1,gamma=dm3.tune$best.model$gamma)
> tuneRF(dm3[,-1],dm3[,1],doBest=T)
mtry = 5  OOB error = 10.79% 
Searching left ...
mtry = 3 	OOB error = 12.63% 
-0.1698113 0.05 
Searching right ...
mtry = 10 	OOB error = 12.42% 
-0.1509434 0.05 

Call:
 randomForest(x = x, y = y, mtry = res[which.min(res[, 2]), 1]) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 5

        OOB estimate of  error rate: 10.39%
Confusion matrix:
    0   1 class.error
0 229  22   0.0876494
1  29 211   0.1208333
> dm3.rf<-randomForest(Result~.,dm3,mtry=5)
> dm3.rf

Call:
 randomForest(formula = Result ~ ., data = dm3, mtry = 5) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 5

        OOB estimate of  error rate: 9.78%
Confusion matrix:
    0   1 class.error
0 231  20  0.07968127
1  28 212  0.11666667
> table(dw3$Result,predict(dm3.svm,newdata=dw3[,-1]))
   
      0   1
  0 210  17
  1  17 208

# 正答率92.5%

> table(dw3$Result,predict(dm3.rf,newdata=dw3[,-1]))
   
      0   1
  0 219   8
  1  19 206

# 正答率96.2%


randomForestのOOB errorが9.78%に上がりましたが、男子の分類モデルで女子の結果を予測した結果はそれほど悪くなってません。変数重要度を見てみましょう。

> importance(dm3.rf)
      MeanDecreaseGini
FSP.1         3.393704
FSW.1         6.323733
SSP.1         3.744789
SSW.1         4.877575
ACE.1         6.023498
DBF.1         3.886220
WNR.1         4.767306
UFE.1         3.341895
BPC.1        40.832482
BPW.1        21.418975
NPA.1         3.482657
NPW.1         2.940436
TPW.1        11.135520
FSP.2         3.357201
FSW.2        10.112785
SSP.2         3.433470
SSW.2         5.292208
ACE.2         6.303114
DBF.2         3.771373
WNR.2         6.827519
UFE.2         3.841238
BPC.2        44.329237
BPW.2        20.888179
NPA.2         3.975112
NPW.2         3.533628
TPW.2        13.019895
> barplot(c(dm3.rf$importance),names.arg = rownames(dm3.rf$importance),horiz=F,cex.names = 0.7)

f:id:TJO:20150206121201p:plain


今度はTPW.1/2(総獲得ポイント数)の影響が上がってきたものの、全体としてはBPC.1/2(ブレーク機会数)とBPW.1/2(ブレーク成功数)の影響が大きいという構図に変わりはありません。


その中で目に付くのがFSW.2(Player 2の1stサーブ時獲得ポイント数)の影響度が意外と大きいこと。これはPlayer 1のそれよりも大きいんですね。つまり「1stサーブ時にポイントが取れないほど負けやすい」ことを示しています。あー、これ分かるわ。。。


なお、多分僕同様に長年テニスをやってる人なら不思議に思うかもしれないのがUFE.1/2(アンフォーストエラー数)の変数重要度があまり高くないというかほとんどない点。比較の問題で言えばWNR.1/2(ウィナー数)の方が高いということで、これはエラーしてでもウィナーを多く取った方が勢いがつくってことなのかもしれませんね。


ということで、まとめ


皆さん説明変数の取捨選択には十二分に気をつけましょうorz 実は後半部分はRハンズオンでも触れ損ねたところだったので、今回気付いて良かったです。。。


UCI機械学習リポジトリには他にも面白いデータが山ほど転がっているので、暇を見て色々遊びながら自分の練習にでもしようかなと思ってます。


追記1


やっぱりTPW.1/2(総獲得ポイント数)は使わない方がいいんじゃないかなぁと思い直して、削除したバージョンでもう一度やってみました。

> dm4<-dm3
> dw4<-dw3
> dm4$TPW.1<-c()
> dm4$TPW.2<-c()
> dw4$TPW.1<-c()
> dw4$TPW.2<-c()
> dm4.tune<-tune.svm(Result~.,data=dm4)
> dm4.tune$best.model

Call:
best.svm(x = Result ~ ., data = dm4)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  radial 
       cost:  1 
      gamma:  0.04166667 

Number of Support Vectors:  225

> dm4.svm<-svm(Result~.,dm4,cost=1,gamma=dm4.tune$best.model$gamma)
> tuneRF(dm4[,-1],dm4[,1],doBest=T)
mtry = 4  OOB error = 10.59% 
Searching left ...
mtry = 2 	OOB error = 14.66% 
-0.3846154 0.05 
Searching right ...
mtry = 8 	OOB error = 11% 
-0.03846154 0.05 

Call:
 randomForest(x = x, y = y, mtry = res[which.min(res[, 2]), 1]) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 4

        OOB estimate of  error rate: 12.02%
Confusion matrix:
    0   1 class.error
0 225  26   0.1035857
1  33 207   0.1375000
> dm4.rf<-randomForest(Result~.,dm4,mtry=4)
> importance(dm4.rf)
      MeanDecreaseGini
FSP.1         4.478349
FSW.1         7.351250
SSP.1         4.360970
SSW.1         5.423985
ACE.1         7.214073
DBF.1         4.526553
WNR.1         6.557089
UFE.1         4.593019
BPC.1        40.453538
BPW.1        22.406822
NPA.1         4.107380
NPW.1         3.738320
FSP.2         4.269662
FSW.2        11.518477
SSP.2         4.000530
SSW.2         6.670631
ACE.2         7.327702
DBF.2         5.288526
WNR.2         7.633424
UFE.2         5.231134
BPC.2        44.655374
BPW.2        23.135701
NPA.2         5.003801
NPW.2         4.936788
> barplot(c(dm4.rf$importance),names.arg=c(rownames(dm4.rf$importance)))
> table(dw4$Result,predict(dm4.svm,newdata=dw4[,-1]))
   
      0   1
  0 210  17
  1  19 206

# 正答率92%

> table(dw4$Result,predict(dm4.rf,newdata=dw4[,-1]))
   
      0   1
  0 217  10
  1  26 199

# 正答率92%
# 実はSVMとランダムフォレストの正答率が全く同じになった

f:id:TJO:20150209134144p:plain


見た感じ、変数重要度にはそれほど大きな差はないようです。けれども、総獲得ポイント数を説明変数から削除した途端にランダムフォレストの精度が下がり、OOB errorは12%にも及び、女子のデータに適用した場合の正答率はSVMと同じになりました。


汎化が強めに出るSVMと、過学習気味になりやすいランダムフォレストとが同じようなパフォーマンスになるというのはちょっと興味深いのですが、これはまた別に検証が必要ということで。。。


追記2


ついでなので、SVM・ランダムフォレストのそれぞれでどの女子の試合の結果予測に失敗したかを調べてみました。

> res<-data.frame(names1=dw$Player1,names2=dw$Player2,actual=dw$Result,predicted1=predict(dm4.svm,newdata=dw4[,-1]),predicted2=predict(dm4.rf,newdata=dw4[,-1]))
> res[res$actual!=res$predicted1,]
                  names1               names2 actual predicted1 predicted2
8           Kiki Bertens         Ana Ivanovic      0          1          0
37        Sorana Cirstea      Marina Erakovic      0          1          0
39        Lesia Tsurenko    Varvara Lepchenko      0          1          0
45          Alize Cornet        Polona Hercog      0          1          1
58        Tadeja Majeric     Ajla Tomljanovic      0          1          0
69             Jie Zheng         Madison Keys      1          0          0
72          Lauren Davis        Julia Goerges      1          0          0
85  Carla Suarez Navarro    Galina Voskoboeva      1          0          0
88           Karin Knapp      Maria Sharapova      1          0          0
101                Na Li       Lucie Safarova      1          0          0
131        Tamira Paszek        Melanie Oudin      0          1          0
140        Jamie Hampton       Lucie Safarova      1          0          0
148      Flavia Pennetta     Kirsten Flipkens      0          1          0
151       Marion Bartoli      Olga Govortsova      1          0          0
204       Ashleigh Barty      Maria Kirilenko      0          1          1
210        Shelby Rogers Carla Suarez Navarro      0          1          0
248      Jelena Jankovic      Maria Sharapova      0          1          1
264           E.Bouchard         G.Voskoboeva      1          0          0
265          S.Arvidsson       M.Lucic-Baroni      0          1          0
272   M.Larcher De Brito              M.Oudin      1          0          0
304             K.Kanepi              T.Moore      1          0          0
327             C.Giorgi            S.Cirstea      1          0          0
330           S.Stephens           A.Petkovic      1          0          0
344         K.Date-Krumm            A.Cadantu      1          0          1
346             A.Cornet           F.Pennetta      0          1          1
359             L.Robson           M.Erakovic      1          0          0
363            P.Kvitova     C.Suarez Navarro      1          0          0
369           S.Williams            S.Lisicki      0          1          0
385              S Halep           F Pennetta      0          1          0
397            J Glushko         D Hantuchova      0          1          1
401          A.Radwanska     A Pavlyuchenkova      1          0          1
411             C McHale          E Svitolina      1          0          1
421           V Williams              J Zheng      0          1          0
424             L Robson             C Garcia      1          0          1
430              V Duval             S Stosur      1          0          0
443            M Minella           S Stephens      0          1          0
> res[res$actual!=res$predicted2,]
                  names1               names2 actual predicted1 predicted2
14   Alison Van Uytvanck     Virginie Razzano      0          0          1
21    Ekaterina Makarova       Venus Williams      1          1          0
25      Angelique Kerber    Jarmila Gajdosova      1          1          0
45          Alize Cornet        Polona Hercog      0          1          1
69             Jie Zheng         Madison Keys      1          0          0
72          Lauren Davis        Julia Goerges      1          0          0
85  Carla Suarez Navarro    Galina Voskoboeva      1          0          0
88           Karin Knapp      Maria Sharapova      1          0          0
101                Na Li       Lucie Safarova      1          0          0
140        Jamie Hampton       Lucie Safarova      1          0          0
151       Marion Bartoli      Olga Govortsova      1          0          0
154     Stefanie Voegele       Heather Watson      1          1          0
167        Nadia Petrova          Monica Puig      0          0          1
172    Urszula Radwanska       Venus Williams      1          1          0
177        Jana Cepelova     Christina McHale      1          1          0
203       Marion Bartoli Mariana Duque-Marino      1          1          0
204       Ashleigh Barty      Maria Kirilenko      0          1          1
205          Kaia Kanepi     Stefanie Voegele      1          1          0
248      Jelena Jankovic      Maria Sharapova      0          1          1
264           E.Bouchard         G.Voskoboeva      1          0          0
272   M.Larcher De Brito              M.Oudin      1          0          0
273           L.Hradecka              K.Knapp      0          0          1
285     S.Soler-Espinosa                M.Doi      1          1          0
304             K.Kanepi              T.Moore      1          0          0
305           M.Burdette          U.Radwanska      0          0          1
323     C.Suarez Navarro       M.Lucic-Baroni      1          1          0
327             C.Giorgi            S.Cirstea      1          0          0
328            M.Bartoli             C.McHale      1          1          0
330           S.Stephens           A.Petkovic      1          0          0
346             A.Cornet           F.Pennetta      0          1          1
359             L.Robson           M.Erakovic      1          0          0
363            P.Kvitova     C.Suarez Navarro      1          0          0
372          A.Radwanska                 N.Li      1          1          0
383           A Ivanovic           V Azarenka      0          0          1
397            J Glushko         D Hantuchova      0          1          1
430              V Duval             S Stosur      1          0          0


何となく傾向がありそうでなさそうな結果なんですが*4、これだけ見ていても仕方ないので誤判定されたサンプルだけを使ってランダムフォレストの分類モデルを推定し、その変数重要度を見てみます。

> idx<-which(res$actual!=res$predicted1|res$actual!=res$predicted2)
> idx
 [1]   8  14  21  25  37  39  45  58  69  72  85  88 101 131 140 148 151 154 167 172 177 203 204 205 210 248 264 265 272 273 285 304 305
[34] 323 327 328 330 344 346 359 363 369 372 383 385 397 401 411 421 424 430 443
> tuneRF(dw4[idx,-1],dw4[idx,1],doBest=T)
mtry = 4  OOB error = 34.62% 
Searching left ...
mtry = 2 	OOB error = 26.92% 
0.2222222 0.05 
mtry = 1 	OOB error = 44.23% 
-0.6428571 0.05 
Searching right ...
mtry = 8 	OOB error = 40.38% 
-0.5 0.05 

Call:
 randomForest(x = x, y = y, mtry = res[which.min(res[, 2]), 1]) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 2

        OOB estimate of  error rate: 32.69%
Confusion matrix:
   0  1 class.error
0 11 11         0.5
1  6 24         0.2
> dw4_false.rf<-randomForest(Result~.,dw4[idx,],mtry=2)
> dw4_false.rf

Call:
 randomForest(formula = Result ~ ., data = dw4[idx, ], mtry = 2) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 2

        OOB estimate of  error rate: 34.62%
Confusion matrix:
   0  1 class.error
0 10 12   0.5454545
1  6 24   0.2000000
> importance(dw4_false.rf)
      MeanDecreaseGini
FSP.1        1.0430330
FSW.1        2.3864147
SSP.1        0.9143725
SSW.1        0.8364949
ACE.1        0.6901433
DBF.1        0.9256909
WNR.1        1.1882832
UFE.1        1.1402268
BPC.1        1.0722627
BPW.1        0.9630243
NPA.1        1.0757179
NPW.1        0.9913457
FSP.2        1.0406653
FSW.2        0.9847548
SSP.2        0.9199134
SSW.2        0.8876915
ACE.2        0.7315410
DBF.2        0.6752135
WNR.2        1.3790025
UFE.2        0.9585957
BPC.2        0.9941429
BPW.2        1.1534847
NPA.2        0.9699186
NPW.2        0.9360918
> barplot(c(dw4_false.rf$importance),names.arg=c(rownames(dw4_false.rf$importance)))

f:id:TJO:20150209134403p:plain


実際にはOOB errorが非常に大きい(33%を超える)ので必ずしも適切な評価とは言えないのですが、この誤判定サンプルから推定し直したモデルの変数重要度を見るとBPC.1/2の重要度が大幅に下がり、代わりにFSW.1が図抜けて大きくなっています。このサンプル群に限っては、とにかくPlayer 1の1stサーブ獲得ポイントがカギだったということのようです。

*1:例えば女子は3セットマッチなので男子にはある第4・5セットのスコア欄がない

*2:ただし総ポイント数は逆転することもあるので要注意

*3:他にもrandomForestは32個以上のfactor型変数を説明変数に使えないという事情もある

*4:あと僕自身が最近WTAツアー全っ然見てないので誰がどんなプレーしてるかピンと来ないというのもある