About the Numbers

How this site calculates Expected Goals (xG) and goals above expected, Corsi/Fenwick shot attempts, era-adjusted GF+/GA+, PDO and its sustainable rate, and Pythagorean expected points for the Sabres, plus a glossary of the hockey analytics terms fans run into most.

About this site

Buffalo Goal is an independent fan project, built for fun and the love of Buffalo hockey. It isn't affiliated with, endorsed by, or connected to the NHL, the Buffalo Sabres, or any team. The numbers here are an unofficial, best-effort estimate for fun, not a statistic from the league or the team.

Expected Goals (xG)

The short version

Not every shot is equally likely to score. A point-shot through traffic and a wide-open one-timer from the slot are very different scoring chances, even though both count as one shot on goal. Expected Goals (xG) assigns each shot a probability, somewhere between 0 and 1, of becoming a goal, based on where it came from and the situation it was taken in. Add up every shot's xG over a game and you get a measure of shot quality, not just shot quantity.

How this site calculates it

Every unblocked shot attempt (a shot on goal, a missed shot, or a goal) is scored by a gradient-boosted model (LightGBM) built from roughly 360,000 shots of NHL play-by-play data across three seasons, 2023-24 through 2025-26: regular season and playoffs, league-wide, not just Sabres games. Three recent seasons ranked shots better in testing than a ten-season history did, so the older seasons were left out. A league-wide dataset is necessary for the model to learn what actually separates a good scoring chance from a low-danger one. Separate curves per situation (even strength, power play, shorthanded, and empty net, the last split further by distance) map the model's raw output onto the actual rate those shots go in, with an extra curve for redirects and tip-ins, which the model over-rates by about a tenth. Those curves are fit by cross-validation across the three seasons, so each shot's curve comes from a fit that never saw it, and they are re-fit each month on recent games so a game's xG stays anchored to how often shots are really going in this season.

Accuracy is measured honestly: every shot used to check the model, and to fit its probability curves, is scored by a fit that never saw that shot, so the check isn't graded on its own homework. Distance and angle are also constrained so that, all else equal, a shot's xG can never go up as it moves farther from the net or to a worse angle.

The model does not know who took the shot or who was in net. That's deliberate: the goal is to measure the danger of the chance itself, for an average shooter against an average goalie, so the number can be used to judge shooters and goalies rather than bake their skill in.

What goes into a shot's xG

What's left out

Known limitations

The NHL's public play-by-play feed doesn't include player-tracking data, so the model has no way to see a screen in front of the goalie, defensive pressure, traffic, or a cross-ice pass right before the shot. A wide-open snap shot and a heavily screened one from the same spot on the ice look identical to it. It can see how long the shooter and the defenders had been on the ice, but not what they were doing out there. It also has no read on lineups or which goalie is in net, by design.

Deflections and tip-ins are a specific blind spot: the feed logs them as a single event, with no record of the pass or point shot that was redirected, so the model has to lean on the shot type and location alone. It over-rates them by about a tenth, so redirects get their own calibration curve to pull the totals back in line, but a single redirect's xG is still among the shakier numbers here.

The model is checked by cross-validation across the three seasons it was built on, with every shot graded by a fit that never saw it. Sorted by predicted value, each group scores close to the rate the model assigned it, from the low single digits up through the 30 percent range. Because the situation curves are re-fit each month against the current season, a team's season xG stays within a couple of percent of the goals actually scored; on a fresh season, before that re-fit, it can run a few percent high. It is a better guide to season-long play than to a single game. The rougher spots: power-play shot ranking (with no pre-shot passing in the feed, the model can't tell a wide-open one-timer from a point shot) and shots from beyond about 60 feet, which still read a little high. Treat xG as a useful estimate of shot quality, not a precise or official one.

The numbers here will not match MoneyPuck, Natural Stat Trick, Evolving-Hockey, or the other established public xG models, and often not by a small amount. Every model makes its own choices about which shots to count, what goes into the estimate, and how it is calibrated, so no two public xG figures line up exactly. This one is an independent, best-effort attempt at building a model from the same public play-by-play data, worked out from scratch rather than copied from any of them. It is not meant to replace the bigger public models, which have had far more scrutiny. Use whichever you trust, but do not expect the totals to agree.

Data source

Play-by-play data comes from the NHL's public API, api-web.nhle.com, the same feed that powers live scores elsewhere on this site.

Sabres xG in context

