Introduction

With the game on the line, there are many variables that can affect the outcome. The pressure of winning games can affect the performances of players and their respective teams. For this analysis, we will focus on the performance of players and teams during clutch situations, exploring how their performances are affected by pressure situations. More specifically, do teams/players perform better or worse from the regular season to the playoffs?

The term “clutch” is defined as a situation when a game is within 5 points in the final 5 minutes. We will examine data that compares team clutch stats with non-clutch stats, focusing specifically on the regular season. Additionally, we will also analyze regular season and playoff clutch performances for both teams and players, respectively. The data we collected centered on 20 NBA seasons for team statistics from 2001-2002 to 2021-2022, and 10 NBA seasons for players from 2011-2012 to 2021-2022. We will use traditional statistics such as field goal and free throw percentages, win percentage, total points, and plus/minus, which measures whether the score margin increased or decreased with the team or player on the court. While there are numerous amounts of advanced stats, we will use only two metrics for this analysis: true shooting percentage, which uses field goal and free throw attempts to calculate a team and player’s shooting percentages, and turnover rate, which is calculated utilizing turnovers, field goal and free throw attempts. This data was obtained using the hoopR library. libraries.

Loading Packages

new_team_csv <- read.csv("https://raw.githubusercontent.com/moham6839/Data607_Final_Project/master/Team_Clutch_Data.csv")
DT::datatable(new_team_csv)
# split the dataset into regular season and playoff seasons
regular_season_dataset <- new_team_csv %>%
  filter(Season_Type == "Regular Season")

playoff_season_dataset <- new_team_csv %>%
  filter(Season_Type == "Playoffs")

Team Clutch Stats - Regular Season 2001-02 to 2021-22

team_rs_clutch <- regular_season_dataset %>%
  select(TEAM_NAME, PLUS_MINUS, FGM, FGA, FG3M, FG3A, FTM, FTA, GP, W, PTS, REB, TOV) %>%
  group_by(TEAM_NAME) %>%
  summarise(total_pm = sum(PLUS_MINUS),
  points_per_game = sum(PTS)/sum(GP),
  total_games = sum(GP),
  total_wins = sum(W),
  win_pct = sum(W)/sum(GP),
  total_points = sum(PTS),
  total_rebounds = sum(REB),
  total_fgm = sum(FGM),
  total_fga = sum(FGA),
  total_fgpct = sum(FGM)/sum(FGA),
  total_fg3pct = sum(`FG3M`)/sum(`FG3A`),
  total_ftm = sum(FTM),
  total_fta = sum(FTA),
  total_tov = sum(TOV),
  ft_pct = sum(FTM)/sum(FTA),
  #True shooting percentage: PT/(2*(FGA+0.44*FTA))
  true_shoot_pct = total_points/(2*(total_fga+0.44*total_fta)),
  # Turnover Rate = 100*TO/(FGA+0.44*FTA+TO)
  tov_rate = 100*total_tov /(total_fga+0.44*total_fta+total_tov)
  ) %>%
  arrange(desc(total_pm), win_pct)
DT::datatable(team_rs_clutch)

Team Regular Season Clutch Winning Percentage

team_rs_win_pct <- team_rs_clutch %>%
  select(TEAM_NAME, win_pct) %>%
  #filter(total_games >= 20) %>%
  arrange(desc(win_pct))
DT::datatable(team_rs_win_pct)
ggplot(data=team_rs_win_pct, aes(x=reorder(TEAM_NAME, win_pct), y=win_pct)) +
  geom_bar(stat="identity", position="dodge", fill = "orange") +
  theme_minimal() +
  labs(title="NBA Regular Season Team Clutch Win %",
       y="Win %",
       x="Team") +
  coord_flip()

Team Regular Season Clutch Total Plus/Minus

team_rs_pm <- team_rs_clutch %>%
  select(TEAM_NAME, total_pm) %>%
  arrange(desc(total_pm))
DT::datatable(team_rs_pm)
color <- ifelse(team_rs_pm$total_pm < 0, "lightblue", "orange")

ggplot(data=team_rs_pm, aes(x=reorder(TEAM_NAME, -total_pm), y=total_pm)) +
  geom_bar(stat="identity", 
           position="dodge",
           fill = color,
           show.legend = FALSE) +
  geom_text(aes(label = total_pm,
                hjust = ifelse(total_pm < 0, 1.25, -.25),
                vjust = 0.5),
            size = 2) +
  theme_minimal() +
  labs(title="NBA Regular Season Team Clutch Plus/Minus",
       y="Plus/Minus",
       x="Team") +
  coord_flip()

Team Regular Season Clutch Points Per Game

team_rs_ppg <- team_rs_clutch %>%
  select(TEAM_NAME, points_per_game) %>%
  arrange(desc(points_per_game))
DT::datatable(team_rs_ppg)
ggplot(data=team_rs_ppg, aes(x=reorder(TEAM_NAME, points_per_game), y=points_per_game)) +
  geom_bar(stat="identity", 
           position="dodge",
           color = "white",
           fill = "red",
           show.legend = FALSE) +
  geom_text(aes(label = round(points_per_game, 3),
                hjust = -.25,
                vjust = 0.25),
            size = 2)  +
  theme_minimal() +
  labs(title="NBA Regular Season Team Clutch Points Per Game",
       y="Points Per Game",
       x="Team") +
  coord_flip()

