{"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":"### Predicting punt outcomes using punt formations and strategies\n\nThis notebook aims to isolate the impact of punt strategy and formations, on both the offensive and defensive side,  using the tracking and scouting data provided as part of the 2022 big data bowl. The first part of the notebook contains various functions for cleaning and feature engineering the data, and is followed by some EDA on these features and how they relate to outputs, as well as a model that incorporates various pre-snap spatial data and relates uses it to predict the outcomes of a punt.","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport seaborn as sns\nfrom matplotlib import pyplot as plt\npd.set_option('display.max_columns', None)\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"execution":{"iopub.status.busy":"2022-01-06T21:11:07.959777Z","iopub.execute_input":"2022-01-06T21:11:07.960081Z","iopub.status.idle":"2022-01-06T21:11:07.964605Z","shell.execute_reply.started":"2022-01-06T21:11:07.960048Z","shell.execute_reply":"2022-01-06T21:11:07.964003Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Importing Data\npath = '../input/nfl-big-data-bowl-2022/'\ndf_games = pd.read_csv(path + \"games.csv\")\ndf_players = pd.read_csv(path + \"players.csv\")\ndf_pff = pd.read_csv(path + \"PFFScoutingData.csv\")\ndf_plays = pd.read_csv(path + 'plays.csv')","metadata":{"execution":{"iopub.status.busy":"2022-01-06T21:11:07.969508Z","iopub.execute_input":"2022-01-06T21:11:07.96995Z","iopub.status.idle":"2022-01-06T21:11:08.140131Z","shell.execute_reply.started":"2022-01-06T21:11:07.969891Z","shell.execute_reply":"2022-01-06T21:11:08.139138Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#importing\nyear_to_eval = '2019'\ndf_tracking = pd.concat([pd.read_csv(path + \"tracking2019.csv\"),pd.read_csv(path + \"tracking2018.csv\")])","metadata":{"execution":{"iopub.status.busy":"2022-01-06T21:11:08.14191Z","iopub.execute_input":"2022-01-06T21:11:08.142237Z","iopub.status.idle":"2022-01-06T21:12:42.166505Z","shell.execute_reply.started":"2022-01-06T21:11:08.142204Z","shell.execute_reply":"2022-01-06T21:12:42.165537Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#these are columns used to flatten each play frame down to a single row\nplay_columns = ['play_id', 'event',\n                                'game_id',\n                                'play_direction',\n                                'play_time',\n                                'football_x',\n                                'football_y',\n                                'football_s',\n                                'football_a',\n                                'football_dis',\n                                'home_x',\n                                'away_x',\n                                'home_y',\n                                'away_y',\n                                'home_s',\n                                'away_s',\n                                'home_a',\n                                'away_a',\n                                'home_dis',\n                                'away_dis',\n                                'home_o',\n                                'away_o',\n                                'home_dir',\n                                'away_dir',\n                                'home_nflId',\n                                'away_nflId',\n                                'home_jerseyNumber',\n                                'away_jerseyNumber',\n                                'home_position',\n                                'away_position']","metadata":{"execution":{"iopub.status.busy":"2022-01-06T21:12:42.16853Z","iopub.execute_input":"2022-01-06T21:12:42.169565Z","iopub.status.idle":"2022-01-06T21:12:42.178177Z","shell.execute_reply.started":"2022-01-06T21:12:42.169512Z","shell.execute_reply":"2022-01-06T21:12:42.177513Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#flatten play function uses above columns to take a single play and turn it into a flatter df\ndef flatten_play(play):\n    df_play = pd.DataFrame(columns=play_columns)\n    event_list = []\n    for frame in np.unique(play['frameId']):\n        df_frame = (play.loc[play.frameId == frame]).reset_index()\n        home = df_frame.loc[df_frame['team'] == 'home']\n        away = df_frame.loc[df_frame['team'] == 'away']\n        football = df_frame.loc[df_frame['team'] == 'football']\n        play_time = df_frame.time[0]\n        event = df_frame.event[0]\n        event_list.append(event)\n        play_direction = df_frame.playDirection[0]\n        play_id = df_frame.playId[0]\n        game_id = df_frame.gameId[0]\n        for col in ['x','y','s','a','dis','o','dir','nflId','jerseyNumber','position']:\n            df_play.loc[frame,'home_' + col] = home[col].tolist()\n            df_play.loc[frame,'away_' + col] = away[col].tolist()\n        df_play.loc[frame,'play_id'] = play_id\n        df_play.loc[frame,'game_id'] = game_id\n        df_play.loc[frame,'play_direction'] = play_direction\n        df_play.loc[frame,'play_time'] = play_time\n        for col in ['x','y','s','a','dis']:\n            df_play.loc[frame,'football_' + col] = football[col].max()\n    df_play.event = event_list\n    return df_play.reset_index().rename(columns={'index':'frame'})\n\ndef add_unique_id(df,how=None):\n    if how is None:\n        df['game_play_id'] = df.gameId.astype('str') + \"-\" + df.playId.astype('str')\n    else:\n        df['game_play_id'] = df.game_id.astype('str') + \"-\" + df.play_id.astype('str')\n    return df","metadata":{"execution":{"iopub.status.busy":"2022-01-06T21:12:42.180103Z","iopub.execute_input":"2022-01-06T21:12:42.180889Z","iopub.status.idle":"2022-01-06T21:12:42.195881Z","shell.execute_reply.started":"2022-01-06T21:12:42.180833Z","shell.execute_reply":"2022-01-06T21:12:42.194873Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#PFF df but only punts\ndf_pff_punt = df_pff.loc[~df_pff.puntRushers.isnull()].copy()","metadata":{"execution":{"iopub.status.busy":"2022-01-06T21:12:42.197659Z","iopub.execute_input":"2022-01-06T21:12:42.198365Z","iopub.status.idle":"2022-01-06T21:12:42.228382Z","shell.execute_reply.started":"2022-01-06T21:12:42.198324Z","shell.execute_reply":"2022-01-06T21:12:42.227379Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#adding unique key by play and game\ndf_pff_punt = add_unique_id(df_pff_punt)\ndf_pff_punt['year'] = [x[:4] for x in df_pff_punt.gameId.astype('str')] #year from gameId","metadata":{"execution":{"iopub.status.busy":"2022-01-06T21:12:42.229484Z","iopub.execute_input":"2022-01-06T21:12:42.230129Z","iopub.status.idle":"2022-01-06T21:12:42.257097Z","shell.execute_reply.started":"2022-01-06T21:12:42.230085Z","shell.execute_reply":"2022-01-06T21:12:42.256143Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#current year/training punts\npunts_cy = df_pff_punt.loc[df_pff_punt.year.isin(['2018','2019'])]['game_play_id']","metadata":{"execution":{"iopub.status.busy":"2022-01-06T21:12:42.258204Z","iopub.execute_input":"2022-01-06T21:12:42.258442Z","iopub.status.idle":"2022-01-06T21:12:42.269759Z","shell.execute_reply.started":"2022-01-06T21:12:42.258405Z","shell.execute_reply":"2022-01-06T21:12:42.268717Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#adding same key\ndf_tracking = add_unique_id(df_tracking)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T21:12:42.271252Z","iopub.execute_input":"2022-01-06T21:12:42.271474Z","iopub.status.idle":"2022-01-06T21:13:55.600961Z","shell.execute_reply.started":"2022-01-06T21:12:42.271448Z","shell.execute_reply":"2022-01-06T21:13:55.600079Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#tracking but only punts\ndf_tracking_punts = df_tracking.set_index('game_play_id').loc[punts_cy,:]","metadata":{"execution":{"iopub.status.busy":"2022-01-06T21:13:55.602416Z","iopub.execute_input":"2022-01-06T21:13:55.602686Z","iopub.status.idle":"2022-01-06T21:14:24.540372Z","shell.execute_reply.started":"2022-01-06T21:13:55.60265Z","shell.execute_reply":"2022-01-06T21:14:24.53947Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dft = df_tracking_punts.reset_index().rename(columns={'index':'game_play_id'})","metadata":{"execution":{"iopub.status.busy":"2022-01-06T21:14:24.543376Z","iopub.execute_input":"2022-01-06T21:14:24.54362Z","iopub.status.idle":"2022-01-06T21:14:26.818039Z","shell.execute_reply.started":"2022-01-06T21:14:24.543592Z","shell.execute_reply":"2022-01-06T21:14:26.817128Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#flattening all play dataframes to concatenate them\nplay_dfs = []\ncounter = 0\ndenom = len(np.unique(dft.game_play_id))\nfor id in np.unique(dft.game_play_id):\n    counter += 1\n    df = dft.loc[dft.game_play_id == id]\n    play_dfs.append(flatten_play(df))\n    print(counter/denom)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T21:14:26.819522Z","iopub.execute_input":"2022-01-06T21:14:26.821423Z","iopub.status.idle":"2022-01-06T22:15:03.802649Z","shell.execute_reply.started":"2022-01-06T21:14:26.821375Z","shell.execute_reply":"2022-01-06T22:15:03.801922Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tracking_flattened = pd.concat(play_dfs)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:15:03.803974Z","iopub.execute_input":"2022-01-06T22:15:03.804698Z","iopub.status.idle":"2022-01-06T22:15:05.003831Z","shell.execute_reply.started":"2022-01-06T22:15:03.804653Z","shell.execute_reply":"2022-01-06T22:15:05.002353Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tracking_flattened.to_csv('18and19_punts_flattened.csv')","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:15:05.005557Z","iopub.execute_input":"2022-01-06T22:15:05.005852Z","iopub.status.idle":"2022-01-06T22:15:47.8031Z","shell.execute_reply.started":"2022-01-06T22:15:05.005816Z","shell.execute_reply":"2022-01-06T22:15:47.802026Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#can use this tom import a saved version of CSV\n#tracking_flattened = pd.read_csv('2019_punts_flattened.csv',index_col=0)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:15:47.804523Z","iopub.execute_input":"2022-01-06T22:15:47.804789Z","iopub.status.idle":"2022-01-06T22:15:47.808905Z","shell.execute_reply.started":"2022-01-06T22:15:47.804756Z","shell.execute_reply":"2022-01-06T22:15:47.807991Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#add unique id with underscores in column names\ntracking_flattened = add_unique_id(tracking_flattened,how='alt')","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:15:47.810217Z","iopub.execute_input":"2022-01-06T22:15:47.810532Z","iopub.status.idle":"2022-01-06T22:15:48.671096Z","shell.execute_reply.started":"2022-01-06T22:15:47.810495Z","shell.execute_reply":"2022-01-06T22:15:48.669513Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tracking_flattened.reset_index(inplace=True,drop=True)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:15:48.673016Z","iopub.execute_input":"2022-01-06T22:15:48.673313Z","iopub.status.idle":"2022-01-06T22:15:48.680593Z","shell.execute_reply.started":"2022-01-06T22:15:48.673279Z","shell.execute_reply":"2022-01-06T22:15:48.679201Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#all columns with lists\nlist_col =  ['home_x',\n 'away_x',\n 'home_y',\n 'away_y',\n 'home_s',\n 'away_s',\n 'home_a',\n 'away_a',\n 'home_dis',\n 'away_dis',\n 'home_o',\n 'away_o',\n 'home_dir',\n 'away_dir',\n 'home_nflId',\n 'away_nflId',\n 'home_jerseyNumber',\n 'away_jerseyNumber',\n 'home_position',\n 'away_position']","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:15:48.682818Z","iopub.execute_input":"2022-01-06T22:15:48.683125Z","iopub.status.idle":"2022-01-06T22:15:48.693981Z","shell.execute_reply.started":"2022-01-06T22:15:48.683092Z","shell.execute_reply":"2022-01-06T22:15:48.693253Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from ast import literal_eval\ndef read_list(x):\n    return literal_eval(x)\n# only use if importing back in from CSV\n# for c in list_col:\n#     tracking_flattened[c] = tracking_flattened[c].apply(read_list)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:15:48.695719Z","iopub.execute_input":"2022-01-06T22:15:48.696038Z","iopub.status.idle":"2022-01-06T22:15:48.709628Z","shell.execute_reply.started":"2022-01-06T22:15:48.696002Z","shell.execute_reply":"2022-01-06T22:15:48.708325Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#function to plot a single frame to test\ndef plot_frame(df,ind):\n    plt.vlines(x=df.loc[ind,'absoluteYardlineNumber'],colors='b',ymin=0,ymax=53.34,alpha=0.5)\n    sns.scatterplot(df.loc[ind,\"home_x\"],df.loc[ind,\"home_y\"],color='#d65e5e',label='home')\n    sns.scatterplot(df.loc[ind,\"away_x\"],df.loc[ind,\"away_y\"],color='#4272db',label='away')\n    sns.scatterplot([df.loc[ind,\"football_x\"]],[df.loc[ind,\"football_y\"]],color='#542817',label='football')","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:15:48.711792Z","iopub.execute_input":"2022-01-06T22:15:48.712099Z","iopub.status.idle":"2022-01-06T22:15:48.724058Z","shell.execute_reply.started":"2022-01-06T22:15:48.71207Z","shell.execute_reply":"2022-01-06T22:15:48.722988Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#proximity between a list and a point (euclidean distance)\ndef proximity_to_point(x_list,y_list,x_pt,y_pt):\n    foo = ((np.array(x_list) - x_pt) ** 2 + (np.array(y_list) - y_pt) ** 2) ** 0.5\n    return foo.tolist()","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:15:48.725615Z","iopub.execute_input":"2022-01-06T22:15:48.725915Z","iopub.status.idle":"2022-01-06T22:15:48.737461Z","shell.execute_reply.started":"2022-01-06T22:15:48.725882Z","shell.execute_reply":"2022-01-06T22:15:48.736229Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#distance to football\ntracking_flattened['home_dtf'] = tracking_flattened.apply(lambda x:proximity_to_point(x.home_x,x.home_y,x.football_x,x.football_y), axis=1)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:15:48.738917Z","iopub.execute_input":"2022-01-06T22:15:48.739919Z","iopub.status.idle":"2022-01-06T22:16:08.948593Z","shell.execute_reply.started":"2022-01-06T22:15:48.739879Z","shell.execute_reply":"2022-01-06T22:16:08.947606Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#same for away\ntracking_flattened['away_dtf'] = tracking_flattened.apply(lambda x:proximity_to_point(x.away_x,x.away_y,x.football_x,x.football_y), axis=1)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:16:08.950395Z","iopub.execute_input":"2022-01-06T22:16:08.950788Z","iopub.status.idle":"2022-01-06T22:16:28.859487Z","shell.execute_reply.started":"2022-01-06T22:16:08.950734Z","shell.execute_reply":"2022-01-06T22:16:28.858074Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_games = df_games.set_index('gameId')","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:16:28.861365Z","iopub.execute_input":"2022-01-06T22:16:28.861672Z","iopub.status.idle":"2022-01-06T22:16:28.869006Z","shell.execute_reply.started":"2022-01-06T22:16:28.861639Z","shell.execute_reply":"2022-01-06T22:16:28.867557Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_plays['homeTeam'] = [df_games.loc[i,'homeTeamAbbr'] for i in df_plays.gameId]\ndf_plays['awayTeam'] = [df_games.loc[i,'visitorTeamAbbr'] for i in df_plays.gameId]","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:16:28.870603Z","iopub.execute_input":"2022-01-06T22:16:28.870896Z","iopub.status.idle":"2022-01-06T22:16:29.444641Z","shell.execute_reply.started":"2022-01-06T22:16:28.870866Z","shell.execute_reply":"2022-01-06T22:16:29.443306Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#checking to see which team is punting (home or away)\ndf_plays['home_punts'] = df_plays.possessionTeam == df_plays.homeTeam","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:16:29.447183Z","iopub.execute_input":"2022-01-06T22:16:29.447574Z","iopub.status.idle":"2022-01-06T22:16:29.460409Z","shell.execute_reply.started":"2022-01-06T22:16:29.44753Z","shell.execute_reply":"2022-01-06T22:16:29.459665Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_plays = add_unique_id(df_plays).set_index('game_play_id')","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:16:29.46154Z","iopub.execute_input":"2022-01-06T22:16:29.46224Z","iopub.status.idle":"2022-01-06T22:16:29.575494Z","shell.execute_reply.started":"2022-01-06T22:16:29.462202Z","shell.execute_reply":"2022-01-06T22:16:29.574726Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#merging in add'l columns from play data\nfor c in ['home_punts','absoluteYardlineNumber','specialTeamsResult','kickLength','kickReturnYardage','playResult','yardsToGo']:\n    tracking_flattened[c] = [df_plays.loc[i,c] for i in tracking_flattened.game_play_id]","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:16:29.576611Z","iopub.execute_input":"2022-01-06T22:16:29.577636Z","iopub.status.idle":"2022-01-06T22:16:53.524082Z","shell.execute_reply.started":"2022-01-06T22:16:29.577595Z","shell.execute_reply":"2022-01-06T22:16:53.522754Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#the number of vises\ndf_pff_punt['num_vises'] = [0 if x != x else len(x.split(';')) for x in df_pff_punt.vises]\ndf_pff_punt.num_vises.value_counts()","metadata":{"scrolled":true,"execution":{"iopub.status.busy":"2022-01-06T22:16:53.53056Z","iopub.execute_input":"2022-01-06T22:16:53.530851Z","iopub.status.idle":"2022-01-06T22:16:53.548716Z","shell.execute_reply.started":"2022-01-06T22:16:53.530812Z","shell.execute_reply":"2022-01-06T22:16:53.547292Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#the number of rushers\ndf_pff_punt['num_rushers'] = [0 if x != x else len(x.split(';')) for x in df_pff_punt.puntRushers]\ndf_pff_punt.num_rushers.value_counts()","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:16:53.550521Z","iopub.execute_input":"2022-01-06T22:16:53.551339Z","iopub.status.idle":"2022-01-06T22:16:53.567953Z","shell.execute_reply.started":"2022-01-06T22:16:53.551286Z","shell.execute_reply":"2022-01-06T22:16:53.567027Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#number of gunners\ndf_pff_punt['num_gunners'] = [0 if x != x else len(x.split(';')) for x in df_pff_punt.gunners]\ndf_pff_punt.num_gunners.value_counts()","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:16:53.569527Z","iopub.execute_input":"2022-01-06T22:16:53.569984Z","iopub.status.idle":"2022-01-06T22:16:53.592628Z","shell.execute_reply.started":"2022-01-06T22:16:53.569947Z","shell.execute_reply":"2022-01-06T22:16:53.591492Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import matplotlib.patches as patches\n#https://www.kaggle.com/robikscube/nfl-big-data-bowl-plotting-player-position (from last year)\ndef create_football_field(linenumbers=True,\n                          endzones=True,\n                          highlight_line=False,\n                          highlight_line_number=50,\n                          highlighted_name='Line of Scrimmage',\n                          fifty_is_los=False,\n                          figsize=(12, 6.33)):\n    \"\"\"\n    Function that plots the football field for viewing plays.\n    Allows for showing or hiding endzones.\n    \"\"\"\n    rect = patches.Rectangle((0, 0), 120, 53.3, linewidth=0.1,\n                             edgecolor='r', facecolor='darkgreen', zorder=0)\n\n    fig, ax = plt.subplots(1, figsize=figsize)\n    ax.add_patch(rect)\n\n    plt.plot([10, 10, 10, 20, 20, 30, 30, 40, 40, 50, 50, 60, 60, 70, 70, 80,\n              80, 90, 90, 100, 100, 110, 110, 120, 0, 0, 120, 120],\n             [0, 0, 53.3, 53.3, 0, 0, 53.3, 53.3, 0, 0, 53.3, 53.3, 0, 0, 53.3,\n              53.3, 0, 0, 53.3, 53.3, 0, 0, 53.3, 53.3, 53.3, 0, 0, 53.3],\n             color='white')\n    if fifty_is_los:\n        plt.plot([60, 60], [0, 53.3], color='gold')\n        plt.text(62, 50, '<- Player Yardline at Snap', color='gold')\n    # Endzones\n    if endzones:\n        ez1 = patches.Rectangle((0, 0), 10, 53.3,\n                                linewidth=0.1,\n                                edgecolor='r',\n                                facecolor='blue',\n                                alpha=0.2,\n                                zorder=0)\n        ez2 = patches.Rectangle((110, 0), 120, 53.3,\n                                linewidth=0.1,\n                                edgecolor='r',\n                                facecolor='blue',\n                                alpha=0.2,\n                                zorder=0)\n        ax.add_patch(ez1)\n        ax.add_patch(ez2)\n    plt.xlim(0, 120)\n    plt.ylim(-5, 58.3)\n    plt.axis('off')\n    if linenumbers:\n        for x in range(20, 110, 10):\n            numb = x\n            if x > 50:\n                numb = 120 - x\n            plt.text(x, 5, str(numb - 10),\n                     horizontalalignment='center',\n                     fontsize=20,  # fontname='Arial',\n                     color='white')\n            plt.text(x - 0.95, 53.3 - 5, str(numb - 10),\n                     horizontalalignment='center',\n                     fontsize=20,  # fontname='Arial',\n                     color='white', rotation=180)\n    if endzones:\n        hash_range = range(11, 110)\n    else:\n        hash_range = range(1, 120)\n\n    for x in hash_range:\n        ax.plot([x, x], [0.4, 0.7], color='white')\n        ax.plot([x, x], [53.0, 52.5], color='white')\n        ax.plot([x, x], [22.91, 23.57], color='white')\n        ax.plot([x, x], [29.73, 30.39], color='white')\n\n    if highlight_line:\n        hl = highlight_line_number + 10\n        plt.plot([hl, hl], [0, 53.3], color='yellow')\n        plt.text(hl + 2, 50, '<- {}'.format(highlighted_name),\n                 color='yellow')\n    return fig, ax","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:16:53.594735Z","iopub.execute_input":"2022-01-06T22:16:53.595345Z","iopub.status.idle":"2022-01-06T22:16:53.62367Z","shell.execute_reply.started":"2022-01-06T22:16:53.595287Z","shell.execute_reply":"2022-01-06T22:16:53.622041Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_pff_punt = add_unique_id(df_pff_punt).set_index('game_play_id')","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:16:53.625498Z","iopub.execute_input":"2022-01-06T22:16:53.627422Z","iopub.status.idle":"2022-01-06T22:16:53.670621Z","shell.execute_reply.started":"2022-01-06T22:16:53.627157Z","shell.execute_reply":"2022-01-06T22:16:53.669356Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#merging in these new columns and other PFF data\nfor c in ['num_vises','num_rushers','num_gunners','kickContactType','hangTime','snapTime','snapDetail']:\n    tracking_flattened[c] = [df_pff_punt.loc[i,c] for i in tracking_flattened.game_play_id]","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:16:53.672646Z","iopub.execute_input":"2022-01-06T22:16:53.673551Z","iopub.status.idle":"2022-01-06T22:17:17.535679Z","shell.execute_reply.started":"2022-01-06T22:16:53.67348Z","shell.execute_reply":"2022-01-06T22:17:17.53474Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#deprecated - used to determine if the ball changed X direction\n#is_peak = [0]\n# for i in range(1,len(tracking_flattened)-1):\n#     benchmark = tracking_flattened.loc[i,'football_x']\n#     if tracking_flattened.loc[i-1,'football_x'] == benchmark:\n#         is_peak.append(0)\n#     if tracking_flattened.loc[i-1,'football_x'] < benchmark:\n#         if tracking_flattened.loc[i+1,'football_x'] < benchmark:\n#             is_peak.append(1)\n#         else:\n#             is_peak.append(0)\n#     elif tracking_flattened.loc[i-1,'football_x'] > benchmark:\n#         if tracking_flattened.loc[i+1,'football_x'] > benchmark:\n#             is_peak.append(1)\n#         else:\n#             is_peak.append(0)\n# is_peak.append(0)\n#tracking_flattened['ball_dir_chg'] = is_peak","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:17:17.537139Z","iopub.execute_input":"2022-01-06T22:17:17.537389Z","iopub.status.idle":"2022-01-06T22:17:17.541506Z","shell.execute_reply.started":"2022-01-06T22:17:17.537358Z","shell.execute_reply":"2022-01-06T22:17:17.540604Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#features: pff rushers, ngs rushers, num center dropbacks, num vise dropbacks (pff,ngs) (1/3)\n#targets: depth of rusher penetration, avg proximity to returner at time of catch (use dtf), yds gained, play epa (1/4)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:17:17.542983Z","iopub.execute_input":"2022-01-06T22:17:17.544035Z","iopub.status.idle":"2022-01-06T22:17:17.561306Z","shell.execute_reply.started":"2022-01-06T22:17:17.543988Z","shell.execute_reply":"2022-01-06T22:17:17.560083Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#standardizing coords to have all plays going right\nstandardized_coord = dict(zip(['home_x','away_x','football_x','home_y','away_y','football_y'],[[] for x in range(6)]))\ntracking_flattened.play_direction.value_counts()\nfor i in tracking_flattened.index:\n    if tracking_flattened.loc[i,'play_direction'] == 'right':\n        for c in ['home_x','away_x','football_x','home_y','away_y','football_y']:\n            standardized_coord[c].append(np.nan)\n    else:\n        for c in ['home_x','away_x','football_x']:\n            standardized_coord[c].append((100 - np.array(tracking_flattened.loc[i,c])).tolist())\n        for c in ['home_y','away_y','football_y']:\n            standardized_coord[c].append((160/3 - np.array(tracking_flattened.loc[i,c])).tolist())","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:17:17.563276Z","iopub.execute_input":"2022-01-06T22:17:17.564406Z","iopub.status.idle":"2022-01-06T22:17:37.961663Z","shell.execute_reply.started":"2022-01-06T22:17:17.564334Z","shell.execute_reply":"2022-01-06T22:17:37.960544Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#creating a new dataframe with standardized coordinates\ndf_std = pd.DataFrame(standardized_coord)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:17:37.963594Z","iopub.execute_input":"2022-01-06T22:17:37.96395Z","iopub.status.idle":"2022-01-06T22:17:38.865119Z","shell.execute_reply.started":"2022-01-06T22:17:37.963898Z","shell.execute_reply":"2022-01-06T22:17:38.864115Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#appending these to the original dataframe\nfor c in df_std.columns:\n    tracking_flattened[c+'_flipped'] = df_std[c]","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:17:38.869239Z","iopub.execute_input":"2022-01-06T22:17:38.869588Z","iopub.status.idle":"2022-01-06T22:17:38.89479Z","shell.execute_reply.started":"2022-01-06T22:17:38.869554Z","shell.execute_reply":"2022-01-06T22:17:38.893168Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#filling in missing data with unflipped values for plays already going right\ndf = tracking_flattened.copy()\nfor c in ['home_x','away_x','football_x','home_y','away_y','football_y']:\n    df[c+'_flipped'].fillna(df[c], inplace=True)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:17:38.896658Z","iopub.execute_input":"2022-01-06T22:17:38.897772Z","iopub.status.idle":"2022-01-06T22:17:40.836442Z","shell.execute_reply.started":"2022-01-06T22:17:38.897714Z","shell.execute_reply":"2022-01-06T22:17:40.835767Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#sum lists to find how many players are in front of a particular player (x coordinate wise)\ndef sum_lists(team,oppo,lessthan=True):\n    #print(team)\n    if lessthan:\n        return [sum(np.array(oppo) > i) for i in team]\n    else:\n        return [sum(np.array(oppo) <= i) for i in team]","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:17:40.83769Z","iopub.execute_input":"2022-01-06T22:17:40.838806Z","iopub.status.idle":"2022-01-06T22:17:40.845665Z","shell.execute_reply.started":"2022-01-06T22:17:40.838752Z","shell.execute_reply":"2022-01-06T22:17:40.844794Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#adding these features to df\nhome_lt = []\naway_lt = []\nhome_gt = []\naway_gt = []\nfor i in range(len(df)):\n    home_lt.append(sum_lists(df.loc[i,'home_x_flipped'],df.loc[i,'away_x_flipped']))\n    away_lt.append(sum_lists(df.loc[i,'away_x_flipped'],df.loc[i,'home_x_flipped']))\n    home_gt.append(sum_lists(df.loc[i,'home_x_flipped'],df.loc[i,'away_x_flipped'],False))\n    away_gt.append(sum_lists(df.loc[i,'away_x_flipped'],df.loc[i,'home_x_flipped'],False))","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:17:40.846857Z","iopub.execute_input":"2022-01-06T22:17:40.847135Z","iopub.status.idle":"2022-01-06T22:27:16.922891Z","shell.execute_reply.started":"2022-01-06T22:17:40.847106Z","shell.execute_reply":"2022-01-06T22:27:16.92154Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df['home_lt'] = home_lt\ndf['away_lt'] = away_lt\ndf['home_gt'] = home_gt\ndf['away_gt'] = away_gt","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:27:16.925293Z","iopub.execute_input":"2022-01-06T22:27:16.925838Z","iopub.status.idle":"2022-01-06T22:27:18.083868Z","shell.execute_reply.started":"2022-01-06T22:27:16.925748Z","shell.execute_reply":"2022-01-06T22:27:18.082598Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# from celluloid import Camera\n# from IPython.display import HTML\n# import ffmpeg\n\n# #a function to gif an animated play - doesn't work in kaggle\n# def gif_play(play_id,df):\n#     camera = Camera(plt.figure())\n#     first = True\n#     for i in df.loc[df.game_play_id == play_id].index:\n#         plt.xlim(0,120)\n#         plt.ylim(0,53.33)\n#         plt.vlines(x=df.loc[i,'absoluteYardlineNumber'],colors='b',ymin=0,ymax=53.34,alpha=0.5)\n#         sns.scatterplot(df.loc[i,\"home_x\"],df.loc[i,\"home_y\"],color='#d65e5e',label='home')\n#         sns.scatterplot(df.loc[i,\"away_x\"],df.loc[i,\"away_y\"],color='#4272db',label='away')\n#         sns.scatterplot([df.loc[i,\"football_x\"]],[df.loc[i,\"football_y\"]],color='#542817',label='football')\n#         if first == True:\n#             first = False\n#         else:\n#             ax = plt.gca()\n#         ax.legend_ = None\n#         camera.snap()\n#     anim = camera.animate(blit=True)\n#     anim.save('test.gif')","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:34:31.135238Z","iopub.execute_input":"2022-01-06T22:34:31.136159Z","iopub.status.idle":"2022-01-06T22:34:31.140295Z","shell.execute_reply.started":"2022-01-06T22:34:31.136121Z","shell.execute_reply":"2022-01-06T22:34:31.139755Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#num players on line of scrimmage\ndef scrimmage_players(x_coords,y_coords,line,buffer=2.5):\n    x = (abs(np.array(x_coords) - line) < buffer)\n    y = (np.array(y_coords) > 18) & (np.array(y_coords) < 35)\n    tot = x.astype(int) + y\n    return (tot > 1).sum()\n\nhome_scrim = []\naway_scrim = []\nfor i in df.index:\n    home_scrim.append(scrimmage_players(df.loc[i,'home_x_flipped'],df.loc[i,'home_y_flipped'],df.loc[i,'football_x_flipped']))\n    away_scrim.append(scrimmage_players(df.loc[i,'away_x_flipped'],df.loc[i,'away_y_flipped'],df.loc[i,'football_x_flipped']))","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:34:33.444853Z","iopub.execute_input":"2022-01-06T22:34:33.445868Z","iopub.status.idle":"2022-01-06T22:35:09.905392Z","shell.execute_reply.started":"2022-01-06T22:34:33.445825Z","shell.execute_reply":"2022-01-06T22:35:09.904273Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df['home_scrimmage_players'] = home_scrim\ndf['away_scrimmage_players'] = away_scrim","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:35:09.907532Z","iopub.execute_input":"2022-01-06T22:35:09.907863Z","iopub.status.idle":"2022-01-06T22:35:10.229065Z","shell.execute_reply.started":"2022-01-06T22:35:09.907827Z","shell.execute_reply":"2022-01-06T22:35:10.227977Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#events where a punt lands\nland_events = ['punt_received',\n              'fair_catch',\n              'punt_land',\n              'kick_received',\n              'punt_downed']","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:35:10.230516Z","iopub.execute_input":"2022-01-06T22:35:10.230789Z","iopub.status.idle":"2022-01-06T22:35:10.236303Z","shell.execute_reply.started":"2022-01-06T22:35:10.230757Z","shell.execute_reply":"2022-01-06T22:35:10.234993Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#columns that can be taken at snap time\ndefault_col = ['play_id',\n 'game_id',\n 'game_play_id',\n 'home_punts',\n 'absoluteYardlineNumber',\n 'specialTeamsResult',\n 'kickLength',\n 'kickReturnYardage',\n 'yardsToGo',\n 'num_vises',\n 'num_rushers',\n 'num_gunners',\n 'kickContactType',\n 'hangTime',\n 'snapTime',\n 'snapDetail',\n 'football_x_flipped',\n 'football_y_flipped']","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:35:10.239165Z","iopub.execute_input":"2022-01-06T22:35:10.239583Z","iopub.status.idle":"2022-01-06T22:35:10.251792Z","shell.execute_reply.started":"2022-01-06T22:35:10.239535Z","shell.execute_reply":"2022-01-06T22:35:10.250788Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#dataframe of all features\nX = pd.DataFrame(columns = default_col + ['o_scrim','d_scrim','d_close',\n                                          'o_behind','o_close','d_behind','o_closest','d_closest',\n                                         'punter_depth','kr_depth'])","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:35:10.253856Z","iopub.execute_input":"2022-01-06T22:35:10.254239Z","iopub.status.idle":"2022-01-06T22:35:10.269688Z","shell.execute_reply.started":"2022-01-06T22:35:10.254202Z","shell.execute_reply":"2022-01-06T22:35:10.269Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#populating dataframe, regularizing home/away to off/def\nfor i, unique_id in enumerate(np.unique(df.game_play_id)):\n    try:\n        sample_play = df.loc[df.game_play_id == unique_id]\n        snap = sample_play.loc[sample_play.event == 'ball_snap']\n        kick = sample_play.loc[sample_play.loc[sample_play.event == 'punt'].index - 1,:]#frame before punt\n        land = sample_play.loc[sample_play.event.isin(land_events)].head(1)\n        for c in default_col:\n            X.loc[i,c] = snap[c].item()\n        if snap.home_punts.item() == True:#home team is offense\n            #print('home_complete')\n            X.loc[i,'punter_depth'] = snap.football_x_flipped.item() - np.array(snap.home_x_flipped.item()).min()\n            X.loc[i,'kr_depth'] = np.array(snap.away_x_flipped.item()).max() - snap.football_x_flipped.item()\n            X.loc[i,'o_scrim'] = snap.home_scrimmage_players.item()\n            X.loc[i,'d_scrim'] = snap.away_scrimmage_players.item()\n            X.loc[i,'d_close'] = (np.array(kick.away_dtf.item()) < 5).sum()\n            X.loc[i,'d_closest'] = (np.array(kick.away_dtf.item())).min()\n            X.loc[i,'o_behind'] = (np.array(kick.home_lt.item()) > 7).sum()\n            X.loc[i,'o_close'] = (np.array(kick.home_dtf.item()) < 10).sum()\n            X.loc[i,'o_closest'] = (np.array(kick.home_dtf.item())).min()\n            X.loc[i,'d_behind'] = (np.array(kick.away_gt.item()) > 7).sum()\n        else:\n            #print('away_complete')\n            X.loc[i,'punter_depth'] = snap.football_x_flipped.item() - np.array(snap.away_x_flipped.item()).min()\n            X.loc[i,'kr_depth'] = np.array(snap.home_x_flipped.item()).max() - snap.football_x_flipped.item()\n            X.loc[i,'o_scrim'] = snap.away_scrimmage_players.item()\n            X.loc[i,'d_scrim'] = snap.home_scrimmage_players.item()\n            X.loc[i,'d_close'] = (np.array(kick.home_dtf.item()) < 5).sum()\n            X.loc[i,'d_closest'] = (np.array(kick.home_dtf.item())).min()\n            X.loc[i,'o_behind'] = (np.array(kick.away_lt.item()) > 7).sum()\n            X.loc[i,'o_close'] = (np.array(kick.away_dtf.item()) < 10).sum()\n            X.loc[i,'o_closest'] = (np.array(kick.away_dtf.item())).min()\n            X.loc[i,'d_behind'] = (np.array(kick.home_gt.item()) > 7).sum()\n    except Exception as e:\n        print(unique_id,'failed') #simple catch for weird rows","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:35:10.271206Z","iopub.execute_input":"2022-01-06T22:35:10.271703Z","iopub.status.idle":"2022-01-06T22:36:55.258903Z","shell.execute_reply.started":"2022-01-06T22:35:10.271647Z","shell.execute_reply":"2022-01-06T22:36:55.257244Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data = X.copy()","metadata":{"scrolled":true,"execution":{"iopub.status.busy":"2022-01-06T22:36:55.263062Z","iopub.execute_input":"2022-01-06T22:36:55.263378Z","iopub.status.idle":"2022-01-06T22:36:55.27066Z","shell.execute_reply.started":"2022-01-06T22:36:55.263335Z","shell.execute_reply":"2022-01-06T22:36:55.269371Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Data wrangling done, EDA below\n\nThis next part of the data explores how some of the features relate to various outcomes of a play. This shows that the pre-snap strategy can be crucial in determining the outcomes of a play, and opens the door to modeling how the offensive and defensive tactics interact.","metadata":{}},{"cell_type":"code","source":"sns.kdeplot(x='num_rushers',hue='specialTeamsResult',data=data)\nplt.title('Number of Rushers vs. Kick Result\\nFewer rushers means a higher return likelihood')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:36:55.272234Z","iopub.execute_input":"2022-01-06T22:36:55.272698Z","iopub.status.idle":"2022-01-06T22:36:55.762608Z","shell.execute_reply.started":"2022-01-06T22:36:55.272665Z","shell.execute_reply":"2022-01-06T22:36:55.761818Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.kdeplot(x='d_scrim',hue='specialTeamsResult',data=data)\nplt.title('Number of Defense Players on Line of Scrimmage vs. Kick Result\\nAlmost More players closer to scrimmage means relatively more likely to fair catch')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:36:55.763989Z","iopub.execute_input":"2022-01-06T22:36:55.764849Z","iopub.status.idle":"2022-01-06T22:36:56.111582Z","shell.execute_reply.started":"2022-01-06T22:36:55.764802Z","shell.execute_reply":"2022-01-06T22:36:56.110626Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.kdeplot(x='num_vises',hue='specialTeamsResult',data=data)\nplt.title('Number of Vises vs. Kick Result\\nAlmost More vises means greater return probability')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:36:56.115868Z","iopub.execute_input":"2022-01-06T22:36:56.116224Z","iopub.status.idle":"2022-01-06T22:36:56.439018Z","shell.execute_reply.started":"2022-01-06T22:36:56.116185Z","shell.execute_reply":"2022-01-06T22:36:56.43817Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.kdeplot(x='football_x_flipped',hue='specialTeamsResult',data=data)\nplt.xlabel('Line of Scrimmage')\nplt.title('Line of Scrimmage vs. Play Result\\nPunts are more likely to be returned when the offense is pinned back deeper')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:36:56.440401Z","iopub.execute_input":"2022-01-06T22:36:56.440718Z","iopub.status.idle":"2022-01-06T22:36:56.77283Z","shell.execute_reply.started":"2022-01-06T22:36:56.440684Z","shell.execute_reply":"2022-01-06T22:36:56.771895Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"create_football_field(endzones=False)\nsns.scatterplot(x='football_x_flipped',y='football_y_flipped',data=data.loc[data.specialTeamsResult=='Blocked Punt'].head(26))\nplt.title('Where are Punts Getting Blocked?')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:36:56.774278Z","iopub.execute_input":"2022-01-06T22:36:56.774523Z","iopub.status.idle":"2022-01-06T22:36:57.607116Z","shell.execute_reply.started":"2022-01-06T22:36:56.774491Z","shell.execute_reply":"2022-01-06T22:36:57.605869Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#This inspired me to look at if there's a pattern along the y-axis, but that's just where most punts happen regardless\nsns.kdeplot(x='football_y_flipped',hue='specialTeamsResult',data=data)\nplt.xlabel('Y Coordinate at snap')\nplt.title('Location along y-axis vs. Play Result')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:36:57.608846Z","iopub.execute_input":"2022-01-06T22:36:57.60945Z","iopub.status.idle":"2022-01-06T22:36:57.964629Z","shell.execute_reply.started":"2022-01-06T22:36:57.609407Z","shell.execute_reply":"2022-01-06T22:36:57.963025Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.regplot(x=data.num_rushers.astype('float'),y=data.d_closest.astype('float'))\nplt.title('Number of Rushers vs. Distance to Football of Closest Defensive Player at Kick Time')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:36:57.966192Z","iopub.execute_input":"2022-01-06T22:36:57.966623Z","iopub.status.idle":"2022-01-06T22:36:58.431522Z","shell.execute_reply.started":"2022-01-06T22:36:57.966587Z","shell.execute_reply":"2022-01-06T22:36:58.430431Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.regplot(x=data.num_rushers.astype('float'),y=data.o_closest.astype('float'))\nplt.title('Number of Rushers vs. Distance to Football of Closest Offesnive Player at Catch/Land Time')\nplt.ylim(0,10)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:36:58.433792Z","iopub.execute_input":"2022-01-06T22:36:58.434568Z","iopub.status.idle":"2022-01-06T22:36:58.879806Z","shell.execute_reply.started":"2022-01-06T22:36:58.434509Z","shell.execute_reply":"2022-01-06T22:36:58.878319Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Classifying Punt Outcomes Based on Pre-Snap Characteristics\n\nFrom the EDA above, and from the nature of the features included in `data`, the following features can be used to classify the various outcomes of a punt:\n- yardsToGo\n- num_vises\n- num_rushers\n- num_gunners\n- football_x_flipped (at snap time)\n- football_y_flipped (at snap time)\n- o_scrim (offensive players on line of scrimmage)\n- d_scrim (defensive players on line of scrimmage)\n- punter_depth (distance between punter and football at snap time)\n- kr_depth (distance between returned and football at snap time)\n\nTarget: `specialTeamsResult` (categorical)","metadata":{}},{"cell_type":"code","source":"features_to_use = ['yardsToGo',\n 'num_vises',\n 'num_rushers',\n 'num_gunners',\n 'football_x_flipped',\n 'football_y_flipped',\n 'o_scrim',\n 'd_scrim',\n 'punter_depth',\n 'kr_depth']","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:36:58.882179Z","iopub.execute_input":"2022-01-06T22:36:58.882558Z","iopub.status.idle":"2022-01-06T22:36:58.889251Z","shell.execute_reply.started":"2022-01-06T22:36:58.882516Z","shell.execute_reply":"2022-01-06T22:36:58.887451Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X = data[features_to_use].fillna(0)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:36:58.891362Z","iopub.execute_input":"2022-01-06T22:36:58.891735Z","iopub.status.idle":"2022-01-06T22:36:58.915644Z","shell.execute_reply.started":"2022-01-06T22:36:58.891676Z","shell.execute_reply":"2022-01-06T22:36:58.91466Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"y = data.specialTeamsResult","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:36:58.917771Z","iopub.execute_input":"2022-01-06T22:36:58.91806Z","iopub.status.idle":"2022-01-06T22:36:58.927582Z","shell.execute_reply.started":"2022-01-06T22:36:58.918026Z","shell.execute_reply":"2022-01-06T22:36:58.926864Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n#startifying train and test datasets according to outcomes to have proportionally equal representation\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=8,stratify = y)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:36:58.928761Z","iopub.execute_input":"2022-01-06T22:36:58.929276Z","iopub.status.idle":"2022-01-06T22:36:59.199619Z","shell.execute_reply.started":"2022-01-06T22:36:58.929239Z","shell.execute_reply":"2022-01-06T22:36:59.198461Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from catboost import CatBoostClassifier\nmodel = CatBoostClassifier(iterations=2000,random_state=8)\nmodel.fit(X_train,y_train)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:36:59.201206Z","iopub.execute_input":"2022-01-06T22:36:59.201487Z","iopub.status.idle":"2022-01-06T22:37:10.989808Z","shell.execute_reply.started":"2022-01-06T22:36:59.201454Z","shell.execute_reply":"2022-01-06T22:37:10.989057Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model.score(X_test,y_test)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:37:10.991466Z","iopub.execute_input":"2022-01-06T22:37:10.992167Z","iopub.status.idle":"2022-01-06T22:37:11.014123Z","shell.execute_reply.started":"2022-01-06T22:37:10.992124Z","shell.execute_reply":"2022-01-06T22:37:11.013182Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dict(zip(X.columns,model.feature_importances_))","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:37:11.015487Z","iopub.execute_input":"2022-01-06T22:37:11.015756Z","iopub.status.idle":"2022-01-06T22:37:11.025605Z","shell.execute_reply.started":"2022-01-06T22:37:11.015722Z","shell.execute_reply":"2022-01-06T22:37:11.023987Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#A sampled look at how the model performs for each outcome\nfor result in np.unique(y_test):\n    ind = (y_test.loc[y_test == result]).index[0]\n    #print(ind)\n    predictions = model.predict_proba(X_test.loc[ind,:])\n    pred_df = pd.DataFrame.from_dict(dict(zip(model.classes_,predictions)), orient='index').rename(columns={0:'probability'})\n    print('Actual Result:',result)\n    print('Predictions:')\n    print(pred_df)\n    print('-----')","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:37:11.027124Z","iopub.execute_input":"2022-01-06T22:37:11.027435Z","iopub.status.idle":"2022-01-06T22:37:11.084646Z","shell.execute_reply.started":"2022-01-06T22:37:11.027403Z","shell.execute_reply":"2022-01-06T22:37:11.083418Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Putting together a table of predicted classes and actual classes for model evaluation\ndf_pred_labels = pd.DataFrame(model.predict_proba(X_test),columns=['p'+ x for x in model.classes_])\ndf_pred_labels['actual'] = y_test.reset_index(drop=True)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:37:11.086014Z","iopub.execute_input":"2022-01-06T22:37:11.086256Z","iopub.status.idle":"2022-01-06T22:37:11.108848Z","shell.execute_reply.started":"2022-01-06T22:37:11.086229Z","shell.execute_reply":"2022-01-06T22:37:11.107597Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for c in model.classes_:\n    sns.kdeplot('p'+c,data=df_pred_labels,hue='actual')\n    plt.title('Predicted likelihood of ' +  c + ' colored by actual outcome')\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:37:11.110158Z","iopub.execute_input":"2022-01-06T22:37:11.110481Z","iopub.status.idle":"2022-01-06T22:37:13.437536Z","shell.execute_reply.started":"2022-01-06T22:37:11.11044Z","shell.execute_reply":"2022-01-06T22:37:13.436455Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for c in model.classes_:\n    sns.kdeplot('p'+c,data=df_pred_labels.loc[df_pred_labels.actual == c], label = c)\n    plt.title('Predicted likelihood of ' +  c + ' when actual outcome was ' + c)\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2022-01-06T22:37:13.438805Z","iopub.execute_input":"2022-01-06T22:37:13.439048Z","iopub.status.idle":"2022-01-06T22:37:15.056587Z","shell.execute_reply.started":"2022-01-06T22:37:13.43902Z","shell.execute_reply":"2022-01-06T22:37:15.055222Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Takeaways and building blocks for future work\n\nThe actionable recommendations from this model would be to make choices according to model suggestions. For example, if your current tactical choices are leading to a high probability of downed punts, it might be more beneficial to switch towards a more block-oriented punt defense strategy, as you'd be conceding little return yardage in that case. Conversely, if the situation has a high return probability, you could drop back even more defenders to maximize the value gained from a likely return.\n\nUltimately, the model performance is decent, but leaves room for improvement - especially with more data (or even the computational/time ability to use the 2020/2021 data in this notebook). Another major few areas of room for improvement would include addition data about tactical choices, including protection shifting on offense at the line of scrimmage, or the assignments for players crossing the line of scrimmage to hold-up defenders. Despite this, I think this lends some insight into ","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}