{"cells":[{"metadata":{"_uuid":"b60bb4db313bed103f5166cde22ae4d498bfd7db"},"cell_type":"markdown","source":"<h1><center>Are you interested in adding something like this to your presentation?</center></h1>\n\n![alttext1](https://media.giphy.com/media/5aZQWbvTGMYCcP023g/giphy.gif)\n\n<h2><center>If so, you're in luck! This kernel details the process I used to create that gif (and many others)!</center></h2>"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import matplotlib\n\nimport matplotlib.pyplot as plt\nfrom matplotlib.patches import *\n\nimport pandas as pd\n\nimport numpy as np\n\nimport os\n\nimport imageio\n\nimport feather\n\nimport seaborn as sns\n\n#Trust me, you'll want a progress bar for the final loop in this notebook\nimport tqdm\nimport os\nprint(os.listdir(\"../input\"))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"2186c83614c136ef586f6eb2a2dfd147fd999191"},"cell_type":"markdown","source":"### The process of making Gifs can eat up a ton of memory. As that can become problematic on most machines, I created custom dtypes (shown in the cell below)."},{"metadata":{"trusted":true,"_uuid":"e790ad496992e7b64a3522f6a6b0d5e4cb6dea92"},"cell_type":"code","source":"#create custom dtypes to conserve memory\ncolumn_types = {'GameKey': 'int64',\n 'PlayID': 'int64',\n 'GSISID': 'float32',\n 'x': 'float32',\n 'y': 'float32',\n 'dis': 'float32',\n 'o': 'float32',\n 'dir': 'float32'}","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"kag_dir = \"../input\"\n\n# If you decided to make feather checkpoints for your play data, that works.\n\n# path = os.path.join(kag_dir,'feather_chks','filename.feather')\n# play = feather.read_dataframe(path)\n\n\n#If you're not working with a feather file, you can use the original CSV files\n\n# For this example, I'll only use one of our NGS files. You can concatenate files as needed or run this notebook multiple times to get what you need.\n\nplay= pd.read_csv(\"../input/NGS-2016-pre.csv\", nrows = 25000, dtype=column_types)\n\nplay.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"5a8fd44ec8505aeb0d231de6e1f918ca5c862f1c"},"cell_type":"code","source":"#Let's drop some superfluous columns to save memory\n\n# Obviously, as you merge dataframes, you can drop (or not drop) variables as you need. \nplay = play.drop(['Season_Year',\n                 'Event'], axis=1)\n\nplay.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"0e7a511f2dc4b8734ee1e907468083d892286354"},"cell_type":"code","source":"# This cell changes the time column to DTG\nplay['Time'] = pd.to_datetime(play[\"Time\"])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"b5c00b0ec29cb72338b272ac5bca8e11d58278e9"},"cell_type":"code","source":"# sort the dataframe\nplay.sort_values(by=['Time'], axis=0, inplace=True)\n\n# set the index to be this and don't drop\nplay.set_index(keys=['Time'], drop=False,inplace=True)\n\n# get a list of names\nnames=play['Time'].unique().tolist()\nprint(names)\n\n#get a list of the unique game ids and play ids\n\ngames=play['GameKey'].unique().tolist()\n\nprint(games)\n\nplays=play['PlayID'].unique().tolist()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"68edf77954912813f62d5dff4915941883ac18b1"},"cell_type":"code","source":"#This lets us know which plays and games we can expect\nprint(plays, games)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"9dd2cefcc3aa4e86090bdacb728a33ea6244346b"},"cell_type":"code","source":"play.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"f26fd2794065c38ec105ad3ca6ec70858bda1a98"},"cell_type":"code","source":"#Creating a dictionary for our dataframes\nlist_of_games_dfs = {}\nfor i in games:\n    list_of_games_dfs[i] = play.loc[play.GameKey==i]","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"ad4b9fa25dd59b8131659365fd422434e8b33787"},"cell_type":"markdown","source":"### Please go check out [Tuan Doan Nguyen's](https://towardsdatascience.com/advanced-sports-visualization-with-pandas-matplotlib-and-seaborn-9c16df80a81b) page on visualizing soccer plays. His work inspiried me to use this method of field plotting (seen in the cell below)."},{"metadata":{"trusted":true,"_uuid":"33e7a32c3410c3933c504f9b0d7ec9491baf77cb"},"cell_type":"code","source":"#This cell draws the football field\ndef draw_field(ax):\n    #field Outline & Center Line\n    field = Rectangle([0,0], width = 120, height = 53.3, fill = False)\n    \n    #All of the yard lines\n    line_10 = ConnectionPatch([10,0], [10,53.3], \"data\", \"data\")\n    line_20 = ConnectionPatch([20,0], [20,53.3], \"data\", \"data\")\n    line_30 = ConnectionPatch([30,0], [30,53.3], \"data\", \"data\")\n    line_40 = ConnectionPatch([40,0], [40,53.3], \"data\", \"data\")\n    line_50 = ConnectionPatch([50,0], [50,53.3], \"data\", \"data\")\n    line_60 = ConnectionPatch([60,0], [60,53.3], \"data\", \"data\")\n    line_70 = ConnectionPatch([70,0], [70,53.3], \"data\", \"data\")\n    line_80 = ConnectionPatch([80,0], [80,53.3], \"data\", \"data\")\n    line_90 = ConnectionPatch([90,0], [90,53.3], \"data\", \"data\")\n    line_100 = ConnectionPatch([100,0], [100,53.3], \"data\", \"data\")\n    line_110 = ConnectionPatch([110,0], [110,53.3], \"data\", \"data\")\n    \n    #Yardlines\n    yardmark_10 = plt.text(18, 50, '10')\n    yardmark_20 = plt.text(28, 50, '20')\n    yardmark_30 = plt.text(38, 50, '30')\n    yardmark_40 = plt.text(48, 50, '40')\n    yardmark_50 = plt.text(58, 50, '50')\n    yardmark_60 = plt.text(68, 50, '40')\n    yardmark_70 = plt.text(78, 50, '30')\n    yardmark_80 = plt.text(88, 50, '20')\n    yardmark_90 = plt.text(98, 50, '10')\n    \n    #Endzones\n    endzone_1 = plt.text(5, 30, 'Home End Zone', rotation = 90)\n    endzone_1 = plt.text(114, 30, 'Visitor End Zone', rotation = 270)\n    \n    element = [field, line_10, line_20,\n               line_30, line_40, line_50, \n               line_60, line_70, line_80,\n              line_90, line_100, line_110]\n    for i in element:\n        ax.add_patch(i)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"01bf0085c2d86888831b5652d5c03742baaa8454"},"cell_type":"markdown","source":"### Before diving into a massive loop, let's see what an instance of a play will look like.\n\n#### In the example below, I use scatterplot and seaborn KDE plot to create a layered instance of play activity. \n\n#### Because Seaborn's KDE plot can be problematic for large data, I will only generate a KDE once to demonstrate some of the layering possibilities."},{"metadata":{"trusted":true,"_uuid":"5dc35aadc1a21682ca25b81b7c5bc7528cc3eab9"},"cell_type":"code","source":"fig=plt.figure() #set up the figures\nfig.set_size_inches(12, 8)\nax=fig.add_subplot(1,1,1)\ndraw_field(ax) #overlay our different objects on the field\n\nplt.ylim(-2, 82)\nplt.xlim(-2, 122)\nplt.axis('off')\n\n#Choose which data to draw for the KDE plot\nsns.kdeplot(list_of_games_dfs[3].loc[(list_of_games_dfs[3].PlayID==3949)].x, \n    list_of_games_dfs[3].loc[(list_of_games_dfs[3].PlayID==3949)].y,\n            color= 'green', shade = \"True\", n_levels = 100, shade_lowest=False);\n\n#Choose the play to visualize\nplt.scatter(list_of_games_dfs[3].loc[(list_of_games_dfs[3].PlayID==3949) & (list_of_games_dfs[3].Time=='2016-08-12 02:26:51.200')].x, \n    list_of_games_dfs[3].loc[(list_of_games_dfs[3].PlayID==3949) & (list_of_games_dfs[3].Time=='2016-08-12 02:26:51.200')].y,\n    color=\"blue\")\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"dd8a45a6114b05037581e76b84e612ac35595636"},"cell_type":"code","source":"#cCreate a folder to the images we will create in the next cell.\nos.mkdir(\"../images\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"65b0c26176d9ef60f492faa6ce51f3cbc01ea71b"},"cell_type":"code","source":"#This is important if you are running through a Jupyter notebook. This will ensure that memory doesn't leak as you are creating PNG images.\nmatplotlib.interactive(False)\n\nima_fldr = \"../images\"\n\n# This loop will create PNG files for every instance of every game in our dataset. \n# Utilizing TQDM here will give display a progress bar that steps by GAMES\nfor i in tqdm.tqdm(list_of_games_dfs):\n    \n    for z in list_of_games_dfs[i].PlayID.unique():\n        \n        times = list_of_games_dfs[i].loc[list_of_games_dfs[i].PlayID==z].Time.unique()\n        \n        for t in times:\n            fig=plt.figure() #set up the figures\n            fig.set_size_inches(12, 8)\n            ax=fig.add_subplot(1,1,1)\n            draw_field(ax) #overlay our different objects on the field\n\n            plt.ylim(-2, 82)\n            plt.xlim(-2, 122)\n            plt.axis('off')\n            \n        \n            plt.scatter(list_of_games_dfs[i].loc[(list_of_games_dfs[i].PlayID==z) & (list_of_games_dfs[i].Time==t)].x, \n                        list_of_games_dfs[i].loc[(list_of_games_dfs[i].PlayID==z) & (list_of_games_dfs[i].Time==t)].y,\n                        color=\"blue\")\n            filename=os.path.join(ima_fldr, \"game\" + str(i), \"play\" + str(z))\n            if not os.path.exists(filename):\n                os.makedirs(filename)\n            plt.savefig(os.path.join(filename, str(t).replace(\" \", \"_\").replace(\":\", \"_\") + \".png\"))\n            # Clear the current axes.\n            plt.cla() \n            # Clear the current figure.\n            plt.clf() \n            # Closes all the figure windows.\n            plt.close('all')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"4308be066192987e532638caf0e3afc0371be923"},"cell_type":"markdown","source":"### At this point, we've created all of the png files needed for creating play gifs for a subset of a season. \n\n### It is my recommendation for you to create all of the png files you'll need (for every season subset you'd like) before running the cell below; the cell below will create gifs for every play (it will recreate gifs for every play folder that exists even if it already existed). "},{"metadata":{"trusted":true,"_uuid":"64662356f767b719db1a4f25afe90705e3e3878a"},"cell_type":"code","source":"#This cell will create GIF files for every play in our image folder.\nfiles = os.listdir(ima_fldr)\nfiles = [ima_fldr + \"/\" + s for s in files]\nfiles\n\nfiles_2 = {}\nfor file in files:\n    files_2[file] = os.listdir(file)\n\ngif_files = []\nfor i in files_2:\n    for x in files_2[i]:\n        gif_files.append(os.path.join(i,x))\n\nfor f in tqdm.tqdm(gif_files):\n    images = []\n    for k in os.listdir(f):\n        images.append(imageio.imread(os.path.join(f, k)))\n    imageio.mimsave(os.path.join(f, 'movie.gif'), images)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"83dcbf4a4e0a586b437539d65626e191b4a1840c"},"cell_type":"markdown","source":"<h2><center>This method lends itself well to adding additional dimensions (ie. player role, velocity, game clock, etc.). As memory is always a concern, add what you want as you see fit. I hope this kernel has provided someone with usable information! </center></h2>"}],"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}