{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"_kg_hide-output":true},"cell_type":"code","source":"#NFL Punt Analytics\n#By Cory Hofmann\n#Updated 2019-01-09\n\nimport pandas as pd\nimport numpy as np\n\n#NFL provided data\ngame_data = pd.read_csv('game_data.csv')\nall_punts = pd.read_csv('play_information.csv')\nplayer_punt_position = pd.read_csv('play_player_role_data.csv') #Punt-specific position\nplayer_position = pd.read_csv('player_punt_data.csv')\nconcussion_video = pd.read_csv('video_footage-injury.csv')\nconcussion_cause = pd.read_csv('video_review.csv')\n\n#Add punt position for primary and partner to the concussion cause dataset\ncause_w_position = pd.merge(concussion_cause, player_punt_position, on = ['Season_Year','GameKey','PlayID','GSISID'])\n\n#primary partner contains unclear and NaN, needs to be converted to integer\ncause_w_position.Primary_Partner_GSISID = cause_w_position.Primary_Partner_GSISID.replace(['Unclear',np.nan],0)\ncause_w_position.Primary_Partner_GSISID = cause_w_position.Primary_Partner_GSISID.astype(np.int64)\ncause_w_both_positions = pd.merge(cause_w_position, player_punt_position, left_on=['Primary_Partner_GSISID','PlayID','GameKey'], right_on=['GSISID','PlayID','GameKey'], how = 'left', suffixes = ('','_Secondary'))\ndel cause_w_both_positions['Season_Year_Secondary']\ndel cause_w_both_positions['GSISID_Secondary']\n\n#Add play description and some game data\ncause_w_play = pd.merge(cause_w_both_positions, concussion_video[['PlayDescription', 'gamekey', 'playid']], left_on=['GameKey', 'PlayID'], right_on=['gamekey','playid'])\ndel cause_w_play['gamekey']\ndel cause_w_play['playid']\ncause_w_game = pd.merge(cause_w_play, game_data[['GameKey','Game_Day','Season_Type']], on='GameKey')\n\n#What types of punting play result in a concussion?\ncause_w_game.PlayDescription.str.count(\"fair catch\").sum() #2 Fair Catches\ncause_w_game.PlayDescription.str.count(\"out of bounds\").sum() #0 Punts Out of Bounds\ncause_w_game.PlayDescription.str.count(\"downed by\").sum() #3 Downed by Punting team\ncause_w_game.PlayDescription.str.count(\"Touchback\").sum() #0 Touchbacks\n#Almost all verified concussions were during return plays\n\n#Categorize the concussed players, what positions are most common?\ncause_w_game.Role.value_counts() #positions on punting team far more dangerous\ncause_w_game.Role_Secondary.value_counts() #Most often results from contact with punt returner\n\n#Categorize based on position, as per appendix\nPunting_team_pos = ['P','GL','GR','PPR','PC','PLW','PLT','PLG','PLS','PRG','PRT','PRW']\nReturn_team_pos = ['PR','PFB','VRo','VLo','VRi','VR','PDR1','PDR2','PDR3','PDL3','PDL3','PDL2','PDL1','PLR','PLL','PLL1','PLM']\n\n#Determine which team gets most concussions\ncause_w_game['Role_Team'] = np.empty([37,1])\n\nfor i in range(0,len(cause_w_game.Role)):\n   if cause_w_game.Role[i] in Punting_team_pos:\n      cause_w_game.Role_Team[i] = 'Punting Team'\n   elif cause_w_game.Role[i] in Return_team_pos:\n      cause_w_game.Role_Team[i] = 'Return Team'\n   else:\n       cause_w_game.Role_Team[i] = np.nan\n\ncause_w_game['Role_Team_Secondary'] = np.empty([37,1])\n\nfor i in range(0,len(cause_w_game.Role_Secondary)):\n   if cause_w_game.Role_Secondary[i] in Punting_team_pos:\n      cause_w_game['Role_Team_Secondary'][i] = 'Punting Team'\n   elif cause_w_game.Role_Secondary[i] in Return_team_pos:\n      cause_w_game['Role_Team_Secondary'][i] = 'Return Team'\n   else:\n       cause_w_game['Role_Team_Secondary'][i] = np.nan\n       \ncause_w_game.Role_Team.value_counts() #positions on punting team far more dangerous than return team (27 vs 10)\ncause_w_game.Role_Team_Secondary.value_counts() #Closer to even split for the concussion partner (Return team 18, Punting Team 15)\n\n#Descriptive statistics on punting\nlen(all_punts) #6681 Total Unique Punting Plays\nall_punts.GameKey.nunique() #662 Unique Games\nall_punts.PlayDescription.str.count(\"fair catch\").sum() #1659 fair catches\nall_punts.PlayDescription.str.count(\"out of bounds\").sum() #669 punts out of bounds\nall_punts.PlayDescription.str.count(\"PENALTY\").sum() #1078 penalties\nall_punts.PlayDescription.str.count(\"downed by\").sum() #811 downed by punting team\nall_punts.PlayDescription.str.count(\"Touchback\").sum() #408 Touchbacks\nall_punts.PlayDescription.str.count(\"TOUCHDOWN\").sum() #54 Touchdowns, but how many returns vs. other?\nall_punts.PlayDescription.str.count(\"BLOCKED\").sum() #29 Plays involved a block\n\ntest1 = all_punts.PlayDescription.str.contains('BLOCKED')\ntest2 = all_punts.PlayDescription.str.contains('TOUCHDOWN')\nsum(test1 & test2) \n#9 Touchdowns resulting from blocks, thus 45 were returned for TD (0.67%)\n\n#Supplemental Data from footballdb.com, all punt returns and kickoff returns in regular season!\n#This will be used to show average punt return distance\n#This will also be used to demonstrate that kickoffs are occuring less frequently due to recent rule changes\npunt_ret_2016 = pd.read_csv('punt_ret_2016.csv')\npunt_ret_2017 = pd.read_csv('punt_ret_2017.csv')\nkickoff_ret_2010 = pd.read_csv('kickoff_ret_2010.csv') #in 2011, kickoff moved forward 5 yards\nkickoff_ret_2015 = pd.read_csv('kickoff_ret_2015.csv') #before 2015 rule changes made\nkickoff_ret_2018 = pd.read_csv('kickoff_ret_2018.csv') #before 2018 rule changes made\n\n#Add column describing return ratio for returners (Number returns vs. Fair Catches)\npunt_ret_2016['return_percent'] = punt_ret_2016.Num / (punt_ret_2016.Num + punt_ret_2016.FC)\npunt_ret_2017['return_percent'] = punt_ret_2017.Num / (punt_ret_2017.Num + punt_ret_2017.FC)\n\n#Let's see some information regarding an 'average' punt return using supplemental data\n#Rather than attempt to parse PlayDescription, lets use this dataset\nsum(punt_ret_2016.Yds) / sum(punt_ret_2016.Num) #Avg punt return = 8.6 yds\nsum(punt_ret_2017.Yds) / sum(punt_ret_2017.Num) #Avg punt return = 8.2 yds\n\n#Kickoffs may now be safer, but they are happening far less frequently down from 4/game to less than 2/game\n#Major rule changes occured before 2011, 2015, and 2018 seasons\nsum(kickoff_ret_2010.Num) #2033 attempted returns in 512 reg season games\nsum(kickoff_ret_2015.Num) #1080 attempted returns in 512 reg season games\nsum(kickoff_ret_2018.Num) #970 attempted returns in 512 reg season games\n\n#Conclusions:\n#Concussions occur more often during punts with a return\n#Punt team members are at high risk, as they sprint downfield preparing to tackle the punt returner\n#Rule changes to kickoffs have resulted in fewer kickoff returns (and fewer onsides kicks, as per other references)\n#Penalties occur at a high rate -> Additional (potentially complex) rules during punting may increase this!\n","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"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}