作者: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 jsonimport unicodedataimport numpy as npimport pandas as pd
然后从players.json
文件,根据id读入两个球员的数据:
再从teams.json
文件中根据id找到他们所在的球队:
接下来读入西班牙联赛的比赛数据:
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 1 H 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 1 H 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 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_df
和ronaldo_events_data_df
中,我们可以创建一个坐标写,两个坐标轴的范围都是[0, 100],表示占进攻一方场上的百分比,下面用可视化方式展示。
1 messi_events_data_df['positions' ].head()
输出:
1 2 3 4 from plots import *from bokeh.io import output_notebookfrom bokeh.plotting import figure, showoutput_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' )
注:如果在调试环境中,会得到动态图示,这里显示的是截图效果。
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/