Bリーグのスタッツを使ってレーティングを計算する / PER
前回はBリーグのPPP、POSSおよび4ファクターを計算して対戦チームを比較する仕組みを作りました。この結果から11/5の仙台 vs 横浜BC戦が接戦になると予想し、この試合のWINNERを購入しました。
たしかに試合は接戦でしたが、まさかオーバータイムにもつれ込むとは予想できず、群馬勝利で予想していたのでWINNERも外してしまいました。これで2日通算1600円の損益です。
さらなる見直しが必要と感じ、新たな指標を探す中で見つけたのがPER(Player Efficiency Rating)です。計算は過去に求めたORtgやDRtgよりは簡単ですが、Pythonプログラム的にはリーグ全体の成績を参照するのが一番大きな改修点だと思います。
しかし一度算出できると、おもにオフェンス面で選手同士を比較できる指標になるということで、こちらを計算するPythonコードを書きました。
1.PERをPythonで計算する
指定したチーム選手のPERを計算するPythonコードを紹介します。使い方ですが、事前にBリーグの公式サイトから選手スタッツのデータベースをリンク先記事のように作成する必要があります。
選手のデータベースが完成しましたら、以下のPythonコードとデータベースを同じ場所に保存して実行してください。
from tkinter import *
from tkinter import ttk
import sqlite3
import pandas as pd
import math
def calculate_poss(FGA, FTA, TOV, ORB):
poss = FGA + 0.44 * FTA + TOV - ORB
return poss
def calculate_team_pace(Tm_Poss, Op_Poss, Tm_MP):
t_pace = 100 * (Tm_Poss + Op_Poss) / Tm_MP
return t_pace
def calculate_per(MP, threeP, AST, team_AST, team_FG, FG, FT, TOV, FGA, FTA,
DRB, ORB, STL, BLK, PF, lg_FT, lg_PF, lg_FTA, lg_PTS,
lg_FGA, lg_ORB, lg_TOV, lg_TRB, lg_AST, lg_FG):
if MP == 0:
return 0
# Calculate factors used in the formula
factor = (2 / 3) - (0.5 * (lg_AST / lg_FG)) / (2 * (lg_FG / lg_FT))
VOP = lg_PTS / (lg_FGA - lg_ORB + lg_TOV + 0.44 * lg_FTA)
DRB_perc = (lg_TRB - lg_ORB) / lg_TRB
ApFGM = team_AST / team_FG
# Calculate parts of the PER formula
t_fgm = (2 - factor * ApFGM) * FG
t_ftm = (FT * 0.5 * (1 + (1 - ApFGM) + (2/3) * ApFGM))
t_ast = (2/3) * AST
t_orb = VOP * DRB_perc * ORB
t_drb = VOP * (1 - DRB_perc) * DRB
t_stl = VOP * STL
t_blk = VOP * DRB_perc * BLK
t_fgmiss = VOP * DRB_perc * (FGA - FG)
t_ftmiss = VOP * 0.44 * (0.44 + (0.56 * DRB_perc)) * (FTA - FT)
t_tov = VOP * TOV
t_foul = ((lg_FT / lg_PF) - 0.44 * (lg_FTA / lg_PF) * VOP) * PF
# Final PER calculation
uper = (threeP + t_fgm + t_ftm + t_ast + t_orb + t_drb + t_stl + t_blk -
t_fgmiss - t_ftmiss - t_tov - t_foul) / MP
return uper
def collect_data(tgt_team, league_name, tgt_name, conn):
aper_list = []
aper = 0
per = 0
# Fetch league and team statistics in fewer queries
league_stats_query = "SELECT * FROM player_stats"
league_stats_df = pd.read_sql(league_stats_query, conn)
team_stats_df = league_stats_df[league_stats_df['team'] == tgt_team]
# Aggregate league and team statistics
l_st = league_stats_df.agg('sum').to_dict()
t_st = team_stats_df.agg('sum').to_dict()
t_poss = calculate_poss(t_st['fga'], t_st['fta'], t_st['tov'], t_st['orb'])
t_pace = calculate_team_pace(t_poss, t_poss, t_st['minute'])
l_poss = calculate_poss(l_st['fga'], l_st['fta'], l_st['tov'], l_st['orb'])
l_pace = calculate_team_pace(l_poss, l_poss, l_st['minute'])
# Fetch player stats for all players in one query
player_stats_query = "SELECT * FROM player_stats"
all_player_stats_df = pd.read_sql(player_stats_query, conn)
# Initialize variables for league average PER calculation
total_aper = 0
num_players = len(all_player_stats_df)
# Iterate over all players
for _, i_st in all_player_stats_df.iterrows():
player_team = i_st['team']
player_name = i_st['name']
# Calculate uPER for the player
uper = calculate_per(
i_st['minute'], i_st['tfgm'], i_st['asst'], t_st['asst'],
t_st['fgm'], i_st['fgm'], i_st['ftm'], i_st['tov'],
i_st['fga'], i_st['fta'], i_st['drb'],
i_st['orb'], i_st['st'], i_st['bs'], i_st['f'],
l_st['ftm'], l_st['f'], l_st['fta'], l_st['pts'],
l_st['fga'], l_st['orb'], l_st['tov'],
l_st['orb'] + l_st['drb'], l_st['asst'], l_st['fgm']
)
# Update league average PER
aper = (l_pace / t_pace) * uper
total_aper += aper
if i_st['team'] == tgt_team:
aper_list.append(aper)
per_name = all_player_stats_df[all_player_stats_df['team'] == tgt_team]['name'].tolist()
for index, item in enumerate(aper_list):
per = item * (15 / (total_aper / num_players))
print(f"Player: {per_name[index]}, PER: {round(per, 2)}")
def show_selected(event):
if combobox_t.get() == '':
return
match_up = combobox_t.get()
conn = sqlite3.connect("bleague_stat.db")
# リーグ全体のプレイヤー名を取得
league_player_name = pd.read_sql("SELECT name FROM player_stats", conn)['name'].tolist()
# 対象チームのプレイヤー名を取得
target_player_name = pd.read_sql(f"SELECT name FROM player_stats WHERE team = '{match_up}'", conn)['name'].tolist()
# データ収集と処理の実行
collect_data(match_up, league_player_name, target_player_name, conn)
combobox_t.set('')
root = Tk()
root.title('Match_up')
t_team = ['北海道', '仙台', '秋田', '茨城', '宇都宮', '群馬', '千葉J', 'A東京',
'SR渋谷', '川崎', '横浜BC', '富山', '信州', '三遠', '三河', 'FE名古屋',
'名古屋D', '京都', '大阪', '島根', '広島', '佐賀', '長崎', '琉球']
combobox_t = ttk.Combobox(master=root, values=t_team)
combobox_t.bind('<<ComboboxSelected>>',show_selected)
combobox_t.pack()
root.mainloop()
ここから先はまだ仮説のため、まだPythonコードに書いていませんが、以下のようにPERとPPP/POSSから予想しました。
対戦チームのスタメン戦力(チーム内で出場時間が長い上位5選手)のPERと残り選手のPERを算出する
上記戦力に出場時間を踏まえ、総合的な戦力を算出する
PPPとPOSから求めた両チームのPTS合計をどれくらい奪い合うか算出する
こちらのやり方で11/12の川崎 vs 群馬の試合は8.6点差で川崎という計算結果になりました。そこで川崎の7~9点差と10~14点差で勝利を予想しました。8.6点は限りなく9点差に近いため、10〜14点差にずれ込む可能性があり得ると判断したからです。
その結果、2週目にしてようやくWINNERが当選しました。利益は280円ですので、通算1320円の損益であることには変わりません。7~9点差であれば良かったのですが、このあたりの点数の揺らぎは難しいところです。
なお、今回作成したPythonプログラムで11/12朝の時点でのBリーグのPERを計算したところ、トップ10は以下の選手となりました。本来はシーズン終了時に求めるのか、全体的に値が高い印象があります。
本来は平均的なパフォーマンスを15で設定している指標だからです。今後は過去のシーズンのPERも検証し、どれくらいチーム力が変化しているかを見ても面白いと思いました。
[BリーグPER Top10]
1位:セバスチャン・サイズ(A東京) 40.69
2位:河村 勇輝(横浜BC) 38.29
3位:ザック・オーガスト(三河) 36.85
4位:アレックス・カーク(琉球) 36.35
5位:ジョシュア・スミス(名古屋D) 35.8
6位:ネイサン・ブース(仙台) 35.39
7位:アンジェロ・カロイアロ(大阪) 35.13
8位:ショーン・ロング(大阪) 34.33
9位:ヴォーディミル・ゲルン(仙台) 33.94
10位:馬場 雄大(長崎) 33.63
PERは他の指標と比べると求めた結果が非常に直感的で、他の選手と数値を比較できるので分かりやすいと感じました。
2.次回予告
Bリーグは11/12から数週間お休みですが、その間に最近作成した内容を一旦手順を含めてまとめたいと思います。それは今回も似たような感じですが、基本的に調べた指標を手当たり次第、Pythonで計算してきたからです。
しかし調べていく中で同じ指標でも研究が進む中で計算式が変わってきたなどの背景があることを知り、どこかでまとめないといけないと感じたからです。
そこで次回は現在はどのように運用しているかを紹介したいと思います。
この記事が気に入ったらサポートをしてみませんか?