月別アーカイブ: 2019年2月

ここまでの順位争いをアニメーションで振り返る

代表戦ブレイクも終わり、今週末からまたB1が再開しますね。ワールドカップ出場も決まり、ますますの盛り上がりが期待されます。

この記事ではここまでの順位争いを振り返ってみたいと思います。普通に順位表を作ったのでは面白くないので、y軸を勝ち数としてアニメーションを作ってみました。

なおチャンピオンシップと残留プレーオフの進出に影響する順位の算出方法ですが、Bリーグのサイトを参考に私が実装したものとなっています。

勝率が並んだ場合などに少し複雑な処理があるのですが、実装が間違っている可能性がなきにしもあらずです。あくまで参考値ということで。

ではまずはB1を見てみましょう。名前の横の()内の数字はリーグ全体での順位を表しています。

f:id:rintaromasuda:20190226230753g:plain

上位争いですが、今期絶好調の千葉ジェッツを栃木ブレックスと新潟アルビレックスBBが追いかける展開となっております。そこに強豪のアルバルク東京と琉球ゴールデンキングスが迫っています。

まだ20ゲームを残しており後半に何が起こるか分かりませんが、チャンピオンシップ進出を巡っては川崎ブレイブサンダース、京都ハンナリーズ、名古屋ダイヤモンドドルフィンズ、シーホース三河、そして富山グラウジーズによる椅子取りゲームになる可能性が高そうです。

現在残留プレーオフ対象となっているチームは少し集団から離されてしまっている印象ですが、秋田と北海道がこの先激戦の東地区でどのように勝ち星を挙げていくのかには注目したいと思います。

続いてB2です。

f:id:rintaromasuda:20190226230837g:plain

とにかく強い信州ブレイブウォリアーズが頭一つ抜け出しており、これを連勝で話題となった群馬クレインサンダーズが追いかけています。西地区では島根スサノオマジックと熊本ヴォルターズが並んでいる状態です。

これらの集団を茨城ロボッツと広島ドラゴンフライズが狙っているという構図でしょうか。

八王子ビートレインズは少し集団から離される形となっています。昇格してきたチームがすぐに降格する展開が個人的には好きではないので、同チームにはなんとか踏ん張ってもらいたいと思います。

おまけに2016-17シーズンと2017-18シーズンのB1の順位争いもアニメ化してみました。意外と色々と忘れてしまっていたので、これを見ることで記憶のリフレッシュができました。

滋賀レイクスターズが2シーズン連続でぎりぎり残留プレーオフを回避しているのには注目です。今季も終盤での挽回はあるでしょうか。

f:id:rintaromasuda:20190222164914g:plain

f:id:rintaromasuda:20190223053029g:plain

サンプルコード

この図(アニメを構成する1枚1枚の絵)を描いたときのコードがこちらにアップロードされています。

リバウンド取得率の推移を見てみる

各チームのここまでのリバウンド取得率の推移を見てみましょう。ちなみに取得率の計算は以下のように行っております。

ディフェンスリバウンド取得率 = ディフェンスリバウンド取得数 ÷ (相手のオフェンスリバウンド取得数 + ディフェンスリバウンド取得数)
オフェンスリバウンド取得率 = オフェンスリバウンド取得数 ÷ (相手のディフェンスリバウンド取得数 + オフェンスリバウンド取得数)

B1各チームのここまで(38試合終了時点のデータです)は以下のようになりました。直線は線形の近似線です。

f:id:rintaromasuda:20190213094549j:plain

シーホース三河のオフェンスリバウンド取得率がすごく向上しています。以前にも書きましたがオフェンスリバウンドというのはただ取りに行けばいいというものでもなく、相手にトランジションオフェンスでやられない為にも敢えて無理して行かないという選択も考えないといけないエリアです。

その意味でシーホース三河は、どこかで戦術レベルで変更があったのかなと推測します。それぐらい目立って値が変化しています。

逆にオフェンスリバウンド取得率の減少傾向が目立つのが琉球ゴールデンキングスです。これも上述の戦術的なものによるものかもしれませんが、琉球の場合は外国籍選手に故障が続いており、それが影響している可能性も高そうです。