xG is most useful as a comparison, not a single number in isolation. Buffalo's season-by-season xG table shows team-level shot quality for and against back to 2016-17, so you can see whether a given Sabres season's results lined up with the underlying shot quality, or diverged from it (a team can out-chance opponents and still lose, or the reverse). Every individual game page from that point on breaks the same thing down shot by shot, with a running xG chart alongside the actual score.

Because the model only covers 2016-17 onward, it can't say anything about how, say, the 1990s Sabres would grade out by today's shot-quality standards. That's simply outside what the NHL's public play-by-play feed provides for older seasons. Everything before 2016-17 on this site (records, standings, player stats, head-to-head history) is still fully tracked; xG specifically just isn't one of those things for the older eras.

Goals above and below expected

Once every shot has an xG, two follow-on numbers fall out. For a skater, GAx (goals above expected) is their actual goals minus the total xG of their shots. A player with 20 goals on shots worth 14 xG has a GAx of +6: they finished six more than an average shooter would have from those same chances, whether through skill, a hot stretch, or plain puck luck. For a goalie, GSAx (goals saved above expected) flips it, the total xG of every shot faced minus the goals actually allowed, so a positive number means stopping more than the shots on average should have gone in.

Both appear on the skater and goalie tables on Team Stats, on player pages, and behind the finishing-luck band on the Goals vs xG scatter. The same caveat as xG itself applies, only more so: one season of GAx is still a smallish sample, and a good share of any player's number is noise that won't repeat.

Shot Attempts (Corsi & Fenwick)

The short version

Before xG existed, analysts needed a cheap stand-in for puck possession: count every shot attempt (a shot on goal, a miss, or a blocked shot) for each team while a specific group of skaters is on the ice. That count is Corsi. Drop the blocked shots and count only shots on goal plus misses, and it's called Fenwick instead. Neither knows anything about shot quality, the way xG does. What they're good at is volume: who actually had the puck and was doing something with it.

How this site calculates it

Most sites only report Corsi/Fenwick at the team or individual-player level. This site goes one step further and attributes every 5-on-5 shot attempt to the specific forward line or defense pair that was actually on the ice for it, reconstructed from the NHL's public shift-chart data (who was on the ice, and when) intersected with the play-by-play (what happened, and under what game state). That's what powers the Line Combinations table on every game page and on Team Stats, sortable by CF%, CF% Rel (a combo's CF% compared to the team's own CF% when that combo was off the ice), xG%, and more, with an adjustable minimum-ice-time filter so a real, trusted sample can be told apart from a couple of noisy shifts.

Independently checked against another publicly available source of Corsi/xG numbers for the same players and lines: on-ice time matched within a minute or two in every case tested, and CF% matched within a couple of tenths of a point.

Which shot count is which

Three different “shots” numbers turn up around the site, and they aren't supposed to agree with each other:

CountWhat it includesWhere you see it
Shots on goalShots that hit the net or scored. The official NHL count, all situations, whole game.The box-score line on a game page; season shot totals.
Shot attempts (Corsi)Shots on goal plus missed shots plus blocked shots, counted at 5-on-5.A game page's “5v5 Game Control” and Line Combinations CF%; the Game Flow chart.
Unblocked attempts (Fenwick)Shots on goal plus missed shots, at 5-on-5. Blocks removed.The Game Flow chart's “Unblocked” toggle; FF% columns.

So a game where Buffalo is out-shot on goal but wins the shot-attempt share just means a lot of its tries were blocked or missed. xG is scored per shot on goal and summed, a narrower set still than the attempts CF% counts.

Known limitations

Shift-chart data isn't available for every season this site otherwise covers, so line-level Corsi/ Fenwick is a modern-era feature, not something tracked back through Sabres history the way box-score totals are. A line/pair's xG% specifically can still swing more than its CF% does on a small sample: this site's own xG model won't agree shot-for-shot with any other site's, so the two can genuinely rank a line differently even when their ice-time totals match closely.

Score and venue adjustment

Raw attempts carry the score. A trailing team shoots more and a leading team sits back, so over one game the raw share flatters whoever was losing: across 12,282 regular-season games league-wide, a team down a goal in the third period takes about 57% of the 5-on-5 attempts and a team up two takes about 42%. The site's own Win DNA study found the consequence: out-attempting the opponent over a single game came with a lower Sabres win rate, and only the share while tied ran the other way. Expected goals are already protected (the xG model reads the score state per shot), so the attempt counts are the exposed number.

