Swedish and Finnish League Analytics
Beginner
10 min read
0 views
Nov 27, 2025
SHL and Liiga: Premier Development Leagues
The Swedish Hockey League (SHL) and Finnish Liiga are consistently among the best development systems for NHL prospects. These leagues emphasize tactical play, two-way responsibility, and skill development, producing a steady stream of NHL talent.
League Characteristics
- SHL (Sweden): 14 teams, structured defensive systems, high skill level
- Liiga (Finland): 15 teams, tactical emphasis, physical two-way play
- Allsvenskan (Sweden): Second-tier development league
- Mestis (Finland): Finnish second division
SHL/Liiga Translation to NHL
Python: Nordic League Analysis
import pandas as pd
import numpy as np
from sklearn.ensemble import GradientBoostingRegressor
import matplotlib.pyplot as plt
# Load Nordic league data
shl_to_nhl = pd.read_csv('shl_to_nhl_transitions.csv')
liiga_to_nhl = pd.read_csv('liiga_to_nhl_transitions.csv')
# Combine datasets
nordic_transitions = pd.concat([
shl_to_nhl.assign(league='SHL'),
liiga_to_nhl.assign(league='Liiga')
])
# Calculate translation factors by league
shl_translation = np.corrcoef(
shl_to_nhl['league_ppg'],
shl_to_nhl['nhl_ppg']
)[0, 1]
liiga_translation = np.corrcoef(
liiga_to_nhl['league_ppg'],
liiga_to_nhl['nhl_ppg']
)[0, 1]
print("=== Nordic League Translation Factors ===")
print(f"SHL correlation: {shl_translation:.3f}")
print(f"Liiga correlation: {liiga_translation:.3f}")
# Age-specific analysis
def calculate_u23_performance(df):
"""Analyze U23 player performance"""
u23_players = df[df['age'] < 23]
if len(u23_players) == 0:
return None
return {
'count': len(u23_players),
'avg_league_ppg': u23_players['league_ppg'].mean(),
'avg_nhl_ppg': u23_players['nhl_ppg'].mean(),
'translation_rate': (u23_players['nhl_ppg'].mean() /
u23_players['league_ppg'].mean())
}
shl_u23 = calculate_u23_performance(shl_to_nhl)
liiga_u23 = calculate_u23_performance(liiga_to_nhl)
print("\n=== U23 Player Analysis ===")
print(f"SHL U23: {shl_u23}")
print(f"Liiga U23: {liiga_u23}")
# Draft position impact
def analyze_by_draft_position(df):
"""Analyze success by draft position"""
df['draft_category'] = pd.cut(
df['draft_position'],
bins=[0, 10, 30, 60, 120, 300],
labels=['Top 10', 'First Round', 'Top 2 Rounds',
'Mid Rounds', 'Late/Undrafted']
)
return df.groupby('draft_category').agg({
'nhl_ppg': ['mean', 'count'],
'league_ppg': 'mean'
})
shl_draft_analysis = analyze_by_draft_position(shl_to_nhl)
liiga_draft_analysis = analyze_by_draft_position(liiga_to_nhl)
print("\n=== SHL Success by Draft Position ===")
print(shl_draft_analysis)
print("\n=== Liiga Success by Draft Position ===")
print(liiga_draft_analysis)
# Playing style translation
def classify_nordic_style(row):
"""Classify playing style from Nordic leagues"""
g_ratio = row['goals'] / row['points'] if row['points'] > 0 else 0
# Nordic leagues reward complete players
if row['es_points_pct'] >= 0.70:
base_style = 'Two-Way Forward'
elif g_ratio >= 0.55:
base_style = 'Goal Scorer'
elif g_ratio <= 0.35:
base_style = 'Playmaker'
else:
base_style = 'Balanced Producer'
return base_style
nordic_transitions['style'] = nordic_transitions.apply(
classify_nordic_style, axis=1
)
# Style success rates
style_success = nordic_transitions.groupby(['league', 'style']).agg({
'nhl_ppg': ['mean', 'count']
}).round(3)
print("\n=== NHL Success by Playing Style ===")
print(style_success)
# Current prospects analysis
current_shl = pd.read_csv('current_shl_players.csv')
current_liiga = pd.read_csv('current_liiga_players.csv')
def score_nordic_prospect(row):
"""Comprehensive prospect scoring"""
score = 0
# Production (0-40 points)
ppg = row['points'] / row['games_played']
if ppg >= 0.70:
score += 40
elif ppg >= 0.55:
score += 32
elif ppg >= 0.40:
score += 24
else:
score += 16
# Age factor (0-25 points)
if row['age'] <= 20:
score += 25
elif row['age'] <= 22:
score += 20
elif row['age'] <= 24:
score += 15
else:
score += 10
# Even-strength production (0-20 points)
es_pct = row['es_points'] / row['points'] if row['points'] > 0 else 0
if es_pct >= 0.70:
score += 20
elif es_pct >= 0.60:
score += 15
else:
score += 10
# Ice time (indicates trust) (0-10 points)
if row['toi_per_game'] >= 18:
score += 10
elif row['toi_per_game'] >= 15:
score += 7
else:
score += 4
# Physical maturity (0-5 points)
if row['age'] >= 20 and row['weight_kg'] >= 85:
score += 5
elif row['weight_kg'] >= 80:
score += 3
else:
score += 1
return score
# Score all current prospects
current_shl['prospect_score'] = current_shl.apply(score_nordic_prospect, axis=1)
current_liiga['prospect_score'] = current_liiga.apply(score_nordic_prospect, axis=1)
# Combine and rank
all_nordic_prospects = pd.concat([
current_shl.assign(league='SHL'),
current_liiga.assign(league='Liiga')
])
top_nordic = all_nordic_prospects.sort_values(
'prospect_score', ascending=False
).head(20)
print("\n=== Top Nordic Prospects ===")
print(top_nordic[[
'name', 'age', 'league', 'points', 'games_played',
'toi_per_game', 'prospect_score'
]])
# Project NHL performance
base_translation_factors = {'SHL': 0.36, 'Liiga': 0.34}
def project_nhl_production(row):
"""Project NHL points based on Nordic performance"""
ppg = row['points'] / row['games_played']
base_factor = base_translation_factors.get(row['league'], 0.35)
# Age adjustment
if row['age'] <= 21:
age_adj = 1.15
elif row['age'] <= 23:
age_adj = 1.10
elif row['age'] <= 25:
age_adj = 1.05
else:
age_adj = 1.00
projected_nhl_ppg = ppg * base_factor * age_adj
projected_points = projected_nhl_ppg * 82
return projected_points
top_nordic['projected_nhl_points'] = top_nordic.apply(
project_nhl_production, axis=1
)
print("\n=== NHL Production Projections ===")
print(top_nordic[[
'name', 'age', 'league', 'points', 'games_played',
'projected_nhl_points'
]].head(15))
R: Nordic League Visualization
library(tidyverse)
library(patchwork)
library(scales)
# Load Nordic transition data
shl_to_nhl <- read_csv("shl_to_nhl_transitions.csv") %>% mutate(league = "SHL")
liiga_to_nhl <- read_csv("liiga_to_nhl_transitions.csv") %>% mutate(league = "Liiga")
nordic_transitions <- bind_rows(shl_to_nhl, liiga_to_nhl)
# Translation factor analysis
translation_factors <- nordic_transitions %>%
group_by(league) %>%
summarise(
correlation = cor(league_ppg, nhl_ppg),
n = n(),
avg_league_ppg = mean(league_ppg),
avg_nhl_ppg = mean(nhl_ppg)
)
cat("=== Nordic League Translation Factors ===\n")
print(translation_factors)
# U23 player analysis
u23_analysis <- nordic_transitions %>%
filter(age < 23) %>%
group_by(league) %>%
summarise(
count = n(),
avg_league_ppg = mean(league_ppg),
avg_nhl_ppg = mean(nhl_ppg),
translation_rate = avg_nhl_ppg / avg_league_ppg
)
cat("\n=== U23 Player Analysis ===\n")
print(u23_analysis)
# Draft position impact
draft_analysis <- nordic_transitions %>%
mutate(
draft_category = cut(
draft_position,
breaks = c(0, 10, 30, 60, 120, 300),
labels = c("Top 10", "First Round", "Top 2 Rounds",
"Mid Rounds", "Late/Undrafted")
)
) %>%
group_by(league, draft_category) %>%
summarise(
count = n(),
avg_nhl_ppg = mean(nhl_ppg),
avg_league_ppg = mean(league_ppg),
.groups = "drop"
)
cat("\n=== Success by Draft Position ===\n")
print(draft_analysis)
# Playing style classification
nordic_transitions <- nordic_transitions %>%
mutate(
goals_ratio = goals / points,
style = case_when(
es_points_pct >= 0.70 ~ "Two-Way Forward",
goals_ratio >= 0.55 ~ "Goal Scorer",
goals_ratio <= 0.35 ~ "Playmaker",
TRUE ~ "Balanced Producer"
)
)
# Style success rates
style_success <- nordic_transitions %>%
group_by(league, style) %>%
summarise(
count = n(),
avg_nhl_ppg = mean(nhl_ppg),
.groups = "drop"
)
cat("\n=== NHL Success by Playing Style ===\n")
print(style_success)
# Current prospects
current_shl <- read_csv("current_shl_players.csv") %>% mutate(league = "SHL")
current_liiga <- read_csv("current_liiga_players.csv") %>% mutate(league = "Liiga")
# Prospect scoring function
score_nordic_prospect <- function(points, games_played, age, es_points,
toi_per_game, weight_kg) {
score <- 0
ppg <- points / games_played
# Production (0-40)
score <- score + case_when(
ppg >= 0.70 ~ 40,
ppg >= 0.55 ~ 32,
ppg >= 0.40 ~ 24,
TRUE ~ 16
)
# Age (0-25)
score <- score + case_when(
age <= 20 ~ 25,
age <= 22 ~ 20,
age <= 24 ~ 15,
TRUE ~ 10
)
# ES production (0-20)
es_pct <- ifelse(points > 0, es_points / points, 0)
score <- score + case_when(
es_pct >= 0.70 ~ 20,
es_pct >= 0.60 ~ 15,
TRUE ~ 10
)
# Ice time (0-10)
score <- score + case_when(
toi_per_game >= 18 ~ 10,
toi_per_game >= 15 ~ 7,
TRUE ~ 4
)
# Physical (0-5)
score <- score + case_when(
age >= 20 & weight_kg >= 85 ~ 5,
weight_kg >= 80 ~ 3,
TRUE ~ 1
)
return(score)
}
# Score prospects
all_nordic_prospects <- bind_rows(current_shl, current_liiga) %>%
rowwise() %>%
mutate(
prospect_score = score_nordic_prospect(
points, games_played, age, es_points, toi_per_game, weight_kg
)
) %>%
ungroup()
top_nordic <- all_nordic_prospects %>%
arrange(desc(prospect_score)) %>%
head(20)
cat("\n=== Top Nordic Prospects ===\n")
print(top_nordic %>%
select(name, age, league, points, games_played, toi_per_game, prospect_score))
# NHL projection
base_translation <- c("SHL" = 0.36, "Liiga" = 0.34)
all_nordic_prospects <- all_nordic_prospects %>%
mutate(
ppg = points / games_played,
base_factor = base_translation[league],
age_adj = case_when(
age <= 21 ~ 1.15,
age <= 23 ~ 1.10,
age <= 25 ~ 1.05,
TRUE ~ 1.00
),
projected_nhl_points = ppg * base_factor * age_adj * 82
)
cat("\n=== NHL Production Projections ===\n")
print(all_nordic_prospects %>%
arrange(desc(projected_nhl_points)) %>%
head(15) %>%
select(name, age, league, points, games_played, projected_nhl_points))
# Visualization: Translation comparison
p1 <- ggplot(nordic_transitions, aes(x = league_ppg, y = nhl_ppg, color = league)) +
geom_point(alpha = 0.6, size = 3) +
geom_smooth(method = "lm", se = TRUE) +
labs(title = "Nordic League to NHL Translation",
x = "League PPG", y = "NHL PPG") +
theme_minimal()
# Visualization: Age distribution of success
p2 <- ggplot(nordic_transitions %>% filter(nhl_ppg >= 0.50),
aes(x = age, fill = league)) +
geom_histogram(binwidth = 1, alpha = 0.7, position = "dodge") +
labs(title = "Age Distribution of Successful Transitions",
subtitle = "Players with NHL PPG >= 0.50",
x = "Age at Transition", y = "Count") +
theme_minimal()
# Combine plots
print(p1 / p2)
Why Nordic Leagues Produce NHL Talent
SHL and Liiga consistently develop NHL-ready players due to their emphasis on complete skill development. Young players must earn ice time against professional competition, learn defensive responsibility, and develop hockey IQ in structured systems. The leagues' translation success is among the highest outside North America.
Nordic League Prospect Indicators
- Strong even-strength production (70%+ of points)
- Consistent ice time in top-6 or top-4 role
- Production maintained across multiple seasons
- Success in international junior tournaments
- Two-way responsibility demonstrated
Discussion
Have questions or feedback? Join our community discussion on
Discord or
GitHub Discussions.
Table of Contents
Related Topics
Quick Actions