見出し画像

Bリーグのスタッツを使ってレーティングを計算する / Four Factors

本日初めてWINNERを購入し、試合観戦する経験をしました。第4クォーターの観戦方法が変わったと感じます。終盤でリードされているチームがファールゲームを始めると、点差をこれ以上広げないでほしいと願いながらフリースローを見守ったのは、これまでにない新鮮な体験でした。

もちろん当たればさらに素晴らしいのですが、今回私が購入した6口は全て外れました。また、今回は3試合を予想しましたが1試合で十分と感じました。バスケットボールは短時間で結果が分からないので、試合を最後まで楽しむための手段として考えた方が良いと思います。

1.計算したスタッツの使い方を分かっていない

前回、オフェンシブ・レーティング(ORtg)とディフェンシブ・レーティング(DRtg)をPythonのプログラムで計算しました。数多くの指標を算出しましたが、それぞれが何を意味するのか理解していない状態です。そこで、もう少しデータの関係を学んでみようと思い、Amazonで以下の書籍を購入しました。

Amazonの紹介文を見ますと、「アドバンススタッツから読み取れること」、「データの体系化とBリーグにおける分析の実際」といった知りたいことが書かれていそうだったからです。

240ページくらいの本ですが、文字が大きい上に知りたい内容は90ページくらいでしたので数時間で読めてしまいました。何を見るための指標なのか、各指標の勝敗との相関関係、試合の分解度の高低が書かれており非常に整理されました。またこれらの実際に使って分析した事例の紹介など、いま知りたいことがまとめられていたので満足度は高かったです。

後日、今回一番参考にした箇所とほぼ同じ内容が紹介されているページがありましたのでリンクを紹介します。

この本によると、いままで紹介してきたオフェンシブ・レーティング(ORtg)とディフェンシブ・レーティング(DRtg)はチームを評価する手法であり、勝敗を予想する指標ではないということです。今回のWINNERの結果を見ても、何となく感じていることでしたので、それに代わる指標を出していきたいと思います。

2.指標同士を関連付ける

数多くの指標を計算しても、その関係を知らないと有効に使うことはできません。まずはバスケットボールが得点を重ねながら多い方が勝利するというゲームであることから「どれだけ多くの点数を獲得するか」を最終目的として指標を算出するとこの本には書かれています。

2-1. PTS

得点はPTSです。PTSはPPP「得点の効率」とPOSS「攻撃回数」を掛けた値です。

PTS = PPP * POSS

2-2. PPP

攻撃の質を表す指標がPoint Per PossetionsでPPPと呼ばれています。チームがポゼッションごとにどれだけ得点しているかを示す効率指標です。

高い値の場合: チームがポゼッションごとに多くの得点を獲得していることを示しています。これは一般に効率的なオフェンスを意味し、チームの得点力が高いことを示唆しています。
低い値の場合: チームがポゼッションごとに少ない得点しか獲得していないことを意味します。これはオフェンスの効率が低い、またはディフェンスに苦しんでいることを示唆している可能性があります。

PPP = PTS / (FGA + FTA * 0.44 + TOV)

def calculate_ppp(PTS, FGA, FTA, TO):
    possessions = FGA + 0.44 * FTA + TO
    ppp = PTS / possessions
    return ppp

2-3. POSS

攻撃の回数を表す指標がPossetionでPOSSと呼ばれています。ポゼッションは、チームがボールを持って得点を試みることができる機会を指します。つまりチームがゲーム中にどれだけの攻撃機会を持つかを推定します。

高い値の意味: 試合が高速でプレイされていること、またはチームが多くの攻撃機会を持っていることを示します。これは通常、スコアが高くなる傾向があります。
低い値の意味: 試合が低速でプレイされていること、またはチームが少ない攻撃機会を持っていることを示します。これは通常、スコアが低くなる傾向があります。

POSS = FGA + FTA * 0.44 + TOV

def calculate_poss(FGA, FTA, TOV):
    possessions = FGA + 0.44 * FTA + TOV
    return possessions

2-4. eFG%

