{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": [
    "# College football\n",
    "\n",
    "Nine charts and tables from the 2025 college football season: all 136 FBS teams on one scatter, conference small\n",
    "multiples, a conference standings table, the national champion's season, a rivalry, FCS upsets, an interactive\n",
    "Altair chart, a ranked bar chart and a tier list. The data comes from the cfbfastR releases and ESPN through\n",
    "`sportsdataverse.cfb`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import polars as pl\n",
    "import sportsdataverse.cfb as cfb\n",
    "\n",
    "import sdvplot\n",
    "\n",
    "SEASON = 2025\n",
    "CAPTION = f\"Data: cfbfastR via sportsdataverse-py | {SEASON} season\"\n",
    "\n",
    "# sdvplot team ids are strings: cast the loaders' integer ESPN ids once, here\n",
    "summaries = cfb.load_cfb_team_summaries([SEASON]).select(\n",
    "    pl.col(\"team_id\").cast(pl.Utf8), \"pos_team\", \"conference\", \"EPAplay_off\", \"EPAplay_def\"\n",
    ")\n",
    "schedule = cfb.load_cfb_schedule([SEASON]).with_columns(pl.col(\"home_id\", \"away_id\").cast(pl.Utf8))\n",
    "names = sdvplot.teams(\"cfb\").select(\"team_id\", school=\"short_name\")\n",
    "summaries.height, schedule.height"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2",
   "metadata": {},
   "source": [
    "## 1. All 136 FBS teams on one chart\n",
    "\n",
    "Offensive EPA per play against defensive EPA per play allowed, from the cfbfastR team summaries. With this many\n",
    "teams the logos have to be small: `height=0.045` makes each one 4.5% of the plot's height."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3",
   "metadata": {
    "sdvplot_gallery": {
     "alt": "Scatter of all 136 FBS teams' 2025 offensive and defensive EPA per play, each point a small team logo",
     "title": "136 FBS teams, offense vs defense"
    },
    "tags": [
     "gallery"
    ]
   },
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(figsize=(10, 6))\n",
    "ax.axvline(summaries[\"EPAplay_off\"].mean(), color=\"grey\", lw=0.8, ls=\"--\")\n",
    "ax.axhline(summaries[\"EPAplay_def\"].mean(), color=\"grey\", lw=0.8, ls=\"--\")\n",
    "ax.scatter(summaries[\"EPAplay_off\"], summaries[\"EPAplay_def\"], s=0)\n",
    "ax.margins(0.06)\n",
    "sdvplot.add_logos(\n",
    "    ax,\n",
    "    summaries[\"EPAplay_off\"],\n",
    "    summaries[\"EPAplay_def\"],\n",
    "    summaries[\"team_id\"],\n",
    "    league=\"cfb\",\n",
    "    season=SEASON,\n",
    "    height=0.045,\n",
    ")\n",
    "ax.invert_yaxis()\n",
    "ax.set(xlabel=\"Offense EPA per play\", ylabel=\"Defense EPA per play allowed (better is up)\")\n",
    "ax.set_title(f\"Every FBS offense and defense, {SEASON}\", loc=\"left\", fontweight=\"bold\")\n",
    "fig.text(0.99, 0.01, CAPTION, ha=\"right\", fontsize=8, color=\"grey\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4",
   "metadata": {},
   "source": [
    "## 2. Conference small multiples with plotnine\n",
    "\n",
    "The same data, one panel per conference: every FBS team as a grey dot behind, the conference's own teams as logos.\n",
    "The grey layer gets a copy of the data without the `conference` column, so plotnine repeats it in every panel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5",
   "metadata": {},
   "outputs": [],
   "source": [
    "from plotnine import aes, facet_wrap, geom_point, ggplot, labs, scale_y_reverse, theme, theme_minimal\n",
    "\n",
    "from sdvplot.plotnine import geom_sdv_logos\n",
    "\n",
    "(\n",
    "    ggplot(summaries.to_pandas(), aes(\"EPAplay_off\", \"EPAplay_def\"))\n",
    "    + geom_point(data=summaries.drop(\"conference\").to_pandas(), color=\"#d9d9d9\", size=1)\n",
    "    + geom_sdv_logos(aes(team=\"team_id\"), league=\"cfb\", season=SEASON, height=0.13)\n",
    "    + facet_wrap(\"conference\", ncol=4)\n",
    "    + scale_y_reverse()\n",
    "    + labs(\n",
    "        x=\"Offense EPA per play\",\n",
    "        y=\"Defense EPA per play allowed\",\n",
    "        title=f\"FBS offense and defense by conference, {SEASON}\",\n",
    "        caption=CAPTION,\n",
    "    )\n",
    "    + theme_minimal()\n",
    "    + theme(figure_size=(10, 6))\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6",
   "metadata": {},
   "source": [
    "## 3. A conference standings table\n",
    "\n",
    "Big Ten records built from the schedule: conference games and all games through the regular season (the title game\n",
    "and bowls left out). `gt_sdv_logos` turns the id column into logos and `gt_fmt_tally` writes each pair of win and\n",
    "loss columns as one record."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7",
   "metadata": {},
   "outputs": [],
   "source": [
    "from great_tables import GT\n",
    "\n",
    "from sdvplot.great_tables import gt_fmt_tally, gt_sdv_logos, gt_theme_athletic\n",
    "\n",
    "regular = schedule.filter(pl.col(\"completed\"), pl.col(\"season_type\") == \"regular\", pl.col(\"week\") <= 14)\n",
    "sides = pl.concat(\n",
    "    [\n",
    "        regular.select(\n",
    "            \"conference_game\", team_id=\"home_id\", conf=\"home_conference\", pf=\"home_points\", pa=\"away_points\"\n",
    "        ),\n",
    "        regular.select(\n",
    "            \"conference_game\", team_id=\"away_id\", conf=\"away_conference\", pf=\"away_points\", pa=\"home_points\"\n",
    "        ),\n",
    "    ]\n",
    ").with_columns(win=pl.col(\"pf\") > pl.col(\"pa\"))\n",
    "\n",
    "big_ten = (\n",
    "    sides.filter(pl.col(\"conf\") == \"Big Ten\")\n",
    "    .group_by(\"team_id\", maintain_order=True)\n",
    "    .agg(\n",
    "        conf_w=(pl.col(\"win\") & pl.col(\"conference_game\")).sum(),\n",
    "        conf_l=(~pl.col(\"win\") & pl.col(\"conference_game\")).sum(),\n",
    "        w=pl.col(\"win\").sum(),\n",
    "        l=(~pl.col(\"win\")).sum(),\n",
    "        pf=pl.col(\"pf\").sum(),\n",
    "        pa=pl.col(\"pa\").sum(),\n",
    "    )\n",
    "    .join(names, on=\"team_id\")\n",
    "    .sort([\"conf_w\", \"w\", \"pf\"], descending=True)\n",
    "    .select(\"team_id\", \"school\", \"conf_w\", \"conf_l\", \"w\", \"l\", \"pf\", \"pa\")\n",
    ")\n",
    "\n",
    "(\n",
    "    GT(big_ten)\n",
    "    .pipe(gt_sdv_logos, \"team_id\", league=\"cfb\", season=SEASON, height=24)\n",
    "    .pipe(gt_fmt_tally, [\"conf_w\", \"conf_l\"], label=\"Conf\")\n",
    "    .pipe(gt_fmt_tally, [\"w\", \"l\"], label=\"Overall\")\n",
    "    .cols_label(team_id=\"\", school=\"\", pf=\"PF\", pa=\"PA\")\n",
    "    .tab_header(title=f\"Big Ten standings, {SEASON}\", subtitle=\"Regular season, before the title game\")\n",
    "    .tab_source_note(CAPTION)\n",
    "    .pipe(gt_theme_athletic, density=\"compact\")\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8",
   "metadata": {},
   "source": [
    "## 4. The national champion's season, with a logo in the title\n",
    "\n",
    "Indiana went 16-0. Each bar is one game's margin in Indiana's primary color, the opponent's logo above it, and\n",
    "`title_image` puts the Indiana logo beside the title. `resolve` turns the school name into its id."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9",
   "metadata": {
    "sdvplot_gallery": {
     "alt": "Bar chart of Indiana's 16 game margins in 2025, each bar topped with the opponent's logo, Indiana logo beside the title",
     "title": "Indiana's 16-0 season"
    },
    "tags": [
     "gallery"
    ]
   },
   "outputs": [],
   "source": [
    "from sdvplot.matplotlib import title_image\n",
    "\n",
    "team = sdvplot.resolve(\"Indiana\", \"cfb\")\n",
    "games = (\n",
    "    schedule.filter(pl.col(\"completed\"), (pl.col(\"home_id\") == team) | (pl.col(\"away_id\") == team))\n",
    "    .sort(\"start_date\")\n",
    "    .with_columns(\n",
    "        home=pl.col(\"home_id\") == team,\n",
    "        opponent=pl.when(pl.col(\"home_id\") == team).then(\"away_id\").otherwise(\"home_id\"),\n",
    "    )\n",
    "    .with_columns(\n",
    "        margin=pl.when(pl.col(\"home\"))\n",
    "        .then(pl.col(\"home_points\") - pl.col(\"away_points\"))\n",
    "        .otherwise(pl.col(\"away_points\") - pl.col(\"home_points\"))\n",
    "    )\n",
    ")\n",
    "game_no = list(range(1, games.height + 1))\n",
    "\n",
    "fig, ax = plt.subplots(figsize=(10, 5))\n",
    "ax.bar(game_no, games[\"margin\"], color=sdvplot.team_colors(team, \"cfb\"), width=0.7)\n",
    "sdvplot.add_logos(ax, game_no, games[\"margin\"] + 7, games[\"opponent\"], league=\"cfb\", season=SEASON, height=0.08)\n",
    "ax.set_ylim(0, games[\"margin\"].max() + 14)\n",
    "ax.set_xticks(game_no)\n",
    "ax.set(xlabel=\"Game\", ylabel=\"Margin of victory\")\n",
    "ax.spines[[\"top\", \"right\"]].set_visible(False)\n",
    "record = f\"{(games['margin'] > 0).sum()}-{(games['margin'] < 0).sum()}\"\n",
    "title_image(\n",
    "    ax,\n",
    "    team,\n",
    "    f\"Indiana's {record} national title season, {SEASON}\",\n",
    "    league=\"cfb\",\n",
    "    season=SEASON,\n",
    "    height=30,\n",
    "    loc=\"left\",\n",
    "    fontweight=\"bold\",\n",
    ")\n",
    "fig.text(\n",
    "    0.99,\n",
    "    0.01,\n",
    "    \"Data: ESPN via sportsdataverse-py | opponents' logos above each bar\",\n",
    "    ha=\"right\",\n",
    "    fontsize=8,\n",
    "    color=\"grey\",\n",
    ")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "10",
   "metadata": {},
   "source": [
    "## 5. A rivalry, season by season\n",
    "\n",
    "Ohio State against Michigan since 2004 (`load_cfb_schedule` for 22 seasons). Each bar is the margin from Ohio\n",
    "State's side, colored for the winner, with the winner's logo at the end of the bar."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "11",
   "metadata": {},
   "outputs": [],
   "source": [
    "osu, mich = sdvplot.resolve([\"Ohio State\", \"Michigan\"], \"cfb\")\n",
    "history = cfb.load_cfb_schedule(list(range(2004, SEASON + 1))).with_columns(pl.col(\"home_id\", \"away_id\").cast(pl.Utf8))\n",
    "rivalry = (\n",
    "    history.filter(pl.col(\"home_id\").is_in([osu, mich]), pl.col(\"away_id\").is_in([osu, mich]), pl.col(\"completed\"))\n",
    "    .with_columns(\n",
    "        osu_margin=pl.when(pl.col(\"home_id\") == osu)\n",
    "        .then(pl.col(\"home_points\") - pl.col(\"away_points\"))\n",
    "        .otherwise(pl.col(\"away_points\") - pl.col(\"home_points\"))\n",
    "    )\n",
    "    .with_columns(winner=pl.when(pl.col(\"osu_margin\") > 0).then(pl.lit(osu)).otherwise(pl.lit(mich)))\n",
    "    .sort(\"season\")\n",
    ")\n",
    "\n",
    "fig, ax = plt.subplots(figsize=(10, 5))\n",
    "ax.bar(rivalry[\"season\"], rivalry[\"osu_margin\"], color=sdvplot.team_colors(rivalry[\"winner\"], \"cfb\").to_list())\n",
    "tip = rivalry[\"osu_margin\"] + pl.Series([8 if m > 0 else -8 for m in rivalry[\"osu_margin\"]])\n",
    "sdvplot.add_logos(ax, rivalry[\"season\"], tip, rivalry[\"winner\"], league=\"cfb\", season=SEASON, height=0.08)\n",
    "ax.axhline(0, color=\"black\", lw=0.8)\n",
    "ax.text(2020, 2, \"no game\", ha=\"center\", fontsize=8, color=\"grey\", rotation=90, va=\"bottom\")\n",
    "ax.set_ylim(-35, 50)\n",
    "ax.set(ylabel=\"Ohio State margin\")\n",
    "ax.spines[[\"top\", \"right\"]].set_visible(False)\n",
    "ax.set_title(\"The Game: Ohio State vs Michigan, 2004-2025\", loc=\"left\", fontweight=\"bold\")\n",
    "fig.text(0.99, 0.01, \"Data: ESPN via sportsdataverse-py\", ha=\"right\", fontsize=8, color=\"grey\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12",
   "metadata": {},
   "source": [
    "## 6. FBS, FCS and the schools sdvplot does not know\n",
    "\n",
    "The index has every FBS and FCS program and most of Division II and III. The 2025 schedule also lists opponents\n",
    "outside the NCAA divisions (NAIA schools, mostly); most of those do not resolve, and sdvplot says so in one warning\n",
    "instead of guessing."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "13",
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings\n",
    "\n",
    "opponents = pl.concat(\n",
    "    [\n",
    "        schedule.select(team_id=\"home_id\", division=\"home_division\"),\n",
    "        schedule.select(team_id=\"away_id\", division=\"away_division\"),\n",
    "    ]\n",
    ").unique(\"team_id\", maintain_order=True)\n",
    "\n",
    "with warnings.catch_warnings(record=True) as caught:\n",
    "    warnings.simplefilter(\"always\")\n",
    "    resolved = sdvplot.resolve(opponents[\"team_id\"], \"cfb\")\n",
    "print(str(caught[0].message)[:160], \"...\")\n",
    "\n",
    "(\n",
    "    opponents.with_columns(found=resolved.is_not_null())\n",
    "    .group_by(\"division\", maintain_order=True)\n",
    "    .agg(teams=pl.len(), in_sdvplot=pl.col(\"found\").sum())\n",
    "    .sort(\"teams\", descending=True)\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "14",
   "metadata": {},
   "source": [
    "FCS teams have their own logos and colors, so an FCS-over-FBS upset draws like any other game. In 2025 the FCS won\n",
    "four of its games against FBS teams:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "15",
   "metadata": {},
   "outputs": [],
   "source": [
    "cross = schedule.filter(\n",
    "    pl.col(\"completed\"),\n",
    "    pl.concat_list(\"home_division\", \"away_division\").list.sort() == [\"fbs\", \"fcs\"],\n",
    ")\n",
    "fcs_home = pl.col(\"home_division\") == \"fcs\"\n",
    "upsets = cross.filter(pl.when(fcs_home).then(\"home_winner\").otherwise(\"away_winner\")).select(\n",
    "    winner=pl.when(fcs_home).then(\"home_id\").otherwise(\"away_id\"),\n",
    "    loser=pl.when(fcs_home).then(\"away_id\").otherwise(\"home_id\"),\n",
    "    score=pl.format(\n",
    "        \"{}-{}\", pl.max_horizontal(\"home_points\", \"away_points\"), pl.min_horizontal(\"home_points\", \"away_points\")\n",
    "    ),\n",
    "    week=\"week\",\n",
    ")\n",
    "\n",
    "rows = list(range(upsets.height, 0, -1))\n",
    "fig, ax = plt.subplots(figsize=(7, 4.5))\n",
    "ax.set(xlim=(0, 1), ylim=(0.4, upsets.height + 0.6))\n",
    "ax.axis(\"off\")\n",
    "sdvplot.add_logos(ax, [0.2] * upsets.height, rows, upsets[\"winner\"], league=\"cfb\", season=SEASON, height=0.17)\n",
    "sdvplot.add_logos(ax, [0.8] * upsets.height, rows, upsets[\"loser\"], league=\"cfb\", season=SEASON, height=0.17)\n",
    "for y, row in zip(rows, upsets.iter_rows(named=True), strict=True):\n",
    "    ax.text(0.5, y, f\"{row['score']}\\nweek {row['week']}\", ha=\"center\", va=\"center\", fontsize=11)\n",
    "ax.text(0.2, upsets.height + 0.55, \"FCS winner\", ha=\"center\", fontweight=\"bold\")\n",
    "ax.text(0.8, upsets.height + 0.55, \"FBS loser\", ha=\"center\", fontweight=\"bold\")\n",
    "ax.set_title(\n",
    "    f\"FCS over FBS: {upsets.height} upsets in {cross.height} games, {SEASON}\", loc=\"left\", fontweight=\"bold\", pad=18\n",
    ")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16",
   "metadata": {},
   "source": [
    "## 7. An interactive Altair chart\n",
    "\n",
    "The cfbfastR opponent-adjusted ratings (`load_cfb_ratings`) as an Altair chart with tooltips. `add_logos` layers the\n",
    "logos onto the chart; the transparent points underneath carry the tooltips."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "17",
   "metadata": {},
   "outputs": [],
   "source": [
    "import altair as alt\n",
    "\n",
    "ratings = (\n",
    "    cfb.load_cfb_ratings([SEASON])\n",
    "    .with_columns(pl.col(\"team_id\").cast(pl.Utf8))\n",
    "    .join(names, on=\"team_id\")\n",
    "    .select(\"team_id\", \"school\", \"adj_off_epa\", \"adj_def_epa\", \"adj_net\", \"net_rank\")\n",
    ")\n",
    "\n",
    "points = (\n",
    "    alt.Chart(ratings.to_pandas())\n",
    "    .mark_circle(size=250, opacity=0)\n",
    "    .encode(\n",
    "        x=alt.X(\"adj_off_epa\", title=\"Adjusted offense EPA per play\"),\n",
    "        y=alt.Y(\"adj_def_epa\", title=\"Adjusted defense EPA per play (better is up)\", scale=alt.Scale(reverse=True)),\n",
    "        tooltip=[\"school\", \"net_rank\", alt.Tooltip(\"adj_net\", format=\".3f\")],\n",
    "    )\n",
    "    .properties(width=640, height=440, title=f\"Opponent-adjusted FBS ratings, {SEASON}\")\n",
    ")\n",
    "sdvplot.add_logos(\n",
    "    points, ratings[\"adj_off_epa\"], ratings[\"adj_def_epa\"], ratings[\"team_id\"], league=\"cfb\", season=SEASON, height=0.05\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "18",
   "metadata": {},
   "source": [
    "## 8. A ranked bar chart with logos on the y axis\n",
    "\n",
    "ESPN's final 2025 FPI (`load_cfb_fpi_weekly`, the snapshot after the title game), top 25. The bars use the teams'\n",
    "ESPN colors and `axis_logos(ax, \"y\")` replaces the team ids on the axis."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "19",
   "metadata": {},
   "outputs": [],
   "source": [
    "fpi = cfb.load_cfb_fpi_weekly([SEASON]).filter(pl.col(\"season_type\") == 3)\n",
    "top = (\n",
    "    fpi.select(pl.col(\"team_id\").cast(pl.Utf8), \"fpi\")\n",
    "    .sort(\"fpi\", descending=True)\n",
    "    .head(25)\n",
    "    .reverse()  # barh draws from the bottom up, so number one ends on top\n",
    ")\n",
    "\n",
    "fig, ax = plt.subplots(figsize=(9, 6))\n",
    "ax.barh(top[\"team_id\"], top[\"fpi\"], color=sdvplot.team_colors(top[\"team_id\"], \"cfb\").to_list())\n",
    "for y, value in enumerate(top[\"fpi\"]):\n",
    "    ax.text(value + 0.3, y, f\"{value:.1f}\", va=\"center\", fontsize=8)\n",
    "sdvplot.axis_logos(ax, \"y\", league=\"cfb\", season=SEASON, height=0.04)\n",
    "ax.spines[[\"top\", \"right\"]].set_visible(False)\n",
    "ax.set_xlabel(\"FPI (points better than an average FBS team)\")\n",
    "ax.set_title(f\"Final FPI top 25, {SEASON}\", loc=\"left\", fontweight=\"bold\")\n",
    "fig.text(0.99, 0.01, \"Data: ESPN FPI via sportsdataverse-py\", ha=\"right\", fontsize=8, color=\"grey\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20",
   "metadata": {},
   "source": [
    "## 9. Tiers of a ranking you compute\n",
    "\n",
    "A composite ranking: the average of each team's rank in two systems, cfbfastR's adjusted net EPA and FEI. The top\n",
    "32 go into five tiers with `team_tiers`, on its light theme: Ohio State's, Texas A&M's and Penn State's dark logos\n",
    "vanish on the default dark one."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "21",
   "metadata": {
    "sdvplot_gallery": {
     "alt": "Tier list of the top 32 college football teams of 2025 by a composite ranking, five rows of logos",
     "title": "College football tiers"
    },
    "tags": [
     "gallery"
    ]
   },
   "outputs": [],
   "source": [
    "from sdvplot.matplotlib import team_tiers\n",
    "\n",
    "composite = (\n",
    "    cfb.load_cfb_ratings([SEASON])\n",
    "    .with_columns(pl.col(\"team_id\").cast(pl.Utf8), score=(pl.col(\"net_rank\") + pl.col(\"fei_net_rank\")) / 2)\n",
    "    .sort(\"score\", \"net_rank\", \"team_id\")\n",
    "    .head(32)\n",
    ")\n",
    "sizes = [4, 6, 7, 7, 8]  # teams per tier, top to bottom\n",
    "composite = composite.with_columns(tier_no=pl.Series([tier for tier, n in enumerate(sizes, start=1) for _ in range(n)]))\n",
    "\n",
    "fig = team_tiers(\n",
    "    composite.select(\"tier_no\", team=\"team_id\"),\n",
    "    \"cfb\",\n",
    "    title=f\"College football tiers, {SEASON}\",\n",
    "    subtitle=\"average rank in adjusted net EPA and FEI\",\n",
    "    caption=CAPTION,\n",
    "    alpha=1,\n",
    "    theme=\"light\",\n",
    "    tier_desc={1: \"Elite\", 2: \"Contenders\", 3: \"Very good\", 4: \"Good\", 5: \"Solid\"},\n",
    ")\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  },
  "sdvplot": {
   "description": "Nine college football charts and tables: 136 FBS logos, conferences, a rivalry, FCS upsets, tiers.",
   "label": "College football",
   "position": 11
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
