The Question

Can a simple random model predict how many goals an NHL team scores in a game?

Why ask?

It’s 2–2 late in the third period, and the broadcaster says the home team is “due” for a goal. But if goals are truly random, there is no such thing as “due.” The puck has no memory.

How statistics can help

  • Model: treat goals as random events using the Poisson distribution
  • Estimate: find the average scoring rate from real games
  • Test: check the model against all 1,312 games of the 2025–26 season
  • Predict: use it to forecast scores and overtime games, then compare with what really happened

The Statistical Idea: Counting Rare Events

The Poisson distribution models counts of rare events in a fixed window of time, such as calls to a help desk, typos on a page, or goals in a hockey game.

It assumes events:

  • happen one at a time
  • arrive at a constant average rate \(\lambda\)
  • are independent of each other

Hockey looks like perfect Poisson territory. A game has 3,600 seconds and each team takes about 30 shots, but only about 1 in 10 goes in. That means lots of chances, each with a tiny probability.

The Mathematics (1): From 3,600 Seconds to Poisson

Slice the game into \(n\) tiny moments, each with a goal probability of \(\lambda / n\). The number of goals is Binomial, and as the slices shrink:

\[P(X = k) = \binom{n}{k}\left(\frac{\lambda}{n}\right)^k\left(1-\frac{\lambda}{n}\right)^{n-k} \;\xrightarrow{\;n \to \infty\;}\; \frac{e^{-\lambda}\lambda^k}{k!}, \qquad k = 0, 1, 2, \dots\]

The Poisson’s signature property:

\[E[X] = \lambda \qquad \text{and} \qquad \operatorname{Var}(X) = \lambda\]

If goals really are Poisson, the mean and variance should be about equal. That gives us an easy first check.

R Code: Loading and Cleaning the Data

# 2025-26 NHL regular season, exported from Hockey-Reference.com
games_raw <- read.csv("nhl_2025_26.csv")

# give the unnamed columns clear names (by position)
names(games_raw)[c(4, 6, 7)] <- c("away_G", "home_G", "extra")

games <- games_raw %>%
  filter(!is.na(away_G), !is.na(home_G)) %>%     # drop blank/footer rows
  mutate(past_60  = extra != "",                  # TRUE if OT or shootout
         # the OT/SO winner gets 1 extra goal, so take it away
         home_reg = home_G - (past_60 & home_G > away_G),
         away_reg = away_G - (past_60 & away_G > home_G))

# one goal count per team per game
team_goals <- c(games$home_reg, games$away_reg)

head(games[, c("Visitor", "away_G", "Home", "home_G",
               "extra", "away_reg", "home_reg")])
              Visitor away_G                 Home home_G extra away_reg
1  Chicago Blackhawks      2     Florida Panthers      3              2
2  Colorado Avalanche      4    Los Angeles Kings      1              4
3 Pittsburgh Penguins      3     New York Rangers      0              3
4      Calgary Flames      4      Edmonton Oilers      3    SO        3
5  Montreal Canadiens      2  Toronto Maple Leafs      5              2
6   Los Angeles Kings      6 Vegas Golden Knights      5    SO        5
  home_reg
1        3
2        1
3        0
4        3
5        5
6        5

The Data

  • 1,312 games from the 2025–26 NHL regular season (Hockey-Reference.com)
  • Each game gives two counts (home and away), for a total of 2,624 team-games
  • We count regulation goals only, so every game is the same 60-minute window

The shootout problem: 207 games ended in overtime and 119 in a shootout. The winner gets +1 on the final score, but a shootout “goal” is not scored during play.

Fix: if a game went past 60 minutes, subtract 1 from the winner’s score. This recovers the regulation score, which is always a tie.

Example: Flames 4–3 Oilers (SO) becomes 3–3 after 60 minutes.

The Mathematics (2): Estimating \(\lambda\)

For goal counts \(x_1, \dots, x_n\), the log-likelihood is

