見出し画像

FIFA20のサッカー選手データを統計的に分析してみた

最近はpandasなどを業務で使いながらデーターサイエンスの事などを勉強中です。なかなか仕事で使う機会がなくて積読になっていたデータ分析や機械学習の書籍などを最近は読んでいます。

今回は身につけたことをアウトプットするためにもサッカーネタにちなんでの投稿です。データ分析の知識が少しずつつけばつくほど、サッカーを更に面白く見れると思いモチベーションも上がっていきます。

欧州サッカーリーグも残り数節とリーグ終盤に差し掛かってきました。プレミアリーグではリバプールの優勝が決まっていたり、スペインのリーガもレアル・マドリーが後一勝すれば優勝が決定したりなど、大きな盛り上がりを見せています。

以前、自然言語処理系の勉強も兼ねてこちらの記事を投稿した際、それなりに評判も良かったので再度サッカーネタの投稿になります。

選手データの分析の経緯

サッカー関連ののデータ分析のことを調べていたら以下の記事を偶然見つけました。FIFA19というEAスポーツが開発したサッカーゲームの選手データを分析してみたという内容の記事です。

この選手データのデータセットを公開していたのがあのKaggleでもあることからKaggle内を検索していると、なんと最新のFIFA20のデータセットを見つけました。

こちらのデータセットですが、なんとFIFA15〜FIFA20の選手データまで格納されていました。約5年分もの選手の分析も可能になります。

今回は上記の記事の内容と同じようにFIFA20の選手データを用いてpandasを使用したデータ分析をしてみます。

データ分析の環境

分析で使用した環境は以下になります。使用するライブラリ類はデータ分析では定番のものです。

jupyter notebook上で実行はしていますが、特になくても問題はありません。最低限はpandasとグラフを表示するためのmatplotlibだけあれば問題ありません。(pandasを入れるにはnumpyは必須だったはずです)

seabornはヒートマップを表示するために使用しています。

OS: Mac
Lang: Python 3.8.0
Library:
 - numpy
 - jupyter
 - pandas
 - matplotlib
 - seaborn

グラフとヒートマップを表示するためのmatplotlibとseabornの詳しい使い方には関しては以下に載っています。

使用するデータ

ファイルをダウンロードしたらunzipなどでzipファイルを解凍してください。展開すると以下のcsvファイルが格納されています。

- players_15.csv
- players_16.csv
- players_17.csv
- players_18.csv
- players_19.csv
- players_20.csv
- teams_and_leagues.csv

今回使用するのはplayers_20.csvです。データの中身の項目を見てみます。以下がコードになります。

import pandas as pd

# ファイルのパス名を指定
df = pd.read_csv(
   'players_20.csv'
)

print(df.shape)
# (18278, 104)

for column in df.columns:
     print(column)

df.shapeにより(18278, 104)というタプルが返ってきます。選手数は18278も収録されています。104という値は一選手による項目数(カラム)です。

df.columnsの出力結果は以下になります。104ともなるとかなりの列数になります。

sofifa_id
player_url
short_name
long_name
age
dob
height_cm
weight_kg
nationality
club
overall
potential
value_eur
wage_eur
player_positions
preferred_foot
international_reputation
weak_foot
skill_moves
work_rate
body_type
real_face
release_clause_eur
player_tags
team_position
team_jersey_number
loaned_from
joined
contract_valid_until
nation_position
nation_jersey_number
pace
shooting
passing
dribbling
defending
physic
gk_diving
gk_handling
gk_kicking
gk_reflexes
gk_speed
gk_positioning
player_traits
attacking_crossing
attacking_finishing
attacking_heading_accuracy
attacking_short_passing
attacking_volleys
skill_dribbling
skill_curve
skill_fk_accuracy
skill_long_passing
skill_ball_control
movement_acceleration
movement_sprint_speed
movement_agility
movement_reactions
movement_balance
power_shot_power
power_jumping
power_stamina
power_strength
power_long_shots
mentality_aggression
mentality_interceptions
mentality_positioning
mentality_vision
mentality_penalties
mentality_composure
defending_marking
defending_standing_tackle
defending_sliding_tackle
goalkeeping_diving
goalkeeping_handling
goalkeeping_kicking
goalkeeping_positioning
goalkeeping_reflexes
ls
st
rs
lw
lf
cf
rf
rw
lam
cam
ram
lm
lcm
cm
rcm
rm
lwb
ldm
cdm
rdm
rwb
lb
lcb
cb
rcb
rb

