def make_dataset(play, event_frame, metrica_attack, metrica_defence, bokeh_attack, bokeh_defence, field_dimen = (106.,68.,), new_grid_size = 500): params = mpc.default_model_params(3) event = events_df.loc[[(play, int(event_frame))]] tracking_frame = event['Start Frame'][0] att_frame = bokeh_attack.loc[(play,tracking_frame)] att_player_frame = att_frame[att_frame['player'] != "ball"] att_player_frame['Shirt Number'] = att_player_frame['player'].map(int).map(shirt_mapping[play]).fillna("") def_frame = bokeh_defence.loc[(play,tracking_frame)] def_player_frame = def_frame[def_frame['player'] != "ball"] def_player_frame['Shirt Number'] = def_player_frame['player'].map(int).map(shirt_mapping[play]).fillna("") ball_frame = att_frame[att_frame['player'] == "ball"] PPCF,xgrid,ygrid = pvm.lastrow_generate_pitch_control_for_event(play,event_frame, events_df, metrica_attack, metrica_defence, params, field_dimen = (106.,68.,), n_grid_cells_x = 50) PT = pvm.generate_relevance_at_event(play,event_frame, events_df, PPCF, params) PS = pvm.generate_scoring_opportunity(field_dimen = (106.,68.,),n_grid_cells_x = 50) PPV = pvm.generate_pitch_value(PPCF,PT,PS,field_dimen = (106.,68.,),n_grid_cells_x = 50) RPPV = pvm.generate_relative_pitch_value(play, event_frame, events_df, metrica_attack, PPV, xgrid, ygrid) xgrid_new = np.linspace( -field_dimen[0]/2., field_dimen[0]/2., new_grid_size) ygrid_new = np.linspace( -field_dimen[1]/2., field_dimen[1]/2., new_grid_size) PPCF_int = interpolate.interp2d(xgrid, ygrid, PPCF, kind = 'cubic') PPCF_new = PPCF_int(xgrid_new, ygrid_new) PPCF_dict = dict(image = [PPCF_new],x = [xgrid.min()],y = [ygrid.min()],dw = [field_dimen[0]], dh = [field_dimen[1]]) PT_int = interpolate.interp2d(xgrid, ygrid, PT, kind = 'cubic') PT_new = PT_int(xgrid_new, ygrid_new) PT_dict = dict(image = [PT_new],x = [xgrid.min()],y = [ygrid.min()],dw = [field_dimen[0]], dh = [field_dimen[1]]) PS_int = interpolate.interp2d(xgrid, ygrid, PS, kind = 'cubic') PS_new = PS_int(xgrid_new, ygrid_new) PS_dict = dict(image = [PS_new],x = [xgrid.min()],y = [ygrid.min()],dw = [field_dimen[0]], dh = [field_dimen[1]]) PPV_int = interpolate.interp2d(xgrid, ygrid, PPV, kind = 'cubic') PPV_new = PPV_int(xgrid_new, ygrid_new) PPV_dict = dict(image = [PPV_new],x = [xgrid.min()],y = [ygrid.min()],dw = [field_dimen[0]], dh = [field_dimen[1]]) RPPV_int = interpolate.interp2d(xgrid, ygrid, RPPV, kind = 'cubic') RPPV_new = RPPV_int(xgrid_new, ygrid_new) RPPV_dict = dict(image = [RPPV_new],x = [xgrid.min()],y = [ygrid.min()],dw = [field_dimen[0]], dh = [field_dimen[1]]) event_src = ColumnDataSource(event) att_src = ColumnDataSource(att_player_frame) def_src = ColumnDataSource(def_player_frame) ball_src = ColumnDataSource(ball_frame) PPCF_src = ColumnDataSource(PPCF_dict) PT_src = ColumnDataSource(PT_dict) PS_src = ColumnDataSource(PS_dict) PPV_src = ColumnDataSource(PPV_dict) RPPV_src = ColumnDataSource(RPPV_dict) return event_src, att_src, def_src, ball_src, PPCF_src, PT_src, PS_src, PPV_src, RPPV_src, xgrid, ygrid
def calculate_epv_events_per_match(base_file, folder): """ Calculates the EPV and optimal EPV values for an input DataFrame of passes. Parameters ----------- base_file: input string corresponding to the match CSVs, from which we read the data folder: folder path Returns ----------- EPV_df: is the original events DataFrame with only the transition passes and includes the outcomes of the EPV calculations """ # make path string preprocessed_tracking_home_path = f'{folder}\\preprocessed\\{base_file}_tracking_home_processed.csv' preprocessed_tracking_away_path = f'{folder}\\preprocessed\\{base_file}_tracking_away_processed.csv' transition_passes_path = f'{folder}\\transition_passes\\{base_file}_transition_passes.csv' # load data tracking_home = pd.read_csv(preprocessed_tracking_home_path, index_col=0) tracking_away = pd.read_csv(preprocessed_tracking_away_path, index_col=0) events = pd.read_csv(transition_passes_path, index_col=0) # select only transition passes with an origin in the midfield: events = events[(events['Start X'] > -17.5) & (events['Start X'] < 17.5)] """ *** UPDATES TO THE MODEL: OFFSIDES """ # first get pitch control model parameters params = mpc.default_model_params() # find goalkeepers for offside calculation gk_numbers = [ mio.find_goalkeeper(tracking_home), mio.find_goalkeeper(tracking_away) ] # get EPV surface epv = mepv.load_EPV_grid(DATADIR + '/EPV_grid.csv') # generate EPV values and append to events DataFrame epv_df = generate_epv_df(events, tracking_home, tracking_away, gk_numbers, epv, params) return epv_df
def player_pitch_control_impact(self): st.subheader("Simulation") # read dataset df_dict, color_dict, events_df = read_dataset(self.base_dir, self.play, self.args) # show dataframe st.markdown("event dataframe is ...") st.table(events_df) event_id = st.selectbox("Select a event id for analysis", events_df.index, index=events_df.index[-1]) team = st.selectbox( "Select a team for analysis", [events_df.at[event_id, "Team"]] + [ k for k in list(df_dict.keys()) if k != events_df.at[event_id, "Team"] ], ) # sort player num based on end location end_frame = events_df.at[event_id, "End Frame"] ball_loc = events_df.loc[event_id, ["End X", "End Y"]].values player_num_list = list( set([ c.split("_")[1] for c in df_dict[team].columns if c.startswith(team) ])) sorted_index = np.argsort([ np.linalg.norm(df_dict[team].loc[ end_frame, [f"{team}_{player_num}_{c}" for c in ["x", "y"]]].values - ball_loc) for player_num in player_num_list ]) player_num = st.selectbox( "Select a player number for analysis", np.array(player_num_list)[sorted_index], ) verification_mode = st.selectbox( "Select the verification mode", [ "movement:How much space created during event??", "presense:How much space occupied during event??", "location:if positions changed, how much space difference during event??", ], ).split(":")[0] params = mpc.default_model_params(3) example_player_analysis_away = PlayerPitchControlAnalysisPlayer( df_dict=df_dict, params=params, events=events_df, event_id=event_id, team_player_to_analyze=team, player_to_analyze=str(player_num), field_dimens=(106.0, 68.0), n_grid_cells_x=50, ) with st.spinner("wait for computing ..."): if verification_mode == "movement": st.markdown( example_player_analysis_away.team_player_to_analyze + " Player " + str(example_player_analysis_away.player_to_analyze) + " created " + str( int( example_player_analysis_away. calculate_space_created( replace_function="movement", replace_x_velocity=0, replace_y_velocity=0, ))) + " m^2 of space with his movement during event " + str(example_player_analysis_away.event_id)) # Now, let's plot the space created and conceded by his run fig, ax = example_player_analysis_away.plot_pitch_control_difference( replace_function="movement", replace_x_velocity=0, replace_y_velocity=0, team_color_dict=color_dict, ) st.pyplot(fig, bbox_layout="tight") elif verification_mode == "presense": st.markdown( example_player_analysis_away.team_player_to_analyze + " Player " + str(example_player_analysis_away.player_to_analyze) + " occupied " + str( int( example_player_analysis_away. calculate_space_created( replace_function="presence"))) + " m^2 of space during event " + str(example_player_analysis_away.event_id)) fig, ax = example_player_analysis_away.plot_pitch_control_difference( replace_function="presence", team_color_dict=color_dict) st.pyplot(fig, bbox_layout="tight") elif verification_mode == "location": st_frame = events_df.at[event_id, "Start Frame"] x, y = ( df_dict[team].at[st_frame, f"{team}_{player_num}_x"], df_dict[team].at[st_frame, f"{team}_{player_num}_y"], ) max_x, min_x = int(x_size / 2 - x), -int(x_size / 2 + x) max_y, min_y = int(y_size / 2 - y), -int(y_size / 2 + y) relative_x = st.slider("relative x", min_value=min_x, max_value=max_x, value=0, step=1) relative_y = st.slider("relative y", min_value=min_y, max_value=max_y, value=0, step=1) st.markdown( example_player_analysis_away.team_player_to_analyze + " Player " + str(example_player_analysis_away.player_to_analyze) + " would have occupied a difference of " + str( int(-1 * example_player_analysis_away. calculate_space_created( replace_function="location", relative_x_change=relative_x, relative_y_change=relative_y, ))) + " m^2 of space during event " + str(example_player_analysis_away.event_id) + " if they were changed to x, y = " + str(relative_x) + ", " + str(relative_y)) fig, ax = example_player_analysis_away.plot_pitch_control_difference( replace_function="location", relative_x_change=relative_x, relative_y_change=relative_y, team_color_dict=color_dict, ) st.pyplot(fig, bbox_layout="tight") st.success("done !!")
# Calculate player velocities # tracking_home = mvel.calc_player_velocities(tracking_home, smoothing=True) # tracking_away = mvel.calc_player_velocities(tracking_away, smoothing=True) #%% # **** NOTE ***** # if the lines above produce an error (happens for one version of numpy) change them to the lines below: # *************** tracking_home = mvel.calc_player_velocities(tracking_home, smoothing=True, filter_='moving_average') tracking_away = mvel.calc_player_velocities(tracking_away, smoothing=True, filter_='moving_average') """ *** UPDATES TO THE MODEL: OFFSIDES """ # first get pitch control model parameters params = mpc.default_model_params() # find goalkeepers for offside calculation GK_numbers = [ mio.find_goalkeeper(tracking_home), mio.find_goalkeeper(tracking_away) ] #%% GK_numbers #%% """ *** GET EPV SURFACE **** """ home_attack_direction = mio.find_playing_direction( tracking_home, 'Home') # 1 if shooting left-right, else -1 #%% EPV = mepv.load_EPV_grid(DATADIR + '/EPV_grid.csv')
def calculate_pitch_control_towards_goal(frame, team, event_id, events, tracking_home, tracking_away, column_name): """ Calculates the pitch control percentage of the tiles between the passer and the goal Parameters ----------- frame: frame of corresponding tracking row team: string 'Home' or 'Away' event_id: index corresponding to the passing event events: all transition pass events of this match tracking_home: tracking DataFrame for the Home team tracking_away: tracking DataFrame for the Away team column_name: the passing player's corresponding base column name for the tracking data Returns ----------- percentage_pitch_control: percentage of pitch control of the attacking team of the tile between the passer, and the goal """ # first get pitch control model parameters params = mpc.default_model_params() # find goalkeepers for offside calculation gk_numbers = [ mio.find_goalkeeper(tracking_home), mio.find_goalkeeper(tracking_away) ] # evaluated pitch control surface for pass event PPCa, xgrid, ygrid = mpc.generate_pitch_control_for_event( event_id, events, tracking_home, tracking_away, params, gk_numbers, field_dimen=( 105., 68., ), n_grid_cells_x=50, offsides=True) # get playing_direction and x position of passing player if team == 'Home': player_x = tracking_home.loc[frame, column_name + '_x'] playing_direction = mio.find_playing_direction(tracking_home, team) # player_y = tracking_home.loc[frame, column_name + '_y'] else: player_x = tracking_away.loc[frame, column_name + '_x'] playing_direction = mio.find_playing_direction(tracking_away, team) # divide the playing field into grid cells n_grid_cells_x = 32 n_grid_cells_y = 50 field_dimen = [105, 68] # get grid cell of passing player dx = field_dimen[0] / n_grid_cells_x x_grid = np.arange(n_grid_cells_x) * dx - field_dimen[0] / 2. + dx / 2. # calculate the pitch control value of the grids towards goal, # and calculate the maximum pitch control for that area: if playing_direction == 1: # direction: left -> right # get number of grids closer to goal num_grids_to_goal = len([i for i in x_grid if i > player_x]) # maximum == number of grids to goal and a Pitch Control value of 1, # meaning that it is totally controlled by attacker max_pitch_control = num_grids_to_goal * n_grid_cells_y # get the pitch control using the pitch control values that are created above (PPCa) pitch_control_att_team = sum(sum(PPCa[-num_grids_to_goal:])) else: # direction: right -> left num_grids_to_goal = len([i for i in x_grid if i < player_x]) max_pitch_control = num_grids_to_goal * n_grid_cells_y pitch_control_att_team = sum(sum(PPCa[:num_grids_to_goal])) percentage_pitch_control = round( (pitch_control_att_team / max_pitch_control) * 100, 2) return percentage_pitch_control