Team Regular Season Clutch Field Goal Percentage

team_rs_fgpct <- team_rs_clutch %>%
  select(TEAM_NAME, total_fgpct) %>%
  arrange(desc(total_fgpct))
DT::datatable(team_rs_fgpct)
ggplot(data=team_rs_fgpct, aes(x=reorder(TEAM_NAME, total_fgpct), y=total_fgpct)) +
  geom_bar(stat="identity", 
           position="dodge",
           color = "white",
           fill = "orange",
           show.legend = FALSE) +
  geom_text(aes(label = round(total_fgpct, 3),
                hjust = -.25,
                vjust = 0.25),
            size = 2)  +
  theme_minimal() +
  labs(title="NBA Regular Season Total Clutch Team Field Goal %",
       y="Field Goal Percentage",
       x="Team") +
  coord_flip()

Team Regular Season Clutch Free Throw Percentage

team_rs_ftpct <- team_rs_clutch %>%
  select(TEAM_NAME, ft_pct) %>%
  arrange(desc(ft_pct))
DT::datatable(team_rs_ftpct)
ggplot(data=team_rs_ftpct, aes(x=reorder(TEAM_NAME, ft_pct), y=ft_pct)) +
  geom_bar(stat="identity", 
           position="dodge",
           color = "white",
           fill = "lightblue",
           show.legend = FALSE) +
  geom_text(aes(label = round(ft_pct, 3),
                hjust = -.25,
                vjust = 0.25),
            size = 2) +
  theme_minimal() +
  labs(title="NBA Regular Season Team Clutch Free Throw %",
       y="Free Throw %",
       x="Team") +
  coord_flip()

Team Regular Season Clutch True Shooting Percentage

team_rs_ts <- team_rs_clutch %>%
  select(TEAM_NAME, true_shoot_pct) %>%
  arrange(desc(true_shoot_pct))
DT::datatable(team_rs_ts)
ggplot(data=team_rs_ts, aes(x=reorder(TEAM_NAME, true_shoot_pct), y=true_shoot_pct)) +
  geom_bar(stat="identity", 
           position="dodge",
           color = "white",
           fill = "red",
           show.legend = FALSE) +
  geom_text(aes(label = round(true_shoot_pct, 3),
                hjust = -.25,
                vjust = 0.25),
            size = 2) +
  theme_minimal() +
  labs(y="True Shooting Percentage",
       x="Team") +
  coord_flip()

Team Regular Season Clutch Turnover Rate

team_rs_tov <- team_rs_clutch %>%
  select(TEAM_NAME, tov_rate) %>%
  arrange(desc(tov_rate))
DT::datatable(team_rs_tov)
ggplot(data=team_rs_tov, aes(x=reorder(TEAM_NAME, -tov_rate), y=tov_rate)) +
  geom_bar(stat="identity", 
           position="dodge",
           fill = "lightblue") +
  geom_text(aes(label = round(tov_rate, 3),
                hjust = -.25,
                vjust = 0.25),
            size = 2) +
  theme_minimal() +
  labs(
       y="Turnover Rate",
       x="Team") +
  coord_flip()

# Team Clutch Stats - Playoffs 2001-02 to 2021-22

team_playoff_clutch <- playoff_season_dataset %>%
  select(TEAM_NAME, PLUS_MINUS, FGM, FGA, FG3M, FG3A, FTM, FTA, GP, W, PTS, REB, TOV) %>%
  group_by(TEAM_NAME) %>%
  summarise(total_pm = sum(PLUS_MINUS),
  points_per_game = sum(PTS)/sum(GP),
  total_games = sum(GP),
  total_wins = sum(W),
  win_pct = sum(W)/sum(GP),
  total_points = sum(PTS),
  total_rebounds = sum(REB),
  total_fgm = sum(FGM),
  total_fga = sum(FGA),
  total_fgpct = sum(FGM)/sum(FGA),
  total_fg3pct = sum(`FG3M`)/sum(`FG3A`),
  total_ftm = sum(FTM),
  total_fta = sum(FTA),
  total_tov = sum(TOV),
  ft_pct = sum(FTM)/sum(FTA),
  #True shooting percentage: PT/(2*(FGA+0.44*FTA))
  true_shoot_pct = total_points/(2*(total_fga+0.44*total_fta)),
  # Turnover Rate = 100*TO/(FGA+0.44*FTA+TO)
  tov_rate = 100*total_tov /(total_fga+0.44*total_fta+total_tov)
  ) %>%
  # using filter to remove outlier
  filter(win_pct < 0.8) %>%
  arrange(desc(total_pm), win_pct)
DT::datatable(team_playoff_clutch)

Team Playoffs Clutch Winning Percentage (Minimum 10 games)

team_playoff_win_pct <- team_playoff_clutch %>%
  select(TEAM_NAME, win_pct, total_games) %>%
  filter(total_games >= 10) %>%
  arrange(desc(win_pct))