\[\ell(\lambda) = \sum_{i=1}^{n}\left(x_i \log\lambda - \lambda - \log x_i!\right) \quad\Rightarrow\quad \ell'(\lambda) = \frac{\sum x_i}{\lambda} - n = 0 \quad\Rightarrow\quad \hat\lambda = \bar{x}\]

The best estimate of the scoring rate is just the average goals per game:

\[\hat\lambda = 3.003, \qquad \text{SE}(\hat\lambda) = \sqrt{\hat\lambda / n} = 0.034, \qquad 95\%\ \text{CI: } (2.94,\ 3.07)\]

Model vs. Reality

3 goals was the most common result: 606 team-games, compared with 588 predicted by the model. In every group, the model lands within about 35 games of reality, out of 2,624 team-games. The biggest misses: teams scored exactly 1 goal more often than predicted (425 vs. 391), and exactly 4 goals less often (406 vs. 441).

Testing the Fit: Chi-Square Goodness of Fit

\(H_0\): goals per team follow a Poisson(\(\lambda\)) distribution.

\[X^2 = \sum_{k} \frac{(O_k - E_k)^2}{E_k} = 12.03, \qquad df = 8 \text{ groups} - 1 - 1 \text{ estimated } \lambda = 6, \qquad p = 0.061\]

What we found:

  • The 1-goal and 4-goal groups produce about 48% of the \(X^2\) total. Every other group is very close to the model.
  • p = 0.061: if goals truly were Poisson, we would see gaps this big about 6% of the time just by chance.
  • Since 0.061 > 0.05, we fail to reject \(H_0\). The Poisson model fits well, though not perfectly. The p-value sits close to the cutoff, so the small gaps are worth noticing.

Is There Home-Ice Advantage?

Home teams scored 3.07 regulation goals per game and away teams scored 2.94, a gap of 0.13 goals. Over a team’s 41 home games, that adds up to about 5.5 extra goals per season. The 95% CI for the gap, (0.002, 0.268), stays just above 0, so home ice is a real but small advantage.

Every Possible Scoreline (Interactive 3D)

Surface = Poisson model, gold dots = real games. Drag to rotate and hover for details. Notice the tied scores along the diagonal: many of those dots float above the surface.

Most common score after 60 minutes: 3–3 (9% of games).

Plot Twist: Can the Model Predict Overtime?

If home and away goals were independent Poissons, the chance of a tie after 60 minutes is

\[P(\text{tied after 60}) = \sum_{k=0}^{\infty} P(H = k)\,P(A = k)\]

After 60 minutes Poisson prediction What happened
Tied (goes to OT/SO) 16.6% 24.8%
Home team leads 43.8% 40%
Away team leads 39.5% 35.1%

The model expected about 218 games to go past regulation. In reality, 326 did.

Why Are Ties So “Sticky”?

  • The gap: 326 of 1,312 games were tied after 60 minutes (207 OT + 119 shootouts). That is about 108 more than the model’s 218, or roughly 1 extra overtime game for every 12 games played.
  • Where they came from: the model over-predicted regulation wins. Home leads dropped from 43.8% predicted to 40%, and away leads dropped from 39.5% to 35.1%.
  • Why: the model assumes the two teams’ goals are independent, but real teams react to the score:
    • In a tie late in the game, both teams play safe, because getting to overtime guarantees a point.
    • A team that is behind pulls its goalie and pushes hard to tie it.

Each team’s goals look random on their own, but the two teams react to each other.

What Did We Learn?

  • Goals per team are close to Poisson. Mean = 3, variance = 2.96 (ratio 0.99), and the chi-square test did not reject the model (p = 0.061).
  • A typical team scores about 3 goals in regulation, 95% CI (2.94, 3.07).
  • Home ice is worth about 0.13 goals per game, roughly 5.5 extra goals per season.
  • The model misses ties. It predicted 16.6% of games would go to overtime, but 24.8% did.

Bottom line: no team is ever “due.” Scoring has no memory, but players do. They watch the score, and that shows up in the data.

Data: Hockey-Reference.com, 2025–26 NHL regular season. Built with R, ggplot2, and plotly.

Bonus: Three Seasons, Same Story?

Season Goals / team Var ÷ mean Chi-sq p Home edge Ties predicted Ties actual
2023–24 3.01 1.03 0.234 0.21 16.6% 20.7%
2024–25 2.94 1.03 0.055 0.30 16.7% 20.7%
2025–26 3.00 0.99 0.061 0.13 16.6% 24.8%
  • Poisson fits for the last 3 seasons: variance ≈ mean (0.99–1.03), and no season rejects the model (p = 0.234, 0.055, 0.061).
  • Home ice always helps: 0.21, 0.30, then 0.13 goals per game.
  • Ties always beat the model: about 4 points above it in the first two seasons, and 8 points above it in 2025–26, the stickiest season of the three.