手把手带你可视化分析NBA首轮球队表现及火勇对决前瞻!

NBA激战正酣,首轮除掘金和马刺的较量还没有结束外,其余对决都已经结束,本文将手把手带你可视化分析下各球队的首轮表现,同时将对次轮最受瞩目的火勇大战进行一个简单的前瞻分析!

通过本文,你将学会python中使用matplotlib库绘制柱状图、散点图、雷达图的相关知识!咱们开始吧!

1、数据获取及准备

我们首先获取各球队季后赛首轮的数据,网址是:https://www.basketball-reference.com/playoffs/NBA_2019.html。首先选择Playoffs:

然后,下拉到球队技术统计这里,导出csv,如果无法直接倒出,可以复制到txt文件里面(我就是这么干的):

然后,我们再把对手表现的数据导出,这样基础数据就准备好了:

好了,我们使用代码将数据进行读取,首先导入所需要的库:

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

将数据使用pandas进行读取,看看数据如何:

team_stats_df = pd.read_csv('data/nba1.txt',sep=',')
team_stats_df

同样,将对手表现的数据读入,但是这里列名需要修改一下:

team_opp_stats_df = pd.read_csv('data/nba2.txt',sep=',')
team_opp_stats_df.columns = ['Rk','Team','G'] + ['opp' + x for x in team_opp_stats_df.columns[3:]]

将两个表按照队名进行merge,这样oppPTS列,可以表示场均失分,这也是我们在对手表现数据里面主要关注的列:

mergedf = pd.merge(team_stats_df,team_opp_stats_df,on=['Team'])

2、柱状图分析球队场均得失分

绘制柱状图,需要使用的是plt.bar方法,其主要输入的有两个参数,一个是x轴的值,一个是高度,比如,我们绘制一下各球队首轮平均得分的柱状图:

pts = np.array(mergedf[['PTS','oppPTS']].values.tolist())
index = np.arange(pts.shape[0])
plt.bar(index,pts[:,1])
plt.show()

结果如下:

上面的图还是有几个问题的,首先,我们不希望X显示的是数值,我们希望展示各个球队名称的缩写,同时刻度可以不从0开始,这样看上去区分度不是十分的明显。

针对第一个问题,我们需要首先按顺序得到各个球队名称的缩写:

team_name = ['DEN','SA','GSW','PHI','LAC','BKN','POR','HOU','TOR','OKC','UTA','MIL','ORL','BOS','DET','IND']

随后,将x轴的刻度设置为相应的队名:

plt.xticks(index,team_name) # 设置x轴的刻度

针对第二个问题,我们需要限定y轴的范围,使用ylim方法:

plt.ylim(80,140) # y轴的范围

这样,我们得到了下面的图片:


上面这部分的完整代码如下:

team_name = ['DEN','SA','GSW','PHI','LAC','BKN','POR','HOU','TOR','OKC','UTA','MIL','ORL','BOS','DET','IND']
plt.ylabel('Score') # 设置y轴的label
plt.xlabel('Team') # 设置x轴的label
plt.xticks(index,team_name) # 设置x轴的刻度
plt.yticks(np.arange(80,141,10)) #y轴的刻度
plt.ylim(80,140) # y轴的范围
plt.bar(index,pts[:,0]) # 绘制直方图
plt.rcParams['figure.figsize'] = (12.0, 6.0) # 修改图片大小
plt.show()

此时,我们可能想要将球队的得失分表示出来,代码如下:

fig, ax = plt.subplots()
bar_width = 0.35
opacity = 0.4

rects1 = ax.bar(index, pts[:,0], bar_width,
                alpha=opacity, color='b',
                label='OFFENSE')

rects2 = ax.bar(index + bar_width, pts[:,1], bar_width,
                alpha=opacity, color='r',
                label='DEFENSE')

ax.set_xlabel('Team')
ax.set_ylabel('PTS')
ax.set_xticks(index + bar_width / 2)
ax.set_xticklabels(team_name)
ax.set_yticks(np.arange(80,141,10)) #y轴的刻度
ax.set_ylim(80,140) # y轴的范围
ax.legend()