DT::datatable(team_playoff_win_pct)
ggplot(data=team_playoff_win_pct, aes(x=reorder(TEAM_NAME, win_pct), y=win_pct)) +
  geom_bar(stat="identity", 
           position="dodge",
           fill = "red") +
  theme_minimal() +
  labs(title="NBA Playoffs Team Clutch Win Percentage",
       y="Win Percentage",
       x="Team") +
  coord_flip()

Team Playoffs Clutch Total Plus/Minus

team_playoff_pm <- team_playoff_clutch %>%
  select(TEAM_NAME, total_pm) %>%
  arrange(desc(total_pm))
DT::datatable(team_playoff_pm)
color <- ifelse(team_playoff_pm$total_pm < 0, "lightblue", "orange")
ggplot(data=team_playoff_pm, aes(x=reorder(TEAM_NAME, -total_pm), y=total_pm)) +
  geom_bar(stat="identity", 
           position="dodge",
           fill = color) +
  geom_text(aes(label = round(total_pm, 3),
                hjust = ifelse(total_pm < 0, 1.25, -.25),
                vjust = 0.25),
            size = 2) +
  theme_minimal() +
  labs(title="NBA Playoffs Team Clutch Plus/Minus",
       y="Plus/Minus",
       x="Team") +
  coord_flip()

Team Playoffs Clutch Field Goal Percentage

team_playoff_fg_pct <- team_playoff_clutch %>%
  select(TEAM_NAME, total_fgpct) %>%
  arrange(desc(total_fgpct))
DT::datatable(team_playoff_fg_pct)
ggplot(data=team_playoff_fg_pct, aes(x=reorder(TEAM_NAME, total_fgpct), y=total_fgpct)) +
  geom_bar(stat="identity", 
           position="dodge",
           fill = "orange") +
  geom_text(aes(label = round(total_fgpct, 3),
                hjust = -.25,
                vjust = 0.25),
            size = 2) +
  theme_minimal() +
  labs(title="NBA Playoffs Team Clutch Field Goal %",
       y="Field Goal %",
       x="Team") +
  coord_flip()

Team Playoffs Clutch Free Throw Percentage

team_playoff_ft_pct <- team_playoff_clutch %>%
  select(TEAM_NAME, ft_pct) %>%
  arrange(desc(ft_pct))
DT::datatable(team_playoff_ft_pct)
ggplot(data=team_playoff_ft_pct, aes(x=reorder(TEAM_NAME, ft_pct), y=ft_pct)) +
  geom_bar(stat="identity", 
           position="dodge",
           fill = "lightblue") +
  geom_text(aes(label = round(ft_pct, 3),
                hjust = -.25,
                vjust = 0.25),
            size = 2) +
  theme_minimal() +
  labs(title="NBA Playoffs Team Clutch Free Throw %",
       y="Free Throw %",
       x="Team") +
  coord_flip()

Team Playoffs Clutch Points Per Game

team_playoff_ppg <- team_playoff_clutch %>%
  select(TEAM_NAME, points_per_game) %>%
  arrange(desc(points_per_game))
DT::datatable(team_playoff_ppg)
ggplot(data=team_playoff_ppg, aes(x=reorder(TEAM_NAME, points_per_game), y=points_per_game)) +
  geom_bar(stat="identity", 
           position="dodge",
           fill = "red") +
  geom_text(aes(label = round(points_per_game, 3),
                hjust = -.25,
                vjust = 0.25),
            size = 2) +
  theme_minimal() +
  labs(title="NBA Playoffs Team Clutch Points Per Game ",
       y="Total Points",
       x="Team") +
  coord_flip()

Team Playoffs Clutch True Shooting Percentage

team_playoff_ts <- team_playoff_clutch %>%
  select(TEAM_NAME, true_shoot_pct) %>%
  arrange(desc(true_shoot_pct))
DT::datatable(team_playoff_ts)
ggplot(data=team_playoff_ts, aes(x=reorder(TEAM_NAME, true_shoot_pct), y=true_shoot_pct)) +
  geom_bar(stat="identity", 
           position="dodge",
           fill = "orange") +
  geom_text(aes(label = round(true_shoot_pct, 3),
                hjust = -.25,
                vjust = 0.25),
            size = 2) +
  theme_minimal() +
  labs(title="NBA Playoffs Team Clutch True Shooting % ",
       y="True Shooting %",
       x="Team") +
  coord_flip()

Team Playoffs Clutch Turnover Rate

team_playoff_tov <- team_playoff_clutch %>%
  select(TEAM_NAME, tov_rate) %>%
  arrange(desc(tov_rate))
DT::datatable(team_playoff_tov)
ggplot(data=team_playoff_tov, aes(x=reorder(TEAM_NAME, -tov_rate), y=tov_rate)) +
  geom_bar(stat="identity", 
           position="dodge",
           fill = "lightblue") +
  geom_text(aes(label = round(tov_rate, 3),
                hjust = -.25,
                vjust = 0.25),
            size = 2) +
  theme_minimal() +
  labs(title="NBA Playoffs Team Clutch Turnover Rate",
       y="Turnover Rate",
       x="Team") +
  coord_flip()

