{"cells":[{"metadata":{"_uuid":"2ff24ff5e617652660f24a8afaf78e0bf2de03d8"},"cell_type":"markdown","source":"![](https://s3-eu-west-1.amazonaws.com/nfl-punt-analytics/Speedacccollision.jpg)"},{"metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_kg_hide-input":true,"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","trusted":true},"cell_type":"code","source":"%matplotlib inline\nimport os\nimport pandas as pd\nimport datetime as dt\nimport numpy as np\nfrom tqdm import tqdm\nfrom IPython.core.interactiveshell import InteractiveShell\nInteractiveShell.ast_node_interactivity = \"all\"\nimport matplotlib.pyplot as plt\nplt.rcParams['figure.figsize'] = [16, 10]\nplt.rcParams['font.size'] = 14\nimport seaborn as sns\nsns.set_palette(sns.color_palette('tab20', 20))\nimport plotly.offline as py\npy.init_notebook_mode(connected=True)\nimport plotly.graph_objs as go\nfrom IPython.display import HTML\nfrom matplotlib import animation, rc\nimport matplotlib.image as mpimg\nimport warnings\n\ndef write_image(fig, filename, save=False):\n    if save:\n        try:\n            import plotly.io as pio\n            pio.write_image(fig, './svgs/' + filename)\n        except Exception:\n            pass","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"b8f0f21bb744965f1122ca899a10163107864c6e"},"cell_type":"markdown","source":"# Motivation\n\nWe have explored all the available datasets in our previous kernel. We would like to focus more on the NGS player movements as this is the most interesting part of dataset and it also requires some additional processing to derive insights.\n\n\n# Next Gen Stats\n\nNext Gen Stats (NGS) is processed by BIOCORE to produce player level data that describes the movement (speed and direction) of each player during a play.\n\nThe NGS data is in units of yards, which we converted to meters. All our reported speed calculations are in m/s.\nThe processed dataset has 0.1s time resolution.\n\nPlease note that NFL athletes are surprisingly fast, they could reach 10 m/s max velocity during the game [1]. This means that two consecutive records for the same player could be more than a yard away.\n\nKey findings that you need to be aware when using the Next Gen Stats data.\n\n* The actual punt play is just a subset of the ngs file.\n* We have all the player movements not just the players who play during punt event.\n* We have 0.1s time resolution which is fine for most usecases. Resampling or interpolation might be required to determine collisions.\n* We don't know the expected error range for the provided dataset.\n* Even with this granular data it is impossible to reconstruct exact collisions and estimate the impact on the head.\n\n\n# Medical Research\n\n**Concussion** is an acceleration/deceleration injury resulting from biomechanical forces transmitted to the cerebral tissues from impacts to the head or torso. \n\nThanks to the increasing awareness there are plenty of available studies about concussions. \nWe found two studies [2], [3] that gave us useful ideas how to process the NGS data. The first study used a combination of video surveillance and laboratory reconstruction of game impacts to evaluate concussion biomechanics.\nThe second monitored all football related head impacts in high school athletes for years.\n\nBoth study had a chance to work with more precise (1000 Hz) head acceleration data. The actual impact took only 10 ms. [3] found that rotational acceleration (>5582.3 rad/s2), linear acceleration (>96.1 g), and impact location (front, top, back) yielded the highest predictive value of concussion.\n\n**Be aware hidden code blocks!**\nMost of the source code is hidden by default to improve readability. Please click on the code buttons on the right if you would like to go deeper."},{"metadata":{"_uuid":"5f9a6699e9e4af36fd2c9e1a139fb96e508ac9b6"},"cell_type":"markdown","source":"# Player Speed and Acceleration\n\n*“Speed has never killed anyone. Suddenly becoming stationary, that's what gets you.”*\n\n**― Jeremy Clarkson**\n\n"},{"metadata":{"_kg_hide-input":true,"_uuid":"526e6934b0bf01618df15f9f1fa9c1ad0804c3d3","trusted":true},"cell_type":"code","source":"C = ['#3D0553', '#4D798C', '#7DC170', '#F7E642']\nwarnings.filterwarnings('ignore')\nplt.rcParams['figure.figsize'] = [16, 10]\nplt.rcParams['font.size'] = 14\npd.set_option('display.max_columns', 99)\nstart = dt.datetime.now()\n\nNFL_DATA_DIR = '../input/NFL-Punt-Analytics-Competition'\nALL_PLAYS_PATH = '../input/nflplaybyplay2009to2016/NFL Play by Play 2009-2017 (v4).csv'\nNGS_DIR = '../input/next-gen-stats-by-play'\nEDA_DIR = '../input/exploratory-data-analysis-external-data'\nEXT_DIR = '../input/externalnfl/'\n\nYARD = 0.9144\nMPH = 1.609344\nSPEED_LIMIT = 13\nMAX_SPEED = 11\nSMOOTHING_FACTOR = 3\nACCELERATION_BINS = [0, 10, 15, 100]\n\nPUNT_COVERAGE_ROLES = [\n    'GL', 'GR', 'P', 'PPL', 'PPR', 'PC', 'PLW', 'PRW', 'PLT', 'PLG', 'PLS', 'PRG', 'PRT']\nPUNT_RETURN_ROLES = ['PR', 'PFB', 'PLL', 'PDM', 'PLM', 'PLR', 'VL', 'VR', 'PDL', 'PDR']\n\ndef get_plays():\n    data = pd.read_csv(os.path.join(NFL_DATA_DIR, 'play_information.csv'),\n                       parse_dates=['Game_Date'])\n    data.columns = [col.replace('_', '') for col in data.columns]\n    data['PlayKey'] = data['GameKey'].apply(str) + '_' + data['PlayID'].apply(str)\n    data = data.drop(['PlayID', 'PlayType'], axis=1)\n    data = data.sort_values(['GameKey', 'Quarter', 'GameClock'])\n    data['PlayType'] = 'Punt'\n    return data\n\ndef get_ngs(playkey):\n    ngs = pd.read_csv(os.path.join(NGS_DIR, f'ngs_{playkey}.csv'), parse_dates=['Time'])\n    ngs['t'] = (ngs.Time - ngs.Time.min()) / np.timedelta64(1, 's')\n    ngs = ngs.sort_values(by='t')\n    return ngs\n\ndef get_punt_players():\n    player_role = pd.read_csv(os.path.join(NFL_DATA_DIR, 'play_player_role_data.csv'))\n    player_role.columns = [col.replace('_', '') for col in player_role.columns]\n    player_role['PlayKey'] = player_role['GameKey'].apply(str) + '_' + player_role[\n        'PlayID'].apply(str)\n    player_role['ShortRole'] = player_role['Role'].apply(\n        lambda s: s.replace('i', '').replace('o', '')[:3])\n    player_role['PuntCoverage'] = player_role['ShortRole'].apply(\n        lambda s: s in PUNT_COVERAGE_ROLES)\n    player_role['PuntReturn'] = player_role['ShortRole'].apply(lambda s: s in PUNT_RETURN_ROLES)\n\n    players = get_players()\n\n    return player_role.merge(players, how='left', on='GSISID')\n\ndef get_players():\n    players = pd.read_csv(os.path.join(NFL_DATA_DIR, 'player_punt_data.csv'))\n    players = players.groupby('GSISID').agg({\n        'Number': lambda x: ','.join(\n            x.replace(to_replace='[^0-9]', value='', regex=True).unique()),\n        'Position': lambda x: ','.join(x.unique())})\n    return players.reset_index()\n\ndef get_punt_player_speed():\n    player_punt_speed = pd.read_csv(os.path.join(NGS_DIR, 'player_ngs.csv'))\n    punt_player_speed = pd.merge(player_punt_speed, get_punt_players(),\n                                 on=['PlayKey', 'GSISID'])\n    punt_player_speed = punt_player_speed[punt_player_speed.MaxSpeed < SPEED_LIMIT]\n    punt_player_speed['CollisionId'] = np.digitize(-punt_player_speed.MinAcceleration,\n                                                   bins=ACCELERATION_BINS)\n    punt_player_speed['Collision'] = punt_player_speed['CollisionId'].replace(\n        {1: 'Mild', 2: 'Medium', 3: 'Serious'})\n    return punt_player_speed\n\ndef get_video_review():\n    data = pd.read_csv(os.path.join(NFL_DATA_DIR, 'video_review.csv'))\n    data.columns = [col.replace('_', '') for col in data.columns]\n    data['PlayKey'] = data['GameKey'].apply(str) + '_' + data['PlayID'].apply(str)\n\n    footage = pd.read_csv(os.path.join(NFL_DATA_DIR, 'video_footage-injury.csv'))\n    footage['PlayKey'] = footage['gamekey'].apply(str) + '_' + footage['playid'].apply(str)\n\n    footage = footage.rename(columns={'PREVIEW LINK (5000K)': 'VideoLink'})\n    data = data.merge(footage[['PlayKey', 'VideoLink', 'PlayDescription']],\n                      how='left',\n                      on=['PlayKey'])\n    data['PrimaryPartnerGSISID'] = data['PrimaryPartnerGSISID'].replace('Unclear', np.nan)\n    data = data.fillna({'PrimaryPartnerGSISID': -999})\n    data['PrimaryPartnerGSISID'] = data['PrimaryPartnerGSISID'].astype('int64')\n    return data\n\ndef calculate_speed_and_acceleration(ngs, smoothing_factor=5):\n    speed = ngs.pivot('t', 'GSISID', 'dis') * YARD\n    speed = speed.fillna(0)\n    speed = speed.rolling(smoothing_factor).mean() * 10\n    acc = speed.clip(0, MAX_SPEED).diff(smoothing_factor) * 10. / smoothing_factor\n    return speed, acc\n\ndef collect_ngs_player_stats():\n    plays = get_plays()\n    result = []\n    for playkey in tqdm(plays.PlayKey.values):\n        try:\n            ngs = get_ngs(playkey)\n\n            speed, acc = calculate_speed_and_acceleration(ngs, SMOOTHING_FACTOR)\n            max_speed = speed.max(axis=0).reset_index().rename(columns={0: 'MaxSpeed'})\n            min_acceleration = acc.min(axis=0).reset_index().rename(\n                columns={0: 'MinAcceleration'})\n\n            collision_coords = pd.DataFrame([[c, acc[c].argmin()] for c in acc.columns],\n                                            columns=['GSISID', 't'])\n            collision_coords = collision_coords.merge(ngs[['GSISID', 't', 'x', 'y']],\n                                                      how='left', on=['GSISID', 't'])\n            collision_coords['x'] = collision_coords['x'] - 10\n            collision_coords.columns = ['GSISID', 'CollisionTime', 'CollisionX', 'CollisionY']\n\n            stats = pd.merge(max_speed, min_acceleration, on='GSISID')\n            stats = stats.merge(collision_coords, on='GSISID', how='left')\n            stats['PlayKey'] = playkey\n            result.append(stats)\n        except Exception as e:\n            print(e)\n    return pd.concat(result)\n\ndef show_injured_player_speed_profile(playkey, a, b, smoothing_factor=5):\n    speed, acc = calculate_speed_and_acceleration(get_ngs(playkey), smoothing_factor)\n    fig, axs = plt.subplots(nrows=2, sharex=True)\n    axs[0].plot(speed[a], color=C[0], lw=3, alpha=0.8, label='Injured Player')\n    axs[0].plot(speed.mean(axis=1), color=C[-1], lw=2, alpha=0.5, label='All Player Average')\n    axs[0].set_ylabel('Speed (m/s)')\n    axs[1].set_ylabel('Acceleration (m/s2)')\n    axs[1].plot(acc[a], color=C[0], lw=3, alpha=0.8)\n    try:\n        axs[0].plot(speed[int(b)], color=C[1], lw=3, alpha=0.8, label='Primary Partner')\n        axs[1].plot(acc[int(b)], color=C[1], lw=3, alpha=0.8)\n    except Exception as e:\n        print(e)\n    plt.xlabel('Time (s)')\n    axs[0].grid()\n    axs[1].grid()\n    axs[0].legend(loc=0)\n    axs[0].set_ylim(0, 10)\n    axs[1].set_ylim(-15, 10)\n    plt.show()\n    fig.savefig(f'speed_profile_{playkey}.png', dpi=300)\n","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"_uuid":"23959a75b109245414049b656cc59ec88c35d9b2","scrolled":false,"trusted":true},"cell_type":"code","source":"player_ngs = pd.read_csv(os.path.join(NGS_DIR, 'player_ngs.csv'))\nprint(player_ngs.shape)\nprint(player_ngs.count())","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"02dc6900320116a8d83ecf3c5cad8929fcbde45c","trusted":true},"cell_type":"code","source":"playkey = '274_3609'\nngs = get_ngs(playkey)\nngs.shape\nngs.head()\nspeed, acc = calculate_speed_and_acceleration(ngs, smoothing_factor=5)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"b6da3d1bb9e693b744d9e4b73331d7156d6d884e"},"cell_type":"markdown","source":"While the 100 ms temporal resolution is more than enough for most usecases (player position, speed). For accelaration calculation we need to differentiate the velocity. There is a natural trade-off in selecting time window for speed calculations. We found that using the lowest possible window (100 ms) gives noisy results while using 1 sec window would not allow us to locate the collsions accurately. Finally, we decided to use 300 ms smoothing window for our speed and acceleration calculations.\n\nPlease note that our acceleration measurements are not comparable with the above mentioned medical research measurements collected more precise (1000 Hz) accelerometer sensors."},{"metadata":{"_uuid":"fe9f77ca088fbf9cb7995facca3fc35ea4338582","trusted":true},"cell_type":"code","source":"show_injured_player_speed_profile(playkey, 23742, 31785, 3)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"84610afc5aeb92c7ab334af9a17845aba61e15e1","trusted":true},"cell_type":"code","source":"punt_player_speed = get_punt_player_speed()\npunt_player_speed.describe()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"_uuid":"65eb1f6b85b21c49e7b6d623d18f1c40fe861db3","trusted":true},"cell_type":"code","source":"punt_player_speed = get_punt_player_speed()\npunt_player_speed.shape\npunt_player_speed.head()\n\nvideo_info = get_video_review()\nvideo_info = video_info.merge(\n    punt_player_speed[['GSISID', 'PlayKey', 'MaxSpeed', 'MinAcceleration',\n                       'CollisionTime', 'CollisionX', 'CollisionY']],\n    on=['GSISID', 'PlayKey'],\n    how='left')\nvideo_info = video_info.merge(\n    punt_player_speed[['GSISID', 'PlayKey', 'MaxSpeed', 'MinAcceleration',\n                       'CollisionTime', 'CollisionX', 'CollisionY']],\n    left_on=['PrimaryPartnerGSISID', 'PlayKey'],\n    right_on=['GSISID', 'PlayKey'],\n    how='left',\n    suffixes=['', 'PrimaryPartner'])\nvideo_info['MaxMaxPSpeed'] = video_info[['MaxSpeed', 'MaxSpeedPrimaryPartner']].max(axis=1)\nvideo_info['MinMinAcc'] = video_info[\n    ['MinAcceleration', 'MinAccelerationPrimaryPartner']].min(axis=1)\nvideo_info.shape\nvideo_info.head()\nvideo_info.to_csv('video_info_collision.csv', index=False)\nvideo_info['Concussion'] = 1\n","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"_uuid":"da4718a1d4754933981e8d8e061a6795ee3dcaa9","trusted":true},"cell_type":"code","source":"fig, ax = plt.subplots()\nsns.distplot(punt_player_speed.MaxSpeed, bins=20, kde_kws=dict(shade=True),\n             kde=True, color=C[1], ax=ax, label='MaxSpeed')\nplt.plot(video_info['MaxMaxPSpeed'].values, 0.01 * np.ones(len(video_info)),\n         'kx', alpha=0.8, markersize=10, lw=3, label='Injured player or partner max speed')\nplt.xlim(0, 12)\nplt.xticks(range(0, 12, 1))\nplt.legend(loc=0)\nplt.ylabel('Probability Density')\nplt.xlabel('Max Speed (m/s)')\nplt.title('Player Max Speed Distribution')\nplt.grid()\nplt.show();\n","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"_uuid":"954332b06ba67c6dabf5b782c88514f6d8f00dca","trusted":true},"cell_type":"code","source":"fig, ax = plt.subplots()\nsns.distplot(punt_player_speed.MinAcceleration, bins=20, kde_kws=dict(shade=True),\n             kde=True, color=C[0], ax=ax, label='MinAcceleration')\nplt.plot(video_info['MinMinAcc'].values, 0.01 * np.ones(len(video_info)),\n         'kx', alpha=0.8, markersize=10, lw=3, label='Injured player or partner acceleration')\nplt.legend(loc=0)\nplt.ylabel('Probability Density')\nplt.xlabel('Min Acceleration (m/s2)')\nplt.title('Player Acceleration Distribution')\nplt.grid()\nplt.show();\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"1538325ddcbe2a14c9d84b3d0c9446e349d476ad"},"cell_type":"markdown","source":" \n\n"},{"metadata":{"_uuid":"c73c6ad4e30547fd63940ecbd1275fde6a4cdd9c"},"cell_type":"markdown","source":"# Collisions\n\n*\"Football is not a contact sport, it's a collision sport - dancing is a contact sport.\"*\n\n**Vince Lombardi**\n\n\nUsing our acceleration measurements we are able to identify collisions as sudden decelerations. We used simple 10 m/s2 threshold for Medium Collisions. It would mean that someone with full speed stops moving within a second. We used an additional 15 m/s2 threshold for serious collisions. This is just a heuristic of course and a collision does not necessary lead to concussion. On the other hand any collision could cause injury or head trauma and recent studies also shown that subconcussive head injuries could lead to chronic traumatic encephalopathy as well [4].  "},{"metadata":{"_kg_hide-input":true,"_uuid":"b7cf63260b6fe992b47056a21af640e28f2fc67c","trusted":true},"cell_type":"code","source":"play_acceleration = punt_player_speed.groupby('PlayKey')[['MinAcceleration']].min()\nplay_acceleration = play_acceleration.reset_index()\nplay_acceleration = play_acceleration.merge(video_info[['PlayKey', 'Concussion']],\n                                            on='PlayKey',\n                                            how='left')\nplay_acceleratiaon = play_acceleration.fillna(0)\nplay_acceleration['WorstCollisionId'] = np.digitize(-play_acceleration.MinAcceleration,\n                                                    bins=ACCELERATION_BINS)\nplay_acceleration['WorstCollision'] = play_acceleration['WorstCollisionId'].replace(\n    {1: 'Mild', 2: 'Medium', 3: 'Serious'})\n\nr = play_acceleration.groupby('WorstCollision')[['Concussion', 'MinAcceleration']].mean()\np = play_acceleration.groupby('WorstCollision')[['Concussion']].count()\nc = play_acceleration.groupby('WorstCollision')[['Concussion']].sum()\ncollisions = pd.concat([r, p, c], axis=1)\ncollisions.columns =['ConcussionRate', 'MinAcceleration', '#PuntPlays', '#Concussions']\ncollisions.sort_values(by='MinAcceleration')","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"_uuid":"e460417ad8b57f37ba934dca285b02a7fcd3565d","trusted":true},"cell_type":"code","source":"punts = pd.read_csv(os.path.join(EDA_DIR, 'punts.csv'))\npunts.shape\npunts.head()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"_kg_hide-output":false,"_uuid":"598a63a7523ccdde36333f572974d3e64fc42153","trusted":true},"cell_type":"code","source":"punt_player_speed['SeriousCollision'] = 1 * punt_player_speed['Collision'] == 'Serious'\npunt_player_speed['MediumCollision'] = 1 * punt_player_speed['Collision'] == 'Medium'\nplay_collisions = punt_player_speed.groupby('PlayKey')[\n    ['SeriousCollision', 'MediumCollision']].sum()\nplay_collisions = play_collisions.merge(punts, on='PlayKey')\n\nc = play_collisions.groupby('PuntType')[['GameDate']].count()\ns = play_collisions.groupby('PuntType')[['SeriousCollision', 'MediumCollision']].sum()\nm = play_collisions.groupby('PuntType')[['SeriousCollision', 'MediumCollision']].mean()\n\npunt_type_collisions = pd.concat([c, s, m], axis=1)\npunt_type_collisions.columns = ['#Plays', '#SeriousCollision', '#MediumCollision',\n                                'SeriousCollision', 'MediumCollision']\npunt_type_collisions['#TotalCollisions'] = punt_type_collisions['#SeriousCollision'] + \\\n                                           punt_type_collisions['#MediumCollision']\npunt_type_collisions['TotalCollisions'] = punt_type_collisions['SeriousCollision'] + \\\n                                          punt_type_collisions['MediumCollision']\npunt_type_collisions = punt_type_collisions.sort_values(by='TotalCollisions', ascending=False)\npunt_type_collisions\npunt_type_collisions = punt_type_collisions[\n    ~punt_type_collisions.index.isin(['OTHER', 'NOPLAY'])]\npunt_type_collisions.sum()\n","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"_uuid":"7f74618ad871c93c7fa01a729c656c703af797b0","trusted":true},"cell_type":"code","source":"data = [\n    go.Bar(\n        y=punt_type_collisions['MediumCollision'].values,\n        x=punt_type_collisions.index.values,\n        marker=dict(color=C[1]),\n        text=punt_type_collisions.index.values,\n        name='Medium'\n    ),\n    go.Bar(\n        y=punt_type_collisions['SeriousCollision'].values,\n        x=punt_type_collisions.index.values,\n        marker=dict(color=C[0]),\n        text=punt_type_collisions.index.values,\n        name='Serious'\n    ),\n]\nlayout = go.Layout(\n    title='Collisions per  punt play',\n    barmode='stack',\n    hovermode='closest',\n    xaxis=dict(title='Punt Type', ticklen=5, zeroline=False, gridwidth=2),\n    yaxis=dict(title='Average dangerous collision per punt', ticklen=5, gridwidth=2),\n    showlegend=True\n)\nfig = go.Figure(data=data, layout=layout)\npy.iplot(fig, filename='PuntTypeCollisionBar')\n","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"_uuid":"093ab5e107bb1249a1bc0c1c926384421206944e","scrolled":false,"trusted":true},"cell_type":"code","source":"data = [\n    go.Scatter(\n        y=punt_type_collisions['TotalCollisions'].values,\n        x=punt_type_collisions.index.values,\n        mode='markers',\n        marker=dict(sizemode='diameter',\n                    sizeref=1,\n                    size=np.sqrt(punt_type_collisions['#Plays'].values),\n                    color=punt_type_collisions['TotalCollisions'].values,\n                    colorscale='Viridis',\n                    reversescale=True,\n                    showscale=True\n                    ),\n        text=punt_type_collisions['#Plays'].values,\n    )\n]\nlayout = go.Layout(\n    autosize=True,\n    title='Collisions per punt play',\n    hovermode='closest',\n    xaxis=dict(title='Punt Type', ticklen=5, zeroline=False, gridwidth=2),\n    yaxis=dict(title='Average dangerous collisions per punt', ticklen=5, gridwidth=2),\n    showlegend=False\n)\nfig = go.Figure(data=data, layout=layout)\npy.iplot(fig, filename='WeeklyTopTopics')\nwrite_image(fig, 'CollisionScatter.svg')\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"6ee5b91082cf58e9f81f092c9f7fa21c51ff14dc"},"cell_type":"markdown","source":"# Player Roles"},{"metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"_uuid":"623e8bdf079286bdc2cdfa1410b91ecbe839e4df","trusted":true},"cell_type":"code","source":"player_collisions = punt_player_speed.groupby(['ShortRole', 'PuntReturn'])[\n    ['SeriousCollision', 'MediumCollision']].mean()\nplayer_collisions['TotalCollisions'] = player_collisions['SeriousCollision'] + \\\n                                       player_collisions['MediumCollision']\nplayer_collisions = player_collisions.reset_index()\nplayer_collisions = player_collisions.sort_values(by=[\n    'PuntReturn', 'TotalCollisions'], ascending=False)\nplayer_collisions['color'] = player_collisions.PuntReturn.replace({True: C[1], False: C[0]})\nplayer_collisions\n","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"_uuid":"371d39f9fa8f3d7bcb8ce40c26c6fae9ff81cb7b","trusted":true},"cell_type":"code","source":"data = [\n    go.Bar(\n        y=player_collisions['TotalCollisions'].values,\n        x=player_collisions.ShortRole.values,\n        marker=dict(color=player_collisions.color.values),\n        text=player_collisions.ShortRole.values,\n        name='Medium'\n    )]\nlayout = go.Layout(\n    title='Player Collisions',\n    barmode='stack',\n    hovermode='closest',\n    xaxis=dict(title='Punt Return and Punt Coverge Roles',\n               ticklen=5, zeroline=False, gridwidth=2),\n    yaxis=dict(title='Probablity of collision for each player', ticklen=5, gridwidth=2),\n)\nfig = go.Figure(data=data, layout=layout)\npy.iplot(fig, filename='PlayerRoleCollisionBar')\nwrite_image(fig, 'PlayerRoleCollisionBar.svg')\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"21f2761924049777db06a82d463fe7378281a746"},"cell_type":"markdown","source":"# Collisions on the field"},{"metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"_uuid":"2b8c82e149f7692afb53dfd19514643a278541e2","trusted":true},"cell_type":"code","source":"collision_coords = punt_player_speed[punt_player_speed.Collision.isin(['Medium', 'Serious'])]\ncollision_coords = collision_coords[['CollisionX', 'CollisionY']].dropna()\ncollision_coords['CollisionX'] = collision_coords['CollisionX'].clip(0, 100)\ncollision_coords['CollisionY'] = collision_coords['CollisionY'].clip(0, 54)\ncollision_coords.shape\ncollision_coords.head(2)\ncollision_coords.describe()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"_uuid":"b84ba8ebaf5dd4430f298df1e8ca4e9438c79e5c","trusted":true},"cell_type":"code","source":"g = sns.jointplot(\"CollisionX\", \"CollisionY\", data=collision_coords,\n                  kind=\"kde\", space=0, color=C[1])\ng.ax_joint.plot(collision_coords.CollisionX, collision_coords.CollisionY, 'k+', alpha=0.1)\ng.ax_joint.set_xlim(0, 100)\ng.ax_joint.set_xticks(range(0, 101, 10))\ng.ax_joint.set_xticklabels(\n    ['GL', '10', '20', '30', '40', '50', '40', '30', '20', '10', 'GL'])\ng.ax_joint.set_ylim(-7, 60)\ng.ax_joint.set_yticks([0, 53.3])\ng.ax_joint.set_yticklabels(['Sideline', 'Sideline'])\ng.ax_joint.grid()\ng.fig.set_figheight(8)\ng.fig.set_figwidth(16)\ng.fig.savefig('CollisionCoords.svg')\nplt.show();\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"e4be96b0d41898428d08986b26737d84d4c1cc9f"},"cell_type":"markdown","source":"# Animation"},{"metadata":{"_kg_hide-input":true,"_uuid":"40091e8e6305738939bab9dc6c4c5df362959131","trusted":true},"cell_type":"code","source":"playkey = '397_1526'\nngs = get_ngs(playkey)\nngs['PlayKey'] = playkey\nngs['x'] = ngs['x'] - 10\nngs['px'] = 244 + ngs['x'] / 100 * (1920 - 488)\nngs['py'] = 100 + (53.3 - ngs['y']) / 53.3 * (957 - 200)\np = punt_player_speed[['PlayKey', 'GSISID', 'ShortRole', 'PuntReturn',\n                       'CollisionX', 'CollisionY', 'MinAcceleration', 'Collision']]\nngs = ngs.merge(p, on=['PlayKey', 'GSISID'])\nngs = ngs[(ngs.t >= 8) & (ngs.t <= 17)]\n","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"_uuid":"096680358f7e48245bb0f725f91d985386de4c6c","trusted":true},"cell_type":"code","source":"rc('animation', html='jshtml', embed_limit=100)\nfig, c_ax = plt.subplots(1, 1, figsize=(20, 10))\nfield = mpimg.imread(os.path.join(EXT_DIR, 'field home1920.png'))\nc_ax.imshow(field)\nc_ax.axis('off')\nstep_count = 30\nstep_length = 10 * 1000 / step_count\ntime_steps = np.linspace(ngs['t'].min(), ngs['t'].max(), step_count + 1)\n\ndef update_frame(i):\n    n_rows = ngs[ngs['t'] <= time_steps[i + 1]]\n    n_rows = n_rows[n_rows['t'] > time_steps[i]]\n    for (player_id, punt_return_team, role), player_rows in n_rows.groupby(\n            ['GSISID', 'PuntReturn', 'ShortRole']):\n        player_rows = player_rows.sort_values('t')\n        color = C[0] if punt_return_team else C[-1]\n        alpha = 1. if player_id in (32894, 31763) else 0.2\n        c_ax.plot(player_rows['px'], player_rows['py'], color=color, alpha=alpha,\n                  label='_nolegend_', lw=2)\n\na = animation.FuncAnimation(fig, update_frame, range(step_count), interval=step_length)\nplt.close();\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8758cb2887c54d40e37b9a39405dc3878e72c6fb","trusted":true},"cell_type":"code","source":"HTML(a.to_jshtml())\n","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"_uuid":"ab2b82897803df4efb22944bbb4f3fd0b3bb432d","trusted":true},"cell_type":"code","source":"HTML('''<video width=\"800\" height=\"450\" controls>\n  <source src=\"https://s3-eu-west-1.amazonaws.com/nfl-punt-analytics/BudapesPythonsFromRawDataToInsights.mp4\" type=\"video/mp4\">\nYour browser does not support the video tag.</video>''')\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"9c2a8186b843af9042c06edaf2c717745e9582a6"},"cell_type":"markdown","source":"\n# References\n\n[1] https://nextgenstats.nfl.com/stats/top-plays/fastest-ball-carriers/2016/all\n\n[2] https://www.ncbi.nlm.nih.gov/pubmed/14519212\n\n[3] https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2943536/\n\n[4] https://www.washingtonpost.com/news/early-lead/wp/2018/01/18/a-new-study-shows-that-hits-to-the-head-not-concussions-cause-cte"},{"metadata":{"_uuid":"ee783abc8c7e456b35704bad2c3a89937214b4e0","trusted":true},"cell_type":"code","source":"end = dt.datetime.now()\nprint('Latest run {}.\\nTotal time {}s'.format(end, (end - start).seconds))","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.5"}},"nbformat":4,"nbformat_minor":1}