Year in Review: The 2025-26 NHL Season

Hockey
Featured
Season Recap
A full season of NHL boxscore data, pulled through the hockey pipeline into BigQuery: the scoring race, the goaltending duels, and how the league’s best regular-season team still didn’t win it all.
Published

July 8, 2026

The hockey pipeline has now pulled a complete season: every regular-season and playoff game from the 2025-26 NHL season, backfilled game by game into BigQuery. This post is the first real analysis built on top of it, a look back at the season through the same data the daily pipeline keeps adding to.

One wrinkle worth flagging up front: the NHL’s schedule API also listed games from the Milano Cortina Winter Olympics during the league’s February break, tagged with a different game type than regular-season or playoff games. Those are country-vs-country games, not NHL team games, so they’re filtered out of everything below.

Querying the season

from google.cloud import bigquery

client = bigquery.Client()

query = """
SELECT
  player_name,
  team,
  SUM(goals) AS goals,
  SUM(assists) AS assists,
  SUM(points) AS points,
  COUNT(DISTINCT game_id) AS games_played
FROM `maydaystats.nhl_stats.boxscores`
WHERE CAST(SUBSTR(CAST(game_id AS STRING), 5, 2) AS INT64) IN (2, 3)
  AND position_group != 'goalie'
GROUP BY player_name, team
ORDER BY points DESC
LIMIT 10
"""

scorers = client.query(query).to_dataframe()
scorers
player_name team goals assists points games_played
0 N. MacKinnon COL 64.0 86.0 150.0 93
1 C. McDavid EDM 49.0 98.0 147.0 88
2 N. Kucherov TBL 47.0 91.0 138.0 84
3 N. Suzuki MTL 33.0 89.0 122.0 101
4 M. Necas COL 42.0 79.0 121.0 91
5 J. Eichel VGK 31.0 88.0 119.0 96
6 M. Celebrini SJS 45.0 73.0 118.0 82
7 D. Pastrnak BOS 33.0 79.0 112.0 83
8 K. Kaprizov MIN 52.0 59.0 111.0 89
9 M. Marner VGK 34.0 77.0 111.0 103

N. MacKinnon led the league with 150 points (64 goals, 86 assists) across 93 games, including the playoffs. Connor McDavid finished a close second, and Colorado and Vegas each placed two players in the top ten scorers, more than any other team.

The top ten, by points

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(9, 5))
labels = scorers["player_name"] + " (" + scorers["team"] + ")"
ax.barh(labels[::-1], scorers["points"][::-1], color="#2c3e50")
ax.set_xlabel("Points")
ax.set_title("Top 10 Scorers, 2025-26 Season")
ax.spines[["top", "right"]].set_visible(False)
plt.tight_layout()
plt.show()
Figure 1: Top 10 point scorers, 2025-26 regular season and playoffs combined

The best record didn’t win the Cup

standings_query = """
SELECT
  team,
  COUNTIF(decision = 'W') AS wins,
  COUNTIF(decision = 'L') AS losses
FROM `maydaystats.nhl_stats.boxscores`
WHERE CAST(SUBSTR(CAST(game_id AS STRING), 5, 2) AS INT64) = 2
  AND position_group = 'goalie'
  AND decision IS NOT NULL
GROUP BY team
ORDER BY wins DESC
LIMIT 5
"""

standings = client.query(standings_query).to_dataframe()
standings
team wins losses
0 COL 58 16
1 CAR 55 22
2 DAL 52 20
3 TBL 51 28
4 MTL 50 25

Colorado had the best regular-season record in the league at 58-16, built in part on Nathan MacKinnon’s scoring, and Carolina wasn’t far behind. But the regular season and the playoffs are different tournaments. Colorado swept the Kings in the first round and beat Minnesota 4 games to 1 in the second, then ran into Vegas in the Western Conference Final and lost in a sweep of their own. Carolina kept going all the way to the Stanley Cup Final, where they met that same Vegas team.

What actually happened to Colorado

col_series_query = """
SELECT game_id, team, SUM(goals) AS goals
FROM `maydaystats.nhl_stats.boxscores`
WHERE game_id IN (2025030321, 2025030322, 2025030323, 2025030324)
GROUP BY game_id, team
ORDER BY game_id, team
"""
col_series = client.query(col_series_query).to_dataframe()
col_series
game_id team goals
0 2025030321 COL 2.0
1 2025030321 VGK 4.0
2 2025030322 COL 1.0
3 2025030322 VGK 3.0
4 2025030323 COL 3.0
5 2025030323 VGK 5.0
6 2025030324 COL 1.0
7 2025030324 VGK 2.0

Four games, four Vegas wins: 4-2, 3-1, 5-3, 2-1. It wasn’t a case of Colorado getting run off the ice, three of the four games were decided by a single goal or two, but a sweep is a sweep, and the box scores point at two specific reasons rather than a generic cold streak.