## Combining Team Regular Season and Playoff Clutch Stats

Win Percentage

combined_team_winpct <- inner_join(team_rs_win_pct, team_playoff_win_pct, by="TEAM_NAME") %>%
  mutate(diff = win_pct.y - win_pct.x) %>%
  arrange(desc(diff))
DT::datatable(combined_team_winpct)
color <- ifelse(combined_team_winpct$diff < 0, "lightblue", "orange")
  
ggplot(data=combined_team_winpct, aes(x=reorder(TEAM_NAME, diff), y=diff)) +
  geom_bar(stat="identity", 
           position="dodge",
           fill = color) +
  geom_text(aes(label = round(diff, 3),
                hjust = ifelse(diff < 0, 1.25, -.25),
                vjust = 0.25),
            size = 2) +
  theme_minimal() +
  labs(title="Team Differential in Win %",
       y="Win %",
       x="Team") +
  coord_flip()

### Plus/Minus

combined_team_pm <- inner_join(team_rs_pm, team_playoff_pm, by="TEAM_NAME") %>%
  mutate(diff = total_pm.x - total_pm.y) %>%
  arrange(desc(diff))
DT::datatable(combined_team_pm)
ggplot(data=combined_team_pm, aes(x=reorder(TEAM_NAME, diff), y=diff)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="Team Differential in Plus/Minus",
       y="Plus/Minus",
       x="Team") +
  coord_flip()

Field Goal %

combined_team_fgpct <- inner_join(team_rs_fgpct, team_playoff_fg_pct, by="TEAM_NAME") %>%
  mutate(diff = round((total_fgpct.y - total_fgpct.x), digits=3)) %>%
  arrange(desc(diff))
DT::datatable(combined_team_fgpct)
color <- ifelse(combined_team_fgpct$diff < 0, "lightblue", "orange")
ggplot(data=combined_team_fgpct, aes(x=reorder(TEAM_NAME, diff), y=diff)) +
  geom_bar(stat="identity", position="dodge", fill = color) +
  geom_text(aes(label = round(diff, 3),
                hjust = ifelse(diff < 0, 1.25, -.25),
                vjust = 0.25),
            size = 2) +
  theme_minimal() +
  labs(title="Team Differential in Field Goal %",
       y="Win %",
       x="Team") +
  coord_flip()

Points Per Game

combined_team_ppg <- inner_join(team_rs_ppg, team_playoff_ppg, by="TEAM_NAME") %>%
  mutate(diff = round((points_per_game.y - points_per_game.x), digits=3))%>%
  arrange(desc(diff))
DT::datatable(combined_team_ppg)
color <- ifelse(combined_team_ppg$diff < 0, "lightblue", "orange")
ggplot(data=combined_team_ppg, aes(x=reorder(TEAM_NAME, diff), y=diff)) +
  geom_bar(stat="identity", position="dodge", fill = color) +
  geom_text(aes(label = round(diff, 3),
                hjust = ifelse(diff < 0, 1.25, -.25),
                vjust = 0.25),
            size = 2) +
  theme_minimal() +
  labs(title="Team Differential in Points Per Game",
       y="PPG",
       x="Team") +
  coord_flip()

Free Throw Percentage

combined_team_ftpct <- inner_join(team_rs_ftpct, team_playoff_ft_pct, by="TEAM_NAME") %>%
  mutate(diff = ft_pct.y - ft_pct.x) %>%
  arrange(desc(diff))
DT::datatable(combined_team_ftpct)
color <- ifelse(combined_team_ftpct$diff < 0, "lightblue", "orange")
ggplot(data=combined_team_ftpct, aes(x=reorder(TEAM_NAME, diff), y=diff)) +
  geom_bar(stat="identity", position="dodge", fill = color) +
  geom_text(aes(label = round(diff, 3),
                hjust = ifelse(diff < 0, 1.25, -.25),
                vjust = 0.25),
            size = 2) +
  theme_minimal() +
  labs(title="Team Differential in Free Throw %",
       y="FT %",
       x="Team") +
  coord_flip()

True Shooting %

combined_team_tspct <- inner_join(team_rs_ts, team_playoff_ts, by="TEAM_NAME") %>%
  mutate(diff = true_shoot_pct.y - true_shoot_pct.x) %>%
  arrange(desc(diff))
DT::datatable(combined_team_tspct)
color <- ifelse(combined_team_tspct$diff < 0, "lightblue", "orange")
ggplot(data=combined_team_tspct, aes(x=reorder(TEAM_NAME, diff), y=diff)) +
  geom_bar(stat="identity", position="dodge", fill = color) +
  geom_text(aes(label = round(diff, 3),
                hjust = ifelse(diff < 0, 1.25, -.25),
                vjust = 0.25),
            size = 2) +
  theme_minimal() +
  labs(title="Team Differential in True Shooting %",
       y="TS %",
       x="Team") +
  coord_flip()

Turnover Rate

combined_team_tov <- inner_join(team_rs_tov, team_playoff_tov, by="TEAM_NAME") %>%
  mutate(diff = tov_rate.y - tov_rate.x) %>%
  arrange(desc(diff))
