Example #1
0
def question1():
    # set up initial path to data
    DATADIR = '/Users/Kafka/PycharmProjects/analytics/metrica/'
    game_id = 2  # let's look at sample match 2

    # read in the event data
    events = mio.read_event_data(DATADIR, game_id)
    # unit conversion
    events = mio.to_metric_coordinates(events)

    # Get all shots
    shots = events[events['Type'] == 'SHOT']

    # Get the shots that led to a goal
    all_goals = shots[shots['Subtype'].str.contains('-GOAL')].copy()

    # Add a column event 'Minute' to the data frame
    all_goals['Minute'] = all_goals['Start Time [s]'] / 60.

    # Plotting pitch
    fig, ax = mviz.plot_pitch()

    # point of shot
    ax.plot(events.loc[823]['Start X'], events.loc[823]['Start Y'], 'ro')
    # negative as it is for the away team and it make it easier to differentiate
    ax.plot(-events.loc[1118]['Start X'], -events.loc[1118]['Start Y'], 'bo')

    # Shot direction
    ax.annotate("",
                xy=events.loc[823][['End X', 'End Y']],
                xytext=events.loc[823][['Start X', 'Start Y']],
                alpha=0.6,
                arrowprops=dict(arrowstyle="->", color='r'))
    ax.annotate("",
                xy=-events.loc[1118][['End X', 'End Y']],
                xytext=-events.loc[1118][['Start X', 'Start Y']],
                alpha=0.6,
                arrowprops=dict(arrowstyle="->", color='b'))

    # plot passing move in run up to goal
    mviz.plot_events(events.loc[818:822],
                     indicators=['Marker', 'Arrow'],
                     annotate=True,
                     figax=(fig, ax))
    mviz.plot_events(events.loc[1109:1111],
                     indicators=['Marker', 'Arrow'],
                     color='b',
                     annotate=True,
                     neg=True,
                     figax=(fig, ax))
    mviz.plot_events(events.loc[1115:1116],
                     indicators=['Marker', 'Arrow'],
                     color='b',
                     annotate=True,
                     neg=True,
                     figax=(fig, ax))

    fig.savefig('question1.pdf', dpi=100)
    fig.show()
Example #2
0
def question2():
    # set up initial path to data
    DATADIR = '/Users/Kafka/PycharmProjects/analytics/metrica/'
    game_id = 2  # let's look at sample match 2

    # read in the event data
    events = mio.read_event_data(DATADIR, game_id)
    # unit conversion
    events = mio.to_metric_coordinates(events)

    # Get all shots
    shots = events[events['Type'] == 'SHOT']

    # Get the shots that led to a goal
    all_goals = shots[shots['Subtype'].str.contains('-GOAL')].copy()

    # Add a column event 'Minute' to the data frame
    all_goals['Minute'] = all_goals['Start Time [s]'] / 60.

    # Plotting pitch
    fig, ax = mviz.plot_pitch()

    for i in shots.index[shots['From'] == 'Player9'].tolist():
        if 'OFF' in events.loc[i]['Subtype']:
            arr = "-[, widthB=.4"
            clr = 'r'
        else:
            arr = "->"
            clr = 'b'

        ax.plot(events.loc[i]['Start X'], events.loc[i]['Start Y'], 'ro')

        # Shot direction
        ax.annotate("",
                    xy=events.loc[i][['End X', 'End Y']],
                    xytext=events.loc[i][['Start X', 'Start Y']],
                    alpha=0.6,
                    arrowprops=dict(arrowstyle=arr, color=clr))

    fig.savefig('question2.pdf', dpi=100)
    fig.show()
