NBA Metrics in the Regular Season and Playoffs - Posit Table Competition 2022

Author

Brendan Lam

Basketball Players – Regular Season vs. Playoffs

There is a lot of discussion on how well top NBA players perform in the playoffs vs. during the regular season. I wanted to investigate and visualize the differences in performance (as measured by metrics such as points above average per 100 possessions added by player). The table shows a players stats for the regular season and the same stats for the playoffs. If the column is highlighted green in the “Regular season” section, it means that metric was higher compared to the post-season. If it’s highlighted red, it means it was lower than the post-season metric. Radial/spider plots also show how people perform on all the advanced metrics during the regular season and playoffs. This data was obtained from https://github.com/fivethirtyeight/data/blob/master/nba-raptor/README.md.

Here are the advanced metrics I used:

  • RAPTOR: Points above average per 100 possessions added by player on both offense and defense, using both box and on-off components
  • WAR: Wins Above Replacement
  • PREDATOR: Predictive points above average per 100 possessions added by player on both offense and defense

Loading Libraries and Data

library('tidyverse')
library('gt')
library('gtExtras')
library('BasketballAnalyzeR')

bball_df <- read_csv("modern_RAPTOR_by_team.csv")

I focused on the top NBA players in the 21-22 season according to Ringer.com that made it to the playoffs:https://www.theringer.com/nba/2022/3/9/22967447/nba-top-25-players-2021-22-season

  1. Jimmy Butler 9 Chris Paul
  2. Demar DeRozan
  3. Ja Morant
  4. Luka Doncic
  5. Stephen Curry
  6. Kevin Durant
  7. Joel Embiid
  8. Giannis Antetokuompo
  9. Nikola Jokic
bball_tbl <- bball_df %>% 
  filter(season == 2022) %>% 
  filter(player_name %in% c("Jimmy Butler", "Chris Paul", 
                            "DeMar DeRozan", "Ja Morant", "Luka Doncic",
                            "Stephen Curry", "Kevin Durant", 
                            "Joel Embiid", "Giannis Antetokounmpo",
                            "Nikola Jokic"))

First, I manually add all the images of the team logo to the data frame. I also format the data frame so that the regular season and playoff metrics are beside each other, instead of on different rows.

# Taking regular season stats and putting it into a data frame, while also adding images to the data frame.
bball_pre_df <- bball_tbl %>% 
  filter(season_type == "RS") %>% # Replace with your own file path:
  mutate(logo = c("/Users/brendanlam/R Projects/Table_Contest_22/milwaukee.jpeg",
                  "/Users/brendanlam/R Projects/Table_Contest_22/miami.png",
                  "/Users/brendanlam/R Projects/Table_Contest_22/golden_state.png",
                  "/Users/brendanlam/R Projects/Table_Contest_22/chicago.png",
                  "/Users/brendanlam/R Projects/Table_Contest_22/dallas.png",
                  "/Users/brendanlam/R Projects/Table_Contest_22/brooklyn.png",
                  "/Users/brendanlam/R Projects/Table_Contest_22/philadelphia.png",
                  "/Users/brendanlam/R Projects/Table_Contest_22/denver.png",
                  "/Users/brendanlam/R Projects/Table_Contest_22/memphis.png",
                  "/Users/brendanlam/R Projects/Table_Contest_22/phoenix.png")) %>% 
  select(player_name, logo, season_type, poss, mp, war_reg_season, raptor_total, predator_total) %>% 
  dplyr::rename(reg_season = season_type)

# Taking playoff stats and putting it into a data frame.
bball_post_df <- bball_tbl %>% 
  filter(season_type == "PO") %>% 
  select(c(player_name, season_type, poss, mp, war_playoffs, raptor_total, predator_total)) %>% 
  dplyr::rename(post_season = season_type)

# Combining regular season and playoff stats so that they are side-by-side
bball_cmb_tbl <- bball_pre_df %>% 
  left_join(bball_post_df, by = "player_name") %>% 
  select(!c(reg_season, post_season)) %>% 
  mutate(radial_plots = rep(NA, nrow(bball_pre_df)))

