{"cells":[{"metadata":{},"cell_type":"markdown","source":"# NFL 1st and Future 2019\n## Can you investigate the relationship between the playing surface and the injury and performance of NFL athletes?\n\n![](https://1ycbx02rgnsa1i87hd1i7v1r-wpengine.netdna-ssl.com/wp-content/uploads/2019/01/nfl.png)\n\nThis kernel is made in hopes of helping those interested in joining the competition get a jump start on the data. Much of the text was taken directly from the competition description. However be sure to read the official rules and data description on the kaggle website [here](https://www.kaggle.com/c/nfl-playing-surface-analytics).\n\ntl;dr:\n\n**In this challenge, you're tasked to investigate the relationship between the playing surface and the injury and performance of National Football League (NFL) athletes and to examine factors that may contribute to lower extremity injuries.**\n\nSubmissions will be judged by the NFL based on how well they address:\n- Representation of player movement, including, but not limited to, the development of novel metrics that characterize player movement on the field:\n- Identification of specific variables that present an elevated risk of injury:\n- Evaluation of differences in player movement between playing surfaces:\n\nSubmissions will be scored using the following rubric:\n- Creativity and Presentation (5 points)\n- Methodology (5 points)\n- Application (5 points)","execution_count":null},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pylab as plt\nimport seaborn as sns\nimport matplotlib.patches as patches\nsns.set_style(\"whitegrid\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Data:\nThere are three files provided in the dataset, as described below:\n\n1. **Injury Record:** The injury record file in .csv format contains information on 105 lower-limb injuries that occurred during regular season games over the two seasons. Injuries can be linked to specific records in a player history using the PlayerKey, GameID, and PlayKey fields.\n\n2. **Play List:** – The play list file contains the details for the 267,005 player-plays that make up the dataset. Each play is indexed by PlayerKey, GameID, and PlayKey fields. Details about the game and play include the player’s assigned roster position, stadium type, field type, weather, play type, position for the play, and position group.\n\n3. **Player Track Data:** player level data that describes the location, orientation, speed, and direction of each player during a play recorded at 10 Hz (i.e. 10 observations recorded per second).\n","execution_count":null},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"# Read the input files\nplaylist = pd.read_csv('../input/nfl-playing-surface-analytics/PlayList.csv')\ninj = pd.read_csv('../input/nfl-playing-surface-analytics/InjuryRecord.csv')\ntrk = pd.read_csv('../input/nfl-playing-surface-analytics/PlayerTrackData.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"inj.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Injury Data\nFirst lets look at the injury data. It's a fairly small file with only 105 injury plays shown. I notice that many of the rows for the injury plays do not show `PlayerKey`, `GameId`, etc. I'm not sure if this is a bug or intentially done.\n\n- PlayerKey, GameId, PlayKey\n- BodyPart\n- Surface\n- DM_M1, DM_M7, DM_28, DM_42 - One hot encoding the number of days missed for injury","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# 28 Injurties without PlayKey\ninj['PlayKey'].isna().sum()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"inj.groupby('BodyPart').count()['PlayerKey'].sort_values().plot(kind = 'bar', figsize =(15,5), title = 'Count of injuries by Body Part')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"inj.groupby('Surface').count()['PlayerKey'].sort_values().plot(kind = 'barh', figsize = (15,5), title = 'Count of injuries by Field Surface', color = 'red')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"inj.groupby(['BodyPart','Surface']).count().unstack('BodyPart')['PlayerKey'].T.sort_values('Natural').T.sort_values('Ankle').plot(kind='bar', figsize=(15, 5), title='Injury Body Part by Turf Type')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Playlist Data","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# Number of unique plays in the playlist dataset\nplaylist['PlayKey'].nunique()\nplaylist.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"playlist[['PlayKey', 'PlayType']].drop_duplicates().groupby('PlayType').count()['PlayKey'].sort_values().plot(kind = 'barh', figsize = (15,6), color = 'black', title = 'Number of Plays provided by type')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Match Player info with injury data\n- Only 77 link up the player info","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"inj_detailed = inj.merge(playlist)\ninj_detailed.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"inj_detailed.groupby('RosterPosition').count()['PlayerKey'].sort_values().plot(figsize=(15, 5), kind='barh', title='Injured Players by Position')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"inj_detailed.groupby('PlayType').count()['PlayerKey'].sort_values().plot(figsize=(15, 5), kind='barh', title='Injured Players by PlayType', color='green')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Distribution of Injury Types","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"inj_detailed.groupby(['RosterPosition', 'BodyPart']).count().unstack('BodyPart')['PlayerKey'].T.apply(lambda x: x / x.sum()).sort_values('BodyPart').T.sort_values('Ankle', ascending=False).plot(kind = 'barh', figsize=(15,5), title = 'Injury Body Part by Player Position', stacked = True)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"inj_detailed.groupby(['PlayType', 'BodyPart']).count().unstack('BodyPart')['PlayerKey'].T.apply(lambda x: x / x.sum()).sort_values('BodyPart').T.sort_values('Ankle', ascending=False).plot(kind='barh', figsize=(15, 5), title='Injury Body Part by Play Type', stacked=True)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"inj_detailed.groupby(['RosterPosition','Surface']) \\\n    .count() \\\n    .unstack('Surface')['PlayerKey'] \\\n    .T.apply(lambda x: x / x.sum()) \\\n    .sort_values('Surface').T.sort_values('Natural', ascending=False) \\\n    .plot(kind='barh',\n          figsize=(15, 5),\n          title='Injury Body Part by Turf Type',\n          stacked=True)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Plotting Plays\nCheck out my kernel here where I provide a function for creating and plotting a NFL football field.\n\nhttps://www.kaggle.com/robikscube/nfl-big-data-bowl-plotting-player-position","execution_count":null},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"def 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\n\ncreate_football_field()\nplt.show()\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Plot path of injured player","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"example_play_id = inj['PlayKey'].values[0]\nfig, ax = create_football_field()\ntrk.query('PlayKey == @example_play_id').plot(kind= 'scatter', x='x', y= 'y', ax=ax, color='blue')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Plotting every route of injured players\n- Too much info to draw conclusions, but fun to plot for context.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# Loop through all 99 inj plays\ninj_play_list = inj['PlayKey'].tolist()\nfig, ax = create_football_field()\nfor player, inj_play in trk.query('PlayKey in @inj_play_list').groupby('PlayKey'):\n    inj_play.plot(kind = 'scatter', x = 'x', y = 'y', ax = ax, color = 'orange', alpha = 0.2)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Plotting routes of some non-injured players","execution_count":null},{"metadata":{"trusted":true},"cell_type":"raw","source":"import random\nplayids = trk['PlayKey'].unique() #.sample(100)\nnon_inj_play = [x for x in playids if x not in inj_play_list]\nsample_non_inj_plays = random.sample(non_inj_play, 100)\n\nfig, ax = create_football_field()\nfor playkey, inj_play in trk.query('PlayKey in @sample_non_inj_plays').groupby('PlayKey'):\n    inj_play.plot(kind='scatter', x='x', y='y', ax=ax, color='red', alpha=0.2)\nplt.show()","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"## Distribution of tracking info for players with injuries","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"fig, axes = plt.subplots(1,2)\n\ntrk.query('PlayKey in @inj_play_list')['s'].plot(kind='hist',title='Distribution of player Speed injured', figsize=(15,5), bins=30, ax=axes[0])\ntrk.query('PlayKey not in @inj_play_list')['s'].sample(10000).plot(kind='hist', title = 'Distribution of player Speed not injured', figsize=(15,5),bins=30,ax=axes[1],color='orange')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"fig, axes = plt.subplots(1,2)\n\ntrk.query('PlayKey in @inj_play_list')['o'].plot(kind='hist',title='Distribution of player Orientation injured', figsize=(15,5),bins=30,ax=axes[0])\ntrk.query('PlayKey not in @inj_play_list')['o'].sample(10000).plot(kind='hist',title='Distribution of player Orientation not injured',figsize=(15,5),bins=30,ax=axes[1],color='orange')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Differences in x, y could be attributed to the player positions which are more likely to have injury.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"fig, axes = plt.subplots(1,2)\ntrk.query('PlayKey in @inj_play_list')['x'].plot(kind='hist',title='Distribution of player X injured',figsize = (15,5), bins=30, ax=axes[0])\ntrk.query('PlayKey not in @inj_play_list')['x'].sample(10000).plot(kind='hist',title='Distribution of player X not in injured',figsize=(15,5),bins=30,ax=axes[1],color='orange')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"fig, axes = plt.subplots(1,2)\n\ntrk.query('PlayKey in @inj_play_list')['y'].plot(kind='hist',title='Distribution of player Y injured',figsize=(15,5),bins=30,ax=axes[0])\ntrk.query('PlayKey not in @inj_play_list')['y'].sample(10000).plot(kind='hist',title='Distribution of player Y not injured',figsize=(15,5),bins=30,ax=axes[1],color='orange')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Compass plots of direction/velocity","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def compass(angles, radii, arrowprops=None, ax=None):\n    \"\"\"\n    * Modified for NFL data plotting\n    Compass draws a graph that displays the vectors with\n    components `u` and `v` as arrows from the origin.\n\n    Examples\n    --------\n    >>> import numpy as np\n    >>> u = [+0, +0.5, -0.50, -0.90]\n    >>> v = [+1, +0.5, -0.45, +0.85]\n    >>> compass(u, v)\n    \"\"\"\n    \n    if ax is None:\n        fig,ax=plt.subplots(subplot_kw=dict(polar=True))\n        \n    kw=dict(arrowstyle=\"->\", color='k')\n    if arrowprops:\n        kw.update(arrowprops)\n    [ax.annotate(\"\",xy=(angle,radius),xytext=(0,0),arrowprops=kw) for angle, radius in zip(angles,radii)]\n    \n    ax.set_ylim(0,np.max(radii))\n    \ndef plot_play_compass(playkey, **kwargs):\n    d = trk.loc[trk['PlayKey'] == playkey].copy()\n    d['dir_theta'] = d['dir'] * np.pi / 180\n    # Calculate velocity in meters per second\n    d['dis_meters'] = d['dis'] / 1.0936  # Add distance in meters\n    # Speed\n    d['dis_meters'] / 0.01\n    d['v_mps'] = d['dis_meters'] / 0.1\n\n    ax = compass(d['dir_theta'], d['v_mps'],\n                  arrowprops={'alpha': 0.3},\n                **kwargs)\n    return ax","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"fig,axes=plt.subplots(2,3,subplot_kw=dict(polar=True),figsize=(15,10))\naxes=np.array(axes)\naxes=axes.reshape(-1)\n\ni=0\nfor p in inj_detailed['PlayKey'].values[:6]:\n    plot_play_compass(p,ax=axes[i])\n    axes[i].set_title(f'PlayKey:{p}')\n    i+=1\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Player Position vs Compass Plot","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"example_play_id=inj['PlayKey'].values[6]\ninj_detailed.query('PlayKey in @example_play_id')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"fig,ax = create_football_field(ax)\nax.set_title(f'PlayKey: {example_play_id}')\ntrk.query('PlayKey == @example_play_id').plot(kind='scatter',x='x',y='y',ax=ax,color='blue')\nplt.show()\n\nax = plot_play_compass(example_play_id)\nax.set_title(f'PlayKey: {p}')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Plays by max speed","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"trk.groupby('PlayKey')[['s']].max().sort_values('s', ascending=False).query('s != 0').head(20).plot(kind='barh',figsize=(15,5),title='Top 20 Plays by Max Player Speed')\nplt.show()\ntrk.groupby('PlayKey')[['s']].max().sort_values('s', ascending=True).query('s != 0').head(20).plot(kind='barh',figsize=(15,5),title='Bottom 20 Plays by Min Player Speed')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Injury Length\n- DM columns tell us a one-hot encoding for the number of days missed due to injury\n- 1+, 7+, 28+, and 48+ days missed\n\nWe can see that all the injuries caused at least 1 day missing and around 30% missed 48+ days.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"inj[['DM_M1','DM_M7','DM_M28','DM_M42']].mean().plot(kind='bar',figsize=(15,5),title='Percent of injuries by injury length')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Look at the top of each data file","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"inj.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"trk.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"playlist.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Any ideas of what else you would like to see? Let me know in the comments.","execution_count":null}],"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":4,"nbformat_minor":4}