overallが選手の総合値を指しています。高いと呼ばれる選手は基本的に80以上です。いわゆるビッククラブに所属している選手であれば80以上を超えているのがほとんどです。

potentialはその名の通りその選手のポテンシャルを示しており、成長して行けば最終的に到達する総合値になります。ここの値はゲーム内でトレーニングをなどをしていけば上げることも可能だそうです。

国別選手数TOP10を見てみる

それでは国籍ごとに登録されている選手のトップ10を見てみます。以下のコードを実行すればmatplotlibによるグラフも表示されます。

import numpy as np
import matplotlib.pyplot as plt

import pandas as pd

df = pd.read_csv(
   '../data/players_20.csv'
)

# nationalityで国籍の列を取り出す
nationalities = df['nationality']

# value_countsでそれぞれの要素数をカウントする
# これで国ごとの選手数が集計される
countries = list(nationalities.value_counts(sort=True).to_dict().keys())[:10]
numbers = nationalities.value_counts(sort=True).to_list()[:10]

left = np.array(countries)
height = np.array(numbers)


# matplotlibによるグラフの描写
# 画像サイズが小さくて収まらないのでfgureで画像サイズを大きくしています.

plt.figure(figsize=(10, 10))

plt.bar(left, height)
plt.title('Players count')
plt.grid(True)

plt.show()

結果は以下のグラフが表示されるはずです。イングランドの選手の登録数が圧倒的です。イングランド、ドイツ、スペイン、フランス、アルゼンチン、ブラジル、イタリアとサッカー強豪国の選手数は圧倒的です。

意外にも日本の登録選手数も多いです。

画像1

期待の若手選手を抱えている国TOP10を見てみる

次にこれから活躍するであろう期待の若手選手を多く抱えている上位10各国を見てみます。

選手の期待度をpotentialとします。「これから活躍する可能性がある=ポテンシャルが高い」として考え、potentialの数値が80以上の選手に絞ります。

また若手の条件を現時点で年齢が23歳以下と考え、東京五輪世代を対象とします。

以下のコードを実行してみましょう。df.queryで条件を指定することで、抽出する選手を絞ることができます。

age <= 23で23歳以下
80 <= potentialでポテンシャルが80以上

import numpy as np
import matplotlib.pyplot as plt

import pandas as pd

df = pd.read_csv(
   '../data/players_20.csv'
)

# 取り出す条件を絞る
young_talents = df.query('age <= 23 & 80 <= potential')

# nationalityで国籍の列を取り出す
nationalities = young_talents['nationality']

# 標準出力結果
print(nationalities.value_counts(sort=True).to_dict())

# value_countsでそれぞれの要素数をカウントする
# これで国ごとの選手数が集計される
countries = list(nationalities.value_counts(sort=True).to_dict().keys())[:10]
numbers = nationalities.value_counts(sort=True).to_list()[:10]

left = np.array(countries)
height = np.array(numbers)


# matplotlibによるグラフの描写
# 画像サイズが小さくて収まらないのでfgureで画像サイズを大きくしています.

plt.figure(figsize=(10, 10))

plt.bar(left, height)
plt.title('Players count')
plt.grid(True)

plt.show()

nationalities.value_counts(sort=True).to_dict()で全ての国別の結果が得られます。上記ではprint文で出力しています。標準出力での結果は以下になります。

