{"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":"# Big Data Bowl Data EDA\n\nThis notebook was created during a live coding session. [Check it out the stream here.](https://www.twitch.tv/medallionstallion_)","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pylab as plt\nimport seaborn as sns\n\npd.set_option('max_columns', 100)\nplt.style.use('ggplot')\ncolor_pal = plt.rcParams['axes.prop_cycle'].by_key()['color']","metadata":{"execution":{"iopub.status.busy":"2021-10-01T02:22:39.234140Z","iopub.execute_input":"2021-10-01T02:22:39.234890Z","iopub.status.idle":"2021-10-01T02:22:39.242172Z","shell.execute_reply.started":"2021-10-01T02:22:39.234848Z","shell.execute_reply":"2021-10-01T02:22:39.241237Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Data Overview\nIn this competition we are provided with player, game and player stats for special teams plays in the 2018-2020 NFL Seasons. We are also provided tracking data with each players position during the plays.","metadata":{}},{"cell_type":"code","source":"!ls -lh ../input/nfl-big-data-bowl-2022/","metadata":{"execution":{"iopub.status.busy":"2021-10-01T01:35:55.201670Z","iopub.execute_input":"2021-10-01T01:35:55.202045Z","iopub.status.idle":"2021-10-01T01:35:56.000612Z","shell.execute_reply.started":"2021-10-01T01:35:55.202004Z","shell.execute_reply":"2021-10-01T01:35:55.999631Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Loading in game, players and plays files\ngames = pd.read_csv('../input/nfl-big-data-bowl-2022/games.csv')\nplayers = pd.read_csv('../input/nfl-big-data-bowl-2022/players.csv')\nplays = pd.read_csv('../input/nfl-big-data-bowl-2022/plays.csv')\n\nplays = plays.merge(games, on=['gameId'],\n            how='left',\n            validate='m:1')","metadata":{"execution":{"iopub.status.busy":"2021-10-01T01:37:52.124560Z","iopub.execute_input":"2021-10-01T01:37:52.124844Z","iopub.status.idle":"2021-10-01T01:37:52.257568Z","shell.execute_reply.started":"2021-10-01T01:37:52.124816Z","shell.execute_reply":"2021-10-01T01:37:52.256544Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Player Counts\n- What are the most common positions found in the player file?","metadata":{}},{"cell_type":"code","source":"players['Position'].value_counts() \\\n    .sort_values(ascending=True) \\\n    .plot(kind='barh', figsize=(10, 15\n                               ),\n         title='Count of Players by Position')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-10-01T01:40:15.566823Z","iopub.execute_input":"2021-10-01T01:40:15.567190Z","iopub.status.idle":"2021-10-01T01:40:15.944111Z","shell.execute_reply.started":"2021-10-01T01:40:15.567155Z","shell.execute_reply":"2021-10-01T01:40:15.943006Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Play Info\n- Almost 20,000 plays.\n- 7,800 kickoffs, 6,000 Punts, 3,400 Extra points, and 2,600 Field Goals.","metadata":{}},{"cell_type":"code","source":"plays['specialTeamsPlayType'].value_counts().to_frame()","metadata":{"execution":{"iopub.status.busy":"2021-10-01T01:41:04.958860Z","iopub.execute_input":"2021-10-01T01:41:04.959506Z","iopub.status.idle":"2021-10-01T01:41:04.974237Z","shell.execute_reply.started":"2021-10-01T01:41:04.959441Z","shell.execute_reply":"2021-10-01T01:41:04.973024Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# What Do we know about kickoffs?\n- 60% Touchback / 37% Returned","metadata":{}},{"cell_type":"code","source":"(plays.query('specialTeamsPlayType == \"Kickoff\"')['specialTeamsResult'] \\\n    .value_counts() / len(plays.query('specialTeamsPlayType == \"Kickoff\"'))) \\\n    .to_frame()","metadata":{"execution":{"iopub.status.busy":"2021-10-01T01:41:06.696794Z","iopub.execute_input":"2021-10-01T01:41:06.697113Z","iopub.status.idle":"2021-10-01T01:41:06.737471Z","shell.execute_reply.started":"2021-10-01T01:41:06.697063Z","shell.execute_reply":"2021-10-01T01:41:06.736455Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# What do we know about Punts?\n- 38% Returned, 27% Fair Catch, 13.8% Out of Bounds\n- 3,926 Punts resulted in a fair catch or return","metadata":{}},{"cell_type":"code","source":"(plays.query('specialTeamsPlayType == \"Punt\"')['specialTeamsResult'] \\\n    .value_counts() / len(plays.query('specialTeamsPlayType == \"Punt\"'))) \\\n    .to_frame()","metadata":{"execution":{"iopub.status.busy":"2021-10-01T01:41:13.574818Z","iopub.execute_input":"2021-10-01T01:41:13.575362Z","iopub.status.idle":"2021-10-01T01:41:13.600641Z","shell.execute_reply.started":"2021-10-01T01:41:13.575326Z","shell.execute_reply":"2021-10-01T01:41:13.600014Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Processing Play Data\nTo assist in reviewing the play data we will apply some processing to add features about each play.","metadata":{}},{"cell_type":"code","source":"def add_seconds_into_game(df):\n    \"\"\"\n    Takes in a dataframe with \"gameClock\" column.\n    \n    Adds secondsOfGameTime column\n    \"\"\"\n    game_clock_minutes = df['gameClock'].str.split(':', expand=True)[0].astype('int')\n    game_clock_sec = df['gameClock'].str.split(':', expand=True)[1].astype('int')\n    gameClockSeconds = game_clock_minutes * 60 + game_clock_sec\n    df['secondsOfGameTime'] = (df['quarter'] * 15 * 60) - gameClockSeconds\n    return df\n\ndef process_play_data(plays, players):\n    plays['returnTeam'] = plays \\\n        .apply(lambda row: row['visitorTeamAbbr'] if row['possessionTeam'] == row['homeTeamAbbr'] else row['homeTeamAbbr'],\n                                                        axis=1)\n    \n    # Calculate the absolute yardline relative to the possession team.\n    plays['yardlineNumberAbs'] = plays['yardlineNumber']\n    plays.loc[plays['yardlineSide'] == plays['returnTeam'],\n              'yardlineNumberAbs'] = \\\n        (50 - plays.loc[plays['yardlineSide'] == plays['returnTeam']]['yardlineNumberAbs']) + 50\n\n    # Mapping Players to positions\n    player_pos_map = players.set_index('nflId')['Position'].to_dict()\n    plays['kickerPos'] = plays['kickerId'].map(player_pos_map)\n    # Expand \n    plays['returnerId1'] = plays['returnerId'].str.split(';', expand=True)[0]\n    plays['returnerId2'] = plays['returnerId'].str.split(';', expand=True)[1]\n    \n    # Seconds within game\n    plays = add_seconds_into_game(plays)\n    \n    # Team Scores and score differential.\n    plays['preSnapPossessionTeamScore'] = plays.apply(lambda row: row['preSnapHomeScore'] if row['possessionTeam'] == row['homeTeamAbbr'] else row['preSnapVisitorScore'], axis=1)\n    plays['preSnapReturnTeamScore'] = plays.apply(lambda row: row['preSnapVisitorScore'] if row['possessionTeam'] == row['homeTeamAbbr'] else row['preSnapHomeScore'], axis=1)\n    plays['preSnapScoreDifferential'] = plays['preSnapPossessionTeamScore'] - plays['preSnapReturnTeamScore']\n    \n    return plays\n\nplays = process_play_data(plays, players)","metadata":{"execution":{"iopub.status.busy":"2021-10-01T01:55:43.015074Z","iopub.execute_input":"2021-10-01T01:55:43.015422Z","iopub.status.idle":"2021-10-01T01:55:44.976849Z","shell.execute_reply.started":"2021-10-01T01:55:43.015380Z","shell.execute_reply":"2021-10-01T01:55:44.975828Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plays_punt_fc_r = plays.query('specialTeamsPlayType == \"Punt\" and specialTeamsResult in (\"Return\", \"Fair Catch\")').copy()\nplays_punt_fc_r = plays_punt_fc_r.reset_index(drop=True)\n\nfig, ax = plt.subplots(figsize=(15, 8))\nplays_punt_fc_r.groupby(['specialTeamsResult'])['yardlineNumberAbs'] \\\n    .plot(kind='hist', bins=30, alpha=0.5, ax=ax)\nax.set_title('Punt Yardline by Result', fontsize=20)\nplt.legend()\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-10-01T01:55:44.979110Z","iopub.execute_input":"2021-10-01T01:55:44.979437Z","iopub.status.idle":"2021-10-01T01:55:45.670620Z","shell.execute_reply.started":"2021-10-01T01:55:44.979394Z","shell.execute_reply":"2021-10-01T01:55:45.669723Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plays_punt_fc_r.query('quarter <= 4') \\\n    .groupby(['quarter','specialTeamsResult']).size().unstack() \\\n    .plot(kind='bar',\n          figsize=(15, 6),\n          title='Fair Catch vs. Returned by Quarter',\n          stacked=True)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-10-01T01:55:45.672076Z","iopub.execute_input":"2021-10-01T01:55:45.672575Z","iopub.status.idle":"2021-10-01T01:55:45.937580Z","shell.execute_reply.started":"2021-10-01T01:55:45.672529Z","shell.execute_reply":"2021-10-01T01:55:45.936619Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Return Yardarge on Returned Punts\n- 2286 Plays","metadata":{"execution":{"iopub.status.busy":"2021-09-27T00:07:51.640355Z","iopub.execute_input":"2021-09-27T00:07:51.640658Z","iopub.status.idle":"2021-09-27T00:07:51.678441Z","shell.execute_reply.started":"2021-09-27T00:07:51.640619Z","shell.execute_reply":"2021-09-27T00:07:51.677581Z"}}},{"cell_type":"code","source":"returned_punts = plays \\\n    .query('specialTeamsPlayType == \"Punt\" and specialTeamsResult == \"Return\"') \\\n    .query('kickReturnYardage == kickReturnYardage') \\\n    .copy() \\\n    .reset_index(drop=True)\n\nax = returned_punts['kickReturnYardage'] \\\n    .plot(kind='hist', bins=60, figsize=(15, 5),\n          title='Distribution of Punt Return Yards', color=color_pal[2])\nax.axvline(returned_punts['kickReturnYardage'].median(), color='black', ls='--')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-10-01T02:23:27.530595Z","iopub.execute_input":"2021-10-01T02:23:27.530883Z","iopub.status.idle":"2021-10-01T02:23:27.890941Z","shell.execute_reply.started":"2021-10-01T02:23:27.530855Z","shell.execute_reply":"2021-10-01T02:23:27.889896Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Lets model to predict return yards\nWe only want to model punt plays that do not involve a pentalty (1881 plays)\n\nPotential Features:\n- kickerId\n- possessionTeam\n- returnTeam\n- yardsToGo\n- yardlineNumberAbs\n- returnerId\n- returner1Pos\n- secondsOfGameTime\n- preSnapReturnTeamScore\n- preSnapPossessionTeamScore\n- preSnapScoreDifferential","metadata":{}},{"cell_type":"code","source":"player_pos_map = players.set_index('nflId')['Position'].to_dict()\nreturned_punt_nopenalty = returned_punts.loc[returned_punts['penaltyCodes'].isna()] \\\n    .reset_index(drop=True)\nreturned_punt_nopenalty['returner1Pos'] = returned_punt_nopenalty['returnerId1'].astype('int').map(player_pos_map)\nreturned_punt_nopenalty['returner1Pos'].value_counts()","metadata":{"execution":{"iopub.status.busy":"2021-10-01T02:23:35.125851Z","iopub.execute_input":"2021-10-01T02:23:35.126864Z","iopub.status.idle":"2021-10-01T02:23:35.148999Z","shell.execute_reply.started":"2021-10-01T02:23:35.126823Z","shell.execute_reply":"2021-10-01T02:23:35.147608Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Simple Linear Model\n\nIn this section we use a simple regression model (ElasticNet) to predict the outcome of punt plays based on the features we've created above.","metadata":{}},{"cell_type":"code","source":"FEATURES = ['kickerId',\n            'possessionTeam',\n            'returnTeam',\n            'yardsToGo',\n            'yardlineNumberAbs',\n            'returnerId',\n            'returner1Pos',\n            'secondsOfGameTime',\n            'preSnapPossessionTeamScore',\n            'preSnapReturnTeamScore',\n            'preSnapScoreDifferential']\n\nTARGET = ['kickReturnYardage']\n\nnumeric_features = ['yardsToGo','yardlineNumberAbs','secondsOfGameTime',\n                    'preSnapPossessionTeamScore','preSnapReturnTeamScore',\n                    'preSnapScoreDifferential']\n\ndf = returned_punt_nopenalty.copy()\nX = df[FEATURES].copy()\ny = df[TARGET].copy()\noof = df[['gameId','playId'] + TARGET].copy()\nX_num = X[numeric_features].values\ny = y.values","metadata":{"execution":{"iopub.status.busy":"2021-10-01T02:23:35.931678Z","iopub.execute_input":"2021-10-01T02:23:35.932795Z","iopub.status.idle":"2021-10-01T02:23:35.945700Z","shell.execute_reply.started":"2021-10-01T02:23:35.932755Z","shell.execute_reply":"2021-10-01T02:23:35.944756Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.linear_model import ElasticNetCV\n\nreg = ElasticNetCV()\nreg.fit(X_num, y.reshape(-1))\n\n# Pull the coeffiencts from the model\nen_coef = pd.DataFrame(index=numeric_features,\n             data=reg.coef_,\n            columns=['coef'])","metadata":{"execution":{"iopub.status.busy":"2021-10-01T02:24:08.628222Z","iopub.execute_input":"2021-10-01T02:24:08.629242Z","iopub.status.idle":"2021-10-01T02:24:08.725466Z","shell.execute_reply.started":"2021-10-01T02:24:08.629203Z","shell.execute_reply":"2021-10-01T02:24:08.724349Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Reviewing our model coefficents we can see the `yardlineNumberAbs` and `preSnapScoreDifferential` are the most correlated with the play's outcome.","metadata":{}},{"cell_type":"code","source":"en_coef.sort_values('coef')","metadata":{"execution":{"iopub.status.busy":"2021-10-01T02:24:09.180216Z","iopub.execute_input":"2021-10-01T02:24:09.181316Z","iopub.status.idle":"2021-10-01T02:24:09.194022Z","shell.execute_reply.started":"2021-10-01T02:24:09.181237Z","shell.execute_reply":"2021-10-01T02:24:09.192947Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Plot the predictions vs. Actual of the linear model","metadata":{}},{"cell_type":"code","source":"returned_punt_nopenalty['en_pred'] = reg.predict(X_num)\nreturned_punt_nopenalty.plot(x=TARGET[0],\n                             y='en_pred',\n                             style='.',\n                             figsize=(10, 10),\n                            color=color_pal[5])","metadata":{"execution":{"iopub.status.busy":"2021-10-01T02:24:09.986998Z","iopub.execute_input":"2021-10-01T02:24:09.987720Z","iopub.status.idle":"2021-10-01T02:24:10.292333Z","shell.execute_reply.started":"2021-10-01T02:24:09.987675Z","shell.execute_reply":"2021-10-01T02:24:10.291610Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Machine Learning Model\nNext we will use a machine learning model.\n- 5 kfold cross validation.\n- Fit a final model on the average best iteration across folds.","metadata":{}},{"cell_type":"code","source":"import warnings\nwarnings.filterwarnings(\"ignore\")\nimport lightgbm as lgb\nfrom sklearn.model_selection import KFold\nfrom sklearn.metrics import mean_absolute_error, mean_squared_error\n\n\nLEARNING_RATE = 0.001\nCAT_COLS = ['kickerId','possessionTeam','returnTeam','returnerId','returner1Pos']\n\nfor c in CAT_COLS:\n    X[c] = X[c].astype('category')\n\nkf = KFold(n_splits=5, shuffle=True)\n\nfold = 0\nbest_iters = []\nfor tr_idx, val_idx in kf.split(X, y):\n    df.loc[val_idx, 'fold'] = fold\n    X_tr = X.loc[tr_idx]\n    y_tr = y[tr_idx]\n\n    X_val = X.loc[val_idx]\n    y_val = y[val_idx]\n    reg = lgb.LGBMRegressor(n_estimators=1000,\n                            learning_rate=LEARNING_RATE,\n                            random_state=529\n                           )\n    reg.fit(X_tr, y_tr, eval_set=(X_val, y_val),\n            verbose=False,\n            early_stopping_rounds=100)\n    preds = reg.predict(X_val)\n    oof.loc[val_idx, 'pred'] = preds\n    \n    # Scoring\n    mae_score = mean_absolute_error(y_val, preds)\n    mse_score = mean_squared_error(y_val, preds)\n    best_iter = reg.best_iteration_\n    best_iters.append(best_iter)\n    print(f'Fold {fold}: MAE {mae_score:0.4f} - MSE {mse_score:0.4f} - Best Iteration {best_iter}')\n    fold += 1\n    \nbest_avg_iteration = np.mean(best_iters)\nmae_oof = mean_absolute_error(oof['kickReturnYardage'], oof['pred'])\nmse_oof = mean_squared_error(oof['kickReturnYardage'], oof['pred'])\n\nprint(f'The average best iteraction across folds is {best_avg_iteration}')\nprint(f'OOF Score MAE {mae_oof:0.2f} - MSE {mse_oof:0.2f}')","metadata":{"execution":{"iopub.status.busy":"2021-10-01T02:24:13.781968Z","iopub.execute_input":"2021-10-01T02:24:13.783145Z","iopub.status.idle":"2021-10-01T02:24:17.557027Z","shell.execute_reply.started":"2021-10-01T02:24:13.783078Z","shell.execute_reply":"2021-10-01T02:24:17.556345Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Out model on average is 6.77 yards off from the predicted target. Finally, we fit a single model on all of the training data using the average best iteration across folds.","metadata":{}},{"cell_type":"code","source":"reg = lgb.LGBMRegressor(n_estimators=round(best_avg_iteration),\n                        learning_rate=LEARNING_RATE)\nreg.fit(X, y)\npreds = reg.predict(X)\noof['fullfit_pred'] = reg.predict(X)\noof.plot(x='fullfit_pred', y=TARGET[0], kind='scatter',\n         figsize=(10, 10), title='Predictions vs Target for LGBMRegressor Model')","metadata":{"execution":{"iopub.status.busy":"2021-10-01T02:24:19.028248Z","iopub.execute_input":"2021-10-01T02:24:19.029067Z","iopub.status.idle":"2021-10-01T02:24:20.039495Z","shell.execute_reply.started":"2021-10-01T02:24:19.029008Z","shell.execute_reply":"2021-10-01T02:24:20.038445Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"\n## Model Feature Importances","metadata":{}},{"cell_type":"code","source":"pd.DataFrame(index=reg.feature_name_,\n             data=reg.feature_importances_,\n            columns=['importance']).sort_values('importance') \\\n    .plot(kind='barh', title='LGBM Feature Importance', figsize=(12, 8))\nplt.legend().remove()","metadata":{"execution":{"iopub.status.busy":"2021-10-01T02:21:25.703894Z","iopub.execute_input":"2021-10-01T02:21:25.704719Z","iopub.status.idle":"2021-10-01T02:21:26.040562Z","shell.execute_reply.started":"2021-10-01T02:21:25.704671Z","shell.execute_reply":"2021-10-01T02:21:26.039532Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# PFF Data Exploration","metadata":{}},{"cell_type":"code","source":"pff = pd.read_csv('../input/nfl-big-data-bowl-2022/PFFScoutingData.csv')\npunt_pff = oof[['gameId','playId']].merge(pff, validate='1:1').copy()\n# See if punt where the punter intended\npunt_pff['puntedWhereIntended'] = punt_pff['kickDirectionIntended'] == punt_pff['kickDirectionActual']\npunt_pff['puntedWhereIntended'].value_counts()","metadata":{"execution":{"iopub.status.busy":"2021-10-01T02:37:38.298214Z","iopub.execute_input":"2021-10-01T02:37:38.298530Z","iopub.status.idle":"2021-10-01T02:37:38.389419Z","shell.execute_reply.started":"2021-10-01T02:37:38.298500Z","shell.execute_reply":"2021-10-01T02:37:38.388426Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"punt_pff['puntedWhereIntended'].value_counts() / punt_pff.shape[0]","metadata":{"execution":{"iopub.status.busy":"2021-10-01T02:37:45.527244Z","iopub.execute_input":"2021-10-01T02:37:45.527533Z","iopub.status.idle":"2021-10-01T02:37:45.536568Z","shell.execute_reply.started":"2021-10-01T02:37:45.527505Z","shell.execute_reply":"2021-10-01T02:37:45.535631Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Snap time vs. Operation Time","metadata":{}},{"cell_type":"code","source":"sns.jointplot(x='snapTime', y='operationTime',\n              data=punt_pff, hue='kickType',\n              alpha=0.5, height=10)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-10-01T02:47:44.263304Z","iopub.execute_input":"2021-10-01T02:47:44.263763Z","iopub.status.idle":"2021-10-01T02:47:45.128037Z","shell.execute_reply.started":"2021-10-01T02:47:44.263713Z","shell.execute_reply":"2021-10-01T02:47:45.126895Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# How to improve this model?\nIn the next section we will explore the tracking data, and features that may help improve the model score.\n- Up next I'll explore the tracking data.\n- More to come.","metadata":{}},{"cell_type":"code","source":"tracking2018 = pd.read_csv('../input/nfl-big-data-bowl-2022/tracking2018.csv')\nmy_play = tracking2018.query('gameId == 2018090600 and playId == 2599').reset_index(drop=True).copy()","metadata":{"execution":{"iopub.status.busy":"2021-10-01T02:51:05.833264Z","iopub.execute_input":"2021-10-01T02:51:05.834356Z"},"trusted":true},"execution_count":null,"outputs":[]}]}