{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"markdown","source":"# Evolving The Punt Play\n[Github Repo with Additional Code](https://github.com/RobMulla/kaggle-nfl)  \n[Link to Slide Deck](https://docs.google.com/presentation/d/17tF9tf8AAAnpmmOAq7VM6BhFWC0hjE7JukyvGlwI8c4/edit?usp=sharing)\n\n## Reducing Injuries while maintaining the integrity of the game\n\nThe NFL Punt Analytics Competition is being held by the NFL to elicit the public's proposal for modifications to punt rules. The overarching goal of this challenge is to reduce injuries to players during punt plays.\n\nSubmissions will be judged by the NFL on (1) **Solution Efficacy** This parameter is looking for submissions to clearly demonstrate, through analysis, an understanding for the play features associated with conncussions and how proposed rule changes will reduce these injuries. (2) **Game Integrity** This parameter is looking for submissions to be actionable ... ideas that the NFL could implement while maintaining the integrity of the game. One must also consider changes to game dynamics and any potential new risks to player safety as a result of downstream consequences.\n\n# High Level Overview\nPunt plays have the potential to be some of the most exciting and game changing moments in football. Specialized positions such as punters and punt returners have worked to hone their craft and are an essential part of the game. As part of our research for this challenge, we interviewed College Football Hall of Fame coach Frank Beamer, known for his innovative approach to special teams at the college level. He was adamant that punt plays have the potential to be the most exciting part of the game and the integrity of the game needs to be considered when implementing rule changes. We agree with Frank Beamer- it is our utmost priority to minimize the number of unnecessary dangerous plays while keeping the integrity of the game. Football is a dangerous sport, and removing all risk is impossible, but by focusing on plays that are not pivotal to the game (like short punt returns or returns for no gain), we can allow the data to speak to some of the current challenges of these football moments.\n\nOur kernel is organized similar to our overall analytical approach to the problem:\n\n## Kernel Outline\n- Section I. **High Level Analysis of All Punts**\n    - What are the types of results for punt plays and how common are each of them?\n    - What the distribution of return yards on punts?\n    - What are the common formations for punts and punt returns?\n- Section II. **Focused Analysis of Punts Resulting in Concussions**\n    - Which players are commonly involved in injuries?\n    - What are the types of returns which result in injuries?\n        - Clustering similar types of punt plays resulting in concussions.\n    - What is the velocity/direction of players hurt?\n        - Looking at common trends with players changing direction prior to impact.\n- Section III. **Analysis of Fair Catch vs. Returned**\n    - What are the players' positions on the field at the time of fair catch / return\n    - What are the distances between the coverage team and the punt returner during fair catches?\n- Section IV. **Exploration of the Routes Run by Role Types**\n    - What are all routes by position? What players are injured in the play?  What are the identifiable trends?\n    - What routes do punt returners tend to take?\n- Section V. **Using Physics to Calculate Play and Player Risk**\n    - Can we calculate a heuristic that captures the risk of each pair of players in a given play?\n    - Can we aggregate risk to the play level to rank reach play's risk?\n    - What trends in the risk can be associated with certain types of plays?\n- Section VI **Our Proposed Rule Changes**\n    - Incentivize the fair catch.\n    - Expand the definition of a defenseless player to include punting team players in pursuit of the returner.\n    - Restrict double teaming gunners to balance the starting position of opposing players.\n- Section VII **Solution Efficacy and Impact on Game Integrity**\n    - Explanation of how these proposed rule changes would reduce the type of plays associated with concussions.\n    - Details about how the integrity of the game will remain intact with these changes.\n    - Possible negative impacts of the proposed rule changes."},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","collapsed":true,"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":false},"cell_type":"markdown","source":"## Setup and Data Prep"},{"metadata":{"trusted":true,"_uuid":"a542447073557f5ef87903f259fe0038830dc764","_kg_hide-input":true},"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pylab as plt\nimport os\nimport seaborn as sns\nimport math\n\nfrom IPython.core.display import display, HTML\n\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\nfrom string import ascii_letters\n\nplt.style.use('ggplot')\n\ncolor1 = '#E24A33'\ncolor2 = '#348ABD'\ncolor3 = '#988ED5'\ncolor4 = '#777777'\n\n%matplotlib inline","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"6532024e937842d50e0b32686719c34b2dac6453","_kg_hide-input":true},"cell_type":"code","source":"def create_football_field(linenumbers=True,\n                          endzones=True,\n                          highlight_line=False,\n                          highlight_line_number=50,\n                          highlighted_name='Line of Scrimmage',\n                          fifty_is_los=False,\n                          figsize=(12, 6.33)):\n    \"\"\"\n    Function that plots the football field for viewing plays.\n    Allows for showing or hiding endzones.\n    \"\"\"\n    rect = patches.Rectangle((0, 0), 120, 53.3, linewidth=0.1,\n                             edgecolor='r', facecolor='darkgreen', zorder=0)\n\n    fig, ax = plt.subplots(1, figsize=figsize)\n    ax.add_patch(rect)\n\n    plt.plot([10, 10, 10, 20, 20, 30, 30, 40, 40, 50, 50, 60, 60, 70, 70, 80,\n              80, 90, 90, 100, 100, 110, 110, 120, 0, 0, 120, 120],\n             [0, 0, 53.3, 53.3, 0, 0, 53.3, 53.3, 0, 0, 53.3, 53.3, 0, 0, 53.3,\n              53.3, 0, 0, 53.3, 53.3, 0, 0, 53.3, 53.3, 53.3, 0, 0, 53.3],\n             color='white')\n    if fifty_is_los:\n        plt.plot([60, 60], [0, 53.3], color='gold')\n        plt.text(62, 50, '<- Player Yardline at Snap', color='gold')\n    # Endzones\n    if endzones:\n        ez1 = patches.Rectangle((0, 0), 10, 53.3,\n                                linewidth=0.1,\n                                edgecolor='r',\n                                facecolor='blue',\n                                alpha=0.2,\n                                zorder=0)\n        ez2 = patches.Rectangle((110, 0), 120, 53.3,\n                                linewidth=0.1,\n                                edgecolor='r',\n                                facecolor='blue',\n                                alpha=0.2,\n                                zorder=0)\n        ax.add_patch(ez1)\n        ax.add_patch(ez2)\n    plt.xlim(0, 120)\n    plt.ylim(-5, 58.3)\n    plt.axis('off')\n    if linenumbers:\n        for x in range(20, 110, 10):\n            numb = x\n            if x > 50:\n                numb = 120 - x\n            plt.text(x, 5, str(numb - 10),\n                     horizontalalignment='center',\n                     fontsize=20,  # fontname='Arial',\n                     color='white')\n            plt.text(x - 0.95, 53.3 - 5, str(numb - 10),\n                     horizontalalignment='center',\n                     fontsize=20,  # fontname='Arial',\n                     color='white', rotation=180)\n    if endzones:\n        hash_range = range(11, 110)\n    else:\n        hash_range = range(1, 120)\n\n    for x in hash_range:\n        ax.plot([x, x], [0.4, 0.7], color='white')\n        ax.plot([x, x], [53.0, 52.5], color='white')\n        ax.plot([x, x], [22.91, 23.57], color='white')\n        ax.plot([x, x], [29.73, 30.39], color='white')\n\n    if highlight_line:\n        hl = highlight_line_number + 10\n        plt.plot([hl, hl], [0, 53.3], color='yellow')\n        plt.text(hl + 2, 50, '<- {}'.format(highlighted_name),\n                 color='yellow')\n\n    return fig, ax","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"dd2bf6c3e14b1eade2cf9b342e6057e5f6dfe51d","_kg_hide-input":true},"cell_type":"code","source":"# Read in non-NGS data sources\nppd = pd.read_csv('../input/NFL-Punt-Analytics-Competition/player_punt_data.csv')\ngd = pd.read_csv('../input/NFL-Punt-Analytics-Competition/game_data.csv')\npprd = pd.read_csv('../input/NFL-Punt-Analytics-Competition/play_player_role_data.csv')\nvr = pd.read_csv('../input/NFL-Punt-Analytics-Competition/video_review.csv')\nvfi = pd.read_csv('../input/NFL-Punt-Analytics-Competition/video_footage-injury.csv')\npi = pd.read_csv('../input/NFL-Punt-Analytics-Competition/play_information.csv')\nvfi = vfi.rename(columns={'season' : 'season_year'})\nall_dfs = [ppd, gd, pprd, vr, vfi, pi]\n# Change column names so they are all lowercase \n# never have to guess about which letters are uppercase\nfor mydf in all_dfs:\n    mydf.columns = [col.lower() for col in mydf.columns]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"2443d2b13af4e0513dc8f20dfd8518499f18bb13","_kg_hide-input":true},"cell_type":"code","source":"\"\"\"\nRead in NGS Data (Only done once)\n\"\"\"\nread_ngs_data = False # Set to true to read NGS data (takes time and uses a lot of memory)\n# Read in NGS data and combine.\n# First try to read parquet file if it exists\ndef load_NGS_df(path = \"../input/\"):\n    \n    # gets all csv with NGS in their filename\n    NGS_csvs = [path+file for file in os.listdir(path) if 'NGS' in file]\n    \n    df = pd.DataFrame() #initialize an empty dataframe\n    \n    # loop to csv then appends it to df\n    for path_csv in NGS_csvs:\n        _df = pd.read_csv(path_csv,low_memory=False)\n        df = df.append(_df,ignore_index=True)\n        del _df # deletes the _df to free up memory\n        \n    return df\n\nif read_ngs_data:\n    try:\n        ngs = pd.read_parquet('../input/ngs_combined_with_role_lssnap.parquet')\n    except:\n        print('No parquet file- reading from CSVs')\n        ngs = load_NGS_df()\n        ngs = pd.merge(ngs, pprd) # Merge player position data\n        ngs.columns = [col.lower() for col in ngs.columns]\n        # Add rows for long snapper position at snap (for reference x,y)\n        ngs_ls_at_snap = ngs.loc[(ngs['event'] == 'ball_snap') &\n                             (ngs['role'] == 'PLS')]\n\n        ngs = pd.merge(ngs,\n                   ngs_ls_at_snap[['season_year','gamekey','playid','time','x','y']],\n                   on=['season_year','gamekey','playid'],\n                   how='left',\n                   suffixes=('','_ls_at_snap'))\n        ngs.to_parquet('../input/ngs_combined_with_role_lssnap.parquet')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"c33d2170db003e0ce02491903ccf07642ba61d76","_kg_hide-input":true},"cell_type":"code","source":"\"\"\"\nCreate Dataframe with Generalized Punting Roles\ninclude which team they are on (punting/returning)\n\"\"\"\nrole_info_dict = {'GL': ['Gunner', 'Punting_Team'],\n                  'GLi': ['Gunner', 'Punting_Team'],\n                  'GLo': ['Gunner', 'Punting_Team'],\n                  'GR': ['Gunner', 'Punting_Team'],\n                  'GRi': ['Gunner', 'Punting_Team'],\n                  'GRo': ['Gunner', 'Punting_Team'],\n                  'P': ['Punter', 'Punting_Team'],\n                  'PC': ['Punter_Protector', 'Punting_Team'],\n                  'PPR': ['Punter_Protector', 'Punting_Team'],\n                  'PPRi': ['Punter_Protector', 'Punting_Team'],\n                  'PPRo': ['Punter_Protector', 'Punting_Team'],\n                  'PDL1': ['Defensive_Lineman', 'Returning_Team'],\n                  'PDL2': ['Defensive_Lineman', 'Returning_Team'],\n                  'PDL3': ['Defensive_Lineman', 'Returning_Team'],\n                  'PDR1': ['Defensive_Lineman', 'Returning_Team'],\n                  'PDR2': ['Defensive_Lineman', 'Returning_Team'],\n                  'PDR3': ['Defensive_Lineman', 'Returning_Team'],\n                  'PDL5': ['Defensive_Lineman', 'Returning_Team'],\n                  'PDL6': ['Defensive_Lineman', 'Returning_Team'],\n                  'PFB': ['PuntFullBack', 'Returning_Team'],\n                  'PLG': ['Punting_Lineman', 'Punting_Team'],\n                  'PLL': ['Defensive_Backer', 'Returning_Team'],\n                  'PLL1': ['Defensive_Backer', 'Returning_Team'],\n                  'PLL3': ['Defensive_Backer', 'Returning_Team'],\n                  'PLS': ['Punting_Longsnapper', 'Punting_Team'],\n                  'PLT': ['Punting_Lineman', 'Punting_Team'],\n                  'PLW': ['Punting_Wing', 'Punting_Team'],\n                  'PRW': ['Punting_Wing', 'Punting_Team'],\n                  'PR': ['Punt_Returner', 'Returning_Team'],\n                  'PRG': ['Punting_Lineman', 'Punting_Team'],\n                  'PRT': ['Punting_Lineman', 'Punting_Team'],\n                  'VLo': ['Jammer', 'Returning_Team'],\n                  'VR': ['Jammer', 'Returning_Team'],\n                  'VL': ['Jammer', 'Returning_Team'],\n                  'VRo': ['Jammer', 'Returning_Team'],\n                  'VRi': ['Jammer', 'Returning_Team'],\n                  'VLi': ['Jammer', 'Returning_Team'],\n                  'PPL': ['Punter_Protector', 'Punting_Team'],\n                  'PPLo': ['Punter_Protector', 'Punting_Team'],\n                  'PPLi': ['Punter_Protector', 'Punting_Team'],\n                  'PLR': ['Defensive_Backer', 'Returning_Team'],\n                  'PRRo': ['Defensive_Backer', 'Returning_Team'],\n                  'PDL4': ['Defensive_Lineman', 'Returning_Team'],\n                  'PDR4': ['Defensive_Lineman', 'Returning_Team'],\n                  'PLM': ['Defensive_Backer', 'Returning_Team'],\n                  'PLM1': ['Defensive_Backer', 'Returning_Team'],\n                  'PLR1': ['Defensive_Backer', 'Returning_Team'],\n                  'PLR2': ['Defensive_Backer', 'Returning_Team'],\n                  'PLR3': ['Defensive_Backer', 'Returning_Team'],\n                  'PLL2': ['Defensive_Backer', 'Returning_Team'],\n                  'PDM': ['Defensive_Lineman', 'Returning_Team'],\n                  'PDR5': ['Defensive_Lineman', 'Returning_Team'],\n                  'PDR6': ['Defensive_Lineman', 'Returning_Team'],\n                  }\nrole_info = pd.DataFrame.from_dict(role_info_dict, orient='index',\n                                   columns=['generalized_role', 'punting_returning_team']) \\\n    .reset_index() \\\n    .rename(columns={'index': 'role'})","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"5435302a44441f58618617100774e06ed85fe21d","_kg_hide-input":true},"cell_type":"code","source":"# Play information preprocessing\n\npenalties_list = ['Offensive Holding', 'Defensive 12 On-field', 'Illegal Block Above the Waist', 'Fair Catch Interference',\n                  'Running Into the Kicker', 'Unnecessary Roughness', 'Illegal Touch Kick',\n                  'Illegal Use of Hands', 'False Start', 'Out of Bounds on Punt', 'Horse Collar Tackle',\n                  'Face Mask', 'Ineligible Downfield Kick', 'Illegal Substitution', 'Illegal Formation',\n                  'Delay of Game', 'Illegal Blindside Block', 'Neutral Zone Infraction', 'Tripping',\n                  'Defensive Holding', 'Roughing the Kicker', 'Unsportsmanlike Conduct', 'Defensive Offside',\n                  'Interference with Opportunity to Catch', 'Illegal Motion', 'Chop Block', 'Clipping',\n                  'Invalid Fair Catch Signal', 'Illegal Shift', 'Offensive 12 On-field', 'Taunting',\n                  'Offensive Pass Interference', 'Disqualification', 'Defensive Pass Interference']\n\npi['PENALTY'] = (pi['playdescription'].str.contains('PENALTY')\n                 | pi['playdescription'].str.contains('Penalty'))\npi['declined'] = pi['playdescription'].str.contains('declined')\n\n\npi['Offensive Holding'] = pi['playdescription'].str.contains(\n    'Offensive Holding')\npi['Defensive 12 On-field'] = pi['playdescription'].str.contains(\n    'Defensive 12 On-field')\npi['Illegal Block Above the Waist'] = pi['playdescription'].str.contains(\n    'Illegal Block Above the Waist')\npi['Fair Catch Interference'] = pi['playdescription'].str.contains(\n    'Fair Catch Interference')\npi['Running Into the Kicker'] = pi['playdescription'].str.contains(\n    'Running Into the Kicker')\npi['Unnecessary Roughness'] = pi['playdescription'].str.contains(\n    'Unnecessary Roughness')\npi['Illegal Touch Kick'] = pi['playdescription'].str.contains(\n    'Illegal Touch Kick')\npi['Illegal Use of Hands'] = pi['playdescription'].str.contains(\n    'Illegal Use of Hands')\npi['False Start'] = pi['playdescription'].str.contains('False Start')\npi['Out of Bounds on Punt'] = pi['playdescription'].str.contains(\n    'Out of Bounds on Punt')\npi['Horse Collar Tackle'] = pi['playdescription'].str.contains(\n    'Horse Collar Tackle')\npi['Face Mask'] = pi['playdescription'].str.contains('Face Mask')\npi['Ineligible Downfield Kick'] = pi['playdescription'].str.contains(\n    'Ineligible Downfield Kick')\npi['Illegal Substitution'] = pi['playdescription'].str.contains(\n    'Illegal Substitution')\npi['Illegal Formation'] = pi['playdescription'].str.contains(\n    'Illegal Formation')\npi['Delay of Game'] = pi['playdescription'].str.contains('Delay of Game')\npi['Illegal Blindside Block'] = pi['playdescription'].str.contains(\n    'Illegal Blindside Block')\npi['Neutral Zone Infraction'] = pi['playdescription'].str.contains(\n    'Neutral Zone Infraction')\npi['Tripping'] = pi['playdescription'].str.contains('Tripping')\npi['Defensive Holding'] = pi['playdescription'].str.contains(\n    'Defensive Holding')\npi['Roughing the Kicker'] = pi['playdescription'].str.contains(\n    'Roughing the Kicker')\npi['Unsportsmanlike Conduct'] = pi['playdescription'].str.contains(\n    'Unsportsmanlike Conduct')\npi['Defensive Offside'] = pi['playdescription'].str.contains(\n    'Defensive Offside')\npi['Interference with Opportunity to Catch'] = pi['playdescription'].str.contains(\n    'Interference with Opportunity to Catch')\npi['Illegal Motion'] = pi['playdescription'].str.contains('Illegal Motion')\npi['Chop Block'] = pi['playdescription'].str.contains('Chop Block')\npi['Clipping'] = pi['playdescription'].str.contains('Clipping')\npi['Invalid Fair Catch Signal'] = pi['playdescription'].str.contains(\n    'Invalid Fair Catch Signal')\npi['Illegal Shift'] = pi['playdescription'].str.contains('Illegal Shift')\npi['Offensive 12 On-field'] = pi['playdescription'].str.contains(\n    'Offensive 12 On-field')\npi['Taunting'] = pi['playdescription'].str.contains('Taunting')\npi['Offensive Pass Interference'] = pi['playdescription'].str.contains(\n    'Offensive Pass Interference')\npi['Disqualification'] = pi['playdescription'].str.contains('Disqualification')\npi['Defensive Pass Interference'] = pi['playdescription'].str.contains(\n    'Defensive Pass Interference')\n\n# No Play\npi['No Play'] = pi['playdescription'].str.contains('No Play')\n\npi['count'] = 1\n\n# Play challended / reviewed\npi['Challenged'] = pi['playdescription'].str.contains('challenged')\npi['Review'] = pi['playdescription'].str.contains('review')\npi['Upheld'] = pi['playdescription'].str.contains('Upheld')\npi['Reversed'] = pi['playdescription'].str.contains('REVERSED')\n\npi['Reversed'] = pi['playdescription'].str.contains('REVERSED')\npi['Out of Bounds'] = pi['playdescription'].str.contains('out of bounds')\npi['Touchback'] = pi['playdescription'].str.contains('Touchback')\npi['Fair Catch'] = pi['playdescription'].str.contains('fair catch')\npi['Muffed'] = pi['playdescription'].str.contains('MUFF')\npi['Downed'] = pi['playdescription'].str.contains('downed')\npi['Punt Blocked'] = pi['playdescription'].str.contains('BLOCKED')\npi['Touchdown'] = pi['playdescription'].str.contains('TOUCHDOWN')\npi['Returned for No Gain'] = pi['playdescription'].str.contains('no gain')\npi['Fumbled'] = pi['playdescription'].str.contains('FUMBLES')\npi['Pass Incomplete'] = pi['playdescription'].str.contains('pass incomplete')\n\npi['Returned'] = (~pi['No Play'] &\n                  ~pi['Out of Bounds'] &\n                  ~pi['Touchback'] &\n                  ~pi['Fair Catch'] &\n                  ~pi['Downed'] &\n                  ~pi['Muffed'] &\n                  ~pi['Punt Blocked'] &\n                  #  ~pi['PENALTY'] &\n                  ~pi['Returned for No Gain'] &\n                  ~pi['Fumbled'] &\n                  ~pi['Pass Incomplete'])\n\n# Extract the number of yards the return was for from the play description\npi['Returned For'] = pi[~pi['No Play'] &\n                        ~pi['Out of Bounds'] &\n                        ~pi['Touchback'] &\n                        ~pi['Fair Catch'] &\n                        ~pi['Downed'] &\n                        ~pi['Muffed'] &\n                        ~pi['Punt Blocked'] &\n                        #    ~pi['PENALTY'] &\n                        ~pi['Returned for No Gain'] &\n                        ~pi['Fumbled'] &\n                        ~pi['Pass Incomplete']]['playdescription'] \\\n    .str.extract('(for .* yard)', expand=True).fillna(False)\n\n# Cleanup ugly retrun yards and get int\npi['return_yards'] = pi['Returned For'].replace('for -2 yards. Lateral to C.Patterson to MIN 31 for 9 yards (W.Woodyard', 'for 9 yards') \\\n    .replace('for -4 yards. Lateral to R.Mostert to SEA 35 for 33 yard', 'for 33 yard') \\\n    .replace('for 10 yards (K.Byard', 'for 11 yard') \\\n    .replace('for 12 yards (A.Blake; W.Woodyard', 'for 12 yard') \\\n    .replace('for 14 yards (N.Palmer; K.Byard', 'for 14 yard') \\\n    .replace('for 44 yards (R.Blanton; C.Schmidt). Buffalo challenged the runner was in bounds ruling, and the play was REVERSED. C.Schmidt punts 35 yards to SEA 38, Center-G.Sanborn. T.Lockett ran ob at BUF 40 for 22 yard', 'for 22 yard') \\\n    .replace('for 2 yards (W.Woodyard', 'for 2 yard') \\\n    .replace('for -2 yards. Lateral to C.Patterson to MIN 31 for 9 yard', 'for 9 yard') \\\n    .replace('for 34 yards (C.Goodwin). Atlanta challenged the runner was in bounds ruling, and the play was REVERSED. M.Bosher punts 56 yards to NE 21, Center-J.Harris. J.Edelman ran ob at NE 47 for 26 yard', 'for 26 yard') \\\n    .dropna() \\\n    .str.replace('for ', '').str.replace('yard', '')\n# Zero return yards for 'no gain'\npi.loc[pi['Returned for No Gain'], 'return_yards'] = 0\n# pull just yards from string\n\n# Clean up `s` from yards field\npi['return_yards'] = pi['return_yards'].str[:4].replace(\n    's', ' ').replace('s', '')\npi.loc[~pi['return_yards'].isna() & pi['return_yards'].str.contains('s'), 'return_yards'] = \\\n    pi.loc[~pi['return_yards'].isna() & pi['return_yards'].str.contains('s')\n           ]['return_yards'].str[:2]\npi['return_yards'] = pi['return_yards'].str.replace('(', '')\npi['return_yards'] = pd.to_numeric(pi['return_yards'])\npi.loc[pi['Returned for No Gain'], 'return_yards'] = 0\npi['Returned for Negative Gain'] = (pi['Returned']) & (pi['return_yards'] < 0)\npi['Returned for Positive Gain'] = (pi['Returned']) & (pi['return_yards'] > 0)\n\n# Punting Distance\nnew = pi['playdescription'].str.split(pat='punts', n=1, expand=True)\npi['split1'] = new[0]\npi['split2'] = new[1]\npi['punt_distance'] = pi['split2'].str.extract('(\\d+)')\npi.drop(columns=[\"split1\", \"split2\"], inplace=True)\n# pi['punt_distance'] = pi['punt_distance'].fillna(0)\npi['punt_distance'] = pd.to_numeric(pi['punt_distance'])\n\n# Return yards calculation version 2 inspired by from https://www.kaggle.com/travisbuhrow/the-fair-catch-advancement-rule\nReturnYards = pi['playdescription'].str.split(\"for \", n=1, expand=True)\nReturnYards = ReturnYards.astype('str')\nReturnYards = ReturnYards[1].str.split(\" yard\", n=1, expand=True)\npi['Return_Yards'] = ReturnYards[0]\n# pi.loc[(play_info.Return_Indicator == False),'Return_Yards'] = 0\n# pi.loc[(play_info.Return_Yards.str.len() > 2),'Return_Yards'] = 0\npi['Return_Yards'] = pi['Return_Yards'].replace('None', np.nan)\npi.loc[pi['Return_Yards'].str.contains('no gain') == True, 'Return_Yards'] = 0\npi.loc[(pi.Return_Yards.str.len() > 3), 'Return_Yards'] = np.nan\npi['Return_Yards'] = pd.to_numeric(pi['Return_Yards'])\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"bcef90fb3390d92bcb1617fc5746c90ae78ba9d3"},"cell_type":"markdown","source":"# Section I - High Level Review of Punting Plays\nWe were provided data from 6681 different punt plays from 2016 and 2017 NFL Pre, Regular and Post-Season games.\n\n## Penalties\nWe can look at some of the common penalties that occur during punt plays. This may not impact our final analysis but we will gain insights to what type of penalties already occur.\n- 1077 Plays with Penalties (16.1%), 1038 plays have 1 type of penalty, 38 have 2 types of penalties, and 1 play has 3 types of penalties.\n- Most common penalty is `Offensive Holding` 442 of plays (6.6% of all punting plays) result in **Offensive Holding**, **Illegal Blocking Above the Waist** is second most common penalty with 227 (3.39% of all punting plays)\n- 243 plays (3.63% of punting plays) ended up not actually counting - ie a `No Play`\n- 16 Plays were reviewed and had the call of field REVERSED, 99 of these penalties declined\n- There was a single punting play with 5 total penalties! https://www.ninersnation.com/2017/8/19/16175124/referee-laughs-five-penalties-49ers-broncos-punt-pete-morelli"},{"metadata":{"trusted":true,"_uuid":"7807b8286f9c9f1e5b13c2f3bac725c0acc2a053","_kg_hide-input":true},"cell_type":"code","source":"# Plot Penalties\n\nax = pi[penalties_list].sum() \\\n    .sort_values() \\\n    .plot(kind='barh', figsize=(15, 10), color='grey', title='Punting Play Penalty Types')\n\nrects = ax.patches\n\n# For each bar: Place a label\nfor rect in rects:\n    # Get X and Y placement of label from rect.\n    x_value = rect.get_width()\n    y_value = rect.get_y() + rect.get_height() / 2\n\n    # Number of points between bar and label. Change to your liking.\n    space = 5\n    # Vertical alignment for positive values\n    ha = 'left'\n\n    # If value of bar is negative: Place label left of bar\n    if x_value < 0:\n        # Invert space to place label to the left\n        space *= -1\n        # Horizontally align label at right\n        ha = 'right'\n\n    # Use X value as label and format number with one decimal place\n    label = \"{:.0f}\".format(x_value)\n\n    # Create annotation\n    plt.annotate(\n        label,                      # Use `label` as label\n        (x_value, y_value),         # Place label at end of the bar\n        xytext=(space, 0),          # Horizontally shift label by `space`\n        textcoords=\"offset points\", # Interpret `xytext` as offset in points\n        va='center',                # Vertically center label\n        ha=ha)                      # Horizontally align label differently for\n                                    # positive and negative values.\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"793780b57d0748a4dd8890380774572799ae7d50"},"cell_type":"markdown","source":"## Penalties on plays involving concussions\n\n### Insights:\n- Surprisingly only one play involved a penalty for unnecessary roughness.\n- Offensive holding and illegal blocking above the waist are the most common penalties (3 each) that occur during concussion plays. These penalties are also common for non-injury plays."},{"metadata":{"trusted":true,"_uuid":"fe8364d7caa9ae27989270e9496ec86f4c066f36","_kg_hide-input":true},"cell_type":"code","source":"injury_plays = pd.merge(pi, vr)\n\nax = injury_plays[penalties_list].sum().loc[(injury_plays[penalties_list].sum() != 0)] \\\n    .sort_values() \\\n    .plot(kind='barh', figsize=(15, 4), color='grey', title='Penalties on Punts Involving Injuries')\n\nrects = ax.patches\n\n# For each bar: Place a label\nfor rect in rects:\n    # Get X and Y placement of label from rect.\n    x_value = rect.get_width()\n    y_value = rect.get_y() + rect.get_height() / 2\n\n    # Number of points between bar and label. Change to your liking.\n    space = 5\n    # Vertical alignment for positive values\n    ha = 'left'\n\n    # If value of bar is negative: Place label left of bar\n    if x_value < 0:\n        # Invert space to place label to the left\n        space *= -1\n        # Horizontally align label at right\n        ha = 'right'\n\n    # Use X value as label and format number with one decimal place\n    label = \"{:.0f}\".format(x_value)\n\n    # Create annotation\n    plt.annotate(\n        label,                      # Use `label` as label\n        (x_value, y_value),         # Place label at end of the bar\n        xytext=(space, 0),          # Horizontally shift label by `space`\n        textcoords=\"offset points\", # Interpret `xytext` as offset in points\n        va='center',                # Vertically center label\n        ha=ha)                      # Horizontally align label differently for\n                                    # positive and negative values.\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"3d6801ada3e064618aedaa20c7d4489cea023e3b"},"cell_type":"markdown","source":"## Punt plays where the punt did not count\n\n- 243 plays resulted in `No Play` meaning that a snap never occurred or the penalty negated the play. Of these 67 were false starts and 41 were delay of games."},{"metadata":{"trusted":true,"_uuid":"0fb1e5333da3f62fa8e12a136db4d943a1187189","_kg_hide-input":true},"cell_type":"code","source":"ax = pi[pi['No Play']][penalties_list].sum() \\\n    .sort_values() \\\n    .plot(kind='barh',\n          figsize=(15, 8),\n          color='grey',\n          title='Penalties Causing Negation of Play')\nrects = ax.patches\n\n# For each bar: Place a label\nfor rect in rects:\n    # Get X and Y placement of label from rect.\n    x_value = rect.get_width()\n    y_value = rect.get_y() + rect.get_height() / 2\n\n    # Number of points between bar and label. Change to your liking.\n    space = 5\n    # Vertical alignment for positive values\n    ha = 'left'\n\n    # If value of bar is negative: Place label left of bar\n    if x_value < 0:\n        # Invert space to place label to the left\n        space *= -1\n        # Horizontally align label at right\n        ha = 'right'\n\n    # Use X value as label and format number with one decimal place\n    label = \"{:.0f}\".format(x_value)\n\n    # Create annotation\n    plt.annotate(\n        label,                      # Use `label` as label\n        (x_value, y_value),         # Place label at end of the bar\n        xytext=(space, 0),          # Horizontally shift label by `space`\n        textcoords=\"offset points\", # Interpret `xytext` as offset in points\n        va='center',                # Vertically center label\n        ha=ha)                      # Horizontally align label differently for\n                                    # positive and negative values.\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"85944aea4e11d67c07bd4d0d2d82676b932e7c5c"},"cell_type":"markdown","source":"## Reviewed / Reversed Plays\nMany of the punt plays were reviewed. These reviews can occur because of a coaches challenge or by the replay official.\n- 41 Punting plays had some sort of review. 23 of the reviews were by challenge. 18 plays reviewed by the Replay Official\n- 52.1% of challenged plays were reversed\n- 22.2% of reviewed by Replay Official reversed"},{"metadata":{"trusted":true,"_uuid":"9391070733abddbc8c0a5e7462ff25b00402ecee","_kg_hide-input":true},"cell_type":"code","source":"pi[pi['Challenged'] | pi['Review']] \\\n    .groupby(['Challenged'])[['Upheld','Reversed']] \\\n    .sum() \\\n    .sort_values('Challenged', ascending=False)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"862141afb8d12f7c2e5b67db8670a898356aa15f"},"cell_type":"markdown","source":"## Result of Punt Plays\n\nPunt plays open the potential for many different things to happen. That's part of what makes them such an exciting part of football. We wanted to look at an aggregate level view of punt plays and how they resulted.\n- Potential outcomes of a punt:\n    - Punt Kicked out of Bounds - (play stops when out of bounds and ball is spotted at the point where it leaves the field of play).\n    - Touchback - The returning team is awarded the ball at the 20 yard line.\n    - Fair Catch - Punt returner indicates a fair catch\n    - Punt downed by punting team - Ball spotted at the point it was downed\n    - Muffed Catch (Could also be a fair catch)\n    - Punt Blocked\n    - Punt Returned for No Gain\n    - Punt Returned (positive or negative gain)\n    - Punt Returned and Fumbled\n    - Trick play (pass or run) - Pass could be incomplete\n    - *Other possible outcomes we did not see in the sample of punt plays like a fumbled snap by the punter - or a snap over the punters head*\n\nUsing the `play description` provided for each play we were able to pull \n1. Punt return yards for plays with returns.\n2. Punt distance. (unless a fake)"},{"metadata":{"trusted":true,"_uuid":"9ef67f52931387671b17c6646d440e9052fe7ca0","_kg_hide-input":true},"cell_type":"code","source":"ax = (pi[['Returned for Negative Gain',\n          'Returned for Positive Gain',\n          'Reversed',\n          'Out of Bounds',\n          'Touchback',\n          'Fair Catch',\n          'Muffed',\n          'Downed',\n          'Punt Blocked',\n          'Touchdown',\n          'Returned for No Gain',\n          'Fumbled',\n          'Pass Incomplete']]\n      .sum() / pi[['Returned for Negative Gain',\n                   'Returned for Positive Gain',\n                   'Reversed',\n                   'Out of Bounds',\n                   'Touchback',\n                   'Fair Catch',\n                   'Muffed',\n                   'Downed',\n                   'Punt Blocked',\n                   'Touchdown',\n                   'Returned for No Gain',\n                   'Fumbled',\n                   'Pass Incomplete']].count() * 100) \\\n    .sort_values(ascending=True) \\\n    .plot(kind='barh',\n          color='grey',\n          figsize=(15, 5),\n          title='Result of Punt Plays (Multiple Results Possible per play)')\n\nrects = ax.patches\n\n# For each bar: Place a label\nfor rect in rects:\n    # Get X and Y placement of label from rect.\n    x_value = rect.get_width()\n    y_value = rect.get_y() + rect.get_height() / 2\n\n    # Number of points between bar and label. Change to your liking.\n    space = 5\n    # Vertical alignment for positive values\n    ha = 'left'\n\n    # If value of bar is negative: Place label left of bar\n    if x_value < 0:\n        # Invert space to place label to the left\n        space *= -1\n        # Horizontally align label at right\n        ha = 'right'\n\n    # Use X value as label and format number with one decimal place\n    label = \"{:.2f}%\".format(x_value)\n\n    # Create annotation\n    plt.annotate(\n        label,                      # Use `label` as label\n        (x_value, y_value),         # Place label at end of the bar\n        xytext=(space, 0),          # Horizontally shift label by `space`\n        textcoords=\"offset points\",  # Interpret `xytext` as offset in points\n        va='center',                # Vertically center label\n        ha=ha)                      # Horizontally align label differently for\n    # positive and negative values.\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"62f0770a042715e342f98d98f6f43850f57d298d"},"cell_type":"markdown","source":"When looking at the results of an average punt, the majority of the time the punt is returned for positive gain with 34.67%. It still is surprising that 2.42% of punts are returned for a negative gain and 5.69% of plays are returned for no gain - almost as many punts as result in a touchback! Many of these type of plays are not exciting for the fans and yet put the players at what we believe to be an unnecessary risk. 24.83% of plays result in a fair catch, but seeing that many result in negative or no gain - this number should be higher.\n\nNext we look at the results of plays that resulted in a concussion. With a sample size this small it is hard to make any conclusions about the result correlating with the injury - but we do note that most were returned for a positive gain. Three were returned for no gain and three were downed."},{"metadata":{"trusted":true,"_uuid":"5b49a77413b1c22867dcbbf90d12eb1e7b2b1679","_kg_hide-input":true},"cell_type":"code","source":"ax = pd.merge(pi, vr, on=['season_year',\n                          'playid',\n                          'gamekey'])[['Returned for Negative Gain',\n                                       'Returned for Positive Gain',\n                                       'Reversed',\n                                       'Out of Bounds',\n                                       'Touchback',\n                                       'Fair Catch',\n                                       'Muffed',\n                                       'Downed',\n                                       'Punt Blocked',\n                                       'Touchdown',\n                                       'Returned for No Gain',\n                                       'Fumbled',\n                                       'Pass Incomplete']] \\\n    .sum() \\\n    .sort_values(ascending=True) \\\n    .plot(kind='barh',\n          color='grey',\n          figsize=(15, 5),\n          title='Result of Punts involving Concussions',\n          grid=True)\n\nrects = ax.patches\nax.grid(False)\n# For each bar: Place a label\nfor rect in rects:\n    # Get X and Y placement of label from rect.\n    x_value = rect.get_width()\n    y_value = rect.get_y() + rect.get_height() / 2\n\n    # Number of points between bar and label. Change to your liking.\n    space = 5\n    # Vertical alignment for positive values\n    ha = 'left'\n\n    # If value of bar is negative: Place label left of bar\n    if x_value < 0:\n        # Invert space to place label to the left\n        space *= -1\n        # Horizontally align label at right\n        ha = 'right'\n\n    # Use X value as label and format number with one decimal place\n    label = \"{:.0f}\".format(x_value)\n\n    # Create annotation\n    plt.annotate(\n        label,                      # Use `label` as label\n        (x_value, y_value),         # Place label at end of the bar\n        xytext=(space, 0),          # Horizontally shift label by `space`\n        textcoords=\"offset points\",  # Interpret `xytext` as offset in points\n        va='center',                # Vertically center label\n        ha=ha)                      # Horizontally align label differently for\n    # positive and negative values.\n\nmajor_ticks = np.arange(0, 28, 2)\nax.set_xticks(major_ticks)\nax.grid(True)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"47d4ac081e5344fd9fed8ebb05ea2a8d3db85ed5"},"cell_type":"markdown","source":"## Distribution of Return Yards\nNext week looked at the distribution of return yards for all the plays provided.  We've excluded plays that resulted in a penalty that reversed the call on the field. We can see there is a fairly normal distribution, except for the large number of returns for no gain (0 yards). This is possibly due to the fact that returners can be tackled almost right after they receive the punt- but if they managed to dodge the first tacklers (gunners) they tend to make around a 7 or 8 yard gain."},{"metadata":{"trusted":true,"_uuid":"45e4cd1d24d5828c3370bb3a37cdee3234df8dbf","_kg_hide-input":true},"cell_type":"code","source":"sns.set(style=\"white\")\nplt.style.use('ggplot')\npi.loc[(~pi['Reversed']) & (~pi['PENALTY'])]['Return_Yards'] \\\n    .plot(kind='hist', figsize=(15, 5),\n          bins=55, color='grey',\n          title='Distribution of Return Yards on Punt Plays (Excluding Penalty and Reversed Plays)')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"96857c701f257f02c589de8585cda74baf93eaff","_kg_hide-input":true},"cell_type":"code","source":"print('Median return distance is for : {:.2f} yards'.format(pi['Return_Yards'].median()))\nprint('Mean return distance is for   : {:.2f} yards'.format(pi['Return_Yards'].mean()))","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"fff14e1be44f2475d65118252754a69127ef7ced"},"cell_type":"code","source":"# Return yards preprocessing\nreturn_yards_dropna = pi.loc[(~pi['Reversed']) & (~pi['PENALTY'])][['Return_Yards']].dropna()\ntry:\n    return_yards_dropna = return_yards_dropna.drop('Return_Summary', axis=1)\nexcept:\n    pass\nreturn_yards_dropna.loc[return_yards_dropna['Return_Yards'] < 0, 'Return_Summary'] = 'Negative Return'\nreturn_yards_dropna.loc[return_yards_dropna['Return_Yards'] == 0, 'Return_Summary'] = 'No Gain'\nreturn_yards_dropna.loc[(return_yards_dropna['Return_Yards'] > 0) &\n                        (return_yards_dropna['Return_Yards'] <= 5), 'Return_Summary'] = 'Return +0 to 5 Yards'\nreturn_yards_dropna.loc[(return_yards_dropna['Return_Yards'] > 5) &\n                        (return_yards_dropna['Return_Yards'] <= 10), 'Return_Summary']= 'Return +5-10 Yards'\nreturn_yards_dropna.loc[(return_yards_dropna['Return_Yards'] > 10) &\n                        (return_yards_dropna['Return_Yards'] <= 15), 'Return_Summary'] = 'Return +10-15 Yards'\nreturn_yards_dropna.loc[(return_yards_dropna['Return_Yards'] > 15) &\n                        (return_yards_dropna['Return_Yards'] <= 20), 'Return_Summary'] = 'Return +15-20 Yards'\nreturn_yards_dropna.loc[(return_yards_dropna['Return_Yards'] > 20), 'Return_Summary'] = 'Return Over +20 Yards'\n\nreturn_yards_dropna.groupby('Return_Summary').count()['Return_Yards']\nreturn_yards_dropna['Return_Summary'] = pd.Categorical(return_yards_dropna['Return_Summary'],\n                                                       [\"Negative Return\",\n                                                        \"No Gain\",\n                                                        \"Return +0 to 5 Yards\",\n                                                        \"Return +5-10 Yards\",\n                                                        \"Return +10-15 Yards\",\n                                                        \"Return +15-20 Yards\",\n                                                        \"Return Over +20 Yards\"])","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"ad64e3f41d9158717b521626c796bbf357d66c37"},"cell_type":"markdown","source":"The same information is shown below, but as a percentage of all plays involving a return. We see the majority of returns (27.25%) are for 5-10 yards followed by returns over 0-10 yards (22.11%). Big returns of 20+ yards are rare (8.02%) but still occur often enough to make punts an exciting play to watch. Our team believes that we do not want to diminish these \"exciting\" plays since they are so integral to the integrity of football. Still, the median return of a punt play is for 7 yards- which is not a game changer compared to a typical offensive play."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"ee078ef6fa02136ab8600a40708e879757aeb8c6"},"cell_type":"code","source":"ax = (return_yards_dropna.groupby('Return_Summary') \\\n    .count() / len(return_yards_dropna) * 100) \\\n    .sort_index(ascending=False) \\\n    .plot(kind='barh', figsize=(15, 5),\n        color='grey',\n         title='Percentage of Returns by Return Yards',\n         legend=False)\n\nrects = ax.patches\nax.grid(False)\n# For each bar: Place a label\nfor rect in rects:\n    # Get X and Y placement of label from rect.\n    x_value = rect.get_width()\n    y_value = rect.get_y() + rect.get_height() / 2\n\n    # Number of points between bar and label. Change to your liking.\n    space = 5\n    # Vertical alignment for positive values\n    ha = 'left'\n\n    # If value of bar is negative: Place label left of bar\n    if x_value < 0:\n        # Invert space to place label to the left\n        space *= -1\n        # Horizontally align label at right\n        ha = 'right'\n\n    # Use X value as label and format number with one decimal place\n    label = \"{:.2f}%\".format(x_value)\n\n    # Create annotation\n    ax.annotate(\n        label,                      # Use `label` as label\n        (x_value, y_value),         # Place label at end of the bar\n        xytext=(space, 0),          # Horizontally shift label by `space`\n        textcoords=\"offset points\",  # Interpret `xytext` as offset in points\n        va='center',                # Vertically center label\n        ha=ha)                      # Horizontally align label differently for\n    # positive and negative values.\nax.set_ylabel('')\nax.grid()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"6afb52e821fc94916f6309c09d3259886a753a71"},"cell_type":"markdown","source":"## Review of Punting Distances\n\nNext we look at the punting distance for each play. Longer punts are not always better, in situations where there is a potential for a touchback- and when the punter can \"out-punt\" his coverage unit. We can see that from all the punt data, the distribution of punts is fairly normal with a mean around 45.5 yards."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"841602665c228566b1d35875251c953a8d059edf"},"cell_type":"code","source":"pi['punt_distance'].plot(kind='hist',\n                         figsize=(15, 5),\n                         bins=79,\n                         title='Distribution of Punt Distances for all plays',\n                        color='grey')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"67f426d6fd0635234efcc1e370c4418767c5bf09"},"cell_type":"code","source":"print('The mean distance of a punt is {:.2f} yards'.format(pi['punt_distance'].mean()))\nprint('The median distance of a punt is {:.2f} yards'.format(pi['punt_distance'].median()))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"fd034a3f004ff6c22977d4cbf82b14b055d3eaf3"},"cell_type":"markdown","source":"When we look only at plays where concussions occurred the mean punt distance is slightly longer 47.4, but the distribution visually we can see is skewed towards longer punts. This may or may not be significant but is important to note."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"b8ae11768105b9369a5f6c9a592e17becccff61a"},"cell_type":"code","source":"injury_plays_df = pd.merge(vr, pi)\n\ninjury_plays_df['punt_distance'].plot(kind='hist',\n                         figsize=(15, 5),\n                         bins=10,\n                         title='Distribution of Punt Distances',\n                        color='grey')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"ad636047601b1b967f1f6e2239786fc152f90ecd"},"cell_type":"code","source":"print('The mean distance of punts involving a concussion is {:.2f} yards'.format(injury_plays_df['punt_distance'].mean()))\nprint('The median distance of punts involving a concussion punt is {:.2f} yards'.format(injury_plays_df['punt_distance'].median()))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"3daaf80a15782660d7b21fffb016eb2c7ea7e1c6"},"cell_type":"markdown","source":"# Section II - Focused Analysis of Punts resulting in concussions\n\nIt's important to have a full understanding of which plays resulted in injury, and gathering intuition for the causes of these injuries. We reviewed all 37 punt plays involving a concussion extensively. This included analyzing video footage- taking notes of the type of situations that caused the injury. We also plotted each play out reviewing player positions and direction of the play. Additionally we reviewed the individual player speed and direction components of the NGS play noting trends in the direction of players especially prior to impact. Of course we also took note of the outcome of the play (fair catch, return, fake, etc)."},{"metadata":{"trusted":true,"_uuid":"a8e36941412214ad93af59c0314743178caaaef0","_kg_hide-input":true},"cell_type":"code","source":"# Data Prep\ninjury_play_ngs = pd.read_parquet(\n    '../input/nfl-punt-data-preprocessing-ngs-injury-plays/NGS-injury-plays.parquet')\ngsisid_numbers = ppd.groupby('gsisid')['number'].apply(\n    lambda x: \"%s\" % ', '.join(x))\ngsisid_numbers = pd.DataFrame(gsisid_numbers).reset_index()\n# Add Player Number and Direction\nvr_with_number = pd.merge(\n    vr, gsisid_numbers, how='left', suffixes=('', '_injured'))\nvr_with_number['primary_partner_gsisid'] = vr_with_number['primary_partner_gsisid'].replace(\n    'Unclear', np.nan).fillna(0).astype('int')\nvr_with_number = pd.merge(vr_with_number, gsisid_numbers, how='left',\n                          left_on='primary_partner_gsisid', right_on='gsisid', suffixes=('', '_primary_partner'))\nvr = vr_with_number\n\nvr_merged = pd.merge(vr, pprd)\nvr_merged = pd.merge(vr_merged, role_info)\n\n\nvr_merged = pd.merge(vr_merged, pprd, left_on=['season_year', 'gamekey', 'playid', 'primary_partner_gsisid'],\n                     right_on=['season_year', 'gamekey', 'playid', 'gsisid'], how='left',\n                     suffixes=('', '_primary_partner'))\nvr_merged = pd.merge(vr_merged, role_info, left_on='role_primary_partner',\n                     right_on='role', how='left', suffixes=('', '_primary_partner'))\n\nvr_merged = vr_merged.fillna('None')\nvr_merged['count'] = 1\n\nvr_merged['generalized_role'] = vr_merged['generalized_role'].str.replace(\n    '_', ' ')\nvr_merged['generalized_role_primary_partner'] = vr_merged['generalized_role_primary_partner'].str.replace(\n    '_', ' ')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"809f946b1ad83c2081cf2bda809714092422487d"},"cell_type":"markdown","source":"## Injury Play Details\nWe can see that the top primary impact is balanced between helmet-to-ground and helmet-to-body impact causing the concussion. Helmet to ground occurred twice. As we know, the NFL has rules in place to leading with the helmet, but it is surprising that helmet to body contact is on par with the rate of helmet to helmet concussions."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"675705fa76161d8c95d5819750f8d84b6a612cd0"},"cell_type":"code","source":"ax = vr_merged.groupby('primary_impact_type').count() \\\n    .sort_values('count')['count'].plot(kind='barh',\n                                        figsize=(15, 5),\n                                        color='grey',\n                                        title='Primary Impact Type on Concussion Plays')\nrects = ax.patches\n# For each bar: Place a label\nfor rect in rects:\n    # Get X and Y placement of label from rect.\n    x_value = rect.get_width()\n    y_value = rect.get_y() + rect.get_height() / 2\n\n    # Number of points between bar and label. Change to your liking.\n    space = 5\n    # Vertical alignment for positive values\n    ha = 'left'\n\n    # If value of bar is negative: Place label left of bar\n    if x_value < 0:\n        # Invert space to place label to the left\n        space *= -1\n        # Horizontally align label at right\n        ha = 'right'\n\n    # Use X value as label and format number with one decimal place\n    label = \"{}\".format(x_value)\n\n    # Create annotation\n    ax.annotate(\n        label,                      # Use `label` as label\n        (x_value, y_value),         # Place label at end of the bar\n        xytext=(space, 0),          # Horizontally shift label by `space`\n        textcoords=\"offset points\",  # Interpret `xytext` as offset in points\n        va='center',                # Vertically center label\n        ha=ha)                      # Horizontally align label differently for\n    # positive and negative values.\nax.set_ylabel('')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"781af785403fa7e8bb5801906db89f563e5cedc3"},"cell_type":"markdown","source":"The activity of the players involved in a concussion play shows us that the most common actions are **concussion from tackling another player** and **concussed while blocking another player** followed by **concussed while being tacked**.\n\nWe found it interesting that the top two actions show that the player receiving the concussion was the one initiating the action (tacking or blocking not being blocked or tacked)."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"e101127e5150d7e664baf5b25e5c1e86ee34b1e3"},"cell_type":"code","source":"vr_merged['derived_text'] = \\\n    vr_merged.apply(lambda row: 'Injured player was {}, Iinjuring player was {}'.format(row['player_activity_derived'],\n                                                                                        row['primary_partner_activity_derived'])\n                    if type(row['primary_partner_activity_derived']) is str\n                    else 'Injured player was {}'.format(row['player_activity_derived']), axis=1)\nax = vr_merged.groupby('derived_text').count()['count'] \\\n    .sort_values() \\\n    .plot(kind='barh',\n          figsize=(15, 5),\n          color='grey',\n          title='Activity of Players involved in Concussion')\n\nrects = ax.patches\n# For each bar: Place a label\nfor rect in rects:\n    # Get X and Y placement of label from rect.\n    x_value = rect.get_width()\n    y_value = rect.get_y() + rect.get_height() / 2\n\n    # Number of points between bar and label. Change to your liking.\n    space = 5\n    # Vertical alignment for positive values\n    ha = 'left'\n\n    # If value of bar is negative: Place label left of bar\n    if x_value < 0:\n        # Invert space to place label to the left\n        space *= -1\n        # Horizontally align label at right\n        ha = 'right'\n\n    # Use X value as label and format number with one decimal place\n    label = \"{}\".format(x_value)\n\n    # Create annotation\n    ax.annotate(\n        label,                      # Use `label` as label\n        (x_value, y_value),         # Place label at end of the bar\n        xytext=(space, 0),          # Horizontally shift label by `space`\n        textcoords=\"offset points\",  # Interpret `xytext` as offset in points\n        va='center',                # Vertically center label\n        ha=ha)                      # Horizontally align label differently for\n    # positive and negative values.\nax.set_ylabel('')\nax.grid(True)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"ea800f936720b79336a98f66f6a597460801b19d"},"cell_type":"markdown","source":"## Generalizing the Player Roles\n\nTo help generalize the player positions, we've taken the player roles and defined them as either punting or receiving. We've generalized the positions below and believe this allows us to better aggregate trends between plays:\n1. Punting Team or Returning Team\n2. General Position\n    - Linemen\n    - Wing\n    - Longsnapper\n    - Punter Protector\n    - Punter\n    - Punt Fullback (Returner Protector)\n    - Jammer\n    - Gunner\n    - Defensive Backer\n    \nSome main takeaways from this data are:\n- Punting team players have more occurrences of concussions than the returning team. Returning team more commonly the primary partner but only slightly (.\n- There are 3 cases of friendly fire- all involving the punting team.\n- Punting Team's linemen top the role that suffered concussions."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"1162f786fd121b46d62034d8ce43166984ba7c35"},"cell_type":"code","source":"fig, (ax1, ax2) = plt.subplots(1, 2, sharey=True, figsize=(15, 4))\nvr_merged \\\n    .groupby('punting_returning_team').count()['count'] \\\n    .rename(index={'Punting_Team': 'Punting Team',\n                   'Returning_Team': 'Returning Team'}) \\\n    .plot(kind='bar',\n          title='Concussions by Team',\n          color='grey',\n          rot=0,\n          ax=ax1)\nvr_merged \\\n    .groupby('punting_returning_team_primary_partner').count()['count'] \\\n    .rename(index={'Punting_Team': 'Punting Team',\n                   'Returning_Team': 'Returning Team'}) \\\n    .plot(kind='bar',\n          title='Primary Partner by Team',\n          color='grey',\n          rot=0,\n          ax=ax2)\nax1.set_xlabel('')\nax2.set_xlabel('')\nax1.set_ylabel('Count')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"051f6eea54d3874b21c3cdba41831e4f4ba98a50"},"cell_type":"code","source":"fig, (ax1, ax2) = plt.subplots(1, 2, sharey=False, figsize=(20, 4))\n\nax1 = vr_merged.groupby('generalized_role') \\\n    .sum()['count'] \\\n    .sort_values() \\\n    .plot(kind='barh',\n          color='grey',\n          title='Count of Injured Players by Generalized Role',\n          ax=ax1)\n\nrects = ax1.patches\nax1.grid(False)\n# For each bar: Place a label\nfor rect in rects:\n    # Get X and Y placement of label from rect.\n    x_value = rect.get_width()\n    y_value = rect.get_y() + rect.get_height() / 2\n\n    # Number of points between bar and label. Change to your liking.\n    space = 5\n    # Vertical alignment for positive values\n    ha = 'left'\n\n    # If value of bar is negative: Place label left of bar\n    if x_value < 0:\n        # Invert space to place label to the left\n        space *= -1\n        # Horizontally align label at right\n        ha = 'right'\n\n    # Use X value as label and format number with one decimal place\n    label = \"{:.0f}\".format(x_value)\n\n    # Create annotation\n    ax1.annotate(\n        label,                      # Use `label` as label\n        (x_value, y_value),         # Place label at end of the bar\n        xytext=(space, 0),          # Horizontally shift label by `space`\n        textcoords=\"offset points\",  # Interpret `xytext` as offset in points\n        va='center',                # Vertically center label\n        ha=ha)                      # Horizontally align label differently for\n    # positive and negative values.\n\nax2 = vr_merged.groupby('generalized_role_primary_partner') \\\n    .sum()['count'] \\\n    .sort_values() \\\n    .plot(kind='barh',\n          color='grey',\n          title='Count of Primary Partner in Injury by Generalized Role',\n          ax=ax2)\n\nrects = ax2.patches\nax2.grid(False)\n# For each bar: Place a label\nfor rect in rects:\n    # Get X and Y placement of label from rect.\n    x_value = rect.get_width()\n    y_value = rect.get_y() + rect.get_height() / 2\n\n    # Number of points between bar and label. Change to your liking.\n    space = 5\n    # Vertical alignment for positive values\n    ha = 'left'\n\n    # If value of bar is negative: Place label left of bar\n    if x_value < 0:\n        # Invert space to place label to the left\n        space *= -1\n        # Horizontally align label at right\n        ha = 'right'\n\n    # Use X value as label and format number with one decimal place\n    label = \"{:.0f}\".format(x_value)\n\n    # Create annotation\n    ax2.annotate(\n        label,                      # Use `label` as label\n        (x_value, y_value),         # Place label at end of the bar\n        xytext=(space, 0),          # Horizontally shift label by `space`\n        textcoords=\"offset points\",  # Interpret `xytext` as offset in points\n        va='center',                # Vertically center label\n        ha=ha)                      # Horizontally align label differently for\n    # positive and negative values.\nax1.set_ylabel('')\nax2.set_ylabel('')\nax1.grid()\nax2.grid()\nplt.subplots_adjust(left=0.02)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"cf72ec94a17fffb151b86d96acf744491d2f1f01"},"cell_type":"markdown","source":"Contrary to our assumptions going into this analysis. The top player involved in punting play concussions is not the punt returner or gunners, but instead punting team linemen. Punt returners are still high on the list, followed by defensive linemen. We understand that there is only a single punt returner on a play in contrast to numerous linemen, so when generalizing the potential for injury of these players is increased by mere fact that more of them are on the field. Regardless, this analysis shows us that punt returners shouldn't be the only focus of the rule change."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"a607e0a711b541358002ad0adad5432589b8a8a1"},"cell_type":"code","source":"ax = pd.concat([vr_merged[['generalized_role_primary_partner',\n                           'punting_returning_team_primary_partner']]\n                .rename(columns={'generalized_role_primary_partner': 'generalized_role',\n                                 'punting_returning_team_primary_partner': 'punting_returning_team'}),\n                vr_merged[['generalized_role', 'punting_returning_team']]]) \\\n    .groupby(['generalized_role']) \\\n    .count() \\\n    .sort_values('punting_returning_team') \\\n    .drop('None') \\\n    .plot(kind='barh',\n          legend=False,\n          title='Roles involved in Concussion Plays (Injured or Partner)',\n          figsize=(15, 5), color='grey')\n\n\nrects = ax.patches\nax.grid(False)\n# For each bar: Place a label\nfor rect in rects:\n    # Get X and Y placement of label from rect.\n    x_value = rect.get_width()\n    y_value = rect.get_y() + rect.get_height() / 2\n\n    # Number of points between bar and label. Change to your liking.\n    space = 5\n    # Vertical alignment for positive values\n    ha = 'left'\n\n    # If value of bar is negative: Place label left of bar\n    if x_value < 0:\n        # Invert space to place label to the left\n        space *= -1\n        # Horizontally align label at right\n        ha = 'right'\n\n    # Use X value as label and format number with one decimal place\n    label = \"{:.0f}\".format(x_value)\n\n    # Create annotation\n    plt.annotate(\n        label,                      # Use `label` as label\n        (x_value, y_value),         # Place label at end of the bar\n        xytext=(space, 0),          # Horizontally shift label by `space`\n        textcoords=\"offset points\",  # Interpret `xytext` as offset in points\n        va='center',                # Vertically center label\n        ha=ha)                      # Horizontally align label differently for\n    # positive and negative values.\nax.set_xticklabels('')\nax.set_ylabel('Generalized Role')\nax.grid()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"bffc5f58c41908270f497b2693f2a2b613faa542"},"cell_type":"markdown","source":"## Common Pairing of Player Roles involved in Injuries\nSome observations:\n- Most common pairing is to the punting lineman when making contact with the punt returner and NOT with gunners.\n- Defensive linemen making contact with Punting Wing and Punting Linemen is common.\n- Gunner / Punt Returner combination only occurs 3 times."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"fb972cfcccd3a5069591f81b706d90f86217162f"},"cell_type":"code","source":"vr_merged.groupby(['generalized_role', 'generalized_role_primary_partner']) \\\n    .sum()['count'] \\\n    .reset_index() \\\n    .rename(columns={'generalized_role': 'Injured Player',\n                     'generalized_role_primary_partner': 'Primary Partner'}) \\\n    .sort_values('count', ascending=False).reset_index(drop=True)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"7a846eb6617abc30dbf4e8cf470a14c1da624c84"},"cell_type":"markdown","source":"## Injury Play Analysis - Plotting all 37 Plays\nPlotting these plays was essential in allowing us to see diverse these 37 plays are. No immediate trends show themselves when looking at all plays. It's also important that our sample size is relatively small and we should not try to confuse correlations with causation for the punt types and cause of concussions.\n\nInterpreting our Punt Play Plots\n- **Blue Line**: Indicates the path of the player who suffered a concussion.\n- **Yellow Line**: Indicates the path of the player who was listed as the primary partner in the injury.\n- **White Line**: If the punt returner is not involved in the injury this shows his path\n- **Orange Circles**: Punting team formation at time of the snap\n- **Purple Circles**: Returning Team formation at time of snap\n- **Red Circe** - Starting position of injured player\n- **Red `+`** - The approximate location where the injury occurred (calculated at the point where the injured player and primary partner were at the closest distance)\n\nWe've also removed any NGS data points from before the snap and after the play has ended to allow us to only focus on moments of action. There were a few plays where the impact occurred slightly after the end of the play."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"7006f2c8950ad44960cfe8a764ce43993f3e634f"},"cell_type":"code","source":"\"\"\"\nThis cell block contains functions for interacting with the NGS data.\nPlotting compass of player angle and velocity along with the playing field\n\"\"\"\n\n\ndef compass(angles, radii, arrowprops=None, ax=None):\n    \"\"\"\n    Compass draws a graph that displays the vectors with\n    components `u` and `v` as arrows from the origin.\n\n    Examples\n    --------\n    >>> import numpy as np\n    >>> u = [+0, +0.5, -0.50, -0.90]\n    >>> v = [+1, +0.5, -0.45, +0.85]\n    >>> compass(u, v)\n    \"\"\"\n\n    #angles, radii = cart2pol(u, v)\n    if ax is None:\n        fig, ax = plt.subplots(subplot_kw=dict(polar=True))\n\n    kw = dict(arrowstyle=\"->\", color='k')\n    if arrowprops:\n        kw.update(arrowprops)\n    [ax.annotate(\"\", xy=(angle, radius), xytext=(0, 0),\n                 arrowprops=kw) for\n     angle, radius in zip(angles, radii)]\n\n    ax.set_ylim(0, np.max(radii))\n\n    return ax\n\n\ndef trim_play_action(df):\n    \"\"\"\n    Trims a play to only the duration of action\n    \"\"\"\n    if len(df.loc[df['event'] == 'ball_snap']['time'].values) == 0:\n        print('........No Snap for this play')\n        ball_snap_time = df['time'].min()\n    else:\n        ball_snap_time = df.loc[df['event'] ==\n                                'ball_snap']['time'].values.min()\n\n    try:\n        end_time = df.loc[(df['event'] == 'out_of_bounds') |\n                          (df['event'] == 'downed') |\n                          (df['event'] == 'tackle') |\n                          (df['event'] == 'punt_downed') |\n                          (df['event'] == 'fair_catch') |\n                          (df['event'] == 'touchback') |\n                          (df['event'] == 'touchdown')]['time'].values.max()\n    except ValueError:\n        end_time = df['time'].values.max()\n    df = df.loc[(df['time'] >= ball_snap_time) & (df['time'] <= end_time)]\n    return df\n\n\ndef plot_injury_play(season_year, gamekey, playid,\n                     plot_velocity=False, ax3=None, display_url=False,\n                     figsize_velocity=(5, 4),\n                     **kwargs):\n    vr_thisplay = vr.loc[(vr['season_year'] == season_year) &\n                         (vr['playid'] == playid) &\n                         (vr['gamekey'] == gamekey)]\n\n    play = injury_play_ngs.loc[(injury_play_ngs['season_year'] == season_year) &\n                               (injury_play_ngs['playid'] == playid) &\n                               (injury_play_ngs['gamekey'] == gamekey)].copy()\n\n    # Calculate velocity in meters per second\n    play['dis_meters'] = play['dis'] / 1.0936  # Add distance in meters\n    # Speed\n    play['dis_meters'] / 0.01\n    play['v_mps'] = play['dis_meters'] / 0.1\n\n    # Filter to only duration of play\n    play = trim_play_action(play)\n\n    # play = pd.read_csv('../working/playlevel/during_play/{}-{}-{}.csv'.format(season_year, gamekey, playid))\n    play['dir_theta'] = play['dir'] * np.pi / 180\n\n    # Video footage link\n    url_link = vfi.loc[(vfi['season_year'] == season_year) &\n                       (vfi['playid'] == playid) &\n                       (vfi['gamekey'] == gamekey)]['preview link (5000k)'].values[0]\n\n    playdescription = vfi.loc[(vfi['season_year'] == season_year) &\n                              (vfi['playid'] == playid) &\n                              (vfi['gamekey'] == gamekey)]['playdescription'].values[0]\n    if display_url:\n        display(HTML(\"\"\"<a href=\"{}\">Play-Video-Link</a>\"\"\".format(url_link)))\n        print('Injured player number {} was injured while {} with primary impact {}'\n              .format(vr_thisplay['number'].values[0],\n                      vr_thisplay['player_activity_derived'].values[0],\n                      vr_thisplay['primary_impact_type'].values[0]))\n\n    # Determine time of injury\n    injured = play.loc[play['injured_player']]\n    primarypartner = play.loc[play['primary_partner_player']]\n    injury_time = None\n    if len(primarypartner) != 0:\n        inj_and_pp = pd.merge(injured[['time', 'x', 'y']], primarypartner[[\n                              'time', 'x', 'y']], on='time', suffixes=('_inj', '_pp'))\n        inj_and_pp['dis_from_eachother'] = np.sqrt(np.square(inj_and_pp['x_inj'] -\n                                                             inj_and_pp['x_pp']) +\n                                                   np.square(inj_and_pp['y_inj'] -\n                                                             inj_and_pp['y_pp']))\n        injury_time = inj_and_pp.sort_values('dis_from_eachother')[\n            'time'].values[0]\n    # PLOT\n    fig, ax3 = create_football_field(**kwargs)\n\n    # Plot path of injured player\n    d = play.loc[play['injured_player']]\n    injured_player_role = play.loc[play['injured_player']]['role'].values[0]\n    d.plot('x', 'y', kind='scatter', ax=ax3,  zorder=5, color='blue', alpha=0.3,\n           xlim=(0, 120), ylim=(0, 53.3),\n           label='Injured Player Path - Role: {}'.format(injured_player_role))  # Plot injured player path\n    play.loc[(play['punting_returning_team'] == 'Returning_Team') &\n             (play['event'] == 'ball_snap')].plot('x', 'y', alpha=1, kind='scatter',\n                                                  color='purple', ax=ax3, zorder=5, style='+',\n                                                  label='Returning Team Player')\n    play.loc[(play['punting_returning_team'] == 'Punting_Team') &\n             (play['event'] == 'ball_snap')].plot('x', 'y', alpha=1, kind='scatter',\n                                                  color='orange', ax=ax3, zorder=4, style='+',\n                                                  label='Punting Team Player')\n    start_pos = d.loc[d['time'] == d['time'].min()]\n    inj_star_pos = ax3.scatter(start_pos['x'], start_pos['y'], color='red',\n                               zorder=5, label='Injured Player Starting Position')\n    end_pos = d.loc[d['time'] == d['time'].max()]\n    ax3.scatter(end_pos['x'], end_pos['y'], color='black',\n                zorder=5, label='Injured Player Ending Position')\n    if injury_time:\n        inj_pos = d.loc[d['time'] == injury_time]\n    pp_player_role = None\n    if len(primarypartner) != 0:\n        pp_player_role = play.loc[play['primary_partner_player']\n                                  ]['role'].values[0]\n        play.loc[play['primary_partner_player']].plot('x', 'y', kind='scatter',\n                                                      xlim=(0, 120), ylim=(0, 53.3),\n                                                      ax=ax3, color='yellow', alpha=0.3, zorder=3,\n                                                      label='Primary Partner Path - Role {}'.format(pp_player_role))\n        ax3.scatter(inj_pos['x'],\n                    inj_pos['y'],\n                    color='red',\n                    zorder=5,\n                    s=50,\n                    marker='+',\n                    label='Aproximate Location of Injury')\n    play_info_string = 'Season {} - Gamekey {} - Playid {}'.format(\n        season_year, gamekey, playid)\n    injured_player_string = 'Injured Player Number: {} - action {}' \\\n        .format(vr_thisplay['number'].values[0],\n                vr_thisplay['player_activity_derived'].values[0])\n    primary_partner_string = 'Primary Partner Player Number: {} - action {}' \\\n        .format(vr_thisplay['number_primary_partner'].values[0],\n                vr_thisplay['primary_partner_activity_derived'].values[0])\n    # Plot punt return path if not one of the players.\n    if (injured_player_role != 'PR') and (pp_player_role != 'PR'):\n        punt_returner = play.loc[play['role'] == 'PR']\n        punt_returner.plot('x', 'y', kind='scatter', ax=ax3,  zorder=3, color='white', alpha=0.3,\n                           label='Punt Returner Path')\n    # print(playdescription)\n    plt.suptitle(play_info_string, fontsize=15)\n    plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))\n\n    if plot_velocity:\n        # Plot injured player compass\n\n        fig3, (ax1, ax2) = plt.subplots(\n            1, 2, subplot_kw=dict(polar=True), figsize=figsize_velocity)\n\n        d = play.loc[play['injured_player']]\n        role = d.role.values[0]\n\n        ax1 = compass(d['dir_theta'], d['v_mps'],\n                      arrowprops={'alpha': 0.3}, ax=ax1)\n        ax1.set_theta_zero_location(\"N\")\n        ax1.set_theta_direction(-1)\n        ax1.set_title('Injured Player: {}'.format(role))\n        # Color point of time when inujury happened\n        if len(primarypartner) != 0:\n            theta_at_inj = d.loc[d['time'] ==\n                                 injury_time]['dir_theta'].values[0]\n            dis_at_inj = d.loc[d['time'] == injury_time]['v_mps'].values[0]\n            impact_arrow = ax1.annotate(\"\",\n                                        xy=(theta_at_inj, dis_at_inj), xytext=(\n                                            0, 0),\n                                        arrowprops={'color': 'orange'},\n                                        label='Aproximate Point of Impact')  # use cir mean\n            # plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))\n        # plt.suptitle('Velocity and Direction (Injured Player): {}'.format(role), x=0.52, y=1.01, fontsize=15)\n\n        if len(primarypartner) != 0:\n            # Plot primary partner compass\n            d = play.loc[play['primary_partner_player']]\n            role = d.role.values[0]\n            ax2 = compass(d['dir_theta'], d['v_mps'],\n                          arrowprops={'alpha': 0.3}, ax=ax2)\n            ax2.set_theta_zero_location(\"N\")\n            ax2.set_theta_direction(-1)\n            ax2.set_title('Primary Partner: {}'.format(role))\n            # Color point of time when inujury happened\n            theta_at_inj = d.loc[d['time'] ==\n                                 injury_time]['dir_theta'].values[0]\n            dis_at_inj = d.loc[d['time'] == injury_time]['v_mps'].values[0]\n            ax2.annotate(\"\", xy=(theta_at_inj, dis_at_inj), xytext=(\n                0, 0), arrowprops={'color': 'orange'})  # use cir mean\n            # plt.suptitle('Velocity and Direction (Primary Partner): {}'.format(role), x=0.52, y=1.01, fontsize=15)\n            plt.show()\n    return ax3","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"73ee2ad866fcaa95f0089d22d288f18447ee364a","_kg_hide-input":true},"cell_type":"code","source":"# Load preprocessed injury data (preprocessing done in seperate kernel)\ninjury_play_ngs = pd.read_parquet('../input/nfl-punt-data-preprocessing-ngs-injury-plays/NGS-injury-plays.parquet')\ninjury_play_ngs.loc[injury_play_ngs['role'] == 'PFB', 'punting_returning_team'] = 'Returning_Team' # Fix typo in preprocessing","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"4221c655ecc291f7854bad984071ed74f6192a00","_kg_hide-input":true},"cell_type":"code","source":"# Plot all plays\nfig, axes = plt.subplots(nrows=10, ncols=4, figsize=(23,30))\naxes_list = [item for sublist in axes for item in sublist] \n\ncount = 0\nfor i, play in injury_play_ngs.groupby(['season_year','gamekey','playid']):\n    \n    #print(i)\n    # Determine time of injury\n    injured = play.loc[play['injured_player']]\n    primarypartner = play.loc[play['primary_partner_player']]\n    injury_time = None\n    if len(primarypartner) != 0:\n        inj_and_pp = pd.merge(injured[['time','x','y']],\n                              primarypartner[['time','x','y']],\n                              on='time',\n                              suffixes=('_inj','_pp'))\n        inj_and_pp['dis_from_eachother'] = np.sqrt(np.square(inj_and_pp['x_inj'] -\n                                                             inj_and_pp['x_pp']) +\n                                                   np.square(inj_and_pp['y_inj'] - \n                                                             inj_and_pp['y_pp']))\n        injury_time = inj_and_pp.sort_values('dis_from_eachother')['time'].values[0]\n    \n    play = play.copy()\n    # Filter to only duration of play\n    play = trim_play_action(play)\n\n    rect = patches.Rectangle((0, 0), 120, 53.3, linewidth=0.1, edgecolor='r', facecolor='darkgreen', zorder=0)\n    figsize=(5, 3.3)\n    ax = axes_list.pop(0)\n    ax.add_patch(rect)\n\n    ax.plot([10, 10, 10, 20, 20, 30, 30, 40, 40, 50, 50, 60, 60, 70, 70, 80,\n              80, 90, 90, 100, 100, 110, 110, 120, 0, 0, 120, 120],\n             [0, 0, 53.3, 53.3, 0, 0, 53.3, 53.3, 0, 0, 53.3, 53.3, 0, 0, 53.3,\n              53.3, 0, 0, 53.3, 53.3, 0, 0, 53.3, 53.3, 53.3, 0, 0, 53.3],\n             color='white', linewidth=0.5)\n    # Endzones\n\n    ez1 = patches.Rectangle((0, 0), 10, 53.3,\n                            linewidth=0.1,\n                            edgecolor='r',\n                            facecolor='blue',\n                            alpha=0.2,\n                            zorder=0)\n    ez2 = patches.Rectangle((110, 0), 120, 53.3,\n                            linewidth=0.1,\n                            edgecolor='r',\n                            facecolor='blue',\n                            alpha=0.2,\n                            zorder=0)\n    ax.add_patch(ez1)\n    ax.add_patch(ez2)\n    # Line Numbers\n    for x in range(20, 110, 10):\n            numb = x\n            if x > 50:\n                numb = 120 - x\n            ax.text(x, 5, str(numb - 10),\n                     horizontalalignment='center',\n                     fontsize=6, # fontname='Arial',\n                     color='white')\n            ax.text(x - 0.95, 53.3 - 5, str(numb - 10),\n                     horizontalalignment='center',\n                     fontsize=6, # fontname='Arial',\n                     color='white', rotation=180)\n\n    hash_range = range(11, 110)\n    for x in hash_range:\n        ax.plot([x, x], [0.4, 0.7], color='white', linewidth=0.4)\n        ax.plot([x, x], [53.0, 52.5], color='white', linewidth=0.4)\n        ax.plot([x, x], [22.91, 23.57], color='white', linewidth=0.4)\n        ax.plot([x, x], [29.73, 30.39], color='white', linewidth=0.4)\n        \n        # Plot path of injured player\n    d = play.loc[play['injured_player']]\n    injured_player_role = play.loc[play['injured_player']]['role'].values[0]\n    d.plot('x', 'y', kind='scatter', ax=ax,  zorder=5, color='blue', alpha=0.3,\n           xlim = (0, 120), ylim=(0,53.3),\n           label='Injured Player Path - Role: {}'.format(injured_player_role)) #Plot injured player path\n    play.loc[(play['punting_returning_team'] == 'Returning_Team') &\n             (play['event'] == 'ball_snap')].plot('x', 'y', alpha=1, kind='scatter',\n                                                  color='purple', ax=ax, zorder=5, style='+',\n                                                  label='Returning Team Player')\n    play.loc[(play['punting_returning_team'] == 'Punting_Team')  &\n             (play['event'] == 'ball_snap')].plot('x', 'y', alpha=1, kind='scatter',\n                                                  color='orange', ax=ax, zorder=4, style='+',\n                                                 label='Punting Team Player')\n    start_pos = d.loc[d['time'] == d['time'].min()]\n    inj_star_pos = ax.scatter(start_pos['x'], start_pos['y'], color='red',\n                               zorder=5, label='Injured Player Starting Position')\n    end_pos = d.loc[d['time'] == d['time'].max()]\n    ax.scatter(end_pos['x'], end_pos['y'], color='black',\n                zorder=5, label='Injured Player Ending Position')\n    if injury_time:\n        inj_pos = d.loc[d['time'] == injury_time]\n    \n    # Plot the primary partner path if it exists\n    pp_player_role = None\n    if len(primarypartner) != 0:\n        pp_player_role = play.loc[play['primary_partner_player']]['role'].values[0]\n        play.loc[play['primary_partner_player']].plot('x', 'y', kind='scatter',\n                                                      xlim = (0, 120), ylim=(0,53.3),\n                                                      ax=ax, color='yellow', alpha=0.3, zorder=3,\n                                                      label='Primary Partner Path - Role {}'.format(pp_player_role))\n        ax.scatter(inj_pos['x'],\n                    inj_pos['y'],\n                    color='red', \n                    zorder=5,\n                    s=50,\n                    marker='+',\n                    label='Aproximate Location of Injury')\n    # If injured player or partner are not the punt returner plot the PR path\n    if (injured_player_role != 'PR') and (pp_player_role != 'PR'):\n        punt_returner = play.loc[play['role'] == 'PR']\n        punt_returner.plot('x', 'y', kind='scatter', ax=ax,  zorder=3, color='white', alpha=0.3,\n                           label='Punt Returner Path')\n\n    ax.axis('off')\n    ax.get_legend().remove()\n\n# Final 3 plots are nothing\nax38 = axes_list.pop(0)\nax38.axis('off')\nax39 = axes_list.pop(0)\nax39.axis('off')\nax40 = axes_list.pop(0)\nax40.axis('off')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"e781ca74e7b97d152496bcbd270dff6010e3d8ec"},"cell_type":"markdown","source":"Preprocessing for this section was completed in this kernel : https://www.kaggle.com/robikscube/nfl-punt-data-preprocessing-ngs-injury-plays/output\n\nWe identified some trends in the type of plays resulting in a player suffering a concussion:\n- **Direct Hit on Punt Returner** - This was the most common type of play, where the punt returner was tackled soon after catching the ball.\n- **Player hit while in pursuit of punt returner** - This was also a common trend. These types of plays typically had the punt returner avoiding the first round of tacklers (gunners) and making their way upfield. We noticed that a common trend was players from the punt return team turning their momentum 180 degrees to follow the punt returner. This then opened the possibility for these players to engage in high velocity collisions with players still running upfield.\n- **Injury Near Line of Scrimmage** - These plays were less common (3 in total) - all occurred near the line of scrimmage- usually on the punting teams side. They were mostly the result to linemen contacting other linemen,\n- **Player injured by a non-contact / fall** - A few injuries appeared to be the result of either a missed tackle or fall where the player suffered a concussion due to hitting their head on the ground. These appear to be less likely to avoid since they were mostly non-contact.\n- **Friendly Fire by players attempting to tackle the punt returner from opposing angles.** - This was less common, but observed in 2 cases where the punting gunners collided with each other near where the punt returner caught the ball.\n- **Other: Trick play, unique, or unclear from video footage** - There were other plays, like the Seahawks trick play, and other injuries where the cause was either not clear on film, or what seemed to be a fluke injury.\n\nNext we will show some plots with examples of each type of play. \n\n*We only plot the path for one of the plays, but the rest are available in the code in commented blocks. You can view them by uncommenting for a specific play.*"},{"metadata":{"_uuid":"d3e88f67d2fbcd0161db847672ee33f67919df03"},"cell_type":"markdown","source":"## Closer Look: Play where concussion a result of direct hit on punt returner (13 Plays)\nThe most common type of play we see from the 37 concussion plays are where the punt returner is directly hit. Either the tackling or punt returner are injured. On the example below the PLW on the punting team suffered a concussion after running up-field and contacting the returner."},{"metadata":{"trusted":true,"_uuid":"c37c3bce82adc5cb5184bb3825c539b324b93478","_kg_hide-input":true},"cell_type":"code","source":"plot_injury_play(2016, 5,   3129, figsize=(10, 5), display_url=True)\nplt.show()\n# plot_injury_play(2016, 29,  538, figsize=(10, 5))\n# plt.show()\n# plot_injury_play(2016, 189, 3509, figsize=(10, 5))\n# plt.show()\n# plot_injury_play(2016, 234, 3278, figsize=(10, 5))\n# plt.show()\n# plot_injury_play(2016, 266, 2902, figsize=(10, 5))\n# plt.show()\n# plot_injury_play(2016, 280, 3746, figsize=(10, 5))\n# plt.show()\n# plot_injury_play(2017, 357, 3630, figsize=(10, 5))\n# plt.show()\n# plot_injury_play(2017, 384, 183, figsize=(10, 5))\n# plt.show()\n# plot_injury_play(2017, 397, 1526, figsize=(10, 5))\n# plt.show()\n# plot_injury_play(2017, 399, 3312, figsize=(10, 5))\n# plt.show()\n# plot_injury_play(2017, 506, 1988, figsize=(10, 5))\n# plt.show()\n# plot_injury_play(2017, 585, 2208, figsize=(10, 5))\n# plt.show()\n# plot_injury_play(2017, 601, 602, figsize=(10, 5))\n# plt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"0d9f9fb8e782e2378e2ab24a9875a1a0063b4647"},"cell_type":"markdown","source":"## Closer Look: Play where concussion a result of hit while in pursuit of returner (9 Plays)\nSecond most commonly we see plays where one player is in pursuit of the punt returner and they are contacted by another player, either from their own team or another.\nWe see a trend with these types of plays where the punt returner has passed the first line of the coverage team (gunners) therefore causing players to reverse direction that they are running to pursue. This appears to be when many of these types of injuries occur.\n\nIn the example below you can see that the PDL2 position player returned up-field to protect the punt returner who was running towards the sideline. The punting team's long snapper (PLS) was then brutally blocked by the PDL2 player while he was in pursuit of the returner. Even though the long snapper took the brunt of the hit, the PDL2 player was the one who incurred a concussion."},{"metadata":{"trusted":true,"_uuid":"83255a6af2f337899988a6957d118b5cd7a2e5ff","_kg_hide-input":true},"cell_type":"code","source":"# plot_injury_play(2016, 21, 2587, figsize=(10, 5))\n# plt.show()\n# plot_injury_play(2016, 289, 2341, figsize=(10, 5))\n# plt.show()\n# plot_injury_play(2017, 364, 2764, figsize=(10, 5))\n# plt.show()\n# plot_injury_play(2017, 392, 1088, figsize=(10, 5))\n# plt.show()\n# plot_injury_play(2017, 448, 2792, figsize=(10, 5))\n# plt.show()\nplot_injury_play(2017, 553, 1683, figsize=(10, 5), display_url=True)\nplt.show()\n# plot_injury_play(2017, 585, 733, figsize=(10, 5))\n# plt.show()\n# plot_injury_play(2016, 54, 1045, figsize=(10, 5))\n# plt.show()\n# plot_injury_play(2016, 231, 1976, figsize=(10, 5))\n# plt.show()\n# plot_injury_play(2017, 618, 2792, figsize=(10, 5))\n# plt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"cf2dee5f45ee763541c2be16a1730e001b227f0b"},"cell_type":"markdown","source":"## Plays where injury occurs near the line of scrimmage (3 Plays)\nThese injuries occurred near or at the line of scrimmage. They did not involve players moving at high velocity. In the example below the coverage team's PLW (left wing) was pushed backwards and hit his head on the ground after being contacted by the returning team's PDR2 role moments after the snap. This can be seen in the video."},{"metadata":{"trusted":true,"_uuid":"ba4fe4a5ffb3518498fbf082e609e3904bcfc741","_kg_hide-input":true},"cell_type":"code","source":"ax = plot_injury_play(2017, 607, 978, figsize=(10, 5), display_url=True)\nplt.show()\n# plot_injury_play(2016, 60, 905, figsize=(10, 5))\n# plt.show()\n# plot_injury_play(2016, 280, 2918, figsize=(10, 5))\n# plt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"15ba2e21bb76c01bde0e9010f2daf08239fdcb21"},"cell_type":"markdown","source":"## Injury due to player falling and hitting head on ground (2 Plays)\n- Players hit head on ground when attempting to tackle or being blocked"},{"metadata":{"trusted":true,"_uuid":"6f7854a69c537adb21e8c38997159d3395050d37","_kg_hide-input":true},"cell_type":"code","source":"plot_injury_play(2016, 218, 3468, figsize=(10, 5), display_url=True)\nplt.show()\n# plot_injury_play(2017, 414, 1262, figsize=(10, 5))\n# plt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"92f426b5ebad473cd6d8f1f715cd088f8dcef34e"},"cell_type":"markdown","source":"## Injury plays as a result of friendly fire where gunners hit each other. (2 plays)\nWe see this occur on two plays where gunners hit each other when attempting to tackle the punt returner."},{"metadata":{"trusted":true,"_uuid":"9bb7229cc68c1206e8e665e08ad6f28c014edae8","_kg_hide-input":true},"cell_type":"code","source":"plot_injury_play(2016, 296, 2667, figsize=(10, 5), display_url=True)\nplt.show()\n# plot_injury_play(2017, 473, 2072, figsize=(10, 5))\n# plt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"9d17bbc5d9de6a45693621a59cce436f8d19b471"},"cell_type":"markdown","source":"## Other types of plays (Fake punt, unclear, etc.)\nThe rest of the plays were either unique situations where the concussion occurred or were not clear to identify given the video footage."},{"metadata":{"trusted":true,"_uuid":"b3a57e05c1a39b57d51a5c09f4ae17c5e0d544f1","_kg_hide-input":true},"cell_type":"code","source":"plot_injury_play(2016, 274, 3609, figsize=(10, 5), display_url=True)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8cfc00975a147d2656da9b058274c7d82c14dfe9"},"cell_type":"markdown","source":"# Velocity and Direction of Players During Injury Plays\n\nBy plotting out the direction and velocity that the two players involved in an injury were moving we can visually see some trends. The players commonly are changing direction, in order to follow the punt returner when they contact the punt returner.\nSome thing we've observed from reviewing all the play's velocity and direction, is that players often suddenly change their direction prior to impact.\nIn the compass plots:\n- Velocity is measured in `meters per second`\n- Orange Arrow indicates the approximate moment when the injury occurred."},{"metadata":{"_uuid":"c68b041de49b50d481d9cb8c88f3f8f2bfe671df"},"cell_type":"markdown","source":"In this first example we can see the punt returner went towards the left sideline upon receiving the punt. The injured player (PDR1) had a pretty straight path towards the punt returner after his initial blocker. The primary partner however, was moving up-field for the majority of the play and then made a sharp turn to block prior to impact. This same trend was observed a number of times when reviewing each injury play."},{"metadata":{"trusted":true,"_uuid":"732df66b83f1c5fa5f0b3a7f355f3146d5a6d7f7","_kg_hide-input":true},"cell_type":"code","source":"plot_injury_play(2017, 448, 2792, figsize=(10, 5), plot_velocity=True,\n                 figsize_velocity=(15, 5), display_url=True)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"c17bd53d2f3f8fc185aba1e52ce9ea082747c1ca"},"cell_type":"markdown","source":"In this example we can see the punt returner retreated slighty and then ran to his right. The injured player (PRG) and the primary partner (PLT) both were moving directly up-field at high velocity. The injured player changed direction just prior to impact."},{"metadata":{"trusted":true,"_uuid":"73da3644df96b434621ace2d09302c91e0b53d61","_kg_hide-input":true},"cell_type":"code","source":"plot_injury_play(2017, 473, 2072, figsize=(10, 5), plot_velocity=True,\n                 figsize_velocity=(15, 5), display_url=True)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"5162943d2a148949f3a0bf42fe548ca95022981f"},"cell_type":"markdown","source":"# Section III - Analysis of Fair Catch vs. Returned Punt\nIn order to analyze how and when punt returners decide to fair catch, we've taken every play and normalized the data such that we have each players position in relation to the punt returner. This allows us to visualize the general distance from the punt returner when he decides to fair catch or try and return."},{"metadata":{"trusted":true,"_uuid":"a630bdd00781ec5c2533a4f36b95ae7c44d0f252","_kg_hide-input":true},"cell_type":"code","source":"fc = pd.read_parquet('../input/robmullanflpreprocessed/position_at_faircatch.parquet')\nfc['y_rel_plus26'] = fc['y_rel'] + 26\nprec = pd.read_parquet('../input/robmullanflpreprocessed/position_at_punt_recieved.parquet')\nprec['y_rel_plus26'] = prec['y_rel'] + 26","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"6dc25ded899b8ca17f266cfddbeb10c2b37b00e7"},"cell_type":"markdown","source":"## Punting Team Relative Position to Punt Returner (Fair Catch vs. Return)\n\nIn these two plots we can see that fair catches occur more commonly when the opposing team is closer (and in some cases behind) the punt returner. This is expected. The distance of injured players and primary partners in injuries appear to be distributed throughout the relative locations. One main observation of this plot is that the location of injured players on punt returns (relative to the punt returner at time of catch) varies quite a bit. Some are almost on top of the punt returner and others are 30+ yards down field. The number of injuries on fair catches is much less, but we do see that these injuries occur relatively far away from the returner."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"742a4413d978c0620b58bd3cd0c99f2ef7e2be42"},"cell_type":"code","source":"fig, ax = create_football_field(endzones=False, linenumbers=False)\nprec['y_rel_plus26'] = prec['y_rel'] + 26.65\nprec['x_rel_left_to_right_plus40'] = prec['x_rel_left_to_right'] + 40\nprec.plot(x='x_rel_left_to_right_plus40',\n          y='y_rel_plus26',\n          kind='scatter',\n          ax=ax, alpha=0.1,\n          title='All Players `Relative to Punt Returned',\n          color='yellow')\n# Add injured and primary partners\nprec.loc[prec['injured_player']] \\\n    .plot(x='x_rel_left_to_right_plus40', y='y_rel_plus26', kind='scatter', ax=ax,\n          alpha=1, title='Gunners Relative to Punt Returner (Injury Colored)',\n          color='red', zorder=3, label='Injured Player')\nprec.loc[prec['primary_partner_player']] \\\n    .plot(x='x_rel_left_to_right_plus40', y='y_rel_plus26', kind='scatter', ax=ax,\n          alpha=1, title='Gunners Relative to Punt Returner (Injury Colored)',\n          color='orange', zorder=3, label='Primary Partner Player')\ncaught_point = plt.plot(40, 26.65, '>', color='blue', markersize=10, zorder=5,\n                        label='Punt Recieved (Relative Location)')\nplt.legend(loc='center left', bbox_to_anchor=(1, 0.5))\nplt.show()\n\nfig, ax = create_football_field(endzones=False, linenumbers=False)\nfc['x_rel_left_to_right_plus40'] = fc['x_rel_left_to_right'] + 40\nfc.plot(x='x_rel_left_to_right_plus40',\n        y='y_rel_plus26',\n        kind='scatter', ax=ax, alpha=0.1,\n        title='All Players Relative to Faircatch',\n        color='orange',\n        zorder=2)\nfc.loc[fc['injured_player']] \\\n    .plot(x='x_rel_left_to_right_plus40', y='y_rel_plus26', kind='scatter', ax=ax,\n          alpha=1, title='Gunners Relative to Punt Returner (Injury Colored)',\n          color='red', zorder=3, label='Injured Player')\n\nfc.loc[fc['primary_partner_player']] \\\n    .plot(x='x_rel_left_to_right_plus40', y='y_rel_plus26', kind='scatter', ax=ax,\n          alpha=1, title='Gunners Relative to Punt Returner (Injury Colored)',\n          color='white', zorder=3, label='Primary Partner Player')\ncaught_point = plt.plot(40, 26.65, '>', color='blue', markersize=10,\n                        zorder=5, label='Fair Catch (Relative Location)')\n\nplt.legend(loc='center left', bbox_to_anchor=(1, 0.5))\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"3ad143e43e64fd57718e1e2e9f316b78b475ec8a"},"cell_type":"markdown","source":"We can look at the distribution of the punting team in relation to the punt returner. On a fair catch there is a noticeable spike in the number of player 2-5 yards from the returner- presumably these are gunners who have reached the returner and are waiting near by.  On returned punts however the distribution of players is much more normally distributed. On returns the gunners have not reached the returner or have overshot the returner."},{"metadata":{"trusted":true,"_uuid":"75a82eecd08c4d252a626e1342a26e24f6096ef9","_kg_hide-input":true},"cell_type":"code","source":"fc['distance_from_rec'] = np.sqrt(np.square(fc['x_rel']) + np.square(fc['y_rel']))\nprec['distance_from_rec'] = np.sqrt(np.square(prec['x_rel']) + np.square(prec['y_rel']))\n\nplt.style.use('ggplot')\n\nfc.loc[fc['punting_returning_team'] == 'Punting_Team']['distance_from_rec'] \\\n    .plot(kind='hist', figsize=(15,5),\n          bins=50,\n          title='Punting Team Distance from Punt Returner (Fair Catch)',\n          color=color1)\nplt.show()\n\nprec.loc[prec['punting_returning_team'] == 'Punting_Team']['distance_from_rec'] \\\n    .plot(kind='hist',\n          figsize=(15,5),\n          bins=50,\n          title='Punting Team Distance from Punt Returner (Punt Returned)',\n          color=color2)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"da68a071b83e648f880e4d17e98d94e8780bfbeb"},"cell_type":"markdown","source":"## Distribution of Closest Player (Gunner) Distance from Punt Returner\nNext we look at the closest player to the punt returner from the punting team at the time of punt or fair catch. Surprisingly this is overwhelmingly the left gunner - 99.28% on fair catches and 98.6% of the time on returned punts.\nWe also see the median distance of the closest player on a fair catch is much less than that of a return (3.19 yards for a fair catch vs 10.03 on a return). From this we can conclude that fair catches are usually only made when the opposing player is very close distance to the punt returner. This makes sense as returners currently have no incentive to try and return if they think they can at least make a gain. Gunners involved in concussion plays are close by the returner on return plays. No concussions involved gunners on fair catch plays."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"4d73a8c6573b3a2f702dfc5d57220391d4225250"},"cell_type":"code","source":"fig, ax = create_football_field(endzones=False, linenumbers=False)\nprec['x_rel_left_to_right_plus40'] = prec['x_rel_left_to_right'] + 40\nprec['y_rel_plus26'] = prec['y_rel'] + 26.65\n\nprec.loc[prec['role'].isin(['GL', 'GLi', 'GR', 'GRi'])] \\\n    .plot(x='x_rel_left_to_right_plus40', y='y_rel_plus26', kind='scatter', ax=ax,\n          alpha=0.2, title='Players Relative to Punt Returned Punting Team',\n          color='yellow', zorder=3, label='Closest Player')\nprec.loc[prec['role'].isin(['GL', 'GLi', 'GR', 'GRi']) & prec['injured_player']] \\\n    .plot(x='x_rel_left_to_right_plus40', y='y_rel_plus26', kind='scatter', ax=ax,\n          alpha=1, title='Players Relative to Punt Returned Punting Team',\n          color='red', zorder=3, label='Injured Player')\nprec.loc[prec['role'].isin(['GL', 'GLi', 'GR', 'GRi']) & prec['primary_partner_player']] \\\n    .plot(x='x_rel_left_to_right_plus40', y='y_rel_plus26', kind='scatter', ax=ax,\n          alpha=1, title='Gunners Relative to Punt Returner (Injury Colored)',\n          color='orange', zorder=3, label='Primary Partner Player')\ncaught_point = plt.plot(40, 26.65, '>', color='blue', markersize=10,\n                        zorder=5, label='Return Catch Point (Relative Location)')\nplt.legend(loc='center left', bbox_to_anchor=(1, 0.5))\n\nplt.show()\n\nfig, ax = create_football_field(endzones=False, linenumbers=False)\n\nfc['x_rel_left_to_right_plus40'] = fc['x_rel_left_to_right'] + 40\nfc['y_rel_plus26'] = fc['y_rel'] + 26.65\n\nfc['x_rel_left_to_right_plus40'] = fc['x_rel_left_to_right'] + 40\nfc.loc[fc['role'].isin(['GL', 'GLi', 'GR', 'GRi'])] \\\n    .plot(x='x_rel_left_to_right_plus40', y='y_rel_plus26', kind='scatter', ax=ax,\n          alpha=0.2, title='Gunners Relative to Fair Catch Punting Team',\n          color='orange', zorder=3, label='Closest Player to Returner')\ncaught_point = plt.plot(40, 26.65, '>', color='blue', markersize=10,\n                        zorder=5, label='Fair Catch (Relative Location)')\nplt.legend(loc='center left', bbox_to_anchor=(1, 0.5))\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"e0e7623cfdb0d6b8ad59a41dbb66091a54f3b579"},"cell_type":"code","source":"fc_closest_player = fc.loc[fc['punting_returning_team'] == 'Punting_Team'] \\\n    .groupby(['season_year', 'gamekey', 'playid'])[['role', 'distance_from_rec']] \\\n    .min().reset_index()\nprec_closest_player = prec.loc[prec['punting_returning_team'] == 'Punting_Team'] \\\n    .groupby(['season_year', 'gamekey', 'playid'])[['role', 'distance_from_rec']].min().reset_index()\n\nfc_closest_player['distance_from_rec'].plot(kind='hist',\n                                            figsize=(15, 5),\n                                            bins=50,\n                                            title='Closest Player Distance from Punt Returner (Fair Catch)',\n                                            color=color1)\nplt.show()\nprec_closest_player['distance_from_rec'].plot(kind='hist',\n                                              figsize=(15, 5),\n                                              bins=50,\n                                              title='Closest Player Distance from Punt Returner (Returned)',\n                                              color=color2)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"b96299fdfb0d93477d4d716e4cb86bb543f0b6a9","_kg_hide-input":true},"cell_type":"code","source":"print('Median closest player to punter returner on a return is {:.2f} yards'.format(prec_closest_player['distance_from_rec'].median()))\nprint('Median closest player to punter returner on a fair catch is {:.2f} yards'.format(fc_closest_player['distance_from_rec'].median()))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"5ca5a5155020977838776d25daccfade869ecc3f"},"cell_type":"markdown","source":"## Punting Team Non-Gunners Relation to Received Punt\nIn this plot we remove the gunners to focus on non-gunners relative location to the punt returner at the moment of catch or fair catch. The distinction between between fair catches and returned punts for non-gunner players is fairly similar. This leads us to conclude that the gunners are the main factor in a punt returners decision to fair catch the ball."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"c3a5af36d657f0890f4a23fc55e74790bc06e270"},"cell_type":"code","source":"fig, ax = create_football_field(endzones=False, linenumbers=False)\n\nnon_gunners_punting_team_roles = ['P', 'PC', 'PPR', 'PPRi', 'PPRo', 'PFB', 'PLG',\n                                  'PLS', 'PLT', 'PLW', 'PRW', 'PRG', 'PRT', 'PPL',\n                                  'PPLo', 'PPLi']\n\nfc['x_rel_left_to_right_plus40'] = fc['x_rel_left_to_right'] + 40\nfc['y_rel_plus26'] = fc['y_rel'] + 26.65\n\nfc['x_rel_left_to_right_plus40'] = fc['x_rel_left_to_right'] + 40\nfc.loc[fc['role'].isin(non_gunners_punting_team_roles)] \\\n    .plot(x='x_rel_left_to_right_plus40', y='y_rel_plus26', kind='scatter', ax=ax,\n          alpha=0.2,\n          title='Punting Team - Non-Gunners Relative to Fair Catch Punt Returner at Catch',\n          color='orange',\n          zorder=3)\ncaught_point = plt.plot(40, 26.65, '>', color='blue', markersize=10,\n                        zorder=5, label='Fair Catch (Relative Location)')\nplt.legend(handles=caught_point)\nplt.show()\n\nfig, ax = create_football_field(endzones=False, linenumbers=False)\nprec['x_rel_left_to_right_plus40'] = prec['x_rel_left_to_right'] + 40\nprec['y_rel_plus26'] = prec['y_rel'] + 26.65\n\nprec.loc[~prec['role'].isin(non_gunners_punting_team_roles)] \\\n    .plot(x='x_rel_left_to_right_plus40',\n          y='y_rel_plus26',\n          kind='scatter', ax=ax,\n          alpha=0.2,\n          title='Players Relative to Punt Returned Punting Team', color='yellow', zorder=3)\nprec.loc[prec['role'].isin(non_gunners_punting_team_roles) & prec['injured_player']] \\\n    .plot(x='x_rel_left_to_right_plus40',\n          y='y_rel_plus26',\n          kind='scatter',\n          ax=ax,\n          alpha=1,\n          title='Players Relative to Punt Returned Punting Team',\n          color='red',\n          label='injured gunners',\n          zorder=3)\nprec.loc[prec['role'].isin(non_gunners_punting_team_roles) & prec['primary_partner_player']] \\\n    .plot(x='x_rel_left_to_right_plus40',\n          y='y_rel_plus26',\n          kind='scatter',\n          ax=ax,\n          alpha=1,\n          title='Punting Team - Non-Gunners Relative to Punt Returner at Moment of Catch (Injury Colored)',\n          color='orange',\n          zorder=3)\ncaught_point = plt.plot(40, 26.65, '>',\n                        color='blue',\n                        markersize=10,\n                        zorder=5,\n                        label='Return Caught (Relative Location)')\nplt.legend(handles=caught_point)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"e9327306dcdbff78d1782eb573a2c0f202307e76"},"cell_type":"markdown","source":"## Punting Formation / Detailed Role Analysis\n\nNext we will look at individual player roles and their frequency in punting plays. We can see some positions (Punter, PRG, PLG, PRT, etc) appear in nearly every punting play. Other positions are commonly swapped out for each other. The positions that vary appear to mainly be on the returning side of the ball. For instance, the returning team can either setup for a return by double teaming the punting team's gunners with multiple jammers (VRi, VRo, VLi, VLo) - or they could decide to single cover the gunner and have more players around where the ball is snapped. Additionally we see many times where the returning team plays in a hybrid formation with two jammers on one side and single coverage on the other.\n\nPunting Team Roles       |  Returning Team Roles\n:-------------------------:|:-------------------------:\n<img src=\"https://storage.googleapis.com/kaggle-media/competitions/NFL%20player%20safety%20analytics/punt_coverage.png\" alt=\"drawing\" style=\"width:430px;height:225px;\"/> |  <img src=\"https://storage.googleapis.com/kaggle-media/competitions/NFL%20player%20safety%20analytics/punt_return.png\" alt=\"drawing\" style=\"width:400px;height:300px;\"/>"},{"metadata":{"trusted":true,"_uuid":"79c729c31b100284e5890213ebe787e25508f93c","_kg_hide-input":true},"cell_type":"code","source":"pprd['count'] = 1\n#unique key to join on\npprd['play_unique'] = pprd['season_year'].astype('str').add(\n    (pprd['gamekey']).astype('str')).add((pprd['playid']).astype('str'))\nvr['play_unique'] = vr['season_year'].astype('str').add(\n    (vr['gamekey']).astype('str')).add((vr['playid']).astype('str'))\n\nplay_roles = pprd.pivot_table(values='count', index='play_unique',\n                              columns='role', aggfunc='mean').fillna(0).reset_index()\nplay_roles.drop(columns=['play_unique']) \\\n    .sum().sort_values() \\\n    .plot(kind='barh', figsize=(15, 10), title='Number of Plays with Role', color='grey')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"832016c9cde5574c70c72b2cc88f65579d623f0b"},"cell_type":"markdown","source":"## Insights from Player Roles\n- The punting team tends to have the same roles on punting plays. The returning team however can decide depending on the position to setup for a return or try for a block.\n- Jammers have a huge influence on the play. A defensive team can double team both, one or none of the punting team's gunners. This appears to have large consequences on the result of the play."},{"metadata":{"trusted":true,"_uuid":"def2f5dcfc4a28cc2d1aaedd35b087763982dd46","_kg_hide-input":true},"cell_type":"code","source":"role_count_df = pd.DataFrame(play_roles.drop(columns=['play_unique'])\n                             .sum()\n                             .sort_values()) \\\n    .reset_index() \\\n    .rename(columns={0: 'Role Count', 'Role': 'role'})\nrole_count_df = pd.merge(role_count_df, role_info)\nrole_count_df.loc[role_count_df['punting_returning_team'] == 'Returning_Team'] \\\n    .set_index('role')['Role Count'] \\\n    .plot(kind='barh',\n          figsize=(15, 10),\n          title='Returning Team - Number of Plays with Role',\n          color='grey')\nplt.show()\nrole_count_df.loc[role_count_df['punting_returning_team'] == 'Punting_Team'] \\\n    .set_index('role')['Role Count'] \\\n    .plot(kind='barh',\n          figsize=(15, 10),\n          title='Punting Team - Number of Plays with Role',\n          color='grey')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"a412dcf9cb2a6707fa021f9a78183e337154a95b"},"cell_type":"markdown","source":"## Number of Jammers on Play (Double teaming gunners)\nOver half of the punts don't double team gunners- and yet the rate of concussions increases as the number of gunners increases from 2 to 4. We believe there are two reasons for this (1) as shown in the previous section, fair catches are commonly made when gunners are relatively close to the punt returner (~3 yards). When a team chooses to double team gunners it is more likely that the punt returner will choose to return. (2) Gunners, when double teamed, create an imbalance on the field between punting and returning players positions. This opens up the center of the field for punting team's linemen to gain higher velocity, and as a result make them more likely for high velocity impact.\n\nWe will explore this more in later sections."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"e7b5cecc25b660679d454a8fe2077e3ad756397f"},"cell_type":"code","source":"play_roles['Number_of_jammers'] = play_roles[['VL', 'VLi',\n                                              'VLo', 'VR', 'VRi', 'VRo']].sum(axis=1).astype('int')\nax = ((play_roles.groupby('Number_of_jammers').count())['play_unique'] / len(play_roles) * 100) \\\n    .plot(kind='bar',\n          figsize=(15, 5),\n          title='Percentage of Punt Plays by Jammers',\n          color='grey',\n          rot=0)\nax.set_ylabel('Percentage of All Punt Plays')\nax.set_xlabel('Number of Jammers')\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"a775de8399428b9b59a085910826ac2bcab61944"},"cell_type":"code","source":"vr_merged['injury_play'] = True\n\npi['play_unique'] = pi['season_year'].astype('str') \\\n    .add((pi['gamekey']).astype('str')) \\\n    .add((pi['playid']).astype('str'))\n\nvr_merged['play_unique'] = vr_merged['season_year'].astype('str') \\\n    .add((vr_merged['gamekey']).astype('str')) \\\n    .add((vr_merged['playid']).astype('str'))\n\nplay_roles2 = pd.merge(pd.merge(play_roles, pi, on='play_unique', how='left', suffixes=('', 'y')),\n                       vr_merged, on='play_unique', how='left', suffixes=('', 'y'))\n\nax = (play_roles2\n      .groupby('Number_of_jammers')['injury_play'].sum() * 100 / play_roles.groupby('Number_of_jammers')['play_unique']\n      .count()) \\\n    .drop([0, 1, 5]) \\\n    .plot(kind='bar',\n          figsize=(15, 5),\n          color='grey',\n          title='Rate of Concussions by # of Jammers',\n          rot=0)\nax.set_ylabel('Rate (%) of Injuries')\nax.set_xlabel('Number of Jammers')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"ee6562fc6985ad6b1f632babe07c22d2f41f9b5c"},"cell_type":"markdown","source":"## Average Values of Distance to Returner depending on single, double, or hybrid coverage\nIn the process of analyzing players position to punt returners we noticed that there is a correlation between the number of jammers on the returning team with the distance of the punt. As we see in the plots below, the number of jammers appears to correlate with the punting team being further from the returner when he catches the ball."},{"metadata":{"trusted":true,"_uuid":"e7bdc457c68a38822ffe1781bfe203672167b600","_kg_hide-input":true},"cell_type":"code","source":"# Fair catch merge\nfc['play_unique'] = fc['season_year'].astype('str') \\\n    .add((fc['gamekey']).astype('str')) \\\n    .add((fc['playid']).astype('str'))\nfc['x_rel_left_to_right_plus40'] = fc['x_rel_left_to_right'] + 40\nfc['y_rel_plus26'] = fc['y_rel'] + 26.65\nngs_fair_catch = pd.merge(\n    fc, play_roles2, on='play_unique', suffixes=('', 'y'))\n\n# Reception data merge\nprec['play_unique'] = prec['season_year'].astype('str') \\\n    .add((prec['gamekey']).astype('str')) \\\n    .add((prec['playid']).astype('str'))\nprec['x_rel_left_to_right_plus40'] = prec['x_rel_left_to_right'] + 40\nprec['y_rel_plus26'] = prec['y_rel'] + 26.65\nngs_return = pd.merge(prec, play_roles2, on='play_unique', suffixes=('', 'y'))\nfig, (ax1, ax2) = plt.subplots(1, 2, sharey=True, figsize=(20, 5))\nngs_return_small = ngs_return[['season_year', 'gamekey', 'playid',\n                               'punting_returning_team', 'role', 'distance_from_rec',\n                               'Number_of_jammers']]\n\n# Return data filtered\nngs_return_small = ngs_return_small.loc[ngs_return_small['Number_of_jammers'].isin([\n                                                                                   2, 3, 4])]\n\nngs_return_small.loc[ngs_return_small['punting_returning_team'] == 'Punting_Team'] \\\n    .groupby(['season_year', 'gamekey', 'playid', 'Number_of_jammers'])[['role', 'distance_from_rec']].min() \\\n    .reset_index() \\\n    .groupby('Number_of_jammers') \\\n    .mean()['distance_from_rec'].plot(kind='bar',\n                                      color='grey',\n                                      title='Distance from Returner (Return)',\n                                      rot=0,\n                                      ax=ax1)\n\nngs_fair_catch_small = ngs_fair_catch[['season_year', 'gamekey', 'playid',\n                                       'punting_returning_team', 'role', 'distance_from_rec',\n                                       'Number_of_jammers']]\n\nngs_fair_catch_small = ngs_fair_catch_small.loc[ngs_fair_catch_small['Number_of_jammers'].isin([\n                                                                                               2, 3, 4])]\nngs_fair_catch_small.loc[ngs_fair_catch_small['punting_returning_team'] == 'Punting_Team'] \\\n    .groupby(['season_year', 'gamekey', 'playid', 'Number_of_jammers'])[['role', 'distance_from_rec']].min() \\\n    .reset_index() \\\n    .groupby('Number_of_jammers') \\\n    .mean()['distance_from_rec'].plot(kind='bar',\n                                      color='grey',\n                                      title='Distance from Returner (Fair Catch)',\n                                      rot=0,\n                                      ax=ax2)\nax1.set_xlabel('Number of Jammers')\nax2.set_xlabel('Number of Jammers')\nax1.set_ylabel('Average Distance of Opponents (yards)')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"170d51d998b04e9cb2128ac05241e58385bd22cd"},"cell_type":"markdown","source":"# Section IV - Visualizing Players Path Relative to Punt\n\nWe wanted to look and see if the paths taken by players who were involved in a concussion were somehow unique. To visualize this we plot all the paths by player type in relation to their starting position of the play. These plots are very helpful in gaining high level insights to the paths of players involved in concussions compared to the average path."},{"metadata":{"trusted":true,"_uuid":"aa5bb61709e81eee1d74e16968657b77720b9d40","_kg_hide-input":true},"cell_type":"code","source":"def create_generalized_role_paths(generalized_role, legend=True):\n    fig, ax = create_football_field(\n        linenumbers=False, endzones=False, fifty_is_los=True)\n\n    role_df_all = pd.read_parquet(\n        '../input/robmullanflpreprocessed/{}.parquet'.format(generalized_role))\n    role_df_all['x-rel-snap-plus50'] = role_df_all['x-rel-snap'] + 60\n    role_df_all['x-rel-snap-plus50'] = pd.to_numeric(\n        role_df_all['x-rel-snap-plus50'])\n    role_df_all.plot(x='x-rel-snap-plus50', y='y', kind='scatter',\n                     ax=ax, alpha=0.05, color='white', s=1,\n                     label='All {}s Routes'.format(generalized_role),\n                     title='{} Routes on Punt Plays'.format(generalized_role.replace('_', ' ')))\n\n    role_df_all[role_df_all['injured_player']].plot(x='x-rel-snap-plus50', y='y',\n                                                    kind='scatter', s=2,\n                                                    color='red', ax=ax, label='Injured Players Routes')\n    if len(role_df_all[role_df_all['primary_partner_player']]) > 0:\n        role_df_all[role_df_all['primary_partner_player']] \\\n            .plot(x='x-rel-snap-plus50', y='y',\n                  kind='scatter', s=2,\n                  color='orange', ax=ax, label='Primary Partner Routes')\n    if legend is True:\n        plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))\n    else:\n        ax.get_legend().remove()\n    return fig, ax","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"c034379f7ad3d0d7ed13d95a1304e6a85aedbdc4"},"cell_type":"markdown","source":"First we will look at the punt returners path. From this plot we see a lot of the paths appear to be sideline to sideline movements. In a few instances the punts are returned for gains of 10+ yards, but even in these we see a lot of horizontal movement. We also see from the while dots, that longer returns tend to have the punt returner going up a sideline, rarely do the punt returns go up the middle for large gains. Overall we can see the movement of punt returners are relatively condensed to +/- 10 yards from their starting location at the time of snap."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"0d30d824733d40aefc6fa943a9465cc1ec6a0b28"},"cell_type":"code","source":"fig, ax = create_generalized_role_paths('Punt_Returner', legend=False)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"64122aa43fcee5b2ea3046ab656b3d1359d105eb"},"cell_type":"markdown","source":"We know that punting linemen are commonly involved in the concussion plays so we will plot their paths relative to snap position next. One thing that is immediately clear is that punting linemen, when involved in concussion plays look to travel a long distance. This supports the theory that punting linemen, when unblocked, reach higher velocity and are more likely to be injured or cause injury. We also notice that the injured punting linemen rarely travel inside the hash marks after the first 20 yards- because at this point they are in pursuit of the punt returner who has moved to one of the sidelines (identified in the last plot)."},{"metadata":{"trusted":true,"_uuid":"4fb3d7c5e0bdd9caa01d3e788ef4be23628ae118","_kg_hide-input":true},"cell_type":"code","source":"fig, ax = create_generalized_role_paths('Punting_Lineman', legend=False)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"0ec639a943638229772e42126696d7e50e7048d9"},"cell_type":"markdown","source":"We will not discuss the remainder of the positions in detail, but provide the plots as we believe they each show us something interesting about how that player responds on a punting play."},{"metadata":{"trusted":true,"_uuid":"47c83760e08280f4375d914fbe5922dfff027b83","scrolled":false,"_kg_hide-input":true},"cell_type":"code","source":"remaining_positions = ['Gunner',\n                       'Punter',\n                       'Punter_Protector', 'Defensive_Lineman',\n                       'PuntFullBack',\n                       #'Punting_Lineman',\n                       'Defensive_Backer',\n                       'Punting_Longsnapper', 'Punting_Wing',\n                       #'Punt_Returner',\n                       'Jammer']\n\nfor role in remaining_positions:\n    try:\n        # print(role)\n        fig, ax = create_generalized_role_paths(role)\n        plt.show()\n    except Exception as e:\n        print(e)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"058a09989ca641198c86c1afc186cee06c442b31"},"cell_type":"markdown","source":"## Gunner Routes - Double Team vs Single Coverage\nBy visualizing the comparison of gunners routes when single vs double covered, you can clearly see the paths of the players take a different form. In single coverage the gunners paths are much more direct to the punt returner. In double coverage the curve of their paths are much wider on average. This wider routes point to the potential of more collisions resulting in injury."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"e0b53562c474dc0ebb96dbcdbdb8c0c18f342cf1"},"cell_type":"code","source":"role_df_all = pd.read_parquet('../input/robmullanflpreprocessed/{}.parquet'.format('Gunner'))\nrole_df_all['play_unique'] = role_df_all['season_year'].astype('str') \\\n    .add((role_df_all['gamekey']).astype('str')) \\\n    .add((role_df_all['playid']).astype('str'))\n\ngunner_routs_with_coverage = pd.merge(role_df_all, play_roles[['play_unique','Number_of_jammers']], how='left')\n\ngunner_single_covered = \\\n    gunner_routs_with_coverage.loc[gunner_routs_with_coverage['Number_of_jammers'] == 2].copy()\ngunner_double_covered = \\\n    gunner_routs_with_coverage.loc[gunner_routs_with_coverage['Number_of_jammers'] == 4].copy()\n\n# Single Covered\nfig, ax1 = create_football_field(linenumbers=False, endzones=False, fifty_is_los=True)\nlegend = False\ngunner_single_covered['x-rel-snap-plus50'] = gunner_single_covered['x-rel-snap'] + 60\ngunner_single_covered['x-rel-snap-plus50'] = pd.to_numeric(gunner_single_covered['x-rel-snap-plus50'])\ngunner_single_covered.plot(x='x-rel-snap-plus50',y='y', kind='scatter',\n                 ax=ax1, alpha=0.05, color='white', s=1,\n                 label='Gunner Routes',\n                 title='Gunner Routes Single Coverage')\n\nax1.get_legend().remove()\n\n# Double Covered\nfig, ax2 = create_football_field(linenumbers=False, endzones=False, fifty_is_los=True)\nlegend = False\ngunner_double_covered['x-rel-snap-plus50'] = gunner_double_covered['x-rel-snap'] + 60\ngunner_double_covered['x-rel-snap-plus50'] = pd.to_numeric(gunner_double_covered['x-rel-snap-plus50'])\ngunner_double_covered.plot(x='x-rel-snap-plus50',y='y', kind='scatter',\n                 ax=ax2, alpha=0.05, color='white', s=1,\n                 label='Gunner Routes',\n                 title='Gunner Routes Double Coverage')\n\nax2.get_legend().remove()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"40be8d97baf3c7228d9a2b23aac4ea323450f114"},"cell_type":"markdown","source":"## Linemen Routes Single/Double Coverage of Gunners\nSimilar to the routes of gunners, the routes of linemen changes when the returning team decides to single-cover, double-cover both gunners, or hybrid of single on one side and double on the other. We can see that both teams' linemen take a tighter path closer to the center of the field when the gunners are double or hybrid covered. Paired with the gunner's routes on double coverage we can see where potential for injury risk may be increased. "},{"metadata":{"trusted":true,"_uuid":"c5950ef542d466319513818870430f885937d39e","_kg_hide-input":true},"cell_type":"code","source":"role_df_all_dline = pd.read_parquet(\n    '../input/robmullanflpreprocessed/Defensive_Lineman.parquet')\nrole_df_all_puntline = pd.read_parquet(\n    '../input/robmullanflpreprocessed/Punting_Lineman.parquet')\nrole_df_all = pd.concat([role_df_all_dline, role_df_all_puntline])\nrole_df_all['play_unique'] = role_df_all['season_year'].astype('str') \\\n    .add((role_df_all['gamekey']).astype('str')) \\\n    .add((role_df_all['playid']).astype('str'))\n\ngunner_routs_with_coverage = pd.merge(\n    role_df_all, play_roles[['play_unique', 'Number_of_jammers']], how='left')\n\ngunner_single_covered = \\\n    gunner_routs_with_coverage.loc[gunner_routs_with_coverage['Number_of_jammers'] == 2].copy()\ngunner_hybrid_covered = \\\n    gunner_routs_with_coverage.loc[gunner_routs_with_coverage['Number_of_jammers'] == 3].copy()\ngunner_double_covered = \\\n    gunner_routs_with_coverage.loc[gunner_routs_with_coverage['Number_of_jammers'] == 4].copy()\n\n# Single Covered\nfig, ax1 = create_football_field(\n    linenumbers=False, endzones=False, fifty_is_los=True)\nlegend = False\ngunner_single_covered['x-rel-snap-plus50'] = gunner_single_covered['x-rel-snap'] + 60\ngunner_single_covered['x-rel-snap-plus50'] = pd.to_numeric(\n    gunner_single_covered['x-rel-snap-plus50'])\ngunner_single_covered.plot(x='x-rel-snap-plus50', y='y', kind='scatter',\n                           ax=ax1, alpha=0.05, color='white', s=1,\n                           label='Punting Linemen Routes',\n                           title='Punting Linemen Routes Single Coverage')\nif legend is True:\n    plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))\nelse:\n    ax1.get_legend().remove()\nplt.show()\n\n# Hybrid\nfig, ax3 = create_football_field(\n    linenumbers=False, endzones=False, fifty_is_los=True)\nlegend = False\ngunner_hybrid_covered['x-rel-snap-plus50'] = gunner_hybrid_covered['x-rel-snap'] + 60\ngunner_hybrid_covered['x-rel-snap-plus50'] = pd.to_numeric(\n    gunner_hybrid_covered['x-rel-snap-plus50'])\ngunner_hybrid_covered.plot(x='x-rel-snap-plus50', y='y', kind='scatter',\n                           ax=ax3, alpha=0.05, color='white', s=1,\n                           label='Punting Linemen Routes',\n                           title='Punting Linemen Routes Hybrid Coverage')\nif legend is True:\n    plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))\nelse:\n    ax3.get_legend().remove()\nplt.show()\n\n\n# Double Covered\nfig, ax2 = create_football_field(\n    linenumbers=False, endzones=False, fifty_is_los=True)\nlegend = False\ngunner_double_covered['x-rel-snap-plus50'] = gunner_double_covered['x-rel-snap'] + 60\ngunner_double_covered['x-rel-snap-plus50'] = pd.to_numeric(\n    gunner_double_covered['x-rel-snap-plus50'])\ngunner_double_covered.plot(x='x-rel-snap-plus50', y='y', kind='scatter',\n                           ax=ax2, alpha=0.05, color='white', s=1,\n                           label='Punting Linemen Routes',\n                           title='Punting Linemen Routes Double Coverage')\nif legend is True:\n    plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))\nelse:\n    ax2.get_legend().remove()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"a77a0b1d1f523299f1effd4e6e377526001d1f81"},"cell_type":"markdown","source":"# Section V - Modeling Risk of Punt Plays based on Physics\n*Note: this section involved computationally intense preprocessing. All code used is provided, and the computation is done below for a single example play. A separate kernel was created that also runs this code. Running for all plays was done in parallel and uploaded as a dataset due to Kaggle's file limitation. All code is available on github https://github.com/RobMulla/kaggle-nfl*\n\nIn this section, we will explain a metric that we developed to help determine player risk. This research still can be improved, but we believe it shows great promise for evaluating risk with the goal of minimizing unnecessary injuries. This data analysis need not apply only to punt plays, but for all plays where NGS data is available. As we've stated before, football is a violent and risky game - we cannot and should not attempt to remove all risk from the sport. We do, however, feel that by focusing on **unnecessary high risk** plays (ie. ones with little excitement or impact on the game yet carrying high risk), we can reduce the likelihood of concussions.\n\nBecause we have such a small number of plays resulting in concussions (37), our heuristic provides us with a way to determine every single player's risk for any given play play. Using Next Gen Stats data with player's location and direction, we did the following:\n\n1. Calculated the momentum of every player in both the `x` and `y` direction during the play. Momentum is velocity multiplied by mass ( `kg * m/s` ). We assumed every player has the mass of `245.86` pounds converted to kg (the average size of an NFL player). This calculation could be improved if we had actual player weights or average player weight per position.\n2. Calculated the distance of every player in relation to each other. For every play we have 22 players. There are `22 * 21` combinations of each player in relation to each other. We calculated the distance for each player pair during the play.\n3. Calculated the opposing momentum that each player has in relation to each other. Because we assume players are moving on a linear plane, we can use geometry to calculate each pair of players' opposing momentum in the x and y direction. If two players are moving at high velocity but in the same direction, their opposing momentum value would be small. Conversely, if the two players are moving at a high velocity towards each other, their opposing momentum would be large.\n4. Calculate the heuristic of `Injury_Risk` as the opposing momentum divided by the distance of the players from each other. While two players may have high opposing momentum, this only creates a risk if they are also in close proximity to each other.\n5. We then take the **maximum risk of every pair of players** during a play. This allows us to see if, during a play, any two players were at a high risk for collision.\n6. Finally, we normalized this risk value by dividing by the (mean + 1 standard deviation) of all players on all plays. This last step allows our metric to be easier to interpret.\n\nOur calculation can be seen as:\n\n$$ Injury\\_Risk = max \\bigg( \\frac{ \\sqrt{(momentum_{p1x} - momentum_{p2x} )^2 + (momentum_{p1y} -  momentum_{p2y} )^2 }}{distance_{p1vsp2}} \\bigg) $$\n\n$$  Normalized\\_Injury\\_Risk = \\frac {Injury\\_Risk}{average\\_risk\\_allplays + stddev\\_risk\\_allplays} $$\n\nIn summary, our risk metric is calculated based on two players *opposing momentum* and *distance from each other* and allows us to measure the potential injury for any two players."},{"metadata":{"_uuid":"36982f0c72905f0a076fd1d17f15e516743339e0"},"cell_type":"markdown","source":"## Example of Injury Risk\nBelow, we've plotted the `Injury Risk` heuristic for two players during a play. You can see the risk is increased as they approach each other. You can also see that the high momentum of both players, the angle, the opposing momentum, and the distance from each other all combine to create this metric."},{"metadata":{"trusted":true,"_uuid":"6fcbac4e33f739ae25fb10ebb42705a46ab35f27","_kg_hide-input":true},"cell_type":"code","source":"# Pull NGS data for example of preprocessing\nngs = pd.read_csv('../input/NFL-Punt-Analytics-Competition/NGS-2016-pre.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"ddeb7f7ec0582780e46ee525e80a9d92bdc3d51c","_kg_hide-input":true},"cell_type":"code","source":"\"\"\"\nFunctions for calculating physics with play\n\"\"\"\n\ndef calculateDistance(x1, y1, x2, y2):\n    dist = math.sqrt((x2 - x1)**2 + (y2 - y1)**2)\n    return dist\n\n\ndef cart2pol(x, y):\n    rho = np.sqrt(x**2 + y**2)\n    phi = np.arctan2(y, x)\n    return(rho, phi)\n\n\ndef pol2cart(rho, phi):\n    x = rho * np.cos(phi)\n    y = rho * np.sin(phi)\n    return(x, y)\n\n\ndef add_play_physics(play):\n    # Format columns\n    play['time'] = pd.to_datetime(play['time'])\n    # Distance\n    play['dis_meters'] = play['dis'] / 1.0936  # Add distance in meters\n    # Speed\n    play['dis_meters'] / 0.01\n    play['v_mps'] = play['dis_meters'] / 0.1\n    # Angles to radians\n    play['dir_radians'] = play['dir'].apply(math.radians)\n    play['o_radians'] = play['o'].apply(math.radians)\n    average_weight_nfl_pounds = 245.86\n    average_weight_nfl_kg = average_weight_nfl_pounds * 0.45359237\n    # http://webpages.uidaho.edu/~renaes/251/HON/Student%20PPTs/Avg%20NFL%20ht%20wt.pdf\n    play['momentum'] = play['v_mps'] * average_weight_nfl_kg\n    play['momentum_x'] = pol2cart(play['momentum'], play['dir_radians'])[0]\n    play['momentum_y'] = pol2cart(play['momentum'], play['dir_radians'])[1]\n    return play","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"dabc911d95577fdb23a44f96d12d43865e7a2120","_kg_hide-input":true},"cell_type":"code","source":"# Example play that we will calculate the risk factor for manually\nplay = ngs.loc[(ngs['Season_Year'] == 2016) &\n               (ngs['GameKey'] == 5) &\n               (ngs['PlayID'] == 3129)].copy()\nplay.columns = [col.lower() for col in play.columns]\nplay = pd.merge(play, pprd, how='left')\nplay = add_play_physics(play)\n\nplayexpanded = pd.merge(play, play,\n                        on=['season_year', 'gamekey', 'playid', 'time'],\n                        suffixes=('', '_partner'))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"a7331e9ec0fafbfeb589300bfba63091740b3e6a","_kg_hide-input":true},"cell_type":"code","source":"playexpanded['opp_momentum'] = np.sqrt(np.square(\n    playexpanded['momentum_x'] - playexpanded['momentum_x_partner']) +\n    np.square(playexpanded['momentum_y'] - playexpanded['momentum_y_partner']))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"9d8ff3e43055699ea9e6277440100ba5acb86fb3"},"cell_type":"markdown","source":"## Example 1: High Injury Risk Player Pair\nIn the first example we see who players who approach each other with high momentum and opposing directions and a close distance. The result is a high injury risk prior to moment of impact. This example did end in a concussion for one of the players."},{"metadata":{"trusted":true,"_uuid":"16b5ff3aba912efe0e56c4f16e0732ceaa95e22b","_kg_hide-input":true},"cell_type":"code","source":"playexpanded['dist'] = np.sqrt((playexpanded['x'] - playexpanded['x_partner']).apply(np.square) +\n                               (playexpanded['y'] - playexpanded['y_partner']).apply(np.square))\nplayexpanded['risk_factor'] = playexpanded['opp_momentum'] / \\\n    playexpanded['dist']\n\nfig, ax = create_football_field(figsize=(20, 8))\ndf = playexpanded.loc[(playexpanded['role'] == 'PLW') & (\n    playexpanded['role_partner'] == 'PR')].copy()\n# These values calculated below at aggreate of all plays\nmean_of_all_risk_factors = 183.40329650430533\nstddev_of_all_risk_factors = 274.16429253242063\n\ndf['risk_factor_normalized'] = df['risk_factor'] / \\\n    (mean_of_all_risk_factors + stddev_of_all_risk_factors)\n\n\ndf[['time', 'role', 'role_partner', 'v_mps', 'v_mps_partner', 'opp_momentum', 'x', 'y', 'x_partner', 'y_partner']] \\\n    .plot(kind='scatter', x='x', y='y', vmin=0, vmax=3,\n          c=df['risk_factor_normalized'].tolist(),\n          cmap='coolwarm',\n          ax=ax)\n\ndf[['time', 'role', 'role_partner', 'v_mps', 'v_mps_partner', 'opp_momentum', 'x', 'y', 'x_partner', 'y_partner']] \\\n    .plot(kind='scatter', x='x_partner', y='y_partner', vmin=0, vmax=3,\n          c=df['risk_factor_normalized'].tolist(),\n          cmap='coolwarm',\n          ax=ax)\n\nplayexpanded.loc[playexpanded['event'] == 'ball_snap'].plot(x='x', y='y',\n                                                            kind='scatter',\n                                                            ax=ax,\n                                                            color='black',\n                                                            marker='o',\n                                                            zorder=3,\n                                                            label='Players Position at Snap')\n\nfig.get_axes()[1].remove()\nfig.get_axes()[0].set_title('Injury Risk')\nplt.title('Example 1: Player Pair with High Injury Risk', fontsize=20)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"00cd9a4a86f4aceff3ec2df520b9f4552085dbb2"},"cell_type":"markdown","source":"## Example 2: Low Injury Risk Player Pair\nBelow is a plot of two players who's pair has relatively low risk. Despite the fact that both players are in close proximity at the end of the play, they are both moving in relatively the same direction, which results in a low opposing momentum. "},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"57b76e7287193f555f5e343d0e0366922bb26bfc"},"cell_type":"code","source":"fig, ax = create_football_field(figsize=(20, 8))\ndf2 = playexpanded.loc[(playexpanded['gsisid'] == 31902) & (\n    playexpanded['gsisid_partner'] == 28041)].copy()\n# These values calculated below at aggreate of all plays\nmean_of_all_risk_factors = 183.40329650430533\nstddev_of_all_risk_factors = 274.16429253242063\n\ndf2['risk_factor_normalized'] = df2['risk_factor'] / \\\n    (mean_of_all_risk_factors + stddev_of_all_risk_factors)\n\n\ndf2[['time', 'role', 'role_partner', 'v_mps', 'v_mps_partner', 'opp_momentum', 'x', 'y', 'x_partner', 'y_partner']] \\\n    .plot(kind='scatter', x='x', y='y', vmin=0, vmax=3,\n          c=df2['risk_factor_normalized'].tolist(),\n          cmap='coolwarm',\n          ax=ax)\n\ndf2[['time', 'role', 'role_partner', 'v_mps', 'v_mps_partner', 'opp_momentum', 'x', 'y', 'x_partner', 'y_partner']] \\\n    .plot(kind='scatter', x='x_partner', y='y_partner',\n          c=df2['risk_factor_normalized'].tolist(), vmin=0, vmax=3,\n          cmap='coolwarm',\n          ax=ax)\n\nplayexpanded.loc[playexpanded['event'] == 'ball_snap'].plot(x='x', y='y',\n                                                            kind='scatter',\n                                                            ax=ax,\n                                                            color='black',\n                                                            style='.',\n                                                            zorder=3,\n                                                            label='Players Position at Snap')\n\nfig.get_axes()[1].remove()\nplt.title('Example 2: Player Pair with Low Injury Risk', fontsize=20)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"e7b761b1bf23f4b729d3e03cc2a5369b7f08b878"},"cell_type":"markdown","source":"## Breaking down the components of our two examples\nWhile both examples have player pairs who are moving at high velocity. The relative distance, and opposing momentum of players in the second example are much less. This is because their momentum is moving in the same direction. However in example 1 the players start to move in opposition to each other- resulting in a high injury risk level. We can see below the components that make up the final risk calculation. We pulled the maximum risk per player pair on every given play."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"6158eed44a274a1bb8eaa19e1f6dddad4afe2583"},"cell_type":"code","source":"# Plot both examples side by side\nfig, ((ax1, ax2), (ax3, ax4), (ax5, ax6), (ax7, ax8), (ax9, ax10)\n      ) = plt.subplots(5, 2, sharey='row', sharex=True, figsize=(23, 12))\n\ndf.set_index('time')[['momentum']] \\\n    .plot(title='Example 1: Player 1 Momentum (kg m/s)', ax=ax1, color=color3)\ndf.set_index('time')[['momentum_partner']] \\\n    .plot(title='Example 1: Player 1 Momentum (kg m/s)', ax=ax3, color=color3)\ndf.set_index('time')[['opp_momentum']] \\\n    .plot(title='Example 1: Opposing Momentum', ax=ax5, color=color1)\ndf.set_index('time')[['dist']] \\\n    .plot(title='Example 1: Distance between Players', ax=ax7, color=color2)\ndf.set_index('time')[['risk_factor_normalized']] \\\n    .plot(title='Example 1: Normalized Injury Risk', ax=ax9, color=color4)\ndf2.set_index('time')[['momentum']] \\\n    .plot(title='Example 2: Player 2 Momentum (kg m/s)', ax=ax2, color=color3)\ndf2.set_index('time')[['momentum_partner']] \\\n    .plot(title='Example 2: Player 2 Momentum (kg m/s)', ax=ax4, color=color3)\ndf2.set_index('time')[['opp_momentum']] \\\n    .plot(title='Example 2: Opposing Momentum', ax=ax6, color=color1)\ndf2.set_index('time')[['dist']] \\\n    .plot(title='Example 2: Distance between Players', ax=ax8, color=color2)\ndf2.set_index('time')[['risk_factor_normalized']] \\\n    .plot(title='Example 2: Normalized Injury Risk', ax=ax10, color=color4)\n\n# Remove Legends\nax1.get_legend().remove()\nax2.get_legend().remove()\nax3.get_legend().remove()\nax4.get_legend().remove()\nax5.get_legend().remove()\nax6.get_legend().remove()\nax7.get_legend().remove()\nax8.get_legend().remove()\nax9.get_legend().remove()\nax10.get_legend().remove()\nplt.subplots_adjust(hspace=0.7)\nplt.show()\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"000cff6eea2924ae10860076c7c8eb94a7f35068"},"cell_type":"markdown","source":"## Evaluating the components of this metric\nWith the 2016 and 2017 NGS data we were able to calculate this metric for over **2.8 Million different plays and player combinations**. We can see below the distribution of players distance to each other, as well as the distribution of opposing forces for each player. These two components are what is used to compute `injury risk`."},{"metadata":{"trusted":true,"_uuid":"c0e216c7cc2d6e75394c96edd0cad7bdfdee175e","_kg_hide-input":true,"_kg_hide-output":false},"cell_type":"code","source":"sns.set(style=\"white\")\nplt.style.use('ggplot')\n\nmax_risk_partners = pd.read_parquet(\n    '../input/robmullanflpreprocessed/max_risk_partners.parquet')\nax1 = max_risk_partners['dist'].plot(kind='hist',\n                                     figsize=(15, 5),\n                                     bins=100,\n                                     color=color4,\n                                     title=\"Distribution Of Players Distance to Eachother During Punt Plays\")\nax1.set_xlabel('Distance between Players (yards)')\nplt.show()\n\n# Cap distance at 0.1, because it is physically impossible to be 0 distance from eachother and divide by zero issues\n# Example of calculating risk factor\n# Distance can't be zero\nmax_risk_partners.loc[max_risk_partners['dist'] < 0.1, 'dist'] = 0.1\nmax_risk_partners['dist'] = max_risk_partners['dist']\nmax_risk_partners['risk_factor'] = max_risk_partners['opp_momentum'] / \\\n    max_risk_partners['dist']\n\n\nax2 = max_risk_partners['opp_momentum'].plot(kind='hist',\n                                             figsize=(15, 5),\n                                             bins=500,\n                                             color=color3,\n                                             xlim=(0, 3000),\n                                             title='Distribution of Opposing Forces between Players')\nax2.set_xlabel('Opposing Momentum between Players kg * (m/s)')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"7b76de0aafd89f3d9e71e6cf17719c0a78999f7f","_kg_hide-input":true},"cell_type":"code","source":"# Read the momentum and distance calculated for every play\n\ninjury_play_ngs = pd.read_parquet(\n    '../input/nfl-punt-data-preprocessing-ngs-injury-plays/NGS-injury-plays.parquet')\ngsisid_numbers = ppd.groupby('gsisid')['number'].apply(\n    lambda x: \"%s\" % ', '.join(x))\ngsisid_numbers = pd.DataFrame(gsisid_numbers).reset_index()\n# Add Player Number and Direction\nvr_with_number = pd.merge(\n    vr, gsisid_numbers, how='left', suffixes=('', '_injured'))\nvr_with_number['primary_partner_gsisid'] = vr_with_number['primary_partner_gsisid'].replace(\n    'Unclear', np.nan).fillna(0).astype('int')\nvr_with_number = pd.merge(vr_with_number, gsisid_numbers,\n                          how='left',\n                          left_on='primary_partner_gsisid',\n                          right_on='gsisid', suffixes=('', '_primary_partner'))\nvr = vr_with_number\n\n# Mark the player pairs where one was injured\nvr['injured_pair'] = True\nrisk_with_inj = pd.merge(max_risk_partners,\n                         vr[['season_year', 'gamekey', 'playid', 'gsisid',\n                             'injured_pair', 'primary_partner_gsisid']],\n                         left_on=['season_year', 'gamekey',\n                                  'playid', 'gsisid', 'gsisid_partner'],\n                         right_on=['season_year', 'gamekey', 'playid',\n                                   'gsisid', 'primary_partner_gsisid'],\n                         how='left',\n                         suffixes=('', '_y'))\nrisk_with_inj = pd.merge(risk_with_inj,\n                         vr[['season_year', 'gamekey', 'playid', 'gsisid',\n                             'injured_pair', 'primary_partner_gsisid']],\n                         left_on=['season_year', 'gamekey',\n                                  'playid', 'gsisid_partner', 'gsisid'],\n                         right_on=['season_year', 'gamekey', 'playid',\n                                   'gsisid', 'primary_partner_gsisid'],\n                         how='left',\n                         suffixes=('', '_y'))\n\nrisk_with_inj['either_injured'] = risk_with_inj[[\n    'injured_pair', 'injured_pair_y']].sum(axis=1)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8e64e5d2856f86c42799758ee1f28575aa8193ba"},"cell_type":"markdown","source":"## Interpreting the Normalized Injury Risk Factor\n\n$$  Normalized\\_Injury\\_Risk = \\frac {Injury\\_Risk}{average\\_risk\\_allplays + stddev\\_risk\\_allplays} $$\n\nOur Normalized Risk Factor can now be thought of in terms of:\n- **Very High Risk** Normalized Risk factor > 1\n- **High Risk** Normalized Risk factor between 0.75 and 1\n- **Medium Risk**  Normalized Risk factor between 0.5 and 0.75\n- **Low Risk**  Normalized Risk factor less than 0.5\n"},{"metadata":{"trusted":true,"_uuid":"debc8307640f87c0b123660affe94eae665ec28a","_kg_hide-input":true},"cell_type":"code","source":"risk_with_inj['risk_factor_normalized'] = \\\n    risk_with_inj['risk_factor'] / (risk_with_inj['risk_factor'].mean() + risk_with_inj['risk_factor'].std())","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"a50d204806bfc24f54820b8c664415a9ab935552"},"cell_type":"markdown","source":"## What is the calculated injury risk of player pairs involved in concussions?\n\nUntil this point our calculation of risk was merely hypothetical. We will test our hypothesis by looking at the injury risk for player pairs where a injury occurred.\n\nIn the plot below we can see nearly all the concussion plays show the injury risk of the two players involved being much higher than the average injury risk of two players. In many cases the risk factor is extremely high (>2). This supports our hypothesis that our risk metric is a good indicator of when players are at risk of concussions."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"bcc8fd5f62e5f7aaaf68939a13990eec38a9f314"},"cell_type":"code","source":"ax = risk_with_inj['risk_factor_normalized'].plot(kind='hist',\n                                                  bins=500,\n                                                  xlim=(0, 10), figsize=(15, 5),\n                                                  title='Distribution of All Player Risks',\n                                                  label='Risk Factor (All Player Pairs)',\n                                                  color=color2)\n\nmean_risk_for_concussions = risk_with_inj.loc[risk_with_inj['injured_pair']\n                                              == True]['risk_factor_normalized'].mean()\n# ax.axvline(x=mean_risk_for_concussions, color=color1, label='Average Risk Concussions')\n# add dots for concussion plays\ninured_pairs = risk_with_inj.loc[risk_with_inj['injured_pair'] == True][[\n    'risk_factor_normalized']]\ninured_pairs['y_axis'] = 10000\ninured_pairs.plot(x='risk_factor_normalized', y='y_axis',\n                  ax=ax, kind='scatter',\n                  color='red',\n                  zorder=3,\n                  label='Concussion Injury Pair')\nax.set_ylabel('Count')\nax.set_xlabel('Normalized Injury Risk Factor')\n\nplt.legend()\nplt.show()\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"ba4713027cbdcfc3b9c0dcf011f547d3ceadb2f1"},"cell_type":"markdown","source":"Looking at the risk of all injury plays. We can see that out of 33 concussion plays involving two players - 27 of them occurred are **Very High Risk** (above 1). The rest are at leave above 0.5. This supports our idea that concussions will occur when the injury risk is high."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"a73fb8ee3bc4b4baa469dde79e509cd436b6cea8"},"cell_type":"code","source":"risk_with_inj.loc[risk_with_inj['injured_pair'] == True][['season_year', 'gamekey', 'playid',\n                                                          'generalized_role', 'generalized_role_partner',\n                                                          'risk_factor_normalized']] \\\n    .sort_values('risk_factor_normalized', ascending=False) \\\n    .reset_index(drop=True)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"835148d354d437b80c1992a64a19161480ccc800"},"cell_type":"markdown","source":"## Role Pairings that have High Risk\nNow that we've developed this risk metric, we can look at general trends to see which players tend to have high risk in relationship to each other. As expected Gunners and Punt Returners have very high risk. Jammer/Gunners and PuntingLinemen/PuntProtector also show high risk. Punters have the lowest risk during punt plays."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"617c698fea1f242a69c187e82f90cb98e49c9e5b"},"cell_type":"code","source":"sns.set(style=\"white\")\n\nrisk_with_inj['generalized_role'] = risk_with_inj['generalized_role'].str.replace(\n    'Punting_Longsnapper', 'Punting_Lineman')\nrisk_with_inj['generalized_role_partner'] = risk_with_inj['generalized_role_partner'].str.replace(\n    'Punting_Longsnapper', 'Punting_Lineman')\n# Format data to compare risk vs plays\nrisk_corr = risk_with_inj[['generalized_role',\n                           'generalized_role_partner',\n                           'risk_factor_normalized']] \\\n    .groupby(['generalized_role', 'generalized_role_partner']) \\\n    .median() \\\n    .reset_index() \\\n    .pivot(index='generalized_role',\n           columns='generalized_role_partner',\n           values='risk_factor_normalized') \\\n    .fillna(0)\n\n# Remove underscores\nrisk_corr.index = risk_corr.index.str.replace('_', ' ')\nrisk_corr.columns = risk_corr.columns.str.replace('_', ' ')\n\n# Generate a mask for the upper triangle\nmask = np.zeros_like(risk_corr, dtype=np.bool)\nmask[np.triu_indices_from(mask, k=1)] = True\n\n# Set up the matplotlib figure\nf, ax = plt.subplots(figsize=(20, 20))\n\n# Draw the heatmap with the mask and correct aspect ratio\nsns.heatmap(risk_corr,\n            mask=mask,\n            cmap='coolwarm', center=0.25,\n            square=True, linewidths=.5,\n            vmax=0.5,\n            cbar_kws={\"shrink\": .5})\nplt.title('Median Risk of Generalized Role Pairs', fontsize=20)\nplt.xlabel('')\nplt.ylabel('')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"535367237a466880097ad2ffc989e645baa557b5"},"cell_type":"markdown","source":"## Aggregating Injury Risk Metric at the Play Level\nNext we will aggregate this risk to the play level, to help up identify if a play shows a higher propensity for risk. In the plot below you can see the risk of all plays by the distance of the punt. The red dots indicate plays where a concussion occurred. Visually you can see these plays are on the high side of the risk factor for punt plays. \n\n$$ Play\\_Risk\\_Factor = \\frac {\\sum (Injury\\_Risk_{player1v2})} {Count\\_of\\_Player\\_Pairings}$$\n\nWhere total player pairing is 22 x 21 = 462 player pairing on the field."},{"metadata":{"trusted":true,"_uuid":"c68bb478dc3b7b6133fc9bf1a0c60cf906833fa7","_kg_hide-input":true},"cell_type":"code","source":"plt.style.use('ggplot')\n\nrisk_sum_per_play = risk_with_inj \\\n    .groupby(['season_year', 'gamekey', 'playid'])['risk_factor_normalized'] \\\n    .mean() \\\n    .reset_index()\n\nplayrisk = pd.merge(risk_sum_per_play, pi, how='left')\nplayrisk = pd.merge(\n    playrisk, vr[['season_year', 'gamekey', 'playid', 'injured_pair']], how='left')\nax = playrisk.plot(x='punt_distance',\n                   y='risk_factor_normalized',\n                   kind='scatter',\n                   figsize=(15, 8), alpha=0.1)\nplayrisk.loc[playrisk['injured_pair'] == 1].plot(x='punt_distance',\n                                                 y='risk_factor_normalized',\n                                                 kind='scatter',\n                                                 color=color1,\n                                                 title='Injury Risk vs Punt Distance',\n                                                 label='Play where a Concussion occurred',\n                                                 ax=ax)\n\nplayrisk.groupby('punt_distance')['risk_factor_normalized'] \\\n    .mean() \\\n    .plot(ax=ax, color=color3, label='Average Play Risk at Punt Distance')\nax.set_xlabel('Punt Distance')\nax.set_ylabel('Normalized Risk Factor of Play')\nplt.legend()\nax.set_xlim(20, 70)\nplt.show()\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"5a5d26c06a4d9636669a28f71578469d0c23f175"},"cell_type":"markdown","source":"## Risk by Play Result\nNow that we've aggregated our injury risk metric at the play level we can look at different play results, and see if the risk factor is higher or lower for those types of plays. It's clear to see that Out of Bounds and Fair Catch plays how lower mean and variance of risk, while plays with Fumbles and those that are returned for No Gain have higher. "},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"022c78ebb01c77787b6cd3a92e2d3375027aa5a2"},"cell_type":"code","source":"playrisk['play_unique'] = playrisk['season_year'].astype('str').add((playrisk['gamekey']).astype('str')).add((playrisk['playid']).astype('str'))","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true,"_uuid":"e913e5e281990ae6cee009cb568c71147c4e01d5"},"cell_type":"code","source":"# Ended up not liking these plots so hiding them\nfig, (ax, ax1, ax2, ax3, ax4, ax5) = plt.subplots(1, 6, figsize=(20, 5), sharey=True)\nsns.boxplot(x='Fumbled', y='risk_factor_normalized', data=playrisk, ax=ax)\nsns.boxplot(x='Returned for Negative Gain', y='risk_factor_normalized', data=playrisk, ax=ax1)\nsns.boxplot(x='Returned', y='risk_factor_normalized', data=playrisk, ax=ax2)\nsns.boxplot(x='Returned for No Gain', y='risk_factor_normalized', data=playrisk, ax=ax3)\nsns.boxplot(x='Fair Catch', y='risk_factor_normalized', data=playrisk, ax=ax4)\nsns.boxplot(x='Out of Bounds', y='risk_factor_normalized', data=playrisk, ax=ax5)\nfig.suptitle('Risk Factor by Play Features')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"18dbde097c424e288c92e35e278bc11605ad2526"},"cell_type":"code","source":"def play_result(row):\n    \"\"\"\n    Function to determine play outcome\"\"\"\n    if row['Punt Blocked']:\n        return 'Punt Blocked'\n    elif row['Fair Catch']:\n        return 'Fair Catch'\n    elif row['Touchback']:\n        return 'Touchback'\n    elif row['Downed']:\n        return 'Downed'\n    elif row['Returned for Negative Gain']:\n        return 'Negative Gain'\n    elif row['Returned for Positive Gain']:\n        return 'Positive Gain'\n    elif row['Returned for No Gain']:\n        return 'No Gain'\n    elif row['Out of Bounds']:\n        return 'Out of Bounds'\n    elif row['PENALTY']:\n        return 'Penalty'\n\n\nplayrisk['Play Result'] = playrisk.apply(play_result, axis=1)\nplayrisk_with_roles = pd.merge(\n    playrisk, play_roles, how='left', on='play_unique')\n\nplayrisk_mean_by_jammers = playrisk_with_roles.groupby(['Number_of_jammers'])[['risk_factor_normalized']] \\\n    .agg([np.mean, 'count']) \\\n    .reset_index()\nplayrisk_mean_by_jammers.columns = [\n    ' '.join(col).strip() for col in playrisk_mean_by_jammers.columns.values]\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"aeb4803344077dd150c4815c83c9ce5b4cc91ae4"},"cell_type":"markdown","source":"This plot shows us the risk of plays grouped by their outcome. They are ordered the the mean risk of the play. We can see that returns for `No Gain` have higher variance in the risk factor, but also the highest average risk. These are the types of plays we would like to avoid and encourage to be fair catches. As you would expect plays resulting in a `Fair Catch` have far less injury risk."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"492f354fa10b2e379b99dd5f0b357cdc03a2ac31"},"cell_type":"code","source":"fig, ax = plt.subplots(1, figsize=(20, 7), sharey=True)\n\nsns.swarmplot(x='Play Result',\n              y='risk_factor_normalized',\n              order=['No Gain', 'Positive Gain', 'Punt Blocked',\n                     'Negative Gain', 'Downed', 'Touchback', 'Fair Catch',\n                     'Out of Bounds', 'Penalty'],\n              data=playrisk_with_roles.replace(\n                  'Returned for No Gain', 'No Gain'),\n              ax=ax,\n              hue_order=[0, 1, 2, 3, 4, 5])\nplt.xlabel('')\nplt.ylabel('Play Risk Metric (Larger is at more risk for injury)')\nplt.suptitle(\n    'Risk of Plays by Outcome (Each dot represents a play)', fontsize=15)\nplt.show()\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"44b7ffed1e965ee80466e646405ff5882183c207"},"cell_type":"markdown","source":"The following plot shows only the average risk by play outcome to clearly show which play results involve the most and least amount of injury risk."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"26cfa1f4b8ac7af007f228902809b871b356aa94"},"cell_type":"code","source":"playrisk_mean_by_type = playrisk.groupby(['Play Result'])[['risk_factor_normalized']] \\\n    .agg([np.mean, 'count']) \\\n    .reset_index()\n\nplayrisk_mean_by_type.columns = [\n    ' '.join(col).strip() for col in playrisk_mean_by_type.columns.values]\n\nplayrisk_mean_by_type.loc[playrisk_mean_by_type['risk_factor_normalized count'] > 1]\n\nplayrisk_mean_by_type.sort_values('risk_factor_normalized mean') \\\n    .plot(x='Play Result',\n          y='risk_factor_normalized mean',\n          kind='barh',\n          figsize=(15, 5),\n          color='grey',\n          legend=False,\n          title='Normalized Risk Factor of Play by Result')\nplt.ylabel('')\nplt.show()\n","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"e78e6f931aa9c19e5bcae2dd53198e84bdab35e0"},"cell_type":"code","source":"playrisk_mean_by_jammers.sort_index(ascending=True) \\\n    .plot(x='Number_of_jammers',\n          y='risk_factor_normalized mean',\n          kind='bar',\n          figsize=(15, 5),\n          color='grey',\n          legend=False,\n          rot=0,\n          title='Normalized Risk Factor of Play by Result')\nplt.ylabel('Normalized Play Injury Risk')\nplt.xlabel('Number of Jammers')\nplt.show()\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"cbafb5ef26ed09ff5bb860e180860de55222c5b1"},"cell_type":"markdown","source":"## Creating a Model of Play's Injury Risk Based on Formation\nWe now have a metric we can use to try and model based on the formations and roles of players for each team. Because we want a model that is easy to interpret we choose a simple linear model as opposed to a complex black-box machine learning model.\n\nWe fit a [linear regression lasso](<https://en.wikipedia.org/wiki/Lasso_(statistics%29>) model using 5 fold cross validation. We then can interpret the coefficients to see which player position impacts the injury risk of the play.\n\nWe include `Punt Distance` and `Return Yards` as variables in our model to account for their impact on the play's injury risk."},{"metadata":{"trusted":true,"_uuid":"d5d2fd74eb26de2daccbf12f4add0b1ee4e4a887","_kg_hide-input":true},"cell_type":"code","source":"from sklearn.linear_model import LinearRegression\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.model_selection import KFold  # import KFold\nfrom sklearn.linear_model import LassoCV\nfrom sklearn.linear_model import Lasso\nfrom sklearn.model_selection import GridSearchCV\n\nX = playrisk_with_roles[['GL', 'GLi', 'GLo', 'GR', 'GRi', 'GRo', 'P', 'PC', 'PDL1', 'PDL2',\n                         'PDL3', 'PDL4', 'PDL5', 'PDL6', 'PDM', 'PDR1', 'PDR2', 'PDR3',\n                         'PDR4', 'PDR5', 'PDR6', 'PFB', 'PLG', 'PLL', 'PLL1', 'PLL2',\n                         'PLL3', 'PLM', 'PLM1', 'PLR', 'PLR1', 'PLR2', 'PLR3', 'PLS', 'PLT',\n                         'PLW', 'PPL', 'PPLi', 'PPLo', 'PPR', 'PPRi', 'PPRo', 'PR', 'PRG',\n                         'PRT', 'PRW', 'VL', 'VLi', 'VLo', 'VR', 'VRi', 'VRo', 'punt_distance', 'return_yards']] \\\n    .rename(columns={'punt_distance': 'Punt Distance',\n                     'return_yards': 'Return Yards'}).fillna(0)\ny = playrisk_with_roles['risk_factor_normalized']\n\nlasso = Lasso(random_state=0)\nalphas = np.logspace(-4, -0.5, 30)\ntuned_parameters = [{'alpha': alphas}]\nn_folds = 5\nclf = GridSearchCV(lasso, tuned_parameters, cv=n_folds, refit=False)\nclf.fit(X, y)\n\nclf.best_params_\nlasso = Lasso(**clf.best_params_, fit_intercept=True)\nlasso.fit(X, y)\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"4d57ffaeaac367b5950010c75d1b8750ec53697b"},"cell_type":"markdown","source":"The model coefficients now can be interpreted to see their correlation with injury risk. Many of the coefficients were zero, indicating that they had no correlation with the injury risk of the play.\n\nWe can see that `Return Yards` and `Punt Distance` both are correlated positively with the injury risk plays. Meaning that, longer the punts are and the longer the return distance, the higher the injury risk may be. This is expected, and these variables were included so that they are accounted for when reviewing the positional coefficients.\n\nThe positions injury risk is increased when a PFB position is in place on the punt return team. If we look at our analysis above we note that the PFB position is rarely seen in plays. VL and VR (the jammer positions) have the strongest correlation with less injury risk. VL and VR are roles used only when in single coverage. This supports our previous analysis where we identified some concerns with double coverage of gunners."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"793ce5493c9cea1573105aa9ce9e4414b789e946"},"cell_type":"code","source":"model_coeffs = pd.DataFrame([X.columns, lasso.coef_]).T\nmodel_coeffs.columns = ['Role in Play', 'Coefficient']\n\nax = model_coeffs.loc[model_coeffs['Coefficient'] != 0] \\\n    .set_index('Role in Play') \\\n    .sort_values('Coefficient') \\\n    .plot(kind='barh',\n          figsize=(20, 6),\n          color='grey',\n          title='Coefficients of Model for Play Injury Risk (Zero coefficients not shown)')\nax.get_legend().remove()\nplt.show()\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"be022c3a4b5687b6ba76e01242ecaa25b035e146"},"cell_type":"markdown","source":"# Section VI - Proposed Rules\n## Rule Change Proposal 1 - **Incentivize the Fair Catch**\n**On a completed Fair Catch, the ball is awarded to the returning team 5 yards in advance of the fair catch location.**\nWe know that the median punt is returned for roughly 7 yards. With a focus on keeping the possibility of 'big play moments' in the game, we think that awarding the returning team an extra 5 yards on a completed fair catch is the best way to reduce unnecessary dangerous plays in the game. We believe that this distance should be in multiples of 5 to stay consistent with all other similar rules. We believe that awarding more yards (10, 15, 20 yards) does not maintain the integrity of the game, as it would be more distance than that of a typical return.\n\n## Rule Change Proposal 2 - **Improve Defenseless Player Rules to Include Certain Punt Coverage Players**\n**Add the following verbiage to the definition of a defenseless player - A player returning upfield in pursuit of a punt returner**\nCurrently, the defenseless player rule specifically applies during punt plays to: \n\n    1. A kicker/punter during the kick or during the return (Also see Article 6(h) for additional restrictions against a kicker/punter)\n    2. A kickoff or punt returner attempting to field a kick in the air\n    3. A player who receives a “blindside” block when the path of the offensive blocker is toward or parallel to his own end line.\n\nWe believe the NFL should explicitly add verbiage to include punt coverage players in pursuit of the punt returner once the punt returner has passed them upfield. While this may already be accounted for in the blindside block rule- it should be explicitly stated as a point of emphasis for officials to focus on.\n\n## Rule Change Proposal 3 - **Discourage double teaming of Gunners**\n**When showing a punt formation, the returning team shall only have one player engaged with each of the punting team’s gunners after the snap within 5 yards of the line of scrimmage (2 maximum)**\n\nThis rule may be the least practical to implement. We understand that returning teams consider several different formations depending on the game situation, location on the field, and opposing gunners. Regardless of these issues, the data is clear that hybrid or double teaming of gunners is correlated strongly with a players having high injury risk. "},{"metadata":{"_uuid":"820da7d13ad14e11c6e97e512837b1d819e371c8"},"cell_type":"markdown","source":"# Section VII Solution Efficacy and Impact on Game Integrity\n## **Rule Change 1** - On a completed Fair Catch, the ball is awarded to the returning team 5 yards in advance of the fair catch location.\n## Findings in support of this rule change\n1. Approximately 35% of all returned punts are for less than 5 yards (ignoring plays with penalties).\n2. Our `Injury Risk Factor` calculates the risk of injury for pairs of players during a play based on player momentum, direction and distance apart. This metric shows that plays that are **Returned For No Gain** average the highest risk for injury. Additionally, we see that fair catches are among the plays where punt players at the lowest risk for injury.\n3. When visually reviewing the paths taken by punt returners, it is clear that most movements  are made within 5 yards of the catch location. Most of these movements are from sideline to sideline and not up-field.\n4. On Fair Catches, the median distance of the closest opponent to the punt return at the moment of the receiving the punt is roughly 3.19 yards. On punt returns, the median distance is 10 yards. It's hard to define what this distance would be with the new rule change, but we can assume that fair catches will be made with higher frequency and with opponents father away. Both of these downstream effects would reduce the time that players are put at risk.\n5. If we conservatively assume that plays returned for negative yards or no gain will be eliminated by this change, then 434 plays from two seasons would have resulted in a fair catch. These are plays that we consider **unnecessary risk** because they are associated with high risk of injury but produce no exciting, game-changing moments.\n\n## Impact on Game Integrity\n1. This rule's simplicity allows it to be easily implemented by the NFL. Fair catch procedures would remain the same from a player perspective, while the only change for officials will be to mark off the 5 yard advancement of the ball. \n2. Game dynamics will change as a result of this rule, but we believe the evolution of the punt play is necessary to increase player safety. Areas where we believe this rule change will have an impact are:\n    - Punt returners must now consider and anticipate punting team's distance from them and decide whether they feel they have the ability to gain at least 5 yards, similar to the decision making process for kickoff returners.\n    - Coaches will need to calculate the benefits of punt returns and how to coach their players on when to decide/attempt a return.\n    - Depending on the situation, the new rule may incentivize the punting team to instead go for it on 4th down.\n    - Punting teams may attempt \"rugby\" style punts in order to make fair catches harder to receive. Punters may also attempt more kicks out of bounds.\n3. There could potentially be some new risks to players:\n    - Punt returners will now need to consider where coverage players are. This added uncertainty may lead to more muffed punts. Would this negate any safety benefit?\n    - Punts landing near the endzone that would have previously been left alone by the returning team (in hopes of a touchback) may now be fair caught. Could this add some additional risk?\n    \n## **Rule Change 2** - Add the following verbiage to the definition of a defenseless player - A player returning upfield in pursuit of a punt returner.\n## Findings in support of this rule change\n1. In reviewing the video footage and NGS path data of plays involving a concussion, we see that approximately 9 involve players who were running up-field in pursuit of the punt returner but who then changed direction to follow the returner.\n2. Only one play out of the 37 resulted in a penalty of Unnecessary Roughness being called by officials.\n3. Player velocity and direction data shows that many of the plays involving concussions also have players hit soon after changing direction.\n4. We believe that by emphasizing the defenseless player verbiage to include players in pursuit of a punt returner, officials would be more confident in calling roughness for these plays.\n\n## Impact on Game Integrity\n1. This rule is actionable by the NFL, however, it involves the subjectivity of key stakeholders.   Official judgment calls are subject to interpretation by the officials calling the game. Our hope would be that officials, taking this as a point of emphasis, would call these penalties more often and, in so doing, would reduce future occurence.\n2. There could potentially be some new risks to players:\n    - Punt coverage players may be more likely to put themselves at risk, believing they will not be blocked by the returning team. It is important for players to be aware of possible impact and have their \"head on a swivel\". There is the potential that coverage players may gain a false sense of safety by this rule change. Still, we believe if these penalties are called correctly this will not be a significant issue.\n\n## **Rule Change 3** - When presenting a punt formation, the returning team shall only have one player engaged with the punting team's gunners per gunner within 5 yards of the line of scrimmage (2 maximum)\n## Findings in support of this rule change\n1. The data clearly shows that formation where one or both gunners are double teamed significantly changes the paths taken by gunners.\n2. We see that Punting Linemen are the most common position in punt plays to sustain concussions (19 of 37 (51%) plays involved as injured or primary partner)\n3. The data also show that the rate of concussions increases when the number of jammers increases.\n4. Double coverage of gunners results in an imbalance on the field of offensive and defensive players in relationship to each other - allowing for punting linemen to gain more velocity and opening up the possibility for concussions. This is supported by the fact that our Injury Risk Factor is highest in plays where there are 4 jammers.\n5. By visualizing the routes of punting linemen and gunners, we see that these players are commonly Punting Linemen 30 or more yards up field from their starting position. This shows that they are commonly unblocked defenders reaching high velocity.\n6. When modeling the injury risk of plays, we find that having single coverage roles (VR and VL) is correlated with a decrease in injury risk.\n\n\n## Impact on Game Integrity\n1. We believe this rule change would be the hardest for the NFL to implement. There are many factors to consider by both teams when deciding to line up for a punting play. When we interviewed Frank Beamer, he was not in favor of this rule change - saying it gave too much advantage to the punting team. Despite cultural opposition, the data points to this as a key risk factor and we would be remiss to not propose something surrounding the double teaming of gunners.\n2. This rule change has the potential to be gamed by teams on fake punts if they would like to take advantage of the one-on-one coverage. Additionally, teams may show a punting formation on field goal attempts, restricting the defensive strategy on these types of plays.\n2. There could potentially be some new risks to players:\n    - By forcing the single coverage on gunners, defensive teams may choose to have more players lined up further back behind the line of scrimmage.\n    - Gunners would have more of a chance to reach returners than they would have if double teamed, increasing the potential for high velocity hits by gunners on punt returners.\n    - We don't believe that these potential injury risks should be of concern: most punting plays are already single coverage. Of the punt plays analyzed, the concussion rate of single coverage was less than that of four jammers."},{"metadata":{"_uuid":"8563e1ebf6cc207d62bee01930ce40a9ac4d1035"},"cell_type":"markdown","source":"## Thanks! \nThanks for taking the time to read our analysis. It is the result of countless hours evaluating data and considering potential rule changes.  If implemented, in part or in whole, our analysis suggests the potential for concussion risk reduction during punting plays. \n\nSpecial thanks to: Ryan Felts, Mike Amodeo, and Daniel Griffith for their support in reviewing this analysis. Thanks to Frank Beamer for taking the time to share his insights on the topic."}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat":4,"nbformat_minor":1}