{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Fatigue Factors and Sprint Speed on Deep Kickoffs (2020 Season)\n<img src=\"https://c.tenor.com/gAIIE5tIz4MAAAAd/superbowllii-superbowl.gif\" style=\"width:500px;\">\n\n## During each NLF kickoff (at the beginning of the half or after a score), each player on the kickoff coverage unit will sprint down the field to tackle the returner. Dhriti Yandpally's [notebook](https://www.kaggle.com/dhritiyandapally/speed-on-kickoff-plays-across-surfaces-python) determined each player's max speed reached during the initial sprint (before they are blocked / obstructed by the return team). Building off that work we perform some basic EDA on the impact of game duration and cumulative kickoffs on a players max speed on the kickoff coverage unit. We explore the difference across positions and suggest further statistical analysis that could be performed with the given data.","metadata":{}},{"cell_type":"markdown","source":"<a id=\"ReadData\"></a>\n# 1. Read Data","metadata":{}},{"cell_type":"code","source":"import numpy as np \nimport pandas as pd\n\nimport warnings\nwarnings.filterwarnings(\"ignore\")","metadata":{"_uuid":"051d70d956493feee0c6d64651c6a088724dca2a","_execution_state":"idle","execution":{"iopub.status.busy":"2022-01-07T22:23:55.70342Z","iopub.execute_input":"2022-01-07T22:23:55.703766Z","iopub.status.idle":"2022-01-07T22:23:55.731761Z","shell.execute_reply.started":"2022-01-07T22:23:55.703682Z","shell.execute_reply":"2022-01-07T22:23:55.731196Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Function to decrease data usage in notebook (from Baek Kyun Shin)\ndef downcast(df, verbose=True):\n    start_mem = df.memory_usage().sum() / 1024**2\n    for col in df.columns:\n        dtype_name = df[col].dtype.name\n        if dtype_name == 'object':\n            pass\n        elif dtype_name == 'bool':\n            df[col] = df[col].astype('int8')\n        elif dtype_name.startswith('int') or (df[col].round() == df[col]).all():\n            df[col] = pd.to_numeric(df[col], downcast='integer')\n        else:\n            df[col] = pd.to_numeric(df[col], downcast='float')\n    end_mem = df.memory_usage().sum() / 1024**2\n    if verbose:\n        print('{:.1f}% Compressed'.format(100 * (start_mem - end_mem) / start_mem))\n    \n    return df","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-01-07T22:23:59.440745Z","iopub.execute_input":"2022-01-07T22:23:59.441138Z","iopub.status.idle":"2022-01-07T22:23:59.448335Z","shell.execute_reply.started":"2022-01-07T22:23:59.441115Z","shell.execute_reply":"2022-01-07T22:23:59.447509Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#includes info on games\ndf_games = pd.read_csv(\"../input/nfl-big-data-bowl-2022/games.csv\")\ndf_games = downcast(df_games);\n\n#includes play-by-play info on specific plays\ndf_plays = pd.read_csv(\"../input/nfl-big-data-bowl-2022/plays.csv\")\ndf_plays = downcast(df_plays);\n\n#includes background info for players\ndf_players = pd.read_csv(\"../input/nfl-big-data-bowl-2022/players.csv\")\n\n#Reading tracking data (2020 only for this case)\ndf_tracking = pd.read_csv(\"../input/nfl-big-data-bowl-2022/tracking2020.csv\")\ndf_tracking = downcast(df_tracking);\n\n#includes scouting info on specific plays\ndf_PFFScouting = pd.read_csv(\"../input/nfl-big-data-bowl-2022/PFFScoutingData.csv\")\n\n#loading data from Lee Sharpe's public GitHub repository. It includes info on field surface.\ndf_leeSharpeGames = pd.read_csv(\"https://raw.githubusercontent.com/nflverse/nfldata/master/data/games.csv\")","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-01-07T22:24:02.055103Z","iopub.execute_input":"2022-01-07T22:24:02.055355Z","iopub.status.idle":"2022-01-07T22:24:44.404502Z","shell.execute_reply.started":"2022-01-07T22:24:02.055332Z","shell.execute_reply":"2022-01-07T22:24:44.403922Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_tracking.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 2. Compute the elapsed (actual) time as a potential factor in fatigue","metadata":{}},{"cell_type":"code","source":"#convert to datetime\ndf_tracking['time'] = df_tracking['time'].astype('datetime64[ns]')\n\n#find the start of the game by actual time\ndf_gameStartTimes = pd.DataFrame(df_tracking.groupby(['gameId'])['time'].min())\ndf_gameStartTimes.reset_index(inplace=True)\ndf_gameStartTimes.rename(columns={'time':'gameStartTime'}, inplace=True)\n\n#add the game start time to the tracking data\ndf_tracking = pd.merge(left=df_tracking, right=df_gameStartTimes, how= 'left', on= ['gameId'])\n\n#change type to datetime\ndf_tracking['gameStartTime'] = df_tracking['gameStartTime'].astype('datetime64[ns]')\n\n#compute the elapsed actual time in minutes\ndf_tracking['elapsedTime'] = (df_tracking['time'] - df_tracking['gameStartTime']).astype('timedelta64[m]')\n\n#create a table for elapsed time for the start of each play in each game\ndf_playElapseTime = pd.DataFrame(df_tracking[['gameId','playId','elapsedTime']].groupby(['gameId','playId'], as_index=False)['elapsedTime'].min())\ndf_playStartTime = pd.DataFrame(df_tracking[['gameId','playId','time']].groupby(['gameId','playId'], as_index=False)['time'].min())\n\ndf_playElapseTime['time'] = df_playStartTime.time \n\nprint(df_playElapseTime.head())","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:24:49.324409Z","iopub.execute_input":"2022-01-07T22:24:49.326486Z","iopub.status.idle":"2022-01-07T22:25:02.338593Z","shell.execute_reply.started":"2022-01-07T22:24:49.326453Z","shell.execute_reply":"2022-01-07T22:25:02.337379Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 3. Clean Data (from Dhriti Yandpally's [notebook](https://www.kaggle.com/dhritiyandapally/speed-on-kickoff-plays-across-surfaces-python))\n\n### To start the cleaning process, we must first use PFF scouting data to identify special teams safeties who are not actively advancing downfield. To be able to merge this information, we will use tracking data to get a map of a player's jersey number to their `nflId` in each game.","metadata":{}},{"cell_type":"code","source":"#using df_tracking to merge to jersey numbers\n\n#selecting variables of interest & dropping duplicates - jersey # is constant throughout game\ndf_jerseyMap = df_tracking.drop_duplicates(subset = [\"gameId\", \"team\", \"jerseyNumber\", \"nflId\"])\n\n#joining to games\ndf_jerseyMap = pd.merge(df_jerseyMap, df_games, left_on=['gameId'], right_on =['gameId'])\n\n#getting name of team\nconditions = [\n    (df_jerseyMap['team'] == \"home\"),\n    (df_jerseyMap['team'] != \"home\"),\n]\n\nvalues = [df_jerseyMap['homeTeamAbbr'], df_jerseyMap['visitorTeamAbbr']]\n\n#adjusting jersey number so that it includes 0 when < 10\ndf_jerseyMap['team'] = np.select(conditions, values)\n\ndf_jerseyMap['jerseyNumber'] = df_jerseyMap['jerseyNumber'].astype(str)\n\ndf_jerseyMap.loc[df_jerseyMap['jerseyNumber'].map(len) < 4, 'jerseyNumber'] = \"0\"+df_jerseyMap.loc[df_jerseyMap['jerseyNumber'].map(len) < 4, 'jerseyNumber'].str[:2]\n\ndf_jerseyMap['jerseyNumber'] = df_jerseyMap['jerseyNumber'].str[:2]\n\n#getting team and jersey\ndf_jerseyMap['teamJersey'] = df_jerseyMap['team'] + ' ' + df_jerseyMap['jerseyNumber'].str[:2]\n\n#map to merge nflId to teamJersey\ndf_jerseyMap = df_jerseyMap[['gameId', 'nflId', 'teamJersey']]\n\ndf_jerseyMap = df_jerseyMap.sort_values(['gameId', 'nflId', 'teamJersey'])","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-01-07T22:25:08.095333Z","iopub.execute_input":"2022-01-07T22:25:08.096006Z","iopub.status.idle":"2022-01-07T22:25:09.39643Z","shell.execute_reply.started":"2022-01-07T22:25:08.095971Z","shell.execute_reply":"2022-01-07T22:25:09.395342Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#dataframe will include gameId, playId and nflId for each special teams safety\ndf_PFF_specialTeamSafeties = df_PFFScouting.copy()\n\n#splitting into a column for each special teams safety\ndf_PFF_specialTeamSafeties[['teamJersey1', 'teamJersey2', 'teamJersey3', 'teamJersey4', 'teamJersey5', 'teamJersey6']] = df_PFF_specialTeamSafeties['specialTeamsSafeties'].str.split('; ',expand=True)\n\n#selecting jersey numbers for each team\ndf_PFF_specialTeamSafeties = df_PFF_specialTeamSafeties[['gameId', 'playId', 'teamJersey1', 'teamJersey2', 'teamJersey3', 'teamJersey4', 'teamJersey5', 'teamJersey6']]\n\n#gathering data\ndf_PFF_specialTeamSafeties = pd.melt(df_PFF_specialTeamSafeties, id_vars =['gameId', 'playId'], value_vars =['teamJersey1', 'teamJersey2', 'teamJersey3', 'teamJersey4', 'teamJersey5', 'teamJersey6'],\n               value_name = 'teamJersey')\n\n#dropping NA rows\ndf_PFF_specialTeamSafeties.dropna()\n\n#joining to jersey map\ndf_PFF_specialTeamSafeties = pd.merge(df_PFF_specialTeamSafeties, df_jerseyMap, on = ['gameId', 'teamJersey'])\n\n#selecting variables of interest\ndf_PFF_specialTeamSafeties = df_PFF_specialTeamSafeties[['gameId', 'playId', 'nflId']]\n\ndf_PFF_specialTeamSafeties = df_PFF_specialTeamSafeties.sort_values(['gameId', 'playId', 'nflId'])","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-01-07T22:25:17.345092Z","iopub.execute_input":"2022-01-07T22:25:17.345698Z","iopub.status.idle":"2022-01-07T22:25:17.490966Z","shell.execute_reply.started":"2022-01-07T22:25:17.345654Z","shell.execute_reply":"2022-01-07T22:25:17.490341Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Next, we will create a dataframe to only include deep kickoffs so that we can remove all unnecessary rows in the tracking data.","metadata":{}},{"cell_type":"code","source":"#creating data frame that will only include deep kickoffs\ndf_deepKickoffs = df_plays.copy()\n\n#joining the scouting data\ndf_deepKickoffs = pd.merge(df_deepKickoffs, df_PFFScouting, on = ['gameId', 'playId'])\n\n#filtering for kickoff plays only & deep kickoffs only\ndf_deepKickoffs = df_deepKickoffs[(df_deepKickoffs['specialTeamsPlayType'] == 'Kickoff') & (df_deepKickoffs['kickType'] == 'D')]\n\n#selecting variables of interest\ndf_deepKickoffs = df_deepKickoffs[['gameId', 'playId', 'kickerId', 'possessionTeam']]","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-01-07T22:25:21.485402Z","iopub.execute_input":"2022-01-07T22:25:21.485844Z","iopub.status.idle":"2022-01-07T22:25:21.55759Z","shell.execute_reply.started":"2022-01-07T22:25:21.485779Z","shell.execute_reply":"2022-01-07T22:25:21.556736Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Now, we will use the data frames we created to filter the tracking data. We will use `df_PFF_specialTeamSafeties` to filter out players who were special teams safeties and use `df_deepKickoffs` to remove plays where there was not a deep kickoff. For each player on each play in the the tracking data, we will filter for the first 40 frames which approximately corresponds to the initial sprint portion of the play. Over that interval, we calculate the maximum speed reached for each player in the play.","metadata":{}},{"cell_type":"code","source":"df_maxSpeeds = df_tracking.copy()\n\n#joining games\ndf_maxSpeeds = pd.merge(df_maxSpeeds, df_games, on = 'gameId')\n\n#using a join to remove special teams safeties\ndf_maxSpeeds = pd.merge(left = df_maxSpeeds, right = df_PFF_specialTeamSafeties, how='left', indicator=True, on = ['gameId', 'playId', 'nflId'])\n\ndf_maxSpeeds.loc[df_maxSpeeds._merge != 'left_only', :].head()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-01-07T22:25:24.502593Z","iopub.execute_input":"2022-01-07T22:25:24.502884Z","iopub.status.idle":"2022-01-07T22:25:56.81079Z","shell.execute_reply.started":"2022-01-07T22:25:24.502853Z","shell.execute_reply":"2022-01-07T22:25:56.810294Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_maxSpeeds = df_maxSpeeds.loc[df_maxSpeeds._merge == 'left_only', :].drop(columns = '_merge')\n\n#joining deep kickoffs\ndf_maxSpeeds = pd.merge(df_maxSpeeds, df_deepKickoffs, on = ['gameId', 'playId'])\n\n#removing the kicker from the tracking data\ndf_maxSpeeds = df_maxSpeeds[(df_maxSpeeds['kickerId'] != df_maxSpeeds['nflId']) &\n                            \n                            #player is on home team and kicking team is home\n                            ((df_maxSpeeds['team']=='home') & (df_maxSpeeds['possessionTeam'] == df_maxSpeeds['homeTeamAbbr']) |\n                             \n                             #or player is on away team and kicking team is away\n                            (df_maxSpeeds['team']=='away') & (df_maxSpeeds['possessionTeam'] == df_maxSpeeds['visitorTeamAbbr']))]\n\n#select variables of interest\ndf_maxSpeeds = df_maxSpeeds[['gameId', 'playId', 'frameId', 'nflId', 'event', 's','time']]\n\n#arranging by gameId, playId, frameId and nflId\ndf_maxSpeeds = df_maxSpeeds.sort_values(['gameId', 'playId', 'frameId'])\n\n#grouping by gameId, playId and nflId & filtering for frames after kickoff\ndf_maxSpeeds = df_maxSpeeds.loc[df_maxSpeeds.groupby(['gameId', 'playId']).event.transform(lambda z: np.cumsum(z.isin(['kickoff', 'free_kick'])) >= 1)]\n\n#grouping by gameId, playId and nflId & filtering for first 40 observations\ndf_maxSpeeds = df_maxSpeeds.groupby(['gameId', 'playId', 'nflId']).head(40).reset_index()\n\n#calculating max speed for given play / player\ndf_maxSpeeds = df_maxSpeeds.groupby(['gameId', 'playId', 'nflId']).s.apply(lambda z: z.max()).reset_index()\n\n#renaming speed column as maxSpeed\ndf_maxSpeeds = df_maxSpeeds.rename(columns={\"s\" : \"maxSpeed\"})","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-01-07T22:26:04.961251Z","iopub.execute_input":"2022-01-07T22:26:04.961488Z","iopub.status.idle":"2022-01-07T22:26:16.963929Z","shell.execute_reply.started":"2022-01-07T22:26:04.961456Z","shell.execute_reply":"2022-01-07T22:26:16.963017Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#merge the  play information of interest\ndf_maxSpeedPlays = pd.merge(left = df_maxSpeeds, right = df_plays, how='left', indicator=True, on = ['gameId', 'playId'])\n\n#select variables of interest\ndf_maxSpeedPlays = df_maxSpeedPlays[['gameId', 'playId', 'nflId', 'maxSpeed', 'quarter', 'possessionTeam', 'preSnapHomeScore', 'preSnapVisitorScore','gameClock']]\n\n#compute the score differential (home - away)\ndf_maxSpeedPlays['scoreDiff'] = df_maxSpeedPlays['preSnapHomeScore'] - df_maxSpeedPlays['preSnapVisitorScore'] \n\n#merge the home team abbr and week of game\ndf_maxSpeedPlays = pd.merge(left = df_maxSpeedPlays, right = df_games[['gameId','week','homeTeamAbbr','visitorTeamAbbr']], how='left', on = ['gameId'])\n\n# Add a column whether the home team is kicking off\ndf_maxSpeedPlays['homeYes'] = df_maxSpeedPlays.possessionTeam.eq(df_maxSpeedPlays['homeTeamAbbr']).astype('int')","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:26:32.111392Z","iopub.execute_input":"2022-01-07T22:26:32.11165Z","iopub.status.idle":"2022-01-07T22:26:32.182825Z","shell.execute_reply.started":"2022-01-07T22:26:32.111623Z","shell.execute_reply":"2022-01-07T22:26:32.181984Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#will have max speeds and play/game situation info for each player with surface info\ndf_maxSpeeds2 = df_maxSpeedPlays.copy()\n\n#merging to Lee Sharpe's data to get surface type\ndf_maxSpeeds2 = pd.merge(df_maxSpeedPlays, df_leeSharpeGames[['old_game_id', 'surface']], left_on = ['gameId'], right_on = ['old_game_id'])\ndf_maxSpeeds2 = df_maxSpeeds2.drop(columns = 'old_game_id')\n\n#striping the surface column to remove extra spaces at the end\ndf_maxSpeeds2['surface'] = df_maxSpeeds2['surface'].transform(lambda x : x.str.strip())\n\n#convert speed to mph 1 yd/sec = 2.0454 mph\nypsToMph = 2.04545\ndf_maxSpeeds2['maxSpeedMph'] = df_maxSpeeds2['maxSpeed']*ypsToMph\n\n#add player name and position to data\ndf_maxSpeeds2 = pd.merge(df_maxSpeeds2, df_players[['nflId', 'Position', 'displayName']], how= 'left', left_on = ['nflId'], right_on = ['nflId'])\n\ndf_maxSpeeds2.head()","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:26:35.539361Z","iopub.execute_input":"2022-01-07T22:26:35.539631Z","iopub.status.idle":"2022-01-07T22:26:35.60911Z","shell.execute_reply.started":"2022-01-07T22:26:35.539601Z","shell.execute_reply":"2022-01-07T22:26:35.608332Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 4. Kickoff Counts as potential factor in fatigue\n### A kickoff coverage player completing their 1st kickoff versus their 7th may have an impact on their max speed. Compute the sequence of kickoffs for a player in a game `koPlayerSeq`","metadata":{}},{"cell_type":"code","source":"#arranging by gameId, playId, nflID\ndf_maxSpeeds2 = df_maxSpeeds2.sort_values(['gameId', 'playId', 'nflId'])\n\n# Sequence of the kick off for a player in a game\ndf_maxSpeeds2['koPlayerSeq'] = df_maxSpeeds2.groupby(['nflId','gameId']).cumcount()+1\ndf_maxSpeeds2.sort_values(['maxSpeedMph'])","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:26:44.798356Z","iopub.execute_input":"2022-01-07T22:26:44.798628Z","iopub.status.idle":"2022-01-07T22:26:44.851097Z","shell.execute_reply.started":"2022-01-07T22:26:44.798598Z","shell.execute_reply":"2022-01-07T22:26:44.850403Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 5. Elapsed time at the start of the kickoff\n\n## While the game clock measures the duration of the game, the real time is also a potential factor in player fatigue. Record the total elapsed time (not game clock) for each kickoff in `elapsedTime`","metadata":{"execution":{"iopub.status.busy":"2022-01-02T22:46:17.273645Z","iopub.execute_input":"2022-01-02T22:46:17.273981Z","iopub.status.idle":"2022-01-02T22:46:17.764954Z","shell.execute_reply.started":"2022-01-02T22:46:17.273951Z","shell.execute_reply":"2022-01-02T22:46:17.763975Z"}}},{"cell_type":"code","source":"#get the elapsed time from start of the game (minutes) for each play\ndf_maxSpeeds2 = pd.merge(left=df_maxSpeeds2, right=df_playElapseTime, how= 'left', left_on = ['gameId', 'playId'], right_on = ['gameId', 'playId'])\n\n#save the data file\ndf_maxSpeeds2.to_csv('maxSpeeds2.csv')\ndf_maxSpeeds2.head()","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:26:59.891566Z","iopub.execute_input":"2022-01-07T22:26:59.892298Z","iopub.status.idle":"2022-01-07T22:27:00.211626Z","shell.execute_reply.started":"2022-01-07T22:26:59.89227Z","shell.execute_reply":"2022-01-07T22:27:00.210874Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 6. EDA for max speed in different game situations\n","metadata":{}},{"cell_type":"code","source":"# (from Baek Kyun Shin)\ndef resumetable(df):\n    print(f'Shape : {df.shape}')\n    summary = pd.DataFrame(df.dtypes, columns=['Data Type'])\n    summary = summary.reset_index()\n    summary = summary.rename(columns={'index': 'Feature'})\n    summary['Num of null'] = df.isnull().sum().values\n    summary['Num of unique'] = df.nunique().values\n    summary['First value'] = df.loc[0].values\n    summary['Second value'] = df.loc[1].values\n    summary['Third value'] = df.loc[2].values\n    return summary\n\nresumetable(df_maxSpeeds2)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-01-07T22:27:06.858633Z","iopub.execute_input":"2022-01-07T22:27:06.85893Z","iopub.status.idle":"2022-01-07T22:27:06.927206Z","shell.execute_reply.started":"2022-01-07T22:27:06.8589Z","shell.execute_reply":"2022-01-07T22:27:06.926684Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_maxSpeeds2.describe().T","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:27:11.057409Z","iopub.execute_input":"2022-01-07T22:27:11.058068Z","iopub.status.idle":"2022-01-07T22:27:11.113551Z","shell.execute_reply.started":"2022-01-07T22:27:11.058027Z","shell.execute_reply":"2022-01-07T22:27:11.112795Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#import grahpics packages\nimport seaborn as sns\nimport matplotlib as mpl\nimport matplotlib.pyplot as plt\n\nplt.style.use('fivethirtyeight')","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-01-07T22:27:13.235742Z","iopub.execute_input":"2022-01-07T22:27:13.236049Z","iopub.status.idle":"2022-01-07T22:27:14.152488Z","shell.execute_reply.started":"2022-01-07T22:27:13.236019Z","shell.execute_reply":"2022-01-07T22:27:14.151593Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 7. Distribution of max speeds of deep kickoffs?\n### Given that the fastest measured speed in a human was Usain Bolt at 27.8 mph between 60 and 80 meters in his world record 100m in 2009, the players reporting max speed above that are clearly measurement errors. I'm going to leave them, but account for these outliers and those with max mph less than 10 mph when viewing the charts.","metadata":{}},{"cell_type":"code","source":"mpl.rc('font', size=15) \nplt.figure(figsize=(10, 5))\n\ng = sns.histplot(data=df_maxSpeeds2, x='maxSpeedMph',bins=50)\n\n#clean up the chart\ng.set_title('Kickoff Coverage Player Max Speeds')\ng.set_ylabel('')\ng.set_xlabel('Player Max MPH on Kickoff Coverage');","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:27:17.509736Z","iopub.execute_input":"2022-01-07T22:27:17.510028Z","iopub.status.idle":"2022-01-07T22:27:17.864746Z","shell.execute_reply.started":"2022-01-07T22:27:17.509999Z","shell.execute_reply":"2022-01-07T22:27:17.863919Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 8. What is the total number of kickoff coverage sprints for a player in game?\n### A deep kickoff is typically a full sprint from the 35-yd line for 40+ yards. Some players do this as many as 10 times within a game while six or less is more typical.\n### Here we see the distribution of kickoff coverage sprints in a game for each player. There are 910 players that participated in a total of 4 deep kickoffs in a game. There are 80 players who completed 8 kickoff coverage sprints in a single game.","metadata":{}},{"cell_type":"code","source":"#find the total number of kickoffs in a game for each player\ndf_totalKos = pd.DataFrame(df_maxSpeeds2.groupby(['gameId','nflId'])['koPlayerSeq'].max())\ndf_totalKos.reset_index(inplace=True)\n\ndf_totalKos.rename(columns={'koPlayerSeq':'totalKos'}, inplace=True)\n\nmpl.rc('font', size=15) \nplt.figure(figsize=(10, 5))\n\ng = sns.histplot(data=df_totalKos, x='totalKos',discrete=True)\n\n#clean up the chart\ng.set_title('Total Kickoff Sprints per Player in a Game')\ng.set_xticks(range(1,11))\ng.set_ylabel('Number of Players')\ng.set_xlabel('Total Kickoff Coverage Player Sprints in Game');","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-01-07T22:27:20.321877Z","iopub.execute_input":"2022-01-07T22:27:20.32214Z","iopub.status.idle":"2022-01-07T22:27:20.556135Z","shell.execute_reply.started":"2022-01-07T22:27:20.322112Z","shell.execute_reply":"2022-01-07T22:27:20.55554Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_totalKos.totalKos.value_counts().sort_index()","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-01-07T22:27:24.192449Z","iopub.execute_input":"2022-01-07T22:27:24.193461Z","iopub.status.idle":"2022-01-07T22:27:24.201636Z","shell.execute_reply.started":"2022-01-07T22:27:24.193399Z","shell.execute_reply":"2022-01-07T22:27:24.200832Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 9. How do cumulative kickoff sprints impact player max sprint speed?\n\n### Here we look that max speed for players on their first, second, third, etc. kickoff coverage of a game. While the range decreases, likely more due to sample size, the actual distribution does not appear to be significantly different with the average only dropping by roughly 0.5 mph from the first kickoff to the eigth of the game.","metadata":{}},{"cell_type":"code","source":"mpl.rc('font', size=15) \nplt.figure(figsize=(10, 5))\n\ng = sns.violinplot(data=df_maxSpeeds2, y='maxSpeedMph', x='koPlayerSeq',color='#30a2da')\n\n#clean up the chart\ng.set_title('Player Max Speed on Kickoff')\ng.set_ylabel('Max Speed (mph)')\ng.set_xlabel('Cumulutive Kickoff Sprints for Player in Game');\n\nmpl.rc('font', size=15) \nplt.figure(figsize=(10, 5))\n\nh = sns.pointplot(data=df_maxSpeeds2, y='maxSpeedMph', x='koPlayerSeq',color='#30a2da', errwidth= 1, scale= 0.5)\n\n#clean up the chart\nh.set_title('Average Player Max Speed on Kickoff')\nh.set_ylabel('Max Speed (mph)')\nh.set_xlabel('Cumulutive Kickoff Sprints for Player in Game');","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:27:26.577008Z","iopub.execute_input":"2022-01-07T22:27:26.577296Z","iopub.status.idle":"2022-01-07T22:27:28.009372Z","shell.execute_reply.started":"2022-01-07T22:27:26.577267Z","shell.execute_reply":"2022-01-07T22:27:28.008691Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 10. Do some positions feel the cumulative effects of repeated kickoff sprints in a game more than others?\n\n### We look at the top 10 positions for players participating in kickoffs and visualize the impact of cumulative kickoffs on their average max speed. As would be expected, some positions are faster than others. The largest dropoffs in max speed appear to occur for MLB.","metadata":{}},{"cell_type":"code","source":"top10pos = df_maxSpeeds2.Position.value_counts().head(10).index.tolist()\ndf_top = df_maxSpeeds2[(df_maxSpeeds2.Position.isin(top10pos)) & (df_maxSpeeds2.koPlayerSeq < 9) & (df_maxSpeeds2.quarter < 5)]\ng = sns.FacetGrid(data=df_top, col=\"Position\", col_wrap=5, height=5, ylim=(15, 22))\ng = g.map(sns.pointplot, \"koPlayerSeq\", \"maxSpeedMph\",color='#30a2da', errwidth= 1, scale= 0.5);\n\n#clean up the chart\ng.set_axis_labels('Cumulative Kickoff Sprints', 'Max Speed (mph)');","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:27:31.662079Z","iopub.execute_input":"2022-01-07T22:27:31.662304Z","iopub.status.idle":"2022-01-07T22:27:36.254091Z","shell.execute_reply.started":"2022-01-07T22:27:31.662281Z","shell.execute_reply":"2022-01-07T22:27:36.25354Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 11. How much does players' kickoff sprint slow as the game progresses (by quarters)?\n\n### The effect isn't as clear here likely due to a variety of factors (limited number of kickoffs).","metadata":{}},{"cell_type":"code","source":"g = sns.FacetGrid(data=df_top, col=\"Position\", col_wrap=5, height=5, ylim=(15, 22))\ng = g.map(sns.pointplot, \"quarter\", \"maxSpeedMph\",color='#30a2da', errwidth= 1, scale= 0.5);\n\n#clean up the chart\n#g.set(xticks=range(1,5))\ng.set_axis_labels('Quarter', 'Max Speed (mph)');","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:27:36.25538Z","iopub.execute_input":"2022-01-07T22:27:36.255731Z","iopub.status.idle":"2022-01-07T22:27:39.639507Z","shell.execute_reply.started":"2022-01-07T22:27:36.255699Z","shell.execute_reply":"2022-01-07T22:27:39.638576Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 12. How much does players' kickoff sprint slow as the game progresses (by actual time)?\n\n### The effect isn't as clear here likely due to a variety of factors (limited number of kickoffs). Clearly a dropoff as the game progresses for some positions more than others.","metadata":{}},{"cell_type":"code","source":"#create 10 minute bins \ninterval_range = pd.interval_range(start=0,end=220,freq=20,closed='left')\ndf_top.loc[:,'elapsedTimeBinned'] = pd.cut(df_top['elapsedTime'], bins=interval_range)\n\ng = sns.FacetGrid(data=df_top, col=\"Position\", col_wrap=5, height=5, ylim=(15, 22))\ng = g.map(sns.pointplot, \"elapsedTimeBinned\", \"maxSpeedMph\", errwidth= 1, scale= 0.5);\n\n#clean up the chart\ng.set_xticklabels(rotation=90)\ng.set_axis_labels('Game Duration (min)', 'Max Speed (mph)');","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:27:41.743343Z","iopub.execute_input":"2022-01-07T22:27:41.744464Z","iopub.status.idle":"2022-01-07T22:27:46.858706Z","shell.execute_reply.started":"2022-01-07T22:27:41.744437Z","shell.execute_reply":"2022-01-07T22:27:46.857912Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 13. Kicking team win margin impact on sprint effort?\n\n### Perhaps the current margin of victory for the kicking team might impact motivation and players will give a lesser effort (sprint)?","metadata":{}},{"cell_type":"code","source":"df_top['kickTeamMargin'] = np.where(df_top['homeYes'] == 1, df_top['scoreDiff'], -df_top['scoreDiff'])\ndf_top.describe()","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:27:51.418436Z","iopub.execute_input":"2022-01-07T22:27:51.418723Z","iopub.status.idle":"2022-01-07T22:27:51.691944Z","shell.execute_reply.started":"2022-01-07T22:27:51.418692Z","shell.execute_reply":"2022-01-07T22:27:51.691162Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Looking at all the positions combined, there doesn't seem to be too much dropoff based on the score. Even when the kicking team is down by 30 (-30) or up by 30.","metadata":{}},{"cell_type":"code","source":"mpl.rc('font', size=15) \nplt.figure(figsize=(10, 5))\n\nh = sns.scatterplot(data=df_top, y='maxSpeedMph', x='kickTeamMargin', color='#30a2da')\n\n#clean up the chart\nh.set_title('Average Player Max Speed on Kickoff')\nh.set_ylabel('Max Speed (mph)')\nh.set_xlabel('Cumulutive Kickoff Sprints for Player in Game');","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:27:53.788198Z","iopub.execute_input":"2022-01-07T22:27:53.788461Z","iopub.status.idle":"2022-01-07T22:27:54.075622Z","shell.execute_reply.started":"2022-01-07T22:27:53.788435Z","shell.execute_reply":"2022-01-07T22:27:54.074597Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Let's look at the positions of players for the those from the top 10 most frequent (only in regulation and less than 9 cumulative kickoffs in a game). We see some of effect we might expect to see (dropoff in the tails where motivation due to large lead or large deficit) is exhibited.","metadata":{}},{"cell_type":"code","source":"g = sns.FacetGrid(data=df_top, col=\"Position\", col_wrap=5, height=5, ylim=(10, 22))\ng = g.map(sns.lineplot, \"kickTeamMargin\", \"maxSpeedMph\");\n#g = g.map(sns.pointplot, \"kickTeamMargin\", \"maxSpeedMph\",color='#30a2da', errwidth= 1, scale= 0.5);\ng.axes[0].legend()\n#clean up the chart\ng.set_axis_labels('Kick Team Score Margin', 'Max Speed (mph)');","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:48:11.841863Z","iopub.execute_input":"2022-01-07T22:48:11.843412Z","iopub.status.idle":"2022-01-07T22:48:23.14621Z","shell.execute_reply.started":"2022-01-07T22:48:11.843353Z","shell.execute_reply":"2022-01-07T22:48:23.145309Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Does late game paint a different picture? Just looking at the fourth quarter.","metadata":{}},{"cell_type":"code","source":"g = sns.FacetGrid(data=df_top[df_top['quarter'] == 4], col=\"Position\", col_wrap=5, height=5, ylim=(10, 22))\ng = g.map(sns.lineplot, \"kickTeamMargin\", \"maxSpeedMph\");\n#g = g.map(sns.pointplot, \"kickTeamMargin\", \"maxSpeedMph\",color='#30a2da', errwidth= 1, scale= 0.5);\ng.axes[0].legend()\n#clean up the chart\ng.set_axis_labels('Kick Team Score Margin', 'Max Speed (mph)');","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:48:24.878439Z","iopub.execute_input":"2022-01-07T22:48:24.878734Z","iopub.status.idle":"2022-01-07T22:48:33.665639Z","shell.execute_reply.started":"2022-01-07T22:48:24.878702Z","shell.execute_reply":"2022-01-07T22:48:33.665056Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Create 5 point interval margins and repeat. This is just for fourth quarter effects and some of the high/low dropoffs are more apparent (RB and OLB).","metadata":{}},{"cell_type":"code","source":"#create 10 minute bins \ninterval_range = pd.interval_range(start=-35,end=50,freq=5,closed='left')\ndf_top.loc[:,'kickTeamMargin_bin'] = pd.cut(df_top['kickTeamMargin'], bins=interval_range)\n\ng = sns.FacetGrid(data=df_top[df_top['quarter'] == 4], col=\"Position\", col_wrap=5, height=5, ylim=(14, 22))\ng = g.map(sns.pointplot, \"kickTeamMargin_bin\", \"maxSpeedMph\", errwidth= 1, scale= 0.5);\n\n#clean up the chart\ng.set_xticklabels(rotation=90)\ng.set_axis_labels('Kick Team Score Margin', 'Max Speed (mph)');","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:48:48.179759Z","iopub.execute_input":"2022-01-07T22:48:48.180073Z","iopub.status.idle":"2022-01-07T22:48:53.071749Z","shell.execute_reply.started":"2022-01-07T22:48:48.180045Z","shell.execute_reply":"2022-01-07T22:48:53.071274Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 14. Conclusion\n\n### Just a quick EDA, but further exploration with this data would look at the potential impact of the score differential (Do players \"relax\" when the game is in hand?). I was intending to do a regression analysis with the effects:\n* Postion\n* Quarter\n* Score Differential\n* Cumulative KOs\n* Real-time since the last KO\n\n### I just don't have time and wanted to get my feet wet with the data and the Big Data Bowl process. Thanks to those that shared their starter code.","metadata":{}},{"cell_type":"code","source":"df_top.head()","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:28:42.479366Z","iopub.execute_input":"2022-01-07T22:28:42.47982Z","iopub.status.idle":"2022-01-07T22:28:42.510628Z","shell.execute_reply.started":"2022-01-07T22:28:42.479771Z","shell.execute_reply":"2022-01-07T22:28:42.5101Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:40:03.633311Z","iopub.execute_input":"2022-01-07T22:40:03.634363Z","iopub.status.idle":"2022-01-07T22:40:03.671602Z","shell.execute_reply.started":"2022-01-07T22:40:03.634303Z","shell.execute_reply":"2022-01-07T22:40:03.670908Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#sort data by time\ndf_topTimeSort = df_top.sort_values(by=['time','nflId'],ascending=True)\n\n#compute the lag of the max speed for each ko in a game\ndf_topTimeSort['lagSpeedMph'] = (df_topTimeSort.groupby((['gameId','nflId']))['maxSpeedMph'].shift(1))\ndf_topTimeSort[df_topTimeSort['nflId'] == 37211].head(20)","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:40:05.922098Z","iopub.execute_input":"2022-01-07T22:40:05.922501Z","iopub.status.idle":"2022-01-07T22:40:05.963909Z","shell.execute_reply.started":"2022-01-07T22:40:05.922467Z","shell.execute_reply":"2022-01-07T22:40:05.963076Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#import statsmodels\nimport statsmodels.api as sm\nimport statsmodels.formula.api as smf","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:40:15.345407Z","iopub.execute_input":"2022-01-07T22:40:15.345647Z","iopub.status.idle":"2022-01-07T22:40:15.350612Z","shell.execute_reply.started":"2022-01-07T22:40:15.345623Z","shell.execute_reply":"2022-01-07T22:40:15.349841Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#regression with all interactions\npreds = '+'.join(['C(Position)','lagSpeedMph*C(quarter)'])\nmy_formula = \"maxSpeedMph~\" + preds\nmod = smf.ols(formula=my_formula, data= df_topTimeSort).fit()\nprint(mod.summary())","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:41:28.039572Z","iopub.execute_input":"2022-01-07T22:41:28.039901Z","iopub.status.idle":"2022-01-07T22:41:28.293514Z","shell.execute_reply.started":"2022-01-07T22:41:28.039869Z","shell.execute_reply":"2022-01-07T22:41:28.292897Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"g = sns.FacetGrid(data=df_topTimeSort[df_topTimeSort['lagSpeedMph'] > 10].dropna(), col=\"Position\", col_wrap=5, height=5, ylim=(0, 32),xlim=(10, 25))\ng = g.map(sns.lineplot, 'lagSpeedMph',\"maxSpeedMph\", color='#30a2da');\n\n#clean up the chart\n#g.set(xticks=range(1,5))\ng.set_axis_labels('lagSpeedMph', 'Max Speed (mph)');","metadata":{"execution":{"iopub.status.busy":"2022-01-07T22:54:09.719351Z","iopub.execute_input":"2022-01-07T22:54:09.719625Z","iopub.status.idle":"2022-01-07T22:54:49.475018Z","shell.execute_reply.started":"2022-01-07T22:54:09.719601Z","shell.execute_reply":"2022-01-07T22:54:49.474091Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}