{"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":"# Evaluating Punt Protection & Punt Block Units with Convex Hulls","metadata":{}},{"cell_type":"markdown","source":"# What are Convex Hulls?\n\nGiven a set of finite points, we can connect the outermost points to create a shape - with the area enclosed being called the convex hull. Think of wrapping a rubberband around a cluster of nails on a piece of wood, the band will mold itself around the outer nails - while some nails still remain in the center.","metadata":{}},{"cell_type":"markdown","source":"![](http://mathonline.wdfiles.com/local--files/the-closed-convex-hull-of-a-set-in-a-lctvs/Screen%20Shot%202018-03-30%20at%201.42.49%20PM.png)","metadata":{}},{"cell_type":"markdown","source":"# How can this be applied to special teams in the NFL?\n\nGiven the nature of this data, we can create 2 convex hulls within each frame for each play.\n\n-      <span style=\"color:#241773;font-size:16px;\">          Hull 1: The Punt Rush unit       </span>\n    - This contains punt return Defensive Linemen, Linebackers or eligible Vises within the box\n- <span style=\"color:#E31837;font-size:16px;\">          Hull 2: The Last Line of Defense (LLOD) unit      </span>\n    - This contains only the Wings, Punt Protector and the Punter\n\n![](https://github.com/jdruzzi/BDB22/blob/main/Convex%20Hull%20-%20PuntPro%20Eval/Hull_ex_1_NoPuntPro.png?raw=true)\n\n\nInstead of giving the entire punt unit its own hull, we only want to isolate the LLOD and observe how the Punt Rush Hull interacts with it.","metadata":{}},{"cell_type":"markdown","source":"# Metrics\n\nAs the play evolves, we can evaluate the Hulls for a variety of metrics. \n\n![](https://github.com/jdruzzi/BDB22/blob/main/Convex%20Hull%20-%20PuntPro%20Eval/Hull_ex_2_NoPuntPro.png?raw=true)\n\n#### Defensive Penetration Percent:\nNotice how the Punt Rush Hull and Punt Pro areas overlap? We can quantify a fill or pressure rate based on area overlap.\n#### Number of Defenders that have breached the Punt Pro Hull:\nSince we know the areas of all Hulls, we can determine how many defenders have breached the LLOD Hull based on their X and Y coordinates.\n#### Defender Time to Hull:\nIf a breach occurred - how fast were they able to achieve the breach? ","metadata":{}},{"cell_type":"markdown","source":"# Exploring Convex Hull Metrics","metadata":{}},{"cell_type":"markdown","source":"## Let's see an example of a standard punt play","metadata":{}},{"cell_type":"code","source":"%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\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/Convex%20Hull%20-%20PuntPro%20Eval/ex_2_Normpunt.csv.gz?raw=true', compression='gzip', low_memory=False, index_col=0)\n\nylim= (-18, 6.5)\nxlim=(-21, 24)\n# fig = plt.figure(figsize=(20,10))\n#ylim=(40,85)\n#xlim=(0,50)\nfig = plt.figure(figsize=(17,10))\nax = plt.axes(xlim=xlim, ylim=ylim)\n\n\n\n\n# plt.ylim([-3, 22])\n# plt.xlim([-23.3, 23.3])\npoints1, = ax.plot([], [],'.',alpha = .85, markersize =45,color='#241773')\npoints2, = ax.plot([], [],'.',alpha = .85, markersize =45,color='#E31837')\npoints3, = ax.plot([], [],'d',alpha = 1, markersize =20,color='brown')\nframe_text = ax.text(16, -5, '', horizontalalignment = 'center', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize='larger', size=15)\n\nIn_vex_last1 = ax.text(10.2, -7, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize='larger', size=14.6)\nIn_vex_last2 = ax.text(10.2, -9, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize='larger', size=14.6)\nIn_vex_last3 = ax.text(10.2, -11, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize='larger', size=14.6)\nIn_vex_last4 = ax.text(10.2, -13, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize='larger', size=14.6)\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='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='large',path_effects=[pe.withStroke(linewidth=3, foreground=\"white\")]))\n    Block_Prob_list.append(ax.text(0, 0, '', horizontalalignment = 'center', verticalalignment = 'top', c = 'green',fontweight='bold',fontsize='larger',path_effects=[pe.withStroke(linewidth=2, 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\nrect = patches.Rectangle((10, -15),\n        (6*2),\n        (6*2),\n        linewidth=3,\n        edgecolor= 'black',\n        facecolor = 'black',\n        alpha= 1,\n        fill = True,\n        label= 'Hull Metrics')\n#   ax.legend()\nax.add_patch(rect)\n\nto_be_deleted = []\n\ndef point_in_hull(point, hull, tolerance=1e-12):\n    return all(\n        (np.dot(eq[:-1], point) + eq[-1] <= tolerance)\n        for eq in hull.equations)\n\ndef truncate(f, n):\n    return math.floor(f * 10 ** n) / 10 ** n\n\nplt.axis('off')\n\nvex_last_time = []\n\nHull_Area = []\n\ndef animate(i):\n    time = use['frameId'].unique()[i]\n\n    trim = use.loc[use['frameId'] == time].drop_duplicates()\n\n    home_x = trim.loc[(trim['frameId'] == time) & (trim['punt_team'] == \"Returning_Team\")]['LOS_X_diff']\n    home_y = trim.loc[(trim['frameId'] == time) & (trim['punt_team'] == \"Returning_Team\")]['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    \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    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    try:\n      Pos = ['W','PP','P','PL','LS']\n\n      off = trim.loc[trim['x_Pos'].isin(Pos)& (trim['LOS_X_diff'] < 2 ) & (np.abs(trim['LOS_Y_diff']) < 8.5 )]\n\n      for patch in to_be_deleted:\n          patch.remove()\n      del to_be_deleted[:]\n\n      offpoints = off[['LOS_Y_diff', 'LOS_X_diff']].values\n\n      offhull = ConvexHull(offpoints)\n      poly1 = plt.Polygon(offpoints[offhull.vertices,:], color='black', alpha=0.6, linewidth=2, edgecolor='black',fill=False)\n      to_be_deleted.append(poly1)\n      ax.add_patch(poly1)\n    except:\n      pass\n    \n    try:\n      Pos = ['W','PP','P']\n\n      off = trim.loc[trim['x_Pos'].isin(Pos)& (trim['LOS_X_diff'] < 2 ) & (np.abs(trim['LOS_Y_diff']) < 8.5 )]\n\n      LastLine = off[['LOS_Y_diff', 'LOS_X_diff']].values\n\n      Lasthull = ConvexHull(LastLine)\n      poly_last = plt.Polygon(LastLine[Lasthull.vertices,:], color='#E31837', alpha=0.35, linewidth=2, edgecolor='black')\n      to_be_deleted.append(poly_last)\n      ax.add_patch(poly_last)\n    except:\n      pass\n\n    try:\n      Pos = ['DL','PLB','V']\n\n      defs = trim.loc[(trim['x_Pos'].isin(Pos)) & (trim['LOS_X_diff'] < 2 ) & (np.abs(trim['LOS_Y_diff']) < 12.5 )]\n\n      # for patch in to_be_deleted:\n      #     patch.remove()\n      # del to_be_deleted[:]\n\n      defpoints = defs[['LOS_Y_diff', 'LOS_X_diff']].values\n\n      defhull = ConvexHull(defpoints)\n      poly2 = plt.Polygon(defpoints[defhull.vertices,:], color='#241773', alpha=0.35, linewidth=2, edgecolor='black')\n      to_be_deleted.append(poly2)\n      ax.add_patch(poly2)\n    except:\n      pass\n    \n #   try:\n    vex = 0\n    vex_last = 0\n    for p in defpoints:\n   #     print(p)\n        point_is_in_hull = point_in_hull(p, offhull)\n        point_is_in_hull_last = point_in_hull(p, Lasthull)\n        try:\n            if point_is_in_hull == True:\n              vex += 1\n            else:\n              continue\n            if point_is_in_hull_last == True:\n              vex_last += 1\n              vex_last_time.append(time)\n            else:\n              continue\n        except:\n           pass\n    \n    try:\n       vex_last_time_min = (vex_last_time[0] - use['snap_frame'].iloc[0]) / 10\n    except:\n       vex_last_time_min = ''\n       pass\n  #  In_vex.set_text(\"\")\n #   In_vex_last.set_text(\"\")\n\n    from decimal import Decimal, ROUND_UP\n\n\n    X1 = Polygon([(i,j) for i, j in defpoints[defhull.vertices,:]])\n    #   X1= make_valid(X1)\n\n    X2 = Polygon([(i,j) for i, j in offpoints[offhull.vertices,:]])\n    #  X2= make_valid(X2)\n\n    Intersection = X1.intersection(X2).area / X2.area\n    Intersection = truncate(Intersection, 2)\n    Intersection = Decimal(str(Intersection)).quantize(Decimal('.01'), rounding=ROUND_UP)\n\n    X3 = Polygon([(i,j) for i, j in LastLine[Lasthull.vertices,:]])\n    #   X3= make_valid(X3)\n\n    Intersection_last = X1.intersection(X3).area / X3.area\n    Intersection_last = truncate(Intersection_last, 2)\n    Hull_Area.append(Intersection_last)\n    Intersection_last = Decimal(str(Intersection_last)).quantize(Decimal('.01'), rounding=ROUND_UP)\n    \n    try:\n        Hull_Area_sum = np.sum(Hull_Area)\n        Hull_Area_sum = Decimal(str(Hull_Area_sum)).quantize(Decimal('.01'), rounding=ROUND_UP)\n    except:\n        Hull_Area_sum = 0\n        pass\n    \n    \n\n  #  In_vex2.set_text('PuntPro Fill Percent: ' + str(\"{0:.00%}\".format(Intersection)))\n    In_vex_last1.set_text('Defenders in LLOD Hull: ' + str(vex_last))\n    In_vex_last2.set_text('LLOD Fill Percent: ' + str(\"{0:.00%}\".format(Intersection_last)))\n    In_vex_last3.set_text('Cumulative Fill Percent: ' + str(\"{0:.00%}\".format(Hull_Area_sum)))\n    \n    try:\n        if time > vex_last_time[0]:\n            In_vex_last4.set_text('Time to LLOD Hull: ' + str(vex_last_time_min) + \" Sec\")\n        else:\n            In_vex_last4.set_text('Time to LLOD Hull: ' )\n            pass\n    except:\n        if vex_last > 0:\n            In_vex_last4.set_text('Time to LLOD Hull: ' + str(vex_last_time_min) + \" Sec\")\n        else:\n            In_vex_last4.set_text('Time to LLOD Hull: ' )\n            pass\n\n\n\n    for (index, row) in trim[trim.displayName.notnull()].reset_index().iterrows():\n      if row.displayName != \"football\":\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      #LOS_X_diff_pred\tLOS_Y_diff_pred\n\n    #   if row.punt_team == \"Punting_Team\" and row.frameId <= (row.snap_frame + 10):\n    #     block[index].remove()\n    #     block[index] = ax.add_patch(FancyArrow(row.LOS_Y_diff, row.LOS_X_diff, (row.LOS_Y_diff_pred_diff*-1), (row.LOS_X_diff_pred_diff*-1)/2, color = 'green', width = .2, shape='left'))\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    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     #   name_list[index].remove()\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      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\n     #   name_list[index].remove()\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\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\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.Block_Prob > 0 and row.frameId <= (row.snap_frame + 10):\n   #       Block_Prob_list[index].set_text(str(round(float(row.Block_Prob)*100,2)) + \"%\")\n    #      Block_Prob_list[index].set_position((row.LOS_Y_diff, row.LOS_X_diff+2.4))\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.4))\n    #      pass\n\n        pass\n      \n    return points1,points2,points3,\n\n\nanim = animation.FuncAnimation(fig, animate,\n                               frames=len(use['frameId'].unique()))\n\nHTML(anim.to_jshtml())","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-12-07T20:40:34.350852Z","iopub.execute_input":"2021-12-07T20:40:34.351521Z","iopub.status.idle":"2021-12-07T20:40:45.757626Z","shell.execute_reply.started":"2021-12-07T20:40:34.351457Z","shell.execute_reply":"2021-12-07T20:40:45.756932Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### This can be considered a routine, well executed punt play. Notice how the the Last Line of Defense (LLOD) Hull was barely penetrated, while no defenders managed to actually breach the hull.","metadata":{}},{"cell_type":"markdown","source":"## Now let's see an example of a blocked punt","metadata":{}},{"cell_type":"code","source":"%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\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/Convex%20Hull%20-%20PuntPro%20Eval/ex_1.csv.gz?raw=true', compression='gzip', low_memory=False, index_col=0)\n\nylim= (-18, 6.5)\nxlim=(-21, 24)\n# fig = plt.figure(figsize=(20,10))\n#ylim=(40,85)\n#xlim=(0,50)\nfig = plt.figure(figsize=(17,10))\nax = plt.axes(xlim=xlim, ylim=ylim)\n\n\n\n\n# plt.ylim([-3, 22])\n# plt.xlim([-23.3, 23.3])\npoints1, = ax.plot([], [],'.',alpha = .85, markersize =45,color='#241773')\npoints2, = ax.plot([], [],'.',alpha = .85, markersize =45,color='#E31837')\npoints3, = ax.plot([], [],'d',alpha = 1, markersize =20,color='brown')\nframe_text = ax.text(16, -5, '', horizontalalignment = 'center', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize='larger', size=15)\n\nIn_vex_last1 = ax.text(10.2, -7, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize='larger', size=14.6)\nIn_vex_last2 = ax.text(10.2, -9, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize='larger', size=14.6)\nIn_vex_last3 = ax.text(10.2, -11, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize='larger', size=14.6)\nIn_vex_last4 = ax.text(10.2, -13, '', horizontalalignment = 'left', verticalalignment = 'center', c = 'white',fontweight='bold',fontsize='larger', size=14.6)\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='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='large',path_effects=[pe.withStroke(linewidth=3, foreground=\"white\")]))\n    Block_Prob_list.append(ax.text(0, 0, '', horizontalalignment = 'center', verticalalignment = 'top', c = 'green',fontweight='bold',fontsize='larger',path_effects=[pe.withStroke(linewidth=2, 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\nrect = patches.Rectangle((10, -15),\n        (6*2),\n        (6*2),\n        linewidth=3,\n        edgecolor= 'black',\n        facecolor = 'black',\n        alpha= 1,\n        fill = True,\n        label= 'Hull Metrics')\n#   ax.legend()\nax.add_patch(rect)\n\nto_be_deleted = []\n\ndef point_in_hull(point, hull, tolerance=1e-12):\n    return all(\n        (np.dot(eq[:-1], point) + eq[-1] <= tolerance)\n        for eq in hull.equations)\n\ndef truncate(f, n):\n    return math.floor(f * 10 ** n) / 10 ** n\n\nplt.axis('off')\n\nvex_last_time = []\n\nHull_Area = []\n\ndef animate(i):\n    time = use['frameId'].unique()[i]\n\n    trim = use.loc[use['frameId'] == time].drop_duplicates()\n\n    home_x = trim.loc[(trim['frameId'] == time) & (trim['punt_team'] == \"Returning_Team\")]['LOS_X_diff']\n    home_y = trim.loc[(trim['frameId'] == time) & (trim['punt_team'] == \"Returning_Team\")]['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    \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    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    try:\n      Pos = ['W','PP','P','PL','LS']\n\n      off = trim.loc[trim['x_Pos'].isin(Pos)& (trim['LOS_X_diff'] < 2 ) & (np.abs(trim['LOS_Y_diff']) < 8.5 )]\n\n      for patch in to_be_deleted:\n          patch.remove()\n      del to_be_deleted[:]\n\n      offpoints = off[['LOS_Y_diff', 'LOS_X_diff']].values\n\n      offhull = ConvexHull(offpoints)\n      poly1 = plt.Polygon(offpoints[offhull.vertices,:], color='black', alpha=0.6, linewidth=2, edgecolor='black',fill=False)\n      to_be_deleted.append(poly1)\n      ax.add_patch(poly1)\n    except:\n      pass\n    \n    try:\n      Pos = ['W','PP','P']\n\n      off = trim.loc[trim['x_Pos'].isin(Pos)& (trim['LOS_X_diff'] < 2 ) & (np.abs(trim['LOS_Y_diff']) < 8.5 )]\n\n      LastLine = off[['LOS_Y_diff', 'LOS_X_diff']].values\n\n      Lasthull = ConvexHull(LastLine)\n      poly_last = plt.Polygon(LastLine[Lasthull.vertices,:], color='#E31837', alpha=0.35, linewidth=2, edgecolor='black')\n      to_be_deleted.append(poly_last)\n      ax.add_patch(poly_last)\n    except:\n      pass\n\n    try:\n      Pos = ['DL','PLB','V']\n\n      defs = trim.loc[(trim['x_Pos'].isin(Pos)) & (trim['LOS_X_diff'] < 2 ) & (np.abs(trim['LOS_Y_diff']) < 12.5 )]\n\n      # for patch in to_be_deleted:\n      #     patch.remove()\n      # del to_be_deleted[:]\n\n      defpoints = defs[['LOS_Y_diff', 'LOS_X_diff']].values\n\n      defhull = ConvexHull(defpoints)\n      poly2 = plt.Polygon(defpoints[defhull.vertices,:], color='#241773', alpha=0.35, linewidth=2, edgecolor='black')\n      to_be_deleted.append(poly2)\n      ax.add_patch(poly2)\n    except:\n      pass\n    \n #   try:\n    vex = 0\n    vex_last = 0\n    for p in defpoints:\n   #     print(p)\n        point_is_in_hull = point_in_hull(p, offhull)\n        point_is_in_hull_last = point_in_hull(p, Lasthull)\n        try:\n            if point_is_in_hull == True:\n              vex += 1\n            else:\n              continue\n            if point_is_in_hull_last == True:\n              vex_last += 1\n              vex_last_time.append(time)\n            else:\n              continue\n        except:\n           pass\n    \n    try:\n       vex_last_time_min = (vex_last_time[0] - use['snap_frame'].iloc[0]) / 10\n    except:\n       vex_last_time_min = ''\n       pass\n  #  In_vex.set_text(\"\")\n #   In_vex_last.set_text(\"\")\n\n    from decimal import Decimal, ROUND_UP\n\n\n    X1 = Polygon([(i,j) for i, j in defpoints[defhull.vertices,:]])\n    #   X1= make_valid(X1)\n\n    X2 = Polygon([(i,j) for i, j in offpoints[offhull.vertices,:]])\n    #  X2= make_valid(X2)\n\n    Intersection = X1.intersection(X2).area / X2.area\n    Intersection = truncate(Intersection, 2)\n    Intersection = Decimal(str(Intersection)).quantize(Decimal('.01'), rounding=ROUND_UP)\n\n    X3 = Polygon([(i,j) for i, j in LastLine[Lasthull.vertices,:]])\n    #   X3= make_valid(X3)\n\n    Intersection_last = X1.intersection(X3).area / X3.area\n    Intersection_last = truncate(Intersection_last, 2)\n    Hull_Area.append(Intersection_last)\n    Intersection_last = Decimal(str(Intersection_last)).quantize(Decimal('.01'), rounding=ROUND_UP)\n    \n    try:\n        Hull_Area_sum = np.sum(Hull_Area)\n        Hull_Area_sum = Decimal(str(Hull_Area_sum)).quantize(Decimal('.01'), rounding=ROUND_UP)\n    except:\n        Hull_Area_sum = 0\n        pass\n    \n    \n\n  #  In_vex2.set_text('PuntPro Fill Percent: ' + str(\"{0:.00%}\".format(Intersection)))\n    In_vex_last1.set_text('Defenders in LLOD Hull: ' + str(vex_last))\n    In_vex_last2.set_text('LLOD Fill Percent: ' + str(\"{0:.00%}\".format(Intersection_last)))\n    In_vex_last3.set_text('Cumulative Fill Percent: ' + str(\"{0:.00%}\".format(Hull_Area_sum)))\n    \n    try:\n        if time > vex_last_time[0]:\n            In_vex_last4.set_text('Time to LLOD Hull: ' + str(vex_last_time_min) + \" Sec\")\n        else:\n            In_vex_last4.set_text('Time to LLOD Hull: ' )\n            pass\n    except:\n        if vex_last > 0:\n            In_vex_last4.set_text('Time to LLOD Hull: ' + str(vex_last_time_min) + \" Sec\")\n        else:\n            In_vex_last4.set_text('Time to LLOD Hull: ' )\n            pass\n\n\n\n    for (index, row) in trim[trim.displayName.notnull()].reset_index().iterrows():\n      if row.displayName != \"football\":\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      #LOS_X_diff_pred\tLOS_Y_diff_pred\n\n    #   if row.punt_team == \"Punting_Team\" and row.frameId <= (row.snap_frame + 10):\n    #     block[index].remove()\n    #     block[index] = ax.add_patch(FancyArrow(row.LOS_Y_diff, row.LOS_X_diff, (row.LOS_Y_diff_pred_diff*-1), (row.LOS_X_diff_pred_diff*-1)/2, color = 'green', width = .2, shape='left'))\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    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     #   name_list[index].remove()\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      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\n     #   name_list[index].remove()\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\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\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.Block_Prob > 0 and row.frameId <= (row.snap_frame + 10):\n   #       Block_Prob_list[index].set_text(str(round(float(row.Block_Prob)*100,2)) + \"%\")\n    #      Block_Prob_list[index].set_position((row.LOS_Y_diff, row.LOS_X_diff+2.4))\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.4))\n    #      pass\n\n        pass\n      \n    return points1,points2,points3,\n\n\nanim = animation.FuncAnimation(fig, animate,\n                               frames=len(use['frameId'].unique()))\n\nHTML(anim.to_jshtml())","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-12-07T20:48:35.370359Z","iopub.execute_input":"2021-12-07T20:48:35.370669Z","iopub.status.idle":"2021-12-07T20:48:46.792707Z","shell.execute_reply.started":"2021-12-07T20:48:35.370637Z","shell.execute_reply":"2021-12-07T20:48:46.791968Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### As you can see, the penetration fill rate is extremely high with as many as 3 defenders reaching the LLOD Hull - eventually blocking the punt.","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":{}}]}