{'France': 133, 'Spain': 124, 'England': 110, 'Argentina': 85, 'Germany': 62, 'Italy': 56, 'Brazil': 54, 'Netherlands': 45, 'Portugal': 43, 'Colombia': 28, 'Belgium': 22, 'Turkey': 21, 'Switzerland': 21, 'Nigeria': 20, 'Croatia': 20, 'Scotland': 19, 'Russia': 15, 'United States': 14, 'Ivory Coast': 13, 'Denmark': 13, 'Serbia': 12, 'Poland': 12, 'Mexico': 12, 'Uruguay': 11, 'Ukraine': 11, 'Senegal': 11, 'Austria': 10, 'Venezuela': 9, 'Ghana': 9, 'Romania': 9, 'Wales': 9, 'Mali': 8, 'Czech Republic': 7, 'Paraguay': 7, 'Morocco': 7, 'Chile': 6, 'Cameroon': 6, 'Kosovo': 6, 'Greece': 6, 'Norway': 6, 'Algeria': 5, 'Burkina Faso': 5, 'Japan': 5, 'Republic of Ireland': 4, 'Guinea': 4, 'Sweden': 4, 'Slovakia': 3, 'FYR Macedonia': 3, 'Korea Republic': 3, 'Australia': 2, 'Guinea Bissau': 2, 'New Zealand': 2, 'Georgia': 2, 'Gambia': 2, 'Luxembourg': 2, 'South Africa': 2, 'Slovenia': 2, 'Ecuador': 2, 'Canada': 2, 'Zambia': 1, 'Iran': 1, 'Angola': 1, 'Honduras': 1, 'Bulgaria': 1, 'Cyprus': 1, 'Israel': 1, 'Cape Verde': 1, 'Albania': 1, 'DR Congo': 1, 'Iceland': 1, 'Mauritania': 1, 'Panama': 1, 'Korea DPR': 1, 'Jamaica': 1, 'Northern Ireland': 1, 'Bolivia': 1, 'Hungary': 1, 'Peru': 1}

グラフにすると以下の画像が出力されるはずです。上位10各国に絞りました。

画像2

フランスが133人、スペインが124人という結果になりました。若手の人材大国であるフランスは流石です。

2018年のロシアW杯の優勝国でもありますし、特に若手で有名なのはパリ・サンジェルマン所属のムバッペやFCバルセロナ所属のデンベレではないでしょうか。(左がデンベレ、右がムバッペ)

画像3

画像参照元

どちらも現時点で23歳(デンベレ)、21歳(ムバッペ)と若手です。
フランスのリーグ(リーグ・アン)でも将来は有望株と呼ばれている選手はかなりいます。

デンベレを輩出したレンヌには17歳のカマビンガが特に注目されていますし、オリンピック・リヨンではまだ16歳であるチェルキが注目されています。

同じリヨンにも22歳のフセム・アワールがいたりと、レンヌとリヨンは良い若手選手を輩出しますね。

スペインの人材も流石といったところです。個人的に好きで一番に注目している若手はリキプッチです。

イニエスタ、シャビのようなテクニックとパスセンスも持ち合わせながらも、メッシのような重心の低いスルスルと抜けていくようなドリブルもあります。

今後がかなり楽しみな選手です。

日本の期待の若手選手達はどうか

日本人としてはどうしてもここが気になる所です。上記の結果としては5人とフランスやスペインのような強豪と比べると圧倒的に少ないのが現状です。

日本人の期待の若手選手達5人を見てみましょう。上記のコードを少し変えて以下コードを実行することで日本人選手に絞れます。

import pandas as pd

df = pd.read_csv(
   '../data/players_20.csv'
)

# 取り出す条件を絞る
young_talents = df.query('age <= 23 & 80 <= potential').query("nationality == 'Japan'")

print(young_talents)

 下記の部分が絞り込む条件になります。

