def yards_to_go(gid, pid, prechecked_gid=False, prechecked_pid=False): """ Finds the distance needed (in yards) to achieve a first down Parameters ---------- gid: an integer of a game_id pid: an integer of a play_id prechecked_gid: a boolean of whether or not the game ID has been checked before being passed to the function prechecked_pid: a boolean of whether or not the play ID has been checked before being passed to the function Returns ------- yds_to_go: an integer number of yards needed on a play to achieve a first down """ if not prechecked_gid: # Validate the game ID gid = check.game_id(gid) prechecked_gid = True if not prechecked_pid: # Validate the play ID pid = check.play_id(gid, pid) prechecked_pid = True # Load in the plays data play = load.plays_data(gid, pid, prechecked_gid, prechecked_pid) # Get the number of yards needed for a first down yds_to_go = play['yds_to_go'].iloc[0] return yds_to_go
def line_of_scrimmage(gid, pid, prechecked_gid=False, prechecked_pid=False): """ Finds the line of scrimmage for a specified play Parameters ---------- gid: an integer of a game_id pid: an integer of a play_id prechecked_gid: a boolean of whether or not the game ID has been checked before being passed to the function prechecked_pid: a boolean of whether or not the play ID has been checked before being passed to the function Returns ------- los: a float of the absolute yardline of the line of scrimmage """ if not prechecked_gid: # Validate the game ID gid = check.game_id(gid) prechecked_gid = True if not prechecked_pid: # Validate the play ID pid = check.play_id(gid, pid) prechecked_pid = True # Load in the plays data play = load.plays_data(gid, pid, prechecked_gid, prechecked_pid) # Get the line of scrimmage los = play['absolute_yard_line'].iloc[0] return los
def n_frames(gid, pid, tracking=pd.DataFrame(), prechecked_gid=False, prechecked_pid=False): """ Finds the number of frames recorded for a particular play Parameters ---------- gid: an integer of a game_id pid: an integer of a play_id tracking: a set of tracking information pertaining to a particular play. If none is provided, the entire tracking set will be used. This is the default prechecked_gid: a boolean of whether or not the game ID has been checked before being passed to the function prechecked_pid: a boolean of whether or not the play ID has been checked before being passed to the function Returns ------- num_frames: an integer representing how many frames were recorded for the play """ if not prechecked_gid: # Validate the game ID gid = check.game_id(gid) prechecked_gid = True if not prechecked_pid: # Validate the play ID pid = check.play_id(gid, pid) prechecked_pid = True # If no tracking information is provided, load the tracking information # for the week containing the desired play if tracking.empty: week = game_week(gid) tracking = load.tracking_data(gid, pid, week, prechecked_gid, prechecked_pid, prechecked_week=True) # Get the last frame of the play num_frames = tracking['frame_id'].max() return num_frames
def play_gif(gid=0, pid=0, home='', away='', prechecked_gid=False, prechecked_pid=False, tracking=pd.DataFrame()): # If a game ID is provided, get the home and away team from the provided # game ID if gid != 0: # Start by checking the game ID if it is provided but not yet checked if not prechecked_gid: gid = check.game_id(gid) prechecked_gid = True # Get the home and away teams for the game home, away = find.game_teams(gid) # If no game ID provided, and the home team is 'NFL', set home and away # to NFC and AFC respectively. Otherwise, check to make sure the teams are # legit else: home = home.upper() away = away.upper() if home == 'NFL': home = 'NFC' away = 'AFC' else: home = check.team_code(home) away = check.team_code(away) gid = find.game_id(home, away) # Next, check the play ID if it has not already been checked if not prechecked_pid: pid = check.play_id(gid, pid, prechecked_gid) prechecked_pid = True # If tracking isn't supplied, load all relevant tracking data if tracking.empty: tracking = merge.tracking_and_plays(gid, pid) # Get the number of frames in the play n_frames = find.n_frames(gid=gid, pid=pid, tracking=tracking, prechecked_gid=True, prechecked_pid=True) # Make the temporary directory to hold static images file_ops.make_gif_temp_dir(gid, pid) # Make each frame as a static image for i in np.arange(1, n_frames + 1): print(f'Processing frame {i} of {n_frames}') fig, ax = play_frame(gid, pid, frame_no=i, prechecked_gid=True, prechecked_pid=True, tracking=tracking, prechecked_frame=True) if i < 10: fname = os.path.join('img', 'temp', f'{gid}_{pid}', f'{gid}_{pid}_000{i}.png') elif i < 100: fname = os.path.join('img', 'temp', f'{gid}_{pid}', f'{gid}_{pid}_00{i}.png') else: fname = os.path.join('img', 'temp', f'{gid}_{pid}', f'{gid}_{pid}_0{i}.png') plt.savefig(f'{fname}', bbox_inches='tight', pad_inches=0) try: gif_fname = tracking['down_dist_summary'].values[0] + '.gif' except: gif_fname = str(pid) + '.gif' # Collect the static images images = file_ops.collect_gif_play_frames(gid, pid) # Make and save the gif file_ops.make_gif(gid, pid, images, fname=gif_fname) # Delete the temporary directory that holds all static images. file_ops.remove_temp_static_frame_directory(gid, pid) return None
def play_frame(gid=0, pid=0, home='', away='', frame_no=0, plot_los=True, plot_first_down_marker=True, plot_arrows=True, prechecked_gid=False, prechecked_pid=False, prechecked_frame=False, tracking=pd.DataFrame()): """ Draw a frame of a given play. Teams are either supplied via the home and away arguments, or by looking them up from the game_id provided by the gid argument Parameters ---------- gid: an int representing the game_id pid: an int representing the play_id home: a string of the home team's code. Not necessary if a game_id is provided away: a string of the away team's code. Not necessary if a game_id is provided frame_no: the number of the frame to plot plot_los: a boolean of whether or not to plot the line of scrimmage on the plot plot_first_down_marker: a boolean of whether or not to plot the first down line on the plot prechecked_frame: a boolean indicating whether or not it's okay to skip the frame validation. Defaulting to False, but should be set to True when using the draw_play_gif() function tracking: a dataframe of tracking data that can be used to speed up plotting Returns ------- fig, ax: the figure and axes objects (respectively) """ if gid != 0: # Start by checking the game ID if it is provided but not yet checked if not prechecked_gid: gid = check.game_id(gid) prechecked_gid = True # Get the home and away teams for the game home, away = find.game_teams(gid) # If no game ID provided, and the home team is 'NFL', set home and away # to NFC and AFC respectively. Otherwise, check to make sure the teams are # legit else: home = home.upper() away = away.upper() if home == 'NFL': home = 'NFC' away = 'AFC' else: home = check.team_code(home) away = check.team_code(away) gid = find.game_id(home, away) # Next, check the play ID if it has not already been checked if not prechecked_pid: pid = check.play_id(gid, pid, prechecked_gid) prechecked_pid = True # If tracking isn't supplied, load all relevant tracking data if tracking.empty: tracking = merge.tracking_and_plays(gid, pid) if not prechecked_frame: frame_no = check.frame_no(gid, pid, frame_no, tracking) # Start prepping the data for the plot. Primarily, the jersey numbers' # rotation angle based on team and play direction tracking['jersey_num_orientation'] = orient_jersey_num( gid, pid, prechecked_gid, prechecked_pid, tracking) # Split the frame's data into the home team, the away team, and the ball's # data (respectively) home_frame = tracking[(tracking['team'] == 'home') & (tracking['frame_id'] == frame_no)] away_frame = tracking[(tracking['team'] == 'away') & (tracking['frame_id'] == frame_no)] ball_frame = tracking[(tracking['team'] == 'football') & (tracking['frame_id'] == frame_no)] # Get the hex color information about each team to use to make the plot teams_info = load.teams_data() home_info = teams_info[teams_info['team_code'] == home] away_info = teams_info[teams_info['team_code'] == away] home_uni_base = home_info['home_uni_base'].iloc[0] home_uni_highlight = home_info['home_uni_highlight'].iloc[0] home_uni_number = home_info['home_uni_number'].iloc[0] home_uni_number_highlight = home_info['home_uni_number_highlight'].iloc[0] away_uni_base = away_info['away_uni_base'].iloc[0] away_uni_highlight = away_info['away_uni_highlight'].iloc[0] away_uni_number = away_info['away_uni_number'].iloc[0] away_uni_number_highlight = away_info['away_uni_number_highlight'].iloc[0] # If the line of scrimmage is to be plotted, determine its position if plot_los: los = find.line_of_scrimmage(gid, pid) los = pd.DataFrame({ 'x': [ los - (2 / 12), los + (2 / 12), los + (2 / 12), los - (2 / 12), los - (2 / 12) ], 'y': [1 / 9, 1 / 9, 53 + (2 / 9), 53 + (2 / 9), 1 / 9] }) # If the first down line is to be plotted, determine its position if plot_first_down_marker: first_down = find.first_down_line(gid, pid, tracking, prechecked_gid, prechecked_pid) first_down_line = pd.DataFrame({ 'x': [ first_down - (2 / 12), first_down + (2 / 12), first_down + (2 / 12), first_down - (2 / 12), first_down - (2 / 12) ], 'y': [1 / 9, 1 / 9, 53 + (2 / 9), 53 + (2 / 9), 1 / 9] }) # Draw the field fig, ax = field(gid) # Plot the home team's players home_frame.plot(x='player_x', y='player_y', kind='scatter', ax=ax, color=home_uni_base, s=800, edgecolor=home_uni_highlight, linewidth=2, zorder=15) # Add the jersey numbers for the home team for i, player in home_frame.iterrows(): ax.text( x=player['player_x'], y=player['player_y'], s=str(int(player['player_no'])), fontsize=15, color=home_uni_number, path_effects=[ pe.withStroke(linewidth=3, foreground=home_uni_number_highlight) ], fontweight='bold', rotation=player['jersey_num_orientation'], zorder=20, fontdict={ 'ha': 'center', 'va': 'center' }, ) if plot_arrows: ax.arrow(x=player['player_x'], y=player['player_y'], dx=3 * math.cos(player['player_orientation']), dy=3 * math.sin(player['player_orientation']), length_includes_head=True, width=0.3, color=home_uni_highlight, zorder=14) # Plot the away team's players away_frame.plot('player_x', 'player_y', kind='scatter', ax=ax, color=away_uni_base, s=800, edgecolor=away_uni_highlight, linewidth=2, zorder=15) # Add the jersey numbers for the away team for i, player in away_frame.iterrows(): ax.text( x=player['player_x'], y=player['player_y'], s=str(int(player['player_no'])), fontsize=15, color=away_uni_number, path_effects=[ pe.withStroke(linewidth=3, foreground=away_uni_number_highlight) ], fontweight='bold', rotation=player['jersey_num_orientation'], zorder=20, fontdict={ 'ha': 'center', 'va': 'center' }, ) if plot_arrows: ax.arrow(x=player['player_x'], y=player['player_y'], dx=3 * math.cos(player['player_orientation']), dy=3 * math.sin(player['player_orientation']), length_includes_head=True, width=0.3, color=away_uni_highlight, zorder=14) # Plot the ball ball_frame.plot('player_x', 'player_y', kind='scatter', ax=ax, color='#624a2e', s=100, edgecolor='#000000', linewidth=2, zorder=15) ax.fill(los['x'], los['y'], '#183ec1') ax.fill(first_down_line['x'], first_down_line['y'], '#ffcb05') return fig, ax
def orient_jersey_num(gid, pid, prechecked_gid=False, prechecked_pid=False, tracking=pd.DataFrame()): """ Manipulate the tracking data to get the correct orientation for the jersey numbers of players involved in the play that will be plotted Parameters ---------- gid: an integer of a game_id pid: an integer of a play_id prechecked_gid: a boolean of whether or not the game ID has been checked before being passed to the function prechecked_pid: a boolean of whether or not the play ID has been checked before being passed to the function tracking: a dataframe of tracking data that can be used to speed up data loading Returns ------- tracking: a dataframe of tracking data with the proper orientation for the jersey numbers of the players involved """ # If the game ID is not already checked, check that first if not prechecked_gid: gid = check.game_id(gid) prechecked_gid = True # Now that the game ID is checked, move to the play ID. If that is not # already checked, check that next. prechecked_gid is now True regardless # of its initially passed value since the game ID has been checked in the # first if statement if not prechecked_pid: pid = check.play_id(gid, pid, prechecked_gid) # Now that the game ID and play ID have been checked, load the tracking # data for the play in the provided game. This will load all tracking data # for the play. It will if tracking.empty: tracking = merge.tracking_and_plays(gid, pid) tracking.loc[tracking['team'] == 'football', 'jersey_num_orientation'] = 0 tracking.loc[(tracking['team'] == 'home') & (tracking['play_direction'] == 'right'), 'jersey_num_orientation'] = -90 tracking.loc[(tracking['team'] == 'away') & (tracking['play_direction'] == 'right'), 'jersey_num_orientation'] = 90 tracking.loc[(tracking['team'] == 'home') & (tracking['play_direction'] == 'left'), 'jersey_num_orientation'] = 90 tracking.loc[(tracking['team'] == 'away') & (tracking['play_direction'] == 'left'), 'jersey_num_orientation'] = -90 return tracking['jersey_num_orientation']
def tracking_data(gid = 0, pid = 0, week = 0, prechecked_gid = False, prechecked_pid = False, prechecked_week = False): """ Loads the tracking information provided for a specified week Parameters ---------- gid: an integer of a game_id pid: an integer of a play_id week: an integer of which week's tracking data to return. A value of 0 implies to return all weeks' tracking. The default is 0. prechecked_gid: a boolean of whether or not the game ID has been checked before being passed to the function prechecked_pid: a boolean of whether or not the play ID has been checked before being passed to the function Returns ------- trk: a data frame containing a cleaned, renamed copy of tracking information for the specified week """ # Check which week to load. If neither the game ID nor week number are # passed to the function, load all weeks (this is slow) if gid == 0 and week == 0: trk = pd.DataFrame() for week in range(1, 17): print(f'Loading week {week}...', end = '\r') week_file = os.path.join(fp.data_dir, f'week{week}.csv') this_week = pd.read_csv(week_file) trk = pd.concat([trk, this_week]) else: # If the game ID is provided, but not checked, check the game ID first if gid != 0: if prechecked_gid == False: gid = check.game_id(gid) else: pass if week == 0: week = find.game_week(gid) prechecked_week = True if week != 0: # If the week is prechecked, that's great. If not, check the week if prechecked_week == True: pass else: week = check.week_number(week) # If the week number is not supplied, set the week number elif gid != 0 : week = check.week_number(week) # If the play ID is provided, but not checked, check the play ID next if pid != 0: if prechecked_pid == False: pid = check.play_id(gid, pid) else: pass # Now that the relevant data has all been checked, load the dataset # accordingly if gid != 0 and pid != 0: # If there is a game ID and play ID supplied, load only the # tracking information for this play in this game week_file = os.path.join(fp.data_dir, f'week{week}.csv') trk = pd.read_csv(week_file)[lambda x: (x['gameId'] == gid) & (x['playId'] == pid)] elif gid != 0 and pid == 0: # If there is a game ID but not a play ID supplied, load all # tracking data from all plays of this game week_file = os.path.join(fp.data_dir, f'week{week}.csv') trk = pd.read_csv(week_file)[lambda x: x['gameId'] == gid] elif gid == 0 and pid != 0: # If there is a play Id but not a game ID supplied, load all # tracking data from all plays in the week of this game with a # matching play ID week_file = os.path.join(fp.data_dir, f'week{week}.csv') trk = pd.read_csv(week_file)[lambda x: (x['playID'] == pid)] else: # If there's no game ID or play ID supplied, load all tracking # data for the week week_file = os.path.join(fp.data_dir, f'week{week}.csv') trk = pd.read_csv(week_file) # Rename columns trk.columns = [ 'time', 'player_x', 'player_y', 'player_speed', 'player_acceleration', 'distance', 'player_orientation', 'player_direction', 'event_str', 'player_id', 'player_name', 'player_no', 'player_position', 'frame_id', 'team', 'game_id', 'play_id', 'play_direction', 'route_type' ] # Correct the angular variables to be plottable (needs to be in radians) trk['player_orientation'] = np.mod(90 - trk['player_orientation'], 360) trk['player_orientation'] *= math.pi / 180 trk['player_direction'] = np.mod(90 - trk['player_direction'], 360) trk['player_direction'] *= math.pi / 180 return trk
def first_down_line(gid, pid, tracking=pd.DataFrame(), prechecked_gid=False, prechecked_pid=False): """ Finds what yardline is needed to be gained to achieve a first down Parameters ---------- gid: an integer of a game_id pid: an integer of a play_id tracking: a set of tracking information pertaining to a particular play. If none is provided, the entire tracking set will be used. This is the default prechecked_gid: a boolean of whether or not the game ID has been checked before being passed to the function prechecked_pid: a boolean of whether or not the play ID has been checked before being passed to the function Returns ------- first_down_yardline: a float representing the absolute yardline needed to achieve a first down """ if not prechecked_gid: # Validate the game ID gid = check.game_id(gid) prechecked_gid = True if not prechecked_pid: # Validate the play ID pid = check.play_id(gid, pid) prechecked_pid = True # Load in the schedule data games = load.games_data(gid, prechecked_gid) # Get the week of the game so that the correct tracking information can be # loaded week = games.loc[games['game_id'] == gid, 'week'].iloc[0] # Get the line of scrimmage and number of yards needed to achieve a first # down los = line_of_scrimmage(gid, pid) distance_to_first = yards_to_go(gid, pid) # Load in the appropriate tracking data, then subset to only be for the # desired play if tracking.empty: tracking = load.tracking_data(gid, pid, week, prechecked_gid=True, prechecked_pid=True, prechecked_week=True) # Get the direction of play. If the play is going right, yards will be # added, otherwise they will be subtracted play_direction = tracking['play_direction'].iloc[0] # Calculate the yardline needed to be gained to achieve a first down if play_direction == 'right': first_down_yardline = los + distance_to_first else: first_down_yardline = los - distance_to_first return first_down_yardline
def play_id(gid=0, home='', away='', play_info={}, prechecked_gid=False): """ Finds the play ID of a particular play Parameters ---------- gid: an integer of a game_id home: a string representing the home team's team code away: a string representing the away team's team code play_info: a dictionary of parameters to use for subsetting. The keys MUST be columns in the plays data to be used. If not, they will be ignored prechecked_gid: a boolean of whether or not the game ID has been prechecked Returns ------- pid: an integer of a play_id """ # Game ID should be the primary lookup tool, so start with loading the # game's data if this is passed if gid != 0: # If the game ID is not already checked, check the game ID first if not prechecked_gid: gid = check.game_id(gid) prechecked_gid = True # If the game ID is not passed, then try to get a game ID based on the home # and away team. If this yields nothing, then load all games if home != '' or away != '': home = check.team_code(home) away = check.team_code(away) gid = game_id(home, away) prechecked_gid = True # Load in plays from the identified game, or from all games if game ID = 0 plays_from_game = load.plays_data(gid=gid, prechecked_gid=prechecked_gid) # Subset by the information about the play in the parameter play_info if bool(play_info): for key, val in play_info.items(): # If the desired parameter is not in the columns of the plays data, # alert user and skip this subsetting parameter if key not in plays_from_game.columns: print(f'{key} is not a valid column to use for subsetting as' ' it does not appear in the dataset.') continue # If the value passed in the plays_info dictionary is a list, use # the .isin() method for subsetting if type(val) == list: plays_from_game = plays_from_game[ plays_from_game[f'{key}'].isin(val)] # Otherwise, use the key and value alone else: plays_from_game = plays_from_game[plays_from_game[f'{key}'] == val] # If the passed parameters are enough to identify the play, there # should only be one play ID remaining. Return this value if len(plays_from_game) == 1: pid = plays_from_game['play_id'].values[0] else: for i, play in plays_from_game.iterrows(): print(f'{play.game_id} -- {play.play_id} -- ' f'{play.down_dist_summary}') gid = input('Which game ID were you looking for?\nGame ID: ') pid = input('Which play of the above are you looking for?\nPlay ID: ') gid = check.game_id(gid) prechecked_gid = True pid = check.play_id(gid, pid, prechecked_gid) return pid