Goaltender Development Tracking
Beginner
10 min read
0 views
Nov 27, 2025
Evaluating Goaltender Prospects
Goaltender development is notoriously unpredictable compared to skaters. Statistical analysis can help identify prospects with the highest NHL potential by tracking key metrics through their development path and comparing them to historical success patterns.
Key Goalie Evaluation Metrics
- Save Percentage (Sv%): Primary performance indicator
- Goals Against Average (GAA): Context-dependent measure
- Quality Start %: Consistency metric (Sv% > .913 per game)
- High-Danger Save %: Performance on difficult saves
- GSAA: Goals Saved Above Average
Python: Goalie Development Analysis
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
# Load goalie development data
goalie_stats = pd.read_csv('goalie_career_progression.csv')
# Key metrics for goalie evaluation
def calculate_goalie_metrics(stats):
"""Calculate advanced goalie metrics"""
metrics = {}
# Save percentage
metrics['save_pct'] = stats['saves'] / stats['shots_against']
# Goals Against Average
metrics['gaa'] = (stats['goals_against'] * 60) / stats['minutes_played']
# Goals Saved Above Average (GSAA)
league_avg_sv_pct = 0.910 # Typical average
expected_goals = stats['shots_against'] * (1 - league_avg_sv_pct)
metrics['gsaa'] = expected_goals - stats['goals_against']
# Quality Start Percentage (games with Sv% > .913)
metrics['quality_start_pct'] = (
stats['quality_starts'] / stats['games_started']
)
# High-danger save percentage
metrics['hd_save_pct'] = (
stats['hd_saves'] / stats['hd_shots_against']
)
return metrics
# Apply metrics to all goalies
for col in ['save_pct', 'gaa', 'gsaa', 'quality_start_pct', 'hd_save_pct']:
goalie_stats[col] = np.nan
for idx, row in goalie_stats.iterrows():
metrics = calculate_goalie_metrics(row)
for metric, value in metrics.items():
goalie_stats.at[idx, metric] = value
# Development trajectory analysis
def analyze_development(goalie_id):
"""Analyze a goalie's development trajectory"""
goalie_career = goalie_stats[
goalie_stats['goalie_id'] == goalie_id
].sort_values('season')
# Calculate year-over-year improvement
goalie_career['sv_pct_change'] = goalie_career['save_pct'].diff()
goalie_career['gaa_change'] = goalie_career['gaa'].diff()
# Development score (composite metric)
scaler = StandardScaler()
key_metrics = goalie_career[['save_pct', 'quality_start_pct',
'hd_save_pct']].values
if len(key_metrics) > 1:
normalized = scaler.fit_transform(key_metrics)
goalie_career['development_score'] = normalized.mean(axis=1)
return goalie_career
# Example: Track top prospects
prospect_goalies = goalie_stats[
(goalie_stats['age'] <= 23) &
(goalie_stats['league'].isin(['AHL', 'ECHL', 'NCAA']))
].sort_values('save_pct', ascending=False).head(10)
print("=== Top Goalie Prospects ===")
print(prospect_goalies[['name', 'age', 'league', 'save_pct',
'gaa', 'gsaa', 'quality_start_pct']])
# Readiness score for NHL (prediction model)
def calculate_nhl_readiness(goalie_stats):
"""Calculate readiness score for NHL (0-100)"""
score = 0
# Save percentage component (40 points max)
if goalie_stats['save_pct'] >= 0.920:
score += 40
elif goalie_stats['save_pct'] >= 0.910:
score += 30
elif goalie_stats['save_pct'] >= 0.900:
score += 20
else:
score += 10
# Quality starts component (20 points max)
qs_pct = goalie_stats['quality_start_pct']
score += min(20, qs_pct * 20)
# Games played component (20 points max)
games = goalie_stats['games_started']
score += min(20, (games / 50) * 20)
# Age component (20 points max)
age = goalie_stats['age']
if age <= 23:
score += 20
elif age <= 25:
score += 15
elif age <= 27:
score += 10
else:
score += 5
return min(100, score)
# Calculate readiness for prospects
prospect_goalies['nhl_readiness'] = prospect_goalies.apply(
calculate_nhl_readiness, axis=1
)
print("\n=== NHL Readiness Rankings ===")
print(prospect_goalies[['name', 'age', 'league', 'save_pct',
'nhl_readiness']].sort_values(
'nhl_readiness', ascending=False))
# Projection model for future performance
def project_goalie_performance(current_stats, years_ahead=3):
"""Project goalie performance based on age and trajectory"""
age = current_stats['age']
save_pct = current_stats['save_pct']
projections = []
for year in range(1, years_ahead + 1):
future_age = age + year
# Goalies typically peak 26-30
if future_age < 26:
# Still improving
improvement = 0.002 # +0.002 Sv% per year
elif future_age <= 30:
# Peak years
improvement = 0.0
else:
# Decline
improvement = -0.001
projected_sv = save_pct + (improvement * year)
projections.append({
'age': future_age,
'projected_save_pct': projected_sv,
'projected_gaa': 2.50 + (0.915 - projected_sv) * 20
})
return pd.DataFrame(projections)
# Example projection
sample_goalie = prospect_goalies.iloc[0]
projection = project_goalie_performance(sample_goalie, years_ahead=3)
print(f"\n=== Projection: {sample_goalie['name']} ===")
print(projection)
R: Goalie Development Visualization
library(tidyverse)
library(scales)
# Load goalie statistics
goalie_stats <- read_csv("goalie_career_progression.csv")
# Calculate advanced goalie metrics
calculate_goalie_metrics <- function(data) {
data %>%
mutate(
save_pct = saves / shots_against,
gaa = (goals_against * 60) / minutes_played,
gsaa = (shots_against * (1 - 0.910)) - goals_against,
quality_start_pct = quality_starts / games_started,
hd_save_pct = hd_saves / hd_shots_against
)
}
goalie_stats <- calculate_goalie_metrics(goalie_stats)
# Development trajectory analysis
analyze_development <- function(goalie_data) {
goalie_data %>%
arrange(season) %>%
mutate(
sv_pct_change = save_pct - lag(save_pct),
gaa_change = gaa - lag(gaa),
# Development score (normalized composite)
development_score = scale(save_pct) +
scale(quality_start_pct) +
scale(hd_save_pct)
)
}
# Identify top prospects
prospect_goalies <- goalie_stats %>%
filter(age <= 23, league %in% c("AHL", "ECHL", "NCAA")) %>%
arrange(desc(save_pct)) %>%
head(10)
cat("=== Top Goalie Prospects ===\n")
print(prospect_goalies %>%
select(name, age, league, save_pct, gaa, gsaa, quality_start_pct))
# NHL readiness scoring function
calculate_nhl_readiness <- function(save_pct, quality_start_pct,
games_started, age) {
score <- 0
# Save percentage (40 points max)
score <- score + case_when(
save_pct >= 0.920 ~ 40,
save_pct >= 0.910 ~ 30,
save_pct >= 0.900 ~ 20,
TRUE ~ 10
)
# Quality starts (20 points max)
score <- score + min(20, quality_start_pct * 20)
# Games played (20 points max)
score <- score + min(20, (games_started / 50) * 20)
# Age (20 points max)
score <- score + case_when(
age <= 23 ~ 20,
age <= 25 ~ 15,
age <= 27 ~ 10,
TRUE ~ 5
)
return(min(100, score))
}
# Calculate readiness scores
prospect_goalies <- prospect_goalies %>%
rowwise() %>%
mutate(
nhl_readiness = calculate_nhl_readiness(save_pct, quality_start_pct,
games_started, age)
) %>%
ungroup()
cat("\n=== NHL Readiness Rankings ===\n")
print(prospect_goalies %>%
select(name, age, league, save_pct, nhl_readiness) %>%
arrange(desc(nhl_readiness)))
# Project future performance
project_goalie_performance <- function(current_age, current_sv_pct,
years_ahead = 3) {
tibble(year = 1:years_ahead) %>%
mutate(
age = current_age + year,
improvement = case_when(
age < 26 ~ 0.002, # Still improving
age <= 30 ~ 0.0, # Peak years
TRUE ~ -0.001 # Decline
),
projected_save_pct = current_sv_pct + (improvement * year),
projected_gaa = 2.50 + (0.915 - projected_save_pct) * 20
)
}
# Example projection
sample_goalie <- prospect_goalies %>% slice(1)
projection <- project_goalie_performance(
sample_goalie$age,
sample_goalie$save_pct,
years_ahead = 3
)
cat(sprintf("\n=== Projection: %s ===\n", sample_goalie$name))
print(projection)
# Visualize goalie development curves
development_curves <- goalie_stats %>%
filter(games_started >= 20) %>%
group_by(age) %>%
summarise(
avg_save_pct = mean(save_pct, na.rm = TRUE),
avg_quality_start_pct = mean(quality_start_pct, na.rm = TRUE),
n_goalies = n()
) %>%
filter(age >= 20, age <= 35)
ggplot(development_curves, aes(x = age)) +
geom_line(aes(y = avg_save_pct, color = "Save %"), size = 1.2) +
geom_line(aes(y = avg_quality_start_pct, color = "Quality Start %"),
size = 1.2) +
geom_point(aes(y = avg_save_pct), size = 2) +
geom_point(aes(y = avg_quality_start_pct), size = 2) +
scale_y_continuous(labels = percent_format(accuracy = 0.1)) +
labs(title = "Goaltender Development Curve",
subtitle = "Average performance by age",
x = "Age", y = "Percentage",
color = "Metric") +
theme_minimal() +
theme(legend.position = "bottom")
The Goalie Development Challenge
Goalies are more difficult to project than skaters. Many highly-touted prospects never reach the NHL, while late bloomers can emerge unexpectedly. Statistical analysis helps, but goalie evaluation requires combining analytics with traditional scouting.
Goalie Evaluation Challenges
- Small sample sizes in developmental leagues
- High variance in goalie performance year-to-year
- Team defense quality heavily influences statistics
- Physical and mental development continues into mid-20s
- Coaching and technique changes can dramatically alter trajectories
Best Practices for Goalie Evaluation
- Track multi-year trends rather than single-season performance
- Consider save percentage on high-danger chances specifically
- Evaluate consistency (quality start %) not just overall averages
- Account for team defensive quality and shot volume
- Monitor physical development and positioning technique
Discussion
Have questions or feedback? Join our community discussion on
Discord or
GitHub Discussions.
Table of Contents
Related Topics
Quick Actions