python實現NBA球員出手位置圖表

2021-03-02 seqyuan

很多籃球迷對NBA球員的投籃出手位置感興趣,想要得到如下的這種圖:

經搜索發現網上已有相關資源,基本都來源於How to Create NBA Shot Charts in Python(http://savvastjortjoglou.com/nba-shot-sharts.html),但是現在按照這些教程都不能重現。

本文將介紹怎樣具體可操作的用python的matplotlib包實現繪製NBA球員投籃出手位置圖。

要做到這件事主要是解決兩個大問題:我們將練習到如下知識:

怎麼樣通過網頁分析獲取數據API

獲取網頁數據的基礎方式

繪製籃球半場圖

第一部分--獲取球員投籃位置數據

NBA官方並沒有提供公共的API方便我們訪問球員的shot log, Web Scraping 201: finding the API(http://www.gregreda.com/2015/02/15/web-scraping-finding-the-api/)這篇文章為我們提供了分析網頁尋找數據API的方法,我們要分析NBA球員shot log可拆解成以下步驟:

1.鎖定目標網站

目標網站:stats.nba.com

2.具體網頁對象

shot log所在的頁面標籤可能會有改變,有時不在很顯眼的位置,這也是很多教程失效的原因(只給了最後API的網址,沒有說這個網址是怎麼來的),所以這個得花時間找一下。

首先打開目標網站stats.nba.com,按下圖所示依次點擊 Player, SeeAllStats

按照圖中1、2順序點擊後會得到以下頁面

頁面表格是每個NBA球員的數據,表頭都是簡寫,通過圖示點擊 GLOSSARY我們得到表頭詳細信息,其中 FGA-FieldGoalsAttempted表示嘗試投籃的位置,表格中每個球員的 FGA列的數字都是可點擊的,我們按上圖所示點擊 JamesHarden的 FGA列數字,跳轉的結果是顯示了 JamesHarden2017-18常規賽的 HexHap

ShotPlot

ShotZones

好了,可以結束了...

等等,我們的目的不是簡單得到 ShotPlot,而是要 練習一些知識,所以,繼續

點擊 JamesHarden的 FGA數據,跳轉後的頁面除了以上3個圖還包括以下這個表格

這個表格的內容結合 ShotPlot的圖,可以確定,要找的 具體網頁對象應該是這個頁面了,但是表格中並沒有直接給出投籃位置信息,這個網頁訪問的API應該包括這些信息,所以我們進入下面的步驟

3.分析shot log API

我們以Chrome瀏覽器為例,在上一步找到的 具體網頁對象頁面打開瀏覽器的開發者選項(更多工具->開發者工具),然後按F5鍵,刷新頁面,你將得到如下頁面

我們按上圖紅色數字標註,依次點擊,選擇 Network,然後點擊 XHR進行過濾, XHR是XMLHttpRequest的簡寫 - 這是一種用來獲取XML或JSON數據的請求類型。經 XHR篩選後表格中的有幾個條目,紅色數字標註為3的既是我們將要查找的shot log API, Preview標籤中包括:

resource - 請求的名稱 shotchartdetail。

parameters - 請求參數,提交給API的請求參數,我們可以理解成SQL語言的條件語句,例如賽季、球員ID等等,我們改變URL中的參數就能得到不同的數據

resultSets - 請求得到的數據集,包含兩個表格。仔細看表頭(headers)第一個表格包含我們想要的shot log信息(LOCX,LOCY)。

與 Preview並列的 Headers標籤包含:

通過API獲取感興趣球員的shot log數據

4.通過API獲取感興趣球員的shot log數據

上一步得到了本賽季MVP熱門人選 JamesHardenshot log的Request URL和Requset Headers,下面我們要做的是通過python代碼獲取shot log數據,以下是代碼

   import requests

   import pandas as pd

   shot_chart_url = 'http://stats.nba.com/stats/shotchartdetail?AheadBehind=&'\

                    'CFID=&CFPARAMS=&ClutchTime=&Conference=&ContextFilter=&ContextMeasure=FGA'\

                    '&DateFrom=&DateTo=&Division=&EndPeriod=10&EndRange=28800&GROUP_ID=&GameEventID='\

                    '&GameID=&GameSegment=&GroupID=&GroupMode=&GroupQuantity=5&LastNGames=0&LeagueID=00'\

                    '&Location=&Month=0&OnOff=&OpponentTeamID=0&Outcome=&PORound=0&Period=0&PlayerID={PlayerID}'\

                    '&PlayerID1=&PlayerID2=&PlayerID3=&PlayerID4=&PlayerID5=&PlayerPosition=&PointDiff=&Position='\

                    '&RangeType=0&RookieYear=&Season={Season}&SeasonSegment=&SeasonType={SeasonType}'\

                    '&ShotClockRange=&StartPeriod=1&StartRange=0&StarterBench=&TeamID=0&VsConference='\

                    '&VsDivision=&VsPlayerID1=&VsPlayerID2=&VsPlayerID3=&VsPlayerID4=&VsPlayerID5='\

                    '&VsTeamID='.format(PlayerID=201935,Season='2017-18',SeasonType='Regular+Season')

   header = { 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)'\

                             ' Chrome/62.0.3202.94 Safari/537.36'}

   response = requests.get(shot_chart_url,headers=header)

   # headers是模擬瀏覽器訪問行為,現在沒有這一項獲取不到數據

   headers = response.json()['resultSets'][0]['headers']

   shots = response.json()['resultSets'][0]['rowSet']

   shot_df = pd.DataFrame(shots, columns=headers)

   # View the head of the DataFrame and all its columns

   from IPython.display import display

   with pd.option_context('display.max_columns', None):

       display(shot_df.head())

   # Or

   #shot_df.head().to_excel('outfile.xls',index=True,header=True)

我們得到的pandas DataFrame: shot_df,表頭及前3行數據展示如下:

shot_chart_url其中PlayerID、Season、SeasonType三項是可變參數,如果想獲得其他球員的PlayerID可以登錄nba.com/players搜索感興趣球員的名字,如下

點擊搜索結果,跳轉到頁面的網址最後一項既是PlayerID,例如: http://www.nba.com/players/giannis/antetokounmpo/203507中的 203507即是字母哥的PlayerID。

我們這一步得到的 shot_df包含了James Harden在2017-18賽季常規賽目前為止(20180219全明星賽)所有投籃嘗試。我們需要的數據為 LOC_X和 LOC_Y兩列,這些是每次投籃嘗試的坐標值,然後可以將這些坐標值繪製到代表籃球場的坐標軸上,當然我們可能還需要 EVENT_TYPE列,來區分投籃是否投進。

第二部分--繪製球員shot log到球場圖

關於這一部分,How to Create NBA Shot Charts in Python已經做了非常優秀的工作,我們會延續其框架,並對代碼做修改以達到更好的適用性。

首先我們對上一步得到的James Harden的shot log LOC_X和 LOC_Y進行快速繪圖,看其X、Y是怎麼定義的。

import matplotlib.pyplot as plt

fig = plt.figure(figsize=(4.3,4))

ax = fig.add_subplot(111)

ax.scatter(shot_df.LOC_X, shot_df.LOC_Y)

plt.show()

通過快速預覽圖我們對 LOC_X和 LOC_Y有了一個大概的認識,有一點需要注意: LOC_X其實是觀眾視野從中場面向籃筐來說的, LOC_X是正值則在籃筐的左邊。所以最終繪圖時需要按以下代碼做調整,我們以shot log中Right Side(R) 投籃區域(投籃區域劃分請參考前文 ShotZones圖)的出手作為示例說明。

right_shot_df = shot_df[shot_df.SHOT_ZONE_AREA == "Right Side(R)"]

other_shot_df = shot_df[~(shot_df.SHOT_ZONE_AREA == "Right Side(R)")]

fig = plt.figure(figsize=(4.3,4))

ax = fig.add_subplot(111)

ax.scatter(right_shot_df.LOC_X, right_shot_df.LOC_Y, s=1, c='red', label='Right Side(R)')

ax.scatter(other_shot_df.LOC_X, other_shot_df.LOC_Y, s=1, c='blue', label='Other AREA')

ax.set_ylim(top=-50,bottom=580)

ax.legend()

plt.show()

畫籃球半場圖

通過對 LOC_X和 LOC_Y數據的快速畫圖,我們大概知道了籃筐的位置大概就是 LOC_X和 LOC_Y的原點。知道了這一點,我們結合籃球半場的具體尺寸 (下圖)和比例就可以畫出籃球半場圖了。

通過上圖我們知道了籃球場寬度是 50FT,轉換成 INCH是 600IN,籃球場長 94FT,轉換成 INCH是 1128IN,再結合我們上一步畫出的投籃點快速預覽圖,通過對很多球員的 LOC_Y為0時, LOC_X與 SHOT_DISTANCE,我們能夠推測出 LOC_X和 LOC_Y的單位與 IN的換算大概為10/12。

畫圖函數如下:

import matplotlib.pyplot as plt

from matplotlib.patches import Rectangle, Arc,Wedge

class data_linewidth_plot():

   def __init__(self,**kwargs):

       self.ax = kwargs.pop("ax", plt.gca())

       self.lw_data = kwargs.pop("linewidth", 1)

       self.lw = 1

       #self.ax.figure.canvas.draw()

       self.ppd=72./fig.dpi

       self.trans = self.ax.transData.transform

       self._resize()

   def _resize(self):

       lw =  ((self.trans((1,self.lw_data))-self.trans((0,0)))*self.ppd)[1]

       self.lw = lw

def draw_half_court(ax=None, unit=1):

   lw = unit * 2 #line width

   color = 'k'

   # 精確line width

   court_lw = data_linewidth_plot(ax = ax,linewidth = lw).lw

   ## Create the basketball hoop

   #籃筐直徑(內徑)是18IN.,我們設置半徑為9.2IN刨除line width 0.2IN,正好為籃筐半徑

   hoop = Wedge((0, 0), unit * 9.2, 0, 360, width=unit * 0.2, color='#767676')

   hoop_neck = Rectangle((unit * -2, unit * -15 ), unit * 4, unit * 6, linewidth=None, color='#767676')

   ## Create backboard

   #Rectangle, left lower at xy = (x, y) with specified width, height and rotation angle

   backboard = Rectangle((unit * -36, unit * -15 ), unit * 72, court_lw, linewidth=None, color='#767676')

   # List of the court elements to be plotted onto the axes

   ## Restricted Zone, it is an arc with 4ft radius from center of the hoop

   restricted = Arc((0, 0), 96*unit+court_lw, 96*unit+court_lw, theta1=0, theta2=180,linewidth=court_lw, color='#767676', fill=False)

   restricted_left = Rectangle((-48*unit-court_lw/2, unit * -15 ), 0, unit * 17, linewidth=court_lw, color='#767676')

   restricted_right = Rectangle((unit*48+court_lw/2, unit * -15 ), 0, unit * 17, linewidth=court_lw, color='#767676')

   # Create free throw top arc  罰球線弧頂

   top_arc_diameter = 6 * 12 * 2*unit - court_lw

   top_free_throw = Arc((0, unit * 164), top_arc_diameter, top_arc_diameter, theta1=0, theta2=180,linewidth=court_lw, color=color, fill=False)

   # Create free throw bottom arc 罰球底弧

   bottom_free_throw = Arc((0, unit * 164), top_arc_diameter, top_arc_diameter, theta1=180, theta2=0,linewidth=abs(court_lw), color=color, linestyle='dashed', fill=False)

   # Create the outer box 0f the paint, width=16ft outside , height=18ft 10in

   outer_box = Rectangle((court_lw/2 - unit*96, -court_lw/2 - unit*63), 192*unit-court_lw, 230*unit-court_lw, linewidth=court_lw, color=color, fill=False)

   # Create the inner box of the paint, widt=12ft, height=height=18ft 10in

   inner_box = Rectangle((court_lw/2 - unit*72, -court_lw/2 - unit*63), 144*unit-court_lw, 230*unit-court_lw, linewidth=court_lw, color=color, fill=False)

   ## Three point line

   # Create the side 3pt lines, they are 14ft long before they begin to arc

   corner_three_left = Rectangle((-264*unit+court_lw/2, -63*unit-court_lw/2), 0, 14*12*unit +court_lw, linewidth=court_lw, color=color)

   corner_three_right = Rectangle((264*unit-court_lw/2, -63*unit-court_lw/2), 0, 14*12*unit +court_lw, linewidth=court_lw, color=color)

   # 3pt arc - center of arc will be the hoop, arc is 23'9" away from hoop

   # I just played around with the theta values until they lined up with the

   # threes

   three_diameter = (23 * 12 + 9) * 2*unit - court_lw

   three_arc = Arc((0, 0), three_diameter, three_diameter, theta1=21.9, theta2=158, linewidth=court_lw, color=color)

   # Center Court

   center_outer_arc = Arc((0, (94*12/2-63)*unit), 48*unit+court_lw, 48*unit+court_lw, theta1=180, theta2=0,linewidth=court_lw, color=color)

   center_inner_arc = Arc((0, (94*12/2-63)*unit), 144*unit-court_lw, 144*unit-court_lw, theta1=180, theta2=0,linewidth=court_lw, color=color)

   # Draw the half court line, baseline and side out bound lines

   outer_lines = Rectangle((-25*12*unit - court_lw/2, -63*unit-court_lw/2), 50*12*unit+court_lw, 94/2*12*unit + court_lw, linewidth=court_lw, color=color, fill=False)

   #2 IN. WIDE BY 3 FT. DEEP, 28 FT. INSIDE, 3FT. extenf onto court

   court_elements = [hoop_neck, backboard, restricted, restricted_left, restricted_right,top_free_throw,bottom_free_throw,

                       outer_box,inner_box, corner_three_left,corner_three_right,three_arc,center_outer_arc,center_inner_arc,outer_lines]

   # Add the court elements onto the axes

   for element in court_elements:

       ax.add_patch(element)

畫圖:

fig = plt.figure(figsize=(9,9))

ax = fig.add_subplot(111,aspect='equal')

ax.set_xlim(-330,330)

ax.set_ylim(-200,600)

draw_half_court(ax=ax)

plt.show()

添加上投籃出手位置數據

fig = plt.figure(figsize=(9,8))

ax = fig.add_subplot(111,aspect='equal')

ax.set_xlim(-330,330)

ax.set_ylim(top= -100,bottom = 500)

draw_half_court(ax=ax,unit=10/12)

df_missed = shot_df[shot_df.EVENT_TYPE=='Missed Shot'][['LOC_X','LOC_Y']]

ax.scatter(df_missed.LOC_X, df_missed.LOC_Y,s=2,color='r',label = 'Missed Shot',alpha=0.5)

df_made = shot_df[shot_df.EVENT_TYPE=='Made Shot'][['LOC_X','LOC_Y']]

ax.scatter(df_made.LOC_X, df_made.LOC_Y,s=2,color='b',label = 'Made Shot',alpha=0.5)

legend = ax.legend(bbox_to_anchor=(0.49, 0.13), loc=2, borderaxespad=0.,prop={'size':8},ncol=2,frameon=False)

plt.axis('off')

FG = "%.1f" % ((df_made.shape[0]/shot_df.shape[0])*100)

ax.text(-250,440,'FG%:{0}%({1}-{2})'.format(FG,df_made.shape[0],shot_df.shape[0]), fontsize=8)

ax.text(-250,-63,'FGA for Harden, James during the 2017-18 Regular Season'.format(FG,df_made.shape[0],shot_df.shape[0]), fontsize=8)

plt.show()

文中圖片看不清的可以訪問www.seqyuan.com查看原文微信公眾號

下圖是我的微信公眾號,會寫一些生物信息及編程相關的文章,歡迎關注。

相關焦點

  • Python告訴你NBA球星都喜歡在哪個位置出手?
    原圖、更多球員生成的結果及完整代碼,見以下網址:NBA出手點統計代碼:https://gitee.com/crossin/snippet/tree/master/nba-fpa幾十位球星生成圖:https://pan.baidu.com/s/1JxMsICm_-_uTMLIgN2Sznw 提取碼: jbpw
  • Python爬取NBA虎撲球員數據
    受害者地址https://nba.hupu.com/stats/players本文知識點:環境介紹:python 3.6pycharmrequestscsv爬蟲案例的一般步驟/td[2]/a/text()').get() # 球員 team = tr.xpath('./td[3]/a/text()').get() # 球隊 score = tr.xpath('./td[4]/text()').get() # 得分 hit_shot = tr.xpath('.
  • NBA球星都喜歡在哪個位置出手?看見科比的統計圖我驚呆了
    原圖、更多球員生成的結果及完整代碼,見以下網址:NBA出手點統計代碼:https://gitee.com/crossin/snippet/tree/master/nba-fpa幾十位球星生成圖相關的接口和文檔你可以從這個項目裡查看:nba_py - stats.nba.com API for pythonhttps://github.com/seemethere/nba_py/通過以下接口,我們可以獲取某個球員在指定賽季的投籃詳細數據:https://stats.nba.com/stats/shotchartdetail?
  • 用Python爬取NBA官網球員數據,並自動在Excel中可視化!
    目標URL如下:URL1:http://nba.hupu.com/players/URL2(此處以湖人球隊為例):https://nba.hupu.com/players/lakersURL3(此處以詹姆斯為例):https://nba.hupu.com/players/lebronjames-650.html
  • Python實現視頻裁剪添加水印功能
    python目前我們實現的是將單獨一個視頻進行裁剪。本次我們將目前視頻截取一小段內容,並為其添加一個水印圖片。我們使用python的moviepy類庫,首先安裝moviepy類庫,使用pip進行安裝,命令如下:pip install moviepy簡短的介紹一下,MoviePy是一個用於視頻編輯的python模塊,可以用它實現一些基本的操作(比如視頻剪輯,視頻拼接
  • 重磅:包郵寄送《Python數據可視化之美》
    所以很有必要系統性地介紹python的繪圖語法系統,包括最基礎也最常用的matplotlib包、常用於統計分析的seaborn、最新出現類似R ggplot2語法的plotnine以及用於地理空間數據可視化的basemap包。在先介紹數據可視化基礎理論後,本書系統性地介紹了幾乎所有常見的二維和三維圖表的繪製方法,包括簡單的柱形圖系列、條形圖系列、折線圖系列,地圖系列等。
  • 使用python實現一個簡單計算器
    如果做一些簡單的界面,使用tkinter還是很方便的,畢竟是python自帶的庫。今天將會做下面這樣的一個計算器,可以實現基本的加減程序的運算,整體代碼邏輯比較簡單,主要是一個回調函數的理解。實現思路1.UI界面布局2.功能函數實現3.重構布局代碼4.按鈕回調函數綁定具體實現過程1.界面實現
  • #湖人總冠軍#NBA投籃數據可視化更新版,4行代碼就能實現!
    虎撲的JRs喊了那麼年「湖人總冠軍」,今年終於實現了!36歲的老詹還沒有老,4座總冠軍+4次FMVP。之前我們做過一篇NBA球員投籃數據可視化的案例:NBA的球星們喜歡在哪個位置出手可惜的是,文中使用到的官方接口現在被關閉了,導致代碼無法正常使用。
  • NBA 投籃數據可視化,4行代碼就能實現
    關於NBA球員投籃數據的可視化,小F以前也寫過一篇文章。訪問地址:NBA球員投籃數據可視化自己畫球場圖,自己爬數據,碼了不少代碼。這回發現了大佬造的輪子,只需4行代碼就能實現。# 2019-2020賽季常規賽數據https://nba-shot-charts.s3.amazonaws.com/shots-2019.tgz直接在瀏覽器上訪問地址,下載壓縮包,解壓得到CSV文件。
  • NBA 投籃數據可視化,4 行代碼就能實現
    關於NBA球員投籃數據的可視化,小F以前也寫過一篇文章。訪問地址:NBA球員投籃數據可視化自己畫球場圖,自己爬數據,碼了不少代碼。這回發現了大佬造的輪子,只需4行代碼就能實現。# 2019-2020賽季常規賽數據https://nba-shot-charts.s3.amazonaws.com/shots-2019.tgz直接在瀏覽器上訪問地址,下載壓縮包,解壓得到CSV文件。
  • 超硬核的 Python 數據可視化教程!
    Python實現可視化的三個步驟:確定問題,選擇圖形 轉換數據,應用函數 參數設置,一目了然 1、首先,要知道我們用哪些庫來畫圖?matplotlibpython中最基本的作圖庫就是matplotlib,是一個最基礎的Python可視化庫,一般都是從matplotlib上手Python數據可視化,然後開始做縱向與橫向拓展。
  • python群聊工具實現(上)
    今天要實現的是一個群聊小程序,程序有一個服務端和一個客戶端,客戶端有一個下面如下:當用戶連接上伺服器後,伺服器就會給用戶發送恭喜你已經加入python學習群(後面還會實現在左側顯示用戶的名字),當還有其它用戶繼續加入時,會通知已經加入的用戶,說某個用戶加入python學習群,之後不管哪個用戶發送消息,大家的窗口中都會顯示出消息來
  • 太酷了,用Python 製作足球可視化圖表 !
    本期小F就和大家分享一下,用Python和Matplotlib繪製一個足球運動員的數據可視化圖表。數據來源於下面兩個網站,Understat和Fbref。連結:https://understat.com/先看一下射門數據的可視化,本質上和籃球的出手點圖差不多,都是散點圖類型。
  • 太酷了,用Python製作足球可視化圖表!
    先看一下射門數據的可視化,本質上和籃球的出手點圖差不多,都是散點圖類型。導入相關的Python庫。查詢中國球員武磊,點擊訪問,在地址欄處,可以看到球員ID。得到數據如下。包含射門位置(x、y)、xG(預期進球)、射門結果、賽季。
  • Python實現的快速排序
    今天看了下《算法新解》這本書,很薄的一本書,最開始吸引我的有兩點,一個是裡面的大量的圖,內容相對來說比較清新,第二個是裡面的代碼是基於Python實現
  • Python 實現視覺特效:秒變超級賽亞人
    OK,搞定,我選擇的是第五個圖片,下載後是白底jpg格式,先利用Photoshop將其改為背景透明的png格式:(16).x, landmarks.part(16).yd = math.sqrt((x2-x1)**2+(y2-y1)**2)size = int(d / 236 * 439)resized = adding.resize((size,size))im.paste(resized,(int(x1-d*86/236),int(y1-d*
  • nba這個位置的球員最具統治力
    我們都知道籃球比賽中有五個位置,分別是控球後衛,得分後衛,小前鋒,大前鋒,中鋒。那麼在nba歷史上哪個位置的球員最具統治力呢?這肯定是一個見仁見智的位置,因為籃球之神喬丹打的是得分後衛,可能不少球迷認為得分後衛在nba歷史上最有統治力,但我看未必,畢竟喬丹只是代表得分後衛位置上的最高境界,並不能體現這個位置的真正水平。
  • 乾貨R語言vs Python:數據分析哪家強?
    Pythonimport pandasnba = pandas.read_csv("nba_2013.csv")上面的代碼分別在兩種語言中將包含2013-2014賽季NBA球員的數據的 nba_2013.csv 文件加載為變量
  • 職場人士如何一小時學會從0到1用Python
    它有非常豐富的代碼模塊,這些模塊內置了各種函數、類、變量,可以實現各種各樣尤其是數據相關的功能。網際網路時代,數據是非常重要的信息與生產資料,但沒有經過加工處理的數據是很難提煉出價值的。Python正是因為其簡潔的語法、豐富多樣的處理模塊,成為了一個簡潔且實用的代碼工具。
  • 動圖,用Python追蹤NBA球員的運動軌跡
    看NBA的比賽是我生命中不可缺少的部分,這是我看到這個利用Python寫NBA球員系列時,特別感興趣的原因。希望這個系列能帶給大家一點關於NBA的知識。同時為中國男籃再次獲得亞錦賽冠軍喝彩!什麼時候,我們的CBA也提供這麼詳細的數據讓大家研究一下隊員就好了。文摘曾於8月18日發布《如何運用Python繪製NBA投籃圖表》,與本文有直接聯繫,點擊文章名稱可回顧舊文。