{"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":"First we have to install a couple of libraries in order to make the visualizations. The important one is [ptplot](https://github.com/AndrewRook/ptplot), which provides a ggplot2-like interface to the extremely powerful [bokeh](https://bokeh.org/) interactive visualization package. ","metadata":{}},{"cell_type":"code","source":"!conda install nodejs -y\n!pip install ptplot","metadata":{"execution":{"iopub.status.busy":"2021-09-24T13:16:03.413357Z","iopub.execute_input":"2021-09-24T13:16:03.413674Z","iopub.status.idle":"2021-09-24T13:16:52.163881Z","shell.execute_reply.started":"2021-09-24T13:16:03.413596Z","shell.execute_reply":"2021-09-24T13:16:52.162998Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Imports!","metadata":{"execution":{"iopub.status.busy":"2021-09-24T01:33:22.142435Z","iopub.execute_input":"2021-09-24T01:33:22.142851Z","iopub.status.idle":"2021-09-24T01:33:22.152316Z","shell.execute_reply.started":"2021-09-24T01:33:22.142812Z","shell.execute_reply":"2021-09-24T01:33:22.150539Z"}}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\n\nfrom ptplot import PTPlot\nfrom ptplot.animation import Animation\nfrom ptplot.hover import Hover\nfrom ptplot.nfl import Aesthetics, Field\nfrom ptplot.plot import Positions\n\nfrom bokeh.plotting import show\nfrom bokeh.io import output_notebook\n\noutput_notebook()","metadata":{"execution":{"iopub.status.busy":"2021-09-24T13:16:52.166093Z","iopub.execute_input":"2021-09-24T13:16:52.166354Z","iopub.status.idle":"2021-09-24T13:16:55.988243Z","shell.execute_reply.started":"2021-09-24T13:16:52.166321Z","shell.execute_reply":"2021-09-24T13:16:55.987096Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now we'll load the raw data in.","metadata":{}},{"cell_type":"code","source":"scouting_data = pd.read_csv(\"../input/nfl-big-data-bowl-2022/PFFScoutingData.csv\")\ngame_data = pd.read_csv(\"../input/nfl-big-data-bowl-2022/games.csv\")\nplay_data = pd.read_csv(\"../input/nfl-big-data-bowl-2022/plays.csv\")\ntracking_data_2018 = pd.read_csv(\"../input/nfl-big-data-bowl-2022/tracking2018.csv\")\nscouting_data.shape, game_data.shape, play_data.shape, tracking_data_2018.shape","metadata":{"execution":{"iopub.status.busy":"2021-09-24T13:16:55.990705Z","iopub.execute_input":"2021-09-24T13:16:55.991042Z","iopub.status.idle":"2021-09-24T13:17:37.698383Z","shell.execute_reply.started":"2021-09-24T13:16:55.990999Z","shell.execute_reply":"2021-09-24T13:17:37.697145Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We'll pick an [interesting return somewhat at random as the example](https://www.youtube.com/watch?v=fIaNbxIQgxI), then join all the data together.","metadata":{}},{"cell_type":"code","source":"sample_play = play_data[(play_data.gameId == 2018100702) & (play_data.playId == 1828)]\nsample_play_joined = sample_play.merge(\n    tracking_data_2018, on=[\"gameId\", \"playId\"], how=\"inner\"\n).merge(\n    game_data, on=\"gameId\", how=\"inner\"\n).merge(scouting_data, on=[\"gameId\", \"playId\"], how=\"inner\")\n\nsample_play_joined.shape","metadata":{"execution":{"iopub.status.busy":"2021-09-24T13:27:30.914222Z","iopub.execute_input":"2021-09-24T13:27:30.915069Z","iopub.status.idle":"2021-09-24T13:27:35.229998Z","shell.execute_reply.started":"2021-09-24T13:27:30.915013Z","shell.execute_reply":"2021-09-24T13:27:35.229007Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Some preprocessing to fill null values for the team number and add team names to the data.","metadata":{}},{"cell_type":"code","source":"# I want the jersey number to be a string with NA values filled by \"\", which is surprisingly\n# convoluted to do:\nsample_play_joined[\"jerseyNumber\"] = sample_play_joined.jerseyNumber.fillna(0).astype(int).astype(str)\nsample_play_joined.loc[sample_play_joined.jerseyNumber == \"0\", \"jerseyNumber\"] = \"\"\n\n# First get home/away team abbreviations, then insert \"football\" for all rows relating to the\n# ball's position:\nsample_play_joined[\"team_abbr\"] = sample_play_joined.homeTeamAbbr.where(\n    sample_play_joined.team == 'home', sample_play_joined.visitorTeamAbbr\n)\nsample_play_joined.loc[sample_play_joined.displayName == \"football\", \"team_abbr\"] = \"football\"","metadata":{"execution":{"iopub.status.busy":"2021-09-24T13:27:35.231471Z","iopub.execute_input":"2021-09-24T13:27:35.231707Z","iopub.status.idle":"2021-09-24T13:27:35.249639Z","shell.execute_reply.started":"2021-09-24T13:27:35.231680Z","shell.execute_reply":"2021-09-24T13:27:35.248711Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now let's show an animation of the play, showing the position and orientation of all the players and the ball at each frame. We'll also add color-coding by team, jersey numbers, and a simple hover label with player names to make the plot easier to read.","metadata":{}},{"cell_type":"code","source":"plot = (\n    PTPlot(data=sample_play_joined, pixel_height=300) \n    + Field() \n    + Positions(\n        \"x-10\", \"y\", orientation=\"o\", number=\"jerseyNumber\",\n        name=\"positions\", marker_radius=1.2\n    )\n    + Aesthetics(team_ball_mapping=\"team_abbr\", home_away_mapping=\"team == 'home'\", ball_identifier=\"football\")\n    + Hover([(\"name\", \"@displayName\")], \"positions\", [\"displayName\"])\n    + Animation(\"frameId\", 10)\n)\n    \nshow(plot.draw())","metadata":{"execution":{"iopub.status.busy":"2021-09-24T13:27:40.905572Z","iopub.execute_input":"2021-09-24T13:27:40.905885Z","iopub.status.idle":"2021-09-24T13:27:41.508063Z","shell.execute_reply.started":"2021-09-24T13:27:40.905859Z","shell.execute_reply":"2021-09-24T13:27:41.507184Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Interestingly, you can see that there's a small error with the ball position, where it briefly goes behind the punter. We can plot one of the frames where that's happening to make it really clear.","metadata":{}},{"cell_type":"code","source":"plot = (\n    PTPlot(data=sample_play_joined, pixel_height=300) \n    + Field() \n    + Positions(\n        \"x-10\", \"y\", orientation=\"o\", number=\"jerseyNumber\",\n        name=\"positions\", marker_radius=1.2, frame_filter=\"frameId == 23\"\n    )\n    + Aesthetics(team_ball_mapping=\"team_abbr\", home_away_mapping=\"team == 'home'\", ball_identifier=\"football\")\n    + Hover([(\"Name\", \"@displayName\")], \"positions\", [\"displayName\"])\n)\n    \nshow(plot.draw())","metadata":{"execution":{"iopub.status.busy":"2021-09-24T13:27:47.540785Z","iopub.execute_input":"2021-09-24T13:27:47.541074Z","iopub.status.idle":"2021-09-24T13:27:48.136953Z","shell.execute_reply.started":"2021-09-24T13:27:47.541048Z","shell.execute_reply":"2021-09-24T13:27:48.135980Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"That might be something that needs to be accounted for before building any kind of model off this data, especially if it's common.\n\nAnother thing I was wondering is what some of the player designations in the scouting report data meant, especially who are the \"vises\". Let's add those designations to the hover labels.","metadata":{}},{"cell_type":"code","source":"team_abbr_plus_number = sample_play_joined.team_abbr.str.cat(sample_play_joined.jerseyNumber, sep=\" \")\n# this is yucky but there's no clean way to to an n-n string contains operation in pandas\nis_gunner = np.core.char.find(\n    sample_play_joined.gunners.values.astype(str),team_abbr_plus_number.values.astype(str)\n) != -1\nis_vise = np.core.char.find(\n    sample_play_joined.vises.values.astype(str),team_abbr_plus_number.values.astype(str)\n) != -1\nis_rusher = np.core.char.find(\n    sample_play_joined.puntRushers.values.astype(str),team_abbr_plus_number.values.astype(str)\n) != -1\n\nsample_play_joined[\"player_role\"] = \"Not Specified\"\nsample_play_joined.loc[is_gunner, \"player_role\"] = \"Gunner\"\nsample_play_joined.loc[is_vise, \"player_role\"] = \"Vise\"\nsample_play_joined.loc[is_rusher, \"player_role\"] = \"Rusher\"\n","metadata":{"execution":{"iopub.status.busy":"2021-09-24T13:44:51.299128Z","iopub.execute_input":"2021-09-24T13:44:51.300287Z","iopub.status.idle":"2021-09-24T13:44:51.322216Z","shell.execute_reply.started":"2021-09-24T13:44:51.300242Z","shell.execute_reply":"2021-09-24T13:44:51.321373Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plot = (\n    PTPlot(data=sample_play_joined, pixel_height=350) \n    + Field() \n    + Positions(\n        \"x-10\", \"y\", orientation=\"o\", number=\"jerseyNumber\",\n        name=\"positions\", marker_radius=1.2\n    )\n    + Aesthetics(team_ball_mapping=\"team_abbr\", home_away_mapping=\"team == 'home'\", ball_identifier=\"football\")\n    + Hover(\n        [(\"Name\", \"@displayName\"), (\"Role\", \"@player_role\")], \n        \"positions\", [\"displayName\", \"player_role\"]\n    )\n    + Animation(\"frameId\", 10)\n)\n    \nshow(plot.draw())","metadata":{"execution":{"iopub.status.busy":"2021-09-24T13:47:06.317398Z","iopub.execute_input":"2021-09-24T13:47:06.317932Z","iopub.status.idle":"2021-09-24T13:47:06.803229Z","shell.execute_reply.started":"2021-09-24T13:47:06.317893Z","shell.execute_reply":"2021-09-24T13:47:06.802469Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Oh, ok. The gunners are the players on the punting team lined up near the sidelines who attempt to make the tackle, while the vises are the players on the returning team who are trying to keep the gunners away from the returner.","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}