{"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":"# Estimating Punt Protection Blocking Assignments","metadata":{}},{"cell_type":"markdown","source":"![](https://github.com/jdruzzi/BDB22/blob/main/Expected%20Blocking%20Assignments/Punt_block.jpg?raw=true)","metadata":{}},{"cell_type":"markdown","source":"## Preface","metadata":{}},{"cell_type":"markdown","source":"Without knowing the actual blocking assignment for players in punt protection beforehand, it can be a difficult task to accurately determine block responsibility. In this notebook I will propose a solution to estimate blocking assignments for all players involved in the punt protection, for every single play.","metadata":{}},{"cell_type":"markdown","source":"## Weighted Calculation\n\nIn order to achieve this, we need a function to represent true blocking responsibility. \n\nOne may think that identifying the closest defender throughout the play would satisfy this task - however I don't believe this is the right approach. In many cases, the blocker could be engaged in a block while the \"closest defender\" is actually behind them or next to them. To solve this problem, we will need to take into account the blockers orientation, and blend it with our closest defender distance metric.\n\n### We need these 3 things:\n\n### - Euclidean Distance\n - Calculate the euclidean distance of the five closest defenders for each player in punt protection, every 10th of a second.\n \n### - Orientation Difference\n - Calculate the blockers' orientation difference with the five closest defender locations. This is to determine which defender they are likely looking at, every 10th of a second.\n \n### - Time\n - Lastly we will use time, more specifically the frameId number used throughout the play. We want to give more weight to the later stages of the play, as this gives a clearer picture of blocking assignments, so we will use the frameId number divided by 3 (frameId/3) \n\n### Putting it together\n\nSo for each punt pro blocker, and for each one of their 5 closest defenders, on every frame, the calculation will be:\n\n##### Expected Block Value (EBV) = (((Defender Distance) / (Total 5 Defender Distances))*((Defender Orientation Difference) / (Total 5 Defender Orientation Differences)))\n\nOnce this calculation is performed, each punt pro player will have 5 expected block values representing each one of the 5 closest defenders, and then we will need to choose the minimum of the 5. The expectation is that true blocking assignment will be close to the blocker, and also the blocker will have his torso facing the defender - creating a low orientation difference.\n\n##### The Blocking Assignment = MIN([EBV1, EBV2, EBV3, EBV4, EBV5])\n\nSo once we find the expected assignment, we simply do a cumulative count of the expected assignment as they pop up throughout the play - except we weigh it with our time weight of frameId/3.\n\n\nLastly, after the first 1.5 seconds of the play, we will switch from the closest 5 defenders to the closest 3. The assumption is by that time, closest defenders 4 and 5 are likely to be irrelvant. \n ","metadata":{}},{"cell_type":"markdown","source":"# Let's see it in action!","metadata":{}},{"cell_type":"markdown","source":"Instead of showing an example of a Wing who is generally the easiest to predict in blocking assignments, I will show two examples estimating punt protector assignments. They are the most difficult because they are lined up off the ball, and could potentially move across the formation to pick up a block.","metadata":{}},{"cell_type":"markdown","source":"## Clear Blocking Assignment Example","metadata":{}},{"cell_type":"code","source":"import numpy as np \nimport pandas as pd \n\nuse = pd.read_csv(r'https://github.com/jdruzzi/BDB22/blob/main/Expected%20Blocking%20Assignments/W_Blocking_good.csv.gz?raw=true', compression='gzip', low_memory=False)\nuse['Block_Oppt_pctMax_fwd'] = use.groupby(['gameId','playId','nflId'])['Block_Oppt_x_Pos3_pctMax'].shift(-1)\nuse['Block_Oppt_x_Pos3_fwd'] = use.groupby(['gameId','playId','nflId'])['Block_Oppt_x_Pos3'].shift(-1)\n\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\nfrom shapely.geometry import Point, Polygon, GeometryCollection,MultiPoint\nfrom shapely.validation import make_valid\nimport matplotlib.patches as patches\n\nimport warnings \nwarnings.filterwarnings(\"ignore\")\n\nylim= (-10, 2.5)\nxlim=(-10, 10)\n\nfig = plt.figure(figsize=(16,8))\nax = plt.axes(xlim=xlim, ylim=ylim)\n\n\npoints0, = ax.plot([], [],'.',alpha = 1, markersize =75,color='red')\npoints1, = ax.plot([], [],'.',alpha = 1, markersize =75,color='#2F4F4F')\npoints2, = ax.plot([], [],'.',alpha = 1, markersize =75,color='#C0C0C0')\npoints3, = ax.plot([], [],'d',alpha = 1, markersize =20,color='brown')\n\nrect = patches.Rectangle((4.5, -10),\n        (3.5),\n        (5),\n        linewidth=3,\n        edgecolor= 'yellow',\n        facecolor = 'black',\n        alpha= 1,\n        fill = True,\n        label= 'Hull Metrics')\n#   ax.legend()\nax.add_patch(rect)\n\nframe_text = ax.text(5, -5.5, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize=20)\nW_Block = ax.text(4.57, -6.3, 'Weighted Blocking Credit', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize=10.45)\nCredit1 = ax.text(4.75, -7, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize=15,path_effects=[pe.withStroke(linewidth=3, foreground=\"black\")])\nCredit2 = ax.text(4.75, -7.5, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize=15,path_effects=[pe.withStroke(linewidth=3, foreground=\"black\")])\nCredit3 = ax.text(4.75, -8, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize=15,path_effects=[pe.withStroke(linewidth=3, foreground=\"black\")])\nCredit4 = ax.text(4.75, -8.5, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize=15,path_effects=[pe.withStroke(linewidth=3, foreground=\"black\")])\nCredit5 = ax.text(4.75, -9, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize=15,path_effects=[pe.withStroke(linewidth=3, foreground=\"black\")])\n\n\n\na_or_list = []\nname_list = []\nname_list2 = []\nscat_number_list = []\nblock = []\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='medium',path_effects=[pe.withStroke(linewidth=3, foreground=\"gold\")]))\n    name_list2.append(ax.text(0, 0, '', horizontalalignment = 'center', verticalalignment = 'center', c = 'black',fontweight='bold',fontsize='medium',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\nplt.axhline(y=0, color='black', linestyle='-',linewidth=6,alpha=.5)\n\n\nplt.axis('off')\n\n\nto_be_deleted = []\n\nBlocking = []\n\ndef animate(i):\n    b_df = pd.DataFrame(Blocking, columns=['Defender','Credit']).drop_duplicates(subset=['Defender'],keep='last').sort_values(by=['Credit'], ascending=False)\n\n    time = use['frameId'].unique()[i]\n\n    trim = use.loc[use['frameId'] == time].drop_duplicates()\n\n    PPhome_x = trim.loc[(trim['frameId'] == time)& (trim['x_Pos'] == \"PP\")]['LOS_Y_diff']\n    PPhome_y = trim.loc[(trim['frameId'] == time)& (trim['x_Pos'] == \"PP\")]['LOS_X_diff']\n\n    home_x = trim.loc[(trim['frameId'] == time)& (trim['punt_team'] == \"Returning_Team\")& (trim['x_Pos'] != \"PP\")]['LOS_Y_diff']\n    home_y = trim.loc[(trim['frameId'] == time)& (trim['punt_team'] == \"Returning_Team\")& (trim['x_Pos'] != \"PP\")]['LOS_X_diff']\n\n    away_x = trim.loc[(use['frameId'] == time) & (trim['punt_team'] == \"Punting_Team\")& (trim['x_Pos'] != \"PP\")]['LOS_Y_diff']\n    away_y = trim.loc[(use['frameId'] == time) & (trim['punt_team'] == \"Punting_Team\")& (trim['x_Pos'] != \"PP\")]['LOS_X_diff']\n    \n    PPhome_player_coordinate = pd.DataFrame({'x':PPhome_x,'y':PPhome_y})\n    home_player_coordinate = pd.DataFrame({'x':home_x,'y':home_y})\n    away_player_coordinate = pd.DataFrame({'x':away_x,'y':away_y})\n\n    frame_text.set_text('frame ' + str(trim.frameId.iloc[0]))\n\n    points0.set_data((PPhome_player_coordinate['x']),(PPhome_player_coordinate['y']))\n    points1.set_data((home_player_coordinate['x']),(home_player_coordinate['y']))\n    points2.set_data((away_player_coordinate['x']),(away_player_coordinate['y']))\n\n    try:\n        Credit1.set_text('#1  ' + str(b_df.Defender.iloc[0]) + \"   \" +str(\"{0:.00%}\".format(b_df.Credit.iloc[0])) )\n    except:\n        pass\n    \n    try:\n        Credit2.set_text('#2  ' + str(b_df.Defender.iloc[1]) + \"   \" +str(\"{0:.00%}\".format(b_df.Credit.iloc[1])) )\n    except:\n        pass\n\n    try:\n        Credit3.set_text('#3  ' + str(b_df.Defender.iloc[2]) + \"   \" +str(\"{0:.00%}\".format(b_df.Credit.iloc[2])) )\n    except:\n        pass\n\n    try:\n        Credit4.set_text('#4  ' + str(b_df.Defender.iloc[3]) + \"   \" +str(\"{0:.00%}\".format(b_df.Credit.iloc[3])) )\n    except:\n        pass\n\n    try:\n        Credit3.set_text('#5  ' + str(b_df.Defender.iloc[4]) + \"   \" +str(\"{0:.00%}\".format(b_df.Credit.iloc[4])) )\n    except:\n        pass\n\n    for (index, row) in trim[trim.displayName.notnull()].reset_index().iterrows():\n        if (row.punt_team == \"Punting_Team\") & (row.x_Pos == \"PP\"):\n            Blocking.append([row.Block_Oppt_x_Pos3_fwd, np.round(row.Block_Oppt_pctMax_fwd, 3)])\n\n\n    for (index, row) in trim[trim.displayName.notnull()].reset_index().iterrows():\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(int(row['jerseyNumber']))\n    # else:\n    #     scat_number_list[index].set_text(\"\")\n    #     pass\n    \n    for (index, row) in trim[trim.displayName.notnull()].reset_index().iterrows():\n        if row.punt_team == \"Returning_Team\":\n            name_list[index].set_text(row.x_Pos3)\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\n    for (index, row) in trim[trim.displayName.notnull()].reset_index().iterrows():\n        if (row.punt_team == \"Punting_Team\") & (row.x_Pos == \"PP\"):\n            name_list2[index].set_text(row.Block_Oppt_x_Pos3)\n            name_list2[index].set_position((row.LOS_Y_diff, row.LOS_X_diff+1))\n\n        else:\n            name_list2[index].set_text(\"\")\n            name_list2[index].set_position((row.LOS_Y_diff, row.LOS_X_diff+1))\n\n        if (row.punt_team == \"Punting_Team\") & (row.x_Pos == \"PP\"):\n            block[index].remove()\n            block[index] = ax.add_patch(Arrow(row.LOS_Y_diff, row.LOS_X_diff, (row.LOS_Y_diff_pred_diff*-1), (row.LOS_X_diff_pred_diff*-1), color = 'green', width = .2))\n\n        else:\n            block[index].remove()\n            block[index] = ax.add_patch(ax.add_patch(Arrow(0, 0, 0, 0, color = 'white', width = .001)))\n\n\n    return points1,points2\n\n\nanim = animation.FuncAnimation(fig, animate,\n                              frames=len(use['frameId'].unique()))\n\n\n\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-16T01:51:11.411786Z","iopub.execute_input":"2021-12-16T01:51:11.412105Z","iopub.status.idle":"2021-12-16T01:51:16.444182Z","shell.execute_reply.started":"2021-12-16T01:51:11.412069Z","shell.execute_reply":"2021-12-16T01:51:16.443142Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"This is pretty clear and concise, at the beginning of the play he appears to be looking more towards RDL3 until he eventually locks on to RDL2 until the punt is made. RDL2 soaked up about 91% of the credit, leaving us pretty confident that it was the actual assignment.","metadata":{}},{"cell_type":"markdown","source":"## An Example of a Missed Block","metadata":{}},{"cell_type":"code","source":"import numpy as np \nimport pandas as pd \n\nuse = pd.read_csv(r'https://github.com/jdruzzi/BDB22/blob/main/Expected%20Blocking%20Assignments/W_Blocking_bad.csv.gz?raw=true', compression='gzip', low_memory=False)\nuse['Block_Oppt_pctMax_fwd'] = use.groupby(['gameId','playId','nflId'])['Block_Oppt_x_Pos3_pctMax'].shift(-1)\nuse['Block_Oppt_x_Pos3_fwd'] = use.groupby(['gameId','playId','nflId'])['Block_Oppt_x_Pos3'].shift(-1)\n\n\nylim= (-10, 2.5)\nxlim=(-10, 10)\n\nfig = plt.figure(figsize=(16,8))\nax = plt.axes(xlim=xlim, ylim=ylim)\n\n\npoints0, = ax.plot([], [],'.',alpha = 1, markersize =75,color='red')\npoints1, = ax.plot([], [],'.',alpha = 1, markersize =75,color='#2F4F4F')\npoints2, = ax.plot([], [],'.',alpha = 1, markersize =75,color='#C0C0C0')\npoints3, = ax.plot([], [],'d',alpha = 1, markersize =20,color='brown')\n\nrect = patches.Rectangle((4.5, -10),\n        (3.5),\n        (5),\n        linewidth=3,\n        edgecolor= 'yellow',\n        facecolor = 'black',\n        alpha= 1,\n        fill = True,\n        label= 'Hull Metrics')\n#   ax.legend()\nax.add_patch(rect)\n\nframe_text = ax.text(5, -5.5, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize=20)\nW_Block = ax.text(4.57, -6.3, 'Weighted Blocking Credit', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize=10.45)\nCredit1 = ax.text(4.75, -7, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize=15,path_effects=[pe.withStroke(linewidth=3, foreground=\"black\")])\nCredit2 = ax.text(4.75, -7.5, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize=15,path_effects=[pe.withStroke(linewidth=3, foreground=\"black\")])\nCredit3 = ax.text(4.75, -8, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize=15,path_effects=[pe.withStroke(linewidth=3, foreground=\"black\")])\nCredit4 = ax.text(4.75, -8.5, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize=15,path_effects=[pe.withStroke(linewidth=3, foreground=\"black\")])\nCredit5 = ax.text(4.75, -9, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize=15,path_effects=[pe.withStroke(linewidth=3, foreground=\"black\")])\n\n\n\na_or_list = []\nname_list = []\nname_list2 = []\nscat_number_list = []\nblock = []\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='medium',path_effects=[pe.withStroke(linewidth=3, foreground=\"gold\")]))\n    name_list2.append(ax.text(0, 0, '', horizontalalignment = 'center', verticalalignment = 'center', c = 'black',fontweight='bold',fontsize='medium',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\nplt.axhline(y=0, color='black', linestyle='-',linewidth=6,alpha=.5)\n\n\nplt.axis('off')\n\n\nto_be_deleted = []\n\nBlocking = []\n\ndef animate(i):\n    b_df = pd.DataFrame(Blocking, columns=['Defender','Credit']).drop_duplicates(subset=['Defender'],keep='last').sort_values(by=['Credit'], ascending=False)\n\n    time = use['frameId'].unique()[i]\n\n    trim = use.loc[use['frameId'] == time].drop_duplicates()\n\n    PPhome_x = trim.loc[(trim['frameId'] == time)& (trim['x_Pos'] == \"PP\")]['LOS_Y_diff']\n    PPhome_y = trim.loc[(trim['frameId'] == time)& (trim['x_Pos'] == \"PP\")]['LOS_X_diff']\n\n    home_x = trim.loc[(trim['frameId'] == time)& (trim['punt_team'] == \"Returning_Team\")& (trim['x_Pos'] != \"PP\")]['LOS_Y_diff']\n    home_y = trim.loc[(trim['frameId'] == time)& (trim['punt_team'] == \"Returning_Team\")& (trim['x_Pos'] != \"PP\")]['LOS_X_diff']\n\n    away_x = trim.loc[(use['frameId'] == time) & (trim['punt_team'] == \"Punting_Team\")& (trim['x_Pos'] != \"PP\")]['LOS_Y_diff']\n    away_y = trim.loc[(use['frameId'] == time) & (trim['punt_team'] == \"Punting_Team\")& (trim['x_Pos'] != \"PP\")]['LOS_X_diff']\n    \n    PPhome_player_coordinate = pd.DataFrame({'x':PPhome_x,'y':PPhome_y})\n    home_player_coordinate = pd.DataFrame({'x':home_x,'y':home_y})\n    away_player_coordinate = pd.DataFrame({'x':away_x,'y':away_y})\n\n    frame_text.set_text('frame ' + str(trim.frameId.iloc[0]))\n\n    points0.set_data((PPhome_player_coordinate['x']),(PPhome_player_coordinate['y']))\n    points1.set_data((home_player_coordinate['x']),(home_player_coordinate['y']))\n    points2.set_data((away_player_coordinate['x']),(away_player_coordinate['y']))\n\n    try:\n        Credit1.set_text('#1  ' + str(b_df.Defender.iloc[0]) + \"   \" +str(\"{0:.00%}\".format(b_df.Credit.iloc[0])) )\n    except:\n        pass\n    \n    try:\n        Credit2.set_text('#2  ' + str(b_df.Defender.iloc[1]) + \"   \" +str(\"{0:.00%}\".format(b_df.Credit.iloc[1])) )\n    except:\n        pass\n\n    try:\n        Credit3.set_text('#3  ' + str(b_df.Defender.iloc[2]) + \"   \" +str(\"{0:.00%}\".format(b_df.Credit.iloc[2])) )\n    except:\n        pass\n\n    try:\n        Credit4.set_text('#4  ' + str(b_df.Defender.iloc[3]) + \"   \" +str(\"{0:.00%}\".format(b_df.Credit.iloc[3])) )\n    except:\n        pass\n\n    try:\n        Credit3.set_text('#5  ' + str(b_df.Defender.iloc[4]) + \"   \" +str(\"{0:.00%}\".format(b_df.Credit.iloc[4])) )\n    except:\n        pass\n\n    for (index, row) in trim[trim.displayName.notnull()].reset_index().iterrows():\n        if (row.punt_team == \"Punting_Team\") & (row.x_Pos == \"PP\"):\n            Blocking.append([row.Block_Oppt_x_Pos3_fwd, np.round(row.Block_Oppt_pctMax_fwd, 3)])\n\n\n    for (index, row) in trim[trim.displayName.notnull()].reset_index().iterrows():\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(int(row['jerseyNumber']))\n    # else:\n    #     scat_number_list[index].set_text(\"\")\n    #     pass\n    \n    for (index, row) in trim[trim.displayName.notnull()].reset_index().iterrows():\n        if row.punt_team == \"Returning_Team\":\n            name_list[index].set_text(row.x_Pos3)\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\n    for (index, row) in trim[trim.displayName.notnull()].reset_index().iterrows():\n        if (row.punt_team == \"Punting_Team\") & (row.x_Pos == \"PP\"):\n            name_list2[index].set_text(row.Block_Oppt_x_Pos3)\n            name_list2[index].set_position((row.LOS_Y_diff, row.LOS_X_diff+1))\n\n        else:\n            name_list2[index].set_text(\"\")\n            name_list2[index].set_position((row.LOS_Y_diff, row.LOS_X_diff+1))\n\n        if (row.punt_team == \"Punting_Team\") & (row.x_Pos == \"PP\"):\n            block[index].remove()\n            block[index] = ax.add_patch(Arrow(row.LOS_Y_diff, row.LOS_X_diff, (row.LOS_Y_diff_pred_diff*-1), (row.LOS_X_diff_pred_diff*-1), color = 'green', width = .2))\n\n        else:\n            block[index].remove()\n            block[index] = ax.add_patch(ax.add_patch(Arrow(0, 0, 0, 0, color = 'white', width = .001)))\n\n\n    return points1,points2\n\n\n\nanim = animation.FuncAnimation(fig, animate,\n                              frames=len(use['frameId'].unique()))\n\n\n\n\nHTML(anim.to_jshtml())","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-12-16T03:21:49.636276Z","iopub.execute_input":"2021-12-16T03:21:49.636628Z","iopub.status.idle":"2021-12-16T03:21:55.14183Z","shell.execute_reply.started":"2021-12-16T03:21:49.636584Z","shell.execute_reply":"2021-12-16T03:21:55.140894Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I've probably watched this play 100 times, and I still couldn't tell you what the punt protector was doing. I think it's pretty clear that he was supposed to block LDL3, yet went to block LV1, even when his teammate was already there and was also aware of the immediate threat of LDL3. This is a perfect example of how difficult it is to estimate plays like these, yet using this weighted metric, LDL3 was still the #1 assignment despite the confidence being low.","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- [Alternate Outcomes WIth Punt Pressure & HAVOC](https://www.kaggle.com/jdruzzi/alternate-outcomes-with-punt-pressure-havoc)\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":{}}]}