DT::datatable(combined_team_tov)
color <- ifelse(combined_team_tov$diff < 0, "lightblue", "orange")
ggplot(data=combined_team_tov, aes(x=reorder(TEAM_NAME, diff), y=diff)) +
  geom_bar(stat="identity", position="dodge", fill = color) +
  geom_text(aes(label = round(diff, 3),
                hjust = ifelse(diff < 0, 1.25, -.25),
                vjust = 0.25),
            size = 2) +
  theme_minimal() +
  labs(title="Team Differential in Turnover Rate",
       y="Turnover Rate",
       x="Team") +
  coord_flip()

Team Takeaways

After reviewing both regular and playoffs season, we see that under pressure, teams performance do get impacted, for better or worse. For instance, Cleveland Cavalier have the biggest change in terms of winning percentage due to an average winning performance during regular season. They do score the highest percentage during playoffs.

In term of points per game, Orlando Magic shows the biggest change. Interestingly, Portland Trail Blazer have the highest PPG on both regular and playoffs season, hence the differential is small.

For Field Goal %, Philly 76ers shows the strongest change due lackluster % in regular reason and big improvement in the playoffs. Utah Jazz have the top spot of field goal % during regular season, but performed poorly during playoffs. For playoffs season, that goes to Phoenix Suns.

We see a lot of significant changes in free throw %. Dallas Maverick have the highest free throw % during regular season and Atlanta Hawks have the highest with 84% during playoffs, making a significant change with 7.2% increase. Maverick is not that far off with 82.3% in that aspect.

We see small positive changes on true shooting %. Utah Jazz have the highest true shooting % during playoffs while LA Clippers scores the highest in regular seasons. From there, we see a significant drop with Clippers (-23.5%).

Finally on turnover rate, we see that Milwaukee Buck have the biggest change. They performed below average during regular season and exceed during playoffs.

Cleveland, Miami, Golden State, and Los Angelos Lakers were the top 4 teams in team clutch win % differential from the regular season to playoffs. Unsurprisingly, all four teams have won NBA championships.

During the regular season, the San Antonio Spurs performed superbly in the clutch. They were 1st in clutch win percentage, ranking 2nd in Plus/Minus, 5th in field goal clutch percentage, 6th in points per game and true shooting percentage. This success in clutch moments translated to the playoffs, ranking 5th in clutch win %, 4th in clutch field goal percentage, 4th in plus/minus, 4th in points per game and 7th in true shooting %.

Player Clutch Data - Regular Season 2011-12 to 2021-22

new_player_csv <- read.csv("https://raw.githubusercontent.com/moham6839/Data607_Final_Project/master/Player_Clutch_Data.csv")
DT::datatable(new_player_csv)
## Warning in instance$preRenderHook(instance): It seems your data is too big for
## client-side DataTables. You may consider server-side processing:
## https://rstudio.github.io/DT/server.html
regular_season_player_dataset <- new_player_csv %>%
  filter(Season_Type == "Regular Season")
rs_clutch_player <- regular_season_player_dataset %>%
  select(PLAYER_NAME, PLUS_MINUS, FGM, FGA, FG3M, FG3A, FTM, FTA, GP, W, PTS, REB, TOV) %>%
  group_by(PLAYER_NAME) %>%
  summarise(total_pm = sum(PLUS_MINUS),
  points_per_game = sum(PTS)/sum(GP),
  total_games = sum(GP),
  total_wins = sum(W),
  win_pct = sum(W)/sum(GP),
  total_points = sum(PTS),
  total_rebounds = sum(REB),
  total_fgm = sum(FGM),
  total_fga = sum(FGA),
  total_fgpct = sum(FGM)/sum(FGA),
  total_fg3pct = sum(`FG3M`)/sum(`FG3A`),
  total_ftm = sum(FTM),
  total_fta = sum(FTA),
  total_tov = sum(TOV),
  ft_pct = sum(FTM)/sum(FTA),
  #True shooting percentage: PT/(2*(FGA+0.44*FTA))
  true_shoot_pct = total_points/(2*(total_fga+0.44*total_fta)),
  # Turnover Rate = 100*TO/(FGA+0.44*FTA+TO)
  tov_rate = 100*total_tov /(total_fga+0.44*total_fta+total_tov)
  ) %>%
  arrange(desc(total_pm), win_pct)
DT::datatable(rs_clutch_player)

Regular Season Top 25 Players Clutch Total Plus/Minus

player_rs_pm <- rs_clutch_player %>%
  select(PLAYER_NAME, total_pm, total_games) %>%
  filter(total_games >= 200) %>%
  arrange(desc(total_pm))
DT::datatable(player_rs_pm)
ggplot(data=player_rs_pm[1:25, ], aes(x=reorder(PLAYER_NAME, -total_pm), y=total_pm)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="NBA Regular Season Top 25 Player Clutch Plus/Minus",
       y="Total Plus/Minus",
       x="Player") +
  coord_flip()

Regular Season Top 25 Players Clutch Win Percentage (Minimum 200 games)

