論理の流刑地

地獄の底を、爆笑しながら闊歩する

Jリーグの各試合におけるシュート位置の座標を推定・取得する

いとマニアックな備忘録というかもはや作業メモでしかない系記事

Motivation

Football LABの各試合の詳細データ(例:川崎vs浦和)だとスタッツとして記載があるのは、シュート数/枠内シュート数/シュート数のみであり、シュート数の位置まではわからない。

だから、全シュートのうち「PA内から打たれたシュート数」がどれだけか、とかを数値として取得してくるのは不可能である。
しかし、同ページには、以下のようなシュート位置のマップが示されている

f:id:ronri_rukeichi:20210105231557p:plain
グラフ出所:Football LAB

この図から座標情報を間接的にわかるんじゃね?と考えて推定していく。

座標の指定方法をしらべる

Chromeの開発ツールで、上のグラフのシュートの位置の起点となる黒丸をクリックしてみると、以下のように座標が指定されていることがわかる。
f:id:ronri_rukeichi:20210105232528p:plain

それで、このcyとかcxを動かしながら*1、ピッチの端などキーとなる座標をおぼえておく。

結果は、

という感じでした。

ピッチの広さに関する知識

上のサイトに各寸法があったので、メモしてく。
自分も学生時代はサッカーをしていたので、練習試合を自校でやるときとか石灰を出す台車*2をつかったりしてラインも引いてたはずなんだけど、意外とほとんどおぼえてないもんで。

  • ゴールの幅:7.32m
  • ゴールエリアの幅: 7.32+5.5*2=18.32m
  • ゴールエリアの高さ: 5.5m
  • PAの幅:16.5*2 +7.32= 40.32m
  • PAの高さ:16.5m


という感じ(タッチラインゴールラインの長さは幅がある)。

座標変換のロジックをつくる

とりあえず、元のcx/cyの座標を変換してくための計算式を用意しなければならない
私たちが知りたいのは、

  • PA内から打たれたシュートか?
  • GA内から打たれたシュートか?

という二点。
だから、PAの横幅/縦幅、GAの縦幅

PAの高さ16.5mがLABのサイトのcx/cy平面では80-8 = 72となっているとするなら、だいたい概算して16.5/72≒0.223mが1cxに相当する
ちなみに横幅は302-8 = 294だが、この倍率を用いて検算すると横68mのピッチが想定されていることがわかる。だから68/294が1cyに相当する。
この計算をもとに、cx/cyの座標をリアル座標に変換する関数をかく。
x座標は中心から左なら負の値、右なら正の値をとるようにしてある。

labToRealXY <- function(cx, cy , PA_height = 71.5 , pitch_width=294){
  scl_y <- 16.5/ PA_height #調整係数の計算
  scl_x <- 68 /pitch_width
  
  y_val <- (cy - 8) * scl_y
  x_val <- (cx - 155) * scl_y
  return( c( x_val , y_val))
} #LabToRealXY

リアル座標(labToRealXY()の戻り値)のほうをもとに、PA内かGA内か、それともPA外かを判定する関数も実装する

judgeArea <- function( x, y){
  if( abs(x ) > 40.32/2 || y > 16.5){
    return("Outside_PA")
  }else if(abs(x) <= 18.32/2 && y <= 5.5){
    return("Inside_GA")
  }else{
    return("Inside_PA")
  } #if
}#func

試合ページから取得できるようにする

あとは仕上げの工程。とか思いきや他の部分は静的に生成していたFootball LABはこの部分だけ動的に生成していた(なぜだ)ので、RSeleniumの出番だった。
まぁクライアント立ち上げてちょっと待ってからgetPageSource()した内容を結局rvestに渡すだけなので、あんまりやること変わらないんだけど、実行時間は結構かかった。

ronri-rukeichi.hatenablog.com

codeは省略するが、とりあえず日付とクラブさえわかれば紐づけができるため、日付/クラブ/X座標/Y座標/エリア判定結果を変数としてもったdata frameをページから取得できるようにする。

f:id:ronri_rukeichi:20210106134233p:plain
川崎vs浦和からの取得例

これをすべての試合のURLについて行って、タテにつなげて統合する。

集計結果(やっぱ川崎すげー)

2020年に放たれた全8031本のシュートをクラブ別に集計して、その内訳をみると以下になる。

◆シュート総数

f:id:ronri_rukeichi:20210106134824p:plain
2020年に打たれたシュート総数(クラブ別)

◆シュートの内訳(%)

f:id:ronri_rukeichi:20210106135004p:plain
シュートのエリア別比率:2020年J1リーグ(クラブ別)

川崎はシュート数が1位なだけじゃなくて、ゴールエリア内で打ってる率も1位というやばいことになっている。
翻って名古屋は、シュート数が少ないだけじゃなくてPA外から打ってるのが多い。

うーん...

※追記(21/3/18)
2020年のクラブ別の集計結果詳細は以下の通り

クラブ名 シュート総数 PA内シュート数 うちGA内シュート数 PA内シュート率(%) GA内シュート率(%)
C大阪 434 263 24 60.6 5.5
FC東京 444 255 39 57.4 8.8
G大阪 463 281 30 60.7 6.5
広島 473 290 32 61.3 6.8
川崎F 643 420 65 65.3 10.1
鹿島 597 356 40 59.6 6.7
463 294 22 63.5 4.8
神戸 482 274 32 56.8 6.6
名古屋 381 200 29 52.5 7.6
大分 347 235 32 67.7 9.2
札幌 494 295 28 59.7 5.7
仙台 381 226 22 59.3 5.8
清水 458 279 25 60.9 5.5
湘南 315 194 30 61.6 9.5
鳥栖 409 252 25 61.6 6.1
浦和 404 227 29 56.2 7.2
横浜FC 366 219 19 59.8 5.2
横浜FM 534 339 31 63.5 5.8

ちなみにリーグ平均のPA内シュート率は60.6%で、GA内シュート率は6.8%である。

ちなみに、LABの試合情報ページの表に掲載されている「シュート数」を集計すると2020シーズンの総シュート数は全8088本で、上のやりかたで座標が取得可能なシュートは8031本である。
つまり、マップ上から座標を取得できるシュートのほうが若干少ない。

これは、シュートマップに収まらないくらいの超ロングシュートの存在が影響していると推測している。
比率でいうと\frac{8088-8031}{8088}は0.63%くらいなので、なんとなく直感的にも納得がいく。

Conclusion

原データが手元にないのでアレなんですが、わりと正確な近似はできている気がします。
あとはこれをどう分析に使っていくか。


レミオロメン - 茜空


Enjoy!!

*1:アナログな努力だ...

*2:正式名称なんだっけ...