eFG%はプレイヤーまたはチームのシューティング効率を測るための指標です。特に、3ポイントシュートの追加価値を考慮して、通常のフィールドゴール成功率を調整します。

eFG%、PPP、POSSの関係
eFG%が高いと、PPPも高くなります。eFG%が高いということは、プレイヤーやチームがフィールドゴールを効率的に決めていることを意味し、ポゼッションあたりの得点が多くなるからです。POSSは攻撃機会の数を示しますが、これ自体は効率とは直接関係しません。高いeFG%を持つチームは、与えられた多くのポゼッション(POSS)を利用して、より多くの得点を生み出します。

eFG%が高い場合:高いeFG%は、プレイヤーまたはチームがシュートを効率的に決めていることを示し、高いPPP(効率的な得点)につながります。高いeFG%と多くのPOSSは、多くの得点につながる可能性があります。
eFG%が低い場合:低いeFG%は、シュートの効率が低いことを意味し、低いPPP(低い得点効率)につながります。たとえPOSSが多くても、低いeFG%は得点の機会を十分に活かせていないことを示します。

eFG%とPPP、POSSは、オフェンスの効率と効果を評価するために相互に関連する重要な指標です。これらを組み合わせることで、チームの攻撃戦略の理解を深めることができます。

eFG% = (FGM + 0.5 * 3PM) / FGA * 100

def calculate_efg_percent(FGM, TFGM, FGA):   
    efg_percent = (FGM + 0.5 * TFGM) / FGA * 100
    return efg_percent

2-5. OR%

取得したオフェンシブリバウンドの割合を示す指標がOffensive Rebound PercentageでOR%と呼ばれています。

OR%、PPP、POSSの関係
高いOR%は攻撃側がリバウンドを取り、追加のポゼッション(POSS)を獲得することを意味します。これにより追加の得点機会を生み出し、結果的としてPPPを高めることができるからです。

PPPとPOSSは直接OR%に影響しませんが、追加のポゼッションが得ることで、より多くの得点機会(POSS)と効率的な得点(PPP)が期待できます。

OR%が高い場合:チームはオフェンスリバウンドを多く取得し、追加の攻撃機会を得ています。これは、ゲーム中により多くの得点機会(POSS)を持つことにつながり、結果として得点(PTS)やPPPを向上させる可能性があります。
OR%が低い場合:チームはオフェンスリバウンドをあまり取得できていないため、追加の攻撃機会が少なくなります。これは攻撃機会(POSS)の損失を意味し、PPPにも影響を与える可能性があります。

OR%はオフェンスリバウンドを通じて追加の攻撃機会を獲得する能力を示し、これはゲーム中の得点機会(POSS)の増加とオフェンスの効率(PPP)に間接的に影響を及ぼす重要な指標です。

OR% = OR / (OR + OpponentDR) * 100

def calculate_or_percentage(OR, OpponentDR):
    or_percentage = (OR / (OR + OpponentDR)) * 100
    return or_percentage

2-6. TO%

「TO%」(Turnover Percentage、ターンオーバー率)は、プレイヤーやチームがポゼッションを失う割合を示します。この指標は、攻撃中に失われたボール(ターンオーバー)が全攻撃機会(ポゼッション)に占める割合を測定します。

TO%が高い場合:ターンオーバー率が高い場合、得点機会が失われていることを意味します。多くのポゼッションが得点につながらず終わっているため、PPPは低くなります。
TO%が低い場合:ターンオーバー率が低い場合、攻撃機会をより効率的に利用しているlことを意味します。これはPPPが高くなることを意味し、攻撃の効果を高めることができます。

TO%は、チームの攻撃効率と直接関連しており、低いTO%は高いPPPと関連しています。したがって、TO%の低下は通常、攻撃効率の向上に寄与します。

TO% = TO / (FGA + FTA * 0.44 + TO) * 100

def calculate_to_percentage(TO, FGA, FTA):
    to_percentage = (TO / (FGA + 0.44 * FTA + TO)) * 100
    return to_percentage

2-7. FTR

FTRはチームがどれだけ効率的にフリースローラインに獲得しているかを測るものです。