player_rs_win_pct <- rs_clutch_player %>%
  select(PLAYER_NAME, win_pct, total_games) %>%
  filter(total_games >= 200) %>%
  arrange(desc(win_pct))
DT::datatable(player_rs_win_pct)
ggplot(data=player_rs_win_pct[1:25, ], aes(x=reorder(PLAYER_NAME, -win_pct), y=win_pct)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="NBA Top 25 Regular Season Player Clutch Win %",
       y="Win Percentage",
       x="Player") +
  coord_flip()

Regular Season Top 25 Players Clutch Total Points Per Game (Minimum 200 games)

player_rs_ppg <- rs_clutch_player %>%
  select(PLAYER_NAME, points_per_game, total_games) %>%
  filter(total_games >= 200) %>%
  arrange(desc(points_per_game))
DT::datatable(player_rs_ppg)
ggplot(data=player_rs_ppg[1:25, ], aes(x=reorder(PLAYER_NAME, -points_per_game), y=points_per_game)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="NBA Regular Season Top 25 Player Clutch Points Per Game",
       y="Points Per Game",
       x="Player") +
  coord_flip()

Regular Season Top 25 Players Clutch Field Goal Percentage (Minimum 200 Field Goal Attempts)

player_rs_fgpct <- rs_clutch_player %>%
  select(PLAYER_NAME, total_fgpct, total_fga) %>%
  filter(total_fga >=200) %>%
  arrange(desc(total_fgpct))
DT::datatable(player_rs_fgpct)
ggplot(data=player_rs_fgpct[1:25, ], aes(x=reorder(PLAYER_NAME, -total_fgpct), y=total_fgpct)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="NBA Regular Season Top 25 Player Clutch Field Goal %",
       y="Field Goal %",
       x="Player") +
  coord_flip()

Regular Season Top 25 Players Clutch Free Throw Percentange (Minimum 200 games and 100 Free Throws)

player_rs_ftpct <- rs_clutch_player %>%
  select(PLAYER_NAME, ft_pct, total_fta, total_games) %>%
  filter(total_games >= 200 & total_fta >=100) %>%
  arrange(desc(ft_pct))
DT::datatable(player_rs_ftpct)
ggplot(data=player_rs_ftpct[1:25, ], aes(x=reorder(PLAYER_NAME, -ft_pct), y=ft_pct)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="NBA Regular Season Top 20 Player Clutch Free Throw %",
       y="Free Throw %",
       x="Player") +
  coord_flip()

Regular Season Top 25 Players Clutch True Shooting Percentage (Minimum 200 Field Goal Attempts and 100 Free Throw Attempts)

player_rs_tspct <- rs_clutch_player %>%
  select(PLAYER_NAME, true_shoot_pct, total_fga, total_fta) %>%
  filter(total_fga >= 200 & total_fta >= 100) %>%
  arrange(desc(true_shoot_pct))
DT::datatable(player_rs_tspct)
ggplot(data=player_rs_tspct[1:25, ], aes(x=reorder(PLAYER_NAME, -true_shoot_pct), y=true_shoot_pct)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="NBA Regular Season Top 25 Player Clutch True Shooting %",
       y="True Shooting %",
       x="Player") +
  coord_flip()

Player Clutch Data - Playoffs 2011-12 to 2021-22

playoff_season_player_dataset <- new_player_csv %>%
  filter(Season_Type == "Playoffs")
playoff_performers <- playoff_season_player_dataset %>%
  select(PLAYER_NAME, PLUS_MINUS, FGM, FGA, FG3M, FG3A, FTM, FTA, GP, W, PTS, REB, TOV) %>%
  group_by(PLAYER_NAME) %>%
  summarise(total_pm = sum(PLUS_MINUS),
  points_per_game = sum(PTS)/sum(GP),
  total_games = sum(GP),
  total_wins = sum(W),
  win_pct = sum(W)/sum(GP),
  total_points = sum(PTS),
  total_rebounds = sum(REB),
  total_fgm = sum(FGM),
  total_fga = sum(FGA),
  total_fgpct = sum(FGM)/sum(FGA),
  total_fg3pct = sum(`FG3M`)/sum(`FG3A`),
  total_ftm = sum(FTM),
  total_fta = sum(FTA),
  total_tov = sum(TOV),
  ft_pct = sum(FTM)/sum(FTA),
  #True shooting percentage: PT/(2*(FGA+0.44*FTA))
  true_shoot_pct = total_points/(2*(total_fga+0.44*total_fta)),
  # Turnover Rate = 100*TO/(FGA+0.44*FTA+TO)
  tov_rate = 100*total_tov /(total_fga+0.44*total_fta+total_tov)
  ) %>%
  arrange(desc(total_pm), win_pct)
DT::datatable(playoff_performers)

Playoffs Top 20 Players Clutch Total Plus/Minus (Minimum 20 Games)

player_playoff_pm <- playoff_performers %>%
  select(PLAYER_NAME, total_pm, total_games) %>%
  #filter(total_games >= 25) %>%
  arrange(desc(total_pm))
DT::datatable(player_playoff_pm)
ggplot(data=player_playoff_pm[1:20, ], aes(x=reorder(PLAYER_NAME, -total_pm), y=total_pm)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="NBA Playoffs Top 20 Players Clutch Plus/Minus",
       y="Plus/Minus",
       x="Player") +
  coord_flip()

