{"cells":[{"metadata":{"_uuid":"cbbf0a52c885c3ca4dac0a217ba91671fed0e906"},"cell_type":"markdown","source":"### Goal: Create animations of the NGS gameplay data\n- Ideally have animations that also show which way a player is facing\n    - <b>CURRENTLY WORKING ON UPDATING SCRIPT TO INDICATE PLAYER FACING DIRECTION</b>"},{"metadata":{"trusted":true,"_uuid":"b6f167563bedb12e27a94729b5574de37756df5c"},"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\nsns.set()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"622d59c41e6e27fcad4807668216617f5866d27d"},"cell_type":"markdown","source":"- We'll need play_player_role data and playdescription from play dataset for the scripts below so we'll concatenate them with a particular NGS dataset"},{"metadata":{"trusted":true,"_uuid":"9c50128ce2bc65565c652bbc5b399d6f7ddef5bf"},"cell_type":"code","source":"# Combine Player Roles and play info with NGS Data\nngs_data = pd.read_csv('../input/NGS-2016-post.csv')     # <---REPLACE WITH NGS DATA YOU WANT TO LOOK AT\nplay_player_role_df = pd.read_csv('../input/play_player_role_data.csv')\nplay_df = pd.read_csv('../input/play_information.csv')\n\nngs = pd.merge(ngs_data, play_player_role_df,\n                    how='left',\n                    on=['GameKey', 'PlayID', 'GSISID'])\nngs = pd.merge(ngs, play_df,\n                    how='left',\n                    on=['GameKey', 'PlayID'])\n\nkeepers = ['GameKey', 'PlayID', 'GSISID', 'Time', 'x', 'y', 'o', 'PlayDescription', 'Role', 'Event']\nngs = ngs[keepers]\nngs.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"8b92b31c72f0ee1ad01487e80c34121b7c3ddd4c"},"cell_type":"code","source":"# Used to isolate unique plays (GameKey, PlayID)\nngs_ids = ngs.groupby(['GameKey','PlayID']).size().reset_index().rename(columns={0:'count'})\nprint(ngs_ids.shape)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"9a73ba61f5eb9f257bdeacecf0d472f17530b3f5"},"cell_type":"markdown","source":"## Preprocessing\n- So in order to create animations, the data must be pretty structured at least for my scripts. If you run the next two blocks with a particular event on the play you may get <b>widely varied number of players on the field</b> from the standard (22) especially towards the beginning and end of a particular play.\n    - This also means that my code isn't overly robust. It works for most cases if you have already filtered your plays based on ones that are actual punt returns\n- For example, even during the play if you look up the event 'line_set', 79.8% of the time 22 players are on the field. For this particular event some plays have from 22 up to 34 players being tracked. This deeply complicates the labeling of players on the field.\n- As a result, I will perform some preprocessing to try to ensure only plays that have 22 players on the field are able to be viewed. As a result I will filter first by 'line_set' and keep plays where there are 22 players and then filter by some ending event ('tackle', 'out_of_bounds', etc.).\n    - Note: Even with this filtering there can be timestamps between the two events where there are less than 22 players being logged for each timestamp."},{"metadata":{"trusted":true,"_uuid":"ac654cec4b4b9154f8ca35005244fd68a9f3269a"},"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":"bb27ea9121330d6b58cb392aa675641a80c4bcce"},"cell_type":"code","source":"# How many plays have the event '______'?\nevent_df, event_ids = event_df_creation(ngs, 'touchdown')\nprint('# of plays with specific event:', len(event_ids), 'out of', len(ngs_ids))\nevent_ids['count'].value_counts()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"2c1804019a82498dc7d4f5ad6d75bae00c201d57"},"cell_type":"markdown","source":"### Notes on NGS dataset\n- Count of events with 22 Players:\n    - 'line_set': 79\n    - 'tackle': 29\n    - 'out_of_bounds': 18\n    - 'punt_downed': 6\n    - 'touchback': 4\n    - 'touchdown': 0\n    \n### Course of Action:\n- Filter by a particular play (GameKey, PlayID)\n- Check if it meets the 22 player requirement on events ('punt_play' and 'tackle'/'out_of_bounds')\n- Break play down further to only include data between the events ('line_set' and some end event)\n- Order by Time and then GSISID to prevent flickering\n- Create animation of play"},{"metadata":{"trusted":true,"_uuid":"ad80acc3802fd09b884cfdffbfb854f412a36218"},"cell_type":"code","source":"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","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"40584d2589e4cf5a941601fc1358b552f35fe3b7"},"cell_type":"code","source":"# # Run if you need unique keys from loaded NGS data\n# ngs_ids","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"96bd1672e1ba6b6e07a37470e0b8af437af3293c"},"cell_type":"code","source":"'''THIS IS A NEEDED CHECK IF YOU CARE ABOUT MAINTAINING TEAM LABELS DURING ANIMATIONS'''\ndef check_for_22_players(the_play):\n    # Check for 22 players on start event\n    start_event = 'line_set'\n    df, ids = event_df_creation(the_play, start_event)\n    if len(ids) == 1 and len(ids[ids['count'] == 22]) == 1:\n        print('The event', start_event, 'has 22 players participating')\n    else:\n        return 'PLAY IS TOO ODD TO PROCEED!!!'\n    \n    # Check for 22 players on end event\n    end_events = ['tackle', 'out_of_bounds', 'punt_downed', 'touchback']\n    for event in end_events:\n        df, ids = event_df_creation(the_play, event)\n        if len(ids) == 1 and len(ids[ids['count'] == 22]) == 1:\n            return 'The event ' + event + ' has 22 players participating'\n        else:\n            return 'END EVENT IS TOO ODD TO PROCEED!!!'","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"62a38c1a24e5d590a11992636c4b0e0ab9af3bc7"},"cell_type":"code","source":"'''SELECT YOUR PLAY'''\ngame_key = 323\nplay_id = 146\nthe_play = isolate_play(ngs, game_key, play_id)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"1ea3af4f7772ddc7e461d9fbe496bb40d781350f"},"cell_type":"code","source":"check_for_22_players(the_play)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"e4503a4765c6609fce03f355cd83121cb535601f"},"cell_type":"code","source":"def only_the_play(df, event1, event2):\n    '''Condense a play to just the data points between two events'''\n    where_condition = (df['Event'] == event1)\n    start_index = df[where_condition].index[0]\n    where_condition = (df['Event'] == event2)\n    end_index = df[where_condition].index[-1]\n    print(start_index)\n    print(end_index)\n    return df.loc[start_index:end_index, :].reset_index(drop=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"58b3871bb6df13b162ce021ca2d61ddf7f5cfcbc"},"cell_type":"code","source":"# Isolate core of the play and order dataset appropriately\nthe_play = only_the_play(the_play, 'line_set', 'tackle')     # <--- Makes sure to change event appropriately\nthe_play.sort_values(by=['Time', 'GSISID'], inplace=True)\nthe_play.reset_index(drop=True, inplace=True)\nthe_play.shape","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8777eaf391309daf9c77d591f71add8713dc21ae"},"cell_type":"markdown","source":"## Animations"},{"metadata":{"trusted":true,"_uuid":"4ec4ba341600be81e47b1459aeb2998fbf62f121"},"cell_type":"code","source":"'''SCRIPT WITHOUT TEAM LABELS'''\n%matplotlib inline\nimport matplotlib.pyplot as plt\nimport matplotlib.animation\nimport numpy as np\nfrom IPython.display import HTML\n\nfig, ax = plt.subplots(figsize=(10,6))\nax.set(\n    xlim=(-10, 130),\n    ylim=(-10, 65),\n    title='Playing Field',\n    xlabel='yardline',\n    ylabel='width of field'\n)\nplt.xticks(np.arange(0, 130, step=10),\n              ['End', 'GL', '10', '20', '30', '40', '50', '40', '30', '20', '10', 'GL', 'End'])\nplt.yticks(np.arange(0, 65, 53.3), ['Sideline', 'Sideline'])\nscat1 = ax.scatter(the_play.loc[:21, 'x'], the_play.loc[:21, 'y'], color='red', alpha=0.5)\ninterval = 1\n\ndef animate(i):\n    if i == 0:\n        return\n    else:\n        scat1.set_offsets(np.c_[the_play.loc[(i*22):(i*22)+22, 'x'], \n                                the_play.loc[(i*22):(i*22)+22, 'y']])\n\nprint('Play Description:', the_play.loc[0, 'PlayDescription'])\nani = matplotlib.animation.FuncAnimation(fig, animate, frames=int(len(the_play)/22), interval=100, repeat=False)\nplt.close()    # Thanks Garry!\nHTML(ani.to_jshtml())","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"0c7f888667c685a392927b76139302212ee54ce4"},"cell_type":"markdown","source":"- Let's indicate what team the player is playing on based off player role"},{"metadata":{"trusted":true,"_uuid":"bae7bb5c99379d7bb93ef5e5eae8572c613b5175"},"cell_type":"code","source":"return_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        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":"b3d318b35e633db808f8db6400a521f38fdb147d"},"cell_type":"code","source":"# Isolate core of the play and order dataset appropriately\nlabel_team(the_play)\nthe_play.sort_values(by=['Time', 'team', 'GSISID'], inplace=True)\nthe_play.reset_index(drop=True, inplace=True)\nthe_play.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"5f80794a7372a8bf3ca2f023185885eaa5cb6ccf"},"cell_type":"code","source":"'''SCRIPT WITH TEAM LABELS'''\n%matplotlib inline\nimport matplotlib.pyplot as plt\nimport matplotlib.animation\nimport numpy as np\nfrom IPython.display import HTML\n\nfig, ax = plt.subplots(figsize=(10,6))\nax.set(\n    xlim=(-10, 130),\n    ylim=(-10, 65),\n    title='Playing Field',\n    xlabel='yardline',\n    ylabel='width of field'\n)\nplt.xticks(np.arange(0, 130, step=10),\n              ['End', 'GL', '10', '20', '30', '40', '50', '40', '30', '20', '10', 'GL', 'End'])\nplt.yticks(np.arange(0, 65, 53.3), ['Sideline', 'Sideline'])\nred_patch = mpatches.Patch(color='red', label='return team')\nblue_patch = mpatches.Patch(color='blue', label='punt team')\nplt.legend(handles=[red_patch, blue_patch])\n\n# Punt team\nscat1 = ax.scatter(the_play.loc[:10, 'x'], the_play.loc[:10, 'y'], color='blue', alpha=0.5)\n# Return team\nscat2 = ax.scatter(the_play.loc[11:21, 'x'], the_play.loc[11:21, 'y'], color='red', alpha=0.5)\n\ndef animate(i):\n    if i == 0:\n        return\n    else:\n        # Punt team update\n        scat1.set_offsets(np.c_[the_play.loc[(i*22):(i*22)+10, 'x'], \n                                the_play.loc[(i*22):(i*22)+10, 'y']])\n        # return team update\n        scat2.set_offsets(np.c_[the_play.loc[(i*22)+11:(i*22)+21, 'x'], \n                                the_play.loc[(i*22)+11:(i*22)+21, 'y']])      \n        \n        \n# print('Play Description:', the_play.loc[0, 'PlayDescription'])\nani = matplotlib.animation.FuncAnimation(fig, animate, frames=int(len(the_play)/22), interval=100, repeat=False)\nplt.close()    # Thanks Garry!\nHTML(ani.to_jshtml())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"f70c11eb3e17ba2bf4ad3322e7dcb803de097bd4"},"cell_type":"code","source":"# To check for timestamps that are missing players\ntime_ids = the_play.groupby(['Time']).size().reset_index().rename(columns={0:'count'})\ntime_ids['count'].value_counts()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d37cb1160a22ac5fd1e809ce6124337bd32fc4c1"},"cell_type":"markdown","source":"- <b>If you experience color switching during a play</b>, it is due to some timestamps (one tenth of a second in time) having less than 22 data points associated with it. I have not figured a way to label particular data points without bogging down the runtime of creation of the animation. My assumption of 22 players across all timestamps fails at times :(. I could probably make my scripts more dynamic to account for the changing number of players being tracked throughout the play, but I'm tired :P.\n- <b>Hopefully checking game animations can help folk see how some plays panout</b>\n- I'll try updating in a future version to have little sticks with player facing directions"},{"metadata":{"trusted":true,"_uuid":"c7d8ff4bb0950cafb2d0c15b70b3ff8302fef1c2"},"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}