Example #3
0
def question3():
    # set up initial path to data
    DATADIR = '/Users/Kafka/PycharmProjects/analytics/metrica/'
    game_id = 2  # let's look at sample match 2

    # read in the event data
    events = mio.read_event_data(DATADIR, game_id)
    # unit conversion
    events = mio.to_metric_coordinates(events)

    # Plot some player trajectories (players 11,1,2,3,4)
    fig, ax = mviz.plot_pitch()

    goal_event_id = 1118

    # READING IN TRACKING DATA
    tracking_home = mio.tracking_data(DATADIR, game_id, 'Home')
    tracking_away = mio.tracking_data(DATADIR, game_id, 'Away')

    # Convert positions from metrica units to meters
    tracking_home = mio.to_metric_coordinates(tracking_home)
    tracking_away = mio.to_metric_coordinates(tracking_away)

    # PLOT POISTIONS AT GOAL
    fig, ax = mviz.plot_events(events.loc[goal_event_id:goal_event_id],
                               indicators=['Marker', 'Arrow'],
                               annotate=True)
    goal_frame = events.loc[goal_event_id]['Start Frame']
    fig, ax = mviz.plot_frame(tracking_home.loc[goal_frame],
                              tracking_away.loc[goal_frame],
                              figax=(fig, ax))

    fig.savefig('question3.pdf', dpi=100)
    fig.show()
Example #4
0
def question4():
    # set up initial path to data
    DATADIR = '/Users/Kafka/PycharmProjects/analytics/metrica/'
    game_id = 2  # let's look at sample match 2

    # READING IN TRACKING DATA
    tracking_home = mio.tracking_data(DATADIR, game_id, 'Home')
    tracking_away = mio.tracking_data(DATADIR, game_id, 'Away')

    # Convert positions from metrica units to meters
    tracking_home = mio.to_metric_coordinates(tracking_home)
    tracking_away = mio.to_metric_coordinates(tracking_away)

    # difference for every row in the first half absolute value and summed. Then added to the second half data.
    home_tot_distance = tracking_home[tracking_home['Period'] == 1].diff().abs(
    ).sum() + tracking_home[tracking_home['Period'] == 2].diff().abs().sum()

    # Removing unnecessary columns
    home_tot_distance = home_tot_distance.drop(
        ['Period', 'Time [s]', 'ball_x', 'ball_y'])

    # seperating x and y coords for each player and zipping the values together and summing.
    # This could be made more efficient.
    home_tot_distance = list(
        zip(home_tot_distance[['_y' in s for s in home_tot_distance.index]],
            home_tot_distance[['_x' in s for s in home_tot_distance.index]]))
    home_tot_distance = [x + y for (x, y) in home_tot_distance]

    away_tot_distance = tracking_away[tracking_away['Period'] == 1].diff().abs(
    ).sum() + tracking_away[tracking_away['Period'] == 2].diff().abs().sum()
    away_tot_distance = away_tot_distance.drop(
        ['Period', 'Time [s]', 'ball_x', 'ball_y'])

    # seperating x and y coords for each player and zipping the values together and summing.
    # This could be made more efficient.
    away_tot_distance = list(
        zip(away_tot_distance[['_y' in s for s in away_tot_distance.index]],
            away_tot_distance[['_x' in s for s in away_tot_distance.index]]))
    away_tot_distance = [x + y for (x, y) in away_tot_distance]
Example #5
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
Example #6
0
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.,
            68.,
        ),
        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
