{"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":"# Cluster formations\n\nI only had a couple days to work on this over the holiday, but I wanted to upload.\n\n1. Invert R to L plays\n2. Line of scrimmage abs val xy coordinates\n3. Shrink field\n4. Fill players on new field \n\nDetect Offensive and Defensive formations for each play type (kickoff, punt, extra point, and field goal)","metadata":{}},{"cell_type":"code","source":"%%capture\n!conda install -y -c conda-forge hdbscan umap-learn;","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:26:31.337687Z","iopub.execute_input":"2022-01-14T14:26:31.337987Z","iopub.status.idle":"2022-01-14T14:27:05.815950Z","shell.execute_reply.started":"2022-01-14T14:26:31.337956Z","shell.execute_reply":"2022-01-14T14:27:05.814151Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\n\nimport hdbscan\nimport seaborn as sns\nfrom umap import UMAP\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:16:09.499091Z","iopub.execute_input":"2022-01-14T14:16:09.499462Z","iopub.status.idle":"2022-01-14T14:16:36.089967Z","shell.execute_reply.started":"2022-01-14T14:16:09.499417Z","shell.execute_reply":"2022-01-14T14:16:36.088671Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"game_data = pd.read_csv(\"/kaggle/input/nfl-big-data-bowl-2022/games.csv\")\nplays = pd.read_csv(\"/kaggle/input/nfl-big-data-bowl-2022/plays.csv\")\nscouting = pd.read_csv(\"/kaggle/input/nfl-big-data-bowl-2022/PFFScoutingData.csv\")\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:16:36.092033Z","iopub.execute_input":"2022-01-14T14:16:36.092395Z","iopub.status.idle":"2022-01-14T14:16:36.402347Z","shell.execute_reply.started":"2022-01-14T14:16:36.092349Z","shell.execute_reply":"2022-01-14T14:16:36.400776Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"punt_plays = plays[plays[\"specialTeamsPlayType\"] == \"Punt\"].copy()\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:16:36.404690Z","iopub.execute_input":"2022-01-14T14:16:36.405289Z","iopub.status.idle":"2022-01-14T14:16:36.427107Z","shell.execute_reply.started":"2022-01-14T14:16:36.405248Z","shell.execute_reply":"2022-01-14T14:16:36.425854Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# merge game data\npunt_plays = punt_plays.merge(game_data, left_on=\"gameId\", right_on=\"gameId\")","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:16:36.429792Z","iopub.execute_input":"2022-01-14T14:16:36.430475Z","iopub.status.idle":"2022-01-14T14:16:36.460823Z","shell.execute_reply.started":"2022-01-14T14:16:36.430427Z","shell.execute_reply":"2022-01-14T14:16:36.459794Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# merge game data\nall_plays = plays.merge(game_data, left_on=\"gameId\", right_on=\"gameId\")","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:16:36.462464Z","iopub.execute_input":"2022-01-14T14:16:36.462756Z","iopub.status.idle":"2022-01-14T14:16:36.499444Z","shell.execute_reply.started":"2022-01-14T14:16:36.462719Z","shell.execute_reply":"2022-01-14T14:16:36.498062Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# merge scouting data\nall_plays = all_plays.merge(scouting, left_on=[\"gameId\", \"playId\"], right_on=[\"gameId\", \"playId\"])","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:16:36.501092Z","iopub.execute_input":"2022-01-14T14:16:36.501476Z","iopub.status.idle":"2022-01-14T14:16:36.547697Z","shell.execute_reply.started":"2022-01-14T14:16:36.501445Z","shell.execute_reply":"2022-01-14T14:16:36.545999Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Add in tracking for 18, 19, 20 and filter to the first frame only\niter_csv = pd.read_csv(\"/kaggle/input/nfl-big-data-bowl-2022/tracking2018.csv\", iterator=True, chunksize=1e6)\ntracking18 = pd.concat([chunk[chunk[\"frameId\"] == 1] for chunk in iter_csv])\n\niter_csv = pd.read_csv(\"/kaggle/input/nfl-big-data-bowl-2022/tracking2019.csv\", iterator=True, chunksize=1e6)\ntracking19 = pd.concat([chunk[chunk[\"frameId\"] == 1] for chunk in iter_csv])\n\niter_csv = pd.read_csv(\"/kaggle/input/nfl-big-data-bowl-2022/tracking2020.csv\", iterator=True, chunksize=1e6)\ntracking20 = pd.concat([chunk[chunk[\"frameId\"] == 1] for chunk in iter_csv])\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:16:36.549327Z","iopub.execute_input":"2022-01-14T14:16:36.549899Z","iopub.status.idle":"2022-01-14T14:19:11.845289Z","shell.execute_reply.started":"2022-01-14T14:16:36.549864Z","shell.execute_reply":"2022-01-14T14:19:11.844431Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"formation_tracking = pd.concat([tracking18, tracking19, tracking20])\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:11.847428Z","iopub.execute_input":"2022-01-14T14:19:11.848452Z","iopub.status.idle":"2022-01-14T14:19:11.899013Z","shell.execute_reply.started":"2022-01-14T14:19:11.848387Z","shell.execute_reply":"2022-01-14T14:19:11.897842Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del tracking18, tracking19, tracking20","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:11.900593Z","iopub.execute_input":"2022-01-14T14:19:11.901012Z","iopub.status.idle":"2022-01-14T14:19:11.918294Z","shell.execute_reply.started":"2022-01-14T14:19:11.900972Z","shell.execute_reply":"2022-01-14T14:19:11.917507Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = formation_tracking.merge(all_plays, on=[\"gameId\", \"playId\"])\n","metadata":{"scrolled":true,"execution":{"iopub.status.busy":"2022-01-14T14:19:11.919792Z","iopub.execute_input":"2022-01-14T14:19:11.920481Z","iopub.status.idle":"2022-01-14T14:19:13.638445Z","shell.execute_reply.started":"2022-01-14T14:19:11.920433Z","shell.execute_reply":"2022-01-14T14:19:13.637122Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del all_plays","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:13.640301Z","iopub.execute_input":"2022-01-14T14:19:13.641073Z","iopub.status.idle":"2022-01-14T14:19:13.649040Z","shell.execute_reply.started":"2022-01-14T14:19:13.640988Z","shell.execute_reply":"2022-01-14T14:19:13.647649Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.loc[df[\"y\"] < 0, \"y\"] = 0\ndf.loc[df[\"y\"] > 53, \"y\"] = 53","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:13.650787Z","iopub.execute_input":"2022-01-14T14:19:13.651462Z","iopub.status.idle":"2022-01-14T14:19:13.674717Z","shell.execute_reply.started":"2022-01-14T14:19:13.651411Z","shell.execute_reply":"2022-01-14T14:19:13.673740Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Normalize the data\ndf[\"x_norm\"] = df[\"x\"]\ndf[\"y_norm\"] = df[\"y\"]\ndf[\"absoluteYardlineNumber_norm\"] = df[\"absoluteYardlineNumber\"]\n\ndf.loc[df[\"playDirection\"] == \"left\", \"x_norm\"] = (\n    120 - df.loc[df[\"playDirection\"] == \"left\", \"x\"]\n)\ndf.loc[df[\"playDirection\"] == \"left\", \"y_norm\"] = (\n    53 - df.loc[df[\"playDirection\"] == \"left\", \"y\"]\n)\ndf.loc[df[\"playDirection\"] == \"left\", \"absoluteYardlineNumber_norm\"] = (\n    120 - df.loc[df[\"playDirection\"] == \"left\", \"absoluteYardlineNumber\"]\n)","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:13.678446Z","iopub.execute_input":"2022-01-14T14:19:13.678757Z","iopub.status.idle":"2022-01-14T14:19:14.170633Z","shell.execute_reply.started":"2022-01-14T14:19:13.678722Z","shell.execute_reply":"2022-01-14T14:19:14.169643Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Assign possession","metadata":{}},{"cell_type":"code","source":"def has_possession(row):\n    if (row[\"team\"] == \"away\") and (\n        row[\"possessionTeam\"] == row[\"visitorTeamAbbr\"]\n    ):\n        return True\n    elif (row[\"team\"] == \"home\") and (\n        row[\"possessionTeam\"] == row[\"homeTeamAbbr\"]\n    ):\n        return True\n    else:\n        return False\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:14.172488Z","iopub.execute_input":"2022-01-14T14:19:14.172791Z","iopub.status.idle":"2022-01-14T14:19:14.181790Z","shell.execute_reply.started":"2022-01-14T14:19:14.172759Z","shell.execute_reply":"2022-01-14T14:19:14.180175Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df[\"has_possession\"] = df.apply(has_possession, axis=1)\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:14.183792Z","iopub.execute_input":"2022-01-14T14:19:14.184072Z","iopub.status.idle":"2022-01-14T14:19:29.415254Z","shell.execute_reply.started":"2022-01-14T14:19:14.184040Z","shell.execute_reply":"2022-01-14T14:19:29.414276Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Normalize x from line of scrimmage and y from ball","metadata":{}},{"cell_type":"code","source":"# should be dist from line of scrimmage\ndf[\"x_from_scrimmage\"] = df[\"absoluteYardlineNumber_norm\"] - df[\"x_norm\"]","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:29.417056Z","iopub.execute_input":"2022-01-14T14:19:29.417580Z","iopub.status.idle":"2022-01-14T14:19:29.426830Z","shell.execute_reply.started":"2022-01-14T14:19:29.417539Z","shell.execute_reply":"2022-01-14T14:19:29.425921Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def calculate_y_from_ball(rows):\n    y_football = rows.loc[rows[\"team\"] == \"football\", \"y_norm\"].item()\n    return rows[\"y_norm\"] - y_football","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:29.428449Z","iopub.execute_input":"2022-01-14T14:19:29.430869Z","iopub.status.idle":"2022-01-14T14:19:29.439713Z","shell.execute_reply.started":"2022-01-14T14:19:29.430810Z","shell.execute_reply":"2022-01-14T14:19:29.438629Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df[\"y_from_ball\"] = df.groupby([\"gameId\", \"playId\"]).apply(calculate_y_from_ball).droplevel(level=[0,1])","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:29.441629Z","iopub.execute_input":"2022-01-14T14:19:29.442516Z","iopub.status.idle":"2022-01-14T14:19:45.883357Z","shell.execute_reply.started":"2022-01-14T14:19:29.442458Z","shell.execute_reply":"2022-01-14T14:19:45.882008Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.loc[df[\"has_possession\"], \"x_from_scrimmage\"].hist(bins=20)","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:45.884686Z","iopub.execute_input":"2022-01-14T14:19:45.885041Z","iopub.status.idle":"2022-01-14T14:19:46.236301Z","shell.execute_reply.started":"2022-01-14T14:19:45.884998Z","shell.execute_reply":"2022-01-14T14:19:46.235207Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.loc[~df[\"has_possession\"], \"x_from_scrimmage\"].hist(bins=20)","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:46.237918Z","iopub.execute_input":"2022-01-14T14:19:46.238288Z","iopub.status.idle":"2022-01-14T14:19:46.532963Z","shell.execute_reply.started":"2022-01-14T14:19:46.238240Z","shell.execute_reply":"2022-01-14T14:19:46.531585Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# offensive players should all be greater than 0 at the start of the play\n# some aren't, let's set the ones greater than a yard off to 0\ndf.loc[\n    (df[\"x_from_scrimmage\"] < -1) & (df[\"has_possession\"]), \"x_from_scrimmage\"\n] = 0\n\n# defensive players should all be less than 0 at the start of the play\n# some aren't, let's set the ones greater than a yard off to 0\ndf.loc[\n    (df[\"x_from_scrimmage\"] > 1) & (~df[\"has_possession\"]), \"x_from_scrimmage\"\n] = 0\n\ndf[\"x_from_scrimmage_abs\"] = df[\"x_from_scrimmage\"].abs()\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:46.534195Z","iopub.execute_input":"2022-01-14T14:19:46.534436Z","iopub.status.idle":"2022-01-14T14:19:46.557360Z","shell.execute_reply.started":"2022-01-14T14:19:46.534406Z","shell.execute_reply":"2022-01-14T14:19:46.555819Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Separate offense and defense","metadata":{}},{"cell_type":"code","source":"# defense\ndefense_df = df.loc[~df[\"has_possession\"]].copy()\n\n# offense\noffense_df = df.loc[df[\"has_possession\"]].copy()\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:46.559167Z","iopub.execute_input":"2022-01-14T14:19:46.559472Z","iopub.status.idle":"2022-01-14T14:19:47.186824Z","shell.execute_reply.started":"2022-01-14T14:19:46.559441Z","shell.execute_reply":"2022-01-14T14:19:47.185449Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Normalize the x,y coordinates","metadata":{}},{"cell_type":"code","source":"def_min_x = defense_df[\"x_from_scrimmage_abs\"].min()\ndef_max_x = defense_df[\"x_from_scrimmage_abs\"].max()\n\noff_min_x = offense_df[\"x_from_scrimmage_abs\"].min()\noff_max_x = offense_df[\"x_from_scrimmage_abs\"].max()\n\nprint(def_min_x, def_max_x)\nprint(off_min_x, off_max_x)","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:47.190165Z","iopub.execute_input":"2022-01-14T14:19:47.190504Z","iopub.status.idle":"2022-01-14T14:19:47.201376Z","shell.execute_reply.started":"2022-01-14T14:19:47.190472Z","shell.execute_reply":"2022-01-14T14:19:47.199981Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"x_dim = 100 // 2 \ny_dim = 53 // 2\n\ndefense_x_range = np.linspace(def_min_x, def_max_x, int(x_dim))\noffense_x_range = np.linspace(off_min_x, off_max_x, int(x_dim))\n\ny_range = np.linspace(df[\"y_from_ball\"].min(), df[\"y_from_ball\"].max(), y_dim)\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:47.204022Z","iopub.execute_input":"2022-01-14T14:19:47.204420Z","iopub.status.idle":"2022-01-14T14:19:47.218699Z","shell.execute_reply.started":"2022-01-14T14:19:47.204370Z","shell.execute_reply":"2022-01-14T14:19:47.217150Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_closest(array, values):\n    # make sure array is a numpy array\n    array = np.array(array)\n\n    # get insert positions\n    idxs = np.searchsorted(array, values, side=\"left\")\n\n    # find indexes where previous index is closer\n    prev_idx_is_less = (idxs == len(array)) | (\n        np.fabs(values - array[np.maximum(idxs - 1, 0)])\n        < np.fabs(values - array[np.minimum(idxs, len(array) - 1)])\n    )\n    idxs[prev_idx_is_less] -= 1\n\n    # this returns the closest value from linspace\n    #return array[idxs]\n\n    # this returns the index of the closest value from linspace, which is really all we need, I think\n    return idxs\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:47.220596Z","iopub.execute_input":"2022-01-14T14:19:47.220851Z","iopub.status.idle":"2022-01-14T14:19:47.230985Z","shell.execute_reply.started":"2022-01-14T14:19:47.220821Z","shell.execute_reply":"2022-01-14T14:19:47.229598Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"defense_df[\"x_transformed\"] = get_closest(\n    defense_x_range, defense_df[\"x_from_scrimmage_abs\"]\n)\ndefense_df[\"y_transformed\"] = get_closest(y_range, defense_df[\"y_norm\"])\n\noffense_df[\"x_transformed\"] = get_closest(\n    offense_x_range, offense_df[\"x_from_scrimmage_abs\"]\n)\noffense_df[\"y_transformed\"] = get_closest(y_range, offense_df[\"y_norm\"])\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:47.232601Z","iopub.execute_input":"2022-01-14T14:19:47.233466Z","iopub.status.idle":"2022-01-14T14:19:47.317156Z","shell.execute_reply.started":"2022-01-14T14:19:47.233357Z","shell.execute_reply":"2022-01-14T14:19:47.316352Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Populate an empty array (field) with players' norm x,y","metadata":{}},{"cell_type":"code","source":"# Set the field to be a grid\nempty_field = np.zeros((y_dim, x_dim))","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:47.319132Z","iopub.execute_input":"2022-01-14T14:19:47.319471Z","iopub.status.idle":"2022-01-14T14:19:47.324344Z","shell.execute_reply.started":"2022-01-14T14:19:47.319434Z","shell.execute_reply":"2022-01-14T14:19:47.323428Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def populate_field(gbdf, binary=False):\n    \"\"\"returns an array (field) with the dimensions (x_dim, y_dim)\n    populated with players locations at each x,y coordinate\n\n    Args:\n        gbdf pandas DataFrame: a groupby dataframe of the play dataframe\n        binary bool: if True, will return a binary field with 1s where players are, else sum up the number of players\n    \"\"\"\n    # remove the football from the field\n    gbdf = gbdf.loc[gbdf[\"team\"] != \"football\"]\n    # copy the empty field to populate it\n    populated_field = empty_field.copy()\n    x_idxs = gbdf[\"x_transformed\"].values\n    y_idxs = gbdf[\"y_transformed\"].values\n    if binary is True:\n        populated_field[y_idxs, x_idxs] = 1\n    else:\n        # results are accumulated for results w/ more than one index :mind_blown:\n        np.add.at(populated_field, (y_idxs, x_idxs), 1)\n\n    # assert populated_field.sum() == 11\n    # if populated_field.sum() != 11:\n    #     print(populated_field.sum())\n    return populated_field.flatten()","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:47.325942Z","iopub.execute_input":"2022-01-14T14:19:47.326782Z","iopub.status.idle":"2022-01-14T14:19:47.339507Z","shell.execute_reply.started":"2022-01-14T14:19:47.326687Z","shell.execute_reply":"2022-01-14T14:19:47.338260Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# boolean field\ndefense_sparse = defense_df.loc[defense_df[\"team\"] != \"football\"].groupby([\"gameId\", \"playId\"]).apply(populate_field, binary=True)\noffense_sparse = offense_df.loc[offense_df[\"team\"] != \"football\"].groupby([\"gameId\", \"playId\"]).apply(populate_field, binary=True)","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:19:47.341213Z","iopub.execute_input":"2022-01-14T14:19:47.341792Z","iopub.status.idle":"2022-01-14T14:20:20.201753Z","shell.execute_reply.started":"2022-01-14T14:19:47.341753Z","shell.execute_reply":"2022-01-14T14:20:20.199843Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# How lossy was the transformation to subspace?\n# Of ~20,000 plays, how many mapped all 11 players uniquely?\n\n# this was from .sum()\ndefense_sparse.apply(lambda row: row.sum()).value_counts()","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:20:20.204340Z","iopub.execute_input":"2022-01-14T14:20:20.204727Z","iopub.status.idle":"2022-01-14T14:20:20.368188Z","shell.execute_reply.started":"2022-01-14T14:20:20.204684Z","shell.execute_reply":"2022-01-14T14:20:20.366922Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# How lossy was the transformation to subspace?\n# Of ~20,000 plays, how many mapped all 11 players uniquely?\noffense_sparse.apply(lambda row: row.sum()).value_counts()","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:20:20.370278Z","iopub.execute_input":"2022-01-14T14:20:20.370626Z","iopub.status.idle":"2022-01-14T14:20:20.506866Z","shell.execute_reply.started":"2022-01-14T14:20:20.370590Z","shell.execute_reply":"2022-01-14T14:20:20.505736Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# field where the counts represent the number of players at each x,y coordinate\ndefense_sparse = defense_df.loc[defense_df[\"team\"] != \"football\"].groupby([\"gameId\", \"playId\", \"specialTeamsPlayType\", \"has_possession\"]).apply(populate_field, binary=False)\noffense_sparse = offense_df.loc[offense_df[\"team\"] != \"football\"].groupby([\"gameId\", \"playId\", \"specialTeamsPlayType\", \"has_possession\"]).apply(populate_field, binary=False)","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:20:20.508573Z","iopub.execute_input":"2022-01-14T14:20:20.509105Z","iopub.status.idle":"2022-01-14T14:20:51.668551Z","shell.execute_reply.started":"2022-01-14T14:20:20.509064Z","shell.execute_reply":"2022-01-14T14:20:51.667543Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# better be all 11\ndefense_sparse.apply(lambda row: row.sum()).value_counts()","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:20:51.670027Z","iopub.execute_input":"2022-01-14T14:20:51.670301Z","iopub.status.idle":"2022-01-14T14:20:51.800339Z","shell.execute_reply.started":"2022-01-14T14:20:51.670269Z","shell.execute_reply":"2022-01-14T14:20:51.799011Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# better be all 11 \noffense_sparse.apply(lambda row: row.sum()).value_counts()","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:20:51.802262Z","iopub.execute_input":"2022-01-14T14:20:51.802630Z","iopub.status.idle":"2022-01-14T14:20:51.932345Z","shell.execute_reply.started":"2022-01-14T14:20:51.802588Z","shell.execute_reply":"2022-01-14T14:20:51.931416Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"defense_play_arrays = defense_sparse.copy()\noffense_play_arrays = offense_sparse.copy()","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:20:51.933743Z","iopub.execute_input":"2022-01-14T14:20:51.934553Z","iopub.status.idle":"2022-01-14T14:20:51.941324Z","shell.execute_reply.started":"2022-01-14T14:20:51.934488Z","shell.execute_reply":"2022-01-14T14:20:51.940111Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# name the pandas series so we can merge them\ndefense_play_arrays.name = \"normalized_player_locations\"\noffense_play_arrays.name = \"normalized_player_locations\"","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:20:51.942746Z","iopub.execute_input":"2022-01-14T14:20:51.942994Z","iopub.status.idle":"2022-01-14T14:20:51.958195Z","shell.execute_reply.started":"2022-01-14T14:20:51.942965Z","shell.execute_reply":"2022-01-14T14:20:51.956611Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"defense_play_arrays = defense_play_arrays.to_frame()\noffense_play_arrays = offense_play_arrays.to_frame()","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:20:51.959994Z","iopub.execute_input":"2022-01-14T14:20:51.960265Z","iopub.status.idle":"2022-01-14T14:20:51.975342Z","shell.execute_reply.started":"2022-01-14T14:20:51.960237Z","shell.execute_reply":"2022-01-14T14:20:51.974349Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Reduce the dimensionality of the populated field and cluster the result","metadata":{}},{"cell_type":"code","source":"defense_players_df = pd.DataFrame(\n    defense_play_arrays[\"normalized_player_locations\"].tolist(),\n    index=defense_play_arrays.index,\n)\noffense_players_df = pd.DataFrame(\n    offense_play_arrays[\"normalized_player_locations\"].tolist(),\n    index=offense_play_arrays.index,\n)\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:20:51.976558Z","iopub.execute_input":"2022-01-14T14:20:51.976832Z","iopub.status.idle":"2022-01-14T14:21:45.743587Z","shell.execute_reply.started":"2022-01-14T14:20:51.976802Z","shell.execute_reply":"2022-01-14T14:21:45.742890Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# PUNT\ndefense_punt = defense_players_df.loc[\n    defense_players_df.index.get_level_values(\"specialTeamsPlayType\") == \"Punt\"\n]\noffense_punt = offense_players_df.loc[\n    offense_players_df.index.get_level_values(\"specialTeamsPlayType\") == \"Punt\"\n]\n\n# KICKOFF\ndefense_kickoff = defense_players_df.loc[\n    defense_players_df.index.get_level_values(\"specialTeamsPlayType\")\n    == \"Kickoff\"\n]\noffense_kickoff = offense_players_df.loc[\n    offense_players_df.index.get_level_values(\"specialTeamsPlayType\")\n    == \"Kickoff\"\n]\n\n# EXTRA POINT\ndefense_extra_point = defense_players_df.loc[\n    defense_players_df.index.get_level_values(\"specialTeamsPlayType\")\n    == \"Extra Point\"\n]\noffense_extra_point = offense_players_df.loc[\n    offense_players_df.index.get_level_values(\"specialTeamsPlayType\")\n    == \"Extra Point\"\n]\n\n# FIELD GOAL\ndefense_field_goal = defense_players_df.loc[\n    defense_players_df.index.get_level_values(\"specialTeamsPlayType\")\n    == \"Field Goal\"\n]\noffense_field_goal = offense_players_df.loc[\n    offense_players_df.index.get_level_values(\"specialTeamsPlayType\")\n    == \"Field Goal\"\n]\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:21:45.745077Z","iopub.execute_input":"2022-01-14T14:21:45.745556Z","iopub.status.idle":"2022-01-14T14:21:46.075146Z","shell.execute_reply.started":"2022-01-14T14:21:45.745521Z","shell.execute_reply":"2022-01-14T14:21:46.074055Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def umapify(\n    play_type_df,\n    umap__n_neighbors=133,\n    hdbscan__min_cluster_size=50,\n    hdbscan__min_samples=15,\n):\n    \"\"\"Apply UMAP and HDBSCAN to a DataFrame of player locations\n\n    Args:\n        play_type_df (DataFrame): A DataFrame of normalized player locations\n\n    Returns:\n        pandas DataFrame: A DataFrame of player locations in UMAP space\n    \"\"\"\n\n    umap = UMAP(\n        n_neighbors=umap__n_neighbors,\n        min_dist=0,\n        metric=\"euclidean\",\n        n_components=2,\n    )\n\n    umapdf = umap.fit_transform(play_type_df)\n    umapdf = pd.DataFrame(umapdf, index=play_type_df.index)\n    umapdf.columns = [\"component_1\", \"component_2\"]\n\n\n    umapdf = umapdf.merge(\n        # note this is the original df, which has play type\n        # keeps only one record per play, which is fine for play-level data\n        df.drop_duplicates(subset=[\"gameId\", \"playId\"]).set_index(\n            [\"gameId\", \"playId\", \"specialTeamsPlayType\", \"has_possession\"]\n        )[\n            [\"specialTeamsResult\"]\n            + scouting.drop(columns=[\"gameId\", \"playId\"]).columns.tolist()\n        ],\n        left_index=True,\n        right_index=True,\n        how=\"left\",\n    )\n\n    labels = hdbscan.HDBSCAN(\n        min_samples=hdbscan__min_samples,\n        min_cluster_size=hdbscan__min_cluster_size,\n    ).fit_predict(umapdf[[\"component_1\", \"component_2\"]])\n\n    umapdf[\"cluster\"] = labels\n\n    return umapdf\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:21:46.083066Z","iopub.execute_input":"2022-01-14T14:21:46.083635Z","iopub.status.idle":"2022-01-14T14:21:46.095889Z","shell.execute_reply.started":"2022-01-14T14:21:46.083593Z","shell.execute_reply":"2022-01-14T14:21:46.094204Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"defense_punt_umap = umapify(\n    defense_punt,\n    umap__n_neighbors=55,\n    hdbscan__min_cluster_size=15,\n    hdbscan__min_samples=5,\n)\noffense_punt_umap = umapify(\n    offense_punt,\n    umap__n_neighbors=75,\n    hdbscan__min_cluster_size=35,\n    hdbscan__min_samples=15,\n)\n\ndefense_kickoff_umap = umapify(\n    defense_kickoff,\n    umap__n_neighbors=75,\n    hdbscan__min_cluster_size=75,\n    hdbscan__min_samples=15,\n)\noffense_kickoff_umap = umapify(\n    offense_kickoff,\n    umap__n_neighbors=75,\n    hdbscan__min_cluster_size=75,\n    hdbscan__min_samples=15,\n)\n\ndefense_extra_point_umap = umapify(\n    defense_extra_point,\n    umap__n_neighbors=500,\n    hdbscan__min_cluster_size=50,\n    hdbscan__min_samples=15,\n)\noffense_extra_point_umap = umapify(\n    offense_extra_point,\n    umap__n_neighbors=75,\n    hdbscan__min_cluster_size=50,\n    hdbscan__min_samples=15,\n)\n\ndefense_field_goal_umap = umapify(\n    defense_field_goal,\n    umap__n_neighbors=150,\n    hdbscan__min_cluster_size=50,\n    hdbscan__min_samples=15,\n)\noffense_field_goal_umap = umapify(\n    offense_field_goal,\n    umap__n_neighbors=75,\n    hdbscan__min_cluster_size=50,\n    hdbscan__min_samples=15,\n)\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:21:46.097877Z","iopub.execute_input":"2022-01-14T14:21:46.098201Z","iopub.status.idle":"2022-01-14T14:25:57.428503Z","shell.execute_reply.started":"2022-01-14T14:21:46.098169Z","shell.execute_reply":"2022-01-14T14:25:57.427092Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def plot_clusters(umapdf):\n    fig, ax = plt.subplots(figsize=(8, 8))\n    for cluster in sorted(umapdf.cluster.unique()):\n        ax.scatter(\n            umapdf.loc[umapdf.cluster == cluster, \"component_1\"],\n            umapdf.loc[umapdf.cluster == cluster, \"component_2\"],\n            s=5,\n            alpha=0.5,\n            label=cluster,\n            color=sns.color_palette(\"tab20\")[cluster],\n        )\n    # ax.grid();\n    ax.legend();\n\n    if umapdf.index.get_level_values(\"has_possession\").unique()[0]:\n        side = \"Offense\"\n    else:\n        side = \"Defense\"\n    n_plays = umapdf.shape[0]\n    play_type = umapdf.index.get_level_values(\"specialTeamsPlayType\").unique()[0]\n    ax.set_title(f\"Clusters of {n_plays} {side} {play_type} Plays\");","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:25:57.430494Z","iopub.execute_input":"2022-01-14T14:25:57.430914Z","iopub.status.idle":"2022-01-14T14:25:57.443342Z","shell.execute_reply.started":"2022-01-14T14:25:57.430865Z","shell.execute_reply":"2022-01-14T14:25:57.441756Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for play_type_umap in [\n    defense_punt_umap,\n    offense_punt_umap,\n    defense_kickoff_umap,\n    offense_kickoff_umap,\n    defense_extra_point_umap,\n    offense_extra_point_umap,\n    defense_field_goal_umap,\n    offense_field_goal_umap,\n]:  \n    plot_clusters(play_type_umap)\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:25:57.445451Z","iopub.execute_input":"2022-01-14T14:25:57.445837Z","iopub.status.idle":"2022-01-14T14:26:01.818916Z","shell.execute_reply.started":"2022-01-14T14:25:57.445787Z","shell.execute_reply":"2022-01-14T14:26:01.817640Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Visualize the mean formation in the subspace (normed field)","metadata":{}},{"cell_type":"code","source":"play_type_dfs = [\n    defense_punt,\n    offense_punt,\n    defense_kickoff,\n    offense_kickoff,\n    defense_extra_point,\n    offense_extra_point,\n    defense_field_goal,\n    offense_field_goal,\n]\nplay_type_df_umaps = [\n    defense_punt_umap,\n    offense_punt_umap,\n    defense_kickoff_umap,\n    offense_kickoff_umap,\n    defense_extra_point_umap,\n    offense_extra_point_umap,\n    defense_field_goal_umap,\n    offense_field_goal_umap,\n]\n\nfor play_type_df, play_type_df_umap in zip(play_type_dfs, play_type_df_umaps):\n    has_possession = play_type_df.index.get_level_values(\"has_possession\").unique()[0]\n    play_type = play_type_df.index.get_level_values(\"specialTeamsPlayType\").unique()[0]\n    if has_possession:\n        side = \"Offense\"\n    else:\n        side = \"Defense\"\n\n    clusters = play_type_df.merge(\n    play_type_df_umap[\"cluster\"], left_index=True, right_index=True\n    )\n    clusters_reshaped = clusters.groupby(\"cluster\").apply(\n        lambda row: row[range(1300)].mean()\n    )\n    clusters_reshaped = clusters_reshaped[\n        range(1300)\n    ].apply(lambda row: row.values.reshape(y_dim, x_dim), axis=1)\n\n    for cluster, cluster_array in clusters_reshaped.iteritems():\n        fig, ax = plt.subplots(figsize=(10, 4))\n        ax.set_title(f\"{play_type} {side} Cluster {cluster} ({clusters['cluster'].value_counts()[cluster]} Plays)\")\n        sns.heatmap(cluster_array, ax=ax)","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:26:01.821184Z","iopub.execute_input":"2022-01-14T14:26:01.822166Z","iopub.status.idle":"2022-01-14T14:26:27.344577Z","shell.execute_reply.started":"2022-01-14T14:26:01.822105Z","shell.execute_reply":"2022-01-14T14:26:27.343229Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Visualize a few plays in each cluster","metadata":{}},{"cell_type":"code","source":"# from svgpathtools import svg2paths\n# from svgpath2mpl import parse_path\n# football, football_attributes = svg2paths(\"american-football.svg\")\n# football_marker = parse_path(football_attributes[0][\"d\"])\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:26:27.346832Z","iopub.execute_input":"2022-01-14T14:26:27.347117Z","iopub.status.idle":"2022-01-14T14:26:27.351965Z","shell.execute_reply.started":"2022-01-14T14:26:27.347081Z","shell.execute_reply":"2022-01-14T14:26:27.350590Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def plot_play(gameid, playid, normdf):\n    play = normdf.loc[(normdf[\"gameId\"] == gameid) & (normdf[\"playId\"] == playid)]\n\n    fig = plt.figure(figsize=(12, 5))\n    ax = plt.axes(xlim=(0, 120), ylim=(0, 53))\n\n\n    football = play.loc[play[\"team\"] == \"football\"]\n\n    ax.scatter(play[\"x_from_scrimmage_abs\"], play[\"y_norm\"], color=\"#fb8500\", s=150)\n    ax.scatter(\n        football[\"x_from_scrimmage_abs\"],\n        football[\"y_norm\"],\n        marker='^',\n        color=\"brown\",\n        s=250,\n    )\n\n    ax.set_yticks([])\n    ax.set_xticks([30, 60, 90])\n    ax.set_xticklabels([\"20\", \"50\", \"20\"])\n    ax.vlines(10, 0, 53.3, color=\"black\", linestyle=\"-\", alpha=0.5)\n    ax.vlines(60, 0, 53.3, color=\"black\", linestyle=\"-\", alpha=0.5)\n    ax.vlines(110, 0, 53.3, color=\"black\", linestyle=\"-\", alpha=0.5)\n\n\n    ","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:26:27.353463Z","iopub.execute_input":"2022-01-14T14:26:27.353748Z","iopub.status.idle":"2022-01-14T14:26:27.367933Z","shell.execute_reply.started":"2022-01-14T14:26:27.353706Z","shell.execute_reply":"2022-01-14T14:26:27.367067Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for cluster in sorted(offense_extra_point_umap.cluster.unique()):\n    print(f\"Cluster {cluster}\")\n    cluster_plays = offense_extra_point_umap.loc[offense_extra_point_umap[\"cluster\"] == cluster].reset_index()\n    gameids = cluster_plays[\"gameId\"]\n    playids = cluster_plays[\"playId\"]\n    \n    for i, (gameid, playid) in enumerate(zip(gameids, playids)):\n        if i == 5:\n            break\n        plot_play(gameid, playid, defense_df)\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:26:27.369336Z","iopub.execute_input":"2022-01-14T14:26:27.369821Z","iopub.status.idle":"2022-01-14T14:26:29.054554Z","shell.execute_reply.started":"2022-01-14T14:26:27.369786Z","shell.execute_reply":"2022-01-14T14:26:29.053412Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Insights from clusters?","metadata":{}},{"cell_type":"markdown","source":"## Kickoff -- formation dectection comparison offense","metadata":{}},{"cell_type":"code","source":"fig, ax = plt.subplots(figsize=(8, 8))\n\nax.scatter(\n    offense_kickoff_umap.loc[\n        offense_kickoff_umap[\"kickoffReturnFormation\"] == \"8-0-2\", \"component_1\"\n    ],\n    offense_kickoff_umap.loc[\n        offense_kickoff_umap[\"kickoffReturnFormation\"] == \"8-0-2\", \"component_2\"\n    ],\n    s=5,\n    alpha=0.5,\n    color=\"lightgrey\",\n    label=\"8-0-2\",\n)\nax.scatter(\n    offense_kickoff_umap.loc[\n        offense_kickoff_umap[\"kickoffReturnFormation\"] == \"8-0-1\", \"component_1\"\n    ],\n    offense_kickoff_umap.loc[\n        offense_kickoff_umap[\"kickoffReturnFormation\"] == \"8-0-1\", \"component_2\"\n    ],\n    s=5,\n    alpha=0.5,\n    label=\"8-0-1\",\n)\n\n# Hide the right and top spines\nax.spines[\"right\"].set_visible(False)\nax.spines[\"top\"].set_visible(False)\nax.legend();\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:26:29.056275Z","iopub.execute_input":"2022-01-14T14:26:29.056559Z","iopub.status.idle":"2022-01-14T14:26:29.464590Z","shell.execute_reply.started":"2022-01-14T14:26:29.056526Z","shell.execute_reply":"2022-01-14T14:26:29.463821Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Kickoff - kickType defense","metadata":{}},{"cell_type":"code","source":"defense_kickoff_umap[\"kickType\"].value_counts()","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:26:29.466167Z","iopub.execute_input":"2022-01-14T14:26:29.466680Z","iopub.status.idle":"2022-01-14T14:26:29.478967Z","shell.execute_reply.started":"2022-01-14T14:26:29.466622Z","shell.execute_reply":"2022-01-14T14:26:29.477530Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig, ax = plt.subplots(figsize=(8, 8))\n\nax.scatter(\n    defense_kickoff_umap.loc[\n        defense_kickoff_umap[\"kickType\"] == \"D\", \"component_1\"\n    ],\n    defense_kickoff_umap.loc[\n        defense_kickoff_umap[\"kickType\"] == \"D\", \"component_2\"\n    ],\n    s=5,\n    alpha=0.5,\n    color=\"lightgrey\",\n    label=\"Deep\",\n)\n\nax.scatter(\n    defense_kickoff_umap.loc[\n        defense_kickoff_umap[\"kickType\"] == \"O\", \"component_1\"\n    ],\n    defense_kickoff_umap.loc[\n        defense_kickoff_umap[\"kickType\"] == \"O\", \"component_2\"\n    ],\n    s=5,\n    alpha=0.5,\n    label=\"Obvious onside\",\n)\n\n\n# Hide the right and top spines\nax.spines[\"right\"].set_visible(False)\nax.spines[\"top\"].set_visible(False)\nax.legend();\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:26:29.481021Z","iopub.execute_input":"2022-01-14T14:26:29.481856Z","iopub.status.idle":"2022-01-14T14:26:29.818179Z","shell.execute_reply.started":"2022-01-14T14:26:29.481803Z","shell.execute_reply":"2022-01-14T14:26:29.816966Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig, ax = plt.subplots(figsize=(8, 8))\n\nax.scatter(\n    defense_kickoff_umap.loc[\n        defense_kickoff_umap[\"kickType\"] == \"D\", \"component_1\"\n    ],\n    defense_kickoff_umap.loc[\n        defense_kickoff_umap[\"kickType\"] == \"D\", \"component_2\"\n    ],\n    s=5,\n    alpha=0.5,\n    color=\"lightgrey\",\n    label=\"Deep\",\n)\n\nax.scatter(\n    defense_kickoff_umap.loc[\n        defense_kickoff_umap[\"kickType\"] == \"F\", \"component_1\"\n    ],\n    defense_kickoff_umap.loc[\n        defense_kickoff_umap[\"kickType\"] == \"F\", \"component_2\"\n    ],\n    s=5,\n    alpha=0.5,\n    label=\"Flat\",\n)\n\nax.scatter(\n    defense_kickoff_umap.loc[\n        defense_kickoff_umap[\"kickType\"] == \"O\", \"component_1\"\n    ],\n    defense_kickoff_umap.loc[\n        defense_kickoff_umap[\"kickType\"] == \"O\", \"component_2\"\n    ],\n    s=5,\n    alpha=0.5,\n    label=\"Obvious onside\",\n)\n\nax.scatter(\n    defense_kickoff_umap.loc[\n        defense_kickoff_umap[\"kickType\"] == \"P\", \"component_1\"\n    ],\n    defense_kickoff_umap.loc[\n        defense_kickoff_umap[\"kickType\"] == \"P\", \"component_2\"\n    ],\n    s=5,\n    alpha=0.5,\n    label=\"Pooch\",\n)\n\nax.scatter(\n    defense_kickoff_umap.loc[\n        defense_kickoff_umap[\"kickType\"] == \"Q\", \"component_1\"\n    ],\n    defense_kickoff_umap.loc[\n        defense_kickoff_umap[\"kickType\"] == \"Q\", \"component_2\"\n    ],\n    s=5,\n    alpha=0.5,\n    label=\"Squib\",\n)\n\nax.scatter(\n    defense_kickoff_umap.loc[\n        defense_kickoff_umap[\"kickType\"] == \"K\", \"component_1\"\n    ],\n    defense_kickoff_umap.loc[\n        defense_kickoff_umap[\"kickType\"] == \"K\", \"component_2\"\n    ],\n    s=5,\n    alpha=0.5,\n    label=\"Free Kick\",\n)\n\n# Hide the right and top spines\nax.spines[\"right\"].set_visible(False)\nax.spines[\"top\"].set_visible(False)\nax.legend();\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:26:29.820286Z","iopub.execute_input":"2022-01-14T14:26:29.820620Z","iopub.status.idle":"2022-01-14T14:26:30.347608Z","shell.execute_reply.started":"2022-01-14T14:26:29.820575Z","shell.execute_reply":"2022-01-14T14:26:30.346368Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Punt -- Blocked punts defense","metadata":{}},{"cell_type":"code","source":"defense_punt_umap[\"specialTeamsResult\"].value_counts()","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:26:30.349031Z","iopub.execute_input":"2022-01-14T14:26:30.349302Z","iopub.status.idle":"2022-01-14T14:26:30.362853Z","shell.execute_reply.started":"2022-01-14T14:26:30.349269Z","shell.execute_reply":"2022-01-14T14:26:30.361708Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig, ax = plt.subplots(figsize=(8, 8))\n\nresult_type = \"Blocked Punt\"\n\nax.scatter(\n    defense_punt_umap.loc[\n        defense_punt_umap[\"specialTeamsResult\"] != result_type, \"component_1\"\n    ],\n    defense_punt_umap.loc[\n        defense_punt_umap[\"specialTeamsResult\"] != result_type, \"component_2\"\n    ],\n    s=5,\n    alpha=0.5,\n    color=\"lightgrey\",\n    label=\"All punt results (not blocked)\",\n)\nax.scatter(\n    defense_punt_umap.loc[\n        defense_punt_umap[\"specialTeamsResult\"] == result_type, \"component_1\"\n    ],\n    defense_punt_umap.loc[\n        defense_punt_umap[\"specialTeamsResult\"] == result_type, \"component_2\"\n    ],\n    s=10,\n    label=\"Blocked Punt\",\n)\n\n# Hide the right and top spines\nax.spines[\"right\"].set_visible(False)\nax.spines[\"top\"].set_visible(False)\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:26:30.364429Z","iopub.execute_input":"2022-01-14T14:26:30.364744Z","iopub.status.idle":"2022-01-14T14:26:30.654969Z","shell.execute_reply.started":"2022-01-14T14:26:30.364698Z","shell.execute_reply":"2022-01-14T14:26:30.653987Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Punt -- Blocked punts offense","metadata":{}},{"cell_type":"code","source":"fig, ax = plt.subplots(figsize=(8, 8))\n\nresult_type = \"Blocked Punt\"\n\nax.scatter(\n    offense_punt_umap.loc[\n        offense_punt_umap[\"specialTeamsResult\"] != result_type, \"component_1\"\n    ],\n    offense_punt_umap.loc[\n        offense_punt_umap[\"specialTeamsResult\"] != result_type, \"component_2\"\n    ],\n    s=5,\n    alpha=0.5,\n    color=\"lightgrey\",\n    label=\"All punt results (not blocked)\",\n)\nax.scatter(\n    offense_punt_umap.loc[\n        offense_punt_umap[\"specialTeamsResult\"] == result_type, \"component_1\"\n    ],\n    offense_punt_umap.loc[\n        offense_punt_umap[\"specialTeamsResult\"] == result_type, \"component_2\"\n    ],\n    s=10,\n    label=\"Blocked Punt\",\n)\n\n# Hide the right and top spines\nax.spines[\"right\"].set_visible(False)\nax.spines[\"top\"].set_visible(False)\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:26:30.656656Z","iopub.execute_input":"2022-01-14T14:26:30.656947Z","iopub.status.idle":"2022-01-14T14:26:30.919270Z","shell.execute_reply.started":"2022-01-14T14:26:30.656913Z","shell.execute_reply":"2022-01-14T14:26:30.918362Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## kickType for punts","metadata":{}},{"cell_type":"code","source":"fig, ax = plt.subplots(figsize=(8, 8))\n\n# normal style\nax.scatter(\n    defense_punt_umap.loc[\n        defense_punt_umap[\"kickType\"] == \"N\", \"component_1\"\n    ],\n    defense_punt_umap.loc[\n        defense_punt_umap[\"kickType\"] == \"N\", \"component_2\"\n    ],\n    s=5,\n    alpha=0.5,\n    label=\"Normal\",\n    color=\"lightgrey\",\n)\n# aussie style\nax.scatter(\n    defense_punt_umap.loc[\n        defense_punt_umap[\"kickType\"] == \"A\", \"component_1\"\n    ],\n    defense_punt_umap.loc[\n        defense_punt_umap[\"kickType\"] == \"A\", \"component_2\"\n    ],\n    s=5,\n    alpha=0.5,\n    label=\"Aussie Style\"\n)\nax.scatter(\n    defense_punt_umap.loc[\n        defense_punt_umap[\"kickType\"] == \"R\", \"component_1\"\n    ],\n    defense_punt_umap.loc[\n        defense_punt_umap[\"kickType\"] == \"R\", \"component_2\"\n    ],\n    s=5,\n    alpha=0.5,\n    label=\"Rugby\"\n)\n\n# Hide the right and top spines\nax.spines[\"right\"].set_visible(False)\nax.spines[\"top\"].set_visible(False)\nax.legend();\n","metadata":{"execution":{"iopub.status.busy":"2022-01-14T14:26:30.920779Z","iopub.execute_input":"2022-01-14T14:26:30.921749Z","iopub.status.idle":"2022-01-14T14:26:31.332414Z","shell.execute_reply.started":"2022-01-14T14:26:30.921622Z","shell.execute_reply":"2022-01-14T14:26:31.331185Z"},"trusted":true},"execution_count":null,"outputs":[]}]}