{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Quantifying Punt Rush Havoc\n\nBlocking a punt can be a pivotal moment for any team, yet it is one of the most difficult things to accomplish. From 2018-2020, only about .07% of punts were blocked in the NFL. Developing a model to predict rare events like this can be quite difficult, so the objective should instead be to create a pressure metric - this will help evaluate punt block scheme and the individuals ability to block the punt.","metadata":{}},{"cell_type":"markdown","source":"## Region of Influence\n\nThe objective for the defender trying to block the punt is quite simple: \n - Run fast \n - Get as close to the punter as possible\n\nIf we want to measure how well a punt rusher did at achieving this goal, we can steal a page from [*Wide Open Spaces: A statistical technique for measuring\nspace creation in professional soccer*](http://www.lukebornn.com/papers/fernandez_ssac_2018.pdf), and [A Bottom Up Approach to Coverage Assignment\n](https://www.kaggle.com/shahsquatch/a-bottom-up-approach-to-coverage-assignment), as they've implemented a \"Player Influence\" metric to quantify occupied space, a weighted metric based on distance from the ball and velocity. Applying a similiar methodology, we can make some adjustments to instead give more weight to a players' influence based on distance from the punter and velocity.\n\n## Visualizing\n\nSuppose we have a 2D grid that represents the field, with the punt rusher in red approaching the punter in blue. The faster the rusher is moving and the closer he gets to the punter - the stronger his region of influence will be.\n\n![](https://github.com/jdruzzi/BDB22/blob/main/Punt%20Rush%20Havoc/Slater_inf_png.png?raw=true)\n\n\n","metadata":{}},{"cell_type":"markdown","source":"#### Lets view the image from above as a matrix of influence values in the form of a dataframe","metadata":{}},{"cell_type":"code","source":"%matplotlib nbagg\nfrom datetime import datetime\nimport pytz\n\n# HTML \nfrom IPython.display import HTML\n\n# Computation Libraries\nimport numpy as np\nimport pandas as pd\nimport scipy.stats as stats\nfrom scipy.spatial.distance import pdist, squareform\n\n# Plotting libraries\nimport seaborn as sns\nimport matplotlib as mpl\nimport matplotlib.pyplot as plt\nfrom matplotlib import animation, rc\nfrom matplotlib.patches import Rectangle, Arrow\n\n\nimport warnings \nwarnings.filterwarnings(\"ignore\")\npd.set_option('display.max_columns', None)\n\n\n_MAX_PLAYER_SPEED = 11.3\n_MAX_FIELD_Y = 53.3\n_MAX_FIELD_X = 120\n_MAX_FIELD_PLAYERS = 22\n\n\n@np.vectorize\ndef radius_influence(x):\n    assert x >= 0\n\n    if x <= 18:\n        return 4 + (6/(18**2))*(x**2)\n    else:\n        return 10\n\ndef generate_data_grid(N = 120):\n    X = np.linspace(0, _MAX_FIELD_X, N)\n    Y = np.linspace(0, _MAX_FIELD_Y, N)\n    X, Y = np.meshgrid(X, Y)\n    pos = np.empty(X.shape + (2,))\n    pos[:, :, 0] = X\n    pos[:, :, 1] = Y\n\n    return X, Y, pos\n\ndef sigmoid(x, k):\n    return 1 / (1 + np.exp(-k*x))\n\ndef weighted_angle(x1, x2, w):\n    def normalize(v):\n        norm=np.linalg.norm(v, ord=1)\n        if norm==0:\n            norm=np.finfo(v.dtype).eps\n        return v/norm\n\n    norm_weighted = w*normalize(x1) + (1-w)*normalize(x2)\n\n    return np.arctan2(norm_weighted[1], norm_weighted[0]) % (2*np.pi)\n\ndef multivariate_gaussian(pos, mu, Sigma):\n    n = mu.shape[0]\n    Sigma_det = np.linalg.det(Sigma)\n    Sigma_inv = np.linalg.inv(Sigma)\n    N = np.sqrt((2*np.pi)**n * Sigma_det)\n    fac = np.einsum('...k,kl,...l->...', pos-mu.T, Sigma_inv, pos-mu.T)\n    return np.exp(-fac / 2) / N\n\ndef generate_sigma(influence_rad, player_speed, distance_from_football):\n    R = np.array([[np.cos(influence_rad), -np.sin(influence_rad)],[np.sin(influence_rad), np.cos(influence_rad)]])[:,:,0]\n    speed_ratio = (player_speed**2)/(_MAX_PLAYER_SPEED**2)\n    S = np.array([[float(radius_influence(distance_from_football) + (radius_influence(distance_from_football)*speed_ratio)), 0], \n    [0, float(radius_influence(distance_from_football) - (radius_influence(distance_from_football)*speed_ratio))]])\n    return R@(S**2)@R.T\n\ndef generate_mu(player_position, player_vel):\n    return player_position + 0.5*player_vel\n\ndef set_axis_plots(ax, max_x, max_y) -> None:\n    ax.xaxis.set_visible(False)\n    ax.yaxis.set_visible(False)\n\n    ax.set_xlim([0, max_x])\n    ax.set_ylim([0, max_y])\n\ndef convert_orientation(x):\n    return (-x + 90)%360\n\ndef polar_to_z(r, theta):\n    return r * np.exp( 1j * theta)\n\ndef deg_to_rad(deg):\n    return deg*np.pi/180\n\n\ndef speed_weighting(s):\n    return (s**2)/(11.3**2)\n\n_X, _Y, _pos = generate_data_grid()\nZ_def = np.zeros((_pos.shape[0], _pos.shape[1]))\n\ndef convert_orientation(x):\n    return (-x + 90)%360\n\ndef polar_to_z(r, theta):\n    return r * np.exp( 1j * theta)\n\ndef deg_to_rad(deg):\n    return deg*np.pi/180\n\n\nNeal = pd.read_csv('https://github.com/jdruzzi/BDB22/blob/main/Punt%20Rush%20Havoc/Havoc_Slater.csv.gz?raw=true', compression='gzip', low_memory=False)\nAnd = Neal.query('gameId == 2020101812 & playId == 2757 & displayName == \"Matthew Slater\" & frameId == 33', engine='python')[['gameId','playId','frameId','event','punt_team','nflId','displayName','s','a','Dir_std','dx','dy','Orientation_std','X_std','Y_std','Cont_PunterDist']]\nplayer_speed = And.s.values\nspeed_w = player_speed/_MAX_PLAYER_SPEED\nplayer_vel = np.array([np.real(polar_to_z(player_speed, And.Dir_std)), np.imag(polar_to_z(player_speed, And.Dir_std))])\nplayer_orient = np.array([np.real(polar_to_z(2, And.Orientation_std)), np.imag(polar_to_z(2, And.Orientation_std))])\ninfluence_rad = weighted_angle(player_vel, player_orient, speed_w)\n#Distance from punter\ndistance_from_punter = And.Cont_PunterDist.values\nplayer_position = np.array([And.X_std, And.Y_std])\nsigma = generate_sigma(influence_rad, player_speed, distance_from_punter)\nmu = generate_mu(player_position, player_vel)\nZ = multivariate_gaussian(_pos, mu, sigma)\nZ_coarse = np.where(Z > 0.001, Z, np.nan)\n\nR = np.array([[np.cos(influence_rad), -np.sin(influence_rad)],[np.sin(influence_rad), np.cos(influence_rad)]])[:,:,0]\n\nspeed_ratio = (player_speed**2)/(_MAX_PLAYER_SPEED**2)\n\nS = np.array([[radius_influence(distance_from_punter) + (radius_influence(distance_from_punter)*speed_ratio), 0], \n[0, radius_influence(distance_from_punter) - (radius_influence(distance_from_punter)*speed_ratio)]])\n\n\nZ_sums = np.array([np.sum(x) for x in Z])\nSum_indices = np.array([i for i, x in enumerate(Z_sums) if x > .01])\nplayer = Z_sums[Sum_indices]\nmax_inf_point_df = pd.DataFrame(Z_coarse[Sum_indices]).iloc[:,19:47]\n\ndef create_colors(x):\n    df1 = x.copy()\n    df1.loc[:,:] = 'background-color: '\n    df1.loc[:, 33] = 'background-color: aqua'\n    df1.loc[11, 33] = 'background-color: red'\n    return df1      \n\nmax_inf_point_df.style.apply(create_colors, axis=None)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-12-20T00:28:05.732759Z","iopub.execute_input":"2021-12-20T00:28:05.733155Z","iopub.status.idle":"2021-12-20T00:28:06.717993Z","shell.execute_reply.started":"2021-12-20T00:28:05.733089Z","shell.execute_reply":"2021-12-20T00:28:06.717126Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"If you scroll over to column 33 in this dataframe, the red highlighted value is the **point of maximum influence** for the punt rusher. The aqua colored column can be thought of as the **array of maximum influence**, which is essentially the sum of the column where the POMI is located. I've found that both the point of maximum influence, and the summation of the maximum influence array are the most correlated values with blocking a punt.\n\n### Adjusted Point of Maximum Influence\n\nSince we are dealing with extremely low values, we can transform the data to show the discrepancies among rushers.\n\n**Adj. POMI** = ((POMI)x1000) x ((POMI)x1000)\n\n\n### Adjusted Sum of Maximum Influence Array\n\nWe can do something similiar with the summation array:\n\n**Adj. SOMA** = (((SOMA)x1000) x ((SOMA)x1000)) / 100\n\n\n### Absolute Y-Difference from the punter \n\nWhen we blend the 2 influence metrics together, the correlation with blocking a punt did improve, as opposed to leaving them separate. However, I noticed it was giving a high influence to vises coming in off the edge - they were moving at high speeds and were gaining distance on the punter - yet the angle/ difference in Y-coordinates would've made it difficult to block the punt. In order to combat this issue, I've added a penalty term based on the absolute Y-difference in coordinates from the punter.\n\n**Adj. Y-Diff** = (Adj. Y-Diff) x 10\n\n## The Punt Rusher Havoc Equation\n\nAfter testing different weights and comparing correlation to blocking a punt, I've determined the equation as follows:\n\n**HAVOC** = ((Adj. POMI) x .50) + ((Adj. SOMA) x .24) - ((Adj. Y-Diff) x .26)","metadata":{}},{"cell_type":"markdown","source":"### Punt Block Correlation\n\nBelow you will see the values mentioned above, and the correlation with blocking a punt. For some of you, these correlation values may seem arbitrarily low - however you have to remember that we are dealing with exremely rare binary events here. I think everyone can agree that \"Distance from the punter\" would be a significant factor when blocking a punt, and it's a good baseline estimate of what a good metric should look like - that being said, HAVOC has nearly double the relationship strength with blocking a punt.","metadata":{}},{"cell_type":"markdown","source":"![](https://github.com/jdruzzi/BDB22/blob/main/Punt%20Rush%20Havoc/HAVOC_corr_png.png?raw=true)\n\n*Values are determined based on time of the punt, or 22 frames after the snap - whichever event occurs first.*","metadata":{}},{"cell_type":"markdown","source":"# HAVOC In Action\n\nLet's view an example of Matthew Slater rushing in to try and block a punt. Notice how his HAVOC value changes over time, reaching a peak value of 166.09.","metadata":{}},{"cell_type":"code","source":"import numpy as np \nimport pandas as pd \n\n%matplotlib nbagg\nimport matplotlib.pyplot as plt\nimport matplotlib.animation as animation\nimport pandas as pd\nfrom IPython.display import HTML\n\nfrom matplotlib.patches import Polygon\n\nimport pytz\nfrom IPython.display import HTML\nfrom matplotlib import animation, rc\nfrom matplotlib.patches import Rectangle, Arrow, FancyArrow\nfrom matplotlib.patches import Polygon\nimport matplotlib.patheffects as pe\nimport gc\n\n\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\nimport matplotlib._color_data as mcd\nimport matplotlib.patches as mpatch\nimport random\nfrom scipy.spatial import ConvexHull\n\nfrom tqdm import tqdm\nfrom datetime import date\nfrom datetime import datetime\nimport io\nimport time\nimport io\nimport re\n\nfrom shapely.geometry import Point, Polygon, GeometryCollection,MultiPoint\nfrom shapely.validation import make_valid\n\nimport matplotlib as mpl\nmpl.rcParams.update(mpl.rcParamsDefault)\n\npd.options.mode.chained_assignment = None \npd.set_option('display.max_columns', None)\nfrom scipy.spatial import ConvexHull\nimport math\n\nimport warnings \nwarnings.filterwarnings(\"ignore\")\n\nimport numpy as np \nimport pandas as pd\npd.options.mode.chained_assignment = None \npd.set_option('display.max_columns', None)\n\nimport warnings \nwarnings.filterwarnings(\"ignore\")\n\nuse = pd.read_csv('https://github.com/jdruzzi/BDB22/blob/main/Punt%20Rush%20Havoc/Havoc_Slater.csv.gz?raw=true', compression='gzip', low_memory=False)\n\nylim= (-18, 5.5)\nxlim=(-12, 12)\n# fig = plt.figure(figsize=(20,10))\n#ylim=(40,85)\n#xlim=(0,50)\nfig = plt.figure(figsize=(16,10))\nax = plt.axes(xlim=xlim, ylim=ylim)\n\n\n\n# plt.ylim([-3, 22])\n# plt.xlim([-23.3, 23.3])\npoints0, = ax.plot([], [],'.',alpha = .85, markersize =65,color='red')\npoints1, = ax.plot([], [],'.',alpha = .80, markersize =65,color='#2F4F4F')\npoints2, = ax.plot([], [],'.',alpha = .80, markersize =65,color='#C0C0C0')\npoints3, = ax.plot([], [],'d',alpha = 1, markersize =25,color='brown')\nframe_text = ax.text(16, -5, '', horizontalalignment = 'center', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize='larger')\n\n\na_or_list = []\nname_list = []\nscat_number_list = []\nblock = []\nBlock_Prob_list = []\n\nfor _ in range(len(use['displayName'].drop_duplicates())):\n    a_or_list.append(ax.add_patch(Arrow(0, 0, 0, 0, color = 'k')))\n    block.append(ax.add_patch(Arrow(0, 0, 0, 0, color = 'green')))\n    name_list.append(ax.text(0, 0, '', horizontalalignment = 'center', verticalalignment = 'center', c = 'black',fontweight='bold',fontsize=13,path_effects=[pe.withStroke(linewidth=3, foreground=\"gold\")]))\n    scat_number_list.append(ax.text(0, 0, '', horizontalalignment = 'center', verticalalignment = 'center', c = 'black',fontweight='bold',fontsize=15,path_effects=[pe.withStroke(linewidth=3, foreground=\"white\")]))\n    Block_Prob_list.append(ax.text(0, 0, '', horizontalalignment = 'center', verticalalignment = 'top', c = 'black',fontweight='bold',fontsize=15,path_effects=[pe.withStroke(linewidth=3, foreground=\"gold\")]))\n\n#use = puntview.query(' gameId == 2018123013 & playId == 502 & X_diff < 25 ').reset_index(drop=True)\nplt.axhline(y=0, color='black', linestyle='-',linewidth=6,alpha=.5)\n\n\n\nto_be_deleted = []\n\nplt.axis('off')\n\n\ndef animate(i):\n    time = use['frameId'].unique()[i]\n\n    trim = use.loc[use['frameId'] == time].drop_duplicates()\n\n    rusher_x = trim.loc[(trim['frameId'] == time) & (trim['displayName'] == \"Matthew Slater\")]['LOS_X_diff']\n    rusher_y = trim.loc[(trim['frameId'] == time) & (trim['displayName'] == \"Matthew Slater\")]['LOS_Y_diff']\n\n    home_x = trim.loc[(trim['frameId'] == time) & (trim['punt_team'] == \"Returning_Team\") & (trim['displayName'] != \"Matthew Slater\")]['LOS_X_diff']\n    home_y = trim.loc[(trim['frameId'] == time) & (trim['punt_team'] == \"Returning_Team\") & (trim['displayName'] != \"Matthew Slater\")]['LOS_Y_diff']\n\n    away_x = trim.loc[(use['frameId'] == time) & (trim['punt_team'] == \"Punting_Team\")]['LOS_X_diff']\n    away_y = trim.loc[(use['frameId'] == time) & (trim['punt_team'] == \"Punting_Team\")]['LOS_Y_diff']\n    \n    ball_x = trim.loc[(trim['frameId'] == time) & (trim['displayName'] == \"football\")]['LOS_X_diff']\n    ball_y = trim.loc[(trim['frameId'] == time) & (trim['displayName'] == \"football\")]['LOS_Y_diff']\n    \n    rusher_coordinate = pd.DataFrame({'x':rusher_x,'y':rusher_y})\n\n    home_player_coordinate = pd.DataFrame({'x':home_x,'y':home_y})\n    \n    away_player_coordinate = pd.DataFrame({'x':away_x,'y':away_y})\n\n    frame_text.set_text('frame ' + str(trim.frameId.iloc[0]) + \" \" + str(trim.event.iloc[0]))\n    \n    points0.set_data((rusher_coordinate['y']),(rusher_coordinate['x']))\n    points1.set_data((home_player_coordinate['y']),(home_player_coordinate['x']))\n    points2.set_data((away_player_coordinate['y']),(away_player_coordinate['x']))\n    points3.set_data((ball_y),(ball_x))\n\n\n    for (index, row) in trim[trim.displayName.notnull()].reset_index().iterrows():\n      if (row.displayName != \"football\") & (row.punt_team == \"Returning_Team\"):\n        scat_number_list[index].set_text(\"\")\n        scat_number_list[index].set_position((row.LOS_Y_diff, row.LOS_X_diff))\n        scat_number_list[index].set_text(row['jerseyNumber'])\n      else:\n        scat_number_list[index].set_text(\"\")\n        pass\n\n\n    for (index, row) in trim[trim.displayName.notnull()].reset_index().iterrows():\n      if row.punt_team == \"Returning_Team\" and row.frameId <= (row.snap_frame + 10) and row.IsRusher == 0:\n        a_or_list[index].remove()\n        a_or_list[index] = ax.add_patch(Arrow(row.LOS_Y_diff, row.LOS_X_diff, (row.LOS_Y_diff_path_diff*-1), (row.LOS_X_diff_path_diff*-1)/2, color = 'black', width = .5))\n\n      elif row.IsRusher == 1 and row.frameId <= (row.snap_frame + 10):\n        a_or_list[index].remove()\n        a_or_list[index] = ax.add_patch(Arrow(row.LOS_Y_diff, row.LOS_X_diff, (row.LOS_Y_diff_path_diff*-1), (row.LOS_X_diff_path_diff*-1)/2, color = 'red', width = .5))\n      else:\n        a_or_list[index].remove()\n        a_or_list[index] = ax.add_patch(ax.add_patch(Arrow(0, 0, 0, 0, color = 'white', width = .001)))\n        pass\n\n    for (index, row) in trim[trim.displayName.notnull()].reset_index().iterrows():\n      if row.punt_team == \"Returning_Team\" and row.frameId <= (row.snap_frame + 10) and row.displayName == \"Matthew Slater\":\n        name_list[index].set_text(row.displayName.split()[-1])\n        name_list[index].set_position((row.LOS_Y_diff, row.LOS_X_diff+1))\n\n      else:\n        name_list[index].set_text(\"\")\n        name_list[index].set_position((row.LOS_Y_diff, row.LOS_X_diff+1))\n        pass\n\n      for (index, row) in trim[trim.displayName.notnull()].reset_index().iterrows():\n        if row.displayName == \"Matthew Slater\":\n          Block_Prob_list[index].set_text(str(round(float(row.influence_blend_cont),2)))\n      #    Block_Prob_list[index].set_text((row['max']*1000)*(row['max']*1000))\n          Block_Prob_list[index].set_position((row.LOS_Y_diff, row.LOS_X_diff+2))\n          ax.plot(row.LOS_Y_diff, row.LOS_X_diff, \"ro-\", markersize=round(float(row.influence_blend_cont),2) /2 )\n\n        else:\n          Block_Prob_list[index].set_text(\"\")\n          Block_Prob_list[index].set_position((row.LOS_Y_diff, row.LOS_X_diff+2))\n          pass\n      \n    return points1,points2,points3,points0\n\n\nanim = animation.FuncAnimation(fig, animate,\n                               frames=len(use['frameId'].unique()))\n\nHTML(anim.to_jshtml())","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-12-20T02:12:05.221073Z","iopub.execute_input":"2021-12-20T02:12:05.221366Z","iopub.status.idle":"2021-12-20T02:12:17.746488Z","shell.execute_reply.started":"2021-12-20T02:12:05.221336Z","shell.execute_reply":"2021-12-20T02:12:17.745214Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now let's compare the HAVOC generated by his other fellow punt rushers","metadata":{}},{"cell_type":"code","source":"import numpy as np \nimport pandas as pd \n\n%matplotlib nbagg\nimport matplotlib.pyplot as plt\nimport matplotlib.animation as animation\nimport pandas as pd\nfrom IPython.display import HTML\n\nfrom matplotlib.patches import Polygon\n\nimport pytz\nfrom IPython.display import HTML\nfrom matplotlib import animation, rc\nfrom matplotlib.patches import Rectangle, Arrow, FancyArrow\nfrom matplotlib.patches import Polygon\nimport matplotlib.patheffects as pe\nimport gc\n\n\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\nimport matplotlib._color_data as mcd\nimport matplotlib.patches as mpatch\nimport random\nfrom scipy.spatial import ConvexHull\n\nfrom tqdm import tqdm\nfrom datetime import date\nfrom datetime import datetime\nimport io\nimport time\nimport io\nimport re\n\nfrom shapely.geometry import Point, Polygon, GeometryCollection,MultiPoint\nfrom shapely.validation import make_valid\n\nimport matplotlib as mpl\nmpl.rcParams.update(mpl.rcParamsDefault)\n\npd.options.mode.chained_assignment = None \npd.set_option('display.max_columns', None)\nfrom scipy.spatial import ConvexHull\nimport math\n\nimport warnings \nwarnings.filterwarnings(\"ignore\")\n\nimport numpy as np \nimport pandas as pd\npd.options.mode.chained_assignment = None \npd.set_option('display.max_columns', None)\n\nimport warnings \nwarnings.filterwarnings(\"ignore\")\n\nuse = pd.read_csv('https://github.com/jdruzzi/BDB22/blob/main/Punt%20Rush%20Havoc/Havoc_Slater.csv.gz?raw=true', compression='gzip', low_memory=False)\n\nylim= (-18, 5.5)\nxlim=(-12, 12)\n# fig = plt.figure(figsize=(20,10))\n#ylim=(40,85)\n#xlim=(0,50)\nfig = plt.figure(figsize=(16,10))\nax = plt.axes(xlim=xlim, ylim=ylim)\n\n\n\n# plt.ylim([-3, 22])\n# plt.xlim([-23.3, 23.3])\npoints0, = ax.plot([], [],'.',alpha = .85, markersize =65,color='red')\npoints1, = ax.plot([], [],'.',alpha = .80, markersize =65,color='#2F4F4F')\npoints2, = ax.plot([], [],'.',alpha = .80, markersize =65,color='#C0C0C0')\npoints3, = ax.plot([], [],'d',alpha = 1, markersize =25,color='brown')\nframe_text = ax.text(16, -5, '', horizontalalignment = 'center', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize='larger')\n\n\na_or_list = []\nname_list = []\nscat_number_list = []\nblock = []\nBlock_Prob_list = []\n\nfor _ in range(len(use['displayName'].drop_duplicates())):\n    a_or_list.append(ax.add_patch(Arrow(0, 0, 0, 0, color = 'k')))\n    block.append(ax.add_patch(Arrow(0, 0, 0, 0, color = 'green')))\n    name_list.append(ax.text(0, 0, '', horizontalalignment = 'center', verticalalignment = 'center', c = 'black',fontweight='bold',fontsize=13,path_effects=[pe.withStroke(linewidth=3, foreground=\"gold\")]))\n    scat_number_list.append(ax.text(0, 0, '', horizontalalignment = 'center', verticalalignment = 'center', c = 'black',fontweight='bold',fontsize=15,path_effects=[pe.withStroke(linewidth=3, foreground=\"white\")]))\n    Block_Prob_list.append(ax.text(0, 0, '', horizontalalignment = 'center', verticalalignment = 'top', c = 'black',fontweight='bold',fontsize=15,path_effects=[pe.withStroke(linewidth=3, foreground=\"gold\")]))\n\n#use = puntview.query(' gameId == 2018123013 & playId == 502 & X_diff < 25 ').reset_index(drop=True)\nplt.axhline(y=0, color='black', linestyle='-',linewidth=6,alpha=.5)\n\n\n\nto_be_deleted = []\n\nplt.axis('off')\n\n\ndef animate(i):\n    time = use['frameId'].unique()[i]\n\n    trim = use.loc[use['frameId'] == time].drop_duplicates()\n\n    rusher_x = trim.loc[(trim['frameId'] == time) & (trim['displayName'] == \"Matthew Slater\")]['LOS_X_diff']\n    rusher_y = trim.loc[(trim['frameId'] == time) & (trim['displayName'] == \"Matthew Slater\")]['LOS_Y_diff']\n\n    home_x = trim.loc[(trim['frameId'] == time) & (trim['punt_team'] == \"Returning_Team\") & (trim['displayName'] != \"Matthew Slater\")]['LOS_X_diff']\n    home_y = trim.loc[(trim['frameId'] == time) & (trim['punt_team'] == \"Returning_Team\") & (trim['displayName'] != \"Matthew Slater\")]['LOS_Y_diff']\n\n    away_x = trim.loc[(use['frameId'] == time) & (trim['punt_team'] == \"Punting_Team\")]['LOS_X_diff']\n    away_y = trim.loc[(use['frameId'] == time) & (trim['punt_team'] == \"Punting_Team\")]['LOS_Y_diff']\n    \n    ball_x = trim.loc[(trim['frameId'] == time) & (trim['displayName'] == \"football\")]['LOS_X_diff']\n    ball_y = trim.loc[(trim['frameId'] == time) & (trim['displayName'] == \"football\")]['LOS_Y_diff']\n    \n    rusher_coordinate = pd.DataFrame({'x':rusher_x,'y':rusher_y})\n\n    home_player_coordinate = pd.DataFrame({'x':home_x,'y':home_y})\n    \n    away_player_coordinate = pd.DataFrame({'x':away_x,'y':away_y})\n\n    frame_text.set_text('frame ' + str(trim.frameId.iloc[0]) + \" \" + str(trim.event.iloc[0]))\n    \n    points0.set_data((rusher_coordinate['y']),(rusher_coordinate['x']))\n    points1.set_data((home_player_coordinate['y']),(home_player_coordinate['x']))\n    points2.set_data((away_player_coordinate['y']),(away_player_coordinate['x']))\n    points3.set_data((ball_y),(ball_x))\n\n\n    for (index, row) in trim[trim.displayName.notnull()].reset_index().iterrows():\n      if (row.displayName != \"football\") & (row.punt_team == \"Returning_Team\"):\n        scat_number_list[index].set_text(\"\")\n        scat_number_list[index].set_position((row.LOS_Y_diff, row.LOS_X_diff))\n        scat_number_list[index].set_text(row['jerseyNumber'])\n      else:\n        scat_number_list[index].set_text(\"\")\n        pass\n\n\n    for (index, row) in trim[trim.displayName.notnull()].reset_index().iterrows():\n      if row.punt_team == \"Returning_Team\" and row.frameId <= (row.snap_frame + 10) and row.IsRusher == 0:\n        a_or_list[index].remove()\n        a_or_list[index] = ax.add_patch(Arrow(row.LOS_Y_diff, row.LOS_X_diff, (row.LOS_Y_diff_path_diff*-1), (row.LOS_X_diff_path_diff*-1)/2, color = 'black', width = .5))\n\n      elif row.IsRusher == 1 and row.frameId <= (row.snap_frame + 10):\n        a_or_list[index].remove()\n        a_or_list[index] = ax.add_patch(Arrow(row.LOS_Y_diff, row.LOS_X_diff, (row.LOS_Y_diff_path_diff*-1), (row.LOS_X_diff_path_diff*-1)/2, color = 'red', width = .5))\n      else:\n        a_or_list[index].remove()\n        a_or_list[index] = ax.add_patch(ax.add_patch(Arrow(0, 0, 0, 0, color = 'white', width = .001)))\n        pass\n\n    for (index, row) in trim[trim.displayName.notnull()].reset_index().iterrows():\n      if (row.IsRusher == 1) and row.frameId <= (row.snap_frame + 10) :\n        name_list[index].set_text(row.displayName.split()[-1])\n        name_list[index].set_position((row.LOS_Y_diff, row.LOS_X_diff+1))\n\n      else:\n        name_list[index].set_text(\"\")\n        name_list[index].set_position((row.LOS_Y_diff, row.LOS_X_diff+1))\n        pass\n\n      for (index, row) in trim[trim.displayName.notnull()].reset_index().iterrows():\n        if (row.IsRusher == 1):\n          Block_Prob_list[index].set_text(str(round(float(row.influence_blend_cont),2)))\n      #    Block_Prob_list[index].set_text((row['max']*1000)*(row['max']*1000))\n          Block_Prob_list[index].set_position((row.LOS_Y_diff, row.LOS_X_diff+2))\n          ax.plot(row.LOS_Y_diff, row.LOS_X_diff, \"ro-\", markersize=round(float(row.influence_blend_cont),2) /2 )\n\n        else:\n          Block_Prob_list[index].set_text(\"\")\n          Block_Prob_list[index].set_position((row.LOS_Y_diff, row.LOS_X_diff+2))\n          pass\n      \n    return points1,points2,points3,points0\n\n\nanim = animation.FuncAnimation(fig, animate,\n                               frames=len(use['frameId'].unique()))\n\nHTML(anim.to_jshtml())","metadata":{"execution":{"iopub.status.busy":"2021-12-20T02:16:58.923124Z","iopub.execute_input":"2021-12-20T02:16:58.923864Z","iopub.status.idle":"2021-12-20T02:17:20.687009Z","shell.execute_reply.started":"2021-12-20T02:16:58.923822Z","shell.execute_reply":"2021-12-20T02:17:20.686129Z"},"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Notice how the other rushers don't generate nearly as much HAVOC as Slater.","metadata":{}},{"cell_type":"markdown","source":"-------------------------------------------------------------\n## More 2022 Big Data Bowl Content\n\n### [ ⭐ HAVOC: Decoding the Punt Rush ⭐ ](https://www.kaggle.com/jdruzzi/havoc-decoding-the-punt-rush)\n\n- [Quantifying Punt Rush Ability with HAVOC](https://www.kaggle.com/jdruzzi/quantifying-punt-rush-ability-with-havoc)\n\n- [Extended: How to Improve HAVOC & Block Punts 📝](https://www.kaggle.com/jdruzzi/extended-how-to-improve-havoc-block-punts)\n\n\n#### Alternative Punt / Punt Rush\n\n- [Evaluating Punt/Punt Rush Units with Convex Hulls](https://www.kaggle.com/jdruzzi/evaluate-punt-punt-return-units-with-convex-hulls)\n\n#### Punt Protection\n\n- [Estimating Punt Protection Assignments](https://www.kaggle.com/jdruzzi/estimating-punt-protection-blocking-assignments)\n\n#### Misc / Additional Data\n- [Generating Detailed Punt Positions](https://www.kaggle.com/jdruzzi/generating-detailed-punt-positions)\n\n- [Combine, Snap Counts, & Left Footed Kicker Data](https://www.kaggle.com/jdruzzi/combine-snap-counts-left-footed-kicker-data)\n\n------------------------------------------------------------\n#### Socials\n- [Twitter](https://twitter.com/j_druzzi)\n- [LinkedIn](https://www.linkedin.com/in/joe-andruzzi-27b3a7149/)\n\n------------------------------------------------------------","metadata":{}}]}