皆様こんにちは、アメーバのデータ分析に携わっている和田(@wdkz) です。あと少しでサッカーワールドカップ2014が開幕しそうな今日このごろ(*執筆時点)、サッカー好きな自分としては楽しみで仕方ありません。ちなみに好きな(だった)選手はアルベルティーニ、グアルディオラ、ピルロです。今回のワールドカップも2006年の大会同様に大活躍するピルロを見たいですねー。また、大久保のサプライズ選考で話題となった日本代表にも期待しています。大久保といえば自身の名を冠した「オオクボ」というフェイントがあることは皆さんご存知でしょうか?スペインマジョルカ時代にも試合で使っていたので、ヨーロッパでは「クライフターン」に匹敵するくらいの知名度があるはずです(きっと)。
というわけで、本記事ではサッカーワールドカップのデータをRを使って分析してみたいと思います。今回分析する項目
1. ゴールの生じやすい時間帯があるか
2. 先制点を取ったチームは勝利しやすいか
3. 0-2から1点返して1-2になると、負けているけど追いかけているチームほうが試合に有利になると(松木安太郎さんが)いうあの件は本当か
分析データの取得先
データの取得は以下のサイトをスクレイピングして取得しました。データの充実したサイトなので眺めているだけでも面白い良質なサイトだと思います。
http://soccer-db.net
スクレイピングして作成したデータセットはcsv形式にしてここに置いてありますので適時ダウンロードしてお使い下さい。
分析
0.データの読み込み
Rを起動して上記のcsvファイルを読み込んでみます。
例えばgame_id=2の試合に着目しますと、この試合は1982年のワールドカップのもので3-3であったことがわかります。(timeが91以上の行があるのでこの試合は決勝トーナメントの試合でしょう。)
1. ゴールの生じやすい時間帯があるか
3. 0-2から1点返して1-2になると有利か
【番外】関数
まとめ
①先制点大事
②0-2とされたら1点返して1-2となっても、試合展開が有利になることなんて無い
③とはいえ、試合終了間際に最も点が入る確率が高いので最後まで応援する価値あり
以上、技術の話は皆無でしたがここら辺でおしまいにします。
取得したデータは、サッカーワールドカップの1982年~2010年の8大会ぶんの全試合のデータです。なぜ1982年からなのかというと、上記のサイトに1982年以降のデータしか無かったからです。
私はR(のXMLライブラリのreadHTMLTable関数)でスクレイピングしましたがRubyやPython等のLLを用いてやるのが一般的だと思います。お鮭の好きな人はselenium何かも使うみたいです。スクレイピングして作成したデータセットはcsv形式にしてここに置いてありますので適時ダウンロードしてお使い下さい。
分析
0.データの読み込み
Rを起動して上記のcsvファイルを読み込んでみます。
wc_data <- read.csv("wc_data.csv", stringsAsFactors=FALSE)
読み込んだwc_dataの先頭10行を見てみましょう。
head(wc_data, 10)
#library(gridExtra)
#grid.table(head(wc_data, 10)) #今回は便宜上、こちらのコマンドで以下の表を作成した
読み込んだデータのカラムの説明をします。
game_id | 試合を一意に決めるユニークなidです |
comp | 大会やリーグの名前を表すidですが、今回はワールドカップのデータのみを対象としたので"wc"だけになっています |
year | 試合の行われた年です |
team_category | ゴールを決めたのがHome, Awayのチームのどちらかを示すidでそれぞれHかAになっています。ワールドカップは開催国以外はAwayなので今回の分析にHかAかに特別は意味はありません |
score | その時点でのチームの合計得点です |
time | 得点時間を表しますが、ロスタイムの得点は45か90にしています |
例えばgame_id=2の試合に着目しますと、この試合は1982年のワールドカップのもので3-3であったことがわかります。(timeが91以上の行があるのでこの試合は決勝トーナメントの試合でしょう。)
ではこのデータセットを用いて順番に分析していきましょう。
1. ゴールの生じやすい時間帯があるか
time_range_df <- as.data.frame.table(table(get_timerange(wc_data$time)))
# ↑get_timerange関数の実装は後述
colnames(time_range_df) <- c("Time range", "Goal counts")
print(time_range_df)
#grid.table(time_range_df, show.rownames=F) #便宜上こっちで出力
上の表をグラフに図示してみましょう。
library(ggplot2)
qplot(get_timerange(wc_data$time),
xlab="時間帯",
ylab="ゴール数",
main="時間帯別ゴール数")
時間帯別に見ると、前半(1-45)より後半(46-90)のほうがゴール数が多いことがわかります。後半30分以降(76-90)の時間帯は最も得点されやすいのですね!例え試合に負けていたとしても、最後まで諦めずに試合を見続けた方がいいということがわかりましたね。
2. 先制点を取ったチームは勝利(引き分け含む)しやすいか
firstgoal_towin <- daply(.data=wc_data,
.variables="game_id",
.fun=check_1stgoal_towin)
# ↑check_1stgoal_towin関数の実装は後述
table(firstgoal_towin)
#grid.table(as.data.frame.table(table(firstgoal_towin))) #便宜上こっちで出力
qplot(firstgoal_towin,
xlab="先制ゴールした?",
ylab="勝利試合数",
main="先制ゴールは勝利しやすいか")
グラフを見ると、先制点を取ったチームは圧倒的に引き分け以上に持ち込める割合が高い(82%)ことがわかります。サッカーでは先制点を取ることがいかに大事なことかが明確になりました。
return1point_towin <- daply(.data=wc_data, .variables="game_id", .fun=check_21win) # ↑check_21win関数の実装は後述 table(return1point_towin) #grid.table(as.data.frame.table(table(return1point_towin))) #便宜上こっちで出力
上の表をグラフに図示してみましょう。
qplot(daply(.data=wc_data,
.variables="game_id",
.fun=check_21win),
xlab="0-2から1点返して、その後引き分け以上に持ち込めたか",
ylab="試合数",
main="0-2からの1-2は有利であるか?")
まず、0-2状態になってから1点返した試合事態が464試合中47試合しかなく少ない(4%)ことがわかりました。その47試合の中で負けていたチームが逆転勝利したもしくは同点で終わった試合が7試合しかありませんでした(15%)。
この割合が多いのか少ないのか比較対象がないとイマイチわからないと思うので、次は0-1状態になったチームがその後逆転勝利したもしくは同点で終わった試合の数を調べてみました。
before1point_towin <- daply(.data=wc_data,
.variables="game_id",
.fun=check_10win)
# ↑check_10winの関数定義は後述
table(before1point_towin)
#grid.table(as.data.frame.table(table(before1point_towin))) #便宜上こっちで出力
上の表をグラフに図示してみましょう。
qplot(.data=wc_data, .variables="game_id", .fun=check_10min),
xlab="0-1から引き分け以上に持ち込めたか",
ylab="試合数",
main="【比較】0-1からの")
0-0以外の試合が全部で424試合あり、その中で先に失点してもその後逆転勝利したもしくは同点で終わった試合が120試合ありました(28.3%)。
1-2から同点もしくは逆転になる(松木さん仮説)より、0-1から同点もしくは逆転になるほうが高い割合であることがわかりました。
最後に、1-2からの同点・逆転劇が0-1からの同点・逆転劇よりも有意に異なっているか(有意に高い比率で生じるかどうか)をフィッシャーの正確確率検定を用いて(片側)検定しました。
mat <- as.matrix(
merge(as.data.frame.table(table(daply(.data=wc_data, .variables="game_id", .fun=check_21win))),
as.data.frame.table(table(daply(.data=wc_data, .variables="game_id", .fun=check_10win))),
by.x="Var1", by.y="Var1"
)[,-1]
)
colnames(mat) <- c("1-2", "0-1"); rownames(mat) <- c("lose", "win"); mat <- mat[2:1,]
print(mat)
#grid.table(mat) #便宜上こっちで出力
fisher.test(mat, alternative="greater")
#
#Fisher's Exact Test for Count Data
#
#data: mat
#p-value = 0.9876
#alternative hypothesis: true odds ratio is greater than 1
#95 percent confidence interval:
# 0.1920788 Inf
#sample estimates:
#odds ratio
# 0.44398
「1-2からの同点・逆転劇」が「0-1からの同点・逆転劇」と同じ比率から生じるという帰無仮説に対して、p値が0.9876という有意差の無い結果となりました。つまり、98.76%の確率で「1-2からの同点・逆転劇」が「0-1からの同点・逆転劇」より起こりにくいということです。【番外】関数
#ゴールした時間をレンジ幅ごとに集約する関数
get_timerange <- function(x=wc_data$time, time_range=15){
x <- x[-which(is.na(x))]
tgroup <- seq(from=1, to=121, by=time_range)
if(tgroup[length(tgroup)]!=121){
tgroup <- c(tgroup, 121)
}
res_vec <- rep(NA, length=length(x))
for(i in 1:length(x)){
res_vec[i] <- tgroup[max(which((tgroup - x[i])<=0))]
}
conv_tbl <- data.frame(key=tgroup, value=paste0(tgroup, "~", tgroup[-1]-1), stringsAsFactors=F)
conv_tbl$value <- factor(conv_tbl$value, levels=paste0(tgroup, "~", tgroup[-1]-1)[-length(tgroup)])
res_vec2 <- apply(as.data.frame(res_vec), 1, function(y){conv_tbl[which(conv_tbl$key==y),"value"]})
return(res_vec2)
}
#0-1チームが、逆転勝利・引き分け結果になったか調べる関数
check_10win <- function(x){
if(nrow(x)==1){
if(is.na(x$score)){
return(NA)
}else{
#1-0
return(FALSE)
}
}else{
precursor <- x[1, "team_category"]
g_res <- unlist(dlply(.data=x, .variables="team_category", .fun=function(y){max(y$score)}))
if(length(g_res)==1){
#一方は0 score
return(FALSE)
}else{
if(length(which(max(g_res)==g_res))==2){
#同点
return(TRUE)
}else{
return(names(which(max(g_res)==g_res))!=precursor)
}
}
if(g_res[1]==g_res[2]){
return(TRUE)
}else if(precursor != names(which(max(g_res)== g_res))){
return(TRUE)
}else{
return(FALSE)
}
}
}
#0-2からの1-2チームが、逆転勝利・引き分け結果になったか調べる関数
check_21win <- function(x){
if(max(x$score)<2 || nrow(x) < 3){
#2とってない
return(NA)
}else{
if(x[2,"score"]==2 && x[3, "score"]==1){
#2-0からの2-1
loser <- as.character(x[3,"team_category"])
g_res <- unlist(dlply(.data=x, .variables="team_category", .fun=function(y){max(y$score)}))
if(g_res[1]==g_res[2]){
return(TRUE)
}else if(loser==names(which(max(g_res)==g_res))){
return(TRUE)
}else{
return(FALSE)
}
}else{
return(NA)
}
}
}
まとめ
①先制点大事
②0-2とされたら1点返して1-2となっても、試合展開が有利になることなんて無い
③とはいえ、試合終了間際に最も点が入る確率が高いので最後まで応援する価値あり
以上、技術の話は皆無でしたがここら辺でおしまいにします。