Update: Clark’s 2024 season still stands out

Re-visiting her rookie campaign’s offensive production
R
sports
Observable
Author
Published

September 24, 2025

Introduction

Last year, I wrote that Caitlin Clark’s offensive contributions in her 2024 rookie season for the Indiana Fever was unmatched among both WNBA and NBA players since at least 2019. She was the only player in either league over this time span to contribute 40% of a team’s assists and 20% of a team’s points in a single season.

With the WNBA playoffs in full swing, and the Clark-less Fever making an inspired run, I am re-visiting that analysis.

This time, I have expanded the time frame to include that last 10 seasons of WNBA and NBA play as well as the WNBA regular season that just concluded. Data was pulled using the wehoop and hoopR R packages from the Sports Dataverse.

Also new this time is the interactive scatterplot below, created with the Observable Plot JavaScript library. There is a companion notebook on the Observable HQ platform, for those interested in creating something similar.

Interactive Plot

Conculsion(s)

The only players to achieve 40-20 seasons since 2015 are (2017 was wild!):

  • Russell Westbrook (2017): 49% assist share | 29% points share
  • Russell Westbrook (2018): 47% assist share | 23% points share
  • James Harden (2017): 44% assist share | 25% points share
  • Russell Westbrook (2016): 44% assist share | 21% points share
  • Caitlin Clark (2024): 41% assist share | 23% points share
  • John Wall (2017): 42% assist share | 42% points share

Clark is the only rookie and only WNBA player on this list. All of these players’ associated teams made the playoffs, but none won the title. It would be interesting to see if a single player dominating a team’s offensive output has a positive or negative affect on their success–maybe a topic for a future post?

Still, it’s a fun stat and a truly remarkable feat for a rookie season. As a Fever fan, let’s hope she can get back on the court next season and keep breaking records.

Analysis Code

My code is below for those interested in how to create something like this. Typically, I would just pull all required years of data in one API call. However, I realized mid-way through this project that the 2018 NBA team assist values were wrong (chronically low).

So, I pulled 2015:2017 and 2019:2025 from the load_nba_team_box function and then the 2018 NBA team values from the espn_nba_team_stats which seemed to be correct. I opened a GitHub issue for this and may take a look at attempting to fix it so others don’t run into the same problem.

WNBA Data

# Load Packages
library(tidyverse)
library(wehoop)
library(hoopR)
library(ggplot2)
library(plotly)
library(hrbrthemes)
library(scales)
# All WNBA Seasons Data

hist_wnba_player_box <- wehoop::load_wnba_player_box(seasons = 2015:2025)
hist_wnba_team_box <- wehoop::load_wnba_team_box(seasons = 2015:2025)

# Clean data: remove All-Star games' data and only use regular season data

all_star_teams <- c(
  "EAST",
  "WEST",
  "Team Parker",
  "Team Delle Donne",
  "Team Wilson",
  "Team Usa",
  "Team WNBA",
  "Team Stewart",
  "Team USA",
  "TEAM COLLIER",
  "TEAM CLARK"
)

hist_wnba_player_box <- hist_wnba_player_box %>%
  filter(
    !team_display_name %in% all_star_teams,
    season_type == 2
  )

hist_wnba_team_box <- hist_wnba_team_box %>%
  filter(
    !team_display_name %in% all_star_teams,
    season_type == 2
  )

# Calculate total points and assists in season
hist_wnba_pts_assists <- hist_wnba_player_box %>%
  filter(did_not_play == FALSE) %>%
  group_by(season, athlete_id, athlete_display_name, team_id) %>%
  summarize(total_points = sum(points),
            total_assists = sum(assists)) %>%
  arrange(desc(total_points)) 


hist_wnba_team_pts_assists <- hist_wnba_team_box %>%
  group_by(season, team_id, team_display_name) %>%
  summarize(
    total_assists = sum(assists),
    total_points = sum(team_score)
  ) %>%
  arrange(desc(total_points))


# Join historical team and player data together
hist_player_and_team <- merge(x = hist_wnba_pts_assists,
                              y = hist_wnba_team_pts_assists,
                              by.x = c("season", "team_id"),
                              by.y = c("season","team_id"))

