老齐教室

分析梅西和罗纳尔多的比赛数据

作者:Adil Moujahid

翻译:老齐

与本书相关的图书推荐:《跟老齐学Python:数据分析》

本书是数据分析、机器学习、深度学习的起步读物,向学习者阐述了这些领域的最基本工具Numpy、Pandas以及数据可视化的使用和实现方法,其中贯穿着诸多实际案例。


2003年,迈克尔·刘易斯出版了《点球成金》,这是一本关于比利·比恩的书,他是奥克兰运动家棒球队队的总经理,他将统计分析应用于棒球,以确定和招募被低估的棒球运动员。通过数据的运用,比利·比恩所取得的胜利与薪资水平翻倍的球队一样多,并且从2000年到2003年连续4年参加了附加赛。

2011年,《点球成金》被改编成电影,由布拉德·皮特饰演比利·比恩。这本书和这部电影都很成功,极大地促进了利用数据提高比赛成绩的理念。对体育数据进行分析,进而提交竞技比赛成绩,通常称之为体育分析(Sport analytics)。

在棒球中,这项运动的性质使得收集大量关于比赛中的数据点比较容易。你可以从这个链接下载数据(http://www.seanlahman.com/baseball-archive/statistics/),其中包含了自1871年以来的关于球员和球队的比赛数据和其他统计数据。如果你对分析棒球数据感兴趣,你可以本微信公众号关注相关的文章。

然而,收集足球数据就变得复杂多了。在足球场上,22个队员无时不刻不在运动中,他们在球场上的位置和移动路径的可能可以说有无穷种,幸运的是,在这几年里,随着传感器技术和视频分析技术的进步,获得高质量的足球比赛数据也称为了可能,从而可以利用这些数据对比赛、球队和球员的情况进行分析。在本文中,我们将使用公开的足球数据集,分析梅西和罗纳尔多在2017-2018赛季中的有关数据,并为此开发一个网页,在网页上,我们以交互的方式来比较这两名球员在场上的位置。

非常感谢Luca Pappalardo博士(https://lucapappalardo.com/)和他的同事们,感谢他们为公众提供了这么伟大的数据集。

下面的动画,就是这个应用的简单演示,你可以到github仓库中得到源码(https://github.com/adilmoujahid/streamlit-messi-ronaldo)。

1. 数据准备

过去十年里,梅西和罗纳尔多共获得11个金球奖(梅西6个,罗纳尔多5个),可以说统治当时的世界足球。这两位球员都被认为是有史以来最伟大的球员之一,所以也经常被拿来比较。

我们将分析这两位球员在2017-2018赛季西班牙联赛中的表现,这是罗纳尔多转会尤文图斯之前在西班牙的最后一个赛季。

1.1 获取数据

首先,我们要下载有关数据集,下载地址是:https://figshare.com/collections/Soccer_match_event_dataset/4415000/5。然后关注如下部分:

  • matches/matches_Spain.json: 西班牙联赛2017-18赛季有关信息。
  • events/events_Spain.json: 西班牙联赛2017-18赛季中每场比赛的所有事件。
  • players.json: 参加了7场国际国内重要比赛的所有球员(意大利、西班牙、发过、德国和英超联赛,2018年世界杯,2016年欧洲杯)。
  • teams.json: 参加了7场国际国内重要比赛的所有球队。
  • tags2name.csv: 标记符和标记名称的对应关系

1.2 读入数据

下面是我们使用的Python库:

1
2
3
4
import json
import unicodedata
import numpy as np
import pandas as pd

然后从players.json文件,根据id读入两个球员的数据:

  • 梅西的id是3359
  • 罗纳尔多的id是3322

再从teams.json文件中根据id找到他们所在的球队:

  • 巴塞罗那的id是676
  • 皇家马德里的id是675

接下来读入西班牙联赛的比赛数据:

1
2
3
4
5
with open('../data/matches/matches_Spain.json') as json_file:
matches_spain_data = json.load(json_file)

with open('../data/events/events_Spain.json') as json_file:
events_spain_data = json.load(json_file)

1.3 构造数据

从前述数据中,分别得到皇家马德里和巴塞罗那两个俱乐部的比赛信息。

1
2
barca_matches  = [match for match in matches_spain_data if '676' in match['teamsData'].keys()]
real_matches = [match for match in matches_spain_data if '675' in match['teamsData'].keys()]

将它们转化为DataFrame对象。

1
2
barca_matches_df = pd.DataFrame(barca_matches)
real_matches_df = pd.DataFrame(real_matches)
1
barca_matches_df.head(2)

输出:

1
2
3
 	status 	roundId 	gameweek 	teamsData 	seasonId 	dateutc 	winner 	venue 	wyId 	label 	date 	referees 	duration 	competitionId
0 Played 4406122 38 {'676': {'scoreET': 0, 'coachId': 92894, 'side... 181144 2018-05-20 18:45:00 676 Camp Nou 2565922 Barcelona - Real Sociedad, 1 - 0 May 20, 2018 at 8:45:00 PM GMT+2 [{'refereeId': 398931, 'role': 'referee'}, {'r... Regular 795
1 Played 4406122 37 {'676': {'scoreET': 0, 'coachId': 92894, 'side... 181144 2018-05-13 18:45:00 695 Estadio Ciudad de Valencia 2565917 Levante - Barcelona, 5 - 4 May 13, 2018 at 8:45:00 PM GMT+2 [{'refereeId': 420995, 'role': 'referee'}, {'r... Regular 795

接下来,将梅西和罗纳尔多的有关数据,也生成为DataFrame。

1
2
3
4
5
6
7
8
9
10
11
12
13
messi_events_data = []
for event in events_spain_data:
if event['playerId'] == 3359:
messi_events_data.append(event)

messi_events_data_df = pd.DataFrame(messi_events_data)

ronaldo_events_data = []
for event in events_spain_data:
if event['playerId'] == 3322:
ronaldo_events_data.append(event)

ronaldo_events_data_df = pd.DataFrame(ronaldo_events_data)

tags2name.csv中选择我们需要的时间标签:

  • 101: Goal(进球)
  • 301: Assist(助攻)
  • 302: key Pass (关键传球)
  • 401: Left Foot(左脚)
  • 402: Right Foot(右脚)

将这些标签加入到DataFrame中作为一列。

1
2
3
4
def add_tag(tags, tag_id):
return tag_id in [tag['id'] for tag in tags]

messi_events_data_df.columns

输出:

1
2
3
Index(['eventId', 'subEventName', 'tags', 'playerId', 'positions', 'matchId',
'eventName', 'teamId', 'matchPeriod', 'eventSec', 'subEventId', 'id'],
dtype='object')
1
2
3
4
5
6
7
8
9
10
11
12
13
messi_events_data_df['goal'] = messi_events_data_df['tags'].apply(lambda x: add_tag(x, 101))
messi_events_data_df['assist'] = messi_events_data_df['tags'].apply(lambda x: add_tag(x, 301))
messi_events_data_df['key_pass'] = messi_events_data_df['tags'].apply(lambda x: add_tag(x, 302))
messi_events_data_df['left_foot'] = messi_events_data_df['tags'].apply(lambda x: add_tag(x, 401))
messi_events_data_df['right_foot'] = messi_events_data_df['tags'].apply(lambda x: add_tag(x, 402))

ronaldo_events_data_df['goal'] = ronaldo_events_data_df['tags'].apply(lambda x: add_tag(x, 101))
ronaldo_events_data_df['assist'] = ronaldo_events_data_df['tags'].apply(lambda x: add_tag(x, 301))
ronaldo_events_data_df['key_pass'] = ronaldo_events_data_df['tags'].apply(lambda x: add_tag(x, 302))
ronaldo_events_data_df['left_foot'] = ronaldo_events_data_df['tags'].apply(lambda x: add_tag(x, 401))
ronaldo_events_data_df['right_foot'] = ronaldo_events_data_df['tags'].apply(lambda x: add_tag(x, 402))

messi_events_data_df.head(2)

输出:

1
2
3
 	eventId 	subEventName 	tags 	playerId 	positions 	matchId 	eventName 	teamId 	matchPeriod 	eventSec 	subEventId 	id 	goal 	assist 	key_pass 	left_foot 	right_foot
0 8 Simple pass [{'id': 1801}] 3359 [{'y': 50, 'x': 50}, {'y': 50, 'x': 40}] 2565554 Pass 676 1H 1.012047 85 180465950 False False False False False
1 8 Simple pass [{'id': 1801}] 3359 [{'y': 64, 'x': 71}, {'y': 67, 'x': 54}] 2565554 Pass 676 1H 51.068905 85

把前面关于比赛的信息也合并过来。

1
2
3
messi_events_data_df = pd.merge(messi_events_data_df, barca_matches_df, left_on='matchId', right_on='wyId', copy=False, how="left")

ronaldo_events_data_df = pd.merge(ronaldo_events_data_df, real_matches_df, left_on='matchId', right_on='wyId', copy=False, how="left")

保存数据。

1
2
messi_events_data_df.to_pickle('../data/messi_events_data_df.pkl')
ronaldo_events_data_df.to_pickle('../data/ronaldo_events_data_df.pkl')

接下来,还要讲皇家马德里和巴塞罗那两只球队在2017-18赛季的比赛相关数据读取出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
barca_matches_dates_df = barca_matches_df[['label', 'date']].copy()
real_matches_dates_df = real_matches_df[['label', 'date']].copy()

barca_matches_dates_df['date'] = pd.to_datetime(barca_matches_df['date'], utc=True).dt.date
real_matches_dates_df['date'] = pd.to_datetime(real_matches_df['date'], utc=True).dt.date

#Change date to string
barca_matches_dates_df['date'] = barca_matches_dates_df['date'].apply(lambda x: x.strftime('%Y-%m-%d'))
real_matches_dates_df['date'] = real_matches_dates_df['date'].apply(lambda x: x.strftime('%Y-%m-%d'))

barca_matches_dates_df = barca_matches_dates_df.rename(columns={"label": "match"})
real_matches_dates_df = real_matches_dates_df.rename(columns={"label": "match"})

barca_matches_dates_df.head(2)

输出:

1
2
3
                               match 	date
0 Barcelona - Real Sociedad, 1 - 0 2018-05-20
1 Levante - Barcelona, 5 - 4 2018-05-13

也保存一下。

1
2
barca_matches_dates_df.to_pickle('../data/barca_matches_dates_df.pkl')
real_matches_dates_df.to_pickle('../data/real_matches_dates_df.pkl')

2. 分析数据

在这部分,要分析梅西和罗纳尔多在球场上的表现数据了,我们会利用一些统计分析,以可视化的方式表现他们在球场上的状况。

球员的行为统计。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
goals = [messi_events_data_df['goal'].sum(), ronaldo_events_data_df['goal'].sum()]
assists = [messi_events_data_df['assist'].sum(), ronaldo_events_data_df['assist'].sum()]
shots = [messi_events_data_df[messi_events_data_df['eventName'] == 'Shot'].count()['eventName'],
ronaldo_events_data_df[ronaldo_events_data_df['eventName'] == 'Shot'].count()['eventName']]
free_kicks = [messi_events_data_df[messi_events_data_df['subEventName'] == 'Free kick shot'].count()['subEventName'],
ronaldo_events_data_df[ronaldo_events_data_df['subEventName'] == 'Free kick shot'].count()['subEventName']]
passes = [messi_events_data_df[messi_events_data_df['eventName'] == 'Pass'].count()['eventName'],
ronaldo_events_data_df[ronaldo_events_data_df['eventName'] == 'Pass'].count()['eventName']]

stats_df = pd.DataFrame([goals, assists, shots, free_kicks, passes],
columns=['Messi', 'Ronaldo'],
index=['Goals', 'Assists', 'Shots', 'Free Kicks', 'Passes'])

print(stats_df)

左脚和右脚球比较。

1
2
3
4
5
6
7
8
9
messi_lf_goals = messi_events_data_df[messi_events_data_df['left_foot'] == True]['goal'].sum()
messi_rf_goals = messi_events_data_df[messi_events_data_df['right_foot'] == True]['goal'].sum()

print("Messi's goals with left foot: ", messi_lf_goals)
print("Messi's goals with right foot: ", messi_rf_goals)

# 输出:
Messi's goals with left foot: 32
Messi's goals with right foot: 2
1
2
3
4
5
6
7
8
9
ronaldo_lf_goals = ronaldo_events_data_df[ronaldo_events_data_df['left_foot'] == True]['goal'].sum()
ronaldo_rf_goals = ronaldo_events_data_df[ronaldo_events_data_df['right_foot'] == True]['goal'].sum()

print("Ronaldo's goals with left foot: ", ronaldo_lf_goals)
print("Ronaldo's goals with right foot: ", ronaldo_rf_goals)

# 输出:
Ronaldo's goals with left foot: 7
Ronaldo's goals with right foot: 14

两个球员在球场上的有关数据都保存在了messi_events_data_dfronaldo_events_data_df中,我们可以创建一个坐标写,两个坐标轴的范围都是[0, 100],表示占进攻一方场上的百分比,下面用可视化方式展示。

1
messi_events_data_df['positions'].head()

输出:

1
2
3
4
from plots import *
from bokeh.io import output_notebook
from bokeh.plotting import figure, show
output_notebook()

在这里,使用了bokeh可视化模块,涉及到两个简单的函数:

  • draw_pitch():绘制空图的函数
  • plot_events(player_events, event_name, plot_color):输入DataFrame的数据,实现数据可视化。

函数的源码,可以通过连接得到(https://github.com/adilmoujahid/streamlit-messi-ronaldo/blob/master/plots.py)

1
messi_goals = messi_events_data_df[messi_events_data_df['goal'] == True]['positions']
1
p_messi = plot_events(messi_goals, 'Goals', 'red')
1
show(p_messi)

注:如果在调试环境中,会得到动态图示,这里显示的是截图效果。

3. 创建web应用

现在我们知道了如何读取、构造和绘制数据,下面可以开始创建web应用。这个应用的目标是比较梅西和罗纳尔多的比赛,集中在进球、助攻、射门、任意球和传球。

每个动作类型都有一个标签,在选项卡中,我们将显示各种动作的统计数据和位置,分解按比赛计数。该应用还将有一个过滤器,可以使用它来选择左、右脚的动作。

开发中,利用一个开源的框架Streamlit(https://www.streamlit.io/),它是Python中的一个库,可以用`pip install`安装。Streamlit可以很容实现一个web应用,只需要使用Python,不需要HTML/CSS/JS等代码。

你可以在本项目的代码仓库中下载应用源码,然后在本地执行streamlit run app.py将项目运行起来,并在浏览器打开http://localhost:8501查看效果。

最后

本文,我们分析了梅西和罗纳尔多的有关数据,你其实可以参考本文内容,继续分析其他球员、球队或者每场比赛。

原文链接:http://adilmoujahid.com/posts/2020/06/streamlit-messi-ronaldo/

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

关注微信公众号,读文章、听课程,提升技能