Goaltender Development Tracking

Beginner 10 min read 1 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.