足球也能数据分析?来看看Python做的射门可视化

共 12958字,需浏览 26分钟

 ·

2021-07-09 21:28



大家好,欢迎来到 Crossin的编程教室 !


最近不少小伙伴都会熬夜看欧洲杯。有的人还喜欢在看球前预测谁赢谁输,并且会搬出一些历史战绩来佐证。但今年的欧洲杯相比起往年来说,可谓是冷门频爆,出乎意料。真的不知道,最终冠军会花落谁家~


说到分析历史战绩,其实数据分析应用在体育领域已经不是什么新鲜事了。之前我们就分享过好几次关于NBA中使用数据分析的案例:


今天我们就和大家分享一个足球方面的数据可视化案例:用 Python和 Matplotlib 绘制一个足球运动员的赛季射门图表。


先以C罗为例看下效果,跟老詹一样高龄,依然数据亮眼,真的令人佩服。



数据来源于下面两个网站,UnderstatFbref



链接:https://understat.com/

链接:https://fbref.com/en/


欧洲足球五大联赛,英超、意甲、西甲、德甲、法甲,数据都有。



先看一下射门数据的可视化,本质上和篮球的出手点图差不多,都是散点图类型。


导入相关的Python库。


import requests
from bs4 import BeautifulSoup as soup
import json
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl


从Understat网站爬取射门数据,使用BeautifulSoup、JSON和pandas解析和处理数据。


# 请求数据, C罗的ID为2371
url = 'https://understat.com/player/2371'
html = requests.get(url)

# 解析处理数据
parse_soup = soup(html.content, 'lxml')
scripts = parse_soup.find_all('script')
strings = scripts[3].string

ind_start = strings.index("('")+2
ind_end = strings.index("')")
json_data = strings[ind_start:ind_end]
json_data = json_data.encode('utf8').decode('unicode_escape')
data = json.loads(json_data)
print(data)
# 处理数据, 包含射门位置、预期进球、射门结果、赛季
x = []
y = []
xg = []
result = []
season = []
for i, _ in enumerate(data):
    for key in data[i]:
        if key == 'X':
            x.append(data[i][key])
        if key == 'Y':
            y.append(data[i][key])
        if key == 'xG':
            xg.append(data[i][key])
        if key == 'result':
            result.append(data[i][key])
        if key == 'season':
            season.append(data[i][key])
columns = ['X''Y''xG''Result''Season']
df_understat = pd.DataFrame([x, y, xg, result, season], index=columns)
df_understat = df_understat.T
df_understat = df_understat.apply(pd.to_numeric, errors='ignore')
# 得到最终的结果
print(df_understat)


此处的ID,通过查询球员名字可知



查询中国球员武磊,点击访问,在地址栏处,可以看到球员ID。


得到数据如下。



包含射门位置(x、y)、xG(预期进球)、射门结果、赛季。


其中x、y的坐标值为0~1之间,不适合在Matplotlib显示,所以选择放大100倍。


df_understat['X'] = df_understat['X'].apply(lambda x: x*100)
df_understat['Y'] = df_understat['Y'].apply(lambda x: x*100)
print(df_understat)


得到结果如下。



既然已经成功获取Understat网站的数据,就可以去获取Fbref网站的数据啦。


这里是球员的一些个人信息,以及赛季的平均数据。



比如全名、国家、位置、俱乐部、联赛、年龄、出生年份、上场时间、得分数据等等。


因为网页的数据是表格形式,所以直接使用pandas的read_html函数,解析表格爬取数据。


这个网站需要取消一下证书验证,要不然连接不成功。


# 全局取消证书验证

import ssl
ssl._create_default_https_context = ssl._create_unverified_context


获取球员的相关数据。


def readfromhtml(filepath):
    # 选择第二个表格
    df = pd.read_html(filepath)[0]
    column_lst = list(df.columns)
    for index in range(len(column_lst)):
        column_lst[index] = column_lst[index][1]
    df.columns = column_lst
    df.drop(df[df['Player'] == 'Player'].index, inplace=True)
    df = df.fillna('0')
    df.set_index('Rk', drop=True, inplace=True)
    try:
        df['Comp'] = df['Comp'].apply(lambda x: ' '.join(x.split()[1:]))
        df['Nation'] = df['Nation'].astype(str)
        df['Nation'] = df['Nation'].apply(lambda x: x.split()[-1])
    except:
        print('Error in uploading file:' + filepath)
    finally:
        df = df.apply(pd.to_numeric, errors='ignore')
        return df


# 获取2020-2021欧洲五大联赛球员数据
df_fbref = readfromhtml('https://fbref.com/en/comps/Big5/shooting/players/Big-5-European-Leagues-Stats')
print(df_fbref)


得到结果如下。




数据都已经准备好了,那么我们就可以将数据绘制到图表上。