Next, I format the data so that it can be displayed in a radial/spider plot. I’ve displayed each of the plots up close.

  • WRS = WAR metric for the regular season
  • RRS = RAPTOR score for the regular season
  • PRS = PREDATOR score for the regular season
  • WP = WAR measure for the playoffs
  • RP = RAPTOR score for the playoffs
  • PP = PREDATOR score for the playoffs
# Creating radial plots for each player

# Creating data frame for data the we will use for radial plots
radar_data <- bball_cmb_tbl %>% 
  select(war_reg_season, raptor_total.x, predator_total.x, war_playoffs, raptor_total.y,
         predator_total.y) %>% 
  dplyr::rename(WRS = war_reg_season, 
                RRS = raptor_total.x, 
                PRS  = predator_total.x, 
                WP = war_playoffs, 
                RP = raptor_total.y,
                PP = predator_total.y)

radial_plots <- list(rep(NA, 10))
for (i in 1:10) {
  radial_plots[i] <- radialprofile(data=radar_data[i,], std=FALSE)
}

Creating the table

# Create table
bball_cmb_tbl %>% 
  gt() %>% 
  tab_header(
    title = md("**NBA Player Stats**"),
    subtitle = md("Comparing performance during the regular and post=season for 2021-2022")
  ) %>% 
  # Renaming columns
  cols_label(
    player_name = "Name",
    logo = "Team",
    poss.x = "Possessions Played",
    mp.x = "MP",
    war_reg_season = "WAR",
    raptor_total.x = "RAPTOR",
    predator_total.x = "PREDATOR",
    poss.y = "Possessions Played",
    mp.y =  "MP",
    war_playoffs = "WAR",    
    raptor_total.y = "RAPTOR",
    predator_total.y = "PREDATOR",
    radial_plots = "Radial Plots"
  ) %>% 
  # Adding tab headers
  tab_spanner(
    label = "Regular Season",
    columns = c(poss.x, mp.x, war_reg_season, raptor_total.x, predator_total.x) ) %>% 
  tab_spanner(
    label = "Playoffs",
    columns = c(poss.y, mp.y, war_playoffs, raptor_total.y, predator_total.y) ) %>%
  # Centering text
  cols_align(
    align = "center",
    columns =  c(poss.x, war_reg_season, raptor_total.x, predator_total.x, war_playoffs, 
                 poss.y, raptor_total.y, predator_total.y)) %>% 
  # Rounding to one decimal place
  fmt_number(columns = c(war_reg_season, raptor_total.x, predator_total.x, poss.y, mp.y,
                         war_playoffs, raptor_total.y, predator_total.y), decimals = 1) %>% 
  # Regular Season WAR
  tab_style(
    style = list(
      cell_fill(color = "#00BE67"),
      cell_text(weight = "bold") ),
    locations = cells_body(
      columns = war_reg_season,
      rows = war_reg_season > war_playoffs) ) %>%
  # Regular Season RAPTOR
  tab_style(
    style = list(
      cell_fill(color = "#00BE67"),
      cell_text(weight = "bold") ),
    locations = cells_body(
      columns = raptor_total.x,
      rows = raptor_total.x > raptor_total.y) ) %>%
  tab_style(
    style = list(
      cell_fill(color = "#F8766D") ),
    locations = cells_body(
      columns = raptor_total.x,
      rows = raptor_total.x < raptor_total.y) ) %>%
  # Regular Season PREDATOR
  tab_style(
    style = list(
      cell_fill(color = "#00BE67"),
      cell_text(weight = "bold") ),
    locations = cells_body(
      columns = predator_total.x,
      rows = predator_total.x > predator_total.y) ) %>%
  tab_style(
    style = list(
      cell_fill(color = "#F8766D") ),
    locations = cells_body(
      columns = predator_total.x,
      rows = predator_total.x < predator_total.y) ) %>% 
  # Playoffs WAR
  tab_style(
    style = list(
      cell_fill(color = "#F8766D") ),
    locations = cells_body(
      columns = war_playoffs,
      rows = war_reg_season > war_playoffs) ) %>%
  # Playoffs RAPTOR
  tab_style(
    style = list(
      cell_fill(color = "#00BE67"),
      cell_text(weight = "bold") ),
    locations = cells_body(
      columns = raptor_total.y,
      rows = raptor_total.x < raptor_total.y) ) %>%
  tab_style(
    style = list(
      cell_fill(color = "#F8766D") ),
    locations = cells_body(
      columns = raptor_total.y,
      rows = raptor_total.x > raptor_total.y) ) %>%
  # Playoffs PREDATOR
  tab_style(
    style = list(
      cell_fill(color = "#00BE67"),
      cell_text(weight = "bold") ),
    locations = cells_body(
      columns = predator_total.y,
      rows = predator_total.x < predator_total.y) ) %>%
  tab_style(
    style = list(
      cell_fill(color = "#F8766D") ),
    locations = cells_body(
      columns = predator_total.y,
      rows = predator_total.x > predator_total.y) ) %>% 
  # Adding images
  gt_img_rows(columns = logo, img_source = "local", height = 30) %>%
  tab_options(data_row.padding = px(1)) %>% 
  # Adding radial/spider plots
  text_transform(
    locations = cells_body(columns = c(radial_plots)),
    fn = function(x) {
      map(radial_plots, ggplot_image, height = px(300))
    } ) %>% 
  # Adding footnotes
  tab_footnote(
    footnote = "Minutes Played",
    locations = cells_column_labels(columns = c(mp.x, mp.y)) ) %>% 
  tab_footnote(
    footnote = "Wins Above Replacement",
    locations = cells_column_labels(columns = c(war_reg_season, war_playoffs) )  ) %>%
  tab_footnote(
    footnote = "Points above average per 100 possessions added by player on both offense and defense, using both box and on-off components",
    locations = cells_column_labels(columns = c(raptor_total.x, raptor_total.y)) ) %>%
  tab_footnote(
    footnote = "Predictive points above average per 100 possessions added by player on both offense and defense",
    locations = cells_column_labels(columns = c(predator_total.x, predator_total.y)) ) %>%
  tab_footnote(
    footnote = "Radial plots where regular season and playoff metrics are on the opposite side of each other. Points closer to the edge of the circle indicate higher scores.",
    locations = cells_column_labels(columns = radial_plots) ) %>% 
  # Adding divider between Regular season and playoffs
  gt_add_divider(
    predator_total.x,
    sides = "right",
    color = "lightgrey",
    style = "solid",
    weight = px(2),
    include_labels = TRUE
  ) %>% 
  tab_source_note(source_note = "From https://github.com/fivethirtyeight/data/blob/master/nba-raptor/README.md")
