Exemplo n.º 1
0
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
Exemplo n.º 2
0
                                                ascending=False)

# We want the top 5 not including the goalie (Player 11)
top_passers = players_averages[(players_averages['Overplayed per pass'] >= 1.8)
                               & (players_averages.index != 'Player11')]

#We can plot the passes from each player in the top 5. Only passes that broke through 3 away opponents.
for i in top_passers.index:
    pass_success_probability = []
    player_passing = index[(index.From == i) & (index.Break >= 3)]
    for i, row in player_passing.iterrows():
        pass_start_pos = np.array([row['Start X'], row['Start Y']])
        pass_target_pos = np.array([row['End X'], row['End Y']])
        pass_frame = row['Start Frame']

        attacking_players = mpc.initialise_players(
            tracking_home.loc[pass_frame], 'Home', params)
        defending_players = mpc.initialise_players(
            tracking_away.loc[pass_frame], 'Away', params)

        Patt, _ = mpc.calculate_pitch_control_at_target(
            pass_target_pos, attacking_players, defending_players,
            pass_start_pos, params)

        pass_success_probability.append((i, Patt))

    fig, ax = plt.subplots()
    ax.hist([p[1] for p in pass_success_probability], bins=8)
    ax.set_xlabel('Pass success probability')
    ax.set_ylabel('Frequency')

    pass_success_probability = sorted(pass_success_probability,
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
Exemplo n.º 4
0
def save_match_clip_pcf(hometeam,
                        awayteam,
                        fpath,
                        params,
                        fname='clip_test',
                        figax=None,
                        frames_per_second=25,
                        team_colors=('r', 'b'),
                        field_dimen=(106.0, 68.0),
                        include_player_velocities=False,
                        PlayerMarkerSize=10,
                        PlayerAlpha=0.7,
                        n_grid_cells_x=50):
    """ save_match_clip( hometeam, awayteam, fpath )
    
    Generates a movie from Metrica tracking data, saving it in the 'fpath' directory with name 'fname'
    
    Parameters
    -----------
        hometeam: home team tracking data DataFrame. Movie will be created from all rows in the DataFrame
        awayteam: away team tracking data DataFrame. The indices *must* match those of the hometeam DataFrame
        fpath: directory to save the movie
        fname: movie filename. Default is 'clip_test.mp4'
        fig,ax: Can be used to pass in the (fig,ax) objects of a previously generated pitch. Set to (fig,ax) to use an existing figure, or None (the default) to generate a new pitch plot,
        frames_per_second: frames per second to assume when generating the movie. Default is 25.
        team_colors: Tuple containing the team colors of the home & away team. Default is 'r' (red, home team) and 'b' (blue away team)
        field_dimen: tuple containing the length and width of the pitch in meters. Default is (106,68)
        include_player_velocities: Boolean variable that determines whether player velocities are also plotted (as quivers). Default is False
        PlayerMarkerSize: size of the individual player marlers. Default is 10
        PlayerAlpha: alpha (transparency) of player markers. Defaault is 0.7
        
    Returrns
    -----------
       fig,ax : figure and aixs objects (so that other data can be plotted onto the pitch)

    """
    # check that indices match first
    assert np.all(hometeam.index == awayteam.index
                  ), "Home and away team Dataframe indices must be the same"
    # in which case use home team index
    index = hometeam.index
    # Set figure and movie settings
    FFMpegWriter = animation.writers['ffmpeg']
    metadata = dict(title='Tracking Data',
                    artist='Matplotlib',
                    comment='Metrica tracking data clip')
    writer = FFMpegWriter(fps=frames_per_second, metadata=metadata)
    fname = fpath + '/' + fname + '.mp4'  # path and filename
    # create football pitch
    if figax is None:
        fig, ax = plot_pitch(field_color='white', field_dimen=field_dimen)
    else:
        fig, ax = figax
    fig.set_tight_layout(True)
    # Generate movie
    print("Generating movie...", end='')
    with writer.saving(fig, fname, 100):
        for i in index:
            print(i)
            figobjs = []
            for team, color in zip([hometeam.loc[i], awayteam.loc[i]],
                                   team_colors):
                x_columns = [
                    c for c in team.keys()
                    if c[-2:].lower() == '_x' and c != 'ball_x'
                ]  # column header for player x positions
                y_columns = [
                    c for c in team.keys()
                    if c[-2:].lower() == '_y' and c != 'ball_y'
                ]  # column header for player y positions
                objs, = ax.plot(team[x_columns],
                                team[y_columns],
                                color + 'o',
                                MarkerSize=PlayerMarkerSize,
                                alpha=PlayerAlpha)  # plot player positions
                figobjs.append(objs)
                if include_player_velocities:
                    vx_columns = ['{}_vx'.format(c[:-2]) for c in x_columns
                                  ]  # column header for player x positions
                    vy_columns = ['{}_vy'.format(c[:-2]) for c in y_columns
                                  ]  # column header for player y positions
                    objs = ax.quiver(team[x_columns],
                                     team[y_columns],
                                     team[vx_columns],
                                     team[vy_columns],
                                     color=color,
                                     scale_units='inches',
                                     scale=10.,
                                     width=0.0015,
                                     headlength=5,
                                     headwidth=3,
                                     alpha=PlayerAlpha)
                    figobjs.append(objs)
            # plot ball
            n_grid_cells_y = int(n_grid_cells_x * field_dimen[1] /
                                 field_dimen[0])
            xgrid = np.linspace(-field_dimen[0] / 2., field_dimen[0] / 2.,
                                n_grid_cells_x)
            ygrid = np.linspace(-field_dimen[1] / 2., field_dimen[1] / 2.,
                                n_grid_cells_y)
            # initialise pitch control grids for attacking and defending teams
            PPCFa = np.zeros(shape=(len(ygrid), len(xgrid)))
            PPCFd = np.zeros(shape=(len(ygrid), len(xgrid)))
            ball_start_pos = np.array(
                [hometeam.loc[i]['ball_x'], hometeam.loc[i]['ball_y']])
            attacking_players = mpc.initialise_players(hometeam.loc[i], 'Home',
                                                       params)
            defending_players = mpc.initialise_players(awayteam.loc[i], 'Away',
                                                       params)
            for k in range(len(ygrid)):
                for j in range(len(xgrid)):
                    target_position = np.array([xgrid[j], ygrid[k]])
                    PPCFa[k,
                          j], PPCFd[k,
                                    j] = mpc.calculate_pitch_control_at_target(
                                        target_position, attacking_players,
                                        defending_players, ball_start_pos,
                                        params)
            # check probabilitiy sums within convergence
            checksum = np.sum(PPCFa + PPCFd) / float(
                n_grid_cells_y * n_grid_cells_x)
            assert 1 - checksum < params[
                'model_converge_tol'], "Checksum failed: %1.3f" % (1 -
                                                                   checksum)
            pcf = ax.imshow(np.flipud(PPCFa),
                            extent=(np.amin(xgrid), np.amax(xgrid),
                                    np.amin(ygrid), np.amax(ygrid)),
                            interpolation='hanning',
                            vmin=0.0,
                            vmax=1.0,
                            cmap='bwr',
                            alpha=0.5)
            figobjs.append(pcf)
            objs, = ax.plot(team['ball_x'],
                            team['ball_y'],
                            'ko',
                            MarkerSize=6,
                            alpha=1.0,
                            LineWidth=0)
            figobjs.append(objs)
            # include match time at the top
            frame_minute = int(team['Time [s]'] / 60.)
            frame_second = (team['Time [s]'] / 60. - frame_minute) * 60.
            timestring = "%d:%1.2f" % (frame_minute, frame_second)
            objs = ax.text(-2.5,
                           field_dimen[1] / 2. + 1.,
                           timestring,
                           fontsize=14)
            figobjs.append(objs)
            writer.grab_frame()
            # Delete all axis objects (other than pitch lines) in preperation for next frame
            for figobj in figobjs:
                figobj.remove()
    print("done")
    plt.clf()
    plt.close(fig)
Exemplo n.º 5
0
#%%
event_number = 822
# 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)

#%%