age <= 23で23歳以下
80 <= potentialでポテンシャルが80以上
nationality == 'Japan'で国籍が日本

df.query('age <= 23 & 80 <= potential').query("nationality == 'Japan'")

print文で絞り込んだyoung_talentsを表示した結果が以下になります。見やすいようにjupyter notebookで実行した結果を画像で表示しています。

スクリーンショット 2020-07-16 17.06.51

日本人だと以下5名になりました。そのうち3人はA代表にも召集された経験があります。(堂安と富安に関してはスタメン)

・堂安 律
・冨安 健洋
・久保 建英
・安部 裕葵
・中村 敬斗

「安部 裕葵」も鹿島アントラーズからバルセロナBに移籍した20歳の若手選手です。登録ポジションは「LM, RM 」とされていますが、真ん中の偽9番的のポジションをやっており、今季4得点と好調でもあります。

スピードもテクニックもあり、周りとの連携からゴールを狙えます。時折、南米選手のようなトリッキーなテクニックも見せる選手です。

7月頭に怪我から復帰したそうなので、これからも活躍してほしい応援したい日本人選手の一人です。是非トップチームのバルセロナに昇格してほしいなと期待しています。

10代となると「久保 建英」と「中村 敬斗」の2選手があがりました。どちらも19歳(学年的には中村 敬斗が一つ上)であり、世代別代表で一緒にやっている選手でもあります。どちらも将来が楽しみな選手です。

画像5

画像参照元

総合値と相関がある項目をヒートマップで見てみる

次に選手の総合値であるoverallと相関関係が高い項目を分析してみます。これにより総合値であるoverallと関係性が強い値は何かがわかり、総合値が高い選手は何の数値に優れている傾向があるかがわかります。相関係数は0〜1の範囲を取る値です。

以下のcorr()メソッドで簡単にデータの項目間の相関係数を求めることができます。


import pandas as pd

df = pd.read_csv(
   '../data/players_20.csv'
)

print(df.corr())

ただし、選手によってはとある項目に値が無かったりします。こういうのを欠損値と呼びますが、一般的には全体の平均値や中央値で補完したりします。

他にも明らかに相関とは関係なさそうな項目あったり、項目数が104と多いことから、表示が見づらかい関係もあるため、今回はいくつかの項目を削ります。

またわかりやすくするためにもフィールドプレイヤー(GK以外のポジションのプレイヤー)に限定します。

主に以下の項目を削ります。

移籍金や年俸、市場価値などといった金銭的な項目

・value_eur(市場価値)
・wage_eur(年俸)
・release_clause_eur(移籍金)

実は相関度がそれなりに高かった値ですが、プレー面での能力値にはあまり関係なさそうという理由と、表示上の見やすさのためにこれらを削ります。

・gk_diving
・gk_handling
・gk_kicking
・gk_reflexes
・gk_speed
・gk_positioning

・goalkeeping_diving
・goalkeeping_handling
・goalkeeping_kicking
・goalkeeping_positioning
・goalkeeping_reflexes

今回はフィールドプレイヤーに限定しているため、GKに関連するパラメーターを削除します。

まずはdf[df["player_positions"] != 'GK']でGK以外のポジションの選手だけに絞り、field_playersという変数に格納します。

import numpy as np
import matplotlib.pyplot as plt

import pandas as pd

df = pd.read_csv(
   '../data/players_20.csv'
)

# 取り出す条件を絞る
field_players = df[df["player_positions"] != 'GK']

ちなみに以下の.isnull()メソッドで欠損値の情報が見れます。.isnull().sum()でそれぞれの項目の欠損値の数が分かります。

# 欠損値の項目がある場合はTrue,無い場合はFalseが表示される
df.isnull()

# 欠損値がある項目の合計をカウント
df.isnull().sum()

次に上記で指定した項目を削っていきます。.drop()メソッドの引数columnsに削除した項目をリストで与えることで、削除した項目以外の行を返します。

