論理の流刑地

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

J1の各試合のフォーメーションを自動取得する

ただの数字、されど数字。
それがフォーメーション。

Introduction

サッカーを戦術的に語るうえでの大きな要素としてフォーメーションがある。
Football LABではシーズン累計のフォーメーション別戦績*1を、クラブ別のSummaryページにおいて示してくれている(例:2020年の名古屋)。

これはこれでとても有益かつ示唆的な情報ではあるのだが、より細かい粒度の情報が欲しいという欲望もある。
具体的には、M月D日のAAA vs BBBで両クラブがどういうフォメだったか、みたいなのがほしい。

で、一応各試合の詳細ページ(例:湘南 vs 鳥栖)にはフォメ図が乗っているのである。
しかしこれらは画像なので、当然だがテキストデータとしてフォメ図を取得することはできない。
でも、それ以外の情報は自動取得しているのに、両軍のフォーメーションだけ目視で入力するのも、イケてない。

で、なんとかして取れないかなーと試行錯誤した記録である。

Implementation

scriptタグの内部を解析する

上でも触れた通り、画像からはフォーメーションの情報を文字・数字情報として取得することはできない。
....のだが、(イチイチ人が作っているわけではなく)javascriptでこういう画像って自動生成してんじゃないのか、と思ってソースコードを精査すると、以下のような非常に怪しい部分がみつかった

	mOctagon('homeFormation', 269, sz/10*3,60,13,'石原直樹',0,'','out');
	mOctagon('homeFormation', 269, sz/10*7,60,17,'大橋祐紀',0,'','');
	mOctagon('homeFormation', 269, sz/10*3,210,10,'山田直輝',0,'','out');
	mOctagon('homeFormation', 269, sz/10*7,210,30,'柴田壮介',0,'','out');
	mOctagon('homeFormation', 269, sz/10*1,360,42,'高橋諒',0,'','out');
	mOctagon('homeFormation', 269, sz/10*5,360,25,'中村駿',0,'yellow1','');
	mOctagon('homeFormation', 269, sz/10*9,360,6,'岡本拓也',0,'','');
	mOctagon('homeFormation', 269, sz/10*1,510,32,'田中聡',0,'','out');
	mOctagon('homeFormation', 269, sz/10*5,510,3,'石原広教',0,'','');
	mOctagon('homeFormation', 269, sz/10*9,510,22,'大岩一貴',0,'','');
	mOctagon('homeFormation', 269, sz/10*5,660,1,'谷晃生',0,'','');
	mOctagon('awayFormation', 122, sz/10*3,60,9,'山下敬大',0,'yellow1','out');
	mOctagon('awayFormation', 122, sz/10*7,60,8,'林大地',1,'','out');
  .....

どうもこのmOctagon()という関数で、フォメ図上の各選手の位置を指し示しているっぽい。
この第四引数がおそらくY軸上の位置を指し示していると思われるので、それをとってきて、フォーメーションを類推すればいい。


mOctagon...という関数がつかわれている各行から、各選手のフォメ図上のY座標をとってくるには、以下のような簡単な関数をかけばいい。

  get_y_coord <- function( oct_c){
    spl_info <- str_split( oct_c,",") #読点で分割
    y_v <-  as.numeric(spl_info[[1]][4]) #y座標を数値として取得
    return(y_v) #
}

フォーメーションを取得する

11人のY座標が同じ人たちは同列と考えていいので、GKを抜いた10人のうちY座標を同じ人たちをカウントして後列からつなげていくと、"4-2-3-1"とか"3-4-2-1"みたいなよくみるフォーメーション(を表わす数値羅列)になる。

この処理は、以下のような関数で実装できる

#-- 内部関数:数値の羅列からフォメを計算 -- #
  #引数⇒ GKを抜いた10人のy座標のベクトル
  num_to_Form <- function( n_form){
    coord_df <- data.frame(y = n_form)
    cnt_df <- ( coord_df %>>% dplyr::group_by( y ) %>>% dplyr::summarise( N = n()) %>>% dplyr::arrange( y) %>>% as.data.frame())
    form_chr <- paste0( rev(cnt_df$N) , collapse="-") #Formation
    return( form_chr)
  } #num_to_Form

num_to_Form( c( 3, 3, 5 ,5 , 13, 13, 13, 15,15,15))
#[1] "3-3-2-2"


ただ、いわゆる4-4-2は"4-2-2-2"になってしまうので、そこは必要なら適宜判定したうえで"4-4-2"にするコードを追加したほうがよいかもしれない。

実装

以上のような内部処理をしつつ、フォーメーション図を取得してくるコードは以下のようなものである

library( rvest)
library(pipeR)
library(dplyr)
library(stringr)

getFormation <- function( game_page){
  
  #--目的のScriptのタイプ--#
  scr_tag <- (html_nodes( game_page , css = "div + script"))[[1]]
  scr_txt <- html_text( scr_tag )
  
  #-- 改行記号で分割したうえで、画像生成しているcode部分だけとってくる --#
  code_lines <- ( str_split( scr_txt, "\n"))[[1]]
  is_oct <- str_detect( code_lines, "mOctagon") #--mOctagon()関数を使っている行だけ抽出。
  oct_lines <- code_lines[ is_oct] #コレを次の関数に算入する。
  
  frm_info <- code_to_Form( oct_lines) #下で定義したもの
  return(frm_info)
} ##func


##-- 内部関数:mOctagon()を含む22人分の文字列からフォメを計算-- ##

code_to_Form <- function( oct_lines ){

  #--Home vs AwayそれぞれのLineをとる--#
  h_lines <- oct_lines[1:10]
  a_lines <- oct_lines[12:21]

  #-- 内部関数:y座標取得 --#
  get_y_coord <- function( oct_c){
    spl_info <- str_split( oct_c,",") #読点で分割
    y_v <-  as.numeric(spl_info[[1]][4]) #y座標を数値として取得
    return(y_v) #数値
  } #function
  
  #-- 内部関数:数値の羅列からフォメを計算 -- #
  #引数⇒ GKを抜いた10人のy座標のベクトル
  num_to_Form <- function( n_form){
    coord_df <- data.frame(y = n_form)
    cnt_df <- ( coord_df %>>% dplyr::group_by( y ) %>>% dplyr::summarise( N = n()) %>>% dplyr::arrange( y) %>>% as.data.frame())
    form_chr <- paste0( rev(cnt_df$N) , collapse="-") #Formation
    return( form_chr)
  } #num_to_Form
  
  #---上で定義した関数群をつかい、各クラブ(Home/Away)のフォメを取得する---#
  hForm <- num_to_Form(sapply( h_lines , get_y_coord))
  aForm <- num_to_Form(sapply( a_lines , get_y_coord))
  
  return( list( Home = hForm ,Away = aForm )) #listにして戻り値に。
} #func , code_to_Form

実行する

ためしに、先日行われた札幌vs横浜FC(URL)のページからフォメ図を問題なく取得できるか、やってみよう

library( rvest) 

gpage <-  read_html("https://www.football-lab.jp/y-fc/report/?year=2021&month=02&date=27")
form1 <- getFormation( gpage)
form1
# $Home
# [1] "3-4-2-1"
# 
# $Away
# [1] "4-2-2-2"

問題なく取得できている。
(上でも触れたが"4-2-2-2"はお好みで"4-4-2"に変換してもよいかも)

www.youtube.com

Enjoy!!

*1:あくまでも試合開始時のフォーメーションで、試合中の変更等は把捉していない