この図で見ると小さいので各チームの違いがそんなに分からないかもしれませんが、すべてのディフェンスリバウンドとオフェンスリバウンドを合計して取得率を算出すると、以下のような結果になりました。

f:id:rintaromasuda:20190213095107j:plain

総合的に見てやはり強豪チームはリバウンドが強そうです。今季の京都ハンナリーズはリバウンドで苦戦しているようですが、推移を見ると少しずつ向上してきており、おそらく最近の好調さと無関係ではないと思います。ちなみに点がラベルの後ろに隠れてしまっております(すみません。)

B2の方は以下のようになっています。

f:id:rintaromasuda:20190213095414j:plain

f:id:rintaromasuda:20190213095426j:plain

島根スサノオマジックがリバウンドで圧倒的な強さを見せていますね。同チームは西地区の1位を走っていますが、間違いなくその要因のひとつがこのリバウンドの強さではないかと想像します(すみません、あまりB2のゲーム自体は観ておりません。)

信州ブレイブウォリアーズはオフェンスリバウンドが最下位ですが、これはおそらくB1の名古屋ダイヤモンドドルフィンズと同様の理由で、スリーポイントシュートがチームの攻撃の主体になっていることによるものでしょう。

愛媛オレンジバイキングスがディフェンスリバウンドで頑張っているようで素晴らしいですが、残念ながらまだ戦績の方の結果にはそれが結びついていないようですね。

サンプルコード

この記事の図を描いたときのコードがこちらで見つかります。

2日連続のゲームは選手のパフォーマンスにどう影響しているのか

ご存知の通りBリーグでは土日(たまに金土もしくは日月)に渡って2日連続で同じチーム同士が対戦するスケジュールが基本です。私はバスケットボール選手ではないので実感はできませんが、これはなかなかに選手の負担が大きそうです。プレータイムが長ければ尚更です。

私はBリーグ以外のプロバスケットリーグをあまり知りませんが、今シーズンにオーストラリアのNBLを観ていた限りでは、2日間連続のゲームどころか中1日や中2日もそんなになかったように思います(年末などには中1日があったと思います。)

Bリーグがこのスケジュールになっている理由は色々あるでしょうし、私はそれに賛成も反対も述べる気はないのですが、もしBリーグに独特なものであれば分析しない手はないと思いました。またいつも通りデータを見てみたいと思います。

ここではB1、B2の過去3レギュラーシーズンすべてのゲーム(2018-19シーズンは38試合終了時点)から、2日連続で同じチーム同士が対戦したゲームだけ抽出し(ちなみに2,622ゲームありました)、それをゲーム1とゲーム2に分けて比べてみたいと思います。

得点

まずは得点を見てみましょう。ゲーム2ではシュートが入らなくてロースコアになったり、逆にディフェンスが雑になってハイスコアになってしまうような傾向があるでしょうか。シーズンごとに全体の傾向を見てみましょう。

f:id:rintaromasuda:20190210000331j:plain

全体的にはあまり傾向に違いはないようです。チーム別だとどうでしょうか。2018-19シーズンのB1各チームのデータを見てみましょう(図が小さくてすみません。)

f:id:rintaromasuda:20190210003006j:plain

基本的には無視できる程度の違いが多いですが、特色のあるチームもあります。例えばシーホース三河や琉球ゴールデンキングスはハイスコアを出したときはほぼゲーム1だったようです。

また京都ハンナリーズや横浜ビー・コルセアーズはゲーム2のほうが得点が安定する傾向が見られます。新潟アルビレックスBBはゲーム1のほうが安定していますね。色々と面白いです。

eFG%

同じ要領で全体のeFG%(スリーポイントに1.5倍のボーナスを与えたFG%)を見てみましょう。ゲーム2だとシュートの確率が悪くなったりしていますでしょうか。

f:id:rintaromasuda:20190210003140j:plain

こちらも全体傾向では意味のある違いはないようです。チーム別だとどうでしょうか。再び2018-19シーズンのB1で見てみましょう。

f:id:rintaromasuda:20190210004212j:plain