Example #7
0
def plot_EPV_for_event(event_id,
                       events,
                       tracking_home,
                       tracking_away,
                       PPCF,
                       EPV,
                       alpha=0.7,
                       include_player_velocities=True,
                       annotate=False,
                       autoscale=0.1,
                       contours=False,
                       field_dimen=(106.0, 68)):
    """ plot_EPV_for_event( event_id, events,  tracking_home, tracking_away, PPCF, EPV, alpha, include_player_velocities, annotate, autoscale, contours, field_dimen)
    
    Plots the EPVxPitchControl surface at the instant of the event given by the event_id. Player and ball positions are overlaid.
    
    Parameters
    -----------
        event_id: Index (not row) of the event that describes the instant at which the pitch control surface should be calculated
        events: Dataframe containing the event data
        tracking_home: (entire) tracking DataFrame for the Home team
        tracking_away: (entire) tracking DataFrame for the Away team
        PPCF: Pitch control surface (dimen (n_grid_cells_x,n_grid_cells_y) ) containing pitch control probability for the attcking team (as returned by the generate_pitch_control_for_event in Metrica_PitchControl)
        EPV: Expected Possession Value surface. EPV is the probability that a possession will end with a goal given the current location of the ball. 
             The EPV surface is saved in the FoT github repo and can be loaded using Metrica_EPV.load_EPV_grid()
        alpha: alpha (transparency) of player markers. Default is 0.7
        include_player_velocities: Boolean variable that determines whether player velocities are also plotted (as quivers). Default is False
        annotate: Boolean variable that determines with player jersey numbers are added to the plot (default is False)
        autoscale: If True, use the max of surface to define the colorscale of the image. If set to a value [0-1], uses this as the maximum of the color scale.
        field_dimen: tuple containing the length and width of the pitch in meters. Default is (106,68)
        
    Returrns
    -----------
       fig,ax : figure and aixs objects (so that other data can be plotted onto the pitch)

    """

    # pick a pass at which to generate the pitch control surface
    pass_frame = events.loc[event_id]['Start Frame']
    pass_team = events.loc[event_id].Team

    # plot frame and event
    fig, ax = plot_pitch(field_color='white', field_dimen=field_dimen)
    plot_frame(tracking_home.loc[pass_frame],
               tracking_away.loc[pass_frame],
               figax=(fig, ax),
               PlayerAlpha=alpha,
               include_player_velocities=include_player_velocities,
               annotate=annotate)
    plot_events(events.loc[event_id:event_id],
                figax=(fig, ax),
                indicators=['Marker', 'Arrow'],
                annotate=False,
                color='k',
                alpha=1)

    # plot pitch control surface
    if pass_team == 'Home':
        cmap = 'Reds'
        lcolor = 'r'
        EPV = np.fliplr(EPV) if mio.find_playing_direction(
            tracking_home, 'Home') == -1 else EPV
    else:
        cmap = 'Blues'
        lcolor = 'b'
        EPV = np.fliplr(EPV) if mio.find_playing_direction(
            tracking_away, 'Away') == -1 else EPV

    EPVxPPCF = PPCF * EPV

    if autoscale is True:
        vmax = np.max(EPVxPPCF) * 2.
    elif autoscale >= 0 and autoscale <= 1:
        vmax = autoscale
    else:
        assert False, "'autoscale' must be either {True or between 0 and 1}"

    ax.imshow(np.flipud(EPVxPPCF),
              extent=(-field_dimen[0] / 2., field_dimen[0] / 2.,
                      -field_dimen[1] / 2., field_dimen[1] / 2.),
              interpolation='spline36',
              vmin=0.0,
              vmax=vmax,
              cmap=cmap,
              alpha=0.7)

    if contours:
        ax.contour(EPVxPPCF,
                   extent=(-field_dimen[0] / 2., field_dimen[0] / 2.,
                           -field_dimen[1] / 2., field_dimen[1] / 2.),
                   levels=np.array([0.75]) * np.max(EPVxPPCF),
                   colors=lcolor,
                   alpha=1.0)

    return fig, ax
Homework answers for lesson 4 of "Friends of Tracking" #FoT

Data can be found at: https://github.com/metrica-sports/sample-data

@author: Laurie Shaw (@EightyFivePoint)
"""

import LS_functions.Metrica_IO as mio
import LS_functions.Metrica_Viz as mviz

# set up initial path to data
DATADIR = '/Users/Kafka/PycharmProjects/analytics/metrica'
game_id = 2  # let's look at sample match 2

# read in the event data
events = mio.read_event_data(DATADIR, game_id)

# count the number of each event type in the data
print(events['Type'].value_counts())

# Bit of housekeeping: unit conversion from metric data units to meters
# What this really means is that the is the center spot.
# Top right (x,y) = 53,34
# Bottom right (x,y) = 53,-34
# bottom left (x,y) = -53,-34
# Top right (x,y) = -53,34
events = mio.to_metric_coordinates(events)

# Get events by team
home_events = events[events['Team'] == 'Home']
away_events = events[events['Team'] == 'Away']