FIBA Basketball World Cup 2023をデータ分析してみた(Part.2|決定木分析)
本記事は、全5回に分けてFIBAバスケットボールワールドカップ2023の全92試合を分析してみたシリーズ第2回目です。
- 相関分析・可視化分析|勝敗と相関が高いスタッツの項目は何か?勝利したチームと敗北したチームのスタッツの差はどう違うのか?
- 決定木分析|勝利するチームのスタッツの条件は?(その1)
- LightGBM+SHAP分析|勝利するチームのスタッツの条件は?(その2)
- 次元削減分析(主成分分析・t-SNE・UMAP)|スタッツから見る大会参加チームの特徴は?(その1)
- 因子分析+クラスタリング|スタッツから見る大会参加チームの特徴は?(その2)
前回は、シンプルな相関分析と可視化分析を行いました bballdatanote.hatenablog.com
今回は決定木分析を行い、勝利する際のスタッツの条件分岐点を探っていきます。
目次は次の通りです。
データの準備・前処理
前回同様、大会公式サイトのデータを用いて、以下のように試合別・チーム別のデータに集約していきます。また、追加スタッツとしてFour FactorsとField Goal Percentageも出しておきます。
team_df.head()
GAME_KEY | TEAM | GAME_POSITION | RESULT | OREB | DREB | REB | AST | PF | TO | ST | BLK | FG_MADE | FG_ATTEMPT | 2PTS_MADE | 2PTS_ATTEMPT | 3PTS_MADE | 3PTS_ATTEMPT | FT_MADE | FT_ATTEMPT | eFG% | TO% | FTR | DREB_OPPOSITE | OREB% | 2PTS% | 3PTS% | FT% | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 試合日1_ドイツ_日本 | 日本 | right | LOSE | 6 | 24 | 30 | 17 | 16 | 12 | 5 | 3 | 23 | 65 | 17 | 30 | 6 | 35 | 11 | 17 | 0.400000 | 0.142045 | 0.261538 | 36 | 0.142857 | 0.566667 | 0.171429 | 0.647059 |
1 | 試合日1_南スーダン_プエルトリコ | 南スーダン | left | LOSE | 8 | 27 | 35 | 23 | 23 | 19 | 9 | 5 | 34 | 67 | 24 | 41 | 10 | 26 | 18 | 24 | 0.582090 | 0.196769 | 0.358209 | 26 | 0.235294 | 0.585366 | 0.384615 | 0.750000 |
2 | 試合日1_スペイン_コートジボワール | スペイン | left | WIN | 14 | 28 | 42 | 29 | 14 | 17 | 7 | 5 | 34 | 64 | 23 | 35 | 11 | 29 | 15 | 22 | 0.617188 | 0.187472 | 0.343750 | 16 | 0.466667 | 0.657143 | 0.379310 | 0.681818 |
3 | 試合日2_オーストラリア_ドイツ | ドイツ | right | WIN | 5 | 20 | 25 | 18 | 19 | 12 | 9 | 3 | 31 | 61 | 20 | 31 | 11 | 30 | 12 | 15 | 0.598361 | 0.150754 | 0.245902 | 22 | 0.185185 | 0.645161 | 0.366667 | 0.800000 |
4 | 試合日2_レバノン_カナダ | レバノン | left | LOSE | 6 | 10 | 16 | 19 | 18 | 22 | 12 | 1 | 30 | 62 | 22 | 43 | 8 | 19 | 5 | 5 | 0.548387 | 0.255220 | 0.080645 | 24 | 0.200000 | 0.511628 | 0.421053 | 1.000000 |
決定木分析
それでは決定木分析をしていきます。勝敗(RESULT
)がそのままではWINとLOSEで相関係数を計算できないので、WINの場合は1、LOSEの場合は0としておきます。
df = team_df.clone().with_columns( RESULT_INT = pl.col("RESULT").map_dict({"WIN": 1, "LOSE": 0}) )
次に、勝敗と比較するスタッツを前回同様に指定しておきます。
feature_cols = [ 'OREB', # オフェンスリバウンド 'DREB', # ディフェンスリバウンド 'AST', # アシスト 'PF', # ファール 'TO', # ターンオーバー 'ST', # スティール 'BLK', # ブロック '2PTS_ATTEMPT', # 2ポイントの試投数 '3PTS_ATTEMPT', # 3ポイントの試投数 'FT_ATTEMPT', # フリースローの試投数 'eFG%', # efficient Field Goal Percentage(Four Factors) 'TO%', # Turn Over Percentage (Four Factors) 'FTR', # Free Throw Rate (Four Factors) 'OREB%', # Offensive Rebound Percentage (Four Factors) '2PTS%', # 2ポイント成功率 '3PTS%', # 3ポイント成功率 'FT%', # フリースロー成功率 ]
汎化性能の評価
決定木自体の汎化性能を見るために、TrainデータとTestデータに分けておきます。試合(GAME_KEY
)レベルでTrainかTestに分けたいので、通常利用するtrain_test_split
ではなく、GroupShuffleSplit
を利用します。
from sklearn.model_selection import GroupShuffleSplit # Train/Testに分ける df = df.to_pandas() splitter = GroupShuffleSplit(test_size=0.3, random_state=0) split = splitter.split(df, groups=df["GAME_KEY"]) train_inds, test_inds = next(split) train_df = df.iloc[train_inds] test_df = df.iloc[test_inds] # 特徴量Xと目的変数yに分ける train_X = train_df[feature_cols] train_y = train_df["RESULT_INT"] test_X = test_df[feature_cols] test_y = test_df["RESULT_INT"]
次に、決定木を学習させ、汎化性能を見ておきます。どれくらいの確率で勝敗を分類できる決定木なのかを確認するためです。
可視化の際に、あまり決定木が深すぎても解釈しきれないので、決定木の深さを示すパラメータであるmax_depth
は5にしておきます。
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, confusion_matrix from sklearn.tree import DecisionTreeClassifier # 2値分類のモデルの評価用関数 def show_binary_classification_score(cls, x, y): pred = cls.predict(x) pred_prob = cls.predict_proba(x)[:, 1] # 各種精度指標を計算 acc = accuracy_score(y, pred) precision = precision_score(y, pred) recall = recall_score(y, pred) f1 = f1_score(y, pred) auc = roc_auc_score(y, pred) conf_mat = confusion_matrix(y, pred) conf_mat = pd.DataFrame(conf_mat, index=["LOSE", "WIN"], columns=["LOSE", "WIN"]) # 各種精度指標を表示 print(f"Accuracy:\t{acc}") print(f"Precision:\t{precision}") print(f"Recall:\t{recall}") print(f"F1:\t{f1}") print(f"AUC:\t{auc}") print(f"混同行列:\n{conf_mat}") # Trainデータで学習 dt = DecisionTreeClassifier(max_depth=5, random_state=0) dt = dt.fit(train_X, train_y) # Testデータで評価 show_binary_classification_score(dt, test_X, test_y)
# 出力 Accuracy: 0.7321428571428571 Precision: 0.7096774193548387 Recall: 0.7857142857142857 F1: 0.7457627118644068 AUC: 0.7321428571428572 混同行列: LOSE WIN LOSE 19 9 WIN 6 22
ここでは各指標の詳細な説明は避けますが、概ね7割強程度の精度で正確に分類できていそうです。
決定木の可視化
それでは、実際に決定木を可視化していきます。可視化する際には、Train/Testの全てのデータを使っていきます。
可視化にあたっては、sklearnのplot_tree
もよく使われていますが、今回はより綺麗に可視化ができるdtreeviz
を使っていきます。
import dtreeviz # 学習 X = df[feature_cols] y = df["RESULT_INT"] dt.fit(X, y) # 可視化 viz_model = dtreeviz.model( dt, X, y, target_name="RESULT_INT", feature_names=feature_cols, class_names=["LOSE", "WIN"] ) v = viz_model.view() # render as SVG into internal object v
eFG%が58.4%以上かどうかで最初の分岐が入っています。58.4%以下の場合、DREBが24.5本以下になると一気にLOSE、敗北の割合が高くなっていることがわかります。 逆にeFG%が58.4%を上回っている場合、DREBが20.5本よりも多く取れていたらほぼほぼ勝利、さらに3PTS_ATTEMPTが20を上回ったチームは全チーム勝利していることがわかります。
前回の相関分析・可視化分析でも見て取れましたが、効率的にシュートを成功させることができ、DREBをしっかり抑えて相手のオフェンス回数を減らすことが重要であると言えそうです。
番外編|Four Factorsだけで見た場合どうなるか?
先程はできる限りのスタッツ情報を含めて分析しましたが、今度はFour Factorsだけで見た場合どうなるのかを試してみます。今回は4種類のスタッツしか含めないので、max_depth
は3にしておきます。
汎化性能の確認
# Xとyに分ける FOUR_FACTORS_COLS = [ "eFG%", # effecient Field Goal% "OREB%", # Offensive Rebound% "FTR", # Free Throw Rate "TO%" # Turn Over% ] train_X = train_df[FOUR_FACTORS_COLS] train_y = train_df["RESULT_INT"] test_X = test_df[FOUR_FACTORS_COLS] test_y = test_df["RESULT_INT"] # 学習 dt = DecisionTreeClassifier(max_depth=3,random_state=0) dt = dt.fit(train_X, train_y) # 評価 show_binary_classification_score(dt, test_X, test_y)
Accuracy: 0.7142857142857143 Precision: 0.6764705882352942 Recall: 0.8214285714285714 F1: 0.7419354838709677 AUC: 0.7142857142857143 混同行列: LOSE WIN LOSE 17 11 WIN 5 23
Four Factorsだけで学習しても、7割強の精度で分類できていそうです。Recallに至っては8割強と、指標によってはスタッツを絞る前よりも良くなっています。
決定木の可視化
それでは決定木を可視化していきます。
X = df[FOUR_FACTORS_COLS] y = df["RESULT_INT"] dt.fit(X, y) viz_model = dtreeviz.model( dt, X, y, target_name="RESULT_INT", feature_names=FOUR_FACTORS_COLS, class_names=["LOSE", "WIN"] ) v = viz_model.view() # render as SVG into internal object v
やはり、eFG%は58.4%というのが最初の条件になってきています。また、Four Factorsだけで判断する場合はeFG%とOREB%でほぼほぼ勝敗を分類出来てしまっているようです。逆に、FTRが出てこないのは少し意外な結果でした。
もちろん、これは他のスタッツを考慮しない場合の結果ですし、あくまでFIBAバスケットボールワールドカップ2023という大会だけを見た場合なので、必ずしもFTRが重要ではないとは言い切れないことには注意が必要です。
まとめ
今回は、機械学習アルゴリズムである決定木を用いた分析で、勝敗チーム間のスタッツの違いを見てきました。シュートを効率よく決める(eFG% > 58.4%)・リバウンドを制する(DREB > 20.5)といったごく当たり前のことを出来たチームが勝っているということを、更に具体的な定量指標で見ることが出来ました。