{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# πŸˆβšΎπŸ’ Newer ESPN leagues with `sportsdataverse-py`\n", "\n", "Spring pro football is back, college diamonds are packed, and NCAA rinks are full. πŸŽ‰\n", "In a few lines of Python you're about to pull **live scoreboards**, **schedules**,\n", "**standings**, **team rosters**, and **play-by-play** for **seven leagues**\n", "added to `sportsdataverse-py` in the 0.0.59 ESPN expansion:\n", "\n", "| Sport | Leagues |\n", "|---|---|\n", "| Spring / pro football | United Football League (UFL), XFL, Canadian Football League (CFL) |\n", "| College baseball / softball | NCAA Baseball (`college_baseball`), NCAA Softball (`college_softball`) |\n", "| NCAA hockey | Men's College Hockey (`mch`), Women's College Hockey (`wch`) |\n", "\n", "Every league rides the same **cross-league ESPN wrapper surface** introduced in\n", "0.0.51 β€” a single parameterized core (`sportsdataverse._common_espn`) with\n", "thin per-league extension modules. That means **identical function naming** across\n", "all leagues: `espn_{prefix}_scoreboard`, `espn_{prefix}_team_schedule`,\n", "`espn_{prefix}_standings`, `espn_{prefix}_team_roster`, `espn_{prefix}_summary`,\n", "and 80+ more short names per league.\n", "\n", "### Namespace note\n", "\n", "The canonical import paths are **nested under the sport group**:\n", "\n", "```python\n", "from sportsdataverse.football.ufl import espn_ufl_scoreboard\n", "from sportsdataverse.baseball.college_baseball import espn_college_baseball_teams_site\n", "from sportsdataverse.hockey.mch import espn_mch_scoreboard\n", "```\n", "\n", "Top-level shortcuts (e.g. `sportsdataverse.ufl`) exist for back-compat but emit a\n", "`DeprecationWarning` β€” prefer the nested paths shown throughout this notebook." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 🧰 The toolbox\n", "\n", "Every wrapper returns a raw `dict` by default (`return_parsed=False`). Pass\n", "`` to get a tidy **polars** `DataFrame` where a parser is\n", "registered, or `return_as_pandas=True` for a **pandas** `DataFrame`. The\n", "function surface is identical across all seven leagues below.\n", "\n", "| Function pattern | What it gives you |\n", "|---|---|\n", "| `espn_{prefix}_scoreboard(dates=YYYYMMDD)` | Live / historical game slate |\n", "| `espn_{prefix}_team_schedule(team_id=, season=)` | Full team schedule for a season |\n", "| `espn_{prefix}_teams_site()` | All teams in the league (name, abbrev, logo, `team_id`) |\n", "| `espn_{prefix}_standings()` | Conference / division standings table |\n", "| `espn_{prefix}_summary(event_id=)` | Full game summary (~700 KB β€” box score, plays, leaders) |\n", "| `espn_{prefix}_team_roster(team_id=, season=)` | Roster for one team / season |\n", "| `espn_{prefix}_game_plays(event_id=)` | Play-by-play for one game |\n", "| `espn_{prefix}_leaders()` | League statistical leaders |\n", "| `espn_{prefix}_rankings()` | Polls / rankings (NCAA leagues) |\n", "| `espn_{prefix}_news()` | League news feed |\n", "| `espn_{prefix}_injuries()` | Injury report |\n", "| `espn_{prefix}_player_info(athlete_id=)` | Single-player bio / current info |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## πŸ”Œ Setup\n", "\n", "```sh\n", "pip install sportsdataverse\n", "```\n", "\n", "No API key required β€” all seven leagues hit ESPN's public endpoints." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import polars as pl\n", "\n", "# Spring / pro football\n", "import sportsdataverse.football.ufl as ufl\n", "import sportsdataverse.football.xfl as xfl\n", "import sportsdataverse.football.cfl as cfl\n", "\n", "# College baseball + softball\n", "import sportsdataverse.baseball.college_baseball as cbb\n", "import sportsdataverse.baseball.college_softball as cbs\n", "\n", "# NCAA hockey\n", "import sportsdataverse.hockey.mch as mch\n", "import sportsdataverse.hockey.wch as wch\n", "\n", "pl.Config.set_tbl_rows(10)\n", "print('sportsdataverse loaded β€” seven ESPN expansion leagues ready πŸš€')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ESPN's live endpoints are seasonal and occasionally rate-limited, so a tiny\n", "`safe()` helper runs each call defensively β€” you get the result when the feed\n", "is up, and a friendly one-liner when it isn't (never a scary traceback). πŸ›Ÿ\n", "\n", "We also pin to a known completed slate for each league so code examples render\n", "consistently whether you run this in-season or in the off-season." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sportsdataverse.errors import AssetFetchError, NoDataError\n", "\n", "def safe(label, thunk):\n", " try:\n", " out = thunk()\n", " print(f'βœ… {label}')\n", " return out\n", " except (NoDataError, AssetFetchError) as e:\n", " print(f\"\\u23ed\\ufe0f {label}: {type(e).__name__}: {e}\")\n", " return None\n", "\n", "\n", "def _keys(d):\n", " \"\"\"Print top-level keys of a raw dict for quick orientation.\"\"\"\n", " if isinstance(d, dict):\n", " print('top-level keys:', list(d.keys())[:15])\n", " elif d is None:\n", " print('(no data)')\n", " else:\n", " print(type(d))\n", "\n", "\n", "# Stable completed-season reference dates / ids for each league\n", "UFL_DATE = 20240525 # UFL Championship Game, May 25 2024\n", "XFL_DATE = 20230521 # XFL Championship Game, May 21 2023 (last full XFL season)\n", "CFL_DATE = 20231119 # 111th Grey Cup, Nov 19 2023\n", "CBB_DATE = 20240624 # NCAA Baseball CWS finals, June 24 2024\n", "CBS_DATE = 20240606 # NCAA Softball WCWS finals, June 6 2024\n", "MCH_DATE = 20240413 # Frozen Four semi, April 13 2024\n", "WCH_DATE = 20240322 # NCAA Women's hockey Frozen Four, March 22 2024\n", "\n", "SAMPLE_SEASON = 2024\n", "print('reference constants set')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## 🏈 Part 1 β€” Spring & Pro Football: UFL, XFL, CFL\n", "\n", "The United Football League (UFL, formed from the merger of the XFL and USFL),\n", "the XFL, and the Canadian Football League (CFL) are all first-class citizens of\n", "the ESPN Site v2 API surface. Their modules expose the full `_FOOTBALL_WRAPPERS`\n", "set β€” the same family as `cfb` and `nfl` β€” so you get deep scoring-play,\n", "power-index, and drive-level detail out of the box." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### πŸ“‹ UFL: the scoreboard\n", "\n", "[`espn_ufl_scoreboard(dates=YYYYMMDD)`](../ufl/reference/site.md#espn_ufl_scoreboard)\n", "returns the game slate for a specific date. The raw payload is a dict whose\n", "`events` key holds one entry per game with nested team and status objects.\n", "Pass `` to get a flat polars frame." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Raw payload β€” orient yourself with the top-level keys\n", "board_raw = safe('UFL scoreboard (raw)', lambda: ufl.espn_ufl_scoreboard(dates=UFL_DATE))\n", "_keys(board_raw)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Parsed frame β€” one row per game\n", "board = safe(\n", " 'UFL scoreboard (parsed)',\n", " lambda: ufl.espn_ufl_scoreboard(dates=UFL_DATE),\n", ")\n", "if board is not None and getattr(board, 'height', 0):\n", " keep = [c for c in board.columns\n", " if c in ('id', 'name', 'short_name', 'status_type_description',\n", " 'home_team_abbreviation', 'home_score',\n", " 'away_team_abbreviation', 'away_score')]\n", " board.select(keep).head() if keep else board.head()\n", "else:\n", " 'scoreboard unavailable for that date'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 🏫 UFL: teams\n", "\n", "[`espn_ufl_teams_site()`](../ufl/reference/site.md#espn_ufl_teams_site)\n", "lists every team with its `team_id` β€” the key you feed into every\n", "team-scoped call (`espn_ufl_team_schedule`, `espn_ufl_team_roster`, etc.).\n", "There are no required arguments." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "teams_ufl = safe('UFL teams', lambda: ufl.espn_ufl_teams_site())\n", "if teams_ufl is not None and teams_ufl.height:\n", " keep = [c for c in teams_ufl.columns\n", " if c in ('id', 'abbreviation', 'display_name', 'location',\n", " 'color', 'alternate_color')]\n", " teams_ufl.select(keep).head(10) if keep else teams_ufl.head(10)\n", "else:\n", " 'teams unavailable'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### πŸ“Š UFL: standings\n", "\n", "[`espn_ufl_standings()`](../ufl/reference/site.md#espn_ufl_standings)\n", "returns the conference / division standings table. Pair with the\n", "team `id` from the teams frame to build a standings dashboard." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "standings_ufl = safe('UFL standings', lambda: ufl.espn_ufl_standings())\n", "if standings_ufl is not None and standings_ufl.height:\n", " keep = [c for c in standings_ufl.columns\n", " if c in ('team_id', 'team_name', 'team_abbreviation',\n", " 'wins', 'losses', 'win_percent', 'points_for', 'points_against')]\n", " standings_ufl.select(keep).head(8) if keep else standings_ufl.head(8)\n", "else:\n", " 'standings unavailable'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### πŸ“… UFL: team schedule\n", "\n", "[`espn_ufl_team_schedule(team_id=, season=)`](../ufl/reference/site.md#espn_ufl_team_schedule)\n", "returns one row per game for a single team's season β€” useful for building\n", "a results table or joining standings context onto a game-log." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# team_id=10000 is the Michigan Panthers (2024 UFL champions)\n", "schedule_ufl = safe(\n", " 'UFL team schedule',\n", " lambda: ufl.espn_ufl_team_schedule(team_id=10000, season=SAMPLE_SEASON,\n", " ),\n", ")\n", "if schedule_ufl is not None and schedule_ufl.height:\n", " keep = [c for c in schedule_ufl.columns\n", " if c in ('id', 'week', 'name', 'short_name',\n", " 'home_team_abbreviation', 'away_team_abbreviation',\n", " 'home_score', 'away_score', 'status_type_description')]\n", " schedule_ufl.select(keep).head() if keep else schedule_ufl.head()\n", "else:\n", " 'schedule unavailable'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 🏟️ UFL: game summary & play-by-play\n", "\n", "[`espn_ufl_summary(event_id=)`](../ufl/reference/site.md#espn_ufl_summary)\n", "returns the full ~700 KB Site v2 summary payload β€” box score, plays, drives,\n", "scoring plays, leaders, odds, and more. You can extract any sub-frame directly\n", "from the raw dict, or use the `parse_summary` parser (see the architecture\n", "note at the end of this notebook).\n", "\n", "`espn_ufl_game_plays(event_id=)` returns a lightweight play list for the same\n", "game without the full summary overhead." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# 2024 UFL Championship game β€” find the event_id from the scoreboard above\n", "# or look it up via espn_ufl_scoreboard(dates=UFL_DATE)\n", "UFL_CHAMPIONSHIP_ID = 401671648\n", "\n", "summary_ufl = safe(\n", " 'UFL game summary (raw)',\n", " lambda: ufl.espn_ufl_summary(event_id=UFL_CHAMPIONSHIP_ID),\n", ")\n", "_keys(summary_ufl)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Quick play-by-play via the lightweight plays endpoint\n", "plays_ufl = safe(\n", " 'UFL game plays (raw)',\n", " lambda: ufl.espn_ufl_game_plays(event_id=UFL_CHAMPIONSHIP_ID),\n", ")\n", "_keys(plays_ufl)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "### 🏈 XFL: scoreboard, teams & standings\n", "\n", "The XFL module (`sportsdataverse.football.xfl`) has the same shape.\n", "The 2023 season is the most recent fully archived XFL slate before the\n", "merger into the UFL." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# XFL teams β€” good cross-check since some franchises carried over to UFL\n", "teams_xfl = safe('XFL teams', lambda: xfl.espn_xfl_teams_site())\n", "if teams_xfl is not None and teams_xfl.height:\n", " keep = [c for c in teams_xfl.columns\n", " if c in ('id', 'abbreviation', 'display_name', 'location')]\n", " teams_xfl.select(keep).head(10) if keep else teams_xfl.head(10)\n", "else:\n", " 'XFL teams unavailable'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# XFL championship scoreboard β€” May 2023\n", "board_xfl = safe(\n", " 'XFL scoreboard (parsed)',\n", " lambda: xfl.espn_xfl_scoreboard(dates=XFL_DATE),\n", ")\n", "if board_xfl is not None and getattr(board_xfl, 'height', 0):\n", " keep = [c for c in board_xfl.columns\n", " if c in ('id', 'name', 'short_name', 'status_type_description',\n", " 'home_team_abbreviation', 'home_score',\n", " 'away_team_abbreviation', 'away_score')]\n", " board_xfl.select(keep).head() if keep else board_xfl.head()\n", "else:\n", " 'XFL scoreboard unavailable'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# XFL standings (2023)\n", "standings_xfl = safe('XFL standings', lambda: xfl.espn_xfl_standings())\n", "if standings_xfl is not None and standings_xfl.height:\n", " keep = [c for c in standings_xfl.columns\n", " if c in ('team_id', 'team_name', 'team_abbreviation',\n", " 'wins', 'losses', 'win_percent', 'points_for', 'points_against')]\n", " standings_xfl.select(keep).head(8) if keep else standings_xfl.head(8)\n", "else:\n", " 'XFL standings unavailable'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "### 🍁 CFL: scoreboard, teams, standings & schedule\n", "\n", "The Canadian Football League runs a 9-team league with a 18-game regular season\n", "capped by the **Grey Cup** in November. The `cfl` module exposes the full\n", "football wrapper set, including conference/division standings and the\n", "power-index endpoints." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# CFL Grey Cup 2023 scoreboard\n", "board_cfl = safe(\n", " 'CFL scoreboard (parsed)',\n", " lambda: cfl.espn_cfl_scoreboard(dates=CFL_DATE),\n", ")\n", "if board_cfl is not None and getattr(board_cfl, 'height', 0):\n", " keep = [c for c in board_cfl.columns\n", " if c in ('id', 'name', 'short_name', 'status_type_description',\n", " 'home_team_abbreviation', 'home_score',\n", " 'away_team_abbreviation', 'away_score')]\n", " board_cfl.select(keep).head() if keep else board_cfl.head()\n", "else:\n", " 'CFL scoreboard unavailable'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# CFL teams\n", "teams_cfl = safe('CFL teams', lambda: cfl.espn_cfl_teams_site())\n", "if teams_cfl is not None and teams_cfl.height:\n", " keep = [c for c in teams_cfl.columns\n", " if c in ('id', 'abbreviation', 'display_name', 'location', 'color')]\n", " teams_cfl.select(keep).head(10) if keep else teams_cfl.head(10)\n", "else:\n", " 'CFL teams unavailable'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# CFL standings (2023)\n", "standings_cfl = safe('CFL standings', lambda: cfl.espn_cfl_standings())\n", "if standings_cfl is not None and standings_cfl.height:\n", " keep = [c for c in standings_cfl.columns\n", " if c in ('team_id', 'team_name', 'team_abbreviation',\n", " 'wins', 'losses', 'win_percent', 'points_for', 'points_against')]\n", " standings_cfl.select(keep).head(9) if keep else standings_cfl.head(9)\n", "else:\n", " 'CFL standings unavailable'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# CFL team schedule β€” Winnipeg Blue Bombers (team_id=100) 2023 season\n", "schedule_cfl = safe(\n", " 'CFL team schedule',\n", " lambda: cfl.espn_cfl_team_schedule(team_id=100, season=2023),\n", ")\n", "if schedule_cfl is not None and schedule_cfl.height:\n", " keep = [c for c in schedule_cfl.columns\n", " if c in ('id', 'week', 'name', 'short_name',\n", " 'home_team_abbreviation', 'away_team_abbreviation',\n", " 'home_score', 'away_score', 'status_type_description')]\n", " schedule_cfl.select(keep).head(8) if keep else schedule_cfl.head(8)\n", "else:\n", " 'CFL schedule unavailable'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ⚾ Part 2 β€” College Baseball & Softball\n", "\n", "NCAA Baseball and NCAA Softball sit under `sportsdataverse.baseball`.\n", "Both modules expose the full `_NCAA_WRAPPERS` set β€” which adds\n", "`espn_{prefix}_rankings` and `espn_{prefix}_season_recruits` on top of\n", "the universal surface β€” giving you AP-style polls and recruiting boards\n", "alongside the standard scoreboard/schedule/teams/standings family." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ⚾ College baseball: scoreboard & teams\n", "\n", "[`espn_college_baseball_scoreboard(dates=YYYYMMDD)`](../college_baseball/reference/site.md#espn_college_baseball_scoreboard)\n", "returns the game slate for a date. The College World Series in June is peak\n", "traffic, so games should be available; the off-season window (November – January)\n", "returns an empty slate." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# College World Series finals day, 2024\n", "board_cbb = safe(\n", " 'college baseball scoreboard (parsed)',\n", " lambda: cbb.espn_college_baseball_scoreboard(dates=CBB_DATE),\n", ")\n", "if board_cbb is not None and getattr(board_cbb, 'height', 0):\n", " keep = [c for c in board_cbb.columns\n", " if c in ('id', 'name', 'short_name', 'status_type_description',\n", " 'home_team_abbreviation', 'home_score',\n", " 'away_team_abbreviation', 'away_score')]\n", " board_cbb.select(keep).head() if keep else board_cbb.head()\n", "else:\n", " 'college baseball scoreboard unavailable'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# All NCAA baseball teams with their team_id\n", "teams_cbb = safe(\n", " 'college baseball teams',\n", " lambda: cbb.espn_college_baseball_teams_site(),\n", ")\n", "if teams_cbb is not None and teams_cbb.height:\n", " keep = [c for c in teams_cbb.columns\n", " if c in ('id', 'abbreviation', 'display_name', 'location', 'color')]\n", " print('Total teams:', teams_cbb.height)\n", " teams_cbb.select(keep).head(10) if keep else teams_cbb.head(10)\n", "else:\n", " 'teams unavailable'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### πŸ“Š College baseball: standings & rankings\n", "\n", "[`espn_college_baseball_standings()`](../college_baseball/reference/site.md#espn_college_baseball_standings)\n", "returns conference standings. [`espn_college_baseball_rankings()`](../college_baseball/reference/site.md#espn_college_baseball_rankings)\n", "returns the current AP / USA Today poll β€” one of the NCAA extras added by the\n", "`_NCAA_WRAPPERS` family." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Conference standings\n", "standings_cbb = safe(\n", " 'college baseball standings',\n", " lambda: cbb.espn_college_baseball_standings(),\n", ")\n", "if standings_cbb is not None and standings_cbb.height:\n", " keep = [c for c in standings_cbb.columns\n", " if c in ('team_id', 'team_name', 'team_abbreviation',\n", " 'wins', 'losses', 'win_percent')]\n", " standings_cbb.select(keep).head(10) if keep else standings_cbb.head(10)\n", "else:\n", " 'standings unavailable'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# National polls β€” AP / USA Today baseball rankings\n", "rankings_cbb = safe(\n", " 'college baseball rankings',\n", " lambda: cbb.espn_college_baseball_rankings(),\n", ")\n", "if rankings_cbb is not None and rankings_cbb.height:\n", " keep = [c for c in rankings_cbb.columns\n", " if c in ('rank', 'team_id', 'team_name', 'points', 'first_place_votes',\n", " 'wins', 'losses')]\n", " rankings_cbb.select(keep).head(10) if keep else rankings_cbb.head(10)\n", "else:\n", " 'rankings unavailable (only current during the season)'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### πŸ“… College baseball: team schedule\n", "\n", "Pick any `team_id` from the teams frame above and pull its full schedule.\n", "Texas (team_id=251) is a perennial CWS contender β€” a good reference point." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Texas Longhorns baseball 2024 schedule\n", "schedule_cbb = safe(\n", " 'college baseball team schedule',\n", " lambda: cbb.espn_college_baseball_team_schedule(\n", " team_id=251, season=SAMPLE_SEASON\n", " ),\n", ")\n", "if schedule_cbb is not None and schedule_cbb.height:\n", " keep = [c for c in schedule_cbb.columns\n", " if c in ('id', 'name', 'short_name',\n", " 'home_team_abbreviation', 'away_team_abbreviation',\n", " 'home_score', 'away_score', 'status_type_description')]\n", " schedule_cbb.select(keep).head(8) if keep else schedule_cbb.head(8)\n", "else:\n", " 'schedule unavailable'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### πŸ₯Ž College softball: scoreboard & teams\n", "\n", "The `college_softball` module mirrors `college_baseball` exactly β€” same wrapper\n", "family, just a different league slug. The Women's College World Series (WCWS)\n", "wraps up in early June." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# WCWS finals day, June 2024\n", "board_cbs = safe(\n", " 'college softball scoreboard (parsed)',\n", " lambda: cbs.espn_college_softball_scoreboard(dates=CBS_DATE),\n", ")\n", "if board_cbs is not None and getattr(board_cbs, 'height', 0):\n", " keep = [c for c in board_cbs.columns\n", " if c in ('id', 'name', 'short_name', 'status_type_description',\n", " 'home_team_abbreviation', 'home_score',\n", " 'away_team_abbreviation', 'away_score')]\n", " board_cbs.select(keep).head() if keep else board_cbs.head()\n", "else:\n", " 'college softball scoreboard unavailable'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# College softball teams\n", "teams_cbs = safe(\n", " 'college softball teams',\n", " lambda: cbs.espn_college_softball_teams_site(),\n", ")\n", "if teams_cbs is not None and teams_cbs.height:\n", " keep = [c for c in teams_cbs.columns\n", " if c in ('id', 'abbreviation', 'display_name', 'location', 'color')]\n", " print('Total teams:', teams_cbs.height)\n", " teams_cbs.select(keep).head(10) if keep else teams_cbs.head(10)\n", "else:\n", " 'teams unavailable'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# College softball rankings\n", "rankings_cbs = safe(\n", " 'college softball rankings',\n", " lambda: cbs.espn_college_softball_rankings(),\n", ")\n", "if rankings_cbs is not None and rankings_cbs.height:\n", " keep = [c for c in rankings_cbs.columns\n", " if c in ('rank', 'team_id', 'team_name', 'points',\n", " 'first_place_votes', 'wins', 'losses')]\n", " rankings_cbs.select(keep).head(10) if keep else rankings_cbs.head(10)\n", "else:\n", " 'rankings unavailable (only current during the season)'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### πŸ₯Ž College softball: game summary\n", "\n", "The same `espn_{prefix}_summary(event_id=)` pattern works for softball.\n", "The raw dict contains a `boxscore` key (team batting/pitching lines),\n", "a `scoringPlays` array, and β€” for parsed callers β€” all 21 sub-frames\n", "from the shared `parse_summary` dispatcher." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# 2024 WCWS Championship Game 2 β€” Oklahoma vs Texas\n", "CBS_CHAMPIONSHIP_ID = 401581278\n", "\n", "summary_cbs = safe(\n", " 'college softball game summary (raw)',\n", " lambda: cbs.espn_college_softball_summary(event_id=CBS_CHAMPIONSHIP_ID),\n", ")\n", "_keys(summary_cbs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## πŸ’ Part 3 β€” NCAA Men's & Women's College Hockey\n", "\n", "Men's College Hockey (`mch`) and Women's College Hockey (`wch`) round out the\n", "seven-league expansion. Both expose the full `_NCAA_WRAPPERS` set. The\n", "signature events are the **Frozen Four** in April (men's) and March (women's)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### πŸ’ Men's college hockey: scoreboard & teams\n", "\n", "[`espn_mch_scoreboard(dates=YYYYMMDD)`](../mch/reference/site.md#espn_mch_scoreboard)\n", "returns the game slate. [`espn_mch_teams_site()`](../mch/reference/site.md#espn_mch_teams_site)\n", "lists all Division I programs β€” currently 60 teams." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Frozen Four semi-final day, April 2024\n", "board_mch = safe(\n", " 'men\\'s college hockey scoreboard (parsed)',\n", " lambda: mch.espn_mch_scoreboard(dates=MCH_DATE),\n", ")\n", "if board_mch is not None and getattr(board_mch, 'height', 0):\n", " keep = [c for c in board_mch.columns\n", " if c in ('id', 'name', 'short_name', 'status_type_description',\n", " 'home_team_abbreviation', 'home_score',\n", " 'away_team_abbreviation', 'away_score')]\n", " board_mch.select(keep).head() if keep else board_mch.head()\n", "else:\n", " 'men\\'s college hockey scoreboard unavailable'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Men's college hockey teams\n", "teams_mch = safe(\n", " 'men\\'s college hockey teams',\n", " lambda: mch.espn_mch_teams_site(),\n", ")\n", "if teams_mch is not None and teams_mch.height:\n", " keep = [c for c in teams_mch.columns\n", " if c in ('id', 'abbreviation', 'display_name', 'location', 'color')]\n", " print('Total D-I men\\'s programs:', teams_mch.height)\n", " teams_mch.select(keep).head(10) if keep else teams_mch.head(10)\n", "else:\n", " 'teams unavailable'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### πŸ“Š Men's college hockey: standings & rankings\n", "\n", "Conference standings are the primary puck-nerd query β€”\n", "which NCHC / Hockey East / Big Ten team leads their division?\n", "The rankings endpoint returns the USCHO / USA Today poll." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Conference standings\n", "standings_mch = safe(\n", " \"men's college hockey standings\",\n", " lambda: mch.espn_mch_standings(),\n", ")\n", "if standings_mch is not None and standings_mch.height:\n", " keep = [c for c in standings_mch.columns\n", " if c in ('team_id', 'team_name', 'team_abbreviation',\n", " 'wins', 'losses', 'ties', 'win_percent')]\n", " standings_mch.select(keep).head(10) if keep else standings_mch.head(10)\n", "else:\n", " 'standings unavailable'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# National rankings\n", "rankings_mch = safe(\n", " \"men's college hockey rankings\",\n", " lambda: mch.espn_mch_rankings(),\n", ")\n", "if rankings_mch is not None and rankings_mch.height:\n", " keep = [c for c in rankings_mch.columns\n", " if c in ('rank', 'team_id', 'team_name', 'points',\n", " 'first_place_votes', 'wins', 'losses', 'ties')]\n", " rankings_mch.select(keep).head(10) if keep else rankings_mch.head(10)\n", "else:\n", " 'rankings unavailable (only current during the season)'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### πŸ“… Men's college hockey: team schedule & roster\n", "\n", "Pull a program's full schedule with\n", "[`espn_mch_team_schedule(team_id=, season=)`](../mch/reference/site.md#espn_mch_team_schedule)\n", "and their roster with\n", "[`espn_mch_team_roster(team_id=, season=)`](../mch/reference/site.md#espn_mch_team_roster).\n", "Boston University (team_id=103) won the 2024 national title β€” a good\n", "reference point." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# BU Terriers schedule 2023-24\n", "schedule_mch = safe(\n", " \"men's college hockey team schedule\",\n", " lambda: mch.espn_mch_team_schedule(team_id=103, season=SAMPLE_SEASON,\n", " ),\n", ")\n", "if schedule_mch is not None and schedule_mch.height:\n", " keep = [c for c in schedule_mch.columns\n", " if c in ('id', 'name', 'short_name',\n", " 'home_team_abbreviation', 'away_team_abbreviation',\n", " 'home_score', 'away_score', 'status_type_description')]\n", " schedule_mch.select(keep).head(8) if keep else schedule_mch.head(8)\n", "else:\n", " 'schedule unavailable'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# BU Terriers roster 2023-24\n", "roster_mch = safe(\n", " \"men's college hockey roster\",\n", " lambda: mch.espn_mch_team_roster(team_id=103, season=SAMPLE_SEASON,\n", " ),\n", ")\n", "if roster_mch is not None and roster_mch.height:\n", " keep = [c for c in roster_mch.columns\n", " if c in ('id', 'first_name', 'last_name', 'display_name',\n", " 'position_abbreviation', 'jersey', 'birth_country')]\n", " roster_mch.select(keep).head(10) if keep else roster_mch.head(10)\n", "else:\n", " 'roster unavailable'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "### πŸ‘© Women's college hockey: scoreboard, teams & schedule\n", "\n", "The `wch` module is the women's mirror. The NCAA Women's Frozen Four runs\n", "in late March. Wisconsin is the perennial power β€” 12 national championships." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# NCAA Women's Frozen Four 2024\n", "board_wch = safe(\n", " \"women's college hockey scoreboard (parsed)\",\n", " lambda: wch.espn_wch_scoreboard(dates=WCH_DATE),\n", ")\n", "if board_wch is not None and getattr(board_wch, 'height', 0):\n", " keep = [c for c in board_wch.columns\n", " if c in ('id', 'name', 'short_name', 'status_type_description',\n", " 'home_team_abbreviation', 'home_score',\n", " 'away_team_abbreviation', 'away_score')]\n", " board_wch.select(keep).head() if keep else board_wch.head()\n", "else:\n", " \"women's college hockey scoreboard unavailable\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Women's college hockey teams\n", "teams_wch = safe(\n", " \"women's college hockey teams\",\n", " lambda: wch.espn_wch_teams_site(),\n", ")\n", "if teams_wch is not None and teams_wch.height:\n", " keep = [c for c in teams_wch.columns\n", " if c in ('id', 'abbreviation', 'display_name', 'location', 'color')]\n", " print(\"Total women's D-I programs:\", teams_wch.height)\n", " teams_wch.select(keep).head(10) if keep else teams_wch.head(10)\n", "else:\n", " 'teams unavailable'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Wisconsin Badgers women's hockey schedule 2023-24 (team_id=275)\n", "schedule_wch = safe(\n", " \"women's college hockey team schedule\",\n", " lambda: wch.espn_wch_team_schedule(team_id=275, season=SAMPLE_SEASON,\n", " ),\n", ")\n", "if schedule_wch is not None and schedule_wch.height:\n", " keep = [c for c in schedule_wch.columns\n", " if c in ('id', 'name', 'short_name',\n", " 'home_team_abbreviation', 'away_team_abbreviation',\n", " 'home_score', 'away_score', 'status_type_description')]\n", " schedule_wch.select(keep).head(8) if keep else schedule_wch.head(8)\n", "else:\n", " 'schedule unavailable'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Women's college hockey rankings\n", "rankings_wch = safe(\n", " \"women's college hockey rankings\",\n", " lambda: wch.espn_wch_rankings(),\n", ")\n", "if rankings_wch is not None and rankings_wch.height:\n", " keep = [c for c in rankings_wch.columns\n", " if c in ('rank', 'team_id', 'team_name', 'points',\n", " 'first_place_votes', 'wins', 'losses', 'ties')]\n", " rankings_wch.select(keep).head(10) if keep else rankings_wch.head(10)\n", "else:\n", " \"rankings unavailable (only current during the season)\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## 🍳 Cookbook: cross-league recipes\n", "\n", "A handful of patterns you'll reach for constantly across all seven leagues." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Recipe 1 β€” Collect all teams from every league into one frame πŸ—‚οΈ\n", "\n", "All seven modules expose `espn_{prefix}_teams_site()`. Stack them with\n", "`pl.concat` to build a cross-league lookup table." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import importlib\n", "\n", "LEAGUE_MODULES = [\n", " ('ufl', ufl, 'espn_ufl_teams_site'),\n", " ('xfl', xfl, 'espn_xfl_teams_site'),\n", " ('cfl', cfl, 'espn_cfl_teams_site'),\n", " ('college_baseball', cbb, 'espn_college_baseball_teams_site'),\n", " ('college_softball', cbs, 'espn_college_softball_teams_site'),\n", " ('mch', mch, 'espn_mch_teams_site'),\n", " ('wch', wch, 'espn_wch_teams_site'),\n", "]\n", "\n", "frames = []\n", "for league_name, mod, fn_name in LEAGUE_MODULES:\n", " fn = getattr(mod, fn_name)\n", " df = safe(f'{league_name} teams', lambda fn=fn: fn())\n", " if df is not None and df.height:\n", " # parsed teams frames use team_id / team_display_name column names\n", " id_col = 'team_id' if 'team_id' in df.columns else 'id' if 'id' in df.columns else df.columns[0]\n", " name_col = 'team_display_name' if 'team_display_name' in df.columns else 'display_name' if 'display_name' in df.columns else df.columns[1]\n", " frames.append(\n", " df.select([id_col, name_col])\n", " .rename({id_col: 'id', name_col: 'display_name'})\n", " .with_columns(pl.lit(league_name).alias('league'))\n", " )\n", "\n", "if frames:\n", " all_teams = pl.concat(frames, how='diagonal_relaxed')\n", " print('Total teams across all 7 leagues:', all_teams.height)\n", " all_teams.group_by('league').len().sort('len', descending=True)\n", "else:\n", " 'no team data available right now'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Recipe 2 β€” Today's slate for any league πŸ“…\n", "\n", "Pass `dates=` as `YYYYMMDD` to any scoreboard wrapper. Omitting the argument\n", "returns ESPN's *current* default date (today or the most recent active day),\n", "which is handy during the season." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import date\n", "\n", "today_str = int(date.today().strftime('%Y%m%d'))\n", "\n", "# Swap in any module + scoreboard function to check a different league\n", "todays_cfl = safe(\n", " f\"today's CFL slate ({today_str})\",\n", " lambda: cfl.espn_cfl_scoreboard(dates=today_str),\n", ")\n", "if todays_cfl is not None and getattr(todays_cfl, 'height', 0):\n", " print(f\"{todays_cfl.height} game(s) today\")\n", " todays_cfl.head()\n", "else:\n", " 'no CFL games today (or offseason)'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Recipe 3 β€” Player info for any athlete πŸ§‘β€πŸ’»\n", "\n", "Every league exposes `espn_{prefix}_player_info(athlete_id=)`. Find an\n", "`athlete_id` via the roster endpoint, then pull richer bio / career-stats\n", "detail with `espn_{prefix}_player_career_stats(athlete_id=)`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Look up a player from the men's college hockey roster we pulled earlier\n", "# This uses the first athlete_id in the roster frame if it's available\n", "if (\n", " 'roster_mch' in dir()\n", " and roster_mch is not None\n", " and roster_mch.height\n", " and 'id' in roster_mch.columns\n", "):\n", " first_athlete_id = roster_mch['id'][0]\n", " player_info = safe(\n", " f'MCH player info (id={first_athlete_id})',\n", " lambda: mch.espn_mch_player_info(athlete_id=first_athlete_id),\n", " )\n", " _keys(player_info)\n", "else:\n", " print('roster not loaded β€” skipping player info example')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Recipe 4 β€” News & injuries for any league πŸ“°\n", "\n", "[`espn_{prefix}_news()`](../ufl/reference/site.md#espn_ufl_news) and\n", "[`espn_{prefix}_injuries()`](../ufl/reference/site.md#espn_ufl_injuries)\n", "need no arguments β€” just call them for the latest feed. Both return raw\n", "dicts; parsed versions are available where a parser is registered." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# UFL news (current, no date needed)\n", "news_ufl = safe('UFL news', lambda: ufl.espn_ufl_news())\n", "_keys(news_ufl)\n", "\n", "# CFL injuries\n", "injuries_cfl = safe('CFL injuries', lambda: cfl.espn_cfl_injuries())\n", "_keys(injuries_cfl)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## πŸ—οΈ The shared cross-league architecture\n", "\n", "All seven leagues share exactly **one** parameterized core:\n", "`sportsdataverse._common_espn`. The architecture is worth understanding\n", "because it makes every league behave identically:\n", "\n", "```\n", "_common_espn.py\n", " β”œβ”€β”€ ~80 core functions, each parameterized on (sport, league) slugs\n", " β”œβ”€β”€ _UNIVERSAL_WRAPPERS β€” scoreboard, teams, standings, schedule, …\n", " β”œβ”€β”€ _NCAA_WRAPPERS β€” +rankings, +season_recruits (CBB/CBS/MCH/WCH)\n", " β”œβ”€β”€ _FOOTBALL_WRAPPERS β€” +drives, +powerindex, +draft (UFL/XFL/CFL)\n", " └── make_league_module(sport, league, prefix, namespace)\n", " └── _bind(core_fn, sport, league) β†’ espn_{prefix}_{short}\n", "\n", "sportsdataverse/football/ufl.py # 4 lines β€” calls make_league_module\n", "sportsdataverse/football/xfl.py # 4 lines\n", "sportsdataverse/football/cfl.py # 4 lines\n", "sportsdataverse/baseball/college_baseball.py # 4 lines\n", "sportsdataverse/baseball/college_softball.py # 4 lines\n", "sportsdataverse/hockey/mch.py # 4 lines\n", "sportsdataverse/hockey/wch.py # 4 lines\n", "```\n", "\n", "This means:\n", "\n", "- Any bug fix in `_common_espn` applies to all 7 leagues at once.\n", "- Every league has an identical `dir()` surface β€” the only difference\n", " is the prefix string (`ufl`, `xfl`, `cfl`, `college_baseball`, …).\n", "- The `` path routes through `ENDPOINT_PARSERS` β€” a\n", " registry mapping each short name to a dedicated parser function (or\n", " a generic fall-through like `parse_items`, `parse_single_entity`, or\n", " `parse_summary`). You can always get the raw dict by omitting the kwarg.\n", "\n", "**Total wrappers across all eight SDV leagues** (including the 0.0.51 originals\n", "plus the 0.0.59 expansion): **819 registered functions**." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Listing all functions on any league module" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# All espn_ufl_* functions β€” the same list shape exists for every league\n", "ufl_fns = [f for f in dir(ufl) if f.startswith('espn_')]\n", "print(f'{len(ufl_fns)} functions on sportsdataverse.football.ufl')\n", "print('sample:', ufl_fns[:8], '...')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Quick cross-league count\n", "for name, mod in [('ufl', ufl), ('xfl', xfl), ('cfl', cfl),\n", " ('college_baseball', cbb), ('college_softball', cbs),\n", " ('mch', mch), ('wch', wch)]:\n", " n = len([f for f in dir(mod) if f.startswith('espn_')])\n", " print(f' {name:20s}: {n} wrappers')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## πŸ”— See Also\n", "\n", "- **Spring / pro football reference docs:**\n", " [UFL](../ufl/reference/site.md) Β·\n", " [XFL](../football/reference/xfl.md) Β·\n", " [CFL](../football/reference/cfl.md)\n", "- **College baseball / softball reference docs:**\n", " [College Baseball](../college_baseball/reference/site.md) Β·\n", " [College Softball](../baseball/reference/college_softball.md)\n", "- **NCAA hockey reference docs:**\n", " [Men's College Hockey](../mch/reference/site.md) Β·\n", " [Women's College Hockey](../hockey/reference/wch.md)\n", "- **Cross-league architecture deep-dive:** `docs/docs/architecture/espn-cross-league.md`\n", "- **Parser layer:** `docs/docs/parsers/`\n", "- **Companion notebooks:**\n", " `02_cfb_intro.ipynb` (CFB β€” the original football tutorial),\n", " `07_nhl_intro.ipynb` (NHL β€” native feed + ESPN fallback),\n", " `09_mlb_intro.ipynb` (MLB β€” Stats API + Statcast)\n", "- **R package companions:**\n", " [cfbfastR](https://cfbfastR.sportsdataverse.org) Β·\n", " [hoopR](https://hoopR.sportsdataverse.org) Β·\n", " [wehoop](https://wehoop.sportsdataverse.org) Β·\n", " [fastRhockey](https://fastRhockey.sportsdataverse.org) Β·\n", " [baseballr](https://baseballr.sportsdataverse.org)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }