我在本文中将介绍如何获取一个选手的投篮数据并通过matplotlib 和 seaborn制成图表。
In [1]: %matplotlib inline
import requests
importmatplotlib.pyplot as plt
import pandas aspd
import seabornas sns
获取数据
从stats.nba.com获取的数据是非常简单的。虽然NBA没有提供公共的API ,我们实际上可以通过requests 库来访问NBA给stats.nba.com所使用的API。Greg Reda的这篇博客(http://www.gregreda.com/2015/02/15/web-scraping-finding-the-api/)详细讲解了如何访问这个API(或者找到与此类似的任何网页的API )。我们将使用下面程序中提到的网址来获得James Harden的投篮图表数据。
In [2]:shot_chart_url ='http://stats.nba.com/stats/shotchartdetail?CFID=33&CFPAR;'\
'AMS=2014-15&ContextFilter;=&ContextMeasure;=FGA&DateFrom;=&D;'\
'ateTo=&GameID;=&GameSegment;=&LastNGames;=0&LeagueID;=00&Loca;'\
'tion=&MeasureType;=Base&Month;=0&OpponentTeamID;=0&Outcome;=&'\
'PaceAdjust=N&PerMode;=PerGame&Period;=0&PlayerID;=201935&Plu;'\
'sMinus=N&Position;=&Rank;=N&RookieYear;=&Season;=2014-15&Seas;'\
'onSegment=&SeasonType;=Regular+Season&TeamID;=0&VsConferenc;'\
'e=&VsDivision;=&mode;=Advanced&showDetails;=0&showShots;=1&sh;'\
'owZones=0'
上述网址提供给我们的JSON文件包含了我们想要的数据。还要注意的是这个链接包含了用于访问数据的各种API参数。在这个链接中PlayerID参数设置为201935 ,这是James Harden的PlayerID。
现在让我们用requests来获取我们想要的数据。
In [3]: #获得包含数据的网页
response =requests.get(shot_chart_url)
# 提取用于作为我们表格中列的名称的题头
essay-headers =response.json()['resultSets'][0]['essay-headers']
# 提取投篮数据
shots =response.json()['resultSets'][0]['rowSet']
用提取的投篮数据制作一个 pandas DataFrame
In [4]: shot_df = pd.DataFrame(shots,columns=essay-headers)
# 查看数据表的题头以及所有列
fromIPython.display import display
withpd.option_context('display.max_columns', None):
display(shot_df.head())
以上投篮数据包含了所有的James Harden在2014-15赛季常规赛期间的出手投篮。我们需要的数据在LOC_X和LOC_Y 里面。这些坐标值对应每一次出手投篮,然后我们可以把这些坐标绘制到一组表示篮球场的轴上。
绘制投篮数据图
让我们只是快速输出数据来看看它的样子。
In [5]: sns.set_style("white")
sns.set_color_codes()
plt.figure(figsize=(12,11))
plt.scatter(shot_df.LOC_X,shot_df.LOC_Y)
plt.show()
请注意,上述图表歪曲了数据。 x轴的值是实际对应值的倒数。让我们只绘制从右侧的投篮图来看看这个问题。
In [6]: right =shot_df[shot_df.SHOT_ZONE_AREA == "Right Side(R)"]
plt.figure(figsize=(12,11))
plt.scatter(right.LOC_X,right.LOC_Y)
plt.xlim(-300,300)
plt.ylim(-100,500)
plt.show()
图上我们可以看到的投篮数据是“右侧”的投篮,而观众的右侧实际上是篮筐的左侧。这是在创建我们最后投篮图时需要注意修改的。
画出篮球场
首先我们需要弄清楚如何在我们的图表中绘制篮球场。通过查看输出的第一个投篮图和数据,我们可以大致估算出篮筐的中心位于原点。我们还可以估计每10个单位在x或y轴上表示一英尺。我们可以通过看在DataFrame里的第一个观察值验证证这一点。这次上篮是从右侧底角3点,与LOC_X 为226值处距离22英尺。所以这次投篮大约是在离篮筐22.6英尺处发生的。现在我们知道了这一点,就可以在图中画出篮球场了。
篮球场的尺寸可以从下面的图里找到。
利用这些维度,我们可以将它们转换成适用于我们图表的尺寸,并使用 Matplotlib Patches画出来。我们将使用圆形,矩形和圆弧来绘制篮球场。现在来创建我们绘制篮球场的方程。
注:虽然可以到使用Lines2D绘制线条,我发现使用Rectangles更方便(没有高度或宽度)。
修正( 2015年8月4日):我在绘制外场线和半场弧时犯了一个错误。外场线高度从不正确的442.5改为470。中心球场圆弧的中心的y值从395改到422.5 。图表中的ylim值从( 395 , -47.5 )改变为( 422.5 , -47.5 )。
In [7]: from matplotlib.patches importCircle, Rectangle, Arc
defdraw_court(ax=None, color='black', lw=2, outer_lines=False):
# 如果没有可绘制的坐标值,则使用现有值
if ax is None:
ax = plt.gca()
# 绘制NBA篮球场的各个部分
# 绘制篮筐
# 篮筐直径18英寸,所以半径为9英寸
# 也就是我们系统里面相应的的7.5
hoop =Circle((0, 0), radius=7.5, linewidth=lw, color=color, fill=False)
# 绘制篮板
backboard =Rectangle((-30, -7.5), 60, -1, linewidth=lw, color=color)
# 场地线
# 绘制外场场地线, 宽=16ft, 高=19ft
outer_box = Rectangle((-80, -47.5), 160,190, linewidth=lw, color=color,
fill=False)
#绘制内场场地线, 宽=12ft, 高=19ft
inner_box= Rectangle((-60, -47.5), 120, 190, linewidth=lw, color=color,
fill=False)
# 绘制罚球弧顶部
top_free_throw= Arc((0, 142.5), 120, 120, theta1=0, theta2=180,
linewidth=lw,color=color, fill=False)
# 绘制发球弧底部
bottom_free_throw= Arc((0, 142.5), 120, 120, theta1=180, theta2=0,
linewidth=lw,color=color, linestyle='dashed')
# 限制区,是一个以篮筐为中心,半径为4ft的弧
restricted = Arc((0, 0), 80, 80, theta1=0,theta2=180, linewidth=lw,
color=color)
# Three pointline
# 三分线
# Create theside 3pt lines, they are 14ft long before they begin to arc
# 绘制两边的三分线,它们有14ft 长,之后开始成弧形
corner_three_a = Rectangle((-220, -47.5),0, 140, linewidth=lw,
color=color)
corner_three_b = Rectangle((220, -47.5), 0,140, linewidth=lw, color=color)
# 3pt arc -center of arc will be the hoop, arc is 23'9" away from hoop
# 三分线弧形部分 – 以篮筐为中心 半径为 23'9" 的弧
# I just playedaround with the theta values until they lined up with the threes
# 我仅仅通过调整 theta值让它们与两边衔接上
three_arc = Arc((0, 0), 475, 475,theta1=22, theta2=158, linewidth=lw,
color=color)
# 场中心
center_outer_arc = Arc((0, 422.5), 120,120, theta1=180, theta2=0,
linewidth=lw,color=color)
center_inner_arc = Arc((0, 422.5), 40, 40,theta1=180, theta2=0,
linewidth=lw,color=color)
# 列出轴上要画的球场的元素
court_elements = [hoop, backboard,outer_box, inner_box, top_free_throw,
bottom_free_throw,restricted, corner_three_a,
corner_three_b,three_arc, center_outer_arc,
center_inner_arc]
if outer_lines:
# 画出半场线,底线和边线
outer_lines = Rectangle((-250, -47.5),500, 470, linewidth=lw,
color=color,fill=False) court_elements.append(outer_lines)
# 把球场部分元素加到轴上
for element in court_elements:
ax.add_patch(element)
return ax
让我们来画出球场吧!
In [8]:plt.figure(figsize=(12,11))
draw_court(outer_lines=True)
plt.xlim(-300,300)
plt.ylim(-100,500)
plt.show()
绘制投篮图
下面让我们根据球场的数据来绘制投篮图。以下有两种方式可以调整x值:一种是把LOC_X的负倒数传入plt.scatter;另一种是把降序的值传入plt.xlim。我们的选择是后者。
In [9]:plt.figure(figsize=(12,11))
plt.scatter(shot_df.LOC_X, shot_df.LOC_Y)
draw_court(outer_lines=True)
#值沿轴从左到右降序排列
plt.xlim(300,-300)
plt.show()
让我们将投篮图上的篮圈移至顶部,与stats.nba.com上随着镜头与统计图表的方向一致。通过从y轴底部到顶部的降序排列的y值,我们实现这个操作。当我们这样做了,便不再需要来调整我们图上的x值。
In [10]: plt.figure(figsize=(12,11))
plt.scatter(shot_df.LOC_X,shot_df.LOC_Y)
draw_court()
#把图的范围调整为半场
plt.xlim(-250,250)
# 沿 y轴从底部到顶部,t值降序排列
# 设置顶部为篮筐的位置
plt.ylim(422.5,-47.5)
#除去轴刻度标签
# plt.tick_参数(标签底部=假, 标签左边=False)
plt.show()
让我们用seaborn的jointplot来绘制几幅投篮图
In [11]:# 创建jointplot
joint_shot_chart= sns.jointplot(shot_df.LOC_X, shot_df.LOC_Y, stat_func=None,
kind='scatter', space=0, alpha=0.5)
joint_shot_chart.fig.set_size_inches(12,11)
# 合成的投篮图有三个轴,第一个命名为ax_joint
# 是我们绘制场地和调整设置的轴
ax =joint_shot_chart.ax_joint
draw_court(ax)
#调整轴范围以便定位投篮图的方向
# 绘制半场图,设置顶部为篮筐的位置
ax.set_xlim(-250,250)
ax.set_ylim(422.5,-47.5)
#除去轴标签和刻度线
ax.set_xlabel('')
ax.set_ylabel('')
ax.tick_params(labelbottom='off',labelleft='off')
#添加标题
ax.set_title('JamesHarden FGA \n2014-15 Reg. Season',
y=1.2, fontsize=18)
#添加数据来源与作者
ax.text(-250,445,'DataSource: stats.nba.com'
'\nAuthor: Savvas Tjortjoglou(savvastjortjoglou.com)', fontsize=12)
plt.show()
获取选手头像
从stats.nba.com网站上获取Jame Harden的头像,放在我们的图里。他的头像是这个:在http://stats.nba.com/media/players/230x185/201935.png,我们可以通过url.requests中的urlretrieve来为我们获取头像
In [12]: import urllib.request
#第一个参数为头像的链接
#第二个参数是我们想要获取的头像
pic =urllib.request.urlretrieve("http://stats.nba.com/media/players/230x185/201935.png",
"201935.png")
#urlretrieve以元组的形式返回我们需要的头像,imread函数以多维数组形式读取图像,这样matplotlib可以绘制图像。
harden_pic =plt.imread(pic[0])
#绘制图像
plt.imshow(harden_pic)
plt.show()
现在要在jointplot上绘制Harden的脸,我们将从matplotlib.Offset导入OffsetImage,OffsetImage可以使头像出现在图的右上角。现在,让我们像刚才一样绘制投篮图,但是这次我们将先绘制KDE合成图,最后才添加头像。
In [13]: from matplotlib.offsetboximport OffsetImage
#绘制jointplot
#获取主KDE图的色图
#注意:我们可以从cmap中提取一种颜色用于图的边框和顶轴
cmap=plt.cm.YlOrRd_r
#n_levels 为主KDE图设置轮廓线的数量
joint_shot_chart= sns.jointplot(shot_df.LOC_X, shot_df.LOC_Y, stat_func=None,
kind='kde',space=0, color=cmap(0.1),
cmap=cmap,n_levels=50)
joint_shot_chart.fig.set_size_inches(12,11)
#一张合成图有3个轴,第一个是ax_joint,即球场线,也用于调整其它一些设置
ax =joint_shot_chart.ax_joint
draw_court(ax)
#调整轴的范围和方向角来绘制半场,
ax.set_xlim(-250,250)
ax.set_ylim(422.5,-47.5)
#除去轴标签和刻度线
ax.set_xlabel('')
ax.set_ylabel('')
ax.tick_params(labelbottom='off',labelleft='off')
#添加标题
ax.set_title('JamesHarden FGA \n2014-15 Reg. Season',
y=1.2, fontsize=18)
#添加数据源和作者信息
ax.text(-250,445,'DataSource: stats.nba.com'
'\nAuthor: Savvas Tjortjoglou(savvastjortjoglou.com)',
fontsize=12)
#在右上角添加Harden的照片
#首先上传头像,然后设置头像缩放比例
img =OffsetImage(harden_pic, zoom=0.6)
#把(x,y)元组作为坐标信息,传入set_offset函数
#把图放置在你设想的地方
img.set_offset((625,621))
#添加头像
ax.add_artist(img)
plt.show()
另一个用hexbins绘制的合成图
In [14]:#绘制合成图
cmap=plt.cm.gist_heat_r
joint_shot_chart= sns.jointplot(shot_df.LOC_X, shot_df.LOC_Y, stat_func=None,
kind='hex',space=0, color=cmap(.2), cmap=cmap)
joint_shot_chart.fig.set_size_inches(12,11)
#合成图有3个轴,第一个轴是ax_joint,即球场线
ax =joint_shot_chart.ax_joint
draw_court(ax)
#调整轴的范围和方向角来绘制半场
ax.set_xlim(-250,250)
ax.set_ylim(422.5,-47.5)
# 除去轴标签和刻度线
ax.set_xlabel('')
ax.set_ylabel('')
ax.tick_params(labelbottom='off',labelleft='off')
# 添加标题
ax.set_title('FGA2014-15 Reg. Season', y=1.2, fontsize=14)
#添加数据源和作者信息
ax.text(-250,445,'DataSource: stats.nba.com'
'\nAuthor: Savvas Tjortjoglou',fontsize=12)
# 把Harden的照片放在右上角
img =OffsetImage(harden_pic, zoom=0.6)
img.set_offset((625,621))
ax.add_artist(img)
plt.show()
修改:根据Ogi010的建议,使用matplotlib包中翠绿色(Viridis)色图重新创建KDE画板
In [15]:#导入含翠绿色色图
from option_dimport test_cm as viridis
#设置翠绿色的色图
plt.register_cmap(cmap=viridis)
cmap =plt.get_cmap(viridis.name)
#用n_levels设置主KDE图的轮廓线数量
joint_shot_chart= sns.jointplot(shot_df.LOC_X, shot_df.LOC_Y, stat_func=None,
kind='kde',space=0, color=cmap(0.1),
cmap=cmap,n_levels=50)
joint_shot_chart.fig.set_size_inches(12,11)
#合成图有3个轴,第一个轴是ax_joint,即球场线,也用来调整其它设置
ax =joint_shot_chart.ax_joint
draw_court(ax,color="white", lw=1)
#调整轴的范围和方向角来绘制半场
ax.set_xlim(-250,250)
ax.set_ylim(422.5,-47.5)
#除去轴标签和刻度
ax.set_xlabel('')
ax.set_ylabel('')
ax.tick_params(labelbottom='off',labelleft='off')
#添加标题
ax.set_title('JamesHarden FGA \n2014-15 Reg. Season',
y=1.2, fontsize=18)
#添加数据源和作者信息
ax.text(-250,445,'DataSource: stats.nba.com'
'\nAuthor: Savvas Tjortjoglou',fontsize=12)
#在右上角添加Harden的头像
#首先上传头像,然后调整头像大小以适合合成图
img =OffsetImage(harden_pic, zoom=0.6)
#将(x,y)元组作为坐标信息传入set_offset函数
# to place theplot where you want, I just played around
#把图像放置在你设想的位置
img.set_offset((625,621))
#添加头像
ax.add_artist(img)
plt.show()
In [16]: importsys
print('Python version:', sys.version_info)
import IPython
print('IPython version:', IPython.__version__)
print('Requests verstion', requests.__version__)
print('Urllib.requests version', urllib.request.__version__)
import matplotlib as mpl
print('Matplotlib version:', mpl.__version__)
print('Seaborn version:', sns.__version__)
print('Pandas version:', pd.__version__)
Python version: sys.version_info(major=3, minor=4, micro=3,releaselevel='final', serial=0)
IPython version:3.2.0
Requestsverstion 2.7.0
Urllib.requestsversion 3.4
Matplotlibversion: 1.4.3
Seaborn version:0.6.0
Pandas version:0.16.2
原文发布时间为:2015-08-18
本文来自云栖社区合作伙伴“大数据文摘”,了解相关信息可以关注“BigDataDigest”微信公众号