如何用 Python 追踪 NBA 球员的移动轨迹 (2)
(点击上方公号,可快速关注)
昨天的文章《如何用 Python 追踪 NBA 球员的移动轨迹 (1)》中介绍了如何获得数据,接下来我们将介绍剩余的内容来完成最终的轨迹追踪功能!
绘制移动轨迹
下面我们通过动画绘制 James Harden 的移动轨迹。我们可以使用从 stas.nba.com 得到的动画上画好的球场来绘制球场。你可以在这里找到 SVG 图片。我将它转换成一个 PNG 文件,这样可以更容易地使用 matplotlib 来绘制。也要注意 x 或者 y 轴上的每 1 个单位表示篮球场上的 1 英尺。
# get Harden's movements
harden = df[df.player_name=="James Harden"]
# read in the court png file
court = plt.imread("fullcourt.png")
plt.figure(figsize=(15, 11.5))
# Plot the movemnts as scatter plot
# using a colormap to show change in game clock
plt.scatter(harden.x_loc, harden.y_loc, c=harden.game_clock,
cmap=plt.cm.Blues, s=1000, zorder=1)
# Darker colors represent moments earlier on in the game
cbar = plt.colorbar(orientation="horizontal")
cbar.ax.invert_xaxis()
# This plots the court
# zorder=0 sets the court lines underneath Harden's movements
# extent sets the x and y axis values to plot the image within.
# The original animation plots in the SVG coordinate space
# which has x=0, and y=0 at the top left.
# So, we set the axis values the same way in this plot.
# In the list we pass to extent 0,94 representing the x-axis
# values and 50,0 representing the y-axis values
plt.imshow(court, zorder=0, extent=[0,94,50,0])
# extend the x-values beyond the court b/c Harden
# goes out of bounds
plt.xlim(0,101)
plt.show()
我们也可以仅使用 matplotlib Patches 来重新创建球场的大部分。我们使用传统的笛卡尔坐标系,而不使用 SVG 坐标系,所以 y 值将会是负数,而不是正数。
from matplotlib.patches import Circle, Rectangle, Arc
# Function to draw the basketball court lines
def draw_court(ax=None, color="gray", lw=1, zorder=0):
if ax is None:
ax = plt.gca()
# Creates the out of bounds lines around the court
outer = Rectangle((0,-50), width=94, height=50, color=color,
zorder=zorder, fill=False, lw=lw)
# The left and right basketball hoops
l_hoop = Circle((5.35,-25), radius=.75, lw=lw, fill=False,
color=color, zorder=zorder)
r_hoop = Circle((88.65,-25), radius=.75, lw=lw, fill=False,
color=color, zorder=zorder)
# Left and right backboards
l_backboard = Rectangle((4,-28), 0, 6, lw=lw, color=color,
zorder=zorder)
r_backboard = Rectangle((90, -28), 0, 6, lw=lw,color=color,
zorder=zorder)
# Left and right paint areas
l_outer_box = Rectangle((0, -33), 19, 16, lw=lw, fill=False,
color=color, zorder=zorder)
l_inner_box = Rectangle((0, -31), 19, 12, lw=lw, fill=False,
color=color, zorder=zorder)
r_outer_box = Rectangle((75, -33), 19, 16, lw=lw, fill=False,
color=color, zorder=zorder)
r_inner_box = Rectangle((75, -31), 19, 12, lw=lw, fill=False,
color=color, zorder=zorder)
# Left and right free throw circles
l_free_throw = Circle((19,-25), radius=6, lw=lw, fill=False,
color=color, zorder=zorder)
r_free_throw = Circle((75, -25), radius=6, lw=lw, fill=False,
color=color, zorder=zorder)
# Left and right corner 3-PT lines
# a represents the top lines
# b represents the bottom lines
l_corner_a = Rectangle((0,-3), 14, 0, lw=lw, color=color,
zorder=zorder)
l_corner_b = Rectangle((0,-47), 14, 0, lw=lw, color=color,
zorder=zorder)
r_corner_a = Rectangle((80, -3), 14, 0, lw=lw, color=color,
zorder=zorder)
r_corner_b = Rectangle((80, -47), 14, 0, lw=lw, color=color,
zorder=zorder)
# Left and right 3-PT line arcs
l_arc = Arc((5,-25), 47.5, 47.5, theta1=292, theta2=68, lw=lw,
color=color, zorder=zorder)
r_arc = Arc((89, -25), 47.5, 47.5, theta1=112, theta2=248, lw=lw,
color=color, zorder=zorder)
# half_court
# ax.axvline(470)
half_court = Rectangle((47,-50), 0, 50, lw=lw, color=color,
zorder=zorder)
hc_big_circle = Circle((47, -25), radius=6, lw=lw, fill=False,
color=color, zorder=zorder)
hc_sm_circle = Circle((47, -25), radius=2, lw=lw, fill=False,
color=color, zorder=zorder)
court_elements = [l_hoop, l_backboard, l_outer_box, outer,
l_inner_box, l_free_throw, l_corner_a,
l_corner_b, l_arc, r_hoop, r_backboard,
r_outer_box, r_inner_box, r_free_throw,
r_corner_a, r_corner_b, r_arc, half_court,
hc_big_circle, hc_sm_circle]
# Add the court elements onto the axes
for element in court_elements:
ax.add_patch(element)
return ax
plt.figure(figsize=(15, 11.5))
# Plot the movemnts as scatter plot
# using a colormap to show change in game clock
plt.scatter(harden.x_loc, -harden.y_loc, c=harden.game_clock,
cmap=plt.cm.Blues, s=1000, zorder=1)
# Darker colors represent moments earlier on in the game
cbar = plt.colorbar(orientation="horizontal")
# invert the colorbar to have higher numbers on the left
cbar.ax.invert_xaxis()
draw_court()
plt.xlim(0, 101)
plt.ylim(-50, 0)
plt.show()
计算移动距离
我们可以通过获取相邻点的欧氏距离计算出一个球员的移动距离,然后添加那些距离。
def travel_dist(player_locations):
# get the differences for each column
diff = np.diff(player_locations, axis=0)
# square the differences and add them,
# then get the square root of that sum
dist = np.sqrt((diff ** 2).sum(axis=1))
# Then return the sum of all the distances
return dist.sum()
# Harden's travel distance
dist = travel_dist(harden[["x_loc", "y_loc"]])
dist
输出:
197.44816608512659
我们可以使用 groupby 和 apply 得到每个球员总的移动距离。以球员分组,获取他们每个人的坐标位置,然后 apply 上面的 distance 函数。
player_travel_dist = df.groupby('player_name')[['x_loc', 'y_loc']].apply(travel_dist)
player_travel_dist
输出:
player_name
Blake Griffin 153.076637
Chris Paul 176.198330
DeAndre Jordan 119.919877
Dwight Howard 123.439590
JJ Redick 184.504145
James Harden 197.448166
Jason Terry 173.308880
Josh Smith 162.226100
Matt Barnes 161.976406
Trevor Ariza 153.389365
ball 328.317612
dtype: float64
计算平均速度
计算一个球员的平均速度非常简单。我们只需以时间来划分距离。
# get the number of seconds for the play
seconds = df.game_clock.max() - df.game_clock.min()
# feet per second
harden_fps = dist / seconds
# convert to miles per hour
harden_mph = 0.681818 * harden_fps
harden_mph
输出 :
4.7977089702005902
我们可以使用之前创建的 player_travel_dist Series 来获取每个球员的平均速度。
player_speeds = (player_travel_dist/seconds) * 0.681818
player_speeds
输出:
player_name
Blake Griffin 3.719544
Chris Paul 4.281368
DeAndre Jordan 2.913882
Dwight Howard 2.999406
JJ Redick 4.483188
James Harden 4.797709
Jason Terry 4.211159
Josh Smith 3.941863
Matt Barnes 3.935796
Trevor Ariza 3.727143
ball 7.977650
dtype: float64
计算球员之间的距
下面将看下在比赛中,Harden 与其他每个球员之间的距离。
首先获取 Harden 的位置。
harden_loc = df[df.player_name=="James Harden"][["x_loc", "y_loc"]]
harden_loc.head()
现在让我们以 player_name 进行分组,并且获取每个球员和篮球的位置。
group = df[df.player_name!="James Harden"].groupby("player_name")[["x_loc", "y_loc"]]
我们可以利用 group 中 scipy 库的 euclidean 函数来 apply 一个函数。然后为每个球员返回一个列表,该列表包含比赛中 James Harden 与该球员之间的距离。
from scipy.spatial.distance import euclidean
输出:
# Function to find the distance between players
# at each moment
def player_dist(player_a, player_b):
return [euclidean(player_a.iloc[i], player_b.iloc[i])
for i in range(len(player_a))]
每个球员的位置以 player_a 传进 player_dist 函数中,而 Harden 的位置则以 player_b 传进该函数。
harden_dist = group.apply(player_dist, player_b=(harden_loc))
harden_dist
输出:
player_name
Blake Griffin [27.182922508363593, 27.055820685362697, 26.94...
Chris Paul [47.10168680005101, 46.861684798626264, 46.618...
DeAndre Jordan [16.413678482610162, 16.48314022711995, 16.556...
Dwight Howard [14.282883583198455, 14.35720390798292, 14.433...
JJ Redick [5.697440979685529, 5.683098128626677, 5.67370...
Jason Terry [51.685939334067434, 51.40228120171322, 51.096...
Josh Smith [44.06513224475787, 43.81023267813696, 43.5637...
Matt Barnes [37.5405670597302, 37.59395273374297, 37.68516...
Trevor Ariza [41.47340873263252, 41.414794206955804, 41.348...
ball [52.976156009708745, 52.70430545836839, 52.435...
dtype: object
注意到篮球的列表中只有 690 项,而球员的则有 700 项。
len(harden_dist["ball"])
输出:
690
输入:
len(harden_dist["Blake Griffin"])
输出:
700
现在我们知道如何得到球员之间的距离,接下来让我们试着看下 James Harden 突破到篮底是如何影响地板上的一些间距。
让我们再看看 moments 动画。然后仔细查看在 Harden 突破期间会出现什么。
IFrame('http://stats.nba.com/movement/#!/?GameID=0041400235&GameEventID=308',
width=700, height=400)
Python开发者
微信号:PythonCoder
可能是东半球最好的 Python 微信号
--------------------------------------
投稿网址:top.jobbole.com
商务合作QQ:2302462408