def calculate_epv_added( event_id, events, tracking_home, tracking_away, GK_numbers, EPV, params): """ calculate_epv_added Calculates the expected possession value added by a pass Parameters ----------- event_id: Index (not row) of the pass event to calculate EPV-added score events: Dataframe containing the event data tracking_home: tracking DataFrame for the Home team tracking_away: tracking DataFrame for the Away team GK_numbers: tuple containing the player id of the goalkeepers for the (home team, away team) EPV: tuple Expected Possession value grid (loaded using load_EPV_grid() ) params: Dictionary of pitch control model parameters (default model parameters can be generated using default_model_params() ) Returrns ----------- EEPV_added: Expected EPV value-added of pass defined by event_id EPV_difference: The raw change in EPV (ignoring pitch control) between end and start points of pass """ # pull out pass details from the event data pass_start_pos = np.array([events.loc[event_id]['Start X'],events.loc[event_id]['Start Y']]) pass_target_pos = np.array([events.loc[event_id]['End X'],events.loc[event_id]['End Y']]) pass_frame = events.loc[event_id]['Start Frame'] pass_team = events.loc[event_id].Team # direction of play for atacking team (so we know whether to flip the EPV grid) home_attack_direction = mio.find_playing_direction(tracking_home,'Home') if pass_team=='Home': attack_direction = home_attack_direction attacking_players = mpc.initialise_players(tracking_home.loc[pass_frame],'Home',params,GK_numbers[0]) defending_players = mpc.initialise_players(tracking_away.loc[pass_frame],'Away',params,GK_numbers[1]) elif pass_team=='Away': attack_direction = home_attack_direction*-1 defending_players = mpc.initialise_players(tracking_home.loc[pass_frame],'Home',params,GK_numbers[0]) attacking_players = mpc.initialise_players(tracking_away.loc[pass_frame],'Away',params,GK_numbers[1]) # flag any players that are offside attacking_players = mpc.check_offsides( attacking_players, defending_players, pass_start_pos, GK_numbers) # pitch control grid at pass start location Patt_start,_ = mpc.calculate_pitch_control_at_target(pass_start_pos, attacking_players, defending_players, pass_start_pos, params) # pitch control grid at pass end location Patt_target,_ = mpc.calculate_pitch_control_at_target(pass_target_pos, attacking_players, defending_players, pass_start_pos, params) # EPV at start location EPV_start = get_EPV_at_location(pass_start_pos, EPV, attack_direction=attack_direction) # EPV at end location EPV_target = get_EPV_at_location(pass_target_pos,EPV,attack_direction=attack_direction) # 'Expected' EPV at target and start location EEPV_target = Patt_target*EPV_target EEPV_start = Patt_start*EPV_start # difference is the (expected) EPV added EEPV_added = EEPV_target - EEPV_start # Also calculate the straight up change in EPV EPV_difference = EPV_target - EPV_start return EEPV_added, EPV_difference
def find_max_value_added_target(event_id, events, tracking_home, tracking_away, GK_numbers, EPV, params): """ find_max_value_added_target Finds the *maximum* expected possession value that could have been achieved for a pass (defined by the event_id) by searching the entire field for the best target. Parameters ----------- event_id: Index (not row) of the pass event to calculate EPV-added score events: Dataframe containing the event data tracking_home: tracking DataFrame for the Home team tracking_away: tracking DataFrame for the Away team GK_numbers: tuple containing the player id of the goalkeepers for the (home team, away team) EPV: tuple Expected Possession value grid (loaded using load_EPV_grid() ) params: Dictionary of pitch control model parameters (default model parameters can be generated using default_model_params() ) Returrns ----------- maxEPV_added: maximum EPV value-added that could be achieved at the current instant max_target_location: (x,y) location of the position of the maxEPV_added """ # pull out pass details from the event data pass_start_pos = np.array( [events.loc[event_id]["Start X"], events.loc[event_id]["Start Y"]]) pass_frame = events.loc[event_id]["Start Frame"] pass_team = events.loc[event_id].Team # direction of play for atacking team (so we know whether to flip the EPV grid) home_attack_direction = mio.find_playing_direction(tracking_home, "Home") if pass_team == "Home": attack_direction = home_attack_direction attacking_players = mpc.initialise_players( tracking_home.loc[pass_frame], "Home", params, GK_numbers[0]) defending_players = mpc.initialise_players( tracking_away.loc[pass_frame], "Away", params, GK_numbers[1]) elif pass_team == "Away": attack_direction = home_attack_direction * -1 defending_players = mpc.initialise_players( tracking_home.loc[pass_frame], "Home", params, GK_numbers[0]) attacking_players = mpc.initialise_players( tracking_away.loc[pass_frame], "Away", params, GK_numbers[1]) # flag any players that are offside attacking_players = mpc.check_offsides(attacking_players, defending_players, pass_start_pos, GK_numbers) # pitch control grid at pass start location Patt_start, _ = mpc.calculate_pitch_control_at_target( pass_start_pos, attacking_players, defending_players, pass_start_pos, params) # EPV at start location EPV_start = get_EPV_at_location(pass_start_pos, EPV, attack_direction=attack_direction) # calculate pitch control surface at moment of the pass PPCF, xgrid, ygrid = mpc.generate_pitch_control_for_event( event_id, events, tracking_home, tracking_away, params, GK_numbers, field_dimen=( 106.0, 68.0, ), n_grid_cells_x=50, offsides=True, ) # EPV surface at instance of the pass if attack_direction == -1: EEPV = np.fliplr(EPV) * PPCF else: EEPV = EPV * PPCF # find indices of the maxEPV maxEPV_idx = np.unravel_index(EEPV.argmax(), EEPV.shape) # Expected EPV at current ball position EEPV_start = Patt_start * EPV_start # maxEPV_added (difference between max location and current ball location) maxEPV_added = EEPV.max() - EEPV_start # location of maximum max_target_location = (xgrid[maxEPV_idx[1]], ygrid[maxEPV_idx[0]]) return maxEPV_added, max_target_location
# direction of play for atacking team (so we know whether to flip the EPV grid) home_attack_direction = mio.find_playing_direction(tracking_home, 'Home') if pass_team == 'Home': attack_direction = home_attack_direction attacking_players = mpc.initialise_players(tracking_home.loc[pass_frame], 'Home', params, GK_numbers[0]) defending_players = mpc.initialise_players(tracking_away.loc[pass_frame], 'Away', params, GK_numbers[1]) elif pass_team == 'Away': attack_direction = home_attack_direction * -1 defending_players = mpc.initialise_players(tracking_home.loc[pass_frame], 'Home', params, GK_numbers[0]) attacking_players = mpc.initialise_players(tracking_away.loc[pass_frame], 'Away', params, GK_numbers[1]) # flag any players that are offside attacking_players = mpc.check_offsides(attacking_players, defending_players, pass_start_pos, GK_numbers) # pitch control grid at pass start location # Patt_start,_ = mpc.calculate_pitch_control_at_target(pass_start_pos, attacking_players, defending_players, pass_start_pos, params) #%% ball_start_pos = pass_start_pos.copy() target_position = pass_start_pos.copy() if ball_start_pos is None or any( np.isnan(ball_start_pos)): # assume that ball is already at location ball_travel_time = 0.0 else: # ball travel time is distance to target position from current ball position divided assumed average ball speed ball_travel_time = np.linalg.norm( target_position - ball_start_pos) / params['average_ball_speed'] #%%