plt.show()

绘制的结果如下:


3、散点图分析球队场均得失分

这一部分,我们使用散点图来分析球队场均得失分,使用plt.scatter函数,分别指定各个点的x和y值:

plt.scatter(pts[:,0], pts[:,1])
plt.xlabel('Offense PTS') # 设置x轴的label
plt.ylabel('Defense PTS') # 设置y轴的label
plt.show()

得到的效果如下:

简单的绘制散点图,什么都看不出来,此时,我们提出两个需求,首先,能不能将各个球队的简称放到各个点的旁边,这样我们就能清楚知道哪个点代表哪个球队。其次,能不能在图中添加两条直线,分别表示球队得分和失分的平均值?我们一个一个来解决。

首先,将球队标记加入到图中,我们使用plt.annotate函数,该函数需要传入三个参数,依次是注释文本内容,被注释的坐标点,注释文字的坐标位置:

plt.scatter(pts[:,0], pts[:,1])
plt.xlabel('Offense PTS') # 设置x轴的label
plt.ylabel('Defense PTS') # 设置y轴的label
for i in range(len(pts[:,0])):
    plt.annotate(team_name[i], xy = (pts[i,0], pts[i,1]), xytext = (pts[i,0]+0.1, pts[i,1]+0.1)) # 这里xy是需要标记
plt.show()

此时的效果就能满足我们的第一个需求:

对于第二个需求,我们使用plt.vlines和plt.hlines方法,对于plt.vlines方法,它是在图中绘制一条竖直线,因此需要指定x轴的坐标,同时需要指定y轴的起始坐标和结束坐标,对于plt.hlines方法,它是在图中绘制一条水平线,因此需要指定y轴的坐标,同时需要指定x轴的起始坐标和结束坐标:

offense_mean = np.mean(pts[:,0])
defense_mean = np.mean(pts[:,1])
print(offense_mean,defense_mean)
plt.scatter(pts[:,0], pts[:,1])
plt.vlines(np.mean(pts[:,0]), np.min(pts[:,1])-5,np.max(pts[:,1])+5,colors = "r", linestyles = "dashed")
plt.hlines(np.mean(pts[:,1]), np.min(pts[:,0])-5,np.max(pts[:,0])+5,colors = "r", linestyles = "dashed")
plt.ylim(np.min(pts[:,1])-5,np.max(pts[:,1])+5) # y轴的范围
plt.xlim(np.min(pts[:,0])-5,np.max(pts[:,0])+5) # x轴的范围
plt.xlabel('Offense PTS') # 设置x轴的label
plt.ylabel('Defense PTS') # 设置y轴的label
for i in range(len(pts[:,0])):
    plt.annotate(team_name[i], xy = (pts[i,0], pts[i,1]), xytext = (pts[i,0]+0.1, pts[i,1]+0.1)) # 这里xy是需要标记
plt.show()

结果如下:

基于上面的散点图,我们就能很快将十六支球队分为四个象限。
1)第一象限:进攻好,防守差,包含的球队有金州勇士、费城76人、洛杉矶快船、布鲁克林篮网
2)第二象限:进攻差,防守差,包含的球队有底特律活塞、俄克拉荷马雷霆、圣安东尼奥马刺
3)第三象限:进攻差,防守好,包含的球队有休斯顿火箭、奥兰多魔术、印第安纳步行者、犹他爵士、多伦多猛龙、波士顿凯尔特人
4)第四象限:进攻好,防守好,包含的球队有密尔沃基雄鹿、波特兰开拓者、丹佛掘金

哈哈,是不是跟你想象的不太一样,比如火箭怎么能说是进攻差的球队呢!因为他们的对手爵士是全联盟常规赛防守第一的球队,这种因素我们是无法通过这种图分析出来的。也就是说,数据不能表明一切!

4、基于雷达图的火勇对决前瞻