サンロッカーズ渋谷、滋賀レイクスターズ、京都ハンナリーズあたりはゲーム2で確率を上げてきています。修正による向上なのか、それともゲーム1ではエンジンがかからないのかは分かりませんが、面白いです。

琉球ゴールデンキングスはゲーム2での落差がかなりありますね。今まで同チームのゲームを観ていて気が付きませんでしたが、ゲーム2ではなかなか得点に苦労していると思われます。

ポゼッション(または試合のペース)

ゲーム2だとみんな疲れているかもしれません。そうするとゲームのペースが遅くなりがちかもしれません。ポゼッション数もゲーム1とゲーム2で比べてみましょう。

なおこのポゼッション数はオリバー式で求めている近似値で、基本的にはシュートの数とターンオーバーの数からオフェンスリバウンドの数を引いたものだと思ってください。

f:id:rintaromasuda:20190210004938j:plain

おっとこれは!意味があるほど大きい差かどうかは議論の余地がありますが、少なくても3シーズンで同じ傾向にあります。ゲーム2の方がペースが遅いのでしょうか。チーム別ではどうでしょう。

f:id:rintaromasuda:20190210005516j:plain

全体傾向がそうなので当たり前ですが、多くのチームでゲーム2ではポゼッション数が減少する傾向にあるようです。トランジションオフェンスが出なくなるのか、シュートへの積極性が低くなるのか、色々と可能性は考えらそうです。

シーホース三河はポゼッション数のゲーム2での減少も面白いのですが、ポゼッション数が他のチームに比べて安定しているのも面白いですね。ハーフコートオフェンスが中心だからでしょうか。

ファウル数

同じ要領でファウル数も見てみましょう。ゲーム2では足が動かず、結果として手を出してファウルが多くなってしまうかもしれません。

f:id:rintaromasuda:20190210070815j:plain

f:id:rintaromasuda:20190210070827j:plain

こちらも全体的にゲーム2で増加傾向があるように見えます。滋賀レイクスターズや富山グラウジーズではかなりその傾向が顕著ですね。滋賀の場合は主力のラワルのファウルトラブルに苦しむケースが多いようですが、もしかしたらゲーム2でそれが起こりがちなのかもしれません。

まあもちろんファウルが多いから悪いと単純に言える話ではなく、秋田ノーザンハピネッツのようにむしろディフェンスのインテンシティが高いからファウル数が多いというケースもあります。

まとめ

いくらでも深掘りできそうなトピックなのですが、とりえあず長くなってきたのでここまでとしたいと思います。もっとディフェンス寄りのスタッツを見ても面白そうですし、選手の運動量などのいわゆるスタッツを超えるデータがあればさらに面白い結果が見られそうです。

B1のチームだけ図に入れてしまったので、B2のチーム別の図は後でTwitterにでも上げようと思います。

追記1

得失点の図をこちらに貼っておきます。

f:id:rintaromasuda:20190211231021j:plain

追記2

B2版の図をこちらに貼っておきます。

f:id:rintaromasuda:20190211231227j:plain

f:id:rintaromasuda:20190211231427j:plain

f:id:rintaromasuda:20190211231444j:plain

f:id:rintaromasuda:20190211231532j:plain

f:id:rintaromasuda:20190211231545j:plain

サンプルコード

この記事に出てくる図を書いたときのコードがこちらにアップロードされています。

ここまでの観客動員数の累計を過去のシーズンと比べてみる

観客動員数の小ネタです。

Bリーグは観客動員数のゴールを(2020年まで?)年平均成長率10%に設定しているそうですが、今シーズンの観客動員数はここまでどうでしょうか?見てみましょう。

f:id:rintaromasuda:20190208002709j:plain

ご覧の通り今シーズンの序盤は躓いたものの、昨シーズンの同時期と比べて今シーズンが徐々に離しつつあります。これ自体は素晴らしいです。

しかし図からもわかると思いますが、昨季までは1月の頭に長めの空白期間がありました。そして今季はその埋め合わせをするかのように、B1にはFIBAワールドカップアジア予選Window6に向けた空白期間が2月に設定されています。

このB1の空白期間にB2がどこまで頑張れるか、そしてB1が再開してからのシーズン終盤にリーグを盛り上げ、どこまで客足を伸ばせるのかが10%成長達成の為の鍵となりそうです。

