如何运用Python绘制NBA投篮图表

    xiaoxiao2024-03-27  121

    我在本文中将介绍如何获取一个选手的投篮数据并通过matplotlib seaborn制成图表。

    In [1]: %matplotlib inline

    import requests

    importmatplotlib.pyplot as plt

    import pandas aspd

    import seabornas sns

    获取数据

    stats.nba.com获取的数据是非常简单的。虽然NBA没有提供公共的API ,我们实际上可以通过requests 库来访问NBAstats.nba.com所使用的APIGreg 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 HardenPlayerID

    现在让我们用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 Harden2014-15赛季常规赛期间的出手投篮。我们需要的数据在LOC_XLOC_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个单位在xy轴上表示一英尺。我们可以通过看在DataFrame里的第一个观察值验证证这一点。这次上篮是从右侧底角3点,与LOC_X 226值处距离22英尺。所以这次投篮大约是在离篮筐22.6英尺处发生的。现在我们知道了这一点,就可以在图中画出篮球场了。

    篮球场的尺寸可以从下面的图里找到。

    利用这些维度,我们可以将它们转换成适用于我们图表的尺寸,并使用 Matplotlib Patches画出来。我们将使用圆形,矩形和圆弧来绘制篮球场。现在来创建我们绘制篮球场的方程。

    注:虽然可以到使用Lines2D绘制线条,我发现使用Rectangles更方便(没有高度或宽度)。

    修正( 201584日):我在绘制外场线和半场弧时犯了一个错误。外场线高度从不正确的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()

    让我们用seabornjointplot来绘制几幅投篮图

    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导入OffsetImageOffsetImage可以使头像出现在图的右上角。现在,让我们像刚才一样绘制投篮图,但是这次我们将先绘制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)

    #把(xy)元组作为坐标信息,传入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)

    #将(xy)元组作为坐标信息传入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”微信公众号

    相关资源:敏捷开发V1.0.pptx
    最新回复(0)