Source code for plot_packing

# -*- coding: utf-8 -*-
from bokeh.plotting import figure, output_file, show, save
from bokeh.models import ColumnDataSource, Label, LabelSet, Range1d, Title
from bokeh.io import push_notebook, show, output_notebook
from bokeh.layouts import row, gridplot

from statistics import mean

output_notebook()


[docs]class plot_packing(): """ Plot the player location on the pitch and highlight the defending team players that might have been calculated in packing. Parameters ---------- passer_team_df : DataFrame DataFrame with the passing team coordinates Column name with `id` or `_id` are considered player ids (Only 1 column with such name). packing_df : DataFrame Resulting DataFrame from packing module (should not be altered). Column name with `id` or `_id` are considered player ids (Only 1 column with such name). col_label_x : String The column label for defending team's X coordinate in defending_team_xy col_label_y : String The column label for defending team's Y coordinate in defending_team_xy packing_rate : Float Resulting output from packing module (should not be altered) pass_pressure : Int Defending players who are closer to sender/receiver but not involved in packing sender_xy : ndarray Sender XY coordinates as numpy array receiver_xy : ndarray Receiver XY coordinates as numpy array x_range : [start, end] list List of range of x-axis of the pitch, Eg: `[0, 100]` or `[-5250, 5250]` y_range : [start, end] list List of range of y-axis of the pitch, Eg: `[0, 100]`or `[3400, -3400]` path_to_save : A path to save the output html file. Path should end with a `/` pass_frame : String, Optional, default None Identifier to display pass event time on plot bcg_img : String, default None Path to background image file_name : String, default `packing` Filename to save the plot Returns ---------- Defending players who have been calcuated in packing will be marked in a green border. show() : Plot is shown on the browser. If module is run on jupyter notebook, plot will be shown in the notebook. save() : Plot saved locally to path specified under `path_to_save`. Note: If `file_name` is not changed every time module is run, plots will be overwritten. """ def __init__( self, passer_team_df, packing_df, col_label_x, col_label_y, packing_rate, pass_pressure, sender_xy, receiver_xy, x_range, y_range, path_to_save, pass_frame=None, bcg_img=None, file_name='packing' ): self.passer_team_df = passer_team_df self.packing_df = packing_df self.col_label_x = col_label_x self.col_label_y = col_label_y self.packing_rate = packing_rate self.pass_pressure = pass_pressure self.sender_xy = sender_xy self.receiver_xy = receiver_xy self.bcg_img = bcg_img self.x_range = x_range self.y_range = y_range self.pass_frame = pass_frame self.file_name = file_name self.path_to_save = path_to_save output_file(path_to_save + self.file_name + ".html", title=self.file_name + " plot") def save_plots(self, plot): show(plot) save(plot) print( f"Plot successfully saved at {self.path_to_save+self.file_name+'.html'}") def plot(self): # Plot the visualization pass_team_cols = self.passer_team_df.columns.tolist() defend_team_cols = self.packing_df.columns.tolist() if len(pass_team_cols) < 3: raise ValueError( f"Expect minimum 3 columns in 'passer_team_df'. Please provide a valid dataframe with a column for x and y and player_id each.") pass_team_cols_x = [i for i in pass_team_cols if '_x' in i or 'x' == i] pass_team_cols_y = [i for i in pass_team_cols if '_y' in i or 'y' == i] pass_team_cols_id = [ i for i in pass_team_cols if '_id' in i or 'id' == i or 'player' in i] defend_team_cols_id = [ i for i in defend_team_cols if '_id' in i or 'id' == i or 'player' in i] if len(pass_team_cols_x) < 1: raise ValueError( f"The column name for passing team x does not contain 'x' or '_x'. Please provide a valid name with 'x' or '_x'") if len(pass_team_cols_y) < 1: raise ValueError( f"The column name for passing team y does not contain 'y' or '_y'. Please provide a valid name with 'y' or '_y'") if len(pass_team_cols_id) > 1: raise ValueError( f"There are multiple columns containing either 'id' or '_id' in passer_team_df dataframe. Please provide a single column with 'id' or '_id' as column name") if len(defend_team_cols_id) > 1: raise ValueError( f"There are multiple columns containing either 'id' or '_id' in packing_df dataframe. Please provide a single column with 'id' or '_id' as column name") fig_height, fig_width = 900, 550 fig_title = "Packing rate: {} Pass pressure: {} \n".format( self.packing_rate, self.pass_pressure) plot = figure(name='base', plot_height=550, plot_width=850, tools="save, wheel_zoom, reset, pan", toolbar_location="right", x_range=self.x_range, y_range=self.y_range,) plot.add_layout( Title(text=f"Pass pressure: {self.pass_pressure}", text_font_size="10pt", align='center'), 'above') plot.add_layout( Title(text=f"Packing rate: {self.packing_rate}", text_font_size="10pt", align='center'), 'above') if self.bcg_img != None: image_min_x, image_min_y, image_max_x, image_max_y = min(self.x_range), max(self.y_range), \ (abs(self.x_range[0]) + abs(self.x_range[1]) ), (abs(self.y_range[0]) + abs(self.y_range[1])) plot.image_url(url=[self.path_to_save+self.bcg_img], x=image_min_x, y=image_min_y, w=image_max_x, h=image_max_y, anchor="bottom_left") plot.line([self.sender_xy[0], self.receiver_xy[0]], [self.sender_xy[1], self.receiver_xy[1]], line_color="dodgerblue", line_alpha=0.5, line_width=4, line_dash='dashed',) source_pass_team = ColumnDataSource(self.passer_team_df) plot.scatter(x=pass_team_cols_x[0], y=pass_team_cols_y[0], source=source_pass_team, size=17, fill_color='dodgerblue', fill_alpha=0.7) if pass_team_cols_id: labels_pass_team = LabelSet(x=pass_team_cols_x[0], y=pass_team_cols_y[0], text=pass_team_cols_id[0], x_offset=-4.5, y_offset=-6, source=source_pass_team, render_mode='canvas', text_font_size='8pt', text_color='white') plot.add_layout(labels_pass_team) source_sender = ColumnDataSource( data={'x': [self.sender_xy[0]], 'y': [self.sender_xy[1]], 'id': ['S']}) plot.scatter('x', 'y', source=source_sender, size=17, fill_color='dodgerblue', line_color='red', line_width=3) labels_sender = LabelSet(x='x', y='y', text='id', x_offset=-4.5, y_offset=5, source=source_sender, render_mode='canvas') plot.add_layout(labels_sender) source_receiver = ColumnDataSource( data={'x': [self.receiver_xy[0]], 'y': [self.receiver_xy[1]], 'id': ['R']}) plot.scatter('x', 'y', source=source_receiver, size=17, fill_color='dodgerblue', line_color='red', line_width=3) labels_receiver = LabelSet(x='x', y='y', text='id', x_offset=-4.5, y_offset=5, source=source_receiver, render_mode='canvas') plot.add_layout(labels_receiver) colors = {-1: 'green', 1: 'green', 0.5: 'green', 0: 'orangered'} alpha = {-1: 1, 1: 1, 0.5: 1, 0: 0.5} radius = {-1: 1000, 1: 1000, 0.5: 1000, 0: 0} for i in range(len(self.packing_df[self.col_label_x].tolist())): x = self.packing_df.iloc[i][self.col_label_x] y = self.packing_df.iloc[i][self.col_label_y] id = self.packing_df.iloc[i][defend_team_cols_id] edge_col = colors[self.packing_df.iloc[i]['packing_rate']] fill_alpha = alpha[self.packing_df.iloc[i]['packing_rate']] circle_radius = radius[self.packing_df.iloc[i]['packing_rate']] source_def_team = ColumnDataSource( data={'x': [x], 'y': [y], 'id': [id], 'edge_col': [edge_col], 'fill_alpha': [fill_alpha], 'radius': [circle_radius]}) plot.scatter('x', 'y', size=17, fill_color='orangered', line_color='edge_col', line_width=3, source=source_def_team, fill_alpha='fill_alpha') if defend_team_cols_id: labels_pass_team = LabelSet(x='x', y='y', text='id', x_offset=-4.5, y_offset=-6, source=source_def_team, render_mode='canvas', text_font_size='8pt', text_color='white') plot.add_layout(labels_pass_team) plot.axis.visible = False plot.xgrid.grid_line_color = None plot.ygrid.grid_line_color = None plot.title.align = 'center' plot.toolbar.autohide = True plot.min_border = 40 if self.pass_frame != None: caption1 = Label(text=f"Pass Frame: {str(self.pass_frame)}", text_font_size="8pt", x=min(self.x_range) + (0.01*(self.x_range[1]-self.x_range[0])), y=self.y_range[0] + (0.01*(self.y_range[1]-self.y_range[0]))) plot.add_layout(caption1) self.save_plots(plot)