B2の観客動員数と言えば、今シーズン戦績は絶好調である群馬クレインサンダーズが、観客動員数が昇格の規定である平均1,500人(でしたっけ?)に達成しない可能性があり話題になっています。

個人的にはB1に上がるためにはチームがもちろん強くなくてはならない、そして観客動員数というビジネスのゴールも達成していなければならない、というのは少し厳し過ぎるのかな、という印象はあるのですが、規定である以上はクリアしなくてはなりませんね。頑張ってほしいところです。

安藤周人はスリーポイントのBリーグ記録を更新するか

名古屋ダイヤモンドドルフィンズの安藤周人が今シーズン好調です。スリーポイントの成功数で現在リーグトップを走っており(確率では6位)、推移を見てみると2位以下をかなり離す独走状態となっています。

下の図は現時点のスリーポイント成功数ベスト5の選手の推移を表したものです。選手名の横の%は現時点でのスリーポイント成功率です。8ゲーム目くらいから一気に安藤が飛び出しています。

f:id:rintaromasuda:20190205095913j:plain

実はここまであまり名古屋のゲームを観ていなかったのですが、この間の富山グラウジーズとの2連戦を観た限りではスリーポイントだけの選手では決してなく、鋭いドライブからのペイントタッチも出来る選手ですね。

すごく良く言ってしまうと、青山学院大学の先輩である辻と比江島の両方のプレーが出来るような選手だという印象で、日本代表の合宿にも今後はコンスタントに呼ばれそうですし、非常に楽しみな選手となってきました。

さてスリーポイントの成功数と言えば、2016-17シーズンはマブンガの153本が、2017-18は辻の145本がそれぞれ最高記録でしたが、ここまで安藤はそれらの記録を上回る可能性のあるペースでスリーポイントを決めています

f:id:rintaromasuda:20190205100520j:plain

過去の記録からすると1試合に2.5本のペースでスリーポイントを決めることが出来ればシーズンチャンピオンになることができそうですが、記録の更新を狙うのであれば試合平均で3本近くを狙いたいところです。

安藤はここまで平均出場時間も約30分とタフなシーズンを戦っていますが、是非ともこのままどんどんスリーポイントを狙ってもらい、スリーポイント成功数の記録の更新、そして日本人得点王も狙って欲しいと思います。

NBAのデータAPIを試してみた

このブログではBリーグ以外の話を扱うつもりはないですが、NBAのデータAPIで遊んでみたのでそれの共有です。興味のある方もいるかもしれないと思いましたので。

ちなみに私も最初はNBAのデータを使ってブログを書こうかなと思ったりしたのですが、NBAだと似たようなことをやっている人が既にいっぱいいそうだと思ったのと、やはり愛着(愛情?)という面でBリーグ以上の気持ちがNBAに持てなかったので今に至っています。

ただやはりNBAだとAPIがあったりするので便利ですね。そんなに使いやすいAPIだという印象は持ちませんでしたが(ドキュメントもあまりなさそうです)、それでもほぼリアルタイムで最新の情報が手に入るのは素晴らしいことです。

以下のRのコードはここのドキュメント群を参考にして書いたもので、ゴールデンステート・ウォリアーズ各選手の今季のスタッツを取得し、そしてこのブログで前にやりました出場時間のヒートマップを作成しています。各チームのTeam IDはこちらで分かります。