FTRが高い場合:高いFTRは、フリースローからの得点が多いことを意味します。特に接触の多いプレイや積極的な攻撃スタイルにより、フリースロー機会が増えることでPPPが向上します。

FTRが低い場合:低いFTRは、フリースローによる追加得点の機会が少ないことを意味します。これはチームがフィールドゴールに頼っていることを示し、特にフィールドゴールの効率が低い場合、PPPの低下につながります。

FTRは、フリースローが得点に大きく寄与するケースでPPPやPOSSに影響を与える重要な指標です。フリースローによる得点機会が多いチームは、より高いPPPを達成する傾向にあります。

FTR = FTA / FGA

このように多くの点数を獲得するには得点の効率と攻撃回数が必要となります。得点の効率を上げるにはショット効率を上げ、フリースローの機会をより多く得る必要があります。攻撃回数を上げるためにはターンオーバー率を下げ、オフェンシブ・リバウンド率を上げる必要があります。

3.選手スタッツから指標を求める

前回までのORtg、DRtgを求めるプログラムを大幅に変更し、PTSの算出を最終目的とするアドバンスド・スタッツを計算するように変更しました。

from tkinter import *
from tkinter import ttk
import sqlite3
import pandas as pd
import math

def calculate_ppp(PTS, FGA, FTA, TO):
    possessions = FGA + 0.44 * FTA + TO
    ppp = PTS / possessions
    return ppp

def calculate_poss(FGA, FTA, TOV):
    possessions = FGA + 0.44 * FTA + TOV
    return possessions

def calculate_efg_percent(FGM, TFGM, FGA):
    efg_percent = (FGM + 0.5 * TFGM) / FGA * 100
    return efg_percent

def calculate_or_percent(OR, OpponentDR):
    or_percent = (OR / (OR + OpponentDR)) * 100
    return or_percent

def calculate_to_percent(TO, FGA, FTA):
    to_percent = (TO / (FGA + 0.44 * FTA + TO)) * 100
    return to_percent

def collect_data(tgt_team, sum_rtg, conn):
    # 各チームの統計情報を格納するための辞書
    team_stats = {team: {} for team in tgt_team}

    # 両チームのデータを一度のクエリで取得
    query = f"""SELECT team, g, SUM(minute) as minute, SUM(pts) as pts,
            SUM(fgm) as fgm, SUM(fga) as fga, SUM(tfgm) as tfgm, SUM(ftm) as ftm,
            SUM(fta) as fta, SUM(orb) as orb, SUM(drb) as drb, SUM(asst) as asst,
            SUM(tov) as tov, SUM(st) as st, SUM(bs) as bs, SUM(f) as f
            FROM player_stats WHERE team IN (?, ?) GROUP BY team"""
    df = pd.read_sql(query, conn, params=tgt_team)

    # データフレームからチームごとの統計情報を抽出
    for index, row in df.iterrows():
        team_stats[row['team']] = row.to_dict()

    # 各種計算処理
    for team in tgt_team:
        stats = team_stats[team]
        opponent = tgt_team[0] if team != tgt_team[0] else tgt_team[1]
        opp_stats = team_stats[opponent]

        ppp = calculate_ppp(stats['pts'], stats['fga'], stats['fta'], stats['tov'])
        tposs = calculate_poss(stats['fga'], stats['fta'], stats['tov'])
        games = stats['minute'] / 5
        tposs_pg = tposs / stats['g']
        pts = ppp * tposs_pg
        efg_percent = calculate_efg_percent(stats['fgm'], stats['tfgm'], stats['fga'])
        orb_percent = calculate_or_percent(stats['orb'], opp_stats['drb'])
        tov_percent = calculate_to_percent(stats['tov'], stats['fga'], stats['fta'])
        ftr = stats['fta'] / stats['fga']
        ORtg = stats['pts'] / tposs * 100
        DRtg = opp_stats['pts'] / tposs * 100

        # 結果をsum_rtgリストに追加
        sum_rtg += [team, pts, ppp, tposs_pg, efg_percent, orb_percent, tov_percent, ftr, ORtg, DRtg]

    return sum_rtg

