用 Python 建立 NBA 得分圖表
在這篇文章中,我研究瞭如何提取一個籃球運動員的得分圖資料,然後使用 matplotlib
和 seaborn
繪製得分圖。
123456 | %matplotlibinlineimportrequestsimportmatplotlib |
獲取資料
從 stats.nba.com 獲取資料是非常簡單的。雖然 NBA 沒有提供一個公共的 API,但是實際上我們可以通過使用 requests
庫來訪問 NBA 用在 stats.nba.com 上的 API。Greg Reda 釋出的這篇部落格很好地解釋瞭如何訪問這個 API(或者為任意 web 應用找到一個 API 來完成上述問題)。
我們將會使用這個 URL 來獲取 James Harden 的得分圖資料。
Python123456789 | 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' |
上述 URL 傳送給我們一個 JSON 檔案,該檔案包含我們想要的資料。也要注意到 URL 包含了用於訪問資料的各種 API 引數。URL 中被設定為 201935 的 PlayerID 引數就是 James Harden 的 PlayerID。
現在讓我們使用 requests
庫來獲取我們想要的資料。
123456 | # Get the webpage containing the dataresponse=requests.get(shot_chart_url)# Grab the headers to be used as column headers for our DataFrameheaders=response.json()['resultSets'][0]['headers']# Grab the shot chart datashots=response.json()['resultSets'][0]['rowSet'] |
利用獲取到的得分圖資料建立一個 pandas DataFrame
。
12345 | shot_df=pd.DataFrame(shots,columns=headers)# View the head of the DataFrame and all its columnsfromIPython.display importdisplaywithpd.option_context('display.max_columns',None):display(shot_df.head()) |
上述的得分圖資料包含了 2014-15 常規賽期間 James Harden 所有的投籃出手次數。我們想要的資料在 LOC_X 和 LOC_Y 中。這是每次投籃出手位置的座標值,然後就可以在代表籃球場的一組座標軸上繪製這些座標值了。
繪製得分圖資料
讓我們快速地繪製資料,看看資料是如何分佈的。
Python12345 | 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() |
注意到上述的圖表沒能很好的表示資料。橫座標軸上的值與實際的相反了。下面只把右邊的投籃繪製出來,看看問題出在哪裡。
Python123456 | 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() |
正如我們所看到的,屬於“右側”出手的投籃,雖然是位於觀看者的右邊,但是實際上位於籃框的左邊。在建立我們最終的得分圖時,這是需要我們解決的問題。
繪製籃球場
首先我們需要了解如何在圖表上繪製球場線。通過檢視第一張圖片和資料,我們可以大概估計球場的中心位於原點。我們也可以估算在 x 軸或者 y 軸上每 10 個單位代表 1 英尺。我們可以通過檢視 DataFrame
中第一條記錄來驗證前面的估算。某個投籃是從距離為 22 英尺的右邊底角 3 分線出手的,而 22 英尺對應 LOC_X 的值為 226。所以那個投籃是從球場右邊大約 22.6 英尺的地方出手的。現在我們知道這個之後,我們可以在圖表上繪製球場了。
利用這些尺寸,我們可以將它們轉換成適合我們圖表大小的尺寸,並且使用 Matplotlib Patches 來繪製。我們將使用 Circle、Rectangle 和 Arc 物件來繪製我們的球場。現在要建立函式來繪製籃球場了。
NOTE:雖然你可以使用 Lines2D 在圖上繪製線條,但是我發現用 Rectangles 會更加方便(不帶一個高度或者寬度)。
編輯(Aug 4, 2015):我在繪製外線和半場弧形的時候犯了一個錯誤。外面的球場線高度從錯誤的值 442.5 改成 470。球場中央弧形的中心,其 y 值從 395 改到 422.5。在圖表上,y 軸範圍上的值從 (395, -47.5) 改成 (422.5, -47.5)。
Python123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 | frommatplotlib.patches importCircle,Rectangle,Arcdefdraw_court(ax=None,color='black',lw=2,outer_lines=False):# If an axes object isn't provided to plot onto, just get current oneifax isNone:ax=plt.gca()# Create the various parts of an NBA basketball court# Create the basketball hoop# Diameter of a hoop is 18" so it has a radius of 9", which is a value# 7.5 in our coordinate systemhoop=Circle((0,0),radius=7.5,linewidth=lw,color=color,fill=False)# Create backboardbackboard=Rectangle((-30,-7.5),60,-1,linewidth=lw,color=color)# The paint# Create the outer box 0f the paint, width=16ft, height=19ftouter_box=Rectangle((-80,-47.5),160,190,linewidth=lw,color=color,fill=False)# Create the inner box of the paint, widt=12ft, height=19ftinner_box=Rectangle((-60,-47.5),120,190,linewidth=lw,color=color,fill=False)# Create free throw top arctop_free_throw=Arc((0,142.5),120,120,theta1=0,theta2=180,linewidth=lw,color=color,fill=False)# Create free throw bottom arcbottom_free_throw=Arc((0,142.5),120,120,theta1=180,theta2=0,linewidth=lw,color=color,linestyle='dashed')# Restricted Zone, it is an arc with 4ft radius from center of the hooprestricted=Arc((0,0),80,80,theta1=0,theta2=180,linewidth=lw,color=color)# Three point line# Create the side 3pt lines, they are 14ft long before they begin to arccorner_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# I just played around with the theta values until they lined up with the # threesthree_arc=Arc((0,0),475,475,theta1=22,theta2=158,linewidth=lw,color=color)# Center Courtcenter_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)# List of the court elements to be plotted onto the axescourt_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]ifouter_lines:# Draw the half court line, baseline and side out bound linesouter_lines=Rectangle((-250,-47.5),500,470,linewidth=lw,color=color,fill=False)court_elements.append(outer_lines)# Add the court elements onto the axesforelement incourt_elements:ax.add_patch(element)returnax |
讓我們繪製我們的球場吧
Python12345 | plt.figure(figsize=(12,11))draw_court(outer_lines=True)plt.xlim(-300,300)plt.ylim(-100,500)plt.show() |
建立一些得分圖
現在繪製經過我們適當調整的得分圖資料和球場。我們可以用兩種方法調整 x 值。可以傳遞 LOC_X 的負數