# 安装
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple mplsoccer
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple highlight_text


先安装mplsoccer、highlight_text这两个Python库。



其中mplsoccer库可以自定义绘制足球场,无需我们自己绘制场地图。


想了解更多,可以访问它的GitHub地址。


https://github.com/andrewRowlinson/mplsoccer



初始化一些设置,画布背景色、字体颜色、默认字体,字体大小,此处选择中文字体。


from highlight_text import ax_text,fig_text
import mplsoccer

# 背景色
background = '#D6DBD9'
# 字体颜色
text_color = 'black'
mpl.rcParams['xtick.color'] = text_color
mpl.rcParams['ytick.color'] = text_color
mpl.rcParams['text.color'] = text_color
# 中文字体
mpl.rcParams['font.family'] = 'Songti SC'
mpl.rcParams['legend.fontsize'] = 12


新建一个画布。


# 新建画布
fig, ax = plt.subplots(figsize=(108))
# 关闭坐标轴
ax.axis('off')
# 背景色填充
fig.set_facecolor(background)
plt.show()


显示如下。



绘制19-20赛季,C罗的进球情况。


# 垂直方向半个足球场
pitch = mplsoccer.VerticalPitch(half=True, pitch_type='opta', line_zorder=3, pitch_color='grass')
# 图表大小
ax_opta1 = fig.add_axes((0.050.060.450.4))
ax_opta1.patch.set_facecolor(background)
pitch.draw(ax=ax_opta1)
plt.show()


通过设置mplsoccer的参数,绘制半个足球场。



果然,左下方有半个足球场。


将射门数据用散点图表示,分为进球得分和未成功进球得分两种情况。


# 2019-2020赛季, C罗射门位置散点图(未得分), 透明度0.6
df_fil = df_understat.loc[df_understat['Season'] == 2019]

pitch.scatter(df_fil[df_fil['Result'] != 'Goal']['X'], df_fil[df_fil['Result'] != 'Goal']['Y'],
              s=np.sqrt(df_fil[df_fil['Result'] != 'Goal']['xG'])*100, marker='o', alpha=0.6,
              edgecolor='black', facecolor='grey', ax=ax_opta1)
plt.show()


未得分射门散点图。



得分散点图。


# 2019-2020赛季, C罗射门位置散点图(得分), 透明度0.9
pitch.scatter(df_fil[df_fil['Result'] == 'Goal']['X'], df_fil[df_fil['Result'] == 'Goal']['Y'],
              s=np.sqrt(df_fil[df_fil['Result'] == 'Goal']['xG'])*100, marker='o', alpha=0.9,
              edgecolor='black', facecolor='#6778d0', ax=ax_opta1, label='Goal')

plt.show()


结果如下,失败的比成功的多。



这样,我们就将C罗在2019-2020赛季的所有射门点数据可视化出来了。


其中散点的大小,是预期进球的大小。


添加标签及图例,设置相应的位置、文字、字体等设置。


# 添加图例
ax_opta1.legend(loc='lower right').get_texts()[0].set_color("black")

# 文字信息
ax_opta1.text(3061'得分次数 : '+str(len(df_fil[df_fil['Result'] == 'Goal'])), weight='bold', size=11)
ax_opta1.text(3064f"预期进球 : {round(sum(df_fil['xG']),2)}", weight='bold', size=11)
ax_opta1.text(3058'射门次数 : '+str(len(df_fil)), weight='bold', size=11)
ax_opta1.text(9060'2019-20赛季', weight='bold', size=14)

plt.show()


成功添加附加信息。



显示赛季、xG、得分次数、射门次数信息。


同样将20-21赛季的数据绘制出来,放置在19-20赛季的右侧。


# 2020-2021赛季, C罗射门位置散点图
ax_opta2 = fig.add_axes((0.500.060.450.4))
ax_opta2.patch.set_facecolor(background)
pitch.draw(ax=ax_opta2)

# 根据条件, 筛选数据
df_fil = df_understat.loc[df_understat['Season'] == 2020]
# 未得分
pitch.scatter(df_fil[df_fil['Result'] != 'Goal']['X'], df_fil[df_fil['Result'] != 'Goal']['Y'],
              s=np.sqrt(df_fil[df_fil['Result']!='Goal']['xG'])*100, marker='o', alpha=0.6,
              edgecolor='black', facecolor='grey', ax=ax_opta2)
# 得分
pitch.scatter(df_fil[df_fil['Result']=='Goal']['X'], df_fil[df_fil['Result'] == 'Goal']['Y'],
              s=np.sqrt(df_fil[df_fil['Result'] == 'Goal']['xG'])*100, marker='o', alpha=0.9,
              edgecolor='black', facecolor='#6778d0', ax=ax_opta2, label='Goal')

