{"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":"The focus on the 2021 - 2022 Big Data Bowl is special teams. This demo will provide sample code to help with the process of getting started. This demo includes sample code to do the following:\n\n[What is Kick Attempt Offset from Center?](#whatis) - explains the metric of interest and why it can be used to analyze kickers.\n\n[Read Data](#ReadData) - will read the tracking / non-tracking data.\n\n[Clean Data](#CleanData) - will align the tracking data x-y coordinates with the kick direction and filter to what is needed for this analysis\n\n[Visualize Metric by Kicker](#FieldGoalAccuracy) - will create a few plots showing the metric by place kicker.","metadata":{}},{"cell_type":"markdown","source":"<a id=\"whatis\"></a>\n# What is Kick Attempt Offset from Center?\n\nWhen NFL place kickers come on to the field for a field goal or extra poitn play, they attempt to kick the football between the uprights that are located behind the endzone in order to score points for their team:\n\n\n<img src=\"https://clipartstation.com/wp-content/uploads/2018/09/goal-post-football-clipart-3.jpg\" style=\"width:300px;\">\n\n\nWhether the ball goes right down the middle or sneaks within the uprights by an inch does not have an effect on points scored. However, kickers typically attempt to place the ball directly between the uprights as that increases the chances of a succesful field goal or extra point. Thus, the place kicker's <b> offset from center </b> can be measured as the absolute difference between where the ball went through the uprights and the center of the uprights. The lower the offset from center, the more accurate the kick.","metadata":{}},{"cell_type":"markdown","source":"<a id=\"ReadData\"></a>\n## Reading Data","metadata":{}},{"cell_type":"code","source":"#Loading and installing libraries\n!pip install joypy\nimport numpy as np \nimport pandas as pd\nfrom joypy import joyplot\nimport matplotlib.pyplot as plt","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2021-10-04T16:49:26.268204Z","iopub.execute_input":"2021-10-04T16:49:26.269161Z","iopub.status.idle":"2021-10-04T16:49:37.732611Z","shell.execute_reply.started":"2021-10-04T16:49:26.269003Z","shell.execute_reply":"2021-10-04T16:49:37.731632Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"##reading in non-tracking data\n\n#includes play-by-play info on specific plays\ndf_plays = pd.read_csv(\"../input/nfl-big-data-bowl-2022/plays.csv\")\n\n#includes background info for players\ndf_players = pd.read_csv(\"../input/nfl-big-data-bowl-2022/players.csv\")","metadata":{"execution":{"iopub.status.busy":"2021-10-04T17:07:48.369306Z","iopub.execute_input":"2021-10-04T17:07:48.370106Z","iopub.status.idle":"2021-10-04T17:07:48.557178Z","shell.execute_reply.started":"2021-10-04T17:07:48.370055Z","shell.execute_reply":"2021-10-04T17:07:48.556273Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_plays.head()","metadata":{"execution":{"iopub.status.busy":"2021-10-04T17:07:51.573506Z","iopub.execute_input":"2021-10-04T17:07:51.574223Z","iopub.status.idle":"2021-10-04T17:07:51.613995Z","shell.execute_reply.started":"2021-10-04T17:07:51.574174Z","shell.execute_reply":"2021-10-04T17:07:51.613021Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_players.head()","metadata":{"execution":{"iopub.status.busy":"2021-10-04T17:08:06.710504Z","iopub.execute_input":"2021-10-04T17:08:06.710787Z","iopub.status.idle":"2021-10-04T17:08:06.723945Z","shell.execute_reply.started":"2021-10-04T17:08:06.710758Z","shell.execute_reply":"2021-10-04T17:08:06.722973Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"##Reading tracking data (needs to be done iteratively)\n\n#years of NFL seasons\nseasons = [\"2018\", \"2019\", \"2020\"]\n\n#blank dataframe to store tracking data\ndf_tracking = pd.DataFrame()\n\n#iterating through all seasons\nfor s in seasons:\n    \n    #temperory dataframe used for reading week for given iteration\n    df_trackingTemp = pd.read_csv(\"../input/nfl-big-data-bowl-2022/tracking\"+s+\".csv\")\n    \n    #storing temporary dataframe in full season dataframe\n    df_tracking = df_tracking.append(df_trackingTemp)                         ","metadata":{"execution":{"iopub.status.busy":"2021-10-04T17:08:10.140284Z","iopub.execute_input":"2021-10-04T17:08:10.140595Z","iopub.status.idle":"2021-10-04T17:10:43.927105Z","shell.execute_reply.started":"2021-10-04T17:08:10.140561Z","shell.execute_reply":"2021-10-04T17:10:43.925744Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_tracking.head()","metadata":{"execution":{"iopub.status.busy":"2021-10-04T17:10:43.958638Z","iopub.execute_input":"2021-10-04T17:10:43.958898Z","iopub.status.idle":"2021-10-04T17:10:43.992703Z","shell.execute_reply.started":"2021-10-04T17:10:43.958869Z","shell.execute_reply":"2021-10-04T17:10:43.991796Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<a id=\"CleanData\"></a>\n# Cleaning Data","metadata":{}},{"cell_type":"markdown","source":"The tracking data is laid out in absolute coordinates and does not flip with the change of possession or end of quarter. The code below flips the coordinates so they always align with the direction of the kicking team.","metadata":{}},{"cell_type":"code","source":"#Standardizing tracking data so its always in direction of kicking team vs raw on-field coordinates.\ndf_tracking.loc[df_tracking['playDirection'] == \"left\", 'x'] = 120-df_tracking.loc[df_tracking['playDirection'] == \"left\", 'x']\ndf_tracking.loc[df_tracking['playDirection'] == \"left\", 'y'] = 160/3-df_tracking.loc[df_tracking['playDirection'] == \"left\", 'y']","metadata":{"execution":{"iopub.status.busy":"2021-10-04T17:11:25.880854Z","iopub.execute_input":"2021-10-04T17:11:25.881212Z","iopub.status.idle":"2021-10-04T17:11:49.554838Z","shell.execute_reply.started":"2021-10-04T17:11:25.881176Z","shell.execute_reply":"2021-10-04T17:11:49.553974Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#dataframe will store where ball was when kick is crosses uprights\n\n#filtering for football\ndf_ballFieldGoal = df_tracking[df_tracking['displayName'] == 'football']\n\ndf_ballFieldGoal.sort_values(by = ['gameId', 'playId', 'frameId'])\n\n#grouping by gameId and playId\ndf_ballFieldGoal = df_ballFieldGoal.loc[df_ballFieldGoal.groupby(by=['playId', 'gameId']).x.transform(lambda z: (z >= 120) & (z.shift(1) < 120))]\n\n#selecting first occurence in case it crosses uprights multiple times\ndf_ballFieldGoal = df_ballFieldGoal.groupby(by=['playId', 'gameId']).first().reset_index()\n\n#creating and adding a variable for absolute offset from center. Center of field is at coordiante 160/6\ndf_ballFieldGoal.insert(loc = 18, column = \"offsetFromCenter\", value = abs(df_ballFieldGoal['y'] - 160/6))\n\n#selecting offsetFromCenter and key variables only\ndf_ballFieldGoal = df_ballFieldGoal[['gameId', 'playId', 'offsetFromCenter']]","metadata":{"execution":{"iopub.status.busy":"2021-10-04T17:11:49.556509Z","iopub.execute_input":"2021-10-04T17:11:49.556753Z","iopub.status.idle":"2021-10-04T17:12:18.623519Z","shell.execute_reply.started":"2021-10-04T17:11:49.556716Z","shell.execute_reply":"2021-10-04T17:12:18.622556Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#dataframe storing kick offset for each kicker on each play\n\n#filtering for unblocked extra points and field goals only\ndf_fieldGoalAnalysis = df_plays[df_plays['specialTeamsResult'].isin(['Kick Attempt Good', 'Kick Attempt No Good'])]\n\n#using play description to filter out kick attempts that were missed because they were short   \ndf_fieldGoalAnalysis = df_fieldGoalAnalysis[~df_fieldGoalAnalysis['playDescription'].str.contains('No Good, Short')]\n\n\n#kickLength is sometimes missing on extra points. In that case we use impute as yards from target endzone + 18.\nconditions = [\n    (df_fieldGoalAnalysis['yardlineNumber'] == 50),\n    (df_fieldGoalAnalysis['possessionTeam'] == df_fieldGoalAnalysis['yardlineSide']),\n    (df_fieldGoalAnalysis['possessionTeam'] != df_fieldGoalAnalysis['yardlineSide'])\n]\n\nvalues = [df_fieldGoalAnalysis['yardlineNumber'], df_fieldGoalAnalysis['yardlineNumber'] + 50, df_fieldGoalAnalysis['yardlineNumber']]\n\ndf_fieldGoalAnalysis['yardsFromTargetEndzone'] = np.select(conditions, values)\n\n#imputing kick length as yards from target endzone + 18\ndf_fieldGoalAnalysis.loc[df_fieldGoalAnalysis['kickLength'].isnull(), 'kickLength'] = 18.0+df_fieldGoalAnalysis.loc[df_fieldGoalAnalysis['kickLength'].isnull(), 'yardsFromTargetEndzone']\n\n#joining players by kickerId to get displayName\ndf_fieldGoalAnalysis = pd.merge(df_fieldGoalAnalysis, df_players, left_on=['kickerId'], right_on =['nflId'])\n\n#joining filtered tracking data\ndf_fieldGoalAnalysis = pd.merge(df_fieldGoalAnalysis, df_ballFieldGoal, left_on=['gameId', 'playId'], right_on =['gameId', 'playId'])\n\n#selecting only variables of interest:\ndf_fieldGoalAnalysis = df_fieldGoalAnalysis[['gameId', 'playId', 'displayName', 'kickLength', 'offsetFromCenter', 'playDescription']]","metadata":{"execution":{"iopub.status.busy":"2021-10-04T17:12:18.625155Z","iopub.execute_input":"2021-10-04T17:12:18.625485Z","iopub.status.idle":"2021-10-04T17:12:18.711457Z","shell.execute_reply.started":"2021-10-04T17:12:18.625444Z","shell.execute_reply":"2021-10-04T17:12:18.710642Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<a id=\"FieldGoalAccuracy\"></a>\n# Visualize Metric by Kicker\n\nHere we will compare NFL kickers with our offset from center metric. First we make a bar graph:","metadata":{}},{"cell_type":"code","source":"#dataframe for visualization\n\n#filtering for length between 30 and 40 yards\ndf_visual = df_fieldGoalAnalysis[(df_fieldGoalAnalysis['kickLength'] >= 30) & (df_fieldGoalAnalysis['kickLength'] <= 40)]\n\n#filtering for only kickers with 75+ attempts\ndf_visual = df_visual[df_visual['displayName'].map(df_visual['displayName'].value_counts()) >= 75]\n\n#grouping by kickerId and taking mean of data\ndf_visual = df_visual.groupby('displayName').agg(avgOffsetFromCenter = ('offsetFromCenter', 'mean'))\n\n#sorting data by average offset from center\ndf_visual = df_visual.sort_values(by = 'avgOffsetFromCenter', ascending=False)\n\n#ungrouping\ndf_visual = df_visual.reset_index()","metadata":{"execution":{"iopub.status.busy":"2021-10-04T17:12:18.713298Z","iopub.execute_input":"2021-10-04T17:12:18.713526Z","iopub.status.idle":"2021-10-04T17:12:18.741634Z","shell.execute_reply.started":"2021-10-04T17:12:18.7135Z","shell.execute_reply":"2021-10-04T17:12:18.740722Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig = plt.figure(figsize=(10,10))\nplt.rc('grid', linestyle=':', color='lightgray', linewidth=0.5)\nplt.grid(True, zorder = 0)\n\nplt.barh(list(df_visual['displayName']), list(df_visual['avgOffsetFromCenter']), color = \"lightblue\")\nplt.xticks([0.0, 0.5, 1, 1.5])\n\n\nplt.title(\"Average Offset from Center by Place Kicker on 30 to 40 yard Attempts \\n (Ordered from Best to Worst)\", fontsize = 16)\nplt.xlabel(\"Average Offset from Center\", fontsize = 14)\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-10-04T17:12:18.743058Z","iopub.execute_input":"2021-10-04T17:12:18.743342Z","iopub.status.idle":"2021-10-04T17:12:19.139032Z","shell.execute_reply.started":"2021-10-04T17:12:18.743312Z","shell.execute_reply":"2021-10-04T17:12:19.138046Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Next we make a ridge plot:","metadata":{}},{"cell_type":"code","source":"#dataframe for visualization\n\n#filtering for length between 30 and 40 yards\ndf_visual2 = df_fieldGoalAnalysis[(df_fieldGoalAnalysis['kickLength'] >= 30) & (df_fieldGoalAnalysis['kickLength'] <= 40)]\n\n#filtering by previous list of kickers with 75+ attempts\ndf_visual2 = df_visual2[df_visual2['displayName'].isin(list(df_visual['displayName']))]\n\ndf_visual2['order'] = df_visual2.groupby('displayName').offsetFromCenter.transform(lambda x: np.mean(x))\n\ndf_visual2 = df_visual2.sort_values(['order'])\n\ngrouped = df_visual2.groupby(\"displayName\", sort=False)\n\njoyplot(grouped, column = 'offsetFromCenter', figsize = (10,10), overlap = 0.5, color = 'lightblue')\nplt.xticks([0.0, 2.5, 5.0, 7.5])\n\nplt.rc('grid', linestyle=':', color='lightgray', linewidth=0.5)\nplt.grid(True, zorder = 0)\n\nplt.title(\"Density of Offset from Center by Kicker on 30 to 40 yard Attempts \\n (Ordered from Best to Worst)\", fontsize = 16)\nplt.xlabel(\"Kick Attempt Offset from Center Values\", fontsize = 14)\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-10-04T17:12:21.378382Z","iopub.execute_input":"2021-10-04T17:12:21.379318Z","iopub.status.idle":"2021-10-04T17:12:23.640117Z","shell.execute_reply.started":"2021-10-04T17:12:21.379273Z","shell.execute_reply":"2021-10-04T17:12:23.639257Z"},"trusted":true},"execution_count":null,"outputs":[]}]}