# Calculate share of team points and assists
hist_player_and_team <- hist_player_and_team %>%
  rename(
    team_assists = total_assists.y,
    team_points = total_points.y
  ) %>%
  mutate(
    assist_share = total_assists.x/team_assists,
    points_share = total_points.x/team_points,
    assist_points_share = assist_share + points_share
  )

NBA Data

# Load NBA data for comparison
hist_nba_player_box <- load_nba_player_box(seasons = c(2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025))
hist_nba_team_box <- load_nba_team_box(seasons = c(2015, 2016, 2017, 2019, 2020, 2021, 2022, 2023, 2024, 2025))

# Clean data: remove All-Star games' data and only use regular season data

nba_all_star_teams <- c(
  "Eastern Conf All-Stars",
  "Western Conf All-Stars",
  "Team LeBron",
  "Team Stephen",
  "World",
  "USA",
  "Team Giannis",
  "Leb",
  "Usa",
  "Team Durant",
  "Team Chuck",
  "Team Candace",
  "Team Shaq",
  "Team Kenny"
)


hist_nba_player_box <- hist_nba_player_box %>%
  filter(
    !team_display_name %in% nba_all_star_teams,
    season_type == 2
  )

hist_nba_team_box <- hist_nba_team_box %>%
  filter(
    !team_display_name %in% nba_all_star_teams,
    season_type == 2
  )


# Calculate total points and assists in season
hist_nba_pts_assists <- hist_nba_player_box %>%
  filter(did_not_play == FALSE) %>%
  group_by(season, athlete_id, athlete_display_name, team_id) %>%
  summarize(total_points = sum(points),
            total_assists = sum(assists)) %>%
  arrange(desc(total_points)) 


hist_nba_team_pts_assists <- hist_nba_team_box %>%
  group_by(season, team_id, team_display_name) %>%
  summarize(
    total_assists = sum(assists),
    total_points = sum(team_score)
  ) %>%
  arrange(desc(total_points))

########
# Clean data: pull in 2018 totals (incorrect from other data source)
## Function wrapper for one team
get_team_stats <- function(team_id) {
  espn_nba_team_stats(
    team_id = team_id,
    year = 2018,
    season_type = "regular",
    total = FALSE
  )
}

## Vector of team ids (1 to 30)
team_ids <- 1:30

## Run through all and row-bind
team_stats_2018 <- map_dfr(team_ids, get_team_stats, .id = "team_id")

## pull out only stats needed
espn_stats_2018 <- team_stats_2018 %>%
  mutate(season = "2018") %>%
  select(season, team_id, team_display_name, offensive_assists, offensive_points)

### make columns the same type and name
espn_stats_2018[c("season", "team_id", "offensive_assists", "offensive_points")] <- lapply(espn_stats_2018[c("season", "team_id", "offensive_assists", "offensive_points")], as.integer)

espn_stats_2018 <- espn_stats_2018 %>%
  rename(
    total_points = offensive_points,
    total_assists = offensive_assists
  )

## row bind 2018 team espn stats with bref stats
hist_nba_team_pts_assists <- rbind(hist_nba_team_pts_assists, espn_stats_2018)

############

# Join team and player data
hist_nba_player_and_team <- merge(x = hist_nba_pts_assists,
                                  y = hist_nba_team_pts_assists,
                                  by.x = c("season", "team_id"),
                                  by.y = c("season","team_id"))

Combine leagues data and calculate assist and point shares

# Calculate nba share of team points and assists
hist_nba_player_and_team <- hist_nba_player_and_team %>%
  rename(
    team_assists = total_assists.y,
    team_points = total_points.y
  ) %>%
  mutate(
    assist_share = total_assists.x/team_assists,
    points_share = total_points.x/team_points,
    assist_points_share = assist_share + points_share
  )

# Add "League" column
hist_player_and_team <- hist_player_and_team %>%
  mutate(
    League = "WNBA"
  )

hist_nba_player_and_team <- hist_nba_player_and_team %>%
  mutate(
    League = "NBA"
  )

# Union WNBA and NBA data together
nba_wnba_combined <- rbind(hist_player_and_team, hist_nba_player_and_team)