Playoffs Bottom 20 Players Clutch Total Plus/Minus

ggplot(data=playoff_performers[494:514, ], aes(x=reorder(PLAYER_NAME, total_pm), y=total_pm)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="NBA Playoffs Bottom 20 Players Clutch Plus/Minus",
       y="Plus/Minus",
       x="Player") +
  coord_flip()

Playoffs Top 20 Players Clutch Win Percentage (Minimum 20 Playoff Games)

player_playoff_win_pct <- playoff_performers %>%
  select(PLAYER_NAME, win_pct, total_games) %>%
  filter(total_games >= 20) %>%
  arrange(desc(win_pct))
DT::datatable(player_playoff_win_pct)
ggplot(data=player_playoff_win_pct[1:20, ], aes(x=reorder(PLAYER_NAME, -win_pct), y=win_pct)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="NBA Playoffs Top 20 Players Clutch Win %",
       y="Win %",
       x="Player") +
  coord_flip()

Playoffs Bottom 20 Players Clutch Win Percentage

ggplot(data=player_playoff_win_pct[71:91, ], aes(x=reorder(PLAYER_NAME, win_pct), y=win_pct)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="NBA Playoffs Bottom 20 Players Clutch Win %",
       y="Win %",
       x="Player") +
  coord_flip()

Playoffs Top 20 Players Clutch Field Goal Percentage (Minimum 40 Field Goal Attempts)

player_playoff_fg_pct <- playoff_performers %>%
  select(PLAYER_NAME, total_fgpct, total_fga) %>%
  filter(total_fga >= 40) %>%
  arrange(desc(total_fgpct))
DT::datatable(player_playoff_fg_pct)
ggplot(data=player_playoff_fg_pct[1:20, ], aes(x=reorder(PLAYER_NAME, -total_fgpct), y=total_fgpct)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="NBA Playoffs Top 20 Players Clutch Field Goal %",
       y="Win %",
       x="Player") +
  coord_flip()

Playoffs Bottom 20 Players Clutch Field Goal Percentage

ggplot(data=player_playoff_fg_pct[21:41, ], aes(x=reorder(PLAYER_NAME, total_fgpct), y=total_fgpct)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="NBA Playoffs Bottom 20 Players Clutch Field Goal %",
       y="Field Goal %",
       x="Player") +
  coord_flip()

Playoffs Player Clutch Free Throw Percentage (Minimum 25 Free Throw Attempts)

player_playoff_ft_pct <- playoff_performers %>%
  select(PLAYER_NAME, ft_pct, total_fta) %>%
  filter(total_fta >= 25) %>%
  arrange(desc(ft_pct))
DT::datatable(player_playoff_ft_pct)
ggplot(data=player_playoff_ft_pct[1:32, ], aes(x=reorder(PLAYER_NAME, -ft_pct), y=ft_pct)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title= "NBA Playoffs Players Clutch Free Throw %",
       y="Free Throw %",
       x="Player") +
  coord_flip()

Playoffs Top 20 Players Clutch Points Per Game (Minimum 25 games)

player_playoff_ppg <- playoff_performers %>%
  select(PLAYER_NAME, points_per_game, total_games) %>%
  filter(total_games >= 25) %>%
  arrange(desc(points_per_game))
DT::datatable(player_playoff_ppg)
ggplot(data=player_playoff_ppg[1:20, ], aes(x=reorder(PLAYER_NAME, -points_per_game), y=points_per_game)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="NBA Playoffs Top 20 Players Clutch Points Per Game",
       y="Points Per Game",
       x="Player") +
  coord_flip()

Playoffs Bottom 20 Players Clutch Points Per Game

ggplot(data=player_playoff_ppg[45:65, ], aes(x=reorder(PLAYER_NAME, -points_per_game), y=points_per_game)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="NBA Playoffs Bottom 20 Players Clutch Points Per Game",
       y="Points Per Game",
       x="Player") +
  coord_flip()

Playoffs Top 20 Players Clutch True Shooting Percentage (Minimum 50 Field Goal Attempts and 25 Free Throw Attempts)

player_playoff_ts_pct <- playoff_performers %>%
  select(PLAYER_NAME, true_shoot_pct, total_fga, total_fta) %>%
  filter(total_fga >= 50 & total_fta >= 25) %>%
  arrange(desc(true_shoot_pct))
DT::datatable(player_playoff_ts_pct)
ggplot(data=player_playoff_ts_pct[1:20, ], aes(x=reorder(PLAYER_NAME, -true_shoot_pct), y=true_shoot_pct)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="NBA Playoffs Top Players True Shooting %",
       y="True Shooting %",
       x="Player") +
  coord_flip()

Combining Player Regular Season and Playoff Clutch Stats

Field Goal %

combined_fgpct_df <- inner_join(player_rs_fgpct, player_playoff_fg_pct, by="PLAYER_NAME") %>%
  mutate(diff = total_fgpct.y - total_fgpct.x) %>%
  arrange(diff)
