{"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":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2023-10-18T18:10:54.501482Z","iopub.execute_input":"2023-10-18T18:10:54.502088Z","iopub.status.idle":"2023-10-18T18:10:54.521763Z","shell.execute_reply.started":"2023-10-18T18:10:54.502040Z","shell.execute_reply":"2023-10-18T18:10:54.520808Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🎯🏈NFL Data Bowl 2024 - Plays Animation 🏈🎯 \n\n## Let us first import the Python libraries","metadata":{}},{"cell_type":"code","source":"import os\nimport matplotlib\nimport matplotlib.pyplot as plt\nimport pandas as pd\nimport numpy as np\nimport math\nimport scipy\nfrom random import choice\nfrom scipy.spatial.distance import euclidean\nfrom scipy.special import expit\nfrom IPython.display import HTML\nfrom matplotlib import animation\nfrom tqdm import tqdm\nimport glob","metadata":{"execution":{"iopub.status.busy":"2023-10-18T18:10:54.523568Z","iopub.execute_input":"2023-10-18T18:10:54.524145Z","iopub.status.idle":"2023-10-18T18:10:54.529919Z","shell.execute_reply.started":"2023-10-18T18:10:54.524109Z","shell.execute_reply":"2023-10-18T18:10:54.528914Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Let us Load the data files","metadata":{}},{"cell_type":"code","source":"#train_df = pd.read_csv('../input/nfl-big-data-bowl-2024/tracking_week_1.csv', low_memory=False)\nplayers_df = pd.read_csv('../input/nfl-big-data-bowl-2024/players.csv')\n\nplay_files = sorted(glob.glob('../input/nfl-big-data-bowl-2024/tracking*.csv'))\ntrain_df=pd.concat((pd.read_csv(file,  low_memory=False) for file in play_files))","metadata":{"execution":{"iopub.status.busy":"2023-10-18T18:10:54.531229Z","iopub.execute_input":"2023-10-18T18:10:54.531463Z","iopub.status.idle":"2023-10-18T18:11:35.798668Z","shell.execute_reply.started":"2023-10-18T18:10:54.531434Z","shell.execute_reply":"2023-10-18T18:11:35.797270Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Check the columns","metadata":{}},{"cell_type":"code","source":"train_df.columns","metadata":{"execution":{"iopub.status.busy":"2023-10-18T18:11:35.801210Z","iopub.execute_input":"2023-10-18T18:11:35.801600Z","iopub.status.idle":"2023-10-18T18:11:35.809700Z","shell.execute_reply.started":"2023-10-18T18:11:35.801548Z","shell.execute_reply":"2023-10-18T18:11:35.808883Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"players_df.columns","metadata":{"execution":{"iopub.status.busy":"2023-10-18T18:11:35.811832Z","iopub.execute_input":"2023-10-18T18:11:35.813219Z","iopub.status.idle":"2023-10-18T18:11:35.824053Z","shell.execute_reply.started":"2023-10-18T18:11:35.813136Z","shell.execute_reply":"2023-10-18T18:11:35.823315Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Count the game and play ids","metadata":{}},{"cell_type":"code","source":"group_val = train_df.groupby(['gameId','playId'])['frameId'].count()","metadata":{"execution":{"iopub.status.busy":"2023-10-18T18:11:35.825646Z","iopub.execute_input":"2023-10-18T18:11:35.825916Z","iopub.status.idle":"2023-10-18T18:11:36.479455Z","shell.execute_reply.started":"2023-10-18T18:11:35.825886Z","shell.execute_reply":"2023-10-18T18:11:36.478355Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"group_val.sort_values(ascending=False).head(10)","metadata":{"execution":{"iopub.status.busy":"2023-10-18T18:11:36.480740Z","iopub.execute_input":"2023-10-18T18:11:36.480981Z","iopub.status.idle":"2023-10-18T18:11:36.494188Z","shell.execute_reply.started":"2023-10-18T18:11:36.480953Z","shell.execute_reply":"2023-10-18T18:11:36.492976Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tracking2024 = train_df\n\ntemp_df = pd.merge(tracking2024, players_df, on='nflId')\n#temp_df.head()\ntracking2024['position']=temp_df['position']\n#temp_df.drop()\ntracking2024.head()","metadata":{"execution":{"iopub.status.busy":"2023-10-18T18:11:36.495521Z","iopub.execute_input":"2023-10-18T18:11:36.495776Z","iopub.status.idle":"2023-10-18T18:11:48.088810Z","shell.execute_reply.started":"2023-10-18T18:11:36.495748Z","shell.execute_reply":"2023-10-18T18:11:48.087930Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Define the pitch function","metadata":{}},{"cell_type":"code","source":"#football field - code from  https://www.kaggle.com/jaronmichal/tracking-data-visualization \nimport matplotlib.patches as patches\nfrom matplotlib.patches import Arc\nfrom matplotlib import pyplot as plt\nimport matplotlib.patches as mpatches\n\n# Change size of the figure\nplt.rcParams['figure.figsize'] = [24, 16]\ndef drawPitch(width, height, color=\"w\"):\n    fig = plt.figure()\n    ax = plt.axes(xlim=(-10, width + 30), ylim=(-15, height + 5))\n    plt.axis('off')\n\n    # Grass around pitch\n    rect = patches.Rectangle((-10, -5), width + 40, height + 10, linewidth=1, facecolor='#3f995b', capstyle='round')\n    ax.add_patch(rect)\n    ###################\n\n    # Pitch boundaries\n    rect = plt.Rectangle((0, 0), width + 20, height, ec=color, fc=\"None\", lw=2)\n    ax.add_patch(rect)\n    ###################\n\n    # vertical lines - every 5 yards\n    for i in range(21):\n        plt.plot([10 + 5 * i, 10 + 5 * i], [0, height], c=\"w\", lw=2)\n    ###################\n        \n    # distance markers - every 10 yards\n    for yards in range(10, width, 10):\n        yards_text = yards if yards <= width / 2 else width - yards\n        # top markers\n        plt.text(10 + yards - 2, height - 7.5, yards_text, size=15, c=\"w\", weight=\"bold\")\n        # botoom markers\n        plt.text(10 + yards - 2, 7.5, yards_text, size=15, c=\"w\", weight=\"bold\", rotation=180)\n    ###################\n\n    # yards markers - every yard\n    # bottom markers\n    for x in range(20):\n        for j in range(1, 5):\n            plt.plot([10 + x * 5 + j, 10 + x * 5 + j], [1, 3], color=\"w\", lw=2)\n\n    # top markers\n    for x in range(20):\n        for j in range(1, 5):\n            plt.plot([10 + x * 5 + j, 10 + x * 5 + j], [height - 1, height - 3], color=\"w\", lw=2)\n\n    # middle bottom markers\n    y = (height - 18.5) / 2\n    for x in range(20):\n        for j in range(1, 5):\n            plt.plot([10 + x * 5 + j, 10 + x * 5 + j], [y, y + 2], color=\"w\", lw=2)\n\n    # middle top markers\n    for x in range(20):\n        for j in range(1, 5):\n            plt.plot([10 + x * 5 + j, 10 + x * 5 + j], [height - y, height - y - 2], color=\"w\", lw=2)\n    ###################\n\n    # draw home end zone\n    plt.text(2.5, (height - 15) / 2, \"HOME\", size=30, c=\"w\", weight=\"bold\", rotation=90)\n    rect = plt.Rectangle((0, 0), 10, height, ec=color, fc=\"#0064dc\", lw=2)\n    ax.add_patch(rect)\n\n    # draw away end zone    \n    plt.text(111, (height - 15) / 2, \"AWAY\", size=30, c=\"w\", weight=\"bold\", rotation=-90)\n    rect = plt.Rectangle((width + 10, 0), 10, height, ec=color, fc=\"#c80014\", lw=2)\n    ax.add_patch(rect)\n    ###################\n    \n    # draw extra spot point\n    # left\n    y = (height - 3) / 2\n    plt.plot([10 + 2, 10 + 2], [y, y + 3], c=\"w\", lw=2)\n    \n    # right\n    plt.plot([width + 10 - 2, width + 10 - 2], [y, y + 3], c=\"w\", lw=2)\n    ###################\n    \n    # draw goalpost\n    goal_width = 6 # yards\n    y = (height - goal_width) / 2\n    # left\n    plt.plot([0, 0], [y, y + goal_width], \"-\", c=\"y\", lw=10, ms=20)\n    # right\n    plt.plot([width + 20, width + 20], [y, y + goal_width], \"-\", c=\"y\", lw=10, ms=20)\n    \n    return fig, ax","metadata":{"execution":{"iopub.status.busy":"2023-10-18T18:11:48.090249Z","iopub.execute_input":"2023-10-18T18:11:48.090488Z","iopub.status.idle":"2023-10-18T18:11:48.113171Z","shell.execute_reply.started":"2023-10-18T18:11:48.090459Z","shell.execute_reply":"2023-10-18T18:11:48.112423Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Parameters¶","metadata":{}},{"cell_type":"code","source":"from cycler import cycler\ncustom_cycler = (cycler(color=['b','k','m','g']))","metadata":{"execution":{"iopub.status.busy":"2023-10-18T18:11:48.114143Z","iopub.execute_input":"2023-10-18T18:11:48.114703Z","iopub.status.idle":"2023-10-18T18:11:48.132406Z","shell.execute_reply.started":"2023-10-18T18:11:48.114672Z","shell.execute_reply":"2023-10-18T18:11:48.131331Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def plot_play(df, gameId, position):\n    fig, ax = drawPitch(100, 54)\n    ax.set_prop_cycle(custom_cycler)\n    df.query('gameId == ' + gameId + ' and position == \"' + position +  '\"').groupby('club').plot(x='x', y='y', ax=ax, style='.')\n    plt.title(' Plotting for gameId == ' + gameId + ' and position == \"' + position +  '\"',fontsize=36,ha='center')\n    plt.legend().remove()","metadata":{"execution":{"iopub.status.busy":"2023-10-18T18:11:48.134090Z","iopub.execute_input":"2023-10-18T18:11:48.134414Z","iopub.status.idle":"2023-10-18T18:11:48.145458Z","shell.execute_reply.started":"2023-10-18T18:11:48.134371Z","shell.execute_reply":"2023-10-18T18:11:48.144339Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Plot for a sample of positions","metadata":{}},{"cell_type":"code","source":"poslist = [\"QB\",\"OLB\",\"WR\",\"RB\",\"TE\",\"DT\",\"ILB\",\"C\",\"DE\",\"G\",\"SS\",\"T\",\"NT\"]\nfor position in poslist:\n    print('Plotting for a ' + position + ' Postion performance')\n    plot_play(tracking2024,'2022101300', position)\n    plot_play(tracking2024,'2022102309', position)\n    plot_play(tracking2024,'2022092500', position)\n    plot_play(tracking2024,'2022102700', position)\n    plot_play(tracking2024,'2022100206', position)\n    plot_play(tracking2024,'2022100912', position)\n    plot_play(tracking2024,'2022103002', position)\n    plot_play(tracking2024,'2022100205', position)\n    plot_play(tracking2024,'2022092501', position)","metadata":{"execution":{"iopub.status.busy":"2023-10-18T18:11:48.147100Z","iopub.execute_input":"2023-10-18T18:11:48.148107Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Pull down","metadata":{}},{"cell_type":"markdown","source":"## Build the Game and Play Dataframes","metadata":{}},{"cell_type":"code","source":"games_ids = {}\ngames_tracking2024 = tracking2024.groupby(by=[\"gameId\"])\n#games_tracking2024 = tracking2024.groupby(by=[\"playId\"])\nfor game, data in games_tracking2024:\n    games_ids[game] = list(set(data.playId.tolist()))\n\n#print(games_tracking2024.head(20))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Define the Animation","metadata":{}},{"cell_type":"code","source":"\ndef extract_one_game(game_id, play_id, df):\n    game = df[(df.gameId == game_id) & (df.playId == play_id)]\n    home = {}\n    away = {}\n    balls = []\n    \n    players = game.sort_values(['frameId'], ascending=True).groupby('nflId')\n    for id, dx in players:\n        jerseyNumber = int(dx.jerseyNumber.iloc[0])\n        if dx.team.iloc[0] == \"home\":\n            home[jerseyNumber] = list(zip(dx.x.tolist(), dx.y.tolist()))\n        elif dx.team.iloc[0] == \"away\":\n            away[jerseyNumber] = list(zip(dx.x.tolist(), dx.y.tolist()))\n\n\n    ball_df = game.sort_values(['frameId'], ascending=True) \n    ball_df = ball_df[ball_df.club == \"football\"]\n    balls = list(zip(ball_df.x.tolist(), ball_df.y.tolist()))\n    return home, away, balls","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Animate One Play","metadata":{}},{"cell_type":"code","source":"from matplotlib import animation\nfrom IPython.display import HTML\ndef animate_one_play(game_id, play_id, df):\n    fig, ax = drawPitch(100, 53.3)\n    \n    home, away, balls = extract_one_game(game_id, play_id, df)\n\n    team_left, = ax.plot([], [], '>', markersize=15, markerfacecolor=\"r\", markeredgewidth=2, markeredgecolor=\"white\", zorder=7)\n    team_right, = ax.plot([], [], '<', markersize=15, markerfacecolor=\"b\", markeredgewidth=2, markeredgecolor=\"white\", zorder=7)\n    ball, = ax.plot([], [], 'o', markersize=20, markerfacecolor=\"black\", markeredgewidth=2, markeredgecolor=\"white\", zorder=7)\n    drawings = [team_left, team_right, ball]\n\n    def init():\n        team_left.set_data([], [])\n        team_right.set_data([], [])\n        ball.set_data([], [])\n        return drawings\n\n    def draw_teams(i):\n        X = []\n        Y = []\n        for k, v in home.items():\n            x, y = v[i]\n            X.append(x)\n            Y.append(y)\n        team_left.set_data(X, Y)\n        \n        X = []\n        Y = []\n        for k, v in away.items():\n            x, y = v[i]\n            X.append(x)\n            Y.append(y)\n        team_right.set_data(X, Y)\n\n    def animate(i):\n        draw_teams(i)\n        \n        x, y = balls[i]\n        ball.set_data([x, y])\n        return drawings\n    \n    # !May take a while!\n    anim = animation.FuncAnimation(fig, animate, init_func=init,\n                                   frames=len(balls), interval=100, blit=True)\n\n    return HTML(anim.to_html5_video())","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Import Animation Libraries","metadata":{}},{"cell_type":"code","source":"import pandas as pd \nimport seaborn as sns \nimport matplotlib.pyplot as plt \n\n# for mpl animation\nimport matplotlib.animation as animation\nfrom matplotlib import rc\nrc('animation', html='html5')\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Define Functions","metadata":{}},{"cell_type":"code","source":"def get_play_by_frame(fid, ax, los, one_play):\n  \"\"\"\n  take one frame from one play, plot a scatter plot image  \n\n  inputs:\n    fid: frame ID  \n    ax: current matplotlib ax  \n    los: line of scrimmage (for aesthetics)  \n    one_play: pandas dataframe for one play  \n\n  output:\n    seaborn axis level scatter plot  \n  \"\"\"\n  # clear current axis (or else you'll have a tracer effect)\n  ax.cla()\n\n  # get game and play IDs\n  gid = one_play['gameId'].unique()[0]\n  pid = one_play['playId'].unique()[0]\n\n  # isolates a given frame within one play\n  one_frame = one_play.loc[one_play['frameId']==fid]\n\n  # create a scatter plot, hard coded dot size to 100 \n  fig1 = sns.scatterplot(x='x',y='y',data=one_frame, \n                         hue='club', ax=ax, s=100)\n  \n  # plots line of scrimmage \n  fig1.axvline(los, c='k', ls=':')\n\n  # plots a simple end zone \n  fig1.axvline(0, c='k', ls='-')\n  fig1.axvline(100, c='k', ls='-')\n\n  # game and play IDs as the title\n  fig1.set_title(f\"game {gid} play {pid}\")\n\n  # takes out the legend (if you leave this, you'll get an annoying legend)\n  fig1.legend([]).set_visible(False)\n\n  # takes out the left, top, and right borders on the graph \n  sns.despine(left=True)\n\n  # no y axis label\n  fig1.set_ylabel('')\n\n  # no y axis tick marks\n  fig1.set_yticks([])\n\n  # set the x and y graph limits to the entire field (from kaggle BDB page)\n  fig1.set_xlim(-10,110)    \n  fig1.set_ylim(0,54) \n\ndef animate_play(one_play):    \n  \"\"\"\n  animate a given NFL play from the BDB  \n\n  inputs: \n    one_play: one play from the BDB data. you will want to \n      filter your dataset using gameId and playId.\n\n  output: \n    animated gif, saved to your current working directory \n\n  \"\"\"\n  # get game and play IDs\n  #from cycler import cycler\n  #custom_cycler = (cycler(color=['b','k','m','g']))\n    \n  gid = one_play['gameId'].unique()[0]\n  pid = one_play['playId'].unique()[0]\n\n  # get line of scrimmage info from the football X location from the  first frame of data \n  los = one_play.loc[(one_play['frameId']==1) & (one_play['club']=='football'), 'x'].values[0]\n\n  # set figure size; this is hard coded but seemed to work well  \n  fig = plt.figure(figsize=(14.4, 6.4))\n  #fig, ax = drawPitch(66, 36)\n  #ax.set_prop_cycle(custom_cycler)\n  #home, away, balls = extract_one_game(gid, pid, one_play)\n\n  # get current axis of the figure\n  ax = fig.gca()\n\n  # matplotlib animate function\n  # relies on get_play_by_frame()  \n  # `interval = 100` is something like frames per second i think \n  # repeat=True is to have the animation continuously repeat  \n  ani = animation.FuncAnimation(fig, get_play_by_frame, \n                                frames=one_play['frameId'].unique().shape[0],\n                                interval=100, repeat=True, \n                                fargs=(ax,los,one_play,))\n  \n  # close the matplotlib figure when done (if you're batch processing gifs, this allows you to end one gif and begin another gif of a play)\n  plt.close()\n\n  # save the matplotlib animation as a gif\n  # requires imagemagick or some sort of gif renderer\n  # this works in google colab if you apt install imagemagick\n  ani.save(f'{gid}_{pid}.gif', writer='imagemagick', fps=10)\n  return ani   ","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data = tracking2024","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def animate_plot(gameId, playId,data):\n    play = data.loc[(data['gameId']==gameId) & (data['playId']==playId)]\n    play.shape\n    animate_play(play)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gameId = 2022101300\nplayId = 826\nplay = data.loc[(data['gameId']==gameId) & (data['playId']==playId)]\nplay.shape\nanimate_play(play)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gameId = 2022102309\nplayId = 3809\n   \nplay = data.loc[(data['gameId']==gameId) & (data['playId']==playId)]\nplay.shape\nanimate_play(play)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gameId = 2022092500\nplayId = 3674\n   \nplay = data.loc[(data['gameId']==gameId) & (data['playId']==playId)]\nplay.shape\nanimate_play(play)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\ngameId = 2022091800\nplayId = 2801\n   \nplay = data.loc[(data['gameId']==gameId) & (data['playId']==playId)]\nplay.shape\nanimate_play(play)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gameId = 2022102700\nplayId = 1224\n   \nplay = data.loc[(data['gameId']==gameId) & (data['playId']==playId)]\nplay.shape\nanimate_play(play)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gameId = 2022100912\nplayId = 1323    \n   \nplay = data.loc[(data['gameId']==gameId) & (data['playId']==playId)]\nplay.shape\nanimate_play(play)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gameId = 2022090800\nplayId = 56\nplay = data.loc[(data['gameId']==gameId) & (data['playId']==playId)]\nplay.shape\nanimate_play(play)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gameId = 2022091100\nplayId = 546\nplay = data.loc[(data['gameId']==gameId) & (data['playId']==playId)]\nplay.shape\nanimate_play(play)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gameId = 2022091108\nplayId = 1948\nplay = data.loc[(data['gameId']==gameId) & (data['playId']==playId)]\nplay.shape\nanimate_play(play)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gameId = 2022091105\nplayId = 4905\nplay = data.loc[(data['gameId']==gameId) & (data['playId']==playId)]\nplay.shape\nanimate_play(play)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Thanks to many supporting kernels. To be updated...","metadata":{}},{"cell_type":"markdown","source":"# Work In Progress","metadata":{}}]}