{"cells":[{"metadata":{"_uuid":"d5b18f5ad4af926f91869090c8b004380328bcea"},"cell_type":"markdown","source":"# NFL Punt Rule Change Analysis\n## Kevin McGovern and Todd Steussie\nThis notebook contains visualizations used to evaluate NFL punt plays in 2016 and 2017 and support a suggested rule change.\n"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load in \n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\nfrom ipywidgets import interactive\nfrom IPython.display import HTML\n\nfrom bokeh.plotting import figure, ColumnDataSource\nfrom bokeh.io import output_notebook, show, push_notebook\nfrom bokeh.models import CustomJS, Slider, Button, BoxAnnotation, LabelSet, LinearAxis, Range1d\nfrom bokeh.models.markers import Triangle\nfrom bokeh.layouts import row, widgetbox\nimport bokeh.models as bmo\n\nimport seaborn as sns; sns.set()\n\nimport matplotlib.pyplot as plt\n\noutput_notebook()\n\n# Input data files are available in the \"../input/\" directory.\n# For example, running this (by clicking run or pressing Shift+Enter) will list the files in the input directory\n\nimport os\n#print(os.listdir(\"../input/nfl-punt-analysis-data-output\"))\n\n# Load full data set\npuntDF = pd.read_csv('../input/nfl-punt-analysis-data-output/playerMvmt-level-data.csv')\nplayLvlDF = pd.read_csv('../input/nfl-punt-analysis-data-output/play-level-data.csv')\nplayLvlDF.loc[:,('Block Type')] = ['Blindside' if x == 'TRUE' else 'Non-Blindside' for x in playLvlDF['blindsideBlock']]\nplayLvlCleanDF = playLvlDF[(playLvlDF['Primary_Impact_Type'] != 'Unclear') & (playLvlDF['InjCtrlFlag'] == 'Injury')]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"3af6445b69d9b77d9c4a931d3e9da789507110d7","_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"\"\"\"Return a data frame\n\nFunction to return a data frame that contains a set of movements over the entire life of a play, for a specific play and set of actors\n\nParameters: PlayID, actors (list of GSISIDs)\n\"\"\"\ndef get_data_over_time(playID = 3746, actors = ['27654.0','33127.0']):\n    limitedSample = puntDF.loc[(puntDF['PlayID'] == playID)]\n    return limitedSample[limitedSample['GSISID_y'].isin(actors)]\n\n\"\"\"Return a data frame\n\nFunction to return a data frame that contains a single play at a particular point in time for all actors\n\nParameters: PlayID, timeNum (integer describing deciseconds from start of play)\n\"\"\"\ndef update_sample(playID = 3746, timeNum = 1):\n    return puntDF.loc[(puntDF['PlayID'] == playID) & (puntDF['TimeNum'] == timeNum)]\n\n\"\"\"Return a data frame\n\nFunction to return a data frame that contains only the injured and primary actor. Used for drawing distance line between players\n\nParameters: limitedSample (data frame)\n\"\"\"\ndef update_interactors(limitedSample = puntDF.loc[(puntDF['PlayID'] == 3746)]):\n    return limitedSample[(limitedSample['GSISID_x'] == limitedSample['GSISID_y']) | (limitedSample['Primary_Partner_GSISID'] == limitedSample['GSISID_y'])]\n\n\"\"\"Return a data frame\n\nFunction to return a data frame that contains only the data for a specific play\n\nParameters: playID\n\"\"\"\ndef get_play(playID = 3746):\n    return playLvlDF.loc[(playLvlDF['PlayID'] == playID)]\n\n\"\"\"Return a string\n\nFunction to return a string that explains what happened on a particular play\n\nParameters: playID\n\"\"\"\ndef generatePlayText(playID = 3746):\n    playCalled = get_play(playID)\n    return str(playCalled['GSISID'].iloc[0]) + ' was injured in ' + str(playCalled['Season_Year'].iloc[0]) + '. The contact was made by ' + playCalled['Player_Activity_Derived'].iloc[0] + ' on ' + playCalled['Primary_Impact_Type'].iloc[0] + ' contact.'\n\n# Create a dict of impacts\ndata = {\n    '1': [1683, 30665.0, 90, 110, 'Non-Earhole'],\n    '2': [3746, 29510.0, 265, 285, 'Non-Earhole'],\n    '3': [1045, 28698.0, 175, 195, 'Non-Earhole'],\n    '4': [1976, 32807.0, 190, 210, 'Earhole'],\n    '5': [2341, 32998.0, 140, 160, 'Earhole'],\n    '6': [2792, 31317.0, 185, 205, 'Earhole'],\n    '7': [1045, 31756.0, 140, 160, 'Earhole'],\n    '8': [1088, 31999.0, 335, 355, 'Earhole'],\n    '9': [1526, 30789.0, 125, 145, 'Earhole'],\n    '10': [1683, 25503.0, 125, 145, 'Earhole'],\n    '11': [2489, 32851.0, 120, 140, 'Earhole'],\n    '12': [2587, 31059.0, 160, 180, 'Earhole'],\n    '13': [2764, 31930.0, 105, 125, 'Earhole'],\n}\n\n# Create a dataframe from above dictionary\nimpactPlays = pd.DataFrame.from_dict(data, orient='index',\n                       columns=['PlayID', 'GSISID_y', 'ImpactStart', 'ImpactEnd', 'Block Type'])\n\n# merge time-base puntDF with impactPlays df created above\nimpactPlayTime = pd.merge(impactPlays, puntDF, how=\"inner\", on=['PlayID', 'GSISID_y'])\n\n# only keep plays of windows where contact occurred\nimpactPlayTime = impactPlayTime.loc[(impactPlayTime['TimeNum'] >= impactPlayTime['ImpactStart']) & (impactPlayTime['TimeNum'] <= impactPlayTime['ImpactEnd'])]\n\n# group time by every five deciseconds to smooth data\nimpactPlayTime['TimeFloor'] = impactPlayTime['TimeNum']//5\n\n# group by newly created time dimension\nimpactPlayTime = impactPlayTime.groupby(['TimeFloor','PlayID','Block Type','GSISID_y'], as_index=False)['Speed'].mean()\n\n# multiply time by five so new timenum aligns to old timenum\nimpactPlayTime['TimeNum'] = impactPlayTime['TimeFloor']*5","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"0f92068ae424e65d4bcecd8c4d2fda05ee8320f0","_kg_hide-input":true},"cell_type":"code","source":"\"\"\"Return a statement\n\nFunction to update all charts when changes are made to notebook interactive elements. Will update the three charts at bottom of the page\n\nParameters: PlayID, timeNum (integer describing deciseconds from start of play)\n\"\"\"\ndef update_plot(timeNum, playID):\n    #update circles\n    limitedSample = update_sample(playID, timeNum)\n    x = limitedSample['x']\n    y = limitedSample['y']\n    colorMap = ['#ff0000' if x in(limitedSample['GSISID_x'].values) else '#0000ff' if x in(limitedSample['Primary_Partner_GSISID'].values) else '#545454' for x in limitedSample.GSISID_y]\n\n    # update circle data in circle figure\n    circlePlt.data_source.data['x'] = x\n    circlePlt.data_source.data['y'] = y\n    circlePlt.data_source.data['fill_color'] = colorMap\n    \n    #update lines using similar method as above\n    distanceLine = update_interactors(limitedSample)\n    line_x = distanceLine['x']\n    line_y = distanceLine['y']\n    lines.data_source.data['x'] = line_x\n    lines.data_source.data['y'] = line_y\n    \n    # push notebook changes to chart\n    push_notebook(handle=bokeh_handle)\n    \n    # Update distance chart\n    distanceDF = get_data_over_time(playID, actors = [get_play(playID)['GSISID'].iloc[0]])\n    x = distanceDF['TimeNum']\n    y = distanceDF['Distance']\n\n    # update line data in line figure\n    distanceLines.data_source.data['x'] = x\n    distanceLines.data_source.data['y'] = y\n    \n    push_notebook(handle=bokeh_handle2)\n    \n    # Update speed chart\n    distanceDF = get_data_over_time(playID, actors = [get_play(playID)['GSISID'].iloc[0]])\n    x = distanceDF['TimeNum']\n    y = distanceDF['Speed']\n    injSpeedlines.data_source.data['x'] = x\n    injSpeedlines.data_source.data['y'] = y\n\n    # do the same thing as above but for primary partner\n    distanceDF = get_data_over_time(playID, actors = [get_play(playID)['Primary_Partner_GSISID'].iloc[0]])\n    x = distanceDF['TimeNum']\n    y = distanceDF['Speed']\n    primActorSpeedlines.data_source.data['x'] = x\n    primActorSpeedlines.data_source.data['y'] = y\n    \n    push_notebook(handle=bokeh_handle3)\n    \n    return generatePlayText(playID)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"a187d490b390ea6846f7fe6170bd0af7a645ed47","_kg_hide-input":true},"cell_type":"code","source":"\"\"\"Return a Bokeh figure\n\nFunction to create Bokeh x-y movement charts over time\n\nParameters: PlayID, actors (list of relevant players), figure (bokeh chart figure reference)\n\"\"\"\ndef create_bokeh_play_x_y_lines(playID, actors, figure):\n\n    # calculate base data set\n    limitedSample = get_data_over_time(playID = playID, actors = actors)\n    x = limitedSample[limitedSample['GSISID_y'] == float(actors[0])]['x']\n    y = limitedSample[limitedSample['GSISID_y'] == float(actors[0])]['y']\n    x2 = limitedSample[limitedSample['GSISID_y'] == float(actors[1])]['x']\n    y2 = limitedSample[limitedSample['GSISID_y'] == float(actors[1])]['y']\n\n    # add line renderers with color, width, and alpha\n    figure.line(x, y, line_color='#b22222',line_width=3, alpha=0.8)\n    figure.line(x2, y2, line_color='#4682b4',line_width=3, alpha=0.8)\n    #plt2 = figure.circle(x, y, size=8, fill_color=color_map, alpha=0.8)\n\n    # create boxes that segment the field\n    low_box = BoxAnnotation(right=10, fill_alpha=0.2, fill_color='#555555')\n    mid_box = BoxAnnotation(left=10, right=110, fill_alpha=0.2, fill_color='green')\n    high_box = BoxAnnotation(left=110, fill_alpha=0.2, fill_color='#555555')\n\n    # add a line used to create lines on field and a consistent window\n    figure.line([10,10,20,20,30,30,40,40,50,50,60,60,70,70,80,80,90,90,\n              100,100,110,110,120,120],\n             [55.5,0,0,55.5,55.5,0,0,55.5,55.5,0,0,55.5,55.5,0,0,55.5,\n             55.5,0,0,55.5,55.5,0,0,0], line_color='grey')\n\n    figure.add_layout(low_box)\n    figure.add_layout(mid_box)\n    figure.add_layout(high_box)\n\n    return figure\n\n\"\"\"Return a Bokeh figure\n\nFunction to create Bokeh line charts over time\n\nParameters: PlayID, actors (list of relevant players), X (x-axis string ref), Y (y-axis string ref), lineFig (bokeh chart figure reference), \ncolor (color of line), rightAxis (reference to right axis of chart), rightAxisName (name entered inside paren for chart axis)\n\"\"\"\ndef create_bokeh_lines(playID, actor, X, Y, lineFig, color='black', rightAxis=None, rightAxisName=\"\"):\n    # calculate base data set\n    distanceDF = get_data_over_time(playID = playID, actors = [actor])\n    distanceDF['TimeFloor'] = distanceDF['TimeNum']//5\n    distanceDF = distanceDF.groupby(['TimeFloor','PlayID'], as_index=False)[Y].mean()\n    distanceDF['TimeNum'] = distanceDF['TimeFloor']*5\n    x = distanceDF[X]\n    y = distanceDF[Y]\n\n    # return the line renderer with a color, width, and alpha\n    if rightAxis==None:\n        return lineFig.line(x, y, line_color = color, line_width = 2, alpha = 0.8)\n    else:\n        # Setting the second y axis range name and range\n        minval = y.min()*.8 if y.min()>0 else -y.max()*.05 if y.min()==0 else y.min()*1.2\n        lineFig.extra_y_ranges = {rightAxis: Range1d(minval,y.max()*1.2)}\n        lineFig.add_layout(LinearAxis(y_range_name=rightAxis, axis_label=Y + ' ('+ rightAxisName +')'), 'right')\n        return lineFig.line(x, y, line_color = color, line_width = 2, alpha = 0.8, y_range_name=rightAxis)","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"7e31450ff76578cf23a622386a6695ae52304902"},"cell_type":"code","source":"# Create figure plot\ndistanceLineFig = figure(plot_width=800, plot_height=400)\n# Create distance lines\ndistanceLines = create_bokeh_lines(3746, '27654.0', 'TimeNum', 'Distance', distanceLineFig)\n\n# Create injured player and primary actor speed charts\nspeedLineFig = figure(plot_width=800, plot_height=400)\ninjSpeedlines = create_bokeh_lines(3746, '27654.0', 'TimeNum', 'Speed', speedLineFig, 'red')\nprimActorSpeedlines = create_bokeh_lines(3746, '33127.0', 'TimeNum', 'Speed', speedLineFig, 'blue')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"6d5bea9696eed0cc046a87a0366b36570b1579fd"},"cell_type":"markdown","source":"# Play Analysis\nBefore we decided on a rule change, we reviewed each play to determine the players' interactions and activities prior to the injury. The analysis that preceeded our rule recommendation is listed below.\n\nNote: we excluded one injury from our analysis where the cause was listed as 'unclear', as it did not inform the high-level analysis."},{"metadata":{"_uuid":"173b67a32b9ebb328b58c55fe22e86ad7dbfe766"},"cell_type":"markdown","source":"The breakdown of primary impact shows that the vast marjority of concussions occurred during either helmet-to-body or helmet-to-helmet collisions."},{"metadata":{"trusted":true,"_uuid":"b240f9272df2906a528cc9f6af2c58d130aa8bec","_kg_hide-input":true},"cell_type":"code","source":"## Primary Impact\nplt.figure(figsize=(10,5))\nsns.set_style(\"darkgrid\")\nsns.set_palette(\"YlGnBu\", n_colors=3)\ncountplt = sns.countplot(x=\"Primary_Impact_Type\", data=playLvlCleanDF)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"442798270d2274bced1e4510a311c11b352d30e3"},"cell_type":"markdown","source":"The source of the injury was roughly split between tackling and blocking."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"4dddb7923a59d8c7505b60611303662ad4b17213"},"cell_type":"code","source":"## Primary Impact\nplt.figure(figsize=(10,5))\nsns.set_style(\"darkgrid\")\nsns.set_palette(\"YlGnBu\", n_colors=4)\ncountplt2 = sns.countplot(x=\"Player_Activity_Derived\", data=playLvlCleanDF)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d814564566ed2b25ad4f516de360e86cba0d6d62"},"cell_type":"markdown","source":"## Of the plays provided by the NFL for this competition, a significant percentage of concussions occurred when the player was blocking the opponent with helmet-to-body contact."},{"metadata":{"trusted":true,"_uuid":"b19f2f6f751057d429b5dac3e4fe54dbcac0aac8","_kg_hide-input":true},"cell_type":"code","source":"uniform_data = pd.pivot_table(playLvlCleanDF, values='PlayID', index=['Player_Activity_Derived'], columns=['Primary_Impact_Type'], aggfunc='count')\nplt.figure(figsize=(10,8))\nax = sns.heatmap(uniform_data, annot=True, cmap=\"YlGnBu\")","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"ba5715a15667a658ca420d7fec9860e6426a739b"},"cell_type":"markdown","source":"### Some trends start to emerge but it wasn't until we looked at game film that we saw a theme.\nAnalysis of the game film showed what was not immediately clear from the data. A significant number of the injuries occured when a player blocked a \"defenseless player\". Although applied to a different situational context, Article 7 of the NFL Rulebook establishes criteria for defenseless player status, stating that a player should be considered defenseless until \"the player is capable of avoiding or warding off the impending contact of an opponent\". These types of plays are also commonly referred to as blindside blocks."},{"metadata":{"trusted":true,"_uuid":"8354386a8c85b8451a2a8780c9cfb413eef12e46","_kg_hide-input":true},"cell_type":"code","source":"## Block type on blocking plays\nblockedDF = playLvlCleanDF[(playLvlCleanDF['Player_Activity_Derived'] == 'Blocked') | (playLvlCleanDF['Player_Activity_Derived'] == 'Blocking')]\nuniform_data = pd.pivot_table(blockedDF, values='PlayID', index=['Player_Activity_Derived'], columns=['Block Type'], aggfunc='count')\nplt.figure(figsize=(10,8))\nax2 = sns.heatmap(uniform_data, annot=True, cmap=\"YlGnBu\")","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"dc6b8b8ae204aa6b8f0e12449015b6b4897010d8"},"cell_type":"markdown","source":"## Of the 37 plays that lead to a concussion in 2016 and 2017, 11 of these were caused by a block occurring when the opponent does not have an opportunity to anticipate the impact. This is often a result of the opponent being oriented in a different direction, and as a result, was not aware of the imminent collision.\n\n## If adopted, this rule change would impact 65% of the injuries resulting from blocking on punt plays (11 of 17).\n"},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"f42f921c03b357029acc3ecb1649b9f126e9fc37"},"cell_type":"code","source":"HTML('<video width=\"600\" height=\"400\" controls> <source src=\"http://a.video.nfl.com//films/vodzilla/153252/44_yard_Punt_by_Justin_Vogel-n7U6IS6I-20181119_161556468_5000k.mp4\" type=\"video/mp4\"></video>')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"b4917d94cd7d727407c0b8ab624d2958951023bc"},"cell_type":"markdown","source":"# Suggested Rule Change\n## Rule: Penalize blocks where the opponent does not have an opportunity to anticipate the impact. These types of blocks occur when the opponent is oriented in a different direction, and as a result is not aware of an imminent collision. This rule change would extend defenseless player protection to all participants on punt plays.\n\nUnder current NFL rules, it is illegal to initiate contact against a player who is in a defenseless posture.  As our analysis has shown, a significant portion of injuries on punt plays have resulted from blocks against defenseless players. Although applied to a different situational context, Article 7 of the NFL Rulebook establishes criteria for defenseless player status, stating that a player is to be considered defenseless until \"the player is capable of avoiding or warding off the impending contact of an opponent\". \n\nIt is our contention that emposing defenseless player protection status for all players during punt plays would significantly reduce injuries. We believe that this rule change is a logical extension of the protections already enacted to protect defenseless players. Adopting this rule change is likely to have a significant positive impact on player safety as previous rules protecting defenseless players, with minimal risk of negative externalities associated with changes to strategy or player behavior. Players running downfield that do not see an incoming player are particularly vulnerable to injury. Not only are these blocks dangerous to the defenseless player, but they often result in injuries to the player attempting the block as well.\n"},{"metadata":{"_uuid":"1c34ee8b8e4c6846da2d1105ee9b19af1b2142bc"},"cell_type":"markdown","source":"## Rule Change Example #1\n### CAR-WAS 2016 Week 15 example of blindside block on the targeted player.\nThe charts below show how in a 2016 week 15 Panthers vs. Redskins game, a player suffered a concussion while being blocked by a player running downfield.\n"},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"7b7a655734ce4026e71908d27eb982c4b23136c2"},"cell_type":"code","source":"# Distance vs speed\nLineFigPlay1 = figure(plot_width=800, plot_height=400, title=\"CAR-WAS Distance Between Impacted Players (blue) vs. Speed of Injured Player (red)\")\n# Create distance lines\nLineFigPlay1Rtn = create_bokeh_lines(2341, '32007.0', 'TimeNum', 'Distance', LineFigPlay1, color='#008ac2')\nLineFigPlay1.yaxis.axis_label = \"Distance (yards)\"\nLineFigPlay1.xaxis.axis_label = \"Play Time (milliseconds)\"\nLineFigPlay1.background_fill_color = \"#efefef\"\nLineFigPlay1.background_fill_alpha = 0.5\n#LineFigPlay1.legend.location = \"top_right\"\n\nLineFigPlay1Rtn2 = create_bokeh_lines(2341, '32007.0', 'TimeNum', 'Speed', LineFigPlay1, color='#de425b', rightAxis='rightRange', rightAxisName='red')\n\nLineFigPlay1show = show(row(LineFigPlay1), notebook_handle=True)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"e08ba386e765eed8ad0d2df9bb9498f18d42db7e"},"cell_type":"markdown","source":"In the chart above, we compared speed of the injured player and distance between the injured player and the impacting player. As you can see the player is running at a high speed from the 0.6 second mark until the point of contact at 1.5 seconds. The two players are significantly far apart but quickly converged due to the high speed of both players."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"fbb8fbab566c82cdbea5d8d87c4749a87c526db5"},"cell_type":"code","source":"# Speed vs acceleration\n# Create injured player and primary actor speed charts\nspeedLineFigPlay1 = figure(plot_width=800, plot_height=400, title=\"CAR-WAS Speed (red) vs. Acceleration (green) of Injured Player\")\ninjSpeedlinesPlay1 = create_bokeh_lines(2341, '32007.0', 'TimeNum', 'Speed', speedLineFigPlay1, color='#de425b')\nspeedLineFigPlay1.yaxis.axis_label = \"Speed\"\nspeedLineFigPlay1.xaxis.axis_label = \"Play Time (milliseconds)\"\nspeedLineFigPlay1.background_fill_color = \"#efefef\"\nspeedLineFigPlay1.background_fill_alpha = 0.5\n\naccelSpeedlinesPlay1 = create_bokeh_lines(2341, '32007.0', 'TimeNum', 'Acceleration', speedLineFigPlay1, color='#00FA9A', rightAxis='rightRange', rightAxisName='green')\n\nPlay1show = show(row(speedLineFigPlay1), notebook_handle=True)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"f45c791e379253d9b9be9a9676edf2a003b800ab"},"cell_type":"markdown","source":"In the chart above we compare the speed of the injured player against the acceleration of the injured player. The injured player actually briefly decelerates, immediately prior to the moment of impact but not with nearly enough time to make a difference in his overall speed. He then then accelerates again as the two make contact at the 1.5 second mark. Because he makes contact at such a high speed, damage is maximized and he is immediately immobilized."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"89eb89e57a61293ac9ada551e854e1a7ab21a174"},"cell_type":"code","source":"# Paths\nXYFigPlay1 = figure(plot_width=800, plot_height=400, x_range=(0,120), y_range=(0, 53.3), title=\"CAR-WAS Path of Injured (red) and Impacting (blue) Players\")\nXYFigPlay1upd = create_bokeh_play_x_y_lines(playID = 2341, actors = ['32007.0', '32998.0'], figure = XYFigPlay1)\nbokeh_handle6 = show(row(XYFigPlay1upd), notebook_handle=True)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d1cb827ffe53b691e4776c82b43f87e500a4f333"},"cell_type":"markdown","source":"The above chart shows the path of the injured player (red) compared with the path of the impacting player (blue). The contact occurs downfield and you can see the injured player does not move far after the contact, while the blocking player runs through the block and off the field."},{"metadata":{"trusted":true,"_uuid":"15cd620640d74ed6df50be916db6153d95e6d28d"},"cell_type":"code","source":"HTML('<iframe width=\"700\" height=\"400\" src=\"https://streamable.com/s/p7exm/mepwve\" frameborder=\"0\" allowfullscreen></iframe>')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"b3ac935ce73401c5758105477666d23aa040b165","_kg_hide-input":true},"cell_type":"code","source":"HTML('<iframe width=\"700\" height=\"400\" src=\"https://streamable.com/s/qu3q4/azcmbw.mp4\" frameborder=\"0\" allowfullscreen></iframe>')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"49d9de31429a90ebd2bde85eba2c811a98c29a64"},"cell_type":"markdown","source":"View the video above to see the exact contact that led to the concussion."},{"metadata":{"_uuid":"4bfd7dce320ecb941b5e3d4e1bfd68332fb0d65e"},"cell_type":"markdown","source":"### Let's compare the motion metrics from the play above with a median impact from a typical block on a punt play. The typical block represents the replacement scenario if the rule change is adopted."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"cf53a7f7cc738d66f067b3955574fbdfc7970d1b"},"cell_type":"code","source":"# Speed vs acceleration\n# Create injured player and primary actor speed charts\nspeedLineFigPlay3 = figure(plot_width=800, plot_height=400, title=\"NYG-KC Speed (red) vs. acceleration (green) of blocked player on Ideal Block\")\ninjSpeedlinesPlay3 = create_bokeh_lines(1683, '25503.0', 'TimeNum', 'Speed', speedLineFigPlay3, color='#de425b')\nspeedLineFigPlay3.yaxis.axis_label = \"Speed\"\nspeedLineFigPlay3.xaxis.axis_label = \"Play Time (milliseconds)\"\nspeedLineFigPlay3.background_fill_color = \"#efefef\"\nspeedLineFigPlay3.background_fill_alpha = 0.5\n\naccelSpeedlinesPlay3 = create_bokeh_lines(1683, '25503.0', 'TimeNum', 'Acceleration', speedLineFigPlay3, color='#00FA9A', rightAxis='rightRange', rightAxisName='green')\n\nPlay3show = show(row(speedLineFigPlay3), notebook_handle=True)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"2efded3332fef6cab1d66f1342499b9d65e0d1ae"},"cell_type":"markdown","source":"On a play we would call an 'typical block' the above chart shows the speed and acceleration of a player engaging on a block. The contact with the player happens at roughly 0.9 seconds. There is a large deceleration period (green line) before engaging in the block, and once the two players release, the player accelerates again and begins to speed up to continue the remainder of the play."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"51213ce5c84d9b8ad78b05c9974cc2e95189d3a9"},"cell_type":"code","source":"# Speed vs acceleration\n# Create injured player and primary actor speed charts\nspeedLineFigPlay32 = figure(plot_width=800, plot_height=400, title=\"NYG-KC Speed (red) vs. acceleration (green) of blocking player on Ideal Block\")\ninjSpeedlinesPlay32 = create_bokeh_lines(1683, '30665.0', 'TimeNum', 'Speed', speedLineFigPlay32, color='#de425b')\nspeedLineFigPlay32.yaxis.axis_label = \"Speed\"\nspeedLineFigPlay32.xaxis.axis_label = \"Play Time (milliseconds)\"\nspeedLineFigPlay32.background_fill_color = \"#efefef\"\nspeedLineFigPlay32.background_fill_alpha = 0.5\n\naccelSpeedlinesPlay32 = create_bokeh_lines(1683, '30665.0', 'TimeNum', 'Acceleration', speedLineFigPlay32, color='#00FA9A', rightAxis='rightRange', rightAxisName='green')\n\nPlay32show = show(row(speedLineFigPlay32), notebook_handle=True)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"425841843cc1aa46733b00e0db196ee1ab27852f"},"cell_type":"markdown","source":"For the blocked player, because he is able to anticipate the block he decelerates leading into the the 0.9 second contact period and is able to accelerate again after he is free from the block. This contact is significantly safer than contact where one player does not anticipate being blocked."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"3a81ea8ac9214e24b5d0de4efca64a0894a7f16c"},"cell_type":"code","source":"plt.figure(figsize=(10,8))\nax = sns.boxplot(x=\"Block Type\", y=\"Speed\", hue=\"Block Type\",\n                  data=impactPlayTime, palette=\"YlGnBu\")","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"7dab09ff8ba858a118a1029f928f40bbb0d70138"},"cell_type":"markdown","source":"The above chart shows the speed of the blocking player on the plays with an blindside block vs. the speed of player on what we considered ideal blocks during the point of contact. As you can see the speed of the player on blindside blocks is signficantly higher as he engages with a defenseless blocker."},{"metadata":{"_uuid":"884f9184812e4b9b41dacccf91048b8d122d12e1"},"cell_type":"markdown","source":"## Rule Change Example #2\n### NO-MIA 2017 Week 4 example of blindside block leading to concussion for the blocking player.\n\nIn the below example a Saints player makes a block against a Dolphins player. The contact leads to a concussion for the blocking player."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"4d551726e0ed84f60b0bf94cb625ac58b11649fb"},"cell_type":"code","source":"HTML('<video width=\"560\" height=\"315\" controls> <source src=\"http://a.video.nfl.com//films/vodzilla/153272/Haack_42_yard_punt-iP6aZSRU-20181119_165050694_5000k.mp4\" type=\"video/mp4\"></video>')","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"8dbe1c11e77bf30643e500b1e120f2f425af1bb3"},"cell_type":"code","source":"# Distance vs speed\nLineFigPlay2 = figure(plot_width=800, plot_height=400, title=\"NO-MIA Distance Between Impacted Players (blue) vs. Speed of Injured Player (red)\")\n# Create distance lines\nLineFigPlay2Rtn = create_bokeh_lines(2792, '33838.0', 'TimeNum', 'Distance', LineFigPlay2, color='#008ac2')\nLineFigPlay2.yaxis.axis_label = \"Distance (yards)\"\nLineFigPlay2.xaxis.axis_label = \"Play Time (milliseconds)\"\nLineFigPlay2.background_fill_color = \"#efefef\"\nLineFigPlay2.background_fill_alpha = 0.5\n#LineFigPlay2.legend.location = \"top_right\"\n\nLineFigPlay2Rtn2 = create_bokeh_lines(2792, '33838.0', 'TimeNum', 'Speed', LineFigPlay2, color='#de425b', rightAxis='rightRange', rightAxisName='red')\n\nLineFigPlay2show = show(row(LineFigPlay2), notebook_handle=True)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"2b61109ca8a7d236d6c9d677f779c95e12fa078f"},"cell_type":"markdown","source":"The above chart shows that both distance between the injured and impacting player and the speed of the injured player are maximized at roughtly the same time. The moment of impact occurs around the 1.9 second mark and leads to a quick drop in the speed of the injured player. The contact from this is most signficant because the high speed causes the players to collide at an injury-inducing pace."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"656a91232e5391495ef4692adfba3f60f2f7f3dc"},"cell_type":"code","source":"# Speed vs acceleration\n# Create injured player and primary actor speed charts\nspeedLineFigPlay2 = figure(plot_width=800, plot_height=400, title=\"NO-MIA Speed (red) vs. Acceleration (green) of Injured Player\")\ninjSpeedlinesPlay2 = create_bokeh_lines(2792, '33838.0', 'TimeNum', 'Speed', speedLineFigPlay2, color='#de425b')\nspeedLineFigPlay2.yaxis.axis_label = \"Speed\"\nspeedLineFigPlay2.xaxis.axis_label = \"Play Time (milliseconds)\"\nspeedLineFigPlay2.background_fill_color = \"#efefef\"\nspeedLineFigPlay2.background_fill_alpha = 0.5\n\naccelSpeedlinesPlay2 = create_bokeh_lines(2792, '33838.0', 'TimeNum', 'Acceleration', speedLineFigPlay2, color='#00FA9A', rightAxis='rightRange', rightAxisName='green')\n\nPlay2show = show(row(speedLineFigPlay2), notebook_handle=True)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"89f0ce83cc583a8e16c449172f7b9adfd6d81fc1"},"cell_type":"markdown","source":"The above chart shows speed and acceleration compared for the injured player. Acceleration increases rapidly when the ball is kicked as the player initially backpedals to block. As the ball goes down field, the player accelerates again and maintains that speed until the point of contact near the sideline down field. At the moment of impact (190-200) the player quickly decelerates as he quickly comes to a stop after making contact with the player running downfield.\n\n### This example shows that the blocking player, not the unsuspecting player, sustains the injury. Blocks on players who are caught off guard often occur at a high speed  and endanger both the player being blocked and the blocking player."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"8ebc20d4e1589fe32ed7654ea220d99eec4286bf"},"cell_type":"code","source":"## Create X-Y Chart showing path of impacted players\n\nXYFigPlay2 = figure(plot_width=800, plot_height=400, x_range=(0,120), y_range=(0, 53.3), title=\"NO-MIA Path of Injured (red) and Impacting (blue) Players\")\nXYFigPlay2 = create_bokeh_play_x_y_lines(playID = 2792, actors = ['33838.0', '31317.0'], figure = XYFigPlay2)\nbokeh_handle4 = show(row(XYFigPlay2), notebook_handle=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"d50109d0ce9f9cc1e1484ac6cadd417b17e0e3b9","_kg_hide-input":true},"cell_type":"code","source":"HTML('<iframe width=\"700\" height=\"400\" src=\"https://streamable.com/s/vdd1h/djxdgd\" frameborder=\"0\" allowfullscreen></iframe>')","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"0e38790f6645a3b9a217b1f833eee578305951df"},"cell_type":"code","source":"HTML('<iframe width=\"700\" height=\"400\" src=\"https://streamable.com/s/rgdx5/jewmgc.mp4\" frameborder=\"0\" allowfullscreen></iframe>')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"c6676909043446b96b0dddedefa62475cd0003af"},"cell_type":"markdown","source":"Please note: the video is shown from the opposite side of the player tracking graphs above"},{"metadata":{"_uuid":"337d9d72bd24359620afe82aeca59132e0ec7589"},"cell_type":"markdown","source":"The two examples above illustrate particular situations where a low block and a low dive resulted in concussions. It is our belief that these concussions could have been avoided had the players not gone to the ground on the play."},{"metadata":{"_uuid":"20649b0d3e20e4932f6a54735aee927d1453a8f7"},"cell_type":"markdown","source":"One note of caution, because the data we evaluated only used the 2016 and 2017 seasons, it is possible that this sample size is not representative of a more comprehensive dataset. To fully test this theory, additional seasons would have to be evaluated."},{"metadata":{"_uuid":"803a416c1bbea80c1903ea1889b648738eea0aac"},"cell_type":"markdown","source":"## Interactive Section\nThe charts below allow the selection of other plays to navigate through time and view different results. The controls to change inputs are located immediately following the charts.\n\n### Please note: to run this workbook with fully interactivity, you must first fork the notebook and run all cells. The last cells has interactive elements for PlayID and Time during play \n\n"},{"metadata":{"_kg_hide-input":false,"_uuid":"13dc24b3f59ef32ea1c007d8d54560ec539526d6"},"cell_type":"markdown","source":"### Distance between injured player and primary actor"},{"metadata":{"trusted":true,"_uuid":"f3b6ddc76071ffd56e255c88bc16db74ba297a10","scrolled":false,"_kg_hide-input":true},"cell_type":"code","source":"bokeh_handle2 = show(row(distanceLineFig), notebook_handle=True)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d9a44e20f1359ceb5d5193bd506ae83ba1d1c8c5"},"cell_type":"markdown","source":"### Speed of injured player (red) vs. primary actor (blue)"},{"metadata":{"trusted":true,"_uuid":"4b975183767737c609e31c70109def76c520d00f","_kg_hide-input":true},"cell_type":"code","source":"bokeh_handle3 = show(row(speedLineFig), notebook_handle=True)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"2a6fae21ab3559a09cfece87d12269c49b184e7c"},"cell_type":"markdown","source":"### Play Motion Chart"},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"78aec8247adea9194057d5a226a577f6875710c2"},"cell_type":"code","source":"# Create chart for plays\n# calculate base data set\nlimitedSample = update_sample(playID = 3746)\nx = limitedSample['x']\ny = limitedSample['y']\n\n# get data for distance line\ndistanceLine = update_interactors(limitedSample)\nline_x = distanceLine['x']\nline_y = distanceLine['y']\n\nfig = figure(plot_width=800, plot_height=400, x_range=(0,120), y_range=(0, 53.3))\n\ncolor_map = ['#ff0000' if x in(limitedSample['GSISID_x'].values) else '#0000ff' if x in(limitedSample['Primary_Partner_GSISID'].values) else '#545454' for x in limitedSample.GSISID_y]\n\n# add a circle renderer with a size, color, and alpha\nlines = fig.line(line_x, line_y, line_color='black',line_width=2, alpha=0.6)\ncirclePlt = fig.circle(x, y, size=8, fill_color=color_map, alpha=0.8)\n\n# add boxes to either end represented endzones\nlow_box = BoxAnnotation(right=10, fill_alpha=0.2, fill_color='#555555')\nmid_box = BoxAnnotation(left=10, right=110, fill_alpha=0.2, fill_color='green')\nhigh_box = BoxAnnotation(left=110, fill_alpha=0.2, fill_color='#555555')\n\n# add lines to field\nfig.line([10,10,20,20,30,30,40,40,50,50,60,60,70,70,80,80,90,90,\n          100,100,110,110,120,120],\n         [55.5,0,0,55.5,55.5,0,0,55.5,55.5,0,0,55.5,55.5,0,0,55.5,\n         55.5,0,0,55.5,55.5,0,0,0], line_color='grey')\n\nfig.add_layout(low_box)\nfig.add_layout(mid_box)\nfig.add_layout(high_box)\n\nbokeh_handle = show(row(fig), notebook_handle=True)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"cf61bcbe6dab7f444f5990361c9d79f108ef1aa1"},"cell_type":"markdown","source":"### Change the time or play viewed above (must edit/fork the notebook first)"},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"97a52a7ccb800252ee9975ad3f2cc701e4d0ba7e"},"cell_type":"code","source":"interactive(update_plot, playID = playLvlDF[playLvlDF['InjCtrlFlag'] == 'Injury']['PlayID'].sort_values().values, timeNum = (0,350))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"b62cc7fc629e8b0d36d77b5f7a61e6bbfb85fec9"},"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}