Historical WNBA Trends Analysis
Beginner
10 min read
0 views
Nov 27, 2025
Evolution of the WNBA
Since its inception in 1997, the WNBA has evolved dramatically in terms of playing style, scoring patterns, and strategic emphasis. Analyzing historical trends reveals how the three-point shot, pace of play, and player athleticism have transformed the league.
Key Historical Trends to Analyze
- Scoring Trends: Points per game over time
- Three-Point Revolution: Increasing 3PA and 3P% over seasons
- Pace Evolution: Changes in game tempo
- Shooting Efficiency: eFG% and TS% improvements
- Position Evolution: Changing roles of guards, forwards, centers
Python: Historical Trend Analysis
Python: WNBA Historical Data Analysis
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# Sample historical WNBA data (normally compiled from multiple seasons)
# This would typically be loaded from a database or CSV file
historical_data = pd.DataFrame({
'season': list(range(1997, 2025)),
'avg_ppg': [
65.2, 67.8, 68.5, 69.2, 67.5, 68.8, 70.2, 71.5, # 1997-2004
72.8, 73.5, 74.2, 75.8, 76.5, 77.2, 78.5, 79.8, # 2005-2012
80.5, 81.2, 82.5, 83.8, 84.5, 85.2, 86.8, 87.5, # 2013-2020
88.2, 89.5, 90.2, 91.5 # 2021-2024
],
'avg_pace': [
78.5, 79.2, 78.8, 78.5, 77.2, 76.8, 77.5, 78.2,
79.5, 80.2, 80.8, 81.5, 82.2, 82.8, 83.5, 84.2,
84.8, 85.5, 86.2, 86.8, 87.5, 88.2, 89.0, 89.5,
90.2, 90.8, 91.5, 92.2
],
'avg_3pa': [
5.2, 6.1, 6.8, 7.2, 7.8, 8.5, 9.2, 10.1,
11.2, 12.5, 13.8, 14.5, 15.2, 16.8, 17.5, 18.2,
19.5, 20.8, 21.5, 22.8, 23.5, 24.2, 25.5, 26.8,
27.5, 28.2, 29.5, 30.2
],
'avg_3p_pct': [
0.312, 0.318, 0.325, 0.328, 0.330, 0.332, 0.335, 0.338,
0.340, 0.342, 0.345, 0.348, 0.350, 0.352, 0.355, 0.358,
0.360, 0.362, 0.365, 0.368, 0.370, 0.372, 0.375, 0.378,
0.380, 0.382, 0.385, 0.388
],
'avg_fg_pct': [
0.428, 0.432, 0.435, 0.438, 0.440, 0.442, 0.445, 0.448,
0.450, 0.452, 0.454, 0.456, 0.458, 0.460, 0.462, 0.464,
0.466, 0.468, 0.470, 0.472, 0.474, 0.476, 0.478, 0.480,
0.482, 0.484, 0.486, 0.488
]
})
# =============================================================================
# 1. Scoring Trends Over Time
# =============================================================================
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Plot 1: Points Per Game Evolution
axes[0, 0].plot(historical_data['season'], historical_data['avg_ppg'],
marker='o', linewidth=2, color='blue')
axes[0, 0].set_title('WNBA Scoring Evolution', fontsize=14, fontweight='bold')
axes[0, 0].set_xlabel('Season')
axes[0, 0].set_ylabel('Average Points Per Game')
axes[0, 0].grid(alpha=0.3)
# Add trend line
z = np.polyfit(historical_data['season'], historical_data['avg_ppg'], 1)
p = np.poly1d(z)
axes[0, 0].plot(historical_data['season'], p(historical_data['season']),
"r--", alpha=0.8, label=f'Trend: +{z[0]:.2f} ppg/year')
axes[0, 0].legend()
# Plot 2: Three-Point Attempts Over Time
axes[0, 1].plot(historical_data['season'], historical_data['avg_3pa'],
marker='o', linewidth=2, color='green')
axes[0, 1].set_title('Three-Point Revolution', fontsize=14, fontweight='bold')
axes[0, 1].set_xlabel('Season')
axes[0, 1].set_ylabel('Average 3PA Per Game')
axes[0, 1].grid(alpha=0.3)
# Add trend line
z2 = np.polyfit(historical_data['season'], historical_data['avg_3pa'], 1)
p2 = np.poly1d(z2)
axes[0, 1].plot(historical_data['season'], p2(historical_data['season']),
"r--", alpha=0.8, label=f'Trend: +{z2[0]:.2f} 3PA/year')
axes[0, 1].legend()
# Plot 3: Pace Evolution
axes[1, 0].plot(historical_data['season'], historical_data['avg_pace'],
marker='o', linewidth=2, color='orange')
axes[1, 0].set_title('Pace of Play Evolution', fontsize=14, fontweight='bold')
axes[1, 0].set_xlabel('Season')
axes[1, 0].set_ylabel('Pace (Possessions Per 40 Min)')
axes[1, 0].grid(alpha=0.3)
# Plot 4: Shooting Efficiency
axes[1, 1].plot(historical_data['season'], historical_data['avg_fg_pct'],
marker='o', linewidth=2, color='red', label='FG%')
axes[1, 1].plot(historical_data['season'], historical_data['avg_3p_pct'],
marker='s', linewidth=2, color='purple', label='3P%')
axes[1, 1].set_title('Shooting Efficiency Trends', fontsize=14, fontweight='bold')
axes[1, 1].set_xlabel('Season')
axes[1, 1].set_ylabel('Shooting Percentage')
axes[1, 1].grid(alpha=0.3)
axes[1, 1].legend()
plt.tight_layout()
# plt.show()
# =============================================================================
# 2. Era-Based Analysis
# =============================================================================
def categorize_era(season):
"""Categorize WNBA seasons into eras"""
if season < 2005:
return "Early Era (1997-2004)"
elif season < 2013:
return "Middle Era (2005-2012)"
elif season < 2020:
return "Modern Era (2013-2019)"
else:
return "Current Era (2020+)"
historical_data['era'] = historical_data['season'].apply(categorize_era)
# Calculate era averages
era_stats = historical_data.groupby('era').agg({
'avg_ppg': 'mean',
'avg_pace': 'mean',
'avg_3pa': 'mean',
'avg_3p_pct': 'mean',
'avg_fg_pct': 'mean'
}).round(2)
print("=== WNBA Statistical Evolution by Era ===")
print(era_stats)
# =============================================================================
# 3. Three-Point Impact Analysis
# =============================================================================
# Calculate points from three-pointers
historical_data['pts_from_3'] = historical_data['avg_3pa'] * historical_data['avg_3p_pct'] * 3
historical_data['pct_pts_from_3'] = (historical_data['pts_from_3'] / historical_data['avg_ppg']) * 100
print("\n=== Three-Point Impact Over Time ===")
print(historical_data[['season', 'avg_3pa', 'pts_from_3', 'pct_pts_from_3']].tail(10))
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(historical_data['season'], historical_data['pct_pts_from_3'],
marker='o', linewidth=2, color='darkgreen')
ax.set_title('Percentage of Points from Three-Pointers', fontsize=14, fontweight='bold')
ax.set_xlabel('Season')
ax.set_ylabel('% of Total Points from 3PT')
ax.grid(alpha=0.3)
plt.tight_layout()
# plt.show()
# =============================================================================
# 4. Year-Over-Year Growth Rates
# =============================================================================
historical_data['ppg_growth'] = historical_data['avg_ppg'].pct_change() * 100
historical_data['3pa_growth'] = historical_data['avg_3pa'].pct_change() * 100
historical_data['pace_growth'] = historical_data['avg_pace'].pct_change() * 100
print("\n=== Average Annual Growth Rates ===")
print(f"PPG Growth: {historical_data['ppg_growth'].mean():.2f}% per year")
print(f"3PA Growth: {historical_data['3pa_growth'].mean():.2f}% per year")
print(f"Pace Growth: {historical_data['pace_growth'].mean():.2f}% per year")
# =============================================================================
# 5. Predict Future Trends
# =============================================================================
from sklearn.linear_model import LinearRegression
# Prepare data for prediction
X = historical_data['season'].values.reshape(-1, 1)
y_ppg = historical_data['avg_ppg'].values
y_3pa = historical_data['avg_3pa'].values
# Train models
model_ppg = LinearRegression().fit(X, y_ppg)
model_3pa = LinearRegression().fit(X, y_3pa)
# Predict next 5 years
future_seasons = np.array(range(2025, 2030)).reshape(-1, 1)
pred_ppg = model_ppg.predict(future_seasons)
pred_3pa = model_3pa.predict(future_seasons)
print("\n=== Projected WNBA Trends (2025-2029) ===")
for i, season in enumerate(range(2025, 2030)):
print(f"{season}: {pred_ppg[i]:.1f} PPG, {pred_3pa[i]:.1f} 3PA")
# =============================================================================
# 6. Correlation Analysis
# =============================================================================
correlations = {
'3PA vs PPG': np.corrcoef(historical_data['avg_3pa'], historical_data['avg_ppg'])[0,1],
'Pace vs PPG': np.corrcoef(historical_data['avg_pace'], historical_data['avg_ppg'])[0,1],
'3P% vs FG%': np.corrcoef(historical_data['avg_3p_pct'], historical_data['avg_fg_pct'])[0,1]
}
print("\n=== Trend Correlations ===")
for trend, corr in correlations.items():
print(f"{trend}: {corr:.3f}")
print("\n=== Historical Analysis Complete ===")
print("✓ Scoring evolution tracked")
print("✓ Three-point revolution quantified")
print("✓ Era-based comparisons")
print("✓ Future trends projected")
R: Historical WNBA Trends with wehoop
library(wehoop)
library(tidyverse)
library(scales)
library(broom)
# =============================================================================
# 1. Load Multi-Season Data
# =============================================================================
# Load multiple seasons of WNBA data
seasons_to_analyze <- 2010:2024
# Load team box scores across multiple seasons
historical_team_data <- seasons_to_analyze %>%
map_dfr(function(season) {
cat(sprintf("Loading season %d...\n", season))
wehoop::load_wnba_team_box(seasons = season) %>%
mutate(season = season)
})
# =============================================================================
# 2. Calculate Season-Level Statistics
# =============================================================================
season_trends <- historical_team_data %>%
group_by(season) %>%
summarise(
games = n(),
avg_ppg = mean(team_score, na.rm = TRUE),
avg_fgm = mean(field_goals_made, na.rm = TRUE),
avg_fga = mean(field_goals_attempted, na.rm = TRUE),
avg_fg3m = mean(three_point_field_goals_made, na.rm = TRUE),
avg_fg3a = mean(three_point_field_goals_attempted, na.rm = TRUE),
avg_ftm = mean(free_throws_made, na.rm = TRUE),
avg_fta = mean(free_throws_attempted, na.rm = TRUE),
avg_reb = mean(total_rebounds, na.rm = TRUE),
avg_ast = mean(assists, na.rm = TRUE),
avg_tov = mean(turnovers, na.rm = TRUE),
.groups = "drop"
) %>%
mutate(
# Calculate percentages
fg_pct = avg_fgm / avg_fga,
three_pct = avg_fg3m / avg_fg3a,
ft_pct = avg_ftm / avg_fta,
# Advanced metrics
efg_pct = (avg_fgm + 0.5 * avg_fg3m) / avg_fga,
three_rate = avg_fg3a / avg_fga,
# Estimate pace (simplified)
possessions = avg_fga + 0.44 * avg_fta + avg_tov,
pace = possessions,
# Points from three
pts_from_three = avg_fg3m * 3,
pct_pts_from_three = (pts_from_three / avg_ppg) * 100
)
cat("=== WNBA Season Trends (2010-2024) ===\n")
print(season_trends %>%
select(season, avg_ppg, avg_fg3a, three_rate, efg_pct))
# =============================================================================
# 3. Visualize Scoring Evolution
# =============================================================================
p1 <- ggplot(season_trends, aes(x = season, y = avg_ppg)) +
geom_line(linewidth = 1.2, color = "steelblue") +
geom_point(size = 3, color = "steelblue") +
geom_smooth(method = "lm", se = TRUE, color = "red",
linetype = "dashed", alpha = 0.3) +
labs(
title = "WNBA Scoring Evolution",
subtitle = "Average points per game by season",
x = "Season",
y = "Points Per Game"
) +
theme_minimal()
print(p1)
# =============================================================================
# 4. Three-Point Revolution
# =============================================================================
p2 <- ggplot(season_trends, aes(x = season)) +
geom_line(aes(y = avg_fg3a, color = "3PA"), linewidth = 1.2) +
geom_point(aes(y = avg_fg3a, color = "3PA"), size = 3) +
geom_line(aes(y = pct_pts_from_three, color = "% Pts from 3"),
linewidth = 1.2) +
geom_point(aes(y = pct_pts_from_three, color = "% Pts from 3"), size = 3) +
scale_color_manual(values = c("3PA" = "darkgreen", "% Pts from 3" = "purple")) +
labs(
title = "WNBA Three-Point Revolution",
subtitle = "Three-point attempts and scoring contribution over time",
x = "Season",
y = "Value",
color = "Metric"
) +
theme_minimal() +
theme(legend.position = "bottom")
print(p2)
# =============================================================================
# 5. Era-Based Comparison
# =============================================================================
season_trends <- season_trends %>%
mutate(
era = case_when(
season < 2015 ~ "Early Modern (2010-2014)",
season < 2020 ~ "Late Modern (2015-2019)",
TRUE ~ "Current Era (2020+)"
)
)
era_comparison <- season_trends %>%
group_by(era) %>%
summarise(
seasons = n(),
avg_ppg = mean(avg_ppg),
avg_3pa = mean(avg_fg3a),
avg_three_pct = mean(three_pct, na.rm = TRUE),
avg_pace = mean(pace),
avg_efg = mean(efg_pct, na.rm = TRUE),
.groups = "drop"
)
cat("\n=== WNBA Statistical Evolution by Era ===\n")
print(era_comparison)
# =============================================================================
# 6. Trend Analysis with Linear Regression
# =============================================================================
# Model 1: PPG over time
model_ppg <- lm(avg_ppg ~ season, data = season_trends)
ppg_summary <- tidy(model_ppg)
cat("\n=== Points Per Game Trend ===\n")
cat(sprintf("Annual increase: %.2f points per season\n",
ppg_summary$estimate[2]))
cat(sprintf("P-value: %.4f\n", ppg_summary$p.value[2]))
# Model 2: 3PA over time
model_3pa <- lm(avg_fg3a ~ season, data = season_trends)
three_summary <- tidy(model_3pa)
cat("\n=== Three-Point Attempt Trend ===\n")
cat(sprintf("Annual increase: %.2f 3PA per season\n",
three_summary$estimate[2]))
cat(sprintf("P-value: %.4f\n", three_summary$p.value[2]))
# Model 3: Pace over time
model_pace <- lm(pace ~ season, data = season_trends)
pace_summary <- tidy(model_pace)
cat("\n=== Pace Trend ===\n")
cat(sprintf("Annual change: %.2f possessions per season\n",
pace_summary$estimate[2]))
# =============================================================================
# 7. Predict Future Trends
# =============================================================================
future_seasons <- data.frame(season = 2025:2029)
predictions <- tibble(
season = 2025:2029,
pred_ppg = predict(model_ppg, newdata = future_seasons),
pred_3pa = predict(model_3pa, newdata = future_seasons),
pred_pace = predict(model_pace, newdata = future_seasons)
)
cat("\n=== Projected WNBA Trends (2025-2029) ===\n")
print(predictions)
# =============================================================================
# 8. Comprehensive Visualization: Multi-Metric Dashboard
# =============================================================================
library(patchwork)
p_ppg <- ggplot(season_trends, aes(x = season, y = avg_ppg)) +
geom_line(linewidth = 1) +
geom_smooth(method = "lm", se = FALSE, color = "red", linetype = "dashed") +
labs(title = "Scoring", y = "PPG") +
theme_minimal()
p_3pa <- ggplot(season_trends, aes(x = season, y = avg_fg3a)) +
geom_line(linewidth = 1, color = "darkgreen") +
geom_smooth(method = "lm", se = FALSE, color = "red", linetype = "dashed") +
labs(title = "Three-Point Volume", y = "3PA") +
theme_minimal()
p_efg <- ggplot(season_trends, aes(x = season, y = efg_pct)) +
geom_line(linewidth = 1, color = "purple") +
geom_smooth(method = "lm", se = FALSE, color = "red", linetype = "dashed") +
scale_y_continuous(labels = percent_format()) +
labs(title = "Efficiency", y = "eFG%") +
theme_minimal()
p_pace <- ggplot(season_trends, aes(x = season, y = pace)) +
geom_line(linewidth = 1, color = "orange") +
geom_smooth(method = "lm", se = FALSE, color = "red", linetype = "dashed") +
labs(title = "Pace", y = "Possessions") +
theme_minimal()
combined_plot <- (p_ppg + p_3pa) / (p_efg + p_pace) +
plot_annotation(
title = "WNBA Historical Trends Dashboard",
subtitle = "Evolution of key metrics over time (red = trend line)"
)
print(combined_plot)
# =============================================================================
# 9. Correlation Matrix
# =============================================================================
correlation_data <- season_trends %>%
select(avg_ppg, avg_fg3a, three_rate, pace, efg_pct)
cor_matrix <- cor(correlation_data, use = "complete.obs")
cat("\n=== Correlation Matrix ===\n")
print(round(cor_matrix, 3))
cat("\n=== Historical Trend Analysis Complete ===\n")
cat("✓ Multi-season data analyzed\n")
cat("✓ Era comparisons completed\n")
cat("✓ Trend models fitted\n")
cat("✓ Future projections generated\n")
cat("✓ Comprehensive visualizations created\n")
Key Historical Insights
The WNBA has experienced a clear three-point revolution similar to the NBA, with teams increasingly emphasizing perimeter shooting. Pace has also gradually increased, leading to higher-scoring games. These trends reflect both rule changes and evolving strategic understanding of optimal basketball play.
Historical Analysis Applications
- Era adjustments: Compare players across different eras fairly
- Strategic evolution: Understand how winning strategies have changed
- Future planning: Project where the league is headed
- Rule impact: Evaluate how rule changes affected gameplay
- Talent assessment: Contextualize current player performance
Discussion
Have questions or feedback? Join our community discussion on
Discord or
GitHub Discussions.
Table of Contents
Related Topics
Quick Actions