NBA Player Stats
Comparing performance during the regular and post=season for 2021-2022
Name Team Regular Season Playoffs Radial Plots5
Possessions Played MP1 WAR2 RAPTOR3 PREDATOR4 Possessions Played MP1 WAR2 RAPTOR3 PREDATOR4
Giannis Antetokounmpo 4735 2204 12.0 7.7 6.8 934.0 448.0 3.0 9.7 7.4
Jimmy Butler 3882 1931 6.7 4.1 4.1 1,237.0 629.0 4.9 12.0 11.1
Stephen Curry 4644 2211 10.1 6.1 7.1 1,574.0 764.0 4.8 8.9 10.5
DeMar DeRozan 5717 2743 6.2 1.7 0.9 429.0 203.0 0.0 −3.0 −4.2
Luka Doncic 4611 2301 10.1 5.7 6.3 1,072.0 552.0 3.5 9.2 12.2
Kevin Durant 4314 2047 8.9 5.7 5.1 353.0 176.0 −0.7 −9.6 −6.9
Joel Embiid 4640 2297 12.7 8.0 8.4 737.0 385.0 1.8 6.2 6.3
Nikola Jokic 5130 2476 22.7 14.9 14.7 351.0 171.0 1.2 10.3 12.2
Ja Morant 3993 1889 5.6 3.1 4.0 721.0 338.0 2.1 8.8 8.0
Chris Paul 4461 2139 6.9 3.6 4.5 868.0 448.0 2.0 5.9 4.4
From https://github.com/fivethirtyeight/data/blob/master/nba-raptor/README.md
1 Minutes Played
2 Wins Above Replacement
3 Points above average per 100 possessions added by player on both offense and defense, using both box and on-off components
4 Predictive points above average per 100 possessions added by player on both offense and defense
5 Radial plots where regular season and playoff metrics are on the opposite side of each other. Points closer to the edge of the circle indicate higher scores.