The fix weights each attempt by its situation. For every regular-season game from 2016-17 to 2025-26 (shootout excluded), each attempt is filed by the shooting team's goal differential at that moment (a goal's own attempt in the state before it), whether the shooter was at home or away, and the period. The share of attempts the teams in that cell took against their opponents in the mirror cell is measured, and an attempt in the cell counts for 0.5 divided by that share. A team leading by one at home in the third takes about 44% of the attempts, so each of those counts for a little over one; a team trailing by one on the road in the third takes about 58%, so each of those counts for a little under 0.9. The pooled 5-on-5 third-period table:

Shooting team isShare at homeShare awayWeight, homeWeight, away
Down 3 or more58.6%56.0%0.8530.893
Down 258.8%56.7%0.8510.881
Down 158.3%55.7%0.8580.897
Tied51.2%48.8%0.9761.026
Up 144.3%41.7%1.1291.198
Up 243.3%41.2%1.1561.213
Up 3 or more44.0%41.4%1.1361.209

Shot attempts (Corsi) at 5-on-5, third period, 2016-17 to 2025-26 regular seasons, 12,282 games. The first and second periods carry the same shape at about half the size, and the effect is stable: season by season, the share for a team down one at 5-on-5 (all periods, both venues) stayed between 51.8% and 53.9%, with the low end in 2020-21 (empty arenas, and the home-ice term shrank to nothing that season too). The site uses the pooled table for every game, including games older than the measured seasons.

The adjusted share is a counterfactual, an estimate of what the share would have been in a tied game at a neutral site, and it is shown as a companion to the raw share, never in its place. It runs whichever way the game pushed: a team that led all night comes out higher than its raw share, a team that chased all night lower. It says nothing about which team deserved the result.

Era Adjustment

GF+ and GA+ (adjusting for the era)

Four goals a game in 1982 and four goals a game in 2004 are not the same accomplishment. League-wide scoring has swung from over eight goals per game in the early 1980s down under five and a half in the dead-puck years, and back up to about 6.2 today. GF+ rebaselines a team's goals for against the league average of its own season, with 100 set to average: a GF+ of 115 means the team scored 15% more than a league-average team did that year, 90 means 10% fewer. GA+ does the same for goals against, inverted so that a higher number is still better defense.

The math is just (team goals per game / league goals per game that season) x 100, against a fixed table of league scoring rates back to 1970-71. It's the hockey version of baseball's OPS+ or ERA+. Available as a stat on the playground's Season Trend and Compare Eras views, and on the Franchise History trend chart.

PDO

What is PDO?

A team's shooting percentage plus save percentage at 5-on-5, the two situational numbers added together so a perfectly break-even team reads as 1.000 (some sources scale this to 100 instead, describing the same thing). Shooting and save percentage both tend to even out for a team over a long enough stretch, so a team running well above 1.000 is generally getting some bounces it won't keep getting, and a team well below it is due for some better luck, more than either reflects real, repeatable team strength. It's a “regression” flag, not a measure of how good a team actually is.

Scoped to 5-on-5 only, not the whole game, since that's the situation PDO is meant to describe. Special-teams goals lean heavily on the shooting team's actual power-play or penalty-kill execution, not puck luck, and mixing them in would blur the signal.