def calculate_rating(rating):
    team1, team2 = rating[0], rating[10]
    stats = [
        ('PTS', math.floor(rating[1]), math.floor(rating[11])),
        ('PPP', round(rating[2], 3), round(rating[12], 3)),
        ('POSS', round(rating[3], 2), round(rating[13], 2)),
        ('eFG%', round(rating[4], 2), round(rating[14], 2)),
        ('OR%', round(rating[5], 2), round(rating[15], 2)),
        ('TO%', round(rating[6], 2), round(rating[16], 2)),
        ('FTR', round(rating[7], 2), round(rating[17], 2)),
        ('ORtg', round(rating[8], 2), round(rating[18], 2)),
        ('DRtg', round(rating[9], 2), round(rating[19], 2))
    ]

    for stat_name, team1_stat, team2_stat in stats:
        print(f'{team1}{stat_name}{team1_stat}{team2}{stat_name}{team2_stat}です。')
    print('--------------------------------')

def show_selected(event):
    selected_team = combobox_t.get()
    opponent_team = combobox_o.get()

    # Check if both teams are selected and are not the same
    if selected_team and opponent_team and selected_team != opponent_team:
        match_up = [selected_team, opponent_team]
        culc_rtg = []

        try:
            # Open database connection
            with sqlite3.connect("bleague_stat.db") as conn:
                culc_rtg = collect_data(match_up, culc_rtg, conn)
                match_up.reverse()
                culc_rtg = collect_data(match_up, culc_rtg, conn)

            calculate_rating(culc_rtg)
        except sqlite3.Error as e:
            print(f"An error occurred: {e}")
        finally:
            # Reset combobox selections
            combobox_t.set('')
            combobox_o.set('')
    elif selected_team == opponent_team:
        print("同じチームは選択できません。異なるチームを選択してください。")

root = Tk()
root.title('Match_up')

team = ['北海道', '仙台', '秋田', '茨城', '宇都宮', '群馬', '千葉J', 'A東京', 'SR渋谷', '川崎', '横浜BC', '富山', '信州', '三遠', '三河', 'FE名古屋', '名古屋D', '京都', '大阪', '島根', '広島', '佐賀', '長崎', '琉球']

combobox_t = ttk.Combobox(master=root, values=team)
combobox_o = ttk.Combobox(master=root, values=team)

combobox_t.bind('<<ComboboxSelected>>',show_selected)
combobox_o.bind('<<ComboboxSelected>>',show_selected)

combobox_t.pack()
combobox_o.pack()

root.mainloop()

こちらのプログラムを実行して対戦カードを指定しますと以下のようにコンソールに出力されます。

仙台のPTSは80、横浜BCは74です。
仙台のPPPは0.903、横浜BCPPPは0.916です。
仙台のPOSSは89.38、横浜BCPOSSは81.59です。
仙台のeFG%は50.51、横浜BCeFG%は49.7です。
仙台のOR%は30.94、横浜BCOR%は23.82です。
仙台のTO%は14.99、横浜BCTO%は11.4です。
仙台のFTRは0.23、横浜BCFTRは0.19です。
仙台のORtgは90.28、横浜BCORtgは91.56です。
仙台のDRtgは91.56、横浜BCDRtgは90.28です。

これを見ると以下のようなことが言えます。

  1. ORtgとDRtgはいずれも横浜BCの方が1.4%程度優位にある。ほぼ互角だと思います。

  2. 仙台と横浜BCのPOSSを見ると、仙台は攻撃回数が1試合で7.79回も上回り(89.38- 81.59)、TO%も多いため、仙台はリスクを取っても早い試合展開にすることで横浜BCに攻撃させない展開にすることが予想されます。

  3. 仙台は横浜BCよりも高いOR%を活かして、セカンドチャンスを含めた攻撃で得点に繋げることが重要となります。

  4. 横浜BCは低いTO%によるミスの少ない堅実な試合運びを行うことで、相手より少ない得点機会を得点に繋げることが求められます。

ORtgとDRtgだけで予想するよりも試合の展開が見えてきて面白く感じます。

この記事が気に入ったらサポートをしてみませんか?