バスケやデータの雑記帳

バスケやデータ分析について徒然なるままに

FIBA Basketball World Cup 2023をデータ分析してみた(Part.2|決定木分析)

本記事は、全5回に分けてFIBAバスケットボールワールドカップ2023の全92試合を分析してみたシリーズ第2回目です。

  1. 相関分析・可視化分析|勝敗と相関が高いスタッツの項目は何か?勝利したチームと敗北したチームのスタッツの差はどう違うのか?
  2. 決定木分析|勝利するチームのスタッツの条件は?(その1)
  3. LightGBM+SHAP分析|勝利するチームのスタッツの条件は?(その2)
  4. 次元削減分析(主成分分析・t-SNE・UMAP)|スタッツから見る大会参加チームの特徴は?(その1)
  5. 因子分析+クラスタリング|スタッツから見る大会参加チームの特徴は?(その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)といったごく当たり前のことを出来たチームが勝っているということを、更に具体的な定量指標で見ることが出来ました。