# 添加图例, 文字信息
ax_opta2.legend(loc='lower right').get_texts()[0].set_color("black")

ax_opta2.text(3061'得分次数 : '+str(len(df_fil[df_fil['Result'] == 'Goal'])), weight='bold', size=11)
ax_opta2.text(3064f"预期进球 : {round(sum(df_fil['xG']),2)}", weight='bold', size=11)
ax_opta2.text(3058'射门次数 : '+str(len(df_fil)), weight='bold', size=11)
ax_opta2.text(9060'2020-21赛季', weight='bold', size=14)

plt.show()


结果如下。



C罗老当益壮啊,状态一点也没有下滑。


下面接着绘制所有球员的数据散点图,看看C罗的数据能在哪一档?


# 初始化
ax_scatter = fig.add_axes([0.520.570.40.35])
ax_scatter.patch.set_facecolor(background)
plt.show()


创建一个坐标轴。



首先对数据进行筛选,上场时间最少要有900s,而且位置为前锋此类的。


毕竟我们不能拿个守门员,跟C罗比数据吧,参考意义不大。


# 得到散点图的X, Y坐标值
no_90s = 10
df_fil = df_fbref[df_fbref['90s'] >= no_90s]
# 前锋位置
df_fil = df_fil[df_fil['Pos'].apply(lambda x: x in ['FW''MF,FW''FW,MF'])]

# 每90s预期进球和得分次数
x, y = (df_fil['xG']/df_fil['90s']).to_list(), (df_fil['Gls']/df_fil['90s']).to_list()

# 生成所有前锋位置, 数据散点图
ax_scatter.scatter(x, y, alpha=0.3, c='#EF8804')

plt.show()


所有球员每90s预期进球和得分次数的数据情况。



现在我们筛选出C罗的数据,在散点图上用不同的颜色及透明度来突出显示它。


# C罗的数据
df_player = df_fil[df_fil['Player'] == 'Cristiano Ronaldo']
ax_scatter.scatter(df_player['xG']/df_player['90s'], df_player['Gls']/df_player['90s'], c='blue')

plt.show()


结果如下。



可以看到C罗的数据还是比较高效的,虽不是第一,但也是前几的存在。


最后给散点图添加网格线,以及x轴和y轴标签。


# 添加网格线及标签
ax_scatter.grid(b=True, color='grey',
                linestyle='-.', linewidth=0.5,
                alpha=0.4)
ax_scatter.set_xlabel('每90秒的预期进球', fontdict={'fontsize'12'weight''bold''color': text_color})
ax_scatter.set_ylabel('每90秒得分', fontdict=dict(fontsize=12, weight='bold', color=text_color))

plt.show()


结果如下。



不愧是C罗,在2020-21赛季几乎每90秒就能进1颗球。


18年就已经有一个记录!C罗成历史第一位在90分钟内每分钟都有进球的球员


最后添加文本信息,包含标题,C罗的头像,场上位置、年龄、效力球队。


此处使用hightlight-text库,可以高亮文本。


# 添加C罗的头像
ax_player = fig.add_axes([0.030.530.250.45])
ax_player.axis('off')
im = plt.imread('ronaldo.png')
ax_player.imshow(im)

# 添加标题信息
fig_text(0.030.94"<克里斯蒂亚诺·罗纳尔多(C罗)> 赛季数据", weight='heavy', size=19, highlight_textprops=[{'color''blue'}])
fig_text(0.250.85'位置: <边锋>',weight='bold', size=15, highlight_textprops=[{'color':'#EF8804'}])
fig_text(0.250.81'年龄: <36>',weight='bold', size=15, highlight_textprops=[{'color':'red'}])

# 添加俱乐部logo
ax_team = fig.add_axes([0.270.550.150.15])
ax_team.axis('off')
im = plt.imread('FCJ.png')
ax_team.imshow(im)

# 添加备注
fig_text(0.070.03'本图制作者:<小F>   数据来源:Fbref.com、Understat.com',
        size=12, highlight_textprops=[{'color''#EF8804'}], weight='bold')
plt.show()


得到结果如下。



保存为图片。


# 保存为图片
plt.savefig('ronaldo_viz.png', dpi=300, facecolor=background)


看起来还不错哦。



好了,本期的分享就到此结束了,有兴趣的小伙伴可以自行去实践学习。


获取本文相关代码和数据,请点击下方公众号名片,回复关键字

足球


快给自己喜欢的足球运动员,也制作一个赛季数据面板吧!


如果文章对你有帮助,欢迎转发/点赞/收藏~

作者:小F
来源:法纳斯特


_往期文章推荐_

NBA投篮数据可视化,4行代码就能实现!




如需了解付费精品课程教学答疑服务
请在Crossin的编程教室内回复: 666

浏览 63
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报