はじめに
B.LEAGUEデータ分析用Rパッケージ「bleaguer」を使って今シーズン前半のBリーグを振り返りました。
Rでの分析を前提として便利関数が用意されてるようですが、Python をメインに使ってるので アップロード済みの生データをダウンロードして利用しました。(頻繁に最新のデータが更新されてるようなので助かります)
詳しい説明は bleaguer の作成者のブログを参考にすると良さそうです。
準備
データをロードする関数を用意した。
URL は何度も叩くのはアレなので 一度リクエスト叩いてロードしたものはローカルに保存しておく。
import io import os import pandas as pd import requests def load(url: str, overwrite: bool = False) -> pd.DataFrame: data_prefix = "./data" # notebooks 以下での実行時に data_prefix を変更する if os.path.basename(os.getcwd()) == "notebooks": data_prefix = f".{data_prefix}" os.makedirs(data_prefix, exist_ok=True) data_path = f"{data_prefix}/{os.path.basename(url)}" # ローカルにデータがないときだけgithubからデータをロードする if not os.path.exists(data_path) or overwrite: print(f"Load from {url}") r = requests.get(url) df = pd.read_csv(io.BytesIO(r.content), sep=",") df.to_csv(data_path, index=False) else: df = pd.read_csv(data_path) return df
生データを集計する関数も用意した。
from typing import Dict, List, Union import pandas as pd def aggregate( df: pd.DataFrame, key: Union[str, List[str]], agg_dict: Dict[str, List[str]] ) -> pd.DataFrame: for v in agg_dict.values(): assert type(v) == list df_agg = df.groupby(key).agg(agg_dict).round(2) df_agg.columns = [f"{c[0]}_{c[1]}" for c in df_agg.columns] df_agg = df_agg.reset_index() return df_agg
可視化には Plotly を使う。
使用バージョンは 4.13.0 で、画像を保存する際は kaleido がインストールされている必要がある。(Plotly 4.9.0 以降では Orca ではなく Kaleido がデフォルトのエクスポート操作に使われる。pip install もできて楽)
from abc import ABCMeta, abstractmethod from typing import Dict, Optional import numpy as np import pandas as pd import plotly.graph_objs as go import plotly.offline as py class BasePlotly(metaclass=ABCMeta): colors = ["#3D0553", "#4D798C", "#7DC170", "#F7E642"] def _convert_to_str(self, arr: np.array) -> np.array: return np.array(arr, dtype=str) def _plotly_layout( self, title: str = None, xtitle: str = None, ytitle: str = None ) -> go.Layout: return go.Layout( title=title, xaxis=dict(title=xtitle, ticklen=5, zeroline=False, gridwidth=2), yaxis=dict(title=ytitle, ticklen=5, gridwidth=2), ) def _save(self, fig: go.Figure, path: str): fig.write_image(path, width=1200, height=600) @abstractmethod def show(self): pass class BarPlotly(BasePlotly): def show( self, data: pd.DataFrame, xcol: str, ycol: str, is_horizontal: bool = False, title: Optional[str] = None, xtitle: Optional[str] = None, ytitle: Optional[str] = None, save_path: Optional[str] = None, ): orientation = "v" color_col = ycol text_col = ycol if is_horizontal: orientation = "h" color_col = xcol text_col = xcol trace = [ go.Bar( x=self._convert_to_str(data[xcol].values), y=data[ycol].values, text=data[text_col].values, textposition="auto", marker=dict( color=data[color_col].values, colorscale="Viridis", showscale=True, reversescale=True, ), orientation=orientation, ) ] layout = self._plotly_layout(title=title, xtitle=xtitle, ytitle=ytitle) fig = go.Figure(data=trace, layout=layout) if save_path is not None: self._save(fig, save_path) return py.iplot(fig, show_link=False)
第9節(11/14・11/15)までの順位表
各種のチームスタッツは基本的に順位に比例するので 最初に順位表を貼っておく。
各地区上位3チームずつと、残りチームの中で勝率上位の2チームがチャンピオンシップトーナメントに出場する。
以下では2020-2021年シーズンの第9節(11/14・11/15)以前のデータを集計したものだが、上記順位表は第10節(12/2)を含む。
グラフ化
以下にグラフだけ表示するが 実験に使った Jupyter notebook は以下に nbviewer を使って確認することが出来る。
得点・被得点
チーム得点は千葉・富山が抜けていることが分かる。両チームともオフェンスをプッシュできるPG(富樫・宇都)と、得点力の高いインサイド(エドワーズ・マブンガ)を擁しているのが寄与していそう。
チーム被得点(ディフェンス力を表す)は宇都宮・琉球が上位。両チームともシーズン中に10連勝して、両地区で首位になっている。ディフェンス力が高いチームが安定して強いことが多いが、今シーズンのBリーグでも高いチームディフェンス力は勝利に重要な要素であるよう。
選手別平均得点を見るとインサイドの外国籍選手が独占している。特に下位チームの選手が目立っているように見える。下位チームほど得点源を外国籍選手に頼ってしまっているのだろうか。
ベンチスコア
ベンチ選手の出場時間および得点を見ると、渋谷・宇都宮・東京・川崎といったチームはベンチの出場時間も得点も高いことが分かり、ベンチ層が厚いことが分かる、一方で、千葉や富山は上位チームの割にベンチ選手の出場時間や得点は低く、スターターの出場時間が長くなっている。シーズンを減るごとに 試合の間隔が短いときに 選手に疲労がたまり、怪我やパフォーマンスの低下に繋がらないかが懸念されそう。
リバウンド
チームのトータルリバウンド数は選手のリバウンド力、特にオフェンスリバウンド力に依存しそう。特に琉球のクーリーの平均オフェンスリバウンド数は突出している。それを追う千葉・秋田の選手のリバウンド数がチームのリバウンド数に影響している。また、宇都宮はリバウンド数が突出している選手が少ないのにもかかわらずチーム平均リウバンド数が3位になってる。これはディフェンスが良いので、打たせて落とさせるディフェンスをしているので チーム全体でリバウンドを共有している結果なのだろうか。
アシスト
チーム全体のアシスト数が多いほど パスでつないで点数を決めているので 相手のディフェンスに的を絞らせず守りづらいと言われている。上位チームに混じって大阪が上位にランクインしているので 順位自体は高くないが 良いオフェンスを展開しているのかもしれない(試合を見てないのであまり知らない)。
選手別のアシスト数を見ると、PGを差し置いて滋賀のハミルトンと富山のマブンガが突出している。おそらくボール保持時間が長く、アウトサイドからドライブしてヘルプが来なければ決めるし、寄ってきたらキックアウトするチームの中心的な選手なことがスタッツに現れている。日本バスケの特徴的なところのように感じた。(知らんけど)
スティール
チームスティールが多いのはテンポの早い攻撃をするチームが多そう。
選手別スティール数は三遠の川嶋が突出している(あまりプレーを見たことがないので特徴はわからないが クレバーな選手なのか)。また、外国籍選手が多いのは気になるところ。通常 ガードなどボール保持時間が長い選手についたり、インサイドのヘルプで下のボールを叩く選手がスティールが多くなる傾向があると思ったので。これも日本バスケの特徴なのかもしれない。(知らんけど)
ブロック
チームブロックは秋田が多い。デイビスが突出して多いのが影響していそう。秋田はスティールも多い割に順位が振るわないのは 激戦の東地区に属しているからか得点力が低いからか。
スリーポイント
チームとしてはスリーポイント成功率・成功数は三河が一番になっている。
選手としては三河の金丸が成功数1位にもかかわらず成功率も上位10人に入ってるのは特筆すべき点のように思われる。また、アシスト数の多かった滋賀のハミルトンや富山のマブンガのスリーポイント試投数が多いことから、外でも中でもプレイできるオールラウンダーである印象を受ける。
フリースロー
ポゼッション
簡単に言うと攻撃回数を表すポゼッションをチームごとに見てみる。
def calc_posession(FGA: int, TO: int, FTA: int, OR: int) -> float: """ ref. https://www.nbastuffer.com/analytics101/possession/ - FGA: Field Goal Attempts - TO: Turnovers - FTA: Free Throw Attempts - OR: Offensive Rebounds """ return 0.96 * (FGA + TO + 0.44 * FTA - OR)
富山・大阪は試合のテンポが早いことが分かる。
ターンオーバー
チームのポゼッションあたりのターンオーバー数を見ると東京が少ない。ミスの少ない安定感のある試合運びをしていそう。
ターンオーバー数はボール保持時間の長い選手ほど多くなる傾向もあるので、富山のマブンガが多いのはチームのスタイルの影響だろう(その割に滋賀のハミルトンは少なく見える)。また、ターンオーバー数をPGに絞ると、信州の西山が多いのが気になる。得点力も低いのはボール運びに苦戦しているのも一因かもしれない(見てないのでわからない)。また、宇都宮のガード陣のターンオーバー数が少ないのも目立つ。単に宇都宮の場合はボール運びにフォワードの比江島やロシターが参加することが多いことが影響しているかもしれないが。
オフェンス効率
千葉のオフェンス効率は突出していることが分かる。
ディフェンス効率
宇都宮のディフェンス効率は突出して良いことが分かる。
クォーターごとの平均得点
終わりに
最初はデータがあるので軽く見てみるかといろいろ眺めているうちに、かなりのボリュームになってしまいました。
過去データを見ながら試合を見ると楽しさが倍増です。(この選手はフリースロー成功率が悪いから このフリースローも1本は外すなと思って見ていると実際に外したり)
最後になりますが bleaguer を公開・保守してくださっているメンテナーの方に感謝します。
ソースコードは以下に置いてあります。