{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import os\nfrom collections import namedtuple\nimport pandas as pd\nimport numpy as np\n\n# data visualization modules\nfrom bokeh.io import output_notebook, show, push_notebook\nfrom bokeh.layouts import layout, column, widgetbox\nfrom bokeh.models import ColumnDataSource, CDSView, BooleanFilter, CustomJS\nfrom bokeh.models.glyphs import ImageURL\nfrom bokeh.models.ranges import Range1d\nfrom bokeh.models.widgets import Slider\nfrom bokeh.palettes import Category20\nfrom bokeh.plotting import figure, Figure\nfrom IPython.core.display import display, HTML\nfrom ipywidgets import interact\noutput_notebook()","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"data_files = [file for file in os.listdir('../input/') if file.endswith('.csv') and not file.startswith('NGS')]\ndata_files","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"5dc579559e98e6c1c3e2dc37fdc182f4021779ce"},"cell_type":"markdown","source":"# Player Roles\nIn order to make the most of the player roles that have been provided, I classified each into \"role_groups\" of players with similar starting positions. Since we only have 37 Injuries to choose from, this grouped information will help understand which types of positions are most at-risk of injury.\n\nIn addition, the \"Team\" classifications will be used throughout this analysis."},{"metadata":{"trusted":true,"_uuid":"2c0cd71fa6b88747b701ec1b0b6b470bd9e689c1"},"cell_type":"code","source":"GUNNERS = ['GL', 'GLi', 'GLo', 'GR', 'GRi', 'GRo']\nSHIELD = ['PC', 'PPL', 'PPLi', 'PPLo','PPR', 'PPRi', 'PPRo']\nPUNT_LINE = ['PLG', 'PLS', 'PLT', 'PRG', 'PRT']\nWINGS = ['PLW', 'PRW']\nCOVERAGE_TEAM = GUNNERS + SHIELD + WINGS + PUNT_LINE + ['P']\n\nPUNT_RUSH = ['PDL1', 'PDL2', 'PDL3', 'PDL4', 'PDL5', 'PDL6', 'PDM',\n             'PDR1', 'PDR2', 'PDR3', 'PDR4', 'PDR5', 'PDR6']\nJAMMERS = ['VL', 'VLi', 'VLo', 'VR', 'VRi', 'VRo']\nLINEBACKERS = ['PLL', 'PLL1', 'PLL2', 'PLL3', 'PLM',\n               'PLM1', 'PLR', 'PLR1', 'PLR2', 'PLR3']\nRETURNERS = ['PFB', 'PR']\nRETURN_TEAM = PUNT_RUSH + JAMMERS + LINEBACKERS + RETURNERS\n\n\ndef team_lookup(role):\n    if role in COVERAGE_TEAM:\n        return 'COVERAGE'\n    return 'RETURN'\n\n\ndef role_group_lookup(role):\n    if role in GUNNERS:\n        return 'GUNNER'\n    elif role in SHIELD:\n        return 'SHIELD'\n    elif role in PUNT_LINE:\n        return 'PUNT_LINE'\n    elif role in WINGS:\n        return 'WINGS'\n    elif role in PUNT_RUSH:\n        return 'PUNT_RUSH'\n    elif role in JAMMERS:\n        return 'JAMMERS'\n    elif role in LINEBACKERS:\n        return 'LINEBACKERS'\n    elif role in RETURNERS:\n        return 'RETURNERS'\n    elif role == 'P':\n        return 'PUNTER'\n    else:\n        return 'UNKNOWN ROLE'\n\n\nroles = pd.read_csv('../input/play_player_role_data.csv').drop(columns=['Season_Year'])\nroles['Team'] = roles['Role'].apply(team_lookup)\nroles['Role_Group'] = roles['Role'].apply(role_group_lookup)\nroles.head()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"b00261e9ad8f757a0bc37567dba604c97ce9057f"},"cell_type":"markdown","source":"# Injury Summary\nUsing the role information, and video footage, I create an injury-level table for use throughout the rest of the analysis."},{"metadata":{"trusted":true,"_uuid":"2e39a7ce89910406a9b1fabce489f589f89b06f3"},"cell_type":"code","source":"partner_roles = roles.rename(columns={'Role':'Partner_Role',\n                                      'Team':'Partner_Team',\n                                      'Role_Group':'Partner_Role_Group',\n                                      'GSISID':'Primary_Partner_GSISID'})\ninjury_footage = pd.read_csv('../input/video_footage-injury.csv')[['gamekey','playid','PREVIEW LINK (5000K)']]\\\n                   .rename(columns={\"gamekey\": \"GameKey\",\n                                    \"playid\": \"PlayID\",\n                                    \"PREVIEW LINK (5000K)\": \"Video\"})\n\ninjuries = pd.read_csv('../input/video_review.csv')\ninjuries['Primary_Partner_GSISID'] = injuries['Primary_Partner_GSISID'].replace(['Unclear', None], 0).astype(np.int64)\ninjuries = injuries.drop(['Turnover_Related'], axis=1)\\\n                   .merge(injury_footage, how='left', on=['GameKey','PlayID'], validate='1:1')\\\n                   .merge(roles, how='left', on=['GameKey','PlayID','GSISID'], validate='1:1')\\\n                   .merge(partner_roles, how='left', on=['GameKey','PlayID','Primary_Partner_GSISID'])\ninjuries.head()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"6eb74183a2838337c17e504c85eb792d34cda44f"},"cell_type":"markdown","source":"# Role Injuries\nThe first thing I look at is which roles generate the highest number of injuries. A simple count of injuries grouped by Role_Group suggests that the PUNT_LINE is most susceptible to injury (accounting for 14 total injuries in the data set). However, this ignores that there are more linemen on the field than, say, punt returners.\n\nTo solve for this I join the injury data onto the role data using GameKey, PlayID, and GSISID; aggregating to come up with 3 statistics:\n\n1. Plays Participated - The total number of play-role_group combinations (e.g. 1 play with 5 linemen --> PUNT_LINE: 5)\n2. Injuries - The total number of injuries at the position\n3. Injuries per 10k Plays - The average number of injuries a player at each position should expect, given 10,000 punts\n\nFrom this data, it's clear that the player most at-risk of injury is the return man (or PFB). However, the coverage team accounts for far more injuries than does the return team. In fact, if you exclude the returners, a player from the coverage team is more than 5x more likely to get a concussion."},{"metadata":{"trusted":true,"_uuid":"aab1a472e333d041d5fd855c6e5cdbd4ae8d7d89"},"cell_type":"code","source":"role_group_injuries = roles.merge(injuries[['GameKey','PlayID','GSISID','Player_Activity_Derived']],\n                                     how=\"left\", on=['GameKey','PlayID','GSISID'])\\\n                     .rename(columns={'PlayID': 'Plays Participated',\n                                      'Player_Activity_Derived': 'Injuries'})\\\n                     .groupby('Role_Group')[['Plays Participated','Injuries']]\\\n                     .count()\n\nrole_group_injuries['Injuries per 10k Plays'] = 10000 * role_group_injuries['Injuries'] / role_group_injuries['Plays Participated']\nrole_group_injuries.sort_values('Injuries per 10k Plays', ascending=False, inplace=True)\ndisplay(role_group_injuries)\ndisplay(injuries.groupby('Team')['PlayID'].count())","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8018728ee0015ad548c9925a357402a30994f537"},"cell_type":"markdown","source":"# Jammers\nToo late for me to finish this up, but really interesting data suggesting that while Jammers don't get injured, their presence on the field increases the probability of injury. Likely causing players to make it down the field slower, and therefore increasing the number of returns. Would be interesting further analysis."},{"metadata":{"trusted":true,"_uuid":"9a4aa418c2582684204184e1106332ba8c785d72"},"cell_type":"code","source":"role_injuries = roles.merge(injuries[['GameKey','PlayID','Player_Activity_Derived']],\n                                     how=\"left\", on=['GameKey','PlayID'])\\\n                   .rename(columns={'PlayID': 'Plays Participated',\n                                      'Player_Activity_Derived': 'Injuries'})\\\n                   .groupby('Role')[['Plays Participated','Injuries']]\\\n                   .count()\n\nrole_injuries['Injuries per 10k Plays'] = 10000 * role_injuries['Injuries'] / role_injuries['Plays Participated']\nrole_injuries.sort_values('Injuries per 10k Plays', ascending=False, inplace=True)\ndisplay(role_injuries)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"33165c5d7da6edeb7c1455335105dbd46853bcdf"},"cell_type":"markdown","source":"# Video Review\nNext, I wanted to take a look at the video to identify any clear patterns. In order to do so, I created a little module to pull up information about the injured player, their partner, and a video. As an output, I created my own description of each play."},{"metadata":{"trusted":true,"_uuid":"4ad1393bc903f3a5cad48a65f8cabab12fe70d39"},"cell_type":"code","source":"player_data = pd.read_csv('../input/player_punt_data.csv')\nplayer_data.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"8ac9f586f3f9a76ee6119f905ce328eaa481a333"},"cell_type":"code","source":"injury = injuries.loc[5]\nprint(injury)\nprint('\\n')\nprint(player_data[player_data['GSISID']==injury['GSISID']])\nprint(player_data[player_data['GSISID']==injury['Primary_Partner_GSISID']])\nHTML(\"\"\"\n<video width=\"640\" height=\"480\" controls>\n  <source src=\"{}\" type=\"video/mp4\">\n</video>\n\"\"\".format(injury['Video']))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"662cf251533be6206592e389572ba0ecd6e35570"},"cell_type":"markdown","source":"# Qualitative Analysis\nBased on the video above, we have a couple of trends that affect the coverage team:\n1. Running downfield and tackling, often leading with the crown of the helmet\n2. Stopping, or reversing course upfield (towards where the punt originated), and getting hit by a member of the return team\n3. Wings, often set back 4-5 yards from the line of scrimmage, blocking the punt rush"},{"metadata":{"_uuid":"ac8b1274f87cb038027a3f4cbc10de4880fa8d18"},"cell_type":"markdown","source":"|Num| GAMEKEY | PLAYID | DESCRIPTION OF CONCUSSION |\n| --- | --- | --- | --- |\n| 0 | 5 | 3129 | Tackler, tackling the returner, lead with helmet |\n| 1 | 21 | 2587 | Gunner, blindside block while running downfield |\n| 2 | 29 | 538 | Tackler, tackling the returner, lead with helmet |\n| 3 | 45 | 1212 | Tackler, tackling the returner, lead with helmet |\n| 4 | 54 | 1045 | Coverage stopped, teammate running downfield was blocked into back |\n| 5 | 60 | 905 | Wing, blocking in backfield |\n| 6 | 144 | 2342 | Blocker stopped, coverage running downfield ran through player |\n| 7 | 149 | 3663 | Fullback, hit by member of return team while blocking |\n| 8 | 189 | 3509 | Tackler, tackling the returner, lead with helmet |\n| 9 | 218 | 3468 | ?? Fell to ground |\n| 10 | 231 | 1976 | Coverage reversed upfield, teammate was blocked into player by coverage running downfield |\n| 11 | 234 | 3278 | Tackler, tackling the returner, lead with helmet |\n| 12 | 266 | 2902 | Tackler, tackling the returner, lead with helmet -- completely illegal |\n| 13 | 274 | 3609 | Punter running with the ball, not really relevant |\n| 14 | 280 | 2918 | Wing, blocking in backfield |\n| 15 | 280 | 3746 | Tackler, tackling the returner, lead with helmet |\n| 16 | 281 | 1526 | Can't really tell |\n| 17 | 289 | 2341 | Coverage reversed upfield, blocker running downfield |\n| 18 | 296 | 2667 | Tackler, hit by teammate during tackle |\n| 19 | 257 | 3630 | Tackler, tackling the returner, lead with helmet |\n| 20 | 364 | 2489 | Coverage stopped, blocker running downfield |\n| 21 | 364 | 2489 | Coverage running downfield, blocker blindsided at 90 degrees |\n| 22 | 384 | 183 | Coverage reversed upfield, teammate tackled ballcarrier and followed through |\n| 23 | 392 | 1088 | Coverage reversed upfield, blocker running downfield |\n| 24 | 397 | 1526 | Tackling punt returner |\n| 25 | 399 | 3312 | Returner dove headfirst into tackles |\n| 26 | 414 | 1262 | ??? can't tell |\n| 27 | 448 | 2792 | Coverage reversed upfield, blocker running downfield |\n| 28 | 473 | 2072 | Tackler, hit by teammate during tackle |\n| 29 | 506 | 1988 | Returner tackled, opponent lead with helment |\n| 30 | 553 | 1683 | Coverage reversed upfield, blocker running downfield |\n| 31 | 567 | 1407 | Blocker running downfield, blocked leading with helment |\n| 32 | 585 | 2208 | Returner dove headfirst into tackles |\n| 33 | 585 | 733 | unclear |\n| 34 | 601 | 602 | Tackler, tackling the returner, returner lead with helmet |\n| 35 | 607 | 978 | Wing, blocking in backfield |\n| 36 | 618 | 2792 | Tackler reversed upfield, blocker running downfield |"},{"metadata":{"_uuid":"7ffc3bc58d58922cf9b248a076561d1d7509661f"},"cell_type":"markdown","source":"# Play Summaries\nUsing Next Gen Stat Data, I extract several key moments in the play and use them to determine important information about the play. For example, the location of the Long Snapper at the time of the \"ball_snap\" can be used as the line of scrimmage, and the location of the punter in relation to the Long Snapper can be used to orient the data such that all punts move from x=0 --> x=120.\n\nIn order to prevent fanouts, I am throwing out some data to make things easier. I'm only looking at plays with a single returner. Once I do this, we are still left with 6,443 punts and 36 of 37 injuries."},{"metadata":{"trusted":true,"_uuid":"44645807c81bb6417fc72026665d8c46f0aeb388"},"cell_type":"code","source":"def get_play_summary(ngs_df, role_df):\n    \"\"\"\n    Given NGS Data, and roles from each play collect a summary of\n    interesting statistics about each play\n    \"\"\"\n    Moment = namedtuple('Moment', 'event role')\n\n    INTERESTING_MOMENTS = [\n        Moment('ball_snap', 'PLS'),\n        Moment('punt_received', 'PR'),\n        Moment('fair_catch', 'PR'),\n        Moment('tackle', 'PR'),\n        Moment('out_of_bounds', 'PR'),\n        Moment('punt_downed', 'PR'),\n        Moment('touchback', 'PR'),\n        Moment('touchdown', 'PR'),\n    ]\n\n    # gets the location of a player at the moment of an event\n    def get_player_location(ngs_df, role_df, event, role, *args):\n        \"\"\"\n        Given NGS Data, and roles from each play, identify the location\n        of the player(s) with a given role at a specific event\n        \"\"\"\n        event_df = ngs_df[ngs_df['Event'] == event].copy()\n        event_df.drop_duplicates(subset=['GameKey','PlayID','GSISID'], keep=False, inplace=True)\n        player_df = role_df[role_df['Role'] == role].copy()\n        player_df.drop_duplicates(subset=['GameKey','PlayID','Role'], keep=False, inplace=True)\n        event_locations = event_df.merge(player_df, how='inner', on=['GameKey','PlayID','GSISID'], validate='1:1')\n        return event_locations[['GameKey', 'PlayID', 'Time', 'x', 'y']]\\\n                              .rename(index=str, columns={\"x\": event + \"_x\",\n                                                          \"y\": event + \"_y\",\n                                                          \"Time\": event + \"_time\"})\n\n    # get all punts and initialize the punt_plays DataFrame\n    punt_plays = df = get_player_location(ngs_df, role_df, *Moment('punt', 'P'))\n\n    # for all additional moments, left join onto punt plays\n    for moment in INTERESTING_MOMENTS:\n        df = get_player_location(ngs_df, role_df, *moment)\n        punt_plays = punt_plays.merge(df, how='left', on=['GameKey','PlayID'])\n\n    def orient_field(row):\n        \"\"\"Orient the field so that the punting team is punting from x=0 to x=120\"\"\"\n        if row['punt_x'] > row['ball_snap_x']:\n            for col in row.index:\n                if col.endswith('_x'):\n                    row[col] = 120 - row[col]\n        return row\n    \n    def get_final_event(row):\n        \"\"\"Gets the final event of the play from the following list of alternatives\"\"\"\n        ending_events = ['tackle', 'out_of_bounds', 'punt_downed', 'touchback', 'fair_catch', 'touchdown']\n        for event in ending_events:\n            if not pd.isna(row[event + '_x']):\n                row['final_event'] = event\n        return row\n    \n    punt_plays = punt_plays.apply(orient_field, axis=1)\\\n                           .apply(get_final_event, axis=1)\n\n    punt_plays['hang_time'] = punt_plays['punt_received_time'] - punt_plays['punt_time']\n    punt_plays['hang_time'] = punt_plays['hang_time'].dt.total_seconds()\n\n    return punt_plays\n\ndef get_play_summaries(role_df):\n    play_summaries = pd.DataFrame()\n    for file in os.listdir('../input/'):\n        if file.endswith('.csv') and file.startswith('NGS'):\n            ngs_data = pd.read_csv('../input/' + file)\n            ngs_data['Time'] = pd.to_datetime(ngs_data['Time'])\n            play_summary = get_play_summary(ngs_data, role_df)\n            play_summaries = pd.concat([play_summaries, play_summary], sort=False)\n    play_summaries = play_summaries.merge(injuries[['GameKey','PlayID','Player_Activity_Derived']],\n                                      how='left', on=['GameKey','PlayID'])\n    play_summaries['punt_length'] = play_summaries['punt_received_x'] - play_summaries['punt_x']\n    play_summaries['return_length'] = play_summaries['punt_received_x'] - play_summaries['tackle_x']\n    return play_summaries.reset_index(drop=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"f7dcebe88bed38cc9f91ac0cde041810de286275"},"cell_type":"code","source":"play_summaries = get_play_summaries(roles)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"173d8c4c8751707736a921c136fc6cf21bf70ce8"},"cell_type":"code","source":"def custom_round(x, base=5):\n    if pd.isna(x):\n        return None\n    return int(base * round(float(x)/base))\n\nplay_summaries['punt_x_rounded'] = play_summaries['punt_x'].apply(custom_round)\npunt_location_returns = play_summaries.groupby('punt_x_rounded').agg({'return_length': 'mean',\n                                                                       'punt_length':'mean',\n                                                                       'hang_time': 'mean',\n                                                                       'PlayID': 'count',\n                                                                       'punt_received_x': 'count',\n                                                                       'fair_catch_x': 'count',\n                                                                       'out_of_bounds_x':'count',\n                                                                       'Player_Activity_Derived': 'count'})\\\n                                       .rename(columns={'punt_x_rounded': 'Punt Starting Location',\n                                                        'punt_length': 'Avg. Punt Length',\n                                                        'return_length': 'Avg. Return Length',\n                                                        'hang_time': 'Avg. Hang Time',\n                                                        'punt_received_x': 'Returns',\n                                                        'fair_catch_x': 'Fair Catches',\n                                                        'out_of_bounds_x':'Out-of-Bounds',\n                                                        'PlayID': 'Punts',\n                                                        'Player_Activity_Derived': 'Injuries'})\npunt_location_returns['Injuries per 1k Punts'] = 1000* punt_location_returns['Injuries'] / punt_location_returns['Punts']\npunt_location_returns['Injuries per 1k Returns'] = 1000* punt_location_returns['Injuries'] / punt_location_returns['Returns']\npunt_location_returns","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"a52935038ed57db9b63f544cea85710541788ee8"},"cell_type":"code","source":"play_summaries['punt_received_x_rounded'] = play_summaries['punt_received_x'].apply(custom_round)\npunt_received_location = play_summaries.groupby('punt_received_x_rounded').agg({'return_length': 'mean',\n                                                                       'punt_length':'mean',\n                                                                       'hang_time': 'mean',\n                                                                       'PlayID': 'count',\n                                                                       'punt_received_x': 'count',\n                                                                       'fair_catch_x': 'count',\n                                                                       'out_of_bounds_x':'count',\n                                                                       'Player_Activity_Derived': 'count'})\\\n                                       .rename(columns={'punt_x_rounded': 'Punt Starting Location',\n                                                        'punt_length': 'Avg. Punt Length',\n                                                        'return_length': 'Avg. Return Length',\n                                                        'hang_time': 'Avg. Hang Time',\n                                                        'punt_received_x': 'Returns',\n                                                        'fair_catch_x': 'Fair Catches',\n                                                        'out_of_bounds_x':'Out-of-Bounds',\n                                                        'PlayID': 'Punts',\n                                                        'Player_Activity_Derived': 'Injuries'})\npunt_received_location['Injuries per 1k Punts'] = 1000* punt_received_location['Injuries'] / punt_received_location['Punts']\npunt_received_location['Injuries per 1k Returns'] = 1000* punt_received_location['Injuries'] / punt_received_location['Returns']\npunt_received_location","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"39c6e9710a9311e029df5203a5070107b28b31ea"},"cell_type":"code","source":"returns_qualify_for_new_rule = play_summaries[play_summaries['punt_x']<30][play_summaries['punt_received_x']>80]\nfc_qualify_for_new_rule = play_summaries[play_summaries['punt_x']<30][play_summaries['fair_catch_x']>80]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"1b96ff86ad7f40769f87080d9c8ceadd77f73df4"},"cell_type":"code","source":"rq = returns_qualify_for_new_rule['PlayID'].count() # number of returns affected by proposed rule\nrt = play_summaries['punt_received_x'].count()      # total number of returns\n\nfq = fc_qualify_for_new_rule['PlayID'].count()# number of fair catches affected by proposed rule\nft = play_summaries['fair_catch_x'].count()# total number of fair catches\n\niq = returns_qualify_for_new_rule['Player_Activity_Derived'].count() # number of injuries affected by proposed rule\nit = play_summaries['Player_Activity_Derived'].count()      # total number of injuries\n\n\nprint(str(rq) + \" returns qualify, accounting for \" + str(100*rq/rt) + \"% of all returns\" )\nprint(str(fq) + \" fair catches qualify, accounting for \" + str(100*fq/ft) + \"% of all fair catches\" )\nprint(str(iq) + \" injuries qualify, accounting for \" + str(100*iq/it) + \"% of all injuries\" )","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"67bfad95eac8ba5589b902875d43736a37b7002d"},"cell_type":"markdown","source":"# Bokeh Plots\nIn the couple cells below, I decided to try and create a map of the field. I stalled out before taking a couple pages from Rob Mulla's kernel. Specifically the renderers.pop() method.\n\nTurns out that the Javascript callbacks are still cripplingly slow, and this wasn't totally productive for answering the question. Still kinda cool!\n\n¯\\\\ _ (ツ) _/¯"},{"metadata":{"trusted":true,"_uuid":"e0936344175a803ac5d500ebc820c8e08093e71c"},"cell_type":"code","source":"class Field(Figure):\n    __subtype__ = \"Field\"\n    __view_model__ = \"Plot\"\n    \n    def __init__(self, **kwargs):\n        Figure.__init__(\n            self,\n            x_range = Range1d(start=0, end=500),\n            y_range = Range1d(start=0, end=241),\n            plot_height = 250,\n            toolbar_location = None,\n            active_drag = None,\n            **kwargs\n        )\n        self.axis.visible = False\n        self.image_url(url=[\"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/AmFBfield.svg/500px-AmFBfield.svg.png\"], x=[0], y=[0], w=500, h=241, anchor=\"bottom_left\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"d50d7fe9e59a81b4349037a1f54c0eef3e3071b6"},"cell_type":"code","source":"class Play():\n    \"\"\"\n    Create an object that defines a single Play and can be used for visualization\n    \"\"\"\n    def __init__(self, game_key, play_id, ngs_df, role_df):\n        play_df = ngs_df[(ngs_data['GameKey'] == game_key)\\\n                       & (ngs_data['PlayID'] == play_id)]\n        \n        # make sure the play exists in the current ngs_dataframe\n        assert not play_df.empty\n        \n        # remove records after the play ends\n        play_df = play_df.sort_values('Time', ascending=False).reset_index(drop=True)\n        ending_events = ['tackle', 'out_of_bounds', 'punt_downed', 'touchback', 'fair_catch', 'touchdown']\n        play_df = play_df[play_df['Event'].isin(ending_events).idxmax():]\n\n        # sort and remove records prior to snap\n        play_df = play_df.sort_values('Time').reset_index(drop=True)\n        play_df = play_df[(play_df['Event']=='ball_snap').idxmax():]\n\n        # convert x/y coordinates to fit on the field background\n        # buffer on each side of the field is ~ 10 pixels, 1yd = 4px\n        play_df['x'] = play_df['x'] * 4 + 10\n        play_df['y'] = play_df['y'] * 4 + 10\n\n        # associate role & team with each player\n        play_df = play_df.merge(role_df, how='left', on=['GameKey','PlayID','GSISID'])\n\n        # establish a time dimension for the play where ball_snap = 0\n        play_df['seconds_since_snap'] = play_df['Time'] - play_df['Time'].min()\n        play_df['seconds_since_snap'] = play_df['seconds_since_snap'].dt.total_seconds()\n\n        self.game_key = game_key\n        self.play_id = play_id\n        self.play_df = play_df.copy()\n        self.play_duration = play_df['seconds_since_snap'].max()\n        self.field = Field()\n        self.handle = None\n        \n        # Define teams and color of markers\n        Team = namedtuple('Team', 'role color')\n        self.teams = [Team('RETURN','red'), Team('COVERAGE','blue')]\n        \n    def add_players_to_field(self, seconds_since_snap):\n        snapshot_df = self.play_df[self.play_df['seconds_since_snap'] == seconds_since_snap]\n        snapshot_cds = ColumnDataSource(snapshot_df)\n        for team in self.teams:\n            team_bool = [player_team == team.role for player_team in snapshot_df['Team']]\n            team_view = CDSView(source=snapshot_cds, filters=[BooleanFilter(team_bool)])\n            self.field.circle(x='x', y='y', source=snapshot_cds, size=5,\n                              color=team.color, name=team.role, view=team_view)\n#         push_notebook(handle=self.handle)\n\n    def update_field(self, seconds_since_snap):\n        self.field.renderers.pop()\n        self.field.renderers.pop()\n        self.add_players_to_field(seconds_since_snap)\n        push_notebook(handle=self.handle)\n\n    def show_field(self, callback_name, seconds_since_snap=0):\n        self.add_players_to_field(0)\n        callback = CustomJS(code=\"\"\"\n        if (IPython.notebook.kernel !== undefined) {{\n            var kernel = IPython.notebook.kernel;\n            cmd = \"{}.update_field(\" + cb_obj.value + \")\";\n            kernel.execute(cmd, {{}}, {{}});\n        }}\"\"\".format(callback_name))\n        \n        slider = Slider(title=\"Seconds Since Snap\", value=0.0, start=0.0,\n                        end=self.play_duration, step=0.5, callback=callback)\n        field_layout = layout(\n            column(\n                self.field,\n                widgetbox(slider)\n            )\n        )\n\n        self.handle = show(field_layout, notebook_handle=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"43e5c80c5d7a148c344e8a92dbdc0b8335f4d5d7"},"cell_type":"code","source":"ngs_files = [file for file in os.listdir('../input/') if file.endswith('.csv') and file.startswith('NGS')]\nngs_files","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"b281d83a11ce5e4cb97c0c28c0ff750d7934efec"},"cell_type":"code","source":"ngs_data = pd.read_csv('../input/NGS-2016-reg-wk1-6.csv')\nngs_data['Time'] = pd.to_datetime(ngs_data['Time'])\nngs_data","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"caf38fee098ddb87356a5c521599abea9cadc60c"},"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}