2017.06.26 Mon |

Kobe Bryant選手のShotデータ解析その2

basketballそれでは前回(http://ritsuan.com/blog/6684/)に引き続き、Kobe Bryant選手のShotデータ解析をやっていきましょう。

前回、shot_made_flag変数のNAの削除とshot_made_flag変数のファクター化を行いました。今回はその続きからやっていきます。
まずはデータの構造を見ていきましょう。
str(d)
str(d)さて、いろいろ各変数の内容を検討した結果、以下のような前処理を行うことになりました。この決定に至るまでを書くと長くなるので今回は省略して、前処理に移ります。
#変数のファクター化
for(i in c(3,4,10,11,20)){d[,i]=as.factor(d[,i])}
#結果確認
str(d)
str(d)2さて、次に、今回使わない、lat,lon,team_name,team_id,shot_id変数を削除します。
library(dplyr)
t=d%>%select(-lat,-lon,-team_name,-team_id,-shot_id)
str(t)
str(t)1
これで準備は整いました。
これからすぐに機械学習をやってしまっても良いのですが、今回は予測モデルが構築したいというよりも、どの変数がどんな値の時にShotが入りやすいか入りにくいかが知りたいので、可視化によりそれを見ていきます。
library(GGally)
library(ggplot2)
#データを10分の1にサンプリング
set.seed(123)
ind=sample(nrow(t),floor(nrow(t)*0.1))
ggparcoord(data =t[ind,], columns = c(1:20),groupColumn =13,scale = “uniminmax”,alpha=0.3,showPoints = T)+coord_flip()
ggparcooed2shot_distanceまたはloc_yがある一定以上より大きいと、Shotが外れる事がわかります。前回loc_xとloc_yを可視化したときにゴールから離れてさらに3pointのライン(外の半円)から離れると、Shotが決まらなくなることはわかっていました。まあ常識的に考えて、3pointのラインから外側に行ったらゴールがきまりにくくなるのは当たり前ですね。

しかし残念ながら、今回の可視化からは、この常識以上の知識は得られませんでした。個人的な経験から言わせていただくと、今回のデータには、Kobe Bryant選手のShotが決まるかどうかを決める要因は入っていないと思います。アンサンブル学習やDeepLearningを使ったところで、精度の良い学習器は作成できないと思います。
もしも、Shotが決まるか決まらないかの予測をしたければ、遺伝子情報だったり、脳の活動情報だったり、どこを見ているかのeye tracking情報などを取得する必要があるでしょう。

ただ、今回のデータセットの変数の中に、Shotが入るか入らないかに影響を与える変数がないとは、現在の段階では完全にだんていできるわけでないので、より詳細な可視化を行っていきましょう。
ggpairs(t,c(5:9,13),mapping = ggplot2::aes(color = shot_made_flag), upper = list(continuous = wrap(“cor”, size=2)),lower = list(continuous = wrap(“points”, alpha = 0.3), combo = wrap(“dot”, alpha = 0.3)), diag = list(continuous =”barDiag”))
ggpairs5~9列目には、Shotが入るか入らないかに影響を与える重要な変数はないことが分かります(慣れが必要ですが)。細かく言えばいくつかあるのですが些細なことなので無視します。
一応11行目から17行目までも散布図行列を作成してみますが、結論から言うと今まで以上の新しいことは何も言えません。
ggpairs(t,c(11:17),mapping = ggplot2::aes(color = shot_made_flag), upper = list(continuous = wrap(“cor”, size=2)),lower = list(continuous = wrap(“points”, alpha = 0.3), combo = wrap(“dot”, alpha = 0.3)), diag = list(continuous =”barDiag”))
ggpairs2
次に、今まで使ってこなかった、時間情報のgame_date変数を使ってShotの本数を可視化していきます。
#可視化用にデータを整形
library(dplyr)
t1=t %>% group_by(game_date)%>%summarize(TotalShot=length(shot_made_flag),IN=length(shot_made_flag==1),OUT=length(shot_made_flag==0))
#geom_line関数による可視化
ggplot(data=t1)+
geom_line(aes(x=game_date,y=TotalShot),col=”black”)+
geom_line(aes(x=game_date,y=IN),col=”blue”)+
geom_line(aes(x=game_date,y=OUT),col=”red”)
geom_pointShotの総数(黒)は、徐々に増加していき、2002年以降は安定しています。Shotが失敗した(赤)数も、Shotが成功した(黒)数も、2002年以降安定しているのが分かります。
2014年にほとんど試合に出ていないのは、2014年前半にアキレス腱を損傷し、2014年12月に8か月ぶりに試合に復帰するものの、6試合目で相手との接触により右ひざを負傷し、その後回復が思わしくなかったためですね。2015年も試合数が少ないのは、それが理由でしょう。

それでは、時系列の解析をもう一つしていきます。
各シーズンごとの、バスケットリングとShot場所の距離の平均を算出して可視化します。
library(ggplot2)
t%>%group_by(season)%>%summarize(shot_distance_mean=mean(shot_distance))%>%ggplot()+geom_point(aes(x=season,y=shot_distance_mean))
shot_distance年月が経って行くにつれ、Shotする際のゴールリングからの距離が長くなっているのがわかります。

それでは、Shotが入る確率の変化はどうなっているのでしょうか?
success_rate2013年以降は、Shotの成功確率が落ち込んでいるのが分かります。特にケガをした2014年と2015年ではShot10本中3.75本以下しか入らない状況となっています。といっても業界的にはおそらく高い気がします。
それではシーズンごとのShotの種類の変化はどうなっているのでしょうか?まずは、各シーズンにおいて、Shotの成功(1)、失敗(0)ごとに、棒グラフを作成し、Shotの種類で色付けしていきます。
ggplot(data=t)+geom_bar(aes(x=shot_made_flag,fill=combined_shot_type))+facet_wrap(~season)
geom_bar_shot_kind次に各Shotの割合をよりよく見るために、各棒グラフを引き延ばしてみます。
ggplot(data=t)+geom_bar(aes(x=shot_made_flag,fill=combined_shot_type),position=”fill”)+facet_wrap(~season)
geom_bar_shot_kind_fillDunk Shotは成功確率が高いことが分かります。
また年を取るにつれて、Dunk Shotを行わなくなっているのがわかります。
そして、Jump Shotの割合が増えています。また、Layup Shotの成功確率も基本的には(大きなけがをする2013年まで)上昇傾向にあります。

ついでに、年を取るごとに、どこからShotを打つようになっているかも見てみましょう。
ggplot(data=t)+geom_bar(aes(x=shot_made_flag,fill=shot_zone_area),position=”fill”)+facet_wrap(~season)
geom_bar_shot_place基本的に、CenterからのShotが多いですが、2002年-2003年、2005年-2006年、2014年-2015年、2015-2016年は、例外的にCenterからのShotが比較的少なく、代わりに、Left Side CenterとRight Side CenterからのShot数が増えています。調子が悪いと、CenterからのShotが減り、Left Side CenterとRight Side CenterからのShot数が増えるという傾向があるのかもしれませんね。

一応一通りデータを見終わったので、このデータ提供者の希望通り、Shotが入るか入らないかの予測モデルを構築して終わりにしたいと思います。
まず今回カテゴリー数が53以上で多くて使えない変数を削除するところから始めます。削除するのは、action_type、game_event_id、game_id、game_date、matchupの5変数です。
#5変数の削除
t=t%>%select(-action_type,-game_event_id,-game_id,-game_date,-matchup)
str(t)
str(t)3もともと20変数あったものが5変数削除され15変数となりました。

次に学習用データとテスト用データへ分割します。
#sample関数用の乱数の種をまく
set.seed(123)
#整数1~nrow(t)から80%の整数を取り出す。
ind=sample(nrow(t),floor(nrow(t)*0.8))
#学習用(train)とテスト用(test)にデータを分割する。
train=t[ind,]
test =t[-ind,]
#データ数の確認
dim(train)
dim(test)
datanum
それでは次に、randomForest関数を用いて、ランダムフォレストをしていきましょう。
#ランダムフォレストの実行
library(randomForest)
rf_result=randomForest(shot_made_flag~.,data=train,importance=T, mtry=6)
#結果
rf_result
rf_result
では、このモデルでどれくらい、testデータを予想できるか試してみましょう。
#予測
pred=predict(rf_result,test[,c(1:9,11:15)])
head(pred)
#混合行列
a=table(pred,test[,10])
a
#Accuracy
(a[1,1]+a[2,2])/(a[1,1]+a[1,2]+a[2,1]+a[2,2])
prediction2
混合行列を見てみるとわかりますが、
実際に入ったShot、2294本のうち、220本しか正しく予測できていません。この予測器の精度はかなり悪いといえるでしょう。
一応Accuracyを計算してみると、0.590856となっていますが、ランダムな予測でも0.5程度になるはずなので、あまり精度はよくないです。

一応説明変数の重要度を見てみましょう。
varImpPlot(rf_result)
varImpshot_distanceや、loc_yはゴールからの距離なので、ゴールからの距離が遠いほどShotがはずれやすいということで、この二つの変数が重要というのはわかります。combined_shot_typeに関しても、Dunk Shotは成功確率が高い事から重要な変数と判断されたのでしょう。
では、opponentとseconds_remaining変数に関しては、なぜ重要と判断されたのでしょうか?
まずopponent変数とshot_made_flag変数の関係を見ていきます。
ggplot(data=t)+geom_bar(aes(x=opponent,fill=shot_made_flag),position=”fill”)+coord_flip()
opponent相手がどのチームだとShotが入りやすい、入りにくいというのは、多少あるみたいですね。チームごとの違いは最大5%あるかないかですが。

次に、seconds_remaining変数と、shot_made_flag変数の関係を見てみましょう。
ggplot(data=t)+geom_boxplot(aes(x=shot_made_flag,y=seconds_remaining))+geom_violin(aes(x=shot_made_flag,y=seconds_remaining),alpha=0.2)
boxplot箱ひげ図とバイオリンプロットを同時に出力して、データの分布と統計量を同時にわかるようにしました。
残り時間が少ない方が、焦って不正確なShotを打つせいか、成功確率が少し低いようですね。また、Shotが成功しているグループが、残り時間が多い(四分位点と中央値が大きい)ことがわかります。
これがseconds_remaining変数が、重要な変数とみなされた理由のようです。

以上いろいろ見てきましたが、精度の良い予測モデルはできなかったものの、Kobe Bryant選手の歴史と特性がわかるおもしろいデータセットでした。

今回はここで探索的データ解析を終わりにします。

鈴木瑞人
東京大学大学院新領域創成科学研究科 メディカル情報生命専攻 博士課程
東京大学機械学習勉強会 代表
NPO法人Bizjapan

2024.9  
未経験者必見!『キャリアリターン採用』開始しました  詳しく