翻译:老齐
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)。
过去十年里,梅西和罗纳尔多共获得11个金球奖(梅西6个,罗纳尔多5个),可以说统治当时的世界足球。这两位球员都被认为是有史以来最伟大的球员之一,所以也经常被拿来比较。
我们将分析这两位球员在2017-2018赛季西班牙联赛中的表现,这是罗纳尔多转会尤文图斯之前在西班牙的最后一个赛季。
首先,我们要下载有关数据集,下载地址是:https://figshare.com/collections/Soccer_match_event_dataset/4415000/5。然后关注如下部分:
下面是我们使用的Python库:
import json
import unicodedata
import numpy as np
import pandas as pd
然后从players.json
文件,根据id读入两个球员的数据:
再从teams.json
文件中根据id找到他们所在的球队:
接下来读入西班牙联赛的比赛数据:
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)
从前述数据中,分别得到皇家马德里和巴塞罗那两个俱乐部的比赛信息。
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对象。
barca_matches_df = pd.DataFrame(barca_matches)
real_matches_df = pd.DataFrame(real_matches)
barca_matches_df.head(2)
输出:
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。
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
中选择我们需要的时间标签:
将这些标签加入到DataFrame中作为一列。
def add_tag(tags, tag_id):
return tag_id in [tag['id'] for tag in tags]
messi_events_data_df.columns
输出:
Index(['eventId', 'subEventName', 'tags', 'playerId', 'positions', 'matchId',
'eventName', 'teamId', 'matchPeriod', 'eventSec', 'subEventId', 'id'],
dtype='object')
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)
输出:
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
把前面关于比赛的信息也合并过来。
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")
保存数据。
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赛季的比赛相关数据读取出来。
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)
输出:
match date
0 Barcelona - Real Sociedad, 1 - 0 2018-05-20
1 Levante - Barcelona, 5 - 4 2018-05-13
也保存一下。
barca_matches_dates_df.to_pickle('../data/barca_matches_dates_df.pkl')
real_matches_dates_df.to_pickle('../data/real_matches_dates_df.pkl')
在这部分,要分析梅西和罗纳尔多在球场上的表现数据了,我们会利用一些统计分析,以可视化的方式表现他们在球场上的状况。
球员的行为统计。
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)
左脚和右脚球比较。
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
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_df
和ronaldo_events_data_df
中,我们可以创建一个坐标写,两个坐标轴的范围都是[0, 100],表示占进攻一方场上的百分比,下面用可视化方式展示。
messi_events_data_df['positions'].head()
输出:
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)
messi_goals = messi_events_data_df[messi_events_data_df['goal'] == True]['positions']
p_messi = plot_events(messi_goals, 'Goals', 'red')
show(p_messi)
注:如果在调试环境中,会得到动态图示,这里显示的是截图效果。
现在我们知道了如何读取、构造和绘制数据,下面可以开始创建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/