goalie_query = """
SELECT game_id, team, player_name, save_pctg
FROM `maydaystats.nhl_stats.boxscores`
WHERE game_id IN (2025030321, 2025030322, 2025030323, 2025030324)
  AND position_group = 'goalie'
  AND save_pctg IS NOT NULL
ORDER BY game_id, team
"""
goalies = client.query(goalie_query).to_dataframe()
goalies
game_id team player_name save_pctg
0 2025030321 COL S. Wedgewood 0.888889
1 2025030321 VGK C. Hart 0.947368
2 2025030322 COL S. Wedgewood 0.916667
3 2025030322 VGK C. Hart 0.967742
4 2025030323 COL S. Wedgewood 0.818182
5 2025030323 VGK C. Hart 0.914286
6 2025030324 COL M. Blackwood 0.923077
7 2025030324 VGK C. Hart 0.952381

Vegas starter Cam Hart never had a bad night: a .947 save percentage in Game 1, .968 in Game 2, .914 in Game 3, .952 in Game 4, four consecutive starts above his own regular-season form. Colorado’s own goaltending was more uneven than collapsed: Scott Wedgewood started the first three games and posted a .908 save percentage across the regular season, but ranged from .889 to .917 to .818 in this series, a rough Game 3 more than a full collapse. Mackenzie Blackwood took over for Game 4 and actually outplayed his own .894 season average, stopping .923. Colorado’s goaltending wasn’t the story on its own. Vegas’s goalie having the series of his season was.

mackinnon_query = """
SELECT COUNT(DISTINCT game_id) AS games, SUM(goals) AS goals,
  SUM(assists) AS assists, SUM(points) AS points
FROM `maydaystats.nhl_stats.boxscores`
WHERE player_name = 'N. MacKinnon' AND team = 'COL'
  AND game_id IN (2025030321, 2025030322, 2025030323, 2025030324)
"""
mackinnon_series = client.query(mackinnon_query).to_dataframe()
mackinnon_series
games goals assists points
0 4 0.0 2.0 2.0

The bigger story is Nathan MacKinnon, the same player whose scoring this post already credited with carrying Colorado’s regular season. Across 80 regular-season games he scored 57 goals, a .71-goal-per-game pace. Across the four-game sweep against Vegas he scored 0. Zero. Two assists, two total points, in the series that ended his team’s season. Gabriel Landeskog led the team in goals with 3, and tied Nazem Kadri and Devon Toews for the team lead in points at 3 apiece, but a team built around its best player suddenly not scoring, against a goalie who was outplaying his own season averages every single night, is a specific, findable reason a 58-win team went home in May rather than a vague “playoffs are different” shrug.

final_query = """
WITH final_games AS (
  SELECT game_id, game_date, team, SUM(goals) AS team_goals
  FROM `maydaystats.nhl_stats.boxscores`
  WHERE CAST(SUBSTR(CAST(game_id AS STRING), 5, 2) AS INT64) = 3
    AND team IN ('CAR', 'VGK')
  GROUP BY game_id, game_date, team
)
SELECT
  game_date,
  MAX(IF(team = 'CAR', team_goals, NULL)) AS car_goals,
  MAX(IF(team = 'VGK', team_goals, NULL)) AS vgk_goals
FROM final_games
GROUP BY game_date
HAVING car_goals IS NOT NULL AND vgk_goals IS NOT NULL
ORDER BY game_date
"""

final = client.query(final_query).to_dataframe()
final
game_date car_goals vgk_goals
0 2026-05-04 3.0 3.0
1 2026-06-02 4.0 5.0
2 2026-06-04 4.0 3.0
3 2026-06-06 4.0 5.0
4 2026-06-09 5.0 3.0
5 2026-06-11 4.0 2.0
6 2026-06-14 3.0 0.0

The Final went six games, and three of them (Games 1 through 3) were decided by a single goal. Carolina closed it out on June 14 with a 3-0 shutout, taking the series 4 games to 2. Vegas rode Cam Hart the entire playoffs, all 22 games across all four rounds, all the way through the Final loss. Carolina’s path in goal was less steady: Frederik Andersen started most of the postseason, including the first two games of the Final, but Carolina turned to Brandon Bussi for Game 3, which Bussi lost, before he settled in and won Games 4, 5, and 6 to close out the series. Neither goalie led the league in wins during the regular season; that distinction was an exact tie between Tampa Bay’s Andrei Vasilevskiy and Utah’s Karel Vejmelka, both at 39.

What’s next

This post covers the season at a high level: scoring, standings, and the playoff run. The same table supports much narrower questions too, like how a specific team’s goaltending held up across back-to-backs, or how a rookie’s production changed after the Olympic break. Those are posts for another day, now that a full season of clean data is sitting in BigQuery.

The pipeline code behind this post lives here.

Note

Like the baseball posts, this one uses Quarto’s frozen execution (freeze: auto): the queries above ran once, locally, against BigQuery, and the deployed site reuses that committed output rather than re-querying on every build.