{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"In this notebook we'll take a look at tracking2018.csv and plays.csv to first do a light EDA and then make a function to see an animation of the players and the football in a play. \n\nThe animation function near the bottom of the notebook, getPlayAnimation(), should be easily applicable to other problems as well. ","metadata":{}},{"cell_type":"markdown","source":"**In this notebook, you will:**\n* See some simple exploration of the tracking data\n* Pick which play, using widgets, you would like to see an animation of\n\n\n**Quick Note**\n\nIf you're looking for more examples with using widgets, my [notebook](https://github.com/jvmohr/dataScience/blob/master/Spotify/SpotifyV.ipynb) on GitHub uses some other widgets, as well as having a layout system. ","metadata":{}},{"cell_type":"markdown","source":"Thanks to Jaron_Michael and his [notebook](https://www.kaggle.com/jaronmichal/tracking-data-visualization) for inspiration to improve my animation with widgets (with the newly created Interactive Play Animation section), and for the last line I needed for it (as noted by said line)!","metadata":{}},{"cell_type":"code","source":"from IPython.core.display import HTML\nimport matplotlib.animation as anim\nimport matplotlib.pyplot as plt\nimport ipywidgets as wg\nimport pandas as pd\nimport numpy as np\n\n%matplotlib inline","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2021-09-28T17:23:39.855498Z","iopub.execute_input":"2021-09-28T17:23:39.855921Z","iopub.status.idle":"2021-09-28T17:23:39.872979Z","shell.execute_reply.started":"2021-09-28T17:23:39.855853Z","shell.execute_reply":"2021-09-28T17:23:39.871907Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"t2018 = pd.read_csv('../input/nfl-big-data-bowl-2022/tracking2018.csv')\nt2018.head()","metadata":{"execution":{"iopub.status.busy":"2021-09-28T17:23:39.878752Z","iopub.execute_input":"2021-09-28T17:23:39.879013Z","iopub.status.idle":"2021-09-28T17:24:24.372467Z","shell.execute_reply.started":"2021-09-28T17:23:39.878981Z","shell.execute_reply":"2021-09-28T17:24:24.371332Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"t2018.shape # Wow over 12 million rows!","metadata":{"execution":{"iopub.status.busy":"2021-09-28T17:24:24.373987Z","iopub.execute_input":"2021-09-28T17:24:24.374601Z","iopub.status.idle":"2021-09-28T17:24:24.382687Z","shell.execute_reply.started":"2021-09-28T17:24:24.374548Z","shell.execute_reply":"2021-09-28T17:24:24.381454Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Rows Per Play\n\nLooking at the first 5 rows above, each separate play has it's own id, as well as the game id. So, let's look at the first one in this file to see how many rows correspond to it. ","metadata":{}},{"cell_type":"code","source":"t2018[ (t2018['playId'] == 36) & (t2018['gameId'] == 2018123000) ]","metadata":{"execution":{"iopub.status.busy":"2021-09-28T17:24:24.385475Z","iopub.execute_input":"2021-09-28T17:24:24.385764Z","iopub.status.idle":"2021-09-28T17:24:24.469380Z","shell.execute_reply.started":"2021-09-28T17:24:24.385732Z","shell.execute_reply":"2021-09-28T17:24:24.468255Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now checking the average across plays, it looks like the average number of rows is just below 1900. ","metadata":{}},{"cell_type":"code","source":"# Average number of rows per play\nnum_rows = t2018.groupby(['gameId', 'playId'])['x'].count()\nnum_rows.mean()","metadata":{"execution":{"iopub.status.busy":"2021-09-28T17:24:24.471091Z","iopub.execute_input":"2021-09-28T17:24:24.471320Z","iopub.status.idle":"2021-09-28T17:24:25.097253Z","shell.execute_reply.started":"2021-09-28T17:24:24.471292Z","shell.execute_reply":"2021-09-28T17:24:25.096273Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"It would be nice if we could get separate averages for different kinds of plays. plays.csv has some rows that can help with that. ","metadata":{}},{"cell_type":"code","source":"plays = pd.read_csv('../input/nfl-big-data-bowl-2022/plays.csv')\nplays.head(2)","metadata":{"execution":{"iopub.status.busy":"2021-09-28T17:24:25.098958Z","iopub.execute_input":"2021-09-28T17:24:25.099209Z","iopub.status.idle":"2021-09-28T17:24:25.212005Z","shell.execute_reply.started":"2021-09-28T17:24:25.099181Z","shell.execute_reply":"2021-09-28T17:24:25.211002Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plays['specialTeamsPlayType'].value_counts()","metadata":{"execution":{"iopub.status.busy":"2021-09-28T17:24:25.213564Z","iopub.execute_input":"2021-09-28T17:24:25.213796Z","iopub.status.idle":"2021-09-28T17:24:25.224550Z","shell.execute_reply.started":"2021-09-28T17:24:25.213769Z","shell.execute_reply":"2021-09-28T17:24:25.223656Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Add the play type\n# https://stackoverflow.com/questions/41815079/pandas-merge-join-two-data-frames-on-multiple-columns\nnum_rows = num_rows.reset_index().merge(plays[['gameId', 'playId', 'specialTeamsPlayType', 'specialTeamsResult']], \n                             how='left', left_on=['gameId', 'playId'], right_on=['gameId', 'playId'])\nnum_rows","metadata":{"execution":{"iopub.status.busy":"2021-09-28T17:24:25.226002Z","iopub.execute_input":"2021-09-28T17:24:25.226277Z","iopub.status.idle":"2021-09-28T17:24:25.257926Z","shell.execute_reply.started":"2021-09-28T17:24:25.226246Z","shell.execute_reply":"2021-09-28T17:24:25.256394Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now we can look at the average rows per play type. ","metadata":{}},{"cell_type":"code","source":"avg_rows = num_rows.groupby('specialTeamsPlayType')['x'].mean()\navg_rows.plot.bar()\navg_rows","metadata":{"execution":{"iopub.status.busy":"2021-09-28T17:24:25.259904Z","iopub.execute_input":"2021-09-28T17:24:25.260315Z","iopub.status.idle":"2021-09-28T17:24:25.447531Z","shell.execute_reply.started":"2021-09-28T17:24:25.260261Z","shell.execute_reply":"2021-09-28T17:24:25.446220Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"It makes sense that extra points and field goals are fairly close and the lowest two as generally they are over the quickest as once the ball is hit the play usually doesn't last much longer (only long enough for the ball to go through/miss the uprights). While I do find it interesting that punts would have that many more rows than kickoffs, I suppose the hangtime of punts and the possible greater likelihood of a touchback for kickoffs would account for this. In fact let's investigate that next. ","metadata":{}},{"cell_type":"code","source":"avg_rows_result = num_rows.groupby(['specialTeamsPlayType', 'specialTeamsResult'])['x'].mean().astype(int)\navg_rows_result.sort_values().plot.barh()\navg_rows_result","metadata":{"execution":{"iopub.status.busy":"2021-09-28T17:24:25.449174Z","iopub.execute_input":"2021-09-28T17:24:25.449491Z","iopub.status.idle":"2021-09-28T17:24:25.856189Z","shell.execute_reply.started":"2021-09-28T17:24:25.449458Z","shell.execute_reply":"2021-09-28T17:24:25.855196Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I think several interesting things pop out by looking at this data. First is the specialTeamsResult of 'Non-Special Teams Result.' This would be something like a fake extra point or a fake field goal attempt (as in they set up as if they were going to kick the ball but actually did something else). These plays could likely be ignored for parts of later analysis, although the initial setup of players in these plays could offer some insight as to when teams are likely setting up to go for it. \n\nFrom there, blocked kick attempts tend to have more rows, which makes sense as players would try to return the ball. Kickoff/punt returns also seem to yield longer plays and more rows. \n\nAs expected, normal extra point/field goal attempts and non-returned kickoffs generally have the least nnumber of rows. ","metadata":{}},{"cell_type":"markdown","source":"# Play Animation\n\nLet's take another look at the tracking data.","metadata":{}},{"cell_type":"code","source":"t2018","metadata":{"execution":{"iopub.status.busy":"2021-09-28T17:24:25.858020Z","iopub.execute_input":"2021-09-28T17:24:25.858372Z","iopub.status.idle":"2021-09-28T17:24:25.899311Z","shell.execute_reply.started":"2021-09-28T17:24:25.858326Z","shell.execute_reply":"2021-09-28T17:24:25.898596Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"It looks like the football has it's own rows as well. Let's create an animation to watch one play, including the players' movements and the football's movement. ","metadata":{}},{"cell_type":"code","source":"one_play = t2018[ (t2018['playId'] == 36) & (t2018['gameId'] == 2018123000) ]\none_play.head(2)\n# different between frames (looking at time column) is .1 seconds","metadata":{"execution":{"iopub.status.busy":"2021-09-28T17:24:25.900650Z","iopub.execute_input":"2021-09-28T17:24:25.900912Z","iopub.status.idle":"2021-09-28T17:24:25.952169Z","shell.execute_reply.started":"2021-09-28T17:24:25.900883Z","shell.execute_reply":"2021-09-28T17:24:25.951586Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def getPlayAnimation(df, playId=36, gameId=2018123000, color_dict={'home': 'green', 'away':'blue', 'football': 'brown'}, \n                     from_helper=False, add_info=None):\n    # function to draw one frame\n    def plotFrame(i):\n        one_frame = one_play[ one_play['frameId'] == i+1 ]\n        ax.cla()\n        \n        # Constants from frame to frame\n        ax.set_xlim([0, 120])\n        ax.set_ylim([0, 53.3])\n        ax.plot([10, 10], [0, 53.3], c='black')\n        ax.plot([110, 110], [0, 53.3], c='black')\n        ax.plot([60, 60], [0, 53.3], c='gray')\n        \n        # What changes\n        ax.scatter(one_frame['x'], one_frame['y'], c=one_frame['team'].transform(lambda x: color_dict[x]), s=64)\n        ax.set_title('Frame: {}'.format(i+1))\n        \n        if add_info is not None:\n            pass\n    \n    # Get df of tracking data for play\n    if not from_helper:\n        one_play = df[(df['playId'] == playId) & (df['gameId'] == gameId)]\n    else:\n        one_play = df\n    \n    fig, ax = plt.subplots(figsize=(9,6))\n    \n    play_anim = anim.FuncAnimation(fig, plotFrame, frames=one_play['frameId'].max()-1, interval=100)\n    html = play_anim.to_html5_video()\n    plt.close(fig)\n    return HTML(html)\n    \n# Can switch any of these parameters to change the colors/get a different play\n# -- t2018 is the df for the year that the desired game and play is in\ngetPlayAnimation(t2018, playId=36, gameId=2018123000, \n                 color_dict={'home': 'green', \n                             'away':'blue', \n                             'football': 'brown'})","metadata":{"execution":{"iopub.status.busy":"2021-09-28T17:41:58.243080Z","iopub.execute_input":"2021-09-28T17:41:58.243550Z","iopub.status.idle":"2021-09-28T17:42:10.127037Z","shell.execute_reply.started":"2021-09-28T17:41:58.243511Z","shell.execute_reply":"2021-09-28T17:42:10.126254Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Interactive Play Animation","metadata":{}},{"cell_type":"code","source":"# A few variables\nteams = sorted(plays['possessionTeam'].unique())\ngames_df = pd.read_csv('../input/nfl-big-data-bowl-2022/games.csv')","metadata":{"execution":{"iopub.status.busy":"2021-09-28T17:24:37.653745Z","iopub.execute_input":"2021-09-28T17:24:37.654029Z","iopub.status.idle":"2021-09-28T17:24:37.717110Z","shell.execute_reply.started":"2021-09-28T17:24:37.653995Z","shell.execute_reply":"2021-09-28T17:24:37.716021Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"yr_dpd = wg.Dropdown(\n    options=['2018'],\n    value='2018',\n    description='Year:',\n    disabled=False,\n)\n\nteam_dpd = wg.Dropdown(\n    options=teams,\n    value='GB',\n    description='Teams:',\n    disabled=False,\n)\n\nweek_dpd = wg.Dropdown(\n    options=list(range(1,18)),\n    value=1,\n    description='Weeks:',\n    disabled=False,\n)\nbtn = wg.Button(\n    description='Click to Get Plays',\n    disabled=False,\n    button_style='',\n    tooltip='To generate plays',\n    icon='check' \n)\nplays_dpd = wg.Dropdown(\n    options=[1],\n    value=1,\n    description='Plays:',\n    disabled=False,\n)\n\nanim_btn = wg.Button(\n    description='Get Animation',\n    disabled=False,\n    button_style='',\n    tooltip='To generate animation',\n    icon='check'\n)\n\noutput = wg.Output()\n\ndisplay(yr_dpd, team_dpd, week_dpd, btn, plays_dpd, anim_btn, output) # Display widgets\n\n# onClick for btn\ndef getPlaysDPD(b):\n#     if int(yr_dpd.value) != 2018:\n#         t_df = pd.read_csv('../input/nfl-big-data-bowl-2022/tracking{}.csv'.format(yr_dpd.value)) # redo this\n#     else:\n#         t_df = t2018\n    t_df = t2018\n    temp_df = games_df[(week_dpd.value == games_df['week'])]\n    temp_df = temp_df[(temp_df['homeTeamAbbr'] == team_dpd.value) | (temp_df['visitorTeamAbbr'] == team_dpd.value)]\n    play_ids = list(t_df[t_df['gameId'] == temp_df['gameId'].iloc[0]]['playId'].unique())\n    plays_dpd.options = play_ids\n    plays_dpd.value = play_ids[0]\n    return\n    \nbtn.on_click(getPlaysDPD)\n\n#onClick for anim_btn\ndef getAnimOnClick(b):\n    t_df = t2018\n    temp_df = games_df[(week_dpd.value == games_df['week'])] # get df of games at chosen week\n    temp_df = temp_df[(temp_df['homeTeamAbbr'] == team_dpd.value) | (temp_df['visitorTeamAbbr'] == team_dpd.value)] # down to chosen team\n    one_play = t_df[(t_df['gameId'] == temp_df['gameId'].iloc[0]) & (t_df['playId'] == plays_dpd.value)] # get tracking data for chosen game and play\n    output.clear_output()\n    with output:\n        h = getPlayAnimation(one_play, color_dict={'home': 'green', 'away':'blue', 'football': 'brown'}, from_helper=True, \n                             add_info=[temp_df['homeTeamAbbr'], temp_df['visitorTeamAbbr'], play_type])\n        display(h) # Thanks to Jaron_Michael and his notebook linked below for this line, it was the last piece\n        # https://www.kaggle.com/jaronmichal/tracking-data-visualization\n    return\n\nanim_btn.on_click(getAnimOnClick)","metadata":{"execution":{"iopub.status.busy":"2021-09-28T17:24:37.719015Z","iopub.execute_input":"2021-09-28T17:24:37.719341Z","iopub.status.idle":"2021-09-28T17:24:37.812401Z","shell.execute_reply.started":"2021-09-28T17:24:37.719298Z","shell.execute_reply":"2021-09-28T17:24:37.811803Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Future Improvements:\n* Quick way to choose from different years without running into memory issues\n* Clean/shorten widget code up a bit\n* Make animation look nicer\n* Add more info to add_info and display it in the animation nicely","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}