{"cells":[{"metadata":{"trusted":true,"_uuid":"1a81bf2b0efadf0a6eb161a31037c29bde166840"},"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as mpatches\nsns.set()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"252cde6be9ceb8062f9bd3f728a9605370aedf27"},"cell_type":"markdown","source":"Skip to [Observations and Discussion](#discussion)\n\n- **'NGS-punt_return.csv'** is obtained from: https://www.kaggle.com/jdemeo/preprocessing-ngs\n- **'play-punt_return-yardage.csv'** is obtained from: https://www.kaggle.com/jdemeo/preprocessing-punt-play"},{"metadata":{"trusted":true,"_uuid":"6b596449ef006fdd2f7d80f447858d739ea555f2"},"cell_type":"code","source":"# Load in NGS data, player role data, and play info\nngs_df = pd.read_csv('../input/ngsconcussion/NGS-punt_return.csv')\nplay_player_role_df = pd.read_csv('../input/NFL-Punt-Analytics-Competition/play_player_role_data.csv')\nplay_df = pd.read_csv('../input/ngsconcussion/play-punt_return-yardage.csv')\n\n# Merge datasets\nngs_df = pd.merge(ngs_df, play_player_role_df,\n                  how=\"inner\",\n                  on=['GameKey', 'PlayID', 'GSISID'])\n\nngs_df = pd.merge(ngs_df, play_df,\n                  how=\"inner\",\n                  on=['GameKey', 'PlayID'])\n\n# Cleanup\ndroppers = ['count']\nngs_df.drop(columns=droppers, inplace=True)\nngs_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"7eacf9d541c451ad365cdeea9796007833f791d4"},"cell_type":"code","source":"# NGS Unique_ids\nngs_ids = ngs_df.groupby(['GameKey','PlayID']).size().reset_index().rename(columns={0:'count'})\nngs_ids.shape","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"b99532f15caabfe81ded8eca14b334ab0af0b7bd"},"cell_type":"markdown","source":"### Goal: Understand the proximity of the nearest punt player to the PR when the punt is caught by the PR\nThis is done in hopes to understand what the impact of having a restricted zone for a PR would be on the 2016 and 2017 punt returns. I am primarily interested in seeing how much return yardage is being lost if certain plays were negated as a result of the rule to better assess the penalty yardage amount for infringing on a PR restricted zone. This analysis is also done to understand how many plays would be negated by such a rule just in general and have reasoning for a particular restricted zone distance."},{"metadata":{"trusted":true,"_uuid":"ae548378a59b18c797734a5f17af0484eba4af29"},"cell_type":"code","source":"'''ONLY RUN THE FOLLOWING TWO BLOCKS TO GET AN IDEA OF THE COURSE OF EVENTS FOR A PARTICULAR PLAY'''\n\n# def isolate_play(df, game_key, play_id):\n#     '''Create a dataframe of a particular play'''\n#     where_condition = ((df['GameKey'] == game_key) &\n#                        (df['PlayID'] == play_id))\n#     new_df = df[where_condition].copy()\n#     new_df.sort_values(by=['Time'], inplace=True)\n#     new_df.reset_index(drop=True, inplace=True)\n#     return new_df\n\n# def course_of_events(df):\n#     '''Get list of events in order of occurrence for a particular play'''\n#     events = []\n#     for i in range(len(df)):\n#         event = df.loc[i, 'Event']\n#         if event not in events:\n#             events.append(event)\n           \n#     print('Play Description:', df.loc[0, 'PlayDescription'])\n#     print('---')\n#     print('Game Events:', events)\n#     print('-----------------------------------------------')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"d412cedd9ae9e7725fb9fac635fa891c301935ea"},"cell_type":"code","source":"# # Iterate through ids to get events for each play\n# for i in range(len(ngs_ids)):\n#     game_key = ngs_ids.loc[i, 'GameKey']\n#     play_id = ngs_ids.loc[i, 'PlayID']\n#     the_play = isolate_play(ngs_df, game_key, play_id)\n#     course_of_events(the_play)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"c6bf52b4a79f75b99625ded8b05f0c75641426aa"},"cell_type":"markdown","source":"- Looking at the course of events during a play, 'punt_received' seems like a reasonable place to see proximity of oponnent players"},{"metadata":{"trusted":true,"_uuid":"acde189f3507a372ba0b926f970a10e0fff88fce"},"cell_type":"code","source":"def event_df_creation(df, event):\n    '''Get a new dataframe with data pertinent to a particular event'''\n    new_df = df[df['Event'] == event].reset_index(drop=True)\n    unique_ids = new_df.groupby(['GameKey','PlayID']).size().reset_index().rename(columns={0:'count'})\n    return new_df, unique_ids","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"8f64b258654ad4d6ca24e52d1342fa823e1cf818"},"cell_type":"code","source":"# Let's indicate what team the player is playing on based off player role\nreturn_team_positions = ['PR', 'PDL1', 'PDL2', 'PDL3', 'PDL4', 'PDR1', 'PDR2', 'PDR3', 'PDR4', 'VL', 'VR', \n                         'PLL', 'PLR', 'VRo', 'VRi', 'VLi', 'VLo', 'PLM', 'PLR1', 'PLR2', 'PLL1', 'PLL2',\n                         'PFB', 'PDL5', 'PDR5', 'PDL6', 'PLR3', 'PLL3', 'PDR6', 'PLM1', 'PDM']\npunt_team_positions = ['P', 'PLS', 'PPR', 'PLG', 'PRG', 'PLT', 'PRT', 'PLW', 'PRW', 'GL', 'GR',\n                       'GRo', 'GRi', 'GLi', 'GLo', 'PC', 'PPRo', 'PPRi', 'PPL', 'PPLi', 'PPLo']\n\ndef label_team(df):\n    '''Label each player by the team they play on'''\n    df['team'] = ''\n    print('Determining player roles')\n\n    for i, role in enumerate(df['Role']):\n        if role in return_team_positions:\n            df.loc[i, 'team'] = 'return team'\n        elif role in punt_team_positions:\n            df.loc[i, 'team'] = 'punt team'\n        else:\n            df.loc[i, 'team'] = 'unknown'","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"2472f0c70c19492d9a124d4d901584ab8cf75d4f"},"cell_type":"code","source":"def calculate_player_proximity(role_x, role_y, player_x, player_y):\n    '''Calculate euclidean distance between two players'''\n    leg_x = (role_x - player_x) ** 2\n    leg_y = (role_y - player_y) ** 2\n    hypotenuse = np.sqrt(leg_x + leg_y)\n    return hypotenuse","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"4270f91b5957c142e4891ce9c81a9c82bbfc559f"},"cell_type":"code","source":"def calculate_x_proximity(role_x, player_x):\n    '''Calculate distance of a player to a particular role only by yardline'''\n    return np.abs(role_x - player_x)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"1c863effaa57b17656f97c06d2fa62b9ea645e26"},"cell_type":"code","source":"def calculate_proximity_for_play(df, unique_ids, role):\n    '''Calculate proximity of each player to the player of a particular role'''\n    # Create feature for player proximity\n    df['proximity_to_' + role + '_circle'] = 0\n    df['proximity_to_' + role + '_x'] = 0\n    \n    print('Calculating player proximities to', role)\n    \n    # Go through each data point in particular NGS dataset\n    for i in range(len(df)):\n        \n        # Play Information\n        game_key = df.loc[i, 'GameKey']\n        play_id = df.loc[i, 'PlayID']\n        \n        # Get one unique set of data points related to a single (GameKey, PlayID) pair\n        where_condition = ((df['GameKey'] == game_key) &\\\n                           (df['PlayID'] == play_id))\n        just_view = df[where_condition].reset_index()\n        \n        # Get coordinates of a player with a particular role\n        if any(just_view['Role'] == role):\n            role_x = just_view.loc[just_view['Role'] == role, 'x'].values[0]\n            role_y = just_view.loc[just_view['Role'] == role, 'y'].values[0]\n            \n        # Plays that don't actually have the particular role represented\n        else:\n            continue\n\n        # Current Player coordinates\n        position_x = df.loc[i, 'x']\n        position_y = df.loc[i, 'y']\n\n        # Calculate proximity\n        proximity_hypo = calculate_player_proximity(role_x, role_y, position_x, position_y)\n        proximity_x = calculate_x_proximity(role_x, position_x)\n        df.loc[i, 'proximity_to_' + role + '_circle'] = proximity_hypo\n        df.loc[i, 'proximity_to_' + role + '_x'] = proximity_x","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"3082f6f363d8a393dd2aa6eebf65ae1960a17bee"},"cell_type":"code","source":"def calculate_closest_player(df, unique_ids, column):\n    '''Find who the closest player on the punt team is and create new id set'''\n    unique_ids[column] = 0\n    good_indexes = []\n    role = 'PR'\n    print('Determining closest player to', role)\n    \n    for i in range(len(unique_ids)):\n        \n        # Play information\n        game_key = unique_ids.loc[i, 'GameKey']\n        play_id = unique_ids.loc[i, 'PlayID']\n\n        # Get one unique set of data points related to a single (GameKey, PlayID) pair\n        where_condition = ((df['GameKey'] == game_key) &\\\n                           (df['PlayID'] == play_id) &\\\n                           (df['team'] == 'punt team'))\n        just_view = df[where_condition].reset_index(drop=True)\n        \n        # Take minimum of series and Error handling where the NGS data had no punt team :(\n        try:\n            unique_ids.loc[i, column] = min(just_view[column])\n            good_indexes.append(i)\n        except ValueError:\n            continue\n    \n    # Create new set of ids\n    new_ids = unique_ids.loc[good_indexes, :].copy()\n    new_ids.reset_index(inplace=True, drop=True)\n    \n    return new_ids","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"fcf0759f15549b8436a61fe5e4ea9547ff19b432"},"cell_type":"markdown","source":"### Goal: Want to look at the closest punt player's proximity when the ball is caught by the PR\n- Workflow:\n    - Get datapoints for a particular plays event\n    - Label the players by what team they are\n    - Calculate each players proximity to a particular role\n    - Find the minimum distance from punt player to PR\n    - Return those proximities for each play\n- **PR-proximity.csv** is used in notebook: https://www.kaggle.com/jdemeo/analysis-fair-catches"},{"metadata":{"trusted":true,"_uuid":"f677fdbbe1902cee1dc4ffe990469ce2bb5bfd4e"},"cell_type":"code","source":"event_df, event_ids = event_df_creation(ngs_df, 'punt_received')\nlabel_team(event_df)\ncalculate_proximity_for_play(event_df, event_ids, 'PR')\nnew_ids = calculate_closest_player(event_df, event_ids, 'proximity_to_PR_circle')\nnew_ids = calculate_closest_player(event_df, new_ids, 'proximity_to_PR_x')\nnew_ids.to_csv('PR-proximity.csv', index=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"308449a1337649c4c1c2a225021d1a3441aca0c0"},"cell_type":"code","source":"'''Plot of distribution distance of closest punt team player to punt receiver'''\nbins = [i for i in range(0, 40, 1)]\nplt.hist(new_ids['proximity_to_PR_x'], bins=bins)\n\nplt.title('Distribution of closest player on punt team to punt receiver')\nplt.xlabel('Yards')\nplt.ylabel('count')\nplt.show()\n\nnew_ids['proximity_to_PR_x'].describe()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"b2118122351f0b51156091ad812ffe6263e1db6c"},"cell_type":"code","source":"'''Plot of distribution distance of closest punt team player to punt receiver'''\nbins = [i for i in range(0, 40, 1)]\nplt.hist(new_ids['proximity_to_PR_circle'], bins=bins)\n\nplt.title('Distribution of closest player on punt team to punt receiver')\nplt.xlabel('Yards')\nplt.ylabel('count')\nplt.show()\n\nnew_ids['proximity_to_PR_circle'].describe()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"6d89480eda390f47f06a0ccd16066cef1adb57fa"},"cell_type":"markdown","source":"## <a id=\"discussion\">Observations and Discussion</a>\n- Average of the closest punt team player to the PR by yardline distance: 9.17 yards\n- Average of the closest punt team player to the PR by Euclidean distance: 11.04 yards\n- This can be considered relative to fair catches (https://www.kaggle.com/jdemeo/analysis-fair-catches) in which:\n    - Average of the closest punt team player to the by PR yardline distance: 2.72 yards\n    - Average of the closest punt team player to the by PR Euclidean distance: 4.04 yards\n- We'll now look at the differences in distribution in yards returned on a play as well as the reward of the return to get a better idea of at what distance from the PR are there diminishing returns with regards to having a larger restricted zone\n- Part of this analysis is to show why I chose a restricted zone of 8 yards by yardline or 10 yards by euclidean distance."},{"metadata":{"trusted":true,"_uuid":"3047b8b87db081eb48998d645023de8dbc8f6198"},"cell_type":"code","source":"# Combine yardage data and proximity data\nyard_and_proximity = pd.merge(new_ids, play_df,\n                          how='inner',\n                          on=['GameKey', 'PlayID'])\nprint(yard_and_proximity.shape)\nyard_and_proximity.head()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"61e85098d377487a5c6c9087b54df44c4c2b6a23"},"cell_type":"markdown","source":"### Let's Look at Yardage/Reward and Proximity\n- We'll look at the yardage gained on the return and the reward of the play given a certain distance threshold from the PR\n- The intuition would likely be that the further the closest punt team player is from the PR the more yardage on average the PR will gain on the return.\n- I'm interested in this because if a restricted zone type rule were implemented, I would like to know how many punt plays over the two seasons this rule would have affected and also gauge what is being lost in value for that particular play in both yardage or by a proportional reward metric (yards gained / yards needed to gain for a touchdown)"},{"metadata":{"trusted":true,"_uuid":"75edfdf35b9e8ba33441a4f9e47f2ef5bbea910f"},"cell_type":"code","source":"def build_that_histogram(x, title):\n    bins = [i for i in range(min(x), max(x), 1)]\n    plt.hist(x, bins=bins)\n    plt.title(title)\n    plt.xlabel('Yards Gained on Return')\n    plt.ylabel('Count')\n    plt.show()\n    \ndef build_that_histogram_rewards(x, title):\n    bins = [i * 0.01 for i in range(-15, 100, 1)]\n    plt.hist(x, bins=bins)\n    plt.title(title)\n    plt.xlabel('Reward')\n    plt.ylabel('Count')\n    plt.show()\n    \ndef distributions_of_interest(yard_and_proximity, proximity_col, dependent_col):\n    '''Look at distributions based on proximity with regards to return yardage or reward'''\n    mean_dif = []\n    for i in range(1, 16):\n\n        # Check for cases less than or equal to a certain distance\n        where_condition = yard_and_proximity[proximity_col] <= i\n        x = yard_and_proximity[where_condition][dependent_col]\n        title = proximity_col + ' <= ' + str(i) + ' yards'\n        if dependent_col == 'reward':\n            build_that_histogram_rewards(x, title)\n        else:\n            build_that_histogram(x, title)\n        print(x.describe())\n        mean1 = x.describe()[1]\n\n        # Check for cases greater than a certain distance\n        where_condition = yard_and_proximity[proximity_col] > i\n        x = yard_and_proximity[where_condition][dependent_col]\n        title = proximity_col + ' > ' + str(i) + ' yards'\n        if dependent_col == 'reward':\n            build_that_histogram_rewards(x, title)\n        else:\n            build_that_histogram(x, title)\n        print(x.describe())\n        mean2 = x.describe()[1]\n\n        mean_dif.append(mean2 - mean1)\n        print('-----------------------------------------------')\n    \n    return mean_dif","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"4bd2f28c1f7cbc58c3e60574fa3b76fd73ad17eb"},"cell_type":"code","source":"# Try proximity_to_PR_circle' or 'proximity_to_PR_x' as the filter\n# Try reward' or 'yardage on play' to see counts of that variable\nmean_dif = distributions_of_interest(yard_and_proximity, 'proximity_to_PR_circle', 'yardage on play')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"26d69bc9da4aab18a685499227db61039a77ecd7"},"cell_type":"code","source":"'''Plot of mean differences between the two distributions'''\nplt.figure(figsize=(9, 6))\nplt.scatter([i for i in range(1, 16)], mean_dif)\n\nplt.title('Differences of means between Restricted Zone Punt Returns and Outside Restricted Zone Punt Returns')\nplt.ylabel('Difference of average yards gained', fontsize=12)\nplt.xlabel('Restricted Zone Yard Cutoff (Euclidean)', fontsize=12)\nplt.savefig('mean_difference.png', bbox_inches='tight')\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"6f96022bd3cb3549663fcf73e43434bc63698b37"},"cell_type":"code","source":"'''Run to see proximity by yardline distance distributions'''\n# # Try proximity_to_PR_circle' or 'proximity_to_PR_x' as the filter\n# # Try reward' or 'yardage on play' to see counts of that variable\n# mean_dif = distributions_of_interest(yard_and_proximity, 'proximity_to_PR_x', 'yardage on play')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"a00b3d0057a3fd6393802166e342035ce9cddbb8"},"cell_type":"markdown","source":"- Summary\n    - **2604** punt returns were analyzed\n    - **Average proximity by yardline distance: 9.17 yards**\n    - **Average proximity by Euclidean distance: 11.04 yards**\n    - Hypothetical Restricted Zones:\n        - Given that the closest punt team player is less than or equal to 8 yards (yardline distance) from PR at time of catch:\n            - Count: **1216**\n            - **Average yards gained by PR on play: 8.01 yards**\n            - **Average reward by PR on play: 0.11**\n        - Given all punt team players are greater than 8 yards (yardline distance) from PR at time of catch:\n            - Count: **1388**\n            - **Average yards gained by PR on play: 11.71 yards**\n            - **Average reward by PR on play: 0.15**\n        - So we are looking at an average difference of 3.70 yards between the two distributions.\n        - Given that the closest punt team player is less than or equal to 10 yards (Euclidean distance) from PR at time of catch:\n            - Count: **1205**\n            - **Average yards gained by PR on play: 7.91 yards**\n            - **Average reward by PR on play: 0.11**\n        - Given all punt team players are greater than 8 yards (Euclidean distance) from PR at time of catch:\n            - Count: **1399**\n            - **Average yards gained by PR on play: 11.76 yards**\n            - **Average reward by PR on play: 0.16**\n        - So we are looking at an average difference of 3.85 yards between the two distributions.\n        - I present two hypothetical restricted zone distances to only show that they are nearly equivalent. It's just much easier to know 8 yards by yardline distance then 10 yards by Euclidean distance because of the fields painted yardline markers.\n    - **So intuitively the further the closest punt team player is from the PR at the time of the catch, the higher the mean return distance and 'reward' value for return.**"},{"metadata":{"_uuid":"253b537d651c4a926542bc9e4a91f05975ee8f2b"},"cell_type":"markdown","source":"# Links to other notebooks:\n- Concussion play analysis with proposed rule changes: https://www.kaggle.com/jdemeo/analysis-concussions\n- Analysis of uncalled penalties: https://www.kaggle.com/jdemeo/analysis-uncalled-penalties\n- Analysis of fair catches: https://www.kaggle.com/jdemeo/analysis-fair-catches\n- Preprocessing of Play Information: https://www.kaggle.com/jdemeo/preprocessing-punt-play\n- Preprocessing of NGS data for the above notebooks: https://www.kaggle.com/jdemeo/preprocessing-ngs"},{"metadata":{"trusted":false,"_uuid":"1a141d8b42d91e2af11db8299d881a3e8bd99369"},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"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}