This project presents a comprehensive analysis of NBA player networks using two complementary approaches: draft class trajectory analysis and brokerage role identification.
The first part examines how players from the same draft year connect throughout their professional careers, while the second part identifies key “broker” players who serve as connectors across different team communities. Together, these analyses provide insights into career patterns, player mobility, and the interconnected nature of NBA rosters.
This analysis explores NBA draft classes through network analysis, examining how players from the same draft year connect throughout their professional careers. The network is constructed by linking players who have been teammates on the same team during the same season, creating a web of connections that reveals career trajectories and player movement patterns within draft cohorts.
The analysis focuses on the 2018 NBA Draft class, using game log data from 2018-2024 to track player careers and team associations. By visualizing these connections as a network graph, we can identify clustering patterns, understand career longevity differences, and observe how draft class members remain connected through shared team experiences over time.
# Install required packages
install.packages("remotes")
remotes::install_github("abresler/nbastatR", force = TRUE)
remotes::install_version("future", version = "1.15.1")
# Load libraries
library(nbastatR)
library(future)
library(dplyr)
library(tidyr)
library(igraph)
library(ggraph)
library(ggplot2)
# Setup for large data processing
Sys.setenv(VROOM_CONNECTION_SIZE = 131072 * 10)
plan(sequential)
# Get game logs from 2018 to 2024
logs <- game_logs(seasons = 2018:2024)
Data Source: NBA game logs accessed through the
nbastatR package, which pulls from official NBA APIs
Collection Period: 2018-2024 NBA seasons
Access Link: Data is retrieved through the
nbastatR package in R
Here is the network structure,
Vertices (Nodes): Individual NBA players from the 2018 draft class
Edges (Connections): Created when two players from the same draft class played on the same team during the same season
Node Attributes: Average points per game and total games played across career
Data Collectors: NBA official statistics, accessed via nbastatR package
# Function to create NBA Draft Class Network Graphs
draft_class_network <- function(draft_years, logs) {
# Get draft class players
draft_data <- drafts(draft_years = draft_years, nest_data = FALSE, return_message = TRUE)
# Filter logs for draft class players
career_logs <- logs %>%
filter(namePlayer %in% draft_data$namePlayer)
# Create player-team-season dataframe
player_team_season <- career_logs %>%
select(namePlayer, slugTeam, yearSeason) %>%
distinct()
# Build edges, pair players on same team in same season
edges_list <- list()
# Get unique (team, season) combinations
team_seasons <- unique(player_team_season[, c("slugTeam", "yearSeason")])
# Loop over all team-season combinations
for (i in 1:nrow(team_seasons)) {
# Get current team and season
team <- team_seasons$slugTeam[i]
season <- team_seasons$yearSeason[i]
# Get players on this team for this season
players_in_team_season <- player_team_season %>%
filter(slugTeam == team, yearSeason == season) %>%
pull(namePlayer)
# If 2 or more players, create edges (all possible pairs)
if (length(players_in_team_season) >= 2) {
player_combos <- combn(players_in_team_season, 2)
# Store edges as dataframe
edges_list[[length(edges_list) + 1]] <- data.frame(
from = player_combos[1, ],
to = player_combos[2, ],
stringsAsFactors = FALSE
)
}
}
# Combine all edges into one data frame
edges <- do.call(rbind, edges_list)
# Build igraph object
g <- graph_from_data_frame(edges, directed = FALSE)
# Summarize player stats, average points, assists, total rebounds, minutes and games played
career_stats <- career_logs %>%
group_by(namePlayer, yearSeason) %>%
summarize(
pts = mean(pts, na.rm = TRUE),
ast = mean(ast, na.rm = TRUE),
treb = mean(treb, na.rm = TRUE),
minutes = mean(minutes, na.rm = TRUE),
games_played = n_distinct(idGame),
.groups = 'drop'
)
# Aggregate stats across all season for each player
node_stats <- career_stats %>%
group_by(namePlayer) %>%
summarize(
avg_pts = mean(pts, na.rm = TRUE),
total_games = sum(games_played),
.groups = 'drop'
)
# Add node attributes
V(g)$avg_pts <- node_stats$avg_pts[match(V(g)$name, node_stats$namePlayer)] # avg_pts = average points per game across career
V(g)$total_games <- node_stats$total_games[match(V(g)$name, node_stats$namePlayer)] # total_games = total games played across career
#Plot graph with ggraph
ggraph(g, layout = "fr") + # Use Fruchterman-Reingold layout (spread out nicely)
geom_edge_link(alpha = 0.4, color = "gray80") + # Draw edges (gray lines, semi-transparent)
geom_node_point(aes(size = total_games, color = avg_pts), alpha = 0.9) + # Draw nodes (size = games played, color = avg pts)
geom_node_text(aes(label = name), repel = TRUE, size = 3) + # Label nodes (with repel to avoid overlapping text!)
scale_color_viridis_c(option = "C", direction = -1, name = "Avg PPG") + # Color scale = viridis color palette (C), reversed
scale_size(range = c(3, 10), name = "Total Games Played") + # Node size scale
theme_void() + # No background / axis
ggtitle(paste(draft_years, "Draft Class Career Trajectory Network (Spread and Readable)")) + # Title of the plot
theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold")) # Title formatting (centered, bold)
}
# Run function
draft_class_network(2018, logs)
The visualization uses a force-directed layout where node size represents career longevity (total games played) and color intensity shows scoring performance (average points per game). Connections between players indicate they were teammates at some point during their careers.
This network analysis reveals important insights about NBA draft class career trajectories and player movement patterns. The visualization effectively shows how players from the same draft year remain connected through shared team experiences, with some players serving as central hubs who have played with many of their draft class peers. The network structure can indicate player mobility, career longevity, and the interconnected nature of NBA rosters.
This second analysis explores “broker” players using Gould-Fernandez Brokerage analysis with network science methods. We examine how players connect different team communities and identify key connective roles within the NBA ecosystem. We will be using the Louvain community detetction method. The goal is to visualize and interpret player dynamics through graph structures, focusing on players who serve as bridges b
Our main packages include nbastatR for real NBA data,
and igraph, sna, and dplyr for
data wrangling, network creation, and brokerage analysis. This work
helps illustrate team structures and player centrality for coaches,
analysts, and fans.
# Use these in your console before downloading the other packages
# install.packages("remotes")
# remotes::install_github("abresler/nbastatR", force = TRUE)
# Dataset package
library(nbastatR)
# Packages needed
library(devtools)
library(dplyr)
library(future)
library(ggplot2)
library(igraph)
library(knitr)
# For brokerage analysis
library(sna)
# Adjust VROOM buffer size for large data!
Sys.setenv(VROOM_CONNECTION_SIZE = 131072 * 10)
# Change season to anything from (ex 2020:2024, from 2020 to 2024 seasons)
game_data <- game_logs(seasons = 2020:2024)
# Create player-to-player network based on shared teams
cat("Creating player teammate network...\n")
# Unique player-team-season combos
player_teams <- game_data %>%
select(namePlayer, slugTeam, slugSeason) %>%
distinct()
# Create teammate pairs per team-season
teammate_pairs <- player_teams %>%
group_by(slugTeam, slugSeason) %>%
filter(n() > 1) %>%
do({
players <- .$namePlayer
expand.grid(
player1 = players,
player2 = players,
stringsAsFactors = FALSE
) %>%
filter(player1 != player2)
}) %>%
group_by(player1, player2) %>%
summarise(
shared_teams = n(),
teams = paste(unique(paste(slugTeam, slugSeason)), collapse = ", "),
.groups = "drop"
) %>%
filter(shared_teams >= 1)
cat("Teammate connections created:", nrow(teammate_pairs), "\n")
cat("Filtering connections between players...\n")
# Most connected players
player_connections <- teammate_pairs %>%
group_by(player1) %>%
summarise(total_teammates = n(), .groups = "drop") %>%
arrange(desc(total_teammates))
# Filter top 100 players by connections
top_players <- head(player_connections$player1, 100)
filtered_network <- teammate_pairs %>%
filter(player1 %in% top_players & player2 %in% top_players)
# Creates graph + simplify (remove loops and multiple edges)
players_network <- graph_from_data_frame(filtered_network, directed = FALSE)
players_network <- simplify(players_network)
# Detect communities using Louvain algorithm
communities <- cluster_louvain(players_network)
V(players_network)$community <- membership(communities)
# Calculate betweenness centrality
between <- igraph::betweenness(players_network)
# Create a data frame for betweenness results
bet.dat <- data.frame(
player = V(players_network)$name,
between = between
)
# Print top 10 players by betweenness
cat("=== (Betweenness Centrality) ===\n")
print(head(bet.dat[order(bet.dat$between, decreasing = TRUE),], 10))
# Community summary
cat("\n=== COMMUNITY STRUCTURE ===\n")
cat("Modularity:", round(modularity(communities), 3), "\n")
# Get membership vector
membership_vec <- membership(communities)
# Members of community
for(i in 1:min(5, max(membership_vec))) {
community_players <- V(players_network)$name[membership_vec == i]
cat("Community", i, ":", paste(head(community_players, 5), collapse = ", "),
ifelse(length(community_players) > 5, "...", ""), "\n")
}
Brokerage analysis identifies players who connect others across different groups. It measures 5 broker types:
Values are normalized and rounded to two digits. Z-scores show statistical significance, values >1.96 are unusually high brokers for that role.
(^^^ sourced by help function (?brokerage) ).
# MAKE SURE TO RUN THIS LINE, as it will conflict with functions from earlier packages
install.packages("intergraph", dependencies = TRUE)
# PACKAGES
library(intergraph)
library(statnet)
library(knitr)
library(dplyr)
# Convert igraph to sna network
snaNetwork <- asNetwork(players_network)
# Confirm the network size matches igraph vertex count
stopifnot(network.size(snaNetwork) == length(V(players_network)))
# Assign communities as vertex attributes
community_named <- membership(communities)
names(community_named) <- V(players_network)$name
# Assign community attribute to the network vertices by matching names
set.vertex.attribute(
snaNetwork,
"community",
value = community_named[get.vertex.attribute(snaNetwork, "vertex.names")]
)
# Check to see if community listed
print(table(get.vertex.attribute(snaNetwork, "community")))
# Create dataframe from brokerage analysis result, 2 DIGITS
brokerage_df <- round(brokerage(snaNetwork, cl=get.vertex.attribute(snaNetwork, "community"))$z.nli, 2)
# Column names readability
colnames(brokerage_df) <- c(
"Coordinator", # was w_I
"Consultant", # was w_O
"Representative", # was b_IO
"Gatekeeper", # was b_OI
"Liaison", # was b_O
"Total" # was t
)
# Show all data sorted by Total
print("=== ALL PLAYERS BY TOTAL BROKERAGE ===")
print(brokerage_df[order(brokerage_df[, "Total"], decreasing = TRUE), ])
# Show top 5 by Total
print("=== TOP 5 TOTAL BROKERS ===")
print(head(brokerage_df[order(brokerage_df[, "Total"], decreasing = TRUE), ], 5))
# Show top 3 players in each category
print("=== TOP 3 PLAYERS BY CATEGORY ===")
for(col in colnames(brokerage_df)) {
print(paste("=== TOP 3", col, "==="))
sorted <- brokerage_df[order(brokerage_df[, col], decreasing = TRUE), ]
top_3 <- head(sorted[, col, drop = FALSE], 3)
for(i in 1:nrow(top_3)) {
print(paste(i, ".", rownames(top_3)[i], "(", top_3[i, 1], ")"))
}
writeLines("") # blank line
}
The brokerage analysis reveals interesting patterns about player roles in the NBA ecosystem. For example, when analyzing seasons 2016-2024, Jeff Green emerged as a top broker, supporting the narrative from basketball communities about his value as a journeyman player:
“Because even though Jeff Green’s journeyman path through the NBA has been un-sexy, he’s been able to keep a job in the league for 15 seasons, playing for eleven teams, and collecting a lot of paychecks. Jeff Green is the opposite of a ‘star’ and there’s actually something cool about that.” - BasketballxFeelings
This analysis validates that journeyman players like Jeff Green, Wenyen Gabriel, and Troy Brown Jr. often serve crucial connector roles, bridging different team communities throughout their careers.
Interestingly, the analysis also revealed that star players can have high brokerage scores. James Harden, despite being an elite scorer, showed high brokerage totals (2.74 in the 2020-2024 window), indicating that even superstars can serve as important connectors across multiple teams and communities.
### Data Sources and
References
Resources used for brokerage analysis: - R Documentation: brokerage function - RStudio Pubs: Brokerage Roles Analysis (most helpful)
Community discussions: - Reddit: Jeff Green NBA Legend Discussion - Reddit: Dennis Schroder Running Out of Options - Reddit: Dennis Schroder Team Changes
This network analysis shows how two approaches offer complementary views of NBA player dynamics. The draft class analysis highlights how players from the same cohort stay connected over time. The brokerage analysis identifies key players who link different teams.
Together, these methods reveal patterns in team building, player movement, and career paths that traditional stats miss. Network science offers a fresh way to understand the NBA as a complex social and professional system.
The results show that both stars and journeymen play vital roles. While stars lead in performance, journeymen often connect teams and communities. This perspective adds new insight into how the league operates.
Key Takeaways:
Draft class ties often last across careers
Journeymen frequently act as key network brokers
Some stars also serve as important connectors
Network analysis uncovers hidden patterns in player movement and
team dynamics
These insights can help inform team strategy and player
evaluation.