gk_drop_list = [
    'gk_diving',
    'gk_handling',
    'gk_kicking',
    'gk_reflexes',
    'gk_speed',
    'gk_positioning',
    'goalkeeping_diving',
    'goalkeeping_handling',
    'goalkeeping_kicking',
    'goalkeeping_positioning',
    'goalkeeping_reflexes'
]

money_drop_list = [
    'value_eur',
    'wage_eur',
    'release_clause_eur'
]

field_players = field_players.drop(columns=gk_drop_list)
field_players = field_players.drop(columns=money_drop_list)

これで準備は完了しました。以下でデータの項目間の相関係数を求めます。

corr = field_players.corr()
print(corr)

ヒートマップで相関係数を可視化してみましょう。seabornというライブラリを使用して、ヒートマップを可視化してみます。

# 項目があまりにも多いため、見やすいように画像サイズを大きくする
plt.figure(figsize=(30, 20)) 

# annotにTrueを入れることで相関係数の値を項目に表示する.
sns.heatmap(corr, square=True, annot=True)

最終的には以下のようなコードになります。

import numpy as np
import matplotlib.pyplot as plt

import pandas as pd

df = pd.read_csv(
   '../data/players_20.csv'
)

# 取り出す条件を絞る
field_players = df[df["player_positions"] != 'GK']

# 欠損値の項目がある場合はTrue,無い場合はFalseが表示される
print(df.isnull())

# 欠損値がある項目の合計をカウント
print(df.isnull().sum())

gk_drop_list = [
    'gk_diving',
    'gk_handling',
    'gk_kicking',
    'gk_reflexes',
    'gk_speed',
    'gk_positioning',
    'goalkeeping_diving',
    'goalkeeping_handling',
    'goalkeeping_kicking',
    'goalkeeping_positioning',
    'goalkeeping_reflexes'
]

money_drop_list = [
    'value_eur',
    'wage_eur',
    'release_clause_eur'
]

field_players = field_players.drop(columns=gk_drop_list)
field_players = field_players.drop(columns=money_drop_list)

corr = field_players.corr()
print(corr)

# 項目があまりにも多いため、見やすいように画像サイズを大きくする
plt.figure(figsize=(30, 20)) 

# annotにTrueを入れることで相関係数の値を項目に表示する.
sns.heatmap(corr, square=True, annot=True)

可視化されたヒートマップ画像が以下になります。項目に表示された値が相関係数を表し、0~1の値を取ります。1に近いほど相関係数が高く、-1に近いほど相関係数が低いことを表しています。

色では白に近い項目ほど相関係数が高いです。

画像6

print(corr.corr()['overall'].sort_values())

上記のコマンドで総合値がソートされた結果を見ることができます。かなり面白い結果となりました。

sofifa_id -0.798658
team_jersey_number -0.546885
nation_jersey_number -0.141673
height_cm -0.055810
contract_valid_until -0.011786
movement_sprint_speed 0.005828
pace 0.013602
movement_acceleration 0.021805
weight_kg 0.064670
defending_sliding_tackle 0.068619
defending_standing_tackle 0.103707
movement_balance 0.114008
power_jumping 0.144421
defending 0.147475
defending_marking 0.160436
mentality_interceptions 0.173033
power_strength 0.206832
movement_agility 0.219872
physic 0.353269
attacking_heading_accuracy 0.368015
weak_foot 0.384007
mentality_aggression 0.387621
attacking_finishing 0.406674
mentality_positioning 0.477681
shooting 0.497966
skill_dribbling 0.501639
mentality_penalties 0.502611
attacking_volleys 0.508177
skill_moves 0.508792
dribbling 0.538867
power_long_shots 0.574463
skill_curve 0.584624
attacking_crossing 0.591508
skill_fk_accuracy 0.618634
power_stamina 0.623124
mentality_vision 0.630172
power_shot_power 0.674139
potential 0.681277
age 0.700302
skill_ball_control 0.720106
passing 0.744920
international_reputation 0.796385
skill_long_passing 0.810839
attacking_short_passing 0.870070
mentality_composure 0.964283
movement_reactions 0.988081
overall 1.000000
Name: overall, dtype: float64

