WNBA Efficiency Metrics
WNBA Efficiency Metrics: Advanced Statistical Analysis
Efficiency metrics in the WNBA measure player and team performance per possession or per minute, accounting for the league's unique pace, shot distribution, and strategic differences from the NBA. This guide explores advanced efficiency calculations adapted specifically for women's basketball.
Core Efficiency Metrics for WNBA
1. True Shooting Percentage (TS%)
True Shooting adjusts for three-pointers and free throws to provide a comprehensive shooting efficiency metric:
where TSA = FGA + 0.44 × FTA
- Elite: 60%+ (A'ja Wilson: 61.2%)
- Above Average: 55-60%
- Average: 50-55%
- Below Average: <50%
2. Player Efficiency Rating (PER)
PER is a comprehensive per-minute rating developed by John Hollinger, adjusted for WNBA pace:
- MVP Level: 25+ (A'ja Wilson: 31.5 in 2024)
- All-Star: 20-25
- Starter Quality: 15-20
- League Average: 15.0 (by definition)
- Role Player: 10-15
3. Offensive & Defensive Rating
Points produced or allowed per 100 possessions:
Defensive Rating (DRtg) = 100 × (Opponent Points / Possessions)
- League ORtg: ~104.5
- Elite ORtg: 115+ (offensive stars)
- Elite DRtg: <95 (top defenders)
4. Effective Field Goal Percentage (eFG%)
Adjusts for the added value of three-point shots. WNBA average: ~48-49%
5. Usage Rate (USG%)
Percentage of team plays used by a player while on the court.
Python Implementation: WNBA Efficiency Analysis
import pandas as pd
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
import seaborn as sns
class WNBAEfficiencyCalculator:
"""Calculate advanced efficiency metrics for WNBA players"""
def __init__(self, player_stats, team_stats, league_stats):
"""
Parameters:
-----------
player_stats : DataFrame with player statistics
team_stats : DataFrame with team statistics
league_stats : dict with league-wide averages
"""
self.player_stats = player_stats
self.team_stats = team_stats
self.league_stats = league_stats
def true_shooting_percentage(self, pts, fga, fta):
"""Calculate True Shooting Percentage"""
tsa = fga + (0.44 * fta)
if tsa == 0:
return 0
return pts / (2 * tsa)
def effective_fg_percentage(self, fg, threes_made, fga):
"""Calculate Effective Field Goal Percentage"""
if fga == 0:
return 0
return (fg + 0.5 * threes_made) / fga
def usage_rate(self, fga, fta, tov, mp, team_fga, team_fta,
team_tov, team_mp):
"""Calculate Usage Rate"""
if mp == 0:
return 0
player_possessions = fga + (0.44 * fta) + tov
team_possessions = team_fga + (0.44 * team_fta) + team_tov
return 100 * ((player_possessions * (team_mp / 5)) /
(mp * team_possessions))
def offensive_rating(self, pts, fga, fta, tov, orb, ast,
team_pace, team_fg, team_ast):
"""
Simplified Offensive Rating calculation
Based on Dean Oliver's formula adapted for WNBA
"""
# Calculate scoring possessions
scoring_poss = (fga - orb + tov + (0.4 * fta))
if scoring_poss == 0:
return 0
# Points produced per possession
qAST = ((ast * team_fg) / team_ast) if team_ast > 0 else 0
fg = (fga * self.league_stats['fg_pct']) # Estimate
points_produced = (pts + qAST * 0.5)
# Scale to per 100 possessions
return 100 * (points_produced / scoring_poss)
def player_efficiency_rating(self, mp, threes, ast, fg, fga, ft, fta,
orb, drb, stl, blk, pf, tov,
team_stats, league_stats):
"""
Calculate Player Efficiency Rating (PER)
Adjusted for WNBA
"""
if mp == 0:
return 0
# League factors
lg_pace = league_stats['pace']
lg_ast = league_stats['ast']
lg_fg = league_stats['fg']
lg_ft = league_stats['ft']
lg_fta = league_stats['fta']
lg_pf = league_stats['pf']
lg_pts = league_stats['pts']
lg_fga = league_stats['fga']
lg_orb = league_stats['orb']
lg_tov = league_stats['tov']
lg_drb = league_stats['drb']
# Team factors
tm_ast = team_stats['ast']
tm_fg = team_stats['fg']
# Calculate VOP (Value of Possession)
vop = lg_pts / (lg_fga - lg_orb + lg_tov + 0.44 * lg_fta)
# Calculate DRBP (Defensive Rebound Percentage)
drbp = (lg_drb) / (lg_drb + lg_orb)
# Factor
factor = (2/3) - ((0.5 * lg_ast / lg_fg) / (2 * lg_fg / lg_ft))
# Unadjusted PER
uper = (1/mp) * (
threes +
(2/3) * ast +
(2 - factor * (tm_ast/tm_fg)) * fg +
(ft * 0.5 * (1 + (1 - (tm_ast/tm_fg)) + (2/3) * (tm_ast/tm_fg))) -
vop * tov -
vop * drbp * (fga - fg) -
vop * 0.44 * (0.44 + (0.56 * drbp)) * (fta - ft) +
vop * (1 - drbp) * (orb + drb) +
vop * drbp * orb +
vop * stl +
vop * drbp * blk -
pf * ((lg_ft/lg_pf) - 0.44 * (lg_fta/lg_pf) * vop)
)
# Pace adjustment
pace_adj = lg_pace / team_stats['pace']
# Adjusted PER
aper = uper * pace_adj
# Scale to league average of 15
lg_aper = league_stats['avg_per']
per = aper * (15 / lg_aper)
return per
def calculate_all_metrics(self):
"""Calculate all efficiency metrics for all players"""
results = []
for idx, player in self.player_stats.iterrows():
# Get team stats for this player's team
team = self.team_stats[
self.team_stats['team'] == player['team']
].iloc[0]
metrics = {
'player': player['name'],
'team': player['team'],
'ts_pct': self.true_shooting_percentage(
player['pts'], player['fga'], player['fta']
),
'efg_pct': self.effective_fg_percentage(
player['fg'], player['3pm'], player['fga']
),
'usg_rate': self.usage_rate(
player['fga'], player['fta'], player['tov'],
player['mp'], team['fga'], team['fta'],
team['tov'], team['mp']
),
'ortg': self.offensive_rating(
player['pts'], player['fga'], player['fta'],
player['tov'], player['orb'], player['ast'],
team['pace'], team['fg'], team['ast']
),
'per': self.player_efficiency_rating(
player['mp'], player['3pm'], player['ast'],
player['fg'], player['fga'], player['ft'],
player['fta'], player['orb'], player['drb'],
player['stl'], player['blk'], player['pf'],
player['tov'], team, self.league_stats
)
}
results.append(metrics)
return pd.DataFrame(results)
# Example usage with 2024 WNBA data
def analyze_wnba_efficiency():
"""Example analysis of WNBA efficiency metrics"""
# Sample player data (top performers 2024)
player_data = pd.DataFrame([
{
'name': "A'ja Wilson", 'team': 'LVA', 'mp': 1275,
'pts': 886, 'fga': 666, 'fta': 313, 'fg': 450,
'3pm': 14, 'ft': 254, 'ast': 134, 'orb': 120,
'drb': 430, 'stl': 64, 'blk': 82, 'tov': 112, 'pf': 77
},
{
'name': 'Breanna Stewart', 'team': 'NYL', 'mp': 1312,
'pts': 832, 'fga': 643, 'fta': 241, 'fg': 415,
'3pm': 87, 'ft': 191, 'ast': 162, 'orb': 68,
'drb': 324, 'stl': 51, 'blk': 59, 'tov': 119, 'pf': 62
},
{
'name': 'Napheesa Collier', 'team': 'MIN', 'mp': 1357,
'pts': 794, 'fga': 611, 'fta': 200, 'fg': 422,
'3pm': 42, 'ft': 157, 'ast': 159, 'orb': 91,
'drb': 338, 'stl': 73, 'blk': 45, 'tov': 85, 'pf': 61
}
])
# Team stats (simplified)
team_data = pd.DataFrame([
{'team': 'LVA', 'pace': 83.5, 'fga': 2589, 'fta': 798,
'tov': 463, 'mp': 9680, 'fg': 1745, 'ast': 694},
{'team': 'NYL', 'pace': 85.2, 'fga': 2678, 'fta': 745,
'tov': 481, 'mp': 9680, 'fg': 1789, 'ast': 712},
{'team': 'MIN', 'pace': 84.1, 'fga': 2634, 'fta': 721,
'tov': 458, 'mp': 9680, 'fg': 1768, 'ast': 703}
])
# League averages (2024 estimates)
league_stats = {
'pace': 84.3,
'pts': 83.6,
'fga': 64.2,
'fg': 26.8,
'fg_pct': 0.442,
'fta': 19.8,
'ft': 14.9,
'ast': 19.2,
'orb': 8.9,
'drb': 25.4,
'tov': 13.8,
'pf': 18.6,
'avg_per': 15.0
}
# Calculate metrics
calculator = WNBAEfficiencyCalculator(
player_data, team_data, league_stats
)
efficiency_metrics = calculator.calculate_all_metrics()
print("WNBA Efficiency Metrics - 2024 Top Performers")
print("=" * 70)
print(efficiency_metrics.to_string(index=False))
# Visualize efficiency comparison
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# TS% comparison
axes[0, 0].barh(efficiency_metrics['player'],
efficiency_metrics['ts_pct'] * 100)
axes[0, 0].set_xlabel('True Shooting %')
axes[0, 0].set_title('True Shooting Percentage')
axes[0, 0].axvline(x=55, color='r', linestyle='--',
label='League Avg')
axes[0, 0].legend()
# PER comparison
axes[0, 1].barh(efficiency_metrics['player'],
efficiency_metrics['per'])
axes[0, 1].set_xlabel('Player Efficiency Rating')
axes[0, 1].set_title('Player Efficiency Rating (PER)')
axes[0, 1].axvline(x=15, color='r', linestyle='--',
label='League Avg')
axes[0, 1].legend()
# Usage vs Efficiency scatter
axes[1, 0].scatter(efficiency_metrics['usg_rate'],
efficiency_metrics['ts_pct'] * 100, s=100)
for idx, row in efficiency_metrics.iterrows():
axes[1, 0].annotate(row['player'],
(row['usg_rate'], row['ts_pct'] * 100),
fontsize=8, ha='right')
axes[1, 0].set_xlabel('Usage Rate %')
axes[1, 0].set_ylabel('True Shooting %')
axes[1, 0].set_title('Usage vs Efficiency')
axes[1, 0].grid(True, alpha=0.3)
# Offensive Rating
axes[1, 1].barh(efficiency_metrics['player'],
efficiency_metrics['ortg'])
axes[1, 1].set_xlabel('Offensive Rating')
axes[1, 1].set_title('Offensive Rating (per 100 poss)')
axes[1, 1].axvline(x=104.5, color='r', linestyle='--',
label='League Avg')
axes[1, 1].legend()
plt.tight_layout()
plt.savefig('wnba_efficiency_analysis.png', dpi=300,
bbox_inches='tight')
print("\nVisualization saved as 'wnba_efficiency_analysis.png'")
return efficiency_metrics
if __name__ == "__main__":
metrics = analyze_wnba_efficiency()
R Implementation: Advanced WNBA Metrics with wehoop
library(wehoop)
library(tidyverse)
library(ggplot2)
library(scales)
# WNBA Efficiency Metrics Calculator
calculate_wnba_efficiency <- function(season = 2024) {
# Load WNBA player stats
player_box <- load_wnba_player_box(seasons = season)
team_box <- load_wnba_team_box(seasons = season)
# Calculate league averages
league_stats <- player_box %>%
summarize(
avg_pace = mean(pace, na.rm = TRUE),
avg_pts = mean(pts, na.rm = TRUE),
avg_fga = mean(fga, na.rm = TRUE),
avg_fg = mean(fgm, na.rm = TRUE),
avg_fta = mean(fta, na.rm = TRUE),
avg_ft = mean(ftm, na.rm = TRUE),
avg_ast = mean(ast, na.rm = TRUE),
avg_orb = mean(oreb, na.rm = TRUE),
avg_drb = mean(dreb, na.rm = TRUE),
avg_tov = mean(to, na.rm = TRUE),
avg_pf = mean(pf, na.rm = TRUE)
)
# Calculate efficiency metrics
efficiency_data <- player_box %>%
filter(min >= 400) %>% # Minimum 400 minutes
mutate(
# True Shooting %
tsa = fga + 0.44 * fta,
ts_pct = ifelse(tsa > 0, pts / (2 * tsa), 0),
# Effective FG%
efg_pct = ifelse(fga > 0, (fgm + 0.5 * fg3m) / fga, 0),
# Usage Rate (simplified)
player_poss = fga + 0.44 * fta + to,
usg_rate = player_poss / min * 100,
# Points per shot attempt
pps = pts / fga,
# Assist to Turnover Ratio
ast_to_ratio = ifelse(to > 0, ast / to, ast),
# Rebound Rate
reb_rate = (oreb + dreb) / min * 36,
# Stock Rate (Steals + Blocks)
stock_rate = (stl + blk) / min * 36
)
return(list(
efficiency = efficiency_data,
league_avg = league_stats
))
}
# Calculate Player Efficiency Rating
calculate_per <- function(player_stats, team_stats, league_stats) {
player_stats %>%
left_join(team_stats, by = c("team_id", "season")) %>%
mutate(
# Value of Possession
vop = league_stats$avg_pts /
(league_stats$avg_fga - league_stats$avg_orb +
league_stats$avg_tov + 0.44 * league_stats$avg_fta),
# Defensive Rebound Percentage
drbp = league_stats$avg_drb /
(league_stats$avg_drb + league_stats$avg_orb),
# Factor
factor = (2/3) - ((0.5 * league_stats$avg_ast / league_stats$avg_fg) /
(2 * league_stats$avg_fg / league_stats$avg_ft)),
# Team AST/FG ratio
team_ast_fg = team_ast / team_fg,
# Unadjusted PER
uper = (1/min) * (
fg3m +
(2/3) * ast +
(2 - factor * team_ast_fg) * fgm +
(ftm * 0.5 * (1 + (1 - team_ast_fg) + (2/3) * team_ast_fg)) -
vop * to -
vop * drbp * (fga - fgm) -
vop * 0.44 * (0.44 + (0.56 * drbp)) * (fta - ftm) +
vop * (1 - drbp) * (oreb + dreb) +
vop * drbp * oreb +
vop * stl +
vop * drbp * blk -
pf * ((league_stats$avg_ft / league_stats$avg_pf) -
0.44 * (league_stats$avg_fta / league_stats$avg_pf) * vop)
),
# Pace adjustment
pace_adj = league_stats$avg_pace / team_pace,
# Adjusted PER
aper = uper * pace_adj
) %>%
mutate(
# Scale to league average of 15
per = aper * (15 / mean(aper, na.rm = TRUE))
)
}
# Calculate Offensive and Defensive Ratings
calculate_ratings <- function(player_stats, team_stats) {
player_stats %>%
left_join(team_stats, by = c("team_id", "season")) %>%
mutate(
# Possessions (simplified)
poss = fga - oreb + to + (0.44 * fta),
# Offensive Rating (simplified)
ortg = ifelse(poss > 0, (pts / poss) * 100, 0),
# Defensive Rating (estimated from team defense)
# Adjusted by player's defensive stats
def_contribution = (stl + blk + dreb) / min,
drtg_adj = team_drtg * (1 - (def_contribution -
mean(def_contribution, na.rm = TRUE)) / 10)
)
}
# Analyze WNBA efficiency leaders
analyze_efficiency_leaders <- function(season = 2024, min_minutes = 400) {
# Get efficiency data
wnba_data <- calculate_wnba_efficiency(season)
efficiency <- wnba_data$efficiency
league_avg <- wnba_data$league_avg
# Top performers by metric
top_ts <- efficiency %>%
arrange(desc(ts_pct)) %>%
select(athlete_display_name, team_display_name, min,
pts, ts_pct, efg_pct) %>%
head(10)
cat("\nTop 10 True Shooting % Leaders (min 400 min):\n")
print(top_ts)
top_efficiency <- efficiency %>%
filter(ts_pct >= 0.55, usg_rate >= 20) %>%
arrange(desc(ts_pct))
cat("\nHigh Usage, High Efficiency Players (TS% >= 55%, USG >= 20%):\n")
print(top_efficiency %>%
select(athlete_display_name, team_display_name,
ts_pct, usg_rate, pts))
# Visualizations
create_efficiency_plots(efficiency, league_avg)
return(efficiency)
}
# Create comprehensive efficiency visualizations
create_efficiency_plots <- function(data, league_avg) {
# 1. Usage vs Efficiency scatter
p1 <- ggplot(data %>% filter(min >= 400),
aes(x = usg_rate, y = ts_pct * 100)) +
geom_point(aes(size = pts, color = team_display_name),
alpha = 0.6) +
geom_hline(yintercept = 55, linetype = "dashed",
color = "red", alpha = 0.5) +
geom_vline(xintercept = 20, linetype = "dashed",
color = "red", alpha = 0.5) +
geom_text(aes(label = ifelse(ts_pct > 0.58 | usg_rate > 25,
athlete_display_name, "")),
hjust = -0.1, vjust = -0.5, size = 3) +
labs(
title = "WNBA Usage Rate vs True Shooting %",
subtitle = paste("2024 Season | Min 400 minutes"),
x = "Usage Rate %",
y = "True Shooting %",
size = "Points",
color = "Team"
) +
theme_minimal() +
theme(legend.position = "bottom")
ggsave("wnba_usage_vs_efficiency.png", p1,
width = 12, height = 8, dpi = 300)
# 2. Efficiency distribution
p2 <- ggplot(data %>% filter(min >= 400),
aes(x = ts_pct * 100)) +
geom_histogram(aes(y = ..density..), binwidth = 2,
fill = "steelblue", alpha = 0.7) +
geom_density(color = "darkred", size = 1) +
geom_vline(xintercept = mean(data$ts_pct * 100, na.rm = TRUE),
linetype = "dashed", color = "red", size = 1) +
annotate("text",
x = mean(data$ts_pct * 100, na.rm = TRUE) + 2,
y = 0.08,
label = paste("League Avg:",
round(mean(data$ts_pct * 100, na.rm = TRUE), 1), "%"),
color = "red") +
labs(
title = "WNBA True Shooting % Distribution",
subtitle = "2024 Season | Min 400 minutes",
x = "True Shooting %",
y = "Density"
) +
theme_minimal()
ggsave("wnba_ts_distribution.png", p2,
width = 10, height = 6, dpi = 300)
# 3. Multi-metric comparison for top scorers
top_scorers <- data %>%
filter(min >= 600) %>%
arrange(desc(pts)) %>%
head(15) %>%
select(athlete_display_name, ts_pct, efg_pct,
usg_rate, ast_to_ratio) %>%
pivot_longer(cols = -athlete_display_name,
names_to = "metric",
values_to = "value")
p3 <- ggplot(top_scorers, aes(x = athlete_display_name,
y = value, fill = metric)) +
geom_bar(stat = "identity", position = "dodge") +
coord_flip() +
scale_fill_brewer(palette = "Set2",
labels = c("AST/TO Ratio", "eFG%",
"TS%", "Usage Rate")) +
labs(
title = "Top 15 Scorers: Multi-Metric Efficiency",
subtitle = "2024 WNBA Season",
x = NULL,
y = "Value",
fill = "Metric"
) +
theme_minimal() +
theme(legend.position = "bottom")
ggsave("wnba_top_scorers_metrics.png", p3,
width = 12, height = 10, dpi = 300)
cat("\nVisualization plots saved successfully!\n")
}
# Compare WNBA vs NBA efficiency standards
compare_wnba_nba_efficiency <- function() {
comparison <- data.frame(
Metric = c("Average TS%", "Elite TS%", "Average eFG%",
"Average ORtg", "Average Pace", "Average PER"),
WNBA_2024 = c("52-54%", "60%+", "48-49%", "104-105",
"83-85", "15.0"),
NBA_2024 = c("57-58%", "65%+", "54-55%", "114-116",
"99-100", "15.0"),
Difference = c("-5%", "-5%", "-6%", "-10 pts",
"-16 poss", "Same (scaled)")
)
print(comparison)
cat("\nKey Differences:\n")
cat("1. Lower shooting efficiency due to shorter shot clock (24s)\n")
cat("2. Slower pace creates different rhythm and shot selection\n")
cat("3. Different three-point line distance (22'1.75\" vs 23'9\")\n")
cat("4. Lower overall scoring environment affects rating scales\n")
cat("5. PER scaled to 15.0 in both leagues for comparison\n")
}
# Main execution
if (interactive()) {
cat("WNBA Advanced Efficiency Metrics Analysis\n")
cat("==========================================\n\n")
# Analyze 2024 season
efficiency_data <- analyze_efficiency_leaders(season = 2024)
# Compare to NBA
cat("\n\nWNBA vs NBA Efficiency Comparison:\n")
cat("===================================\n")
compare_wnba_nba_efficiency()
# Summary statistics
cat("\n\nSummary Statistics:\n")
cat("===================\n")
summary(efficiency_data %>%
select(ts_pct, efg_pct, usg_rate, ast_to_ratio))
}
2024 WNBA Efficiency Leaders
True Shooting Percentage (min 400 minutes)
| Rank | Player | Team | TS% | PPG | eFG% |
|---|---|---|---|---|---|
| 1 | A'ja Wilson | Las Vegas | 61.2% | 26.9 | 56.8% |
| 2 | Jonquel Jones | New York | 60.1% | 14.2 | 55.3% |
| 3 | DeWanna Bonner | Connecticut | 58.9% | 14.7 | 54.1% |
| 4 | Napheesa Collier | Minnesota | 58.3% | 20.2 | 54.7% |
| 5 | Breanna Stewart | New York | 57.8% | 20.4 | 52.6% |
Player Efficiency Rating (PER) Leaders
| Rank | Player | Team | PER | Usage% | ORtg |
|---|---|---|---|---|---|
| 1 | A'ja Wilson | Las Vegas | 31.5 | 29.8 | 118.4 |
| 2 | Napheesa Collier | Minnesota | 25.9 | 25.2 | 115.7 |
| 3 | Breanna Stewart | New York | 24.8 | 27.1 | 113.9 |
| 4 | Arike Ogunbowale | Dallas | 22.3 | 32.4 | 107.2 |
| 5 | Sabrina Ionescu | New York | 21.7 | 24.6 | 112.4 |
WNBA vs NBA Efficiency Differences
Shooting Efficiency
- WNBA TS% Average: 52-54%
- NBA TS% Average: 57-58%
- Gap: ~5 percentage points
Reasons: Shorter shot clock (24s vs 24s), different ball size, shorter three-point line in corners
Pace Factors
- WNBA Pace: 83-85 possessions/40 min
- NBA Pace: 99-100 possessions/48 min
- Per-40 Pace Gap: ~13-14 possessions
Impact: Lower pace affects counting stats, requires possession-based adjustments
Three-Point Shooting
- WNBA 3P%: ~35-36%
- NBA 3P%: ~36-37%
- WNBA 3PA Rate: 32% of FGA
- NBA 3PA Rate: 39% of FGA
Difference: NBA takes more threes, slightly better accuracy
Offensive Rating Scale
- WNBA Average: 104-105
- NBA Average: 114-116
- Elite WNBA: 115+
- Elite NBA: 122+
Note: Different baseline; compare within league only
Practical Applications
1. Player Evaluation & Scouting
Use Case: Identifying efficient scorers beyond raw points
- Compare TS% among players with similar usage rates
- Look for high-efficiency, low-usage players for role identification
- Evaluate scoring efficiency relative to shot difficulty
# Find high-efficiency role players
role_players = efficiency_df[
(efficiency_df['usg_rate'] < 20) &
(efficiency_df['ts_pct'] > 0.56)
].sort_values('ts_pct', ascending=False)
2. Lineup Optimization
Use Case: Building balanced offensive lineups
- Combine high-usage stars with efficient complementary players
- Balance shooters (high TS%) with playmakers (high AST)
- Optimize spacing using eFG% from different floor areas
# Optimal lineup balance
high_usage = efficiency_df[efficiency_df['usg_rate'] > 25]
high_efficiency = efficiency_df[
(efficiency_df['ts_pct'] > 0.55) &
(efficiency_df['usg_rate'].between(15, 22))
]
3. Contract & Salary Analysis
Use Case: Evaluating player value per dollar
- Calculate PER per salary dollar
- Identify undervalued efficient players
- Project future performance based on efficiency trends
4. Coaching Strategy
Use Case: Shot selection and play calling
- Identify players who maintain efficiency at high usage
- Optimize late-game possessions for best TS%
- Adjust offensive sets based on efficiency by play type
5. Fantasy Basketball
Use Case: Drafting and lineup decisions
- Target players with rising usage and stable efficiency
- Avoid players with declining TS% trends
- Identify breakout candidates through efficiency improvements
Advanced Analysis Techniques
1. Efficiency Consistency
Measure game-to-game variability in efficiency:
# Calculate rolling efficiency
player_games['rolling_ts'] = player_games.groupby('player_id')['ts_pct'].transform(
lambda x: x.rolling(window=10, min_periods=5).mean()
)
# Efficiency standard deviation (consistency metric)
player_consistency = player_games.groupby('player_id').agg({
'ts_pct': ['mean', 'std'],
'per': ['mean', 'std']
})
# Lower std = more consistent
player_consistency['ts_consistency'] = (
player_consistency[('ts_pct', 'mean')] /
player_consistency[('ts_pct', 'std')]
)
2. Clutch Efficiency
Compare efficiency in clutch situations (last 5 min, score within 5):
# Filter clutch possessions
clutch_stats = player_games[
(player_games['time_remaining'] <= 300) &
(player_games['score_diff'].abs() <= 5)
]
# Calculate clutch efficiency
clutch_efficiency = clutch_stats.groupby('player_id').apply(
lambda x: pd.Series({
'clutch_ts': calculate_ts(x['pts'].sum(), x['fga'].sum(), x['fta'].sum()),
'regular_ts': x['ts_pct'].mean(),
'clutch_games': len(x)
})
)
clutch_efficiency['clutch_improvement'] = (
clutch_efficiency['clutch_ts'] - clutch_efficiency['regular_ts']
)
3. Efficiency by Shot Type
Break down efficiency by shot location and type:
# Efficiency by zone
shot_zones = ['rim', 'short_mid', 'long_mid', 'corner_3', 'above_break_3']
zone_efficiency = {}
for zone in shot_zones:
zone_data = shots_df[shots_df['zone'] == zone]
zone_efficiency[zone] = {
'fg_pct': zone_data['made'].mean(),
'frequency': len(zone_data) / len(shots_df),
'pts_per_shot': zone_data['points'].mean()
}
# Identify most efficient shot for each player
player_best_zone = shots_df.groupby(['player_id', 'zone']).agg({
'made': 'mean',
'shot_value': 'mean'
}).reset_index().sort_values('shot_value', ascending=False)
4. Usage-Adjusted Efficiency
Account for difficulty increase at higher usage:
# Model efficiency decay with usage
from sklearn.linear_model import LinearRegression
# Fit relationship between usage and efficiency
X = efficiency_df[['usg_rate']].values
y = efficiency_df['ts_pct'].values
model = LinearRegression()
model.fit(X, y)
# Predict expected efficiency at usage rate
efficiency_df['expected_ts'] = model.predict(X)
efficiency_df['ts_above_expected'] = (
efficiency_df['ts_pct'] - efficiency_df['expected_ts']
)
# Players who maintain efficiency despite high usage
elite_efficiency = efficiency_df[
(efficiency_df['usg_rate'] > 25) &
(efficiency_df['ts_above_expected'] > 0.02)
]
Key Insights & Benchmarks
Elite Efficiency Threshold
Players with TS% > 58% at Usage > 25% are rare and extremely valuable
Only ~5% of players achieve this combination
PER Context
WNBA PER typically ranges 8-32, with MVP candidates above 25
A'ja Wilson's 31.5 PER in 2024 was historically dominant
Usage Optimization
Most players see efficiency decline when usage exceeds 28-30%
Stars who maintain 55%+ TS at high usage are franchise cornerstones
Team Success Correlation
Teams with 3+ players above 18 PER win 65%+ of games
Balanced efficiency distribution beats top-heavy talent
Resources & Further Reading
- Basketball Reference WNBA: Comprehensive stats and historical data
- Her Hoop Stats: Advanced WNBA analytics and metrics
- wehoop R Package: Access WNBA data in R
- WNBA.com Stats: Official league statistics
- Dean Oliver's Basketball on Paper: Foundational work on basketball analytics
- John Hollinger's PER Methodology: Original efficiency rating system
Practice Exercises
- Calculate TS% and eFG% for your favorite WNBA player across multiple seasons
- Compare efficiency metrics between backcourt and frontcourt players
- Identify players whose efficiency improved most with usage changes
- Build a "most efficient lineup" using players from different teams
- Analyze how efficiency correlates with team win percentage
- Create visualizations showing efficiency trends throughout a season
- Calculate clutch vs regular season efficiency differences
- Model the relationship between pace and team efficiency