DT::datatable(combined_fgpct_df)
ggplot(data=combined_fgpct_df, aes(x=reorder(PLAYER_NAME, -diff), y=diff)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="NBA Playoffs Player Field Goal %",
       y="Field Goal %",
       x="Player") +
  coord_flip()

Points Per Game

combined_ppg_df <- inner_join(player_rs_ppg, player_playoff_ppg, by="PLAYER_NAME") %>%
  mutate(diff = round((points_per_game.y - points_per_game.x), digits=3)) %>%
  arrange(diff)
DT::datatable(combined_ppg_df)
ggplot(data=combined_ppg_df[1:20, ], aes(x=reorder(PLAYER_NAME, diff), y=diff)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="Player Points Per Game (Minimum 25 Games)",
       y="Plus/Minus",
       x="Player") +
  coord_flip()

ggplot(data=combined_ppg_df[27:47, ], aes(x=reorder(PLAYER_NAME, -diff), y=diff)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="Player Difference Points Per Game (Minimum 25 Games)",
       y="Points Per Game",
       x="Player") +
  coord_flip()

Free Throw Percentage

combined_ftpct_df <- inner_join(player_rs_ftpct, player_playoff_ft_pct, by="PLAYER_NAME") %>%
  mutate(diff = round((ft_pct.y - ft_pct.x), digits=3)) %>%
  arrange(diff)
DT::datatable(combined_ftpct_df)
ggplot(data=combined_ftpct_df, aes(x=reorder(PLAYER_NAME, -diff), y=diff)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="Player Free Throw Percentage Difference (Minimum 25 FTA)",
       y="Plus/Minus",
       x="Player") +
  coord_flip()

Win Percentage

combined_win_pct_df <- inner_join(player_rs_win_pct, player_playoff_win_pct, by="PLAYER_NAME") %>%
  mutate(diff = round((win_pct.y - win_pct.x), digits=3)) %>%
  arrange(desc(diff))
DT::datatable(combined_win_pct_df)
ggplot(data=combined_win_pct_df[1:20, ], aes(x=reorder(PLAYER_NAME, -diff), y=diff)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="Player Winning % Difference (Minimum 200 Reg. Season and 20 Playoff Games)",
       y="Win %",
       x="Player") +
  coord_flip()

ggplot(data=combined_win_pct_df[45:65, ], aes(x=reorder(PLAYER_NAME, -diff), y=diff)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="Player Win % Difference (Minimum 200 Reg. Season and 20 PO Games)",
       y="Win %",
       x="Player") +
  coord_flip()

True Shooting Percentage

combined_tspct_df <- inner_join(player_rs_tspct, player_playoff_ts_pct, by="PLAYER_NAME") %>%
  mutate(diff = round((true_shoot_pct.y - true_shoot_pct.x), digits=3)) %>%
  arrange(desc(diff))
DT::datatable(combined_tspct_df)
ggplot(data=combined_tspct_df[1:19, ], aes(x=reorder(PLAYER_NAME, -diff), y=diff)) +
  geom_bar(stat="identity", position="dodge") +
  theme_minimal() +
  labs(title="Player TS % Difference",
       y="TS %",
       x="Player") +
  coord_flip()

Player Takeaways

  • Kyrie Irving has the biggest decline in Points Per Game in clutch situations from the regular season to playoffs, followed by Russell Westbrook. Other notable names in the top 10 in declining points per game were Kevin Durant and James Harden.

  • While Chris Paul had the highest positive Plus/Minus number among all players in the regular season, he had one of the highest negative Plus/Minus in the playoffs. Notably, his clutch win % declined in the playoffs as well, from winning 65% of regular season games to just 50% in the playoffs.

  • LeBron James produced in the clutch. He ranked 3rd in points per game during the regular season and playoffs, with a slight decrease of approximately 0.14 points from the regular season to playoffs. Despite having a decrease in field goal percentage from the regular season to playoffs, his win percentage increased by 0.03, illustrating his level of clutch play.

  • While Kawhi Leonard ranked first in clutch win percentage during the regular season, he did not rank high in some of the statistical categories. He ranked 12th in plus/minus, 24th in points per game, and 54th in field goal percentage. Additionally, there was a significant decrease in win percentage , from close to 66% in the regular season to 52% in the playoffs, showing declines in both points per game and field goal percentage.

Next Steps

Possible next steps that can be explored include using more advanced stats such as rebounding rate, effective field goal and assist percentages. Additionally, with the exception of Plus/Minus, this analysis does not measure the impact of defense in clutch situations. Traditional defensive stats such as blocks and steals, as well as advanced stats such as defensive efficiency, could shed light on the affects of defense in clutch situations from a team perspective.

Conclusion

The game of basketball is a team sport. However, players who are capable of making pressurized shots that help their teams win games is an important quality that enables teams to win a lot of games and in some situations a championship. Performing in the clutch is a special skill that permeates throughout sports. In basketball, being “clutch” means that a player is able to make a score a basket or make a play that helps their team to a win. This analysis explores that various stats that can help explain how teams and players have performed in clutch situations, providing a groundwork to build on to perform further and future analyses on clutch team and player stats.