なんと総合値であるoverallと相関が強かったのはmovement_reactionsmentality_composureという項目でした。

こちらの記事でも同じよう結果が出ていますが、フィジカルだとかスピードなどの分かりやすい項目ではなく、改めて意外な結果が出たなと感じます。

movement_reactionsmentality_composureが何を指す値なのかを調べました。公式的には項目の詳細は未公表だそうですが、以下それぞれの資料が参考になりました。

Rections リアクション (movement_reactions)
・ボールへの反応速度。
・切り替え速度
Composure (mentality_composure)
・落ち着き
・相手に迫られても焦らず「落ち着いて」正確なパス、シュートができ流ようにするための値

相関が強い「反応速度」と「落ち着き」からわかること

これらの項目から分かるのは優秀な選手ほど、ボールや相手への反応が速く、プレッシャーをかけられても正確にボールをコントロールできるということがわかります。

いわゆる「球際」にも関連する要素なのかなと思います。よく日本サッカー界の課題だと言われている要素でもありますね。

解任されてしまいましたが、以前日本代表監督でもあったハリルホジッチも「デュエル」とよく表現してJリーグの球際の部分での課題を挙げていたりします。

こちらに現在はヴィッセル神戸でプレーしている酒井高徳選手のインタビュー記事がありますが、やっぱりブンデスリーガとJリーグでは球際の部分で明らかの差はあるそうです。

「Jに戻ってきたばっかりの頃、感じたのは球際の部分のギャップですね。正直、Jの試合よりもドイツの練習の方がプレッシャーがあるなと感じました。敵がボールを奪いに来る迫力はJと海外じゃ比べ物にならない。僕が1人かわしても、2人目や3人目で取られるし、『うわ、全然スペースないな』と思うけど、日本の場合は1人かわしたらスカスカに空いてきちゃう。文化なのか分からないけど、リーグとして違うなと思いましたね」

一部の記事を抜粋してしまっているので一応補足しておくと、記事序盤の部分では神戸では球際の部分にも日本人は意識してやってきているという説明もあります。日本全体でも球際の部分というのは見てて改善されてきているんじゃないかなとは思います。

その次に高かったskill_long_passingattacking_short_passingですが、どちらもロングパスとショートパスの精度を指す項目です。上記の反応速度と寄せられても冷静にボールを扱う技術があるからこそ、次の項目であるキック精度に大きく繋がる部分なんだろうなと思います。

まとめ

サッカーのデータはなかなか公開されていないので、データセットをKaggleで公開されていたのは非常にありがたいです。

分析対象が興味のある分野でもありますから、やっぱりpandasや統計だったり、データ分析系全般の勉強のモチベーションも上がります。

自分で書いたこの記事を読み返してみて思ったのはかなり個人的な見解をいくつか含んでしまいました。

pandaやデータ分析の話というより、サッカーの個人的な見解や仮説を述べているのが大きく占めてしまいました。

あくまでも分析で使用したデータはFIFAというEAスポーツが作ったゲーム上のデータでしかありませんので、必ずしも現実のサッカー選手達の能力と直接関係があるとは言えません。

あくまでもゲーム上のデータでしかありませんが、主観的には結構関係はしてそうだなと感じたりはします。

欧州リーグも残り数節と終盤です。特に久保建英選手や日本人選手の活躍に注目したいところです。

せっかくこれほどの選手データが集まったのもありますので、「この選手とこの選手はプレースタイルが似ているのか」などもできそうだなと思うのでやってみたいです。

今回使用したソースコードは以下のリポジトリで公開しています。

参考資料


この記事が気に入ったらサポートをしてみませんか?