{"cells":[{"metadata":{"_uuid":"7b3445815f9473851c9f8b9f67dd4dfb16fb41bb"},"cell_type":"markdown","source":"## Objective:\n<b>My aim throughout this process has been to look at what is common to plays involving concussions in the NGS data and what you can perceive in the videos (empirical approach). From that analysis I choose to propose rule changes that would likely affect the outcome of the punt plays by negating the possibility of certain events or to have rules that would hopefully create a behavioral change in how players partake in the punt play. I lead with my proposed rules and attempt to walk you through why I am proposing these rules.</b>"},{"metadata":{"_uuid":"1ab36da30e4eea583ee8c9528212db3a2a7d9da2"},"cell_type":"markdown","source":"### Proposed rule changes include:\n1. **Allowance of video review of plays where an injury occurs and to allow for subsequent calls of penalties directly related to the injury to be called. If injury isn't apparent immediately and a penalty is thus an unsuitable deterant, a fine may be incurred on the perpetrater of the penalty after a confirmation later by video review (potential overlap with Rule 17 Section 2 Article 3).**\n2. **Expanding the defenseless player status to punt team players who are within 5 yards of the PR and are running towards their own endzone (this would be similar to the afforded protection to the punter, Rule 12 Section 2 Article 7-a-8)**\n3. **Restricted zone of 8 yards (by yardline) for the punt receiver during play.**\n    - Return and punt team players may not be within 8 yards of the PR (by yardline distance) during a punt play unless the PR has signaled for a fair catch, the ball has been caught/muffed by the PR, or the ball has hit the ground.\n    - **Penalty**: 10 yards from the PR and the play is called dead when infraction occurs.\n    - The fair catch rule could also be removed similar to CFL punt return rules to increase the number of returns, but I believe the number instances where a fair catch would be called is going to be severely reduced because of the restricted zones reward proposition for the punt returner."},{"metadata":{"_uuid":"6ddbe1a5a08d108f9cdc11e0f4b50177fe3a1899"},"cell_type":"markdown","source":"Table of contents:\n- [Analysis and Discussion](#aad)\n- [Concussion Play and Video Analysis](#cpava)\n- [Observations of concussion dataset](#oocd)\n- [Observations of concussion film](#oocf)\n- [Concussions before the punt](#cbtp)\n- [Observations of conussions after the punt](#oocatp)\n- [Behavioral Deterrent Rule Changes](#bdrc)\n- [Structural Changes to the Punt Return](#scttpr)\n- [PR Restricted Zone Analysis](#PR)\n- [Overall Rule Coverage of Concussion Related Plays](#coverage)\n- [Links to Other Notebooks](#lton)"},{"metadata":{"_uuid":"a03feec5ed8c35f84b504eef5bf7e5363d853dd9"},"cell_type":"markdown","source":"## <a id='aad'>Analysis and Discussion</a>\nMy entire process for deriving the above rules comes from viewing the concussion related plays, noting features of those plays I found are distinctive and are directly or indirectly related to the concussion causing event, and understanding the impact of the hypothetical rule change. As we all have seen, the number of concussion events is extremely low (37) and any feature given to us or otherwise derived will likely be used to overfit in any sense to what is contributing to a concussion. **I will also admit I did not particularly focus on ways to reduce the number of punt returns inorder to reduce the number of concussions because punts are already pretty 'uninteresting' at 55.4% of punt plays** resulting in a fair catch, touchback, dead ball, downed ball, no punt, or a kick out of bounds and I was motivated to find ways to potentially increase the number of punt returns while reducing features involved with concussion events. Hopefully the following notebook cells give you an idea of how I came to my conclusions. I also apologize that my analysis spreads out to multiple notebooks, but it made it easier on the memory for the kernels especially with the NGS data."},{"metadata":{"_uuid":"4df25af0c14dbef35dcc2b5961e9e4f30c9d0b32"},"cell_type":"markdown","source":"Just for context I have provided some statistics that were derived in this notebook (https://www.kaggle.com/jdemeo/preprocessing-punt-play) and I may reference occasionally throughout my analysis:\n- 3701 of 6681 punt plays are 'uninteresting' (55.4%)\n- 2627 of 6681 punt plays result in a punt return (39.3%)\n- 353 of 6681 punt plays result in an 'interesting' play other than a punt return (5.3%)\n    - Muffs, blocked kicks, fumbles, fake punts, etc.\n- 37 of 6681 punt plays result in a concussion (0.55%)"},{"metadata":{"_uuid":"5ca5c46cab3989ef8fd9a887161c9a41a9196baa"},"cell_type":"markdown","source":"### <a id=\"cpava\">Concussion Play and Video Analysis</a>"},{"metadata":{"trusted":true,"_uuid":"367972d61f53feca9fb0519826aa8f149182dd53"},"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as mpatches\nfrom IPython.display import Image, display\nsns.set()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"f8a48164cf8daf4ec58ec2c5256627664daf55cd"},"cell_type":"code","source":"# Load in concussion data deduced from video review of the play\nvideo_review_df = pd.read_csv('../input/NFL-Punt-Analytics-Competition/video_review.csv')\nprint(video_review_df.shape)\nvideo_review_df.head(1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"310848506f4757228a2e52f883fb2a7529776911"},"cell_type":"code","source":"# Load in player related data\nplayer_df = pd.read_csv('../input/NFL-Punt-Analytics-Competition/player_punt_data.csv')\nplay_player_role_df = pd.read_csv('../input/NFL-Punt-Analytics-Competition/play_player_role_data.csv')\n\n# Combine Relevant Player Information, Position, Role, Number\nmaster_player_df = pd.merge(player_df, play_player_role_df,\n                          how='inner',\n                          on=['GSISID']).drop(columns=['Season_Year'])\nmaster_player_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"428ac24a346203a6d8323d452f728bd484c36028"},"cell_type":"code","source":"# Check summary counts to see if any are needed\nprint(video_review_df['Player_Activity_Derived'].value_counts())\nprint('---')\nprint(video_review_df['Turnover_Related'].value_counts())\nprint('---')\nprint(video_review_df['Primary_Impact_Type'].value_counts())\nprint('---')\nprint(video_review_df['Primary_Partner_Activity_Derived'].value_counts())\nprint('---')\nprint(video_review_df['Friendly_Fire'].value_counts())\n\nwhere_condition = ((video_review_df['Primary_Partner_GSISID'] == 'Unclear') |\n                   (video_review_df['Primary_Partner_GSISID'].isna()))\nvideo_review_df[where_condition]","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"6b6d91a016a7d90be2716a6e505a1f26cc27836b"},"cell_type":"markdown","source":"### <a id=\"oocd\">Observations of concussion dataset</a>\n- So we know things involve tackling and blocking (you need contact of some sort to have a concussion)\n- We see no plays resulted in a turnover.\n- We can see the majority of concussions are related to a helmet hitting some other mass minus the one 'unclear' instance\n- Majority of concussions are not a result of friendly fire\n- There are 4 plays that have no designated primary_partner: two were helmet-to-ground, 1 was unclear, and 1 was helmet to helmet (H2H).\n    - If you review those two latter videos, the 'unclear' designation was a clear helmet to the back of the head of #44 by Houston Player #55/56 (not sure of number; could probably verify with GSISID from NGS data for the play) and for the H2H you can see Dolphins #34 make a diving head tackle on Baltimore #21.\n        - This information you can deduce by looking at the play video"},{"metadata":{"trusted":true,"_uuid":"6f5d2f55889be1b374750393927b19dfcfaa1219"},"cell_type":"code","source":"# Now that we have a descriptive idea of what's going on, I'm gonna just drop these columns\n# and also clear up the unclear designation and convert it to NaN\ndroppers = ['Player_Activity_Derived', 'Turnover_Related', 'Primary_Partner_Activity_Derived', \n            'Friendly_Fire', 'Season_Year']\nvideo_review_df.drop(columns=droppers, inplace=True)\n\n# Remove 'Unclear' designation\nvideo_review_df.loc[33, 'Primary_Partner_GSISID'] = 'NaN'","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"4006b6d64b01f718617904c2db4ce52f61f0e0e9"},"cell_type":"markdown","source":"#### VIDEO FOOTAGE"},{"metadata":{"trusted":true,"_uuid":"50ba35c490bc686e4594ebefd7f12fc420d1cd53"},"cell_type":"code","source":"'''Concussion Video Footage'''\nvideo_footage_injury_df = pd.read_csv('../input/NFL-Punt-Analytics-Competition/video_footage-injury.csv')\nprint(video_footage_injury_df.shape)\nvideo_footage_injury_df.head(1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"551d181b9a3b6081a50eeebfe9142771269f9a92"},"cell_type":"code","source":"# Preprocess to allow for easier joins\nrename_columns = {'gamekey': 'GameKey', 'playid': 'PlayID', 'season': 'Season_Year'}\nvideo_footage_injury_df.rename(columns=rename_columns, inplace=True)\n\n# Combine Video Review and Video Injury DataFrames to have the injured player and partner player data\ninjury_play = pd.merge(video_review_df, video_footage_injury_df, \n                       how='inner', \n                       on=['GameKey', 'PlayID'])\n\n# Lets Drop Some More Data I consider uncritical for getting a feel for the data\ndroppers = ['Season_Year', 'Type', 'Week', 'Home_team', 'Visit_Team', 'Qtr']\ninjury_play.drop(columns=droppers, inplace=True)\n\ninjury_play.head(1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"b54c75f29d05fe2c50d69d46786d4a9bbbf22be4"},"cell_type":"code","source":"# Join Info (their jersey number, position, role) on the concussed player\ninjury_play = pd.merge(injury_play, master_player_df,\n                           how='inner',\n                           on=['GSISID', 'GameKey', 'PlayID'])\n\nprint('Shape:', injury_play.shape)\ninjury_play.head(1)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"bc790b44fe5cefd75674c428043dac447ad28737"},"cell_type":"markdown","source":"I get the concussed player's information to identify their jersey number in the video. With potentially multiple jersey numbers associated with a player, this was needed information to focus on what events led up to the concussion. I watched the film and cross referenced plays where it was unclear as to the players number with game recaps (googled) to identify the concussed players number."},{"metadata":{"trusted":true,"_uuid":"e94c93525851e005165a13eb3fdc4c072baebdbc"},"cell_type":"code","source":"# Drop Certain Rows After Identifying Concussed Players Jersey Number from video\ndrop_rows = [1, 7, 17, 20, 23, 26, 27, 28, 29, 32, 33, 36, 38, 39, 45, 46, 47, 50, 52, 55, 57]\ninjury_play.drop(labels=drop_rows, inplace=True)\ninjury_play.reset_index(drop=True, inplace=True)\n\n# Convert Primary_Partner_GSISID from str to float\ninjury_play['Primary_Partner_GSISID'] = injury_play['Primary_Partner_GSISID'].astype('float')\ninjury_play.shape","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"4e56a139a5231b20d4b470604b3b10a900b7a609"},"cell_type":"markdown","source":"So from this point I view the films and begin noting features I found important for my analysis or that I thought were indirecty/directly related to a concussion event.\n\n### <a id=\"oocf\">Observations of concussion film</a>\n- Many concussions look to be very bang-bang in nature, that is a **play progresses quickly from one impact to another**\n- I noticed that many players who were concussed were in situations where they had **little to no time to anticipate or brace for a particular impact**\n- I also noticed that the result of some concussions were aided by some **illegal action** (blindside block/tackle on defenseless player, illegal block above the waist, low block) that was not flagged for a particular play \n    - **It's also very notable that every one of these uncalled penalties was executed by the return team**\n    - This is all from my un-expert opinion and I will be labeling some plays based on illegal actions in subsequent analysis\n- The new (2018) **use-of-helmet rule** would apply to many of the plays in this dataset so I also label these cases\n    - https://operations.nfl.com/the-rules/2018-rules-changes-and-points-of-emphasis/\n    - https://operations.nfl.com/the-rules/nfl-video-rulebook/use-of-the-helmet/\n- The vast majority of concussions are occuring after the ball is punted and after the ball is received"},{"metadata":{"_uuid":"f36f7bcb0032d026d1c9607dce980fe37cc1046b"},"cell_type":"markdown","source":"After viewing the the concussion films multiple times, I hand labeled and categorized the films based on specific film attributes from my observations above."},{"metadata":{"trusted":true,"_uuid":"49b5c6e9a61e4e97adb4c71f1b70aa4bc318a491"},"cell_type":"code","source":"# 0 = No, 1 = Yes, 2 = inconclusive\n# When concussions are occuring\nafter_ball_punted = [1,1,1,1,1,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1]\nafter_punt_received = [1,0,1,1,1,0,1,1,1,0,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1]\nfake = [0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]\n\n# My un-expert labeling for uncalled penalties and where the new use-of-helmet rule would apply\nuncalled_penalty = [2,0,2,0,1,0,0,1,0,1,1,0,0,0,0,0,1,2,0,0,1,1,2,1,0,0,0,1,0,0,0,0,0,0,0,0,0]\nhelmet_rule = [1,1,1,1,0,0,0,0,1,0,1,1,1,0,0,1,2,0,1,1,1,0,1,0,0,0,0,0,0,1,1,1,1,1,0,1,0]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"1db583ca21bea4bb311bf83728596a35c38dd7d3"},"cell_type":"code","source":"# Add one hot encodings\ninjury_play['after_ball_punted'] = after_ball_punted\ninjury_play['after_punt_received'] = after_punt_received\ninjury_play['fake'] = fake\ninjury_play['uncalled_penalty'] = uncalled_penalty\ninjury_play['helmet_rule'] = helmet_rule\ninjury_play.to_csv('injury_play.csv', index=False)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"20e8f19129511ed4d1a879ee2c20237db89b8121"},"cell_type":"markdown","source":"Created **'injury_play.csv'** to be used in notebook: https://www.kaggle.com/jdemeo/analysis-uncalled-penalties"},{"metadata":{"_uuid":"0afea31aa2479aff105ed7bea6740c0d0312ec08"},"cell_type":"markdown","source":"---\n### <a id=\"cbtp\">Concussions before the punt</a>\nI wanted to add explicitly that I do not address the concussions **(4 of 37)**  that were at the line of scrimmage or of the fake pyend beyond my statements below:\n- **(60, 905)**: The bull rush results in H2H contact, where the punt team PRW leads with his helmet, the new **use-of-helmet rule** may apply in this scenario.\n- **(274, 3609)**: The fake punt doesn't seem preventable and was an unfortunate circumstance\n- **(280, 2918)**: The punt team PLW is either slipping and hits his helmet into knee of return team player or is executing an illegal low block. I think he is more slipping than anything and is an unfortunate circumstance.\n- **(607, 978)**: Return team player has slight lead with his helmet onto the punt teams PLW's helmet. I wouldn't say the use-of-helmet applies because the return team player leads with his hands. Again this is an unfortunate circumstance and I don't think is preventable.\n- **As a suggestion, it should be emphasized to players in the tackle box to make better use of leading with their hands on punt plays**\n    - Just an aside, It would be interesting to see how often players are banging heads on such plays relative to normal offensive plays.\n---"},{"metadata":{"trusted":true,"_uuid":"58a132630e0da92b2057a20dd8c4810bf385d270"},"cell_type":"code","source":"columns_of_interest = ['after_punt_received', 'uncalled_penalty', 'helmet_rule']\nfor column in columns_of_interest:\n    print(injury_play[injury_play['after_ball_punted'] == 1][column].value_counts())\n    print('---')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"1dccabf125570a3283651ac296f923e377e7e504"},"cell_type":"markdown","source":"### <a id=\"oocatp\">Observations of concussions after the punt:</a>\nAll future analysis focuses on concussions that occured after the punt **(33 of 37)**\n- Notable features\n    - Only **2** of these punt plays had a concussion occur before the punt was received\n    - There were **9 uncalled penalties, all of which were executed by the return team** that resulted in a players concussion\n        - **4 plays** were inconclusive because the video does not show enough of the play to come to a conclusion\n    - There were **18** punts where the new **use-of-helmet rule** would likely apply\n \n### <a id=\"bdrc\">Behavioral Deterrent rule changes</a>\nOf these features, one area I focused attention was on the **uncalled penalties** that directly resulted in a players concussion. It is important to reduce unnecessary and illegal behavior that may be directly related to a concussion event or any injury related play. Any prevention of concussions will have to be behavioral (change to how a player plays) or a restructuring of a particular play (touchback rules, allowable distances between players, etc.). The below two proposed rules aim to be deterrants for unnecessary behavior. I reference my notebook here (https://www.kaggle.com/jdemeo/analysis-uncalled-penalties) for my reasoning of the below two rules as well as a short discussion of their potential impact to the game:\n1. **Allowance of video review of plays where an injury occurs and to allow for subsequent calls of penalties directly related to the injury to be called. If injury isn't apparent immediately and a penalty is thus an unsuitable deterant, a fine may be incurred on the perpetrater of the penalty after a confirmation later by video review (potential overlap with Rule 17 Section 2 Article 3).**\n2. **Expanding the defenseless player status to punt team players who are within 5 yards of the PR and are running towards their own endzone (this would be similar to the afforded protection to the punter, Rule 12 Section 2 Article 7-a-8)**\n---"},{"metadata":{"_uuid":"439b42468823ab741a2b6a5c46016a59e226fdbf"},"cell_type":"markdown","source":"### <a id=\"scttpr\">Structural Change to the Punt Return</a>\nIn a previous notebook (https://www.kaggle.com/jdemeo/nfl-concussion-video-analysis) I looked at the speed of the concussed player and the primary partner involved in the concussion, to get a better idea of the speeds at which concussions occurred. Players were at times reaching speeds faster than the average speeds of the top 40-yard dash runners. Also a number of the concussions were occurring very close the PR. From the gist of that, I wanted to consider ways to reduce the speeds at which players could potentially be playing at in hopes that could reduce high speed collisions as well as allow for more time for a players decision making and anticipation of how a play will unfold especially for the punt receiver. \n\nAs for a fun hypothetical I considered the Canadian Football Leagues rules for punt returns. In the CFL, essentially a PR is given a 5 yard buffer (as long as the punt is still in the air) against any opponent player to allow for receiving the punt or else a \"no yards\" penalty of 5 or 15 yards is given depending on the circumstance and there is no option for a fair catch (example of penalty: https://www.cfl.ca/2018/06/20/football-operations-video-series-week-1-no-yards-interference/). As, I looked at more film of this type of penalty and looked deeper into how such a rule would hypothetically affect concussion related punts, punt returns, and fair catches over the 2016 and 2017 NFL seasons, it became more reasonable to me to make a proposal. The proposed rule change:\n\n3. **Restricted zone of 8 yards (yardline distance) for PR during play.**\n    - Return and punt team players may not be within 8 yards of the PR (by yardline distance) during a punt play unless the PR has signaled for a fair catch, the ball has been caught/muffed by the PR, or the ball has hit the ground.\n    - **Penalty**: 10 yards from the PR and the play is called dead when infraction occurs.\n    - The fair catch rule could also be removed similar to CFL punt return rules to increase the number of returns, but I believe the number instances where a fair catch would be called is going to be severely reduced because of the restricted zones reward proposition for the punt returner.\n    \n<img src=\"https://i.imgur.com/NfkRtPP.png\"/>"},{"metadata":{"_uuid":"d7ea46c9901078edd21e444ff29dce0303766d64"},"cell_type":"markdown","source":"Now how did I come to this reasoning? I particularly look at the proximity of the **closest punt team player to the PR at the time of a particular event** (most often when the punt is received; varies with regards to the concussion data). Looking at this attribute for concussion plays, fair catches, and returned punts gives me a framework for considering the affect of a restricted zone. Essentially I am looking to see well, if this restricted zone existed, how many concussion related plays would have been affected by this rule and possibly have negated the concussion. Let's say we want to cover half of those concussions in hopes to prevent them by this restructuring of the punt play. Well given that restricted zone distance how many punt returns would have been negated? What was the average yardage gained for punt returns that would be negated? Is the penalty then more favorable then that yardage gained? What's the average yardage gained for returns that are not affected by this penalty? Does the penalty balance the risk reward for the punt team and return team? Would fair catches still be called?\n\nI know alot of questions, but this was my thought process and my notebooks are a bit spread out so I wanted to present these questions and answer them directly below. You can go through my notebooks to see additional reasoning for an 8 yard restricted zone for the PR, but hopefully I can sufficiently distill my analysis and conclusions here.\n\nQuick notes: \n- **yardline distance** is the distance between two players by the yardline they are at (ex: player_1 is at 10 yardline, player_2 is at 15 yardline, yardline_distance = 5)\n- **Euclidean distance** is the shortest path distance between the two players\n- **All proximities are that of the closest punt team player to the punt receiver at time of catch**\n- **Reward** is a proportional metric defined as: number of yards gained on the return / total yards needed for touchdown\n    - This was calculated to have a standardized value for the value of a return\n---"},{"metadata":{"_uuid":"4dba1c0b736f44c383bfab384f67bbddc11db445"},"cell_type":"markdown","source":"### <a id='PR'>PR Restricted Zone Analysis</a>\nSo that you don't have to get to the end of this notebook, I am gonna tell you that on average **concussions are occurring on plays where the closest punt team player is within 8.1 yards in the x-direction from the PR or 9.7 yards in euclidean distance from the PR at the time the ball has landed after the punt** ([skip to histogram](#concussion_proximity)). So if we run with the hypothetical and want to cover half of the concussion plays we'd need a restricted zone of 8 yards by yardline distance and 10 yards by Euclidean distance to cover 17 of 33 concussions.\n\nGiven this restricted zone distance how many punt returns would have been negated? What was the average yardage gained for punt returns that would be negated? Is the penalty then more favorable then that yardage gained? \n\nAnalysis of punt returns: https://www.kaggle.com/jdemeo/analysis-punt-returns\n\n- Summary\n    - **2604** punt returns were analyzed\n    - **Average proximity by yardline distance: 9.17 yards**\n    - **Average proximity by Euclidean distance: 11.04 yards**\n    - Hypothetical Restricted Zones:\n        - Given that the closest punt team player is less than or equal to 8 yards (yardline distance) from PR at time of catch:\n            - Count: **1216**\n            - **Average yards gained by PR on play: 8.01 yards**\n            - **Average reward by PR on play: 0.11**\n        - Given all punt team players are greater than 8 yards (yardline distance) from PR at time of catch:\n            - Count: **1388**\n            - **Average yards gained by PR on play: 11.71 yards**\n            - **Average reward by PR on play: 0.15**\n        - So we are looking at an average difference of 3.70 yards between the two distributions.\n        - Given that the closest punt team player is less than or equal to 10 yards (Euclidean distance) from PR at time of catch:\n            - Count: **1205**\n            - **Average yards gained by PR on play: 7.91 yards**\n            - **Average reward by PR on play: 0.11**\n        - Given all punt team players are greater than 8 yards (Euclidean distance) from PR at time of catch:\n            - Count: **1399**\n            - **Average yards gained by PR on play: 11.76 yards**\n            - **Average reward by PR on play: 0.16**\n        - So we are looking at an average difference of 3.85 yards between the two distributions.\n        - I present two hypothetical restricted zone distances to only show that they are nearly equivalent. It's just much easier to know 8 yards by yardline distance then 10 yards by Euclidean distance because of the fields painted yardline markers.\n    - **So intuitively the further the closest punt team player is from the PR at the time of the catch, the higher the mean return distance and 'reward' value for return.**"},{"metadata":{"_uuid":"830c6cd9276cf0f05b41372e01aa60401fc47404"},"cell_type":"markdown","source":"Now why a restricted zone distance of 10 yards (Euclidean) or 8 yards (yardline)? Well if you plot the difference between means of each pair of distributions given a restricted zone distance you reach a minimum around 8-10 yards by Euclidean distance. This esssentially says that a restricted zone of greater size than this does not change the difference of average yards gained between the distributions. So by choosing a restricted zone of this size, we are able to affect or hopefully prevent at least half of the concussion events (as those plays would end up being called dead by penalty or the play would slow down from the restricted zone) and the restricted zone is at about the size where the difference in returns from plays where the penalty would be called and not be called would no longer benefit from a larger restricted zone with respect to yards gained on return .\n\n<img src=\"https://i.imgur.com/rBITQx3.png\"/>\n    \n    \nNow we make the assumption well what happens if we implemented this rule, what affect does it have on the data thats given for the two seasons. This can give us an idea of gains and losses to the game we would have. \n\n---"},{"metadata":{"_uuid":"63bff0718e0b067040ee34b5e5ea40646f8baa7b"},"cell_type":"markdown","source":"Players who are given essentially a buffer of at least 8 yards (yardline) at time of catch are gaining on average **3.7 more yards** than folk who have at least one punt team player in this buffer zone. But under the proposed rule, we'd be **flagging/negating 1216 punt returns**. Man the punt got real boring... But the return team would be gaining on average **2.0 more yards on those particular events** (10 yard penalty - 8.01 yard return average) so I guess that's exciting depending what team you're rooting for. How are we going to make up losing this many plays? Well let's look at the fair catch. \n- **You could also just stop here if you wanted to reduce the number of punt returns to prevent concussions on the punt. Although, this 1216 flagged number would decrease significantly as players would learn to not incur this penalty at the assumed 100% frequency.**\n\nAnalysis of fair catches: https://www.kaggle.com/jdemeo/analysis-fair-catches\n- Don't forget: All proximities are that of the closest punt team player to the punt receiver at time of catch\n- Summary:\n    - **1637** fair catches were analyzed\n    - Average proximity by yardline distance: 2.72 yards\n    - Average proximity by Euclidean distance: 4.04 yards\n    - **~5% or ~85 of all fair catches** occur when all punt team players are greater than 8 yards by yardline distance and 10 yards by Euclidean distance from the PR\n    - Graphic of punt return and fair catch proximity distributions by yardline distance:\n    \n<img src=\"https://i.imgur.com/0Q40fz9.png\"/>\n    \nSo now the next question is well if this restricted zone existed, how many fair catches would have been actually called? Given that **5% of all fair catches (~85 of 1637) were called outside of the proposed restricted zone** distance, I would suspect that the other 95% would be more likely to try a punt return. Let's just say that **95% chooses a punt return** instead just to stay in line with the the above punt return assumptions and negations. We now have **1552 more punt returns** and still would have 85 fair catches given the proposed rule.\n\nNow we are looking at an increase of **336 more punt returns** (1552 - 1216), which brings the **count of interesting plays to 3316 total (49.6% of plays)**. I believe this is also on the conservative end since we assume punt team players do not change their behavior at all and will incur the restricted zone penalty in the case of punt returns. We were also generous with how many PRs would actually attempt the punt return instead of still taking the fair catch, which would lower the estimated increase in number of punt returns. \n\nSo the game got a bit more interesting and we've **eliminated features of 17 of 33 concussions** that occur after the punt. [If you click on this, you'll jump down to my summary of counts of coverage between the NFL's use-of-helmet rule, uncalled penalty rules, and PR restricted zone rule](#coverage). Essentially those 3 **rules would affect 29 of 33 concussion related plays** by being deterrants to certain actions or eliminating features entirely that are associated with these plays. Thanks for taking the time if you got this far along. I'll have a far more condensed rendition in my summary/presentation slides. **Remember safety first, then teamwork** Cheers! \n\n<img src=\"https://i.imgur.com/mjJ4VZn.png\"/>\n\n---"},{"metadata":{"_uuid":"7bf87d53dda499b51848ef4a77b11da9aa3d5030"},"cell_type":"markdown","source":"The rest of the notebook goes over analysis of the concussion plays with use of the NGS dataset.\n\n### CONCUSSION PLAY ANALYSIS\n- <b>Next Gen Stats</b>: player level data that describes the movement of each player during a play. NGS data is processed by BIOCORE to produce relevant speed and direction data. The NGS data is identified using GameKey, PlayID, and GSISID. Player data for each play is provided as a function of time (Time) for the duration of the play.\n- Players are recorded at every <b>10th of a second or 100 milliseconds</b>\n- Field dimensions: 120 yards by 53.3 yards\n- Speed can be calculated with Time and dis\n- <b>Event</b> records essentially game states ('line_set', 'punt', 'punt_received', etc.)\n- **NGS-concussion.csv** can be obtained from : https://www.kaggle.com/jdemeo/preprocessing-ngs"},{"metadata":{"trusted":true,"_uuid":"e05a34f1ac757f8c5dc8374821489d2d6fd903fd"},"cell_type":"code","source":"# Load in NGS data, player role data, and play info\nngs_concussion = pd.read_csv('../input/ngsconcussion/NGS-concussion.csv')\nplay_player_role_df = pd.read_csv('../input/NFL-Punt-Analytics-Competition/play_player_role_data.csv')\nplay_df = pd.read_csv('../input/NFL-Punt-Analytics-Competition/play_information.csv')\n\n# Merge datasets\nngs_concussion = pd.merge(ngs_concussion, play_player_role_df,\n                  how=\"inner\",\n                  on=['GameKey', 'PlayID', 'GSISID'])\n\nngs_concussion = pd.merge(ngs_concussion, play_df,\n                  how=\"inner\",\n                  on=['GameKey', 'PlayID'])\n\n# Cleanup\nkeepers = ['GameKey', 'PlayID', 'GSISID', 'Time', 'x', 'y', 'dis', 'Event', 'Role', 'PlayDescription']\nngs_concussion = ngs_concussion[keepers]\nngs_ids = ngs_concussion.groupby(['GameKey','PlayID']).size().reset_index().rename(columns={0:'count'})\nprint('Number of unique plays in NGS dataset:', ngs_ids.shape[0])\nngs_concussion.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"3732a9ef0449ab0cf23bf26da7094ccf9e36c911"},"cell_type":"code","source":"# Filter for after_punt concussions\nwhere_condition = (injury_play['after_ball_punted'] == 1)\nafter_punt_df = injury_play[where_condition]\nafter_punt_df.reset_index(inplace=True, drop=True)\nafter_punt_ids = after_punt_df.groupby(['GameKey','PlayID']).size().reset_index().rename(columns={0:'count'})\n\n# Get appropriate NGS data\nngs_after_punt = pd.merge(ngs_concussion, after_punt_ids,\n                          how='inner',\n                          on=['GameKey', 'PlayID'])","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"45d8fcbe3b127243f4d5484269460f393ab10251"},"cell_type":"markdown","source":"### Calculating Proximity to PR upon Catch"},{"metadata":{"trusted":true,"_uuid":"a5b67459df13a6abc42fc9c29999e5335a5eda10"},"cell_type":"code","source":"'''ONLY RUN THE FOLLOWING TWO BLOCKS TO GET AN IDEA OF THE COURSE OF EVENTS FOR A PARTICULAR PLAY'''\n\n# def isolate_play(df, game_key, play_id):\n#     '''Create a dataframe of a particular play'''\n#     where_condition = ((df['GameKey'] == game_key) &\n#                        (df['PlayID'] == play_id))\n#     new_df = df[where_condition].copy()\n#     new_df.sort_values(by=['Time'], inplace=True)\n#     new_df.reset_index(drop=True, inplace=True)\n#     return new_df\n\n# def course_of_events(df):\n#     '''Get list of events in order of occurrence for a particular play'''\n#     events = []\n#     for i in range(len(df)):\n#         event = df.loc[i, 'Event']\n#         if event not in events:\n#             events.append(event)\n           \n#     print('Play Description:', df.loc[0, 'PlayDescription'])\n#     print('')\n#     print('Game Events:', events)\n#     print('-----------------------------------------------')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"7ad7116c2e258450a6dd59071c541270545a2ea5"},"cell_type":"code","source":"# # Iterate through ids to get events for each play\n# def whats_going_on(df, ids):\n#     for i in range(len(ids)):\n#         game_key = ids.loc[i, 'GameKey']\n#         play_id = ids.loc[i, 'PlayID']\n#         print('(', game_key, ',', play_id, ')')\n#         the_play = isolate_play(df, game_key, play_id)\n#         course_of_events(the_play)\n        \n# whats_going_on(ngs_after_punt, after_punt_ids)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"5508b4dcb9856f7ec2b6f097d983b55df2cf7095"},"cell_type":"markdown","source":"- So the events where the ball is caught or hits the turf include: 'punt_received', 'punt_downed', 'kick_received', 'punt_land'\n- We will look at the proximity of punt team players to the PR at these events in the NGS dataset\n    - Proximities include just the yardline distance ('x') or Euclidean distance ('circle')\n        - **When I yardline distance I am talking about the distance from one players position to anothers by the yardline. So if player_1 is at the 10 yardline and player_2 is at the 12 yardline, their x-distance from each other is 2 yards.**\n        - **Actual distance is the euclidean distance (straight-line distance) between player_1 and player_2**\n    - Both are considered because implementing a rule that is based on yard distances is easier to gauge when its by the yardline versus the shortest distance between two players"},{"metadata":{"trusted":true,"_uuid":"594b4fd335210a4a5f6335e0ba7d59674a871400"},"cell_type":"code","source":"def event_df_creation(df, event):\n    '''Get a new dataframe with data pertinent to a particular event'''\n    new_df = df[df['Event'] == event].reset_index(drop=True)\n    unique_ids = new_df.groupby(['GameKey','PlayID']).size().reset_index().rename(columns={0:'count'})\n    return new_df, unique_ids","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"fa983e760beb107199c6c111ea6c9f90cdb78319"},"cell_type":"code","source":"# Let's indicate what team the player is playing on based off player role\nreturn_team_positions = ['PR', 'PDL1', 'PDL2', 'PDL3', 'PDL4', 'PDR1', 'PDR2', 'PDR3', 'PDR4', 'VL', 'VR', \n                         'PLL', 'PLR', 'VRo', 'VRi', 'VLi', 'VLo', 'PLM', 'PLR1', 'PLR2', 'PLL1', 'PLL2',\n                         'PFB', 'PDL5', 'PDR5', 'PDL6', 'PLR3', 'PLL3', 'PDR6', 'PLM1', 'PDM']\npunt_team_positions = ['P', 'PLS', 'PPR', 'PLG', 'PRG', 'PLT', 'PRT', 'PLW', 'PRW', 'GL', 'GR',\n                       'GRo', 'GRi', 'GLi', 'GLo', 'PC', 'PPRo', 'PPRi', 'PPL', 'PPLi', 'PPLo']\n\ndef label_team(df):\n    '''Label each player by the team they play on'''\n    df['team'] = ''\n\n    for i, role in enumerate(df['Role']):\n#         print(i, role)\n        if role in return_team_positions:\n            df.loc[i, 'team'] = 'return team'\n        elif role in punt_team_positions:\n            df.loc[i, 'team'] = 'punt team'\n        else:\n            df.loc[i, 'team'] = 'unknown'","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"d3078f06f5ce9072a3082c710fe04a782b1b7db1"},"cell_type":"code","source":"def calculate_player_proximity(role_x, role_y, player_x, player_y):\n    '''Calculate distance of a player to a particular role'''\n    leg_x = (role_x - player_x) ** 2\n    leg_y = (role_y - player_y) ** 2\n    hypotenuse = np.sqrt(leg_x + leg_y)\n    return hypotenuse","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"e0a8e67e1b1bdc1422807f309dd7dc3bee77322a"},"cell_type":"code","source":"def calculate_x_proximity(role_x, player_x):\n    '''Calculate distance of a player to a particular role only by yardline'''\n    return np.abs(role_x - player_x)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"b2032dbd95bde167c9fc0f94126eb5b1f8047b5f"},"cell_type":"code","source":"def calculate_proximity_for_play(df, unique_ids, role):\n    '''Calculate proximity of each player to the player of a particular role'''\n    # Create feature for player proximity\n    df['proximity_to_' + role + '_circle'] = 0\n    df['proximity_to_' + role + '_x'] = 0\n    \n    # Go through each data point in particular NGS dataset\n    for i in range(len(df)):\n#         print(i)\n        \n        # Play Information\n        game_key = df.loc[i, 'GameKey']\n        play_id = df.loc[i, 'PlayID']\n        \n        # Get one unique set of data points related to a single (GameKey, PlayID) pair\n        where_condition = ((df['GameKey'] == game_key) &\\\n                           (df['PlayID'] == play_id))\n        just_view = df[where_condition].reset_index()\n        \n        # Get coordinates of a player with a particular role\n        if any(just_view['Role'] == role):\n            role_x = just_view.loc[just_view['Role'] == role, 'x'].values[0]\n            role_y = just_view.loc[just_view['Role'] == role, 'y'].values[0]\n            \n        # Plays that don't actually have the particular role represented\n        else:\n            continue\n\n        # Current Player coordinates\n        position_x = df.loc[i, 'x']\n        position_y = df.loc[i, 'y']\n\n        # Calculate proximity\n        proximity_hypo = calculate_player_proximity(role_x, role_y, position_x, position_y)\n        proximity_x = calculate_x_proximity(role_x, position_x)\n        df.loc[i, 'proximity_to_' + role + '_circle'] = proximity_hypo\n        df.loc[i, 'proximity_to_' + role + '_x'] = proximity_x\n        \n    # Calculate closest player (adds to ids dataframe)\n    calculate_closest_player(df, unique_ids, 'proximity_to_' + role + '_circle')\n    calculate_closest_player(df, unique_ids, 'proximity_to_' + role + '_x')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"543c4cd2e4ecb8bbd3efa82f0b75c2d4290cd3f1"},"cell_type":"code","source":"def calculate_closest_player(df, unique_ids, column):\n    '''Find who the closest player on the punt team and create new id set'''\n    unique_ids[column] = 0\n    good_indexes = []\n    \n    for i in range(len(unique_ids)):\n#         print(i)\n        \n        # Play information\n        game_key = unique_ids.loc[i, 'GameKey']\n        play_id = unique_ids.loc[i, 'PlayID']\n\n        # Get one unique set of data points related to a single (GameKey, PlayID) pair\n        where_condition = ((df['GameKey'] == game_key) &\\\n                           (df['PlayID'] == play_id) &\\\n                           (df['team'] == 'punt team'))\n        just_view = df[where_condition].reset_index(drop=True)\n        \n        # Take minimum of series and Error handling where the NGS data had no punt team :(\n        try:\n            unique_ids.loc[i, column] = min(just_view[column])\n            good_indexes.append(i)\n        except ValueError:\n            continue","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"a49510c3a1f6a897417d1f014c542c719dfae054"},"cell_type":"code","source":"def all_event_proximities(ngs_df, events):\n    data_frames = []\n    \n    for event in events:\n        event_df, event_ids = event_df_creation(ngs_df, event)\n        label_team(event_df)\n        calculate_proximity_for_play(event_df, event_ids, 'PR')\n        data_frames.append(event_ids)\n        \n    # Combine ids of particular play events\n    new_ids = pd.concat(data_frames,\n                        axis=0)\n    new_ids.reset_index(inplace=True, drop=True)\n    \n    return new_ids","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"bd69ca6c0cf88bc513e93f16fb9274e408be41c1"},"cell_type":"code","source":"# Events of interest\nevents = ['punt_received', 'punt_downed', 'kick_received', 'punt_land']\n\nnew_ids = all_event_proximities(ngs_after_punt, events)\nnew_ids.head()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"02f82fb3c205b373c673ba32a991a1b8d361a473"},"cell_type":"markdown","source":"- We now have the closest proximity of a punt player to the PR when the ball is caught, downed, or lands so let's look at the distribution of this set.\n- Remember: x = yardline distance, circle = euclidean distance"},{"metadata":{"trusted":true,"_uuid":"d6ac42ccf5c62325e46e81462972bdddcacd4ddb"},"cell_type":"code","source":"'''Plot of distribution distance of closest player to punt receiver'''\ndef proximity_distribution(df, column):\n    bins = [i for i in range(0, 30, 1)]\n    plt.hist(df[column], bins=bins)\n\n    plt.title(column)\n    plt.xlabel('Yards')\n    plt.ylabel('count')\n    plt.show()\n\n    print(new_ids[column].describe())","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"ef52b02918fd5f8127496ea77d00e1c52ef625ba"},"cell_type":"markdown","source":"[Back to PR Restricted Zone Analysis](#PR)\n#### <a id='concussion_proximity'>Concussion Proximity</a>"},{"metadata":{"trusted":true,"_uuid":"2f656ce58248ac0931d151eed62ee8a365ed3087"},"cell_type":"code","source":"proximity_distribution(new_ids, 'proximity_to_PR_circle')\nproximity_distribution(new_ids, 'proximity_to_PR_x')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"7443b8dd0df249ca57c5043ec74ea8fbc4f9ea18"},"cell_type":"code","source":"# Number of concussion plays affected by a rule with 8 yard restricted zone for PR by yardline distance\nprint(new_ids[new_ids['proximity_to_PR_x'] <= 8].shape[0])\n\n# Number of concussion plays affected by a rule with 10 yard restricted zone for PR by euclidean distance to PR\nprint(new_ids[new_ids['proximity_to_PR_circle'] <= 10].shape[0])","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"73cc3f2a6a105ee163e1dde98885cb6cda09fc65"},"cell_type":"markdown","source":"- We can see on average **concussions are occurring on plays where the closest punt team player is within 8.1 yards in the x-direction from the PR or 9.7 yards in euclidean distance from the PR at the time the ball has landed after the punt.**\n- Obviously this isn't a normal distribution, but we can see that more than half of concussions are occuring with a yardline distance less than 8 yards (17 of 33) or less than 10 yards euclidean distance (17 or 33) when this type of feature is present during the punt play.\n- I believe this restricted zone could have helped to prevent these concussions, by forcing players to slow their pursuit of the PR and be more aware of their positioning relative to the PR.\n- I also must note that many of the concussions could have potentially been prevented by the implementation of the new 'use of helmet' rule. You may have noticed I had one-hot encoded the concussion plays where I believe the 'use of helmet' rule could have changed the behavior of the player invovled in the concussion.\n    - https://operations.nfl.com/the-rules/2018-rules-changes-and-points-of-emphasis/\n    - https://operations.nfl.com/the-rules/nfl-video-rulebook/use-of-the-helmet/\n- The below script is to just find where the PR restricted zone and helmet rule would have overlapped to understand the 'coverage' of these rules for the concussions involved in this dataset."},{"metadata":{"_uuid":"6213d5e69bec8625ed29a642c38d32623e3b82cb"},"cell_type":"markdown","source":"## <a id=\"coverage\">Overall Rule Coverage of Concussion Related Plays</a>"},{"metadata":{"trusted":true,"_uuid":"99f805084dbabe0a8cf87cbceade48fe5ca60f0e"},"cell_type":"code","source":"# Combine with injury_play data to get one-hot encoding labels\ncombo = pd.merge(new_ids, after_punt_df,\n                 how=\"inner\",\n                 on=['GameKey', 'PlayID'])\n\n# Get Number of concussions that a particular rule may pertain too\nprint('Concussion Coverage by Particular Rules')\nprint('---------------------------------------')\nprint('Count of use-of-helmet rule:', combo[combo['helmet_rule'] == 1].shape[0])\nprint('Count of uncalled penalty rules:', combo[combo['uncalled_penalty'] == 1].shape[0])\nprint('Count of PR restricted zone rule:', combo[combo['proximity_to_PR_x'] <= 8].shape[0])\n\n# Get overlap of rule counts\nwhere_condition_1 = ((combo['helmet_rule'] == 1) &\n                    (combo['uncalled_penalty'] == 1))\nwhere_condition_2 = ((combo['helmet_rule'] == 1) &\n                    (combo['proximity_to_PR_x'] <= 8))\nwhere_condition_3 = ((combo['uncalled_penalty'] == 1) &\n                    (combo['proximity_to_PR_x'] <= 8))\nwhere_condition_4 = ((combo['proximity_to_PR_x'] <= 8) &\n                    (combo['helmet_rule'] == 1) & \n                    (combo['uncalled_penalty'] == 1))\n\nprint('Count for use-of-helmet and uncalled penalty rules:', combo[where_condition_1].shape[0])\nprint('Count for use-of-helmet and PR restricted zone rules:', combo[where_condition_2].shape[0])\nprint('Count for uncalled penalty and PR restricted zone rules:', combo[where_condition_3].shape[0])\nprint('Count for use-of-helmet, uncalled penalty, and PR restricted zone rules:', combo[where_condition_4].shape[0])","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"e7f485ab0bac9bda2202cc73ceb88dd51367d09e"},"cell_type":"markdown","source":"- Total concussions occuring after the punt: 33\n- These threes rules cover <b>29 of 33</b> concussions that occur after the punt"},{"metadata":{"trusted":true,"_uuid":"7fcb005ce8bcfcf3cf428ee80ac114b7ed1641ae"},"cell_type":"code","source":"from matplotlib_venn import venn3, venn3_circles","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"17bacbadf304d661e9898b8d5decfab1530b876a"},"cell_type":"code","source":"# Make the diagram\nplt.figure(figsize=(10, 6))\nlabels = ['Use-of-Helmet Rule', 'Uncalled Penalty Rules', 'PR Restricted Zone Rule']\nv = venn3(subsets = (5, 4, 11, 5, 2, 2, 0), set_labels=labels, )\nc = venn3_circles(subsets = (5, 4, 11, 5, 2, 2, 0), linestyle='dashed')\n\nfor text in v.set_labels:\n    text.set_fontsize(16)\nfor text in v.subset_labels:\n    text.set_fontsize(18)\nplt.title('Rule Coverage of Concussions After the Punt', fontsize=18)\nplt.savefig('venn_diagram.png', bbox_inches='tight')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"a1933c4e2aff7b26eb93147151b360436bc471c1"},"cell_type":"markdown","source":"# <a id=\"lton\">Links to other notebooks</a>:\n- Analysis of uncalled penalties: https://www.kaggle.com/jdemeo/analysis-uncalled-penalties\n- Analysis of punt returns: https://www.kaggle.com/jdemeo/analysis-punt-returns\n- Analysis of fair catches: https://www.kaggle.com/jdemeo/analysis-fair-catches\n- Preprocessing of Play Information: https://www.kaggle.com/jdemeo/preprocessing-punt-play\n- Preprocessing of NGS data for the above notebooks: https://www.kaggle.com/jdemeo/preprocessing-ngs"},{"metadata":{"trusted":false,"_uuid":"e43f5c40e76544ae4d56ca6e265c51d5b0399f03"},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"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}