勇士在今天的比赛中,凭借杜兰特的神奇表现,击败快船与火箭会师西部半决赛,这也是半决赛对决中最受人瞩目的。那么,我们通过雷达图来分析下两队在季后赛首轮的表现吧。这里要说明的是,我们的数据是在勇船第六场之前获取的,因此只有勇士五场的数据。

我们先来看看两队的基础数据,我们挑选了场均得分,场均失分、命中率、三分命中率、篮板、助攻、抢断、盖帽、失误等九项数据:

two_team_stats = mergedf[(mergedf['Team']=='Houston Rockets') | (mergedf['Team']=='Golden State Warriors')]
hou_and_gsw_df = two_team_stats[['FG%','3P%','TRB','AST','STL','BLK','PTS','oppPTS','TOV']]
print(hou_and_gsw_df)

结果如下,其中2代表勇士,7代表火箭:

接下来,我们相通过雷达图来对比一下二者的这九项数据,matplotlib里面的雷达图貌似必须是统一刻度的,至少我目前还没有找到如何设置为不同刻度。因此,我们首先将二者的数据,用统一的标准,转换到0-1区间内:

hou_and_gsw_data[:,0] = hou_and_gsw_data[:,0] / 0.55
hou_and_gsw_data[:,1] = hou_and_gsw_data[:,1] / 0.55
hou_and_gsw_data[:,2] = hou_and_gsw_data[:,2] / 55
hou_and_gsw_data[:,3] = hou_and_gsw_data[:,3] / 40
hou_and_gsw_data[:,4] = hou_and_gsw_data[:,4] / 12
hou_and_gsw_data[:,5] = hou_and_gsw_data[:,5] / 12
hou_and_gsw_data[:,6] = hou_and_gsw_data[:,6] / 130
hou_and_gsw_data[:,7] = hou_and_gsw_data[:,7] / 130
hou_and_gsw_data[:,8] = hou_and_gsw_data[:,8] / 20

绘制雷达图,使用的是极坐标系,因此,我们需要设置一下:

fig=plt.figure(figsize=(14,8))
ax1=fig.add_subplot(1,1,1,polar=True) #设置第一个坐标轴为极坐标体系

随后,我们获取勇士和火箭的数据,并取得对应的数据标签:

gsw=hou_and_gsw_data[0,:] #提取GSW的信息
hou=hou_and_gsw_data[1,:] #提取HOU的信息
label=np.array([j for j in hou_and_gsw_df.columns]) #提取标签

随后,我们基于label的数量,对整圆进行切分,在切分的同时,我们需要加入切分后的第一个元素,以形成一个闭环:

angle = np.linspace(0, 2*np.pi, len(gsw), endpoint=False) #有几个label,就把整圆360°分成几份
angles = np.concatenate((angle, [angle[0]])) #增加第一个angle到所有angle里,以实现闭合
gsw = np.concatenate((gsw, [gsw[0]])) #增加gsw的第一项数据,以实现闭合
hou = np.concatenate((hou, [hou[0]])) #增加hou的第一项数据,以实现闭合

随后,便可以绘制我们的雷达图:

ax1.set_thetagrids(angles*180/np.pi, label, fontproperties="Microsoft Yahei") #设置网格标签
ax1.plot(angles,gsw,"o-",label='GSW')
ax1.plot(angles,hou,"o-",label='HOU')
ax1.set_theta_zero_location('NW') #设置极坐标0°位置
ax1.set_rlim(0,1) #设置显示的极径范围
ax1.fill(angles,gsw,facecolor='g', alpha=0.2) #填充颜色
ax1.set_title("HOU VS GSW",fontproperties="SimHei",fontsize=16) #设置标题
plt.legend(loc = 'best')
plt.show()

结果如下:

从雷达图来看,火箭除了在抢断和失分上占据一定优势外,其他各项数据均落后于勇士。不过还是刚才的那句话,数据不能体现一切,希望火勇双方能给我们带来一场精彩绝伦的较量。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351