Pulled directly from the NHL's own situational team stats, which only go back to the 2009-10 season. The league didn't track 5-on-5-specific splits at all before then, so PDO reads as unavailable (“–”) on this site for anything older. Available by season on Franchise History and Team Stats (chartable over time on Franchise History's trend chart), for a single game on any game page (computed live there from that game's own play-by-play, since the NHL's bulk situational report is season-level only), and on the home page's Buffalo History card.

Reading a PDO: the sustainable rate

Because PDO pulls so hard toward 1.000, a team's number partway through a season says little about the rest of it. A PDO far enough from 1.000 gets a projection: the observed figure regressed toward 1.000 by the amount the sample size supports, which the Team Stats summary prints beside the season's own figure, and beneath the snapshot on the home page with a rough count of how many standings points the run of luck has been worth so far.

The pull toward average is empirical-Bayes shrinkage. The observed PDO's standard error is about 0.075 / sqrt(games played); that's weighed against an estimated spread of real team-talent PDO of about 0.004, and the deviation from 1.000 is kept in proportion talent-variance / (talent-variance + error-variance). Early in a season that keeps almost none of the gap; even a full 82 games keeps only about a fifth of it. The points figure turns the raw gap into goals (roughly gap x 26 five-on-five shots per game x games) and then goals into points (about three goals per standings point), rounded hard so it never looks more precise than it is.

Pythagorean Expectation

Pythagorean expectation

A team's record tends to track its goal differential, and the Pythagorean formula makes that precise: win% = GF^2 / (GF^2 + GA^2), then expected points = win% x 2 x games played. It shows as a projected point pace next to the actual total under the season snapshot on the home page, and as the xPTS column on Standings by Year. A team finishing well above its Pythagorean pace won more one-goal games and shootouts than its overall goal margin would suggest, the kind of edge that doesn't carry cleanly from one season to the next.

This site uses the classic goal-based version. An xG-based Pythagorean is on the list for later.

Hockey analytics glossary

A quick reference for the terms fans run into when reading about hockey analytics, and which of them this site actually tracks for the Sabres.

xG (Expected Goals)

The probability, from 0 to 1, that a given shot becomes a goal, based on where and how it was taken. Summed across a game, it measures shot quality rather than raw shot volume.

Tracked for every Sabres game from 2016-17 on. See the season-by-season xG table or any individual game page.

Corsi (CF%)

The share of all shot attempts (goals, shots on goal, missed shots, and blocked shots) a team generates at a given game state, usually 5-on-5. A rough, easy-to-compute proxy for puck possession.

Tracked for every Sabres forward line and defense pair, not just the team as a whole. See the Shot Attempts section below, or jump straight to any game page's or the Team Stats Line Combinations table.

Fenwick (FF%)

The same idea as Corsi, but excluding blocked shots. Just goals, shots on goal, and missed shots.

Tracked alongside Corsi in the same Shot Attempts section and Line Combinations tables, toggleable as its own column.

Score-adjusted CF%

Shot-attempt share with every attempt weighted by how often teams in that score state, home or away, at that point of the game, take the attempts league-wide. An estimate of what the share would have been in a tied game at a neutral site, shown next to the raw share rather than instead of it.

Under the shot-attempt share on a game page's Game Control strip and as the Game Flow chart's Score-adjusted view. See the Score and venue adjustment section.

GAx (Goals Above Expected)

A skater's actual goals minus the total xG of their shots. Positive means finishing more than an average shooter would have from those chances.

See the Goals above expected section. On skater tables on Team Stats, on player pages, and behind the finishing-luck band on the Goals vs xG scatter.

GSAx (Goals Saved Above Expected)

The total xG of every shot a goalie faced minus the goals they allowed. Positive means stopping more than the shots on average should have gone in.

On the goalie tables on Team Stats and every goalie's player page.

High-danger chance

A shot attempt worth 0.12 xG or more. One threshold sitewide, so a count means the same thing wherever it appears; the scope beside it (5-on-5, power play, all situations) is what changes.

The HDCF-HDCA column on Scores, Game Control and Stats by Period on every game and series page, and the shot-map filter.

PDO

A team's shooting percentage plus save percentage at 5-on-5, which this site prints on the 1.000 scale a break-even team reads at (some sources scale the same figure to 100, or to 1000). Treated as a rough "puck luck" indicator, since it tends to regress toward that baseline over a large enough sample.

See the PDO section above for this site's exact methodology and where it's tracked.

Pythagorean expectation

A projected point total from goal differential alone: win% = GF² / (GF² + GA²), times 2 points times games played. The gap to a team's actual points is close-game and shootout luck.

Shown as a projected point pace under the season snapshot on the home page. See the Pythagorean expectation section.

GF+ / GA+ (era-adjusted)

Goals for (or against) indexed to the league average of that same season, with 100 set to average. Adjusts for how much scoring levels differ across eras, like baseball's OPS+ or ERA+.

See the Era Adjustment section. Chartable on the playground and Franchise History.

GAA (Goals Against Average)

A goalie's goals allowed per 60 minutes played.

Tracked for every Sabres goalie, every season. See any goalie's player page or the Team Stats goalie table.

SV% (Save Percentage)

The share of shots on goal a goalie stops.

Tracked alongside GAA on every goalie's player page, plus a team-wide version on the Team Stats page.

PP% / PK%

Power play percentage (goals scored per power play opportunity) and penalty kill percentage (opponent power plays successfully defended).

Tracked by season on Franchise History and Team Stats, chartable over time on Franchise History's trend chart.