{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": [
    "# PWHL\n",
    "\n",
    "Nine charts and tables from the Professional Women's Hockey League's 2025-26 season, its third: standings, scoring\n",
    "leaders with their photos, a results grid, three seasons of history, shot quality and a shot map of the game that\n",
    "decided the Walter Cup, plus the PHF, the league before it. The data comes from the PWHL's HockeyTech feed (live) and\n",
    "the fastRhockey release, both through [sportsdataverse-py](https://py.sportsdataverse.org/)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1",
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import polars as pl\n",
    "import sportsdataverse.pwhl as pwhl\n",
    "\n",
    "import sdvplot\n",
    "\n",
    "SEASON = 2026  # the 2025-26 season, named by the year it ends"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2",
   "metadata": {},
   "source": [
    "## 1. Team identity: ids resolve, codes do not\n",
    "\n",
    "sdvplot knows PWHL teams by their HockeyTech team id and by name. The three-letter codes in HockeyTech's tables are\n",
    "not in the index, so they warn instead of guessing. Join to the league's team list to pick up the id."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3",
   "metadata": {},
   "outputs": [],
   "source": [
    "teams = pwhl.pwhl_teams(season=SEASON).select(\"team_id\", \"team_code\", \"team_name\")\n",
    "with warnings.catch_warnings(record=True) as caught:\n",
    "    warnings.simplefilter(\"always\")\n",
    "    by_code = sdvplot.resolve(teams[\"team_code\"], \"pwhl\")\n",
    "print(caught[0].message)\n",
    "sdvplot.resolve(teams[\"team_id\"], \"pwhl\").to_list()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4",
   "metadata": {},
   "source": [
    "The team list already holds the four clubs that join for 2026-27, and every club has a logo. Colors are another\n",
    "matter: the index has no official PWHL colors yet (`color_source` is `\"fallback\"`), so the charts below lean on logos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5",
   "metadata": {},
   "outputs": [],
   "source": [
    "sdvplot.teams(\"pwhl\").group_by(\"color_source\", maintain_order=True).len()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6",
   "metadata": {},
   "outputs": [],
   "source": [
    "schedule = pwhl.load_pwhl_schedule(seasons=[SEASON])\n",
    "played = set(schedule[\"home_team_id\"])  # the clubs with a 2025-26 home game\n",
    "clubs = pwhl.pwhl_teams(season=SEASON).with_columns(new=~pl.col(\"team_id\").is_in(played)).sort(\"new\", \"team_name\")\n",
    "fig, axes = plt.subplots(2, 8, figsize=(10, 3.4))\n",
    "for ax, club in zip(axes.flat, clubs.iter_rows(named=True), strict=False):\n",
    "    ax.imshow(sdvplot.logo_image(club[\"team_id\"], \"pwhl\", size=200))\n",
    "    nickname = \"\" if club[\"team_nickname\"] == \"PWHL\" else club[\"team_nickname\"]  # the new clubs have no name yet\n",
    "    ax.set_title(f\"{club['team_label']}\\n{nickname}\", fontsize=8)\n",
    "for ax in axes.flat:\n",
    "    ax.axis(\"off\")\n",
    "fig.suptitle(\"The PWHL's eight 2025-26 clubs (top) and four 2026-27 additions\", fontweight=\"bold\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7",
   "metadata": {},
   "source": [
    "## 2. Final standings\n",
    "\n",
    "The PWHL awards three points for a regulation win, two for an overtime or shootout win and one for an overtime or\n",
    "shootout loss. HockeyTech prefixes clinched teams with `x -` and eliminated teams with `e -`; split that into its own\n",
    "column, then join on the code to get the id `gt_sdv_logos` resolves."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8",
   "metadata": {},
   "outputs": [],
   "source": [
    "from great_tables import GT\n",
    "\n",
    "from sdvplot.great_tables import gt_sdv_logos, gt_theme_sdv\n",
    "\n",
    "raw_standings = pwhl.pwhl_standings(season=SEASON)\n",
    "standings = (\n",
    "    raw_standings.with_columns(\n",
    "        status=pl.col(\"team_code\").str.extract(r\"^(\\w+) - \").replace({\"x\": \"Playoffs\", \"e\": \"Out\"}),\n",
    "        team_code=pl.col(\"team_code\").str.replace(r\"^\\w+ - \", \"\"),\n",
    "    )\n",
    "    .join(teams, on=\"team_code\")\n",
    "    .select(\n",
    "        \"team_id\",\n",
    "        \"team_name\",\n",
    "        pl.col(\"games_played\").cast(pl.Int64).alias(\"gp\"),\n",
    "        pl.col(\"regulation_wins\").cast(pl.Int64).alias(\"rw\"),\n",
    "        pl.col(\"non_reg_wins\").cast(pl.Int64).alias(\"otw\"),\n",
    "        pl.col(\"non_reg_losses\").cast(pl.Int64).alias(\"otl\"),\n",
    "        pl.col(\"losses\").cast(pl.Int64).alias(\"l\"),\n",
    "        \"points\",\n",
    "        pl.col(\"goals_for\").cast(pl.Int64).alias(\"gf\"),\n",
    "        pl.col(\"goals_against\").cast(pl.Int64).alias(\"ga\"),\n",
    "        \"status\",\n",
    "    )\n",
    "    .sort(\"points\", \"gf\", descending=True)\n",
    ")\n",
    "gt = (\n",
    "    GT(standings.rename({\"team_id\": \"logo\"}))\n",
    "    .tab_header(\"PWHL standings, 2025-26\", \"Final regular season: 30 games, 3-2-1-0 points\")\n",
    "    .cols_label(\n",
    "        logo=\"\",\n",
    "        team_name=\"Team\",\n",
    "        gp=\"GP\",\n",
    "        rw=\"RW\",\n",
    "        otw=\"OTW\",\n",
    "        otl=\"OTL\",\n",
    "        l=\"L\",\n",
    "        points=\"PTS\",\n",
    "        gf=\"GF\",\n",
    "        ga=\"GA\",\n",
    "        status=\"\",\n",
    "    )\n",
    "    .tab_source_note(\"Data: PWHL HockeyTech feed via sportsdataverse-py\")\n",
    ")\n",
    "gt_theme_sdv(gt_sdv_logos(gt, \"logo\", league=\"pwhl\", height=26))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9",
   "metadata": {},
   "source": [
    "## 3. Goals for and against, as a dumbbell\n",
    "\n",
    "Each team's line runs from goals allowed (open circle) to goals scored (filled); a filled dot on the right means a\n",
    "positive goal difference. `axis_logos` puts the logos on the y axis; the axis labels are team ids, which resolve."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10",
   "metadata": {},
   "outputs": [],
   "source": [
    "d = standings.sort(\"gf\")\n",
    "fig, ax = plt.subplots(figsize=(8, 5))\n",
    "ax.hlines(d[\"team_id\"], d[\"ga\"], d[\"gf\"], color=\"#bbbbbb\", lw=3, zorder=1)\n",
    "ax.scatter(d[\"ga\"], d[\"team_id\"], s=70, facecolor=\"white\", edgecolor=\"#444444\", zorder=2, label=\"Goals against\")\n",
    "ax.scatter(d[\"gf\"], d[\"team_id\"], s=70, color=\"#5b2c83\", zorder=3, label=\"Goals for\")\n",
    "sdvplot.axis_logos(ax, \"y\", league=\"pwhl\", height=0.09)\n",
    "ax.spines[[\"top\", \"right\", \"left\"]].set_visible(False)\n",
    "ax.legend(loc=\"lower right\", frameon=False)\n",
    "ax.set_xlabel(\"Goals, 30 games\")\n",
    "ax.set_title(\"Goals for and against, PWHL 2025-26\", loc=\"left\", fontweight=\"bold\")\n",
    "fig.text(0.99, 0.01, \"Data: PWHL HockeyTech feed via sportsdataverse-py\", ha=\"right\", fontsize=8, color=\"grey\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "11",
   "metadata": {},
   "source": [
    "Minnesota scored the most (91) but allowed 73; Montréal and Boston, level on 62 points, allowed the fewest."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12",
   "metadata": {},
   "source": [
    "## 4. A leaders card with player photos\n",
    "\n",
    "`add_headshots` covers ESPN leagues; for the PWHL use `add_images`, which draws any image by URL with the same sizing.\n",
    "HockeyTech's leaders feed has the top five in points and in goals, each with a photo URL."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "13",
   "metadata": {
    "sdvplot_gallery": {
     "alt": "The PWHL's top five in points and in goals for 2025-26, with player photos and team logos",
     "title": "PWHL leaders card with player photos"
    },
    "tags": [
     "gallery"
    ]
   },
   "outputs": [],
   "source": [
    "from sdvplot.matplotlib import add_images\n",
    "\n",
    "leaders = pwhl.pwhl_leaders(season=SEASON).with_columns(pl.col(\"stat_formatted\").cast(pl.Int64).alias(\"value\"))\n",
    "fig, axes = plt.subplots(1, 2, figsize=(10, 4.6), gridspec_kw={\"wspace\": 0.85})\n",
    "for ax, stat in zip(axes, (\"Points\", \"Goals\"), strict=True):\n",
    "    top = leaders.filter(pl.col(\"type_formatted\") == stat).sort(\"rank\", descending=True)\n",
    "    y = list(range(top.height))\n",
    "    ax.barh(y, top[\"value\"], color=\"#5b2c83\", height=0.62)\n",
    "    ax.set_yticks(y, top[\"name\"], fontsize=9)\n",
    "    ax.tick_params(axis=\"y\", length=0, pad=36)\n",
    "    ax.set_xlim(-top[\"value\"].max() * 0.2, top[\"value\"].max() * 1.3)\n",
    "    add_images(ax, [-top[\"value\"].max() * 0.1] * top.height, y, top[\"photo\"], height=0.17)\n",
    "    sdvplot.add_logos(ax, (top[\"value\"] * 1.13).to_list(), y, top[\"team_id\"], league=\"pwhl\", height=0.12)\n",
    "    for i, v in enumerate(top[\"value\"]):\n",
    "        ax.text(v - 0.4, i, str(v), va=\"center\", ha=\"right\", color=\"white\", fontweight=\"bold\")\n",
    "    ax.set_xticks([])\n",
    "    ax.spines[[\"top\", \"right\", \"left\", \"bottom\"]].set_visible(False)\n",
    "    ax.set_title(stat, loc=\"left\", fontweight=\"bold\")\n",
    "fig.suptitle(\"PWHL scoring leaders, 2025-26 regular season\", fontweight=\"bold\", x=0.02, ha=\"left\")\n",
    "fig.text(0.99, 0.01, \"Data: PWHL HockeyTech feed via sportsdataverse-py\", ha=\"right\", fontsize=8, color=\"grey\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "14",
   "metadata": {},
   "source": [
    "Minnesota's Kelly Pannek led both lists, with 33 points and 16 goals."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "15",
   "metadata": {},
   "source": [
    "## 5. A results grid with plotnine\n",
    "\n",
    "Every regular-season game as a tile, in date order, one row per team. The release schedule says which games went to\n",
    "overtime or a shootout (`Final OT`, `Final SO`); the play-by-play has each game's date. `axis_logos` swaps the team\n",
    "ids on the y axis for logos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16",
   "metadata": {},
   "outputs": [],
   "source": [
    "from plotnine import aes, element_blank, element_text, geom_tile, ggplot, labs, scale_fill_manual, theme, theme_minimal\n",
    "\n",
    "pbp = pwhl.load_pwhl_pbp(seasons=[SEASON])\n",
    "dates = pbp.group_by(\"game_id\", maintain_order=True).agg(pl.col(\"game_date\").first())\n",
    "schedule = schedule.with_columns(\n",
    "    pl.col(\"game_id\").cast(pl.Int64)\n",
    ")  # a string in the schedule, Int64 in the play-by-play\n",
    "assert schedule.schema[\"game_id\"] == dates.schema[\"game_id\"]\n",
    "regular = schedule.filter(pl.col(\"game_type\") == \"regular\")\n",
    "sides = [\n",
    "    regular.select(\n",
    "        \"game_id\",\n",
    "        \"game_status\",\n",
    "        pl.col(f\"{me}_team_id\").alias(\"team_id\"),\n",
    "        pl.col(f\"{me}_score\").cast(pl.Int64).alias(\"gf\"),\n",
    "        pl.col(f\"{them}_score\").cast(pl.Int64).alias(\"ga\"),\n",
    "    )\n",
    "    for me, them in ((\"home\", \"away\"), (\"away\", \"home\"))\n",
    "]\n",
    "results = (\n",
    "    pl.concat(sides)\n",
    "    .join(dates, on=\"game_id\")\n",
    "    .with_columns(extra=pl.col(\"game_status\") != \"Final\")\n",
    "    .with_columns(\n",
    "        result=pl.when(pl.col(\"gf\") > pl.col(\"ga\"))\n",
    "        .then(pl.when(pl.col(\"extra\")).then(pl.lit(\"OT/SO win\")).otherwise(pl.lit(\"Win\")))\n",
    "        .otherwise(pl.when(pl.col(\"extra\")).then(pl.lit(\"OT/SO loss\")).otherwise(pl.lit(\"Loss\")))\n",
    "    )\n",
    "    .sort(\"game_date\", \"game_id\")\n",
    "    .with_columns(game=pl.col(\"game_id\").cum_count().over(\"team_id\"))\n",
    ")\n",
    "order = standings[\"team_id\"].reverse().to_list()  # best team on top\n",
    "grid = results.to_pandas()\n",
    "grid[\"team_id\"] = grid[\"team_id\"].astype(\"category\").cat.set_categories(order)\n",
    "p = (\n",
    "    ggplot(grid, aes(\"game\", \"team_id\", fill=\"result\"))\n",
    "    + geom_tile(color=\"white\", size=0.6)\n",
    "    + scale_fill_manual(\n",
    "        values={\"Win\": \"#1b7837\", \"OT/SO win\": \"#a6dba0\", \"OT/SO loss\": \"#f4a582\", \"Loss\": \"#b2182b\"},\n",
    "        breaks=[\"Win\", \"OT/SO win\", \"OT/SO loss\", \"Loss\"],\n",
    "    )\n",
    "    + labs(\n",
    "        x=\"Game\",\n",
    "        y=\"\",\n",
    "        fill=\"\",\n",
    "        title=\"Every PWHL game of 2025-26, by team\",\n",
    "        caption=\"Data: fastRhockey PWHL release via sportsdataverse-py\",\n",
    "    )\n",
    "    + theme_minimal()\n",
    "    + theme(\n",
    "        figure_size=(10, 4.5), panel_grid=element_blank(), legend_position=\"top\", plot_title=element_text(weight=\"bold\")\n",
    "    )\n",
    ")\n",
    "sdvplot.axis_logos(p, \"y\", league=\"pwhl\", height=0.09)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17",
   "metadata": {},
   "source": [
    "## 6. Three seasons of the PWHL\n",
    "\n",
    "Each club's regular-season finish in the league's three seasons, as a bump chart. HockeyTech's 2023-24 standings use\n",
    "placeholder names (`PWHL Toronto`) and the codes do not resolve, so join each season's standings to the team list on\n",
    "the code, as in example 1. Seattle and Vancouver joined for 2025-26, so they have one point each."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18",
   "metadata": {},
   "outputs": [],
   "source": [
    "from plotnine import geom_line, scale_x_continuous, scale_y_reverse\n",
    "\n",
    "from sdvplot.plotnine import geom_sdv_logos\n",
    "\n",
    "history = (\n",
    "    pl.concat(\n",
    "        [pwhl.pwhl_standings(season=s).with_columns(season=pl.lit(s)) for s in (2024, 2025, 2026)],\n",
    "        how=\"diagonal_relaxed\",\n",
    "    )\n",
    "    .with_columns(team_code=pl.col(\"team_code\").str.replace(r\"^\\w+ - \", \"\"))\n",
    "    .join(teams, on=\"team_code\")\n",
    "    .select(\"season\", \"team_id\", pl.col(\"team_rank\").alias(\"finish\"))\n",
    ")\n",
    "(\n",
    "    ggplot(history.to_pandas(), aes(\"season\", \"finish\", group=\"team_id\", team=\"team_id\"))\n",
    "    + geom_line(color=\"#bbbbbb\", size=1.2)\n",
    "    + geom_sdv_logos(league=\"pwhl\", height=0.1)\n",
    "    + scale_x_continuous(breaks=[2024, 2025, 2026], labels=[\"2023-24\", \"2024-25\", \"2025-26\"], limits=(2023.8, 2026.2))\n",
    "    + scale_y_reverse(breaks=list(range(1, 9)))\n",
    "    + labs(\n",
    "        x=\"\",\n",
    "        y=\"Regular-season finish\",\n",
    "        title=\"Three seasons of the PWHL\",\n",
    "        caption=\"Data: PWHL HockeyTech feed via sportsdataverse-py\",\n",
    "    )\n",
    "    + theme_minimal()\n",
    "    + theme(figure_size=(8, 5.5), plot_title=element_text(weight=\"bold\"))\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19",
   "metadata": {},
   "source": [
    "## 7. Shot quality with Altair\n",
    "\n",
    "HockeyTech marks each shot on goal as a quality chance or not. The share of a team's shots that were quality chances,\n",
    "as an interactive Altair bar chart; `axis_logos` puts the logos on the y axis of the web chart too."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "20",
   "metadata": {},
   "outputs": [],
   "source": [
    "import altair as alt\n",
    "\n",
    "quality = (\n",
    "    pbp.filter(pl.col(\"event\") == \"shot\")\n",
    "    .group_by(\"team_id\", maintain_order=True)\n",
    "    .agg(pl.len().alias(\"shots\"), pl.col(\"shot_quality\").str.starts_with(\"Quality\").mean().alias(\"quality\"))\n",
    "    .sort(\"quality\", descending=True)\n",
    ")\n",
    "bars = (\n",
    "    alt.Chart(quality.to_pandas(), title=\"Share of shots on goal that were quality chances, PWHL 2025-26\")\n",
    "    .mark_bar(color=\"#5b2c83\")\n",
    "    .encode(\n",
    "        x=alt.X(\"quality:Q\", title=\"Quality share of shots on goal\", axis=alt.Axis(format=\"%\")),\n",
    "        y=alt.Y(\"team_id:N\", sort=quality[\"team_id\"].to_list(), title=None),\n",
    "        tooltip=[\"team_id\", \"shots\", alt.Tooltip(\"quality:Q\", format=\".1%\")],\n",
    "    )\n",
    "    .properties(width=520, height=320)\n",
    ")\n",
    "sdvplot.axis_logos(bars, \"y\", league=\"pwhl\", height=0.09)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21",
   "metadata": {},
   "source": [
    "## 8. The game that decided the Walter Cup\n",
    "\n",
    "The last game of the 2026 playoffs, every shot on a rink: `surface(\"pwhl\")` draws the rink, the home team shoots at\n",
    "the left net and the visitors at the right one (HockeyTech's `x_coord` already fixes that), and each team's logo\n",
    "sits in its attacking half."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "22",
   "metadata": {
    "sdvplot_gallery": {
     "alt": "Every shot and goal of the 2026 Walter Cup-clinching game on a rink, each team's logo in its attacking half",
     "title": "PWHL Walter Cup game shot map"
    },
    "tags": [
     "gallery"
    ]
   },
   "outputs": [],
   "source": [
    "playoffs = schedule.filter(pl.col(\"game_type\") == \"playoffs\").drop(\n",
    "    \"game_date\"\n",
    ")  # \"Wed, May 20\": no year, so use the ISO date\n",
    "last = playoffs.join(dates, on=\"game_id\").sort(\"game_date\").row(-1, named=True)\n",
    "game = pbp.filter((pl.col(\"game_id\") == last[\"game_id\"]) & pl.col(\"event\").is_in([\"shot\", \"goal\"]))\n",
    "home_id, away_id = last[\"home_team_id\"], last[\"away_team_id\"]\n",
    "score = f\"{last['away_team']} {last['away_score']}, {last['home_team']} {last['home_score']}\"\n",
    "\n",
    "fig, ax = plt.subplots(figsize=(10, 5))\n",
    "sdvplot.surface(\"pwhl\", ax=ax)\n",
    "for tid, color in ((home_id, \"#1f3b73\"), (away_id, \"#8c1d40\")):\n",
    "    side = game.filter(pl.col(\"team_id\") == tid)\n",
    "    shots, goals = side.filter(pl.col(\"event\") == \"shot\"), side.filter(pl.col(\"event\") == \"goal\")\n",
    "    ax.scatter(shots[\"x_coord\"], shots[\"y_coord\"], s=28, color=color, alpha=0.45, zorder=20)\n",
    "    ax.scatter(goals[\"x_coord\"], goals[\"y_coord\"], s=160, marker=\"*\", color=color, edgecolor=\"white\", zorder=21)\n",
    "sdvplot.add_logos(ax, [-50, 50], [0, 0], [home_id, away_id], league=\"pwhl\", height=0.22, alpha=0.35, zorder=19)\n",
    "ax.set_title(f\"{score} ({last['game_date']}): the Walter Cup-clinching game\", loc=\"left\", fontweight=\"bold\")\n",
    "fig.text(0.01, 0.02, \"Dots are shots on goal, stars are goals.\", fontsize=9)\n",
    "fig.text(0.99, 0.02, \"Data: fastRhockey PWHL release via sportsdataverse-py\", ha=\"right\", fontsize=8, color=\"grey\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23",
   "metadata": {},
   "source": [
    "Montréal took the best-of-five final against Ottawa three games to one, and the fourth game was the only shutout of\n",
    "the series."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "24",
   "metadata": {},
   "source": [
    "## 9. Before the PWHL: the PHF's last season\n",
    "\n",
    "The Premier Hockey Federation (the NWHL until 2021) played its last season in 2022-23 before the PWHL replaced it. Its\n",
    "results are in the fastRhockey release; its clubs are in the index by name. The Metropolitan Riveters have two\n",
    "archive entries, so their name is ambiguous: sdvplot warns rather than picking one."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "25",
   "metadata": {},
   "outputs": [],
   "source": [
    "phf = pwhl.load_phf_schedules(seasons=[2023]).filter(pl.col(\"game_type\") == \"Regular Season\")\n",
    "sides = [\n",
    "    phf.select(\n",
    "        pl.col(f\"{me}_team\").alias(\"team\"), pl.col(f\"{me}_score\").alias(\"gf\"), pl.col(f\"{them}_score\").alias(\"ga\")\n",
    "    )\n",
    "    for me, them in ((\"home\", \"away\"), (\"away\", \"home\"))\n",
    "]\n",
    "phf_table = (\n",
    "    pl.concat(sides)\n",
    "    .group_by(\"team\", maintain_order=True)\n",
    "    .agg(pl.len().alias(\"gp\"), (pl.col(\"gf\") > pl.col(\"ga\")).sum().alias(\"w\"), pl.col(\"gf\").sum(), pl.col(\"ga\").sum())\n",
    "    .with_columns(diff=pl.col(\"gf\") - pl.col(\"ga\"))\n",
    "    .sort(\"w\", \"diff\", descending=True)\n",
    ")\n",
    "with warnings.catch_warnings(record=True) as caught:\n",
    "    warnings.simplefilter(\"always\")\n",
    "    sdvplot.resolve(phf_table[\"team\"], \"phf\", season=2023)\n",
    "print(caught[0].message)\n",
    "sdvplot.suggest(\"Metropolitan Riveters\", \"phf\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "26",
   "metadata": {},
   "source": [
    "`marks()` shows the 2022-23 Riveters mark belongs to `124984`, so pass that id for the Riveters and the names for\n",
    "everyone else."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "27",
   "metadata": {},
   "outputs": [],
   "source": [
    "phf_table = phf_table.with_columns(\n",
    "    team_key=pl.when(pl.col(\"team\") == \"Metropolitan Riveters\").then(pl.lit(\"124984\")).otherwise(pl.col(\"team\"))\n",
    ")\n",
    "fig, ax = plt.subplots(figsize=(8, 4.8))\n",
    "ax.bar(phf_table[\"team_key\"], phf_table[\"w\"], color=\"#3a3a3a\", width=0.6)\n",
    "for i, (w, gp) in enumerate(zip(phf_table[\"w\"], phf_table[\"gp\"], strict=True)):\n",
    "    ax.text(i, w + 0.3, f\"{w}-{gp - w}\", ha=\"center\", fontsize=9)\n",
    "sdvplot.axis_logos(ax, \"x\", league=\"phf\", season=2023, height=0.14)\n",
    "ax.spines[[\"top\", \"right\"]].set_visible(False)\n",
    "ax.set_yticks(range(0, 21, 5))\n",
    "ax.set_ylabel(f\"Wins ({phf_table['gp'].max()} games)\")\n",
    "ax.set_title(\"The PHF's final regular season, 2022-23\", loc=\"left\", fontweight=\"bold\")\n",
    "fig.subplots_adjust(bottom=0.2)\n",
    "fig.text(\n",
    "    0.99,\n",
    "    0.01,\n",
    "    \"Records include overtime and shootout results. Data: fastRhockey PHF release via sportsdataverse-py\",\n",
    "    ha=\"right\",\n",
    "    fontsize=8,\n",
    "    color=\"grey\",\n",
    ")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "28",
   "metadata": {},
   "source": [
    "Boston had the best record at 19-5, but Toronto won the last Isobel Cup, beating Minnesota 4-3 in the final."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "sdvplot": {
   "description": "PWHL standings, scoring leaders with photos, a results grid, three seasons of history, shot quality, the Walter Cup-clinching game on a rink and the PHF before it.",
   "label": "PWHL",
   "position": 41
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