if (!require(dplyr)) {
install.packages("dplyr")
library(dplyr)
}
if (!require(ggplot2)) {
install.packages("ggplot2")
library(ggplot2)
}
if (!require(stringr)) {
install.packages("stringr")
library(stringr)
}
if(!require(httr)) {
install.packages("httr")
library(httr)
}
if(!require(jsonlite)) {
install.packages("jsonlite")
library(jsonlite)
}
teamId <- 1610612744
# Get game log (a.k.a. schedule) to get Game IDs
url <- paste0("https://stats.nba.com/stats/teamgamelog",
"?DateFrom=&DateTo=&LeagueID=&Season=2018-19&SeasonType=Regular+Season",
"&TeamID=",
as.character(teamId))
httpResponse = GET(url, add_headers(Referer = "http://stats.nba.com"), accept_json())
res <- content(httpResponse)
colNames <- res$resultSets[[1]]$headers
numGames <- length(res$resultSets[[1]]$rowSet)
df.games <- data.frame()
for (i in 1:numGames) {
arrayRow <- as.character(res$resultSets[[1]]$rowSet[[i]])
df <- as.data.frame(matrix(arrayRow, nrow = 1),
stringsAsFactors = FALSE)
colnames(df) <- colNames
df.games <- rbind(df.games, df)
}
# Add game index
df.games$Game_ID <- as.character(df.games$Game_ID)
df.games <- df.games %>%
arrange(Game_ID) %>%
mutate(Game_Index = row_number())
# Access each game
df.boxscore <- data.frame()
for (gameId in df.games$Game_ID) {
url <- paste0("https://stats.nba.com/stats/boxscoretraditionalv2",
"?EndPeriod=1&EndRange=0",
"&GameID=",
gameId,
"&RangeType=0&StartPeriod=1&StartRange=0")
httpResponse = GET(url, add_headers(Referer = "http://stats.nba.com"), accept_json())
res <- content(httpResponse)
colNames <- res$resultSets[[1]]$headers
for (i in 1:length(res$resultSets[[1]]$rowSet)) {
arrayRow <- as.character(res$resultSets[[1]]$rowSet[[i]])
df <- as.data.frame(matrix(arrayRow, nrow = 1),
stringsAsFactors = FALSE)
colnames(df) <- colNames
df.boxscore <- rbind(df.boxscore, df)
}
}
df.output <- merge(df.boxscore,
df.games[, c("Game_ID", "Team_ID", "Game_Index")],
by.x = c("GAME_ID", "TEAM_ID"),
by.y = c("Game_ID", "Team_ID"))
ConvertMinStrToDec <- function(min_str) {
Convert <- function(item) {
min <- as.numeric(item[1])
min <- min + as.numeric(item[2]) / 60
round(min, 2)
}
ls <- sapply(stringr::str_split(min_str, ":"), Convert)
return(ls)
}
df.output$MIN_NUM <- ConvertMinStrToDec(df.output$MIN)
df.output[is.na(df.output$MIN_NUM),]$MIN_NUM <- 0
ggplot() +
geom_tile(data = df.output,
aes(x = Game_Index,
y = PLAYER_NAME,
fill = MIN_NUM)) +
ylab("") +
xlab("nth Game") +
ggtitle("Miniutes Played over Games - Golden State Warriors") +
scale_fill_continuous(high = "navy", low = "white", guide_legend(title = "MIN")) +
scale_x_continuous(breaks = seq(5, 82, by = 5)) +
theme(plot.background = element_blank(),
plot.title = element_text(hjust = 0.5),
panel.grid.minor = element_blank(),
panel.grid.major = element_blank(),
panel.background = element_blank(),
axis.line = element_blank(),
axis.ticks = element_blank(),
axis.text.y = element_text(hjust = 1, size = 8),
axis.text.x = element_text(size = 8),
strip.text = element_text(face = "bold"),
strip.background = element_rect(fill = "white", colour = "white")
)

結果は以下のようなヒートマップになりました。ロスターの人数は多めのNBAですが、長いシーズン、やはりみんな起用されているのですね。カズンズのプレータイムは今後どうなっていきますかね~。

f:id:rintaromasuda:20190202063222j:plain

今後BリーグがこのようなAPIの作成に着手するかは分かりませんが、それなりの投資になりますし、想像するに優先順位は低めの案件でしょうね。

私は純粋なソフトウェアエンジニア的視点で、APIなどを開放することによりサードパーティーの参加を促しエコシステムを強化する、という戦略の信奉者なので期待したいところではありますが。

追記

過去のデータも取得できることがわかったので、ついでに前人未到の72勝を記録したときのシカゴ・ブルズのヒートマップも作ってみました。マイケル・ジョーダンは全試合出場していました。

f:id:rintaromasuda:20190203075110j:plain

追記(2019.12.13)

NBA APIの仕様が変わり、元のコードだとAPIのアクセスがブロックされるようになりましたのコードを修正しました。