This is an R Markdown document. Markdown is a simple formatting syntax for authoring HTML, PDF, and MS Word documents. For more details on using R Markdown see http://rmarkdown.rstudio.com.
When you click the Knit button a document will be generated that includes both content as well as the output of any embedded R code chunks within the document. You can embed an R code chunk like this:
The purpose of this analysis is to evaluate player performance in the 2021–2022 NBA Playoffs through a General Manager (GM) lens, focusing on efficiency, impact, and role value rather than raw box score totals.
This project uses player-level statistics from the 2021–2022 NBA Playoffs, including scoring, assists, rebounds, minutes played, shooting, and defensive metrics. Playoff data was chosen because it reflects performance under high-pressure, reduced-rotation conditions, where efficiency and decision-making are most important.
This analysis helps a GM: - Identify undervalued rotation
players who produce efficiently in limited minutes
- Spot high-minute, low-impact players who may be
playoff liabilities
- Optimize playoff rotations and minute
allocation
- Make trade, contract, and lineup decisions based on
efficiency rather than reputation
- Understand how playoff basketball shifts player
value
summary(cars)
## speed dist
## Min. : 4.0 Min. : 2.00
## 1st Qu.:12.0 1st Qu.: 26.00
## Median :15.0 Median : 36.00
## Mean :15.4 Mean : 42.98
## 3rd Qu.:19.0 3rd Qu.: 56.00
## Max. :25.0 Max. :120.00
You can also embed plots, for example:
Note that the echo = FALSE parameter was added to the
code chunk to prevent printing of the R code that generated the
plot.
nba_playoffs <- read.csv(
"~/Downloads/2021-2022 NBA Player Stats - Playoffs.csv",
sep = ";",
fileEncoding = "latin1"
)
knitr::kable(head(nba_playoffs, 6))
| Rk | Player | Pos | Age | Tm | G | GS | MP | FG | FGA | FG. | X3P | X3PA | X3P. | X2P | X2PA | X2P. | eFG. | FT | FTA | FT. | ORB | DRB | TRB | AST | STL | BLK | TOV | PF | PTS |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | Precious Achiuwa | C | 22 | TOR | 6 | 1 | 27.8 | 4.2 | 8.7 | 0.481 | 0.8 | 2.7 | 0.313 | 3.3 | 6.0 | 0.556 | 0.529 | 1.0 | 1.7 | 0.600 | 1.3 | 3.5 | 4.8 | 1.0 | 0.2 | 0.8 | 1.5 | 2.3 | 10.2 |
| 2 | Steven Adams | C | 28 | MEM | 7 | 5 | 16.3 | 1.3 | 3.0 | 0.429 | 0.0 | 0.0 | 0.000 | 1.3 | 3.0 | 0.429 | 0.429 | 0.9 | 1.6 | 0.545 | 2.1 | 4.3 | 6.4 | 2.1 | 0.1 | 0.1 | 0.6 | 1.7 | 3.4 |
| 3 | Bam Adebayo | C | 24 | MIA | 18 | 18 | 34.1 | 5.8 | 9.7 | 0.594 | 0.0 | 0.1 | 0.000 | 5.8 | 9.7 | 0.598 | 0.594 | 3.2 | 4.2 | 0.763 | 2.1 | 5.9 | 8.0 | 2.7 | 1.0 | 0.7 | 2.1 | 3.1 | 14.8 |
| 4 | Nickeil Alexander-Walker | SG | 23 | UTA | 1 | 0 | 5.0 | 2.0 | 2.0 | 1.000 | 0.0 | 0.0 | 0.000 | 2.0 | 2.0 | 1.000 | 1.000 | 1.0 | 1.0 | 1.000 | 0.0 | 1.0 | 1.0 | 1.0 | 1.0 | 0.0 | 0.0 | 0.0 | 5.0 |
| 5 | Grayson Allen | SG | 26 | MIL | 12 | 5 | 25.4 | 3.1 | 6.8 | 0.451 | 1.6 | 4.0 | 0.396 | 1.5 | 2.8 | 0.529 | 0.567 | 0.6 | 0.9 | 0.636 | 0.4 | 2.5 | 2.9 | 1.3 | 0.7 | 0.3 | 0.8 | 1.8 | 8.3 |
| 6 | Jose Alvarado | PG | 23 | NOP | 6 | 0 | 19.5 | 2.7 | 5.5 | 0.485 | 1.0 | 2.7 | 0.375 | 1.7 | 2.8 | 0.588 | 0.576 | 1.7 | 2.2 | 0.769 | 0.3 | 1.0 | 1.3 | 1.5 | 1.2 | 0.2 | 1.2 | 3.0 | 8.0 |
str(nba_playoffs)
## 'data.frame': 217 obs. of 30 variables:
## $ Rk : int 1 2 3 4 5 6 7 8 9 10 ...
## $ Player: chr "Precious Achiuwa" "Steven Adams" "Bam Adebayo" "Nickeil Alexander-Walker" ...
## $ Pos : chr "C" "C" "C" "SG" ...
## $ Age : int 22 28 24 23 26 23 28 27 29 24 ...
## $ Tm : chr "TOR" "MEM" "MIA" "UTA" ...
## $ G : int 6 7 18 1 12 6 12 12 8 6 ...
## $ GS : int 1 5 18 0 5 0 1 12 0 6 ...
## $ MP : num 27.8 16.3 34.1 5 25.4 19.5 18.4 37.3 2.5 36.2 ...
## $ FG : num 4.2 1.3 5.8 2 3.1 2.7 2.4 11.7 0.3 6.5 ...
## $ FGA : num 8.7 3 9.7 2 6.8 5.5 4.3 23.8 0.4 13.7 ...
## $ FG. : num 0.481 0.429 0.594 1 0.451 0.485 0.569 0.491 0.667 0.476 ...
## $ X3P : num 0.8 0 0 0 1.6 1 0.3 0.8 0 2.3 ...
## $ X3PA : num 2.7 0 0.1 0 4 2.7 1 3.4 0 6.8 ...
## $ X3P. : num 0.313 0 0 0 0.396 0.375 0.25 0.22 0 0.341 ...
## $ X2P : num 3.3 1.3 5.8 2 1.5 1.7 2.2 10.9 0.3 4.2 ...
## $ X2PA : num 6 3 9.7 2 2.8 2.8 3.3 20.3 0.4 6.8 ...
## $ X2P. : num 0.556 0.429 0.598 1 0.529 0.588 0.667 0.537 0.667 0.61 ...
## $ eFG. : num 0.529 0.429 0.594 1 0.567 0.576 0.598 0.507 0.667 0.561 ...
## $ FT : num 1 0.9 3.2 1 0.6 1.7 0.9 7.6 0.1 2 ...
## $ FTA : num 1.7 1.6 4.2 1 0.9 2.2 1.5 11.2 0.4 2.7 ...
## $ FT. : num 0.6 0.545 0.763 1 0.636 0.769 0.611 0.679 0.333 0.75 ...
## $ ORB : num 1.3 2.1 2.1 0 0.4 0.3 0.8 2.2 0.1 1.2 ...
## $ DRB : num 3.5 4.3 5.9 1 2.5 1 3.5 12 0.4 2.8 ...
## $ TRB : num 4.8 6.4 8 1 2.9 1.3 4.3 14.2 0.5 4 ...
## $ AST : num 1 2.1 2.7 1 1.3 1.5 1.8 6.8 0.1 2.5 ...
## $ STL : num 0.2 0.1 1 1 0.7 1.2 0.9 0.7 0.1 1 ...
## $ BLK : num 0.8 0.1 0.7 0 0.3 0.2 0.6 1.3 0 0.2 ...
## $ TOV : num 1.5 0.6 2.1 0 0.8 1.2 0.8 4.5 0 1.7 ...
## $ PF : num 2.3 1.7 3.1 0 1.8 3 1.8 3.6 0.5 3 ...
## $ PTS : num 10.2 3.4 14.8 5 8.3 8 6 31.7 0.6 17.3 ...
names(nba_playoffs)
## [1] "Rk" "Player" "Pos" "Age" "Tm" "G" "GS" "MP"
## [9] "FG" "FGA" "FG." "X3P" "X3PA" "X3P." "X2P" "X2PA"
## [17] "X2P." "eFG." "FT" "FTA" "FT." "ORB" "DRB" "TRB"
## [25] "AST" "STL" "BLK" "TOV" "PF" "PTS"
summary(nba_playoffs)
## Rk Player Pos Age
## Min. : 1 Length:217 Length:217 Min. :19.00
## 1st Qu.: 55 Class :character Class :character 1st Qu.:23.00
## Median :109 Mode :character Mode :character Median :26.00
## Mean :109 Mean :26.59
## 3rd Qu.:163 3rd Qu.:29.00
## Max. :217 Max. :38.00
## Tm G GS MP
## Length:217 Min. : 1.000 Min. : 0.000 Min. : 0.00
## Class :character 1st Qu.: 5.000 1st Qu.: 0.000 1st Qu.: 7.60
## Mode :character Median : 6.000 Median : 0.000 Median :18.40
## Mean : 8.714 Mean : 4.009 Mean :19.43
## 3rd Qu.:12.000 3rd Qu.: 6.000 3rd Qu.:31.20
## Max. :24.000 Max. :24.000 Max. :44.00
## FG FGA FG. X3P
## Min. : 0.000 Min. : 0.000 Min. :0.0000 Min. :0.0000
## 1st Qu.: 1.000 1st Qu.: 2.000 1st Qu.:0.3700 1st Qu.:0.0000
## Median : 2.300 Median : 4.700 Median :0.4390 Median :0.7000
## Mean : 3.045 Mean : 6.738 Mean :0.4375 Mean :0.9346
## 3rd Qu.: 4.500 3rd Qu.:10.000 3rd Qu.:0.5000 3rd Qu.:1.5000
## Max. :12.200 Max. :23.800 Max. :1.0000 Max. :4.1000
## X3PA X3P. X2P X2PA
## Min. : 0.0 Min. :0.0000 Min. : 0.000 Min. : 0.000
## 1st Qu.: 0.5 1st Qu.:0.0000 1st Qu.: 0.500 1st Qu.: 1.100
## Median : 2.2 Median :0.3310 Median : 1.300 Median : 2.600
## Mean : 2.7 Mean :0.2733 Mean : 2.112 Mean : 4.038
## 3rd Qu.: 4.4 3rd Qu.:0.3930 3rd Qu.: 3.000 3rd Qu.: 5.800
## Max. :10.4 Max. :1.0000 Max. :11.200 Max. :20.300
## X2P. eFG. FT FTA
## Min. :0.000 Min. :0.0000 Min. :0.000 Min. : 0.000
## 1st Qu.:0.410 1st Qu.:0.4320 1st Qu.:0.200 1st Qu.: 0.300
## Median :0.500 Median :0.5210 Median :0.800 Median : 1.000
## Mean :0.495 Mean :0.5044 Mean :1.442 Mean : 1.833
## 3rd Qu.:0.622 3rd Qu.:0.5960 3rd Qu.:2.000 3rd Qu.: 2.500
## Max. :1.000 Max. :1.5000 Max. :8.500 Max. :11.200
## FT. ORB DRB TRB
## Min. :0.0000 Min. :0.0000 Min. : 0.000 Min. : 0.000
## 1st Qu.:0.5000 1st Qu.:0.2000 1st Qu.: 1.000 1st Qu.: 1.200
## Median :0.7500 Median :0.5000 Median : 2.000 Median : 2.800
## Mean :0.6232 Mean :0.7737 Mean : 2.627 Mean : 3.404
## 3rd Qu.:0.8570 3rd Qu.:1.0000 3rd Qu.: 3.700 3rd Qu.: 4.800
## Max. :1.0000 Max. :5.5000 Max. :12.000 Max. :14.300
## AST STL BLK TOV
## Min. :0.000 Min. :0.0000 Min. :0.0000 Min. :0.000
## 1st Qu.:0.400 1st Qu.:0.2000 1st Qu.:0.0000 1st Qu.:0.300
## Median :1.000 Median :0.5000 Median :0.2000 Median :0.800
## Mean :1.829 Mean :0.5848 Mean :0.3613 Mean :1.086
## 3rd Qu.:2.700 3rd Qu.:0.9000 3rd Qu.:0.5000 3rd Qu.:1.500
## Max. :9.800 Max. :2.1000 Max. :2.5000 Max. :6.200
## PF PTS
## Min. :0.000 Min. : 0.000
## 1st Qu.:0.800 1st Qu.: 2.100
## Median :1.800 Median : 6.000
## Mean :1.784 Mean : 8.457
## 3rd Qu.:2.800 3rd Qu.:12.600
## Max. :4.700 Max. :31.700
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.1 ✔ stringr 1.5.2
## ✔ ggplot2 4.0.0 ✔ tibble 3.3.0
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.1
## ✔ purrr 1.1.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
# See your exact NBA column names
names(nba_playoffs)
## [1] "Rk" "Player" "Pos" "Age" "Tm" "G" "GS" "MP"
## [9] "FG" "FGA" "FG." "X3P" "X3PA" "X3P." "X2P" "X2PA"
## [17] "X2P." "eFG." "FT" "FTA" "FT." "ORB" "DRB" "TRB"
## [25] "AST" "STL" "BLK" "TOV" "PF" "PTS"
# Summary stats for every numeric column in your NBA dataset
nba_playoffs %>%
select(where(is.numeric)) %>%
pivot_longer(everything(), names_to = "Variable", values_to = "Value") %>%
group_by(Variable) %>%
summarise(
N = sum(!is.na(Value)),
Mean = mean(Value, na.rm = TRUE),
Median = median(Value, na.rm = TRUE),
SD = sd(Value, na.rm = TRUE),
Min = min(Value, na.rm = TRUE),
Max = max(Value, na.rm = TRUE),
.groups = "drop"
) %>%
arrange(desc(Mean))
## # A tibble: 27 × 7
## Variable N Mean Median SD Min Max
## <chr> <int> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 Rk 217 109 109 62.8 1 217
## 2 Age 217 26.6 26 4.24 19 38
## 3 MP 217 19.4 18.4 12.9 0 44
## 4 G 217 8.71 6 5.80 1 24
## 5 PTS 217 8.46 6 7.63 0 31.7
## 6 FGA 217 6.74 4.7 5.87 0 23.8
## 7 X2PA 217 4.04 2.6 4.09 0 20.3
## 8 GS 217 4.01 0 5.94 0 24
## 9 TRB 217 3.40 2.8 2.84 0 14.3
## 10 FG 217 3.05 2.3 2.70 0 12.2
## # ℹ 17 more rows
library(tidyverse)
ggplot(nba_playoffs, aes(x = PTS)) +
geom_histogram(bins = 30, fill = "steelblue", color = "white") +
labs(
title = "Distribution of Points (Playoffs)",
x = "Points",
y = "Frequency"
) +
theme_minimal()
library(tidyverse)
ggplot(nba_playoffs, aes(x = AST)) +
geom_histogram(bins = 30, fill = "steelblue", color = "white") +
labs(
title = "Distribution of Points (Playoffs)",
x = "Points",
y = "Frequency"
) +
theme_minimal()
library(tidyverse)
ggplot(nba_playoffs, aes(x = FG)) +
geom_histogram(bins = 30, fill = "steelblue", color = "white") +
labs(
title = "Distribution of Field Goals (Playoffs)",
x = "Field Goals",
y = "Frequency"
) +
theme_minimal()
library(tidyverse)
ggplot(nba_playoffs, aes(x = ORB)) +
geom_histogram(bins = 30, fill = "steelblue", color = "white") +
labs(
title = "Distribution of Offensive Rebounds (Playoffs)",
x = "Offensive Rebounds",
y = "Frequency"
) +
theme_minimal()
library(tidyverse)
ggplot(nba_playoffs, aes(x = DRB)) +
geom_histogram(bins = 30, fill = "steelblue", color = "white") +
labs(
title = "Distribution of Defensive Rebounds (Playoffs)",
x = "Defensive Rebounds",
y = "Frequency"
) +
theme_minimal()
``` r
library(tidyverse)
nba_playoffs %>%
select(where(is.numeric)) %>% # only numeric NBA stats
pivot_longer(
cols = everything(),
names_to = "Stat",
values_to = "Value"
) %>%
ggplot(aes(x = Value)) +
geom_histogram(bins = 30, fill = "darkorange", color = "white") +
facet_wrap(~ Stat, scales = "free") +
labs(
title = "Distributions of NBA Playoff Statistics",
x = "Value",
y = "Frequency"
) +
theme_minimal()
``` r
library(tidyverse)
nba_playoffs %>%
select(PTS, AST, TRB, MP) %>% # edit to match your dataset names
pivot_longer(everything(), names_to = "Stat", values_to = "Value") %>%
ggplot(aes(Value)) +
geom_histogram(bins = 25, fill = "forestgreen", color = "white") +
facet_wrap(~ Stat, scales = "free") +
theme_minimal() +
labs(
title = "Key Playoff Performance Distributions",
x = "Value",
y = "Count"
)
library(tidyverse)
names(nba_playoffs)
## [1] "Rk" "Player" "Pos" "Age" "Tm" "G" "GS" "MP"
## [9] "FG" "FGA" "FG." "X3P" "X3PA" "X3P." "X2P" "X2PA"
## [17] "X2P." "eFG." "FT" "FTA" "FT." "ORB" "DRB" "TRB"
## [25] "AST" "STL" "BLK" "TOV" "PF" "PTS"
# Look for common "position" column names
possible_pos <- c("Pos","POS","Position","position","pos")
intersect(possible_pos, names(nba_playoffs))
## [1] "Pos"
Pos (most common)
``` r
library(tidyverse)
nba_playoffs %>%
group_by(Pos) %>%
summarise(
Players = n_distinct(Player),
Avg_PTS = mean(PTS, na.rm = TRUE),
Avg_AST = mean(AST, na.rm = TRUE),
Avg_TRB = mean(TRB, na.rm = TRUE),
Avg_MP = mean(MP, na.rm = TRUE),
.groups = "drop"
) %>%
arrange(desc(Avg_PTS))
## # A tibble: 5 × 6
## Pos Players Avg_PTS Avg_AST Avg_TRB Avg_MP
## <chr> <int> <dbl> <dbl> <dbl> <dbl>
## 1 PG 39 10.3 3.31 2.8 21.8
## 2 SF 39 8.91 1.66 2.89 19.7
## 3 PF 50 7.9 1.52 3.74 19.5
## 4 C 40 7.82 1.08 5.38 18.0
## 5 SG 49 7.76 1.70 2.33 18.4
library(tidyverse)
# Pick the first matching column that exists in your dataset
pos_col <- intersect(names(nba_playoffs), c("Pos", "Position", "pos", "position"))[1]
pts_col <- intersect(names(nba_playoffs), c("PTS", "Pts", "points"))[1]
player_col<- intersect(names(nba_playoffs), c("Player", "player", "PlayerName", "Name"))[1]
team_col <- intersect(names(nba_playoffs), c("Team", "Tm", "team", "tm"))[1]
# Stop with a clear message if something critical is missing
if (is.na(pos_col)) stop("No position column found. Check names(nba_playoffs).")
if (is.na(pts_col)) stop("No points column found (PTS/Pts/points). Check names(nba_playoffs).")
nba_playoffs %>%
group_by(.data[[pos_col]]) %>%
arrange(desc(.data[[pts_col]])) %>%
slice_head(n = 10) %>%
select(any_of(c(player_col, team_col, pos_col, pts_col, "AST", "TRB", "REB", "MP", "MPG"))) %>%
ungroup()
## # A tibble: 50 × 7
## Player Tm Pos PTS AST TRB MP
## <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
## 1 Nikola Joki? DEN C 31 5.8 13.2 34.2
## 2 Joel Embiid PHI C 23.6 2.1 10.7 38.5
## 3 Karl-Anthony Towns MIN C 21.8 2.2 10.8 37
## 4 Nikola Vu?evi? CHI C 19.4 3.2 12.4 36.2
## 5 Deandre Ayton PHO C 17.9 1.7 8.9 30.5
## 6 Bam Adebayo MIA C 14.8 2.7 8 34.1
## 7 Jonas Valan?i?nas NOP C 14.5 3 14.3 29.2
## 8 Rudy Gobert UTA C 12 0.5 13.2 32.8
## 9 Al Horford BOS C 12 3.3 9.3 35.4
## 10 DeMarcus Cousins DEN C 10.6 1.2 3.4 11.4
## # ℹ 40 more rows
``` r
library(tidyverse)
nba_pg <- nba_playoffs %>%
mutate(
PTS_PG = if("G" %in% names(nba_playoffs)) PTS / G else PTS,
AST_PG = if("G" %in% names(nba_playoffs)) AST / G else AST,
TRB_PG = if("G" %in% names(nba_playoffs)) TRB / G else TRB,
MP_PG = if("G" %in% names(nba_playoffs)) MP / G else MP
)
nba_pg %>%
group_by(Pos) %>%
summarise(
Players = n_distinct(Player),
Avg_PTS_PG = mean(PTS_PG, na.rm = TRUE),
Avg_AST_PG = mean(AST_PG, na.rm = TRUE),
Avg_TRB_PG = mean(TRB_PG, na.rm = TRUE),
Avg_MP_PG = mean(MP_PG, na.rm = TRUE),
.groups = "drop"
) %>%
arrange(desc(Avg_PTS_PG))
## # A tibble: 5 × 6
## Pos Players Avg_PTS_PG Avg_AST_PG Avg_TRB_PG Avg_MP_PG
## <chr> <int> <dbl> <dbl> <dbl> <dbl>
## 1 SF 39 1.35 0.280 0.432 2.93
## 2 PG 39 1.30 0.442 0.359 2.92
## 3 SG 49 1.26 0.277 0.381 3.06
## 4 C 40 1.23 0.155 0.890 2.79
## 5 PF 50 1.18 0.248 0.570 3.04
``` r
library(tidyverse)
nba_pg %>%
ggplot(aes(x = Pos, y = PTS_PG)) +
geom_boxplot() +
labs(
title = "Points per Game by Position (Playoffs)",
x = "Position",
y = "PTS per Game"
) +
theme_minimal()
Paste the output of:
names(nba_playoffs)
```r
``` r
# Helper function to find first matching column
find_col <- function(options) intersect(names(nba_playoffs), options)[1]
player_col <- find_col(c("Player", "PlayerName", "Name"))
team_col <- find_col(c("Tm", "Team"))
pos_col <- find_col(c("Pos", "Position"))
pts_col <- find_col(c("PTS", "Pts", "points"))
ast_col <- find_col(c("AST", "Ast", "assists"))
trb_col <- find_col(c("TRB", "REB", "rebounds"))
stl_col <- find_col(c("STL", "Stl", "steals"))
blk_col <- find_col(c("BLK", "Blk", "blocks"))
pf_col <- find_col(c("PF", "Fouls"))
fg_col <- find_col(c("FG", "FGM", "FG."))
High points + efficiency proxy (FG) + usage proxy (PTS share)
``` r
nba_playoffs %>%
mutate(
FG_eff = if (!is.na(fg_col)) .data[[fg_col]] else NA,
Scoring_Index = scale(.data[[pts_col]]) +
0.5 * scale(FG_eff)
) %>%
arrange(desc(Scoring_Index)) %>%
select(any_of(c(player_col, team_col, pos_col, pts_col, fg_col))) %>%
head(15)
## Player Tm Pos PTS FG
## 1 Nikola Joki? DEN C 31.0 12.2
## 2 Giannis Antetokounmpo MIL PF 31.7 11.7
## 3 Luka Don?i? DAL PG 31.7 10.7
## 4 Jimmy Butler MIA SF 27.4 9.8
## 5 Stephen Curry GSW PG 27.4 9.2
## 6 Brandon Ingram NOP SF 27.0 9.3
## 7 Ja Morant MEM PG 27.1 9.0
## 8 Donovan Mitchell UTA SG 25.5 8.8
## 9 Kevin Durant BRK PF 26.3 8.0
## 10 Jayson Tatum BOS SF 25.6 8.4
## 11 Anthony Edwards MIN SG 25.2 8.3
## 12 Pascal Siakam TOR PF 22.8 8.5
## 13 Jaylen Brown BOS SF 23.1 8.2
## 14 Joel Embiid PHI C 23.6 7.8
## 15 Devin Booker PHO SG 23.3 7.9
GM use: identifies offensive engines vs volume-only scorers.
High assists + high AST-to-PTS ratio (who creates vs who finishes)
``` r
nba_playoffs %>%
mutate(
AST_to_PTS = .data[[ast_col]] / pmax(.data[[pts_col]], 1)
) %>%
arrange(desc(AST_to_PTS), desc(.data[[ast_col]])) %>%
select(any_of(c(player_col, team_col, pos_col, ast_col, pts_col))) %>%
head(15)
## Player Tm Pos AST PTS
## 1 Andre Iguodala GSW SF 1.7 1.6
## 2 Paul Millsap PHI PF 1.0 0.0
## 3 Draymond Green GSW PF 6.3 8.0
## 4 Elfrid Payton PHO PG 1.5 2.0
## 5 Alex Caruso CHI SG 4.3 6.3
## 6 Steven Adams MEM C 2.1 3.4
## 7 Kyle Lowry MIA PG 4.7 7.8
## 8 George Hill MIL SG 0.6 1.0
## 9 Juan Toscano-Anderson GSW SF 0.6 0.8
## 10 Luca Vildoza MIL PG 0.6 0.7
## 11 D'Angelo Russell MIN PG 6.7 12.0
## 12 Ayo Dosunmu CHI SG 2.2 4.0
## 13 Jordan McLaughlin MIN PG 3.4 6.2
## 14 John Konchar MEM SG 0.6 1.1
## 15 Mike Conley UTA PG 4.8 9.2
📌 GM use: finds initiators, secondary creators, system facilitators.
Stocks + rebounds – foul penalty
``` r
nba_playoffs %>%
mutate(
Defensive_Impact =
scale(.data[[stl_col]]) +
scale(.data[[blk_col]]) +
scale(.data[[trb_col]]) -
0.5 * scale(.data[[pf_col]])
) %>%
arrange(desc(Defensive_Impact)) %>%
select(any_of(c(player_col, team_col, pos_col,
stl_col, blk_col, trb_col, pf_col))) %>%
head(15)
## Player Tm Pos STL BLK TRB PF
## 1 Nic Claxton BRK C 1.3 2.3 6.3 1.5
## 2 Nikola Joki? DEN C 1.6 1.0 13.2 4.0
## 3 Karl-Anthony Towns MIN C 0.7 2.0 10.8 4.2
## 4 Giannis Antetokounmpo MIL PF 0.7 1.3 14.2 3.6
## 5 Jaren Jackson Jr. MEM PF 0.8 2.5 6.8 4.4
## 6 Robert Williams BOS C 0.7 2.2 6.2 2.0
## 7 Jimmy Butler MIA SF 2.1 0.6 7.4 1.5
## 8 Luka Don?i? DAL PG 1.8 0.6 9.8 2.9
## 9 Kyrie Irving BRK PG 1.8 1.3 5.3 3.3
## 10 Ja Morant MEM PG 2.0 0.4 8.0 2.4
## 11 Nikola Vu?evi? CHI C 0.4 1.2 12.4 2.8
## 12 Al Horford BOS C 0.8 1.3 9.3 2.8
## 13 Jrue Holiday MIL PG 1.8 0.6 5.6 2.3
## 14 Rudy Gobert UTA C 0.2 1.0 13.2 3.2
## 15 Andrew Wiggins GSW SF 1.0 1.0 7.5 2.5
📌 GM use: playoff defenders, switchable wings, rim protectors.
(Players who don’t score much but HELP WIN)
``` r
nba_playoffs %>%
mutate(
NonScoring_Impact =
scale(.data[[ast_col]]) +
scale(.data[[stl_col]]) +
scale(.data[[blk_col]]) +
scale(.data[[trb_col]])
) %>%
filter(.data[[pts_col]] < median(.data[[pts_col]], na.rm = TRUE)) %>%
arrange(desc(NonScoring_Impact)) %>%
select(any_of(c(player_col, team_col, pos_col,
pts_col, ast_col, stl_col, blk_col, trb_col))) %>%
head(15)
## Player Tm Pos PTS AST STL BLK TRB
## 1 Jarred Vanderbilt MIN PF 5.5 0.7 1.2 0.3 7.2
## 2 Andre Drummond BRK C 3.8 0.8 1.3 0.8 3.0
## 3 Kevon Looney GSW C 5.8 2.2 0.4 0.5 7.6
## 4 Onyeka Okongwu ATL C 5.2 0.4 0.8 0.8 5.4
## 5 Hassan Whiteside UTA C 1.8 0.0 0.3 1.3 5.2
## 6 De'Anthony Melton MEM SG 5.6 1.6 1.0 0.5 3.1
## 7 Javonte Green CHI SF 1.4 0.4 1.8 0.0 3.0
## 8 Clint Capela ATL C 2.0 0.0 0.5 0.5 7.5
## 9 Otto Porter Jr. GSW PF 5.4 1.8 0.9 0.3 3.4
## 10 Paul Reed PHI C 3.7 0.8 0.8 0.5 3.8
## 11 Austin Rivers DEN SG 4.2 1.2 1.4 0.2 0.6
## 12 Naz Reid MIN C 4.8 0.0 0.2 1.2 2.8
## 13 Thaddeus Young TOR PF 3.3 1.7 0.8 0.2 3.0
## 14 Matisse Thybulle PHI SG 3.0 0.4 0.8 0.8 1.0
## 15 Blake Griffin BRK PF 4.0 2.0 0.5 0.5 2.0
``` r
find_col <- function(options) intersect(names(nba_playoffs), options)[1]
player_col <- find_col(c("Player", "PlayerName", "Name"))
team_col <- find_col(c("Tm", "Team"))
pos_col <- find_col(c("Pos", "Position"))
pts_col <- find_col(c("PTS", "Pts", "points"))
fg_col <- find_col(c("FG", "FGM", "FG."))
fga_col <- find_col(c("FGA", "FieldGoalsAttempted"))
mp_col <- find_col(c("MP", "Minutes", "MIN"))
ast_col <- find_col(c("AST", "Ast"))
tov_col <- find_col(c("TOV", "TO", "Turnovers"))
trb_col <- find_col(c("TRB", "REB", "Rebounds"))
Who converts attempts into makes
``` r
nba_playoffs %>%
mutate(
FG_Efficiency = .data[[fg_col]] / pmax(.data[[fga_col]], 1)
) %>%
arrange(desc(FG_Efficiency)) %>%
select(any_of(c(player_col, team_col, pos_col,
fg_col, fga_col))) %>%
head(15)
## Player Tm Pos FG FGA
## 1 Nickeil Alexander-Walker UTA SG 2.0 2.0
## 2 Tony Bradley CHI C 2.5 2.5
## 3 DeAndre Jordan PHI C 1.7 1.7
## 4 Nic Claxton BRK C 4.8 6.0
## 5 Xavier Tillman Sr. MEM PF 2.0 2.8
## 6 Naji Marshall NOP SF 1.2 1.7
## 7 Jordan McLaughlin MIN PG 2.4 3.4
## 8 JaVale McGee PHO C 2.9 4.2
## 9 Robert Williams BOS C 3.1 4.6
## 10 Vlatko ?an?ar DEN PF 1.0 1.5
## 11 Elfrid Payton PHO PG 1.0 1.5
## 12 DeMarcus Cousins DEN C 3.8 5.8
## 13 Kevon Looney GSW C 2.6 4.0
## 14 Omer Yurtseven MIA C 1.3 2.0
## 15 Gary Payton II GSW PG 2.4 3.7
GM insight: filters out high-volume, low-efficiency shooters.
True scoring impact independent of minutes
``` r
nba_playoffs %>%
mutate(
PTS_per_Min = .data[[pts_col]] / pmax(.data[[mp_col]], 1)
) %>%
arrange(desc(PTS_per_Min)) %>%
select(any_of(c(player_col, team_col, pos_col,
pts_col, mp_col))) %>%
head(15)
## Player Tm Pos PTS MP
## 1 Kevin Knox ATL SF 11.0 4.5
## 2 Tony Bradley CHI C 5.0 4.0
## 3 Aaron Holiday PHO PG 3.5 3.3
## 4 Nickeil Alexander-Walker UTA SG 5.0 5.0
## 5 Willy Hernangómez NOP C 2.0 2.0
## 6 DeMarcus Cousins DEN C 10.6 11.4
## 7 Nikola Joki? DEN C 31.0 34.2
## 8 Dalano Banton TOR PG 1.8 2.0
## 9 Trey Burke DAL PG 3.2 3.7
## 10 Luka Don?i? DAL PG 31.7 36.8
## 11 Giannis Antetokounmpo MIL PF 31.7 37.3
## 12 Stephen Curry GSW PG 27.4 34.7
## 13 Jimmy Butler MIA SF 27.4 37.0
## 14 Ja Morant MEM PG 27.1 37.6
## 15 Brandon Ingram NOP SF 27.0 39.3
GM insight: identifies bench scorers who scale up.
Decision-making efficiency
``` r
nba_playoffs %>%
mutate(
AST_to_TOV = .data[[ast_col]] / pmax(.data[[tov_col]], 1)
) %>%
arrange(desc(AST_to_TOV)) %>%
select(any_of(c(player_col, team_col, pos_col,
ast_col, tov_col))) %>%
head(15)
## Player Tm Pos AST TOV
## 1 Fred VanVleet TOR PG 6.3 1.0
## 2 Alex Caruso CHI SG 4.3 0.3
## 3 Tyus Jones MEM PG 4.5 1.1
## 4 Chris Paul PHO PG 8.3 2.4
## 5 Jordan McLaughlin MIN PG 3.4 0.4
## 6 Monte Morris DEN PG 5.4 1.6
## 7 Jalen Brunson DAL PG 3.7 1.1
## 8 Scottie Barnes TOR PF 4.3 1.3
## 9 Jimmy Butler MIA SF 4.6 1.5
## 10 Bogdan Bogdanovi? ATL SG 3.0 1.0
## 11 Seth Curry BRK SG 3.0 0.8
## 12 Mikal Bridges PHO SF 2.8 0.8
## 13 Delon Wright ATL SG 2.8 1.0
## 14 Ja Morant MEM PG 9.8 3.6
## 15 Bones Hyland DEN PG 3.2 1.2
GM insight: playoff-safe ball handlers.
Hustle + positioning independent of minutes
``` r
nba_playoffs %>%
mutate(
TRB_per_Min = .data[[trb_col]] / pmax(.data[[mp_col]], 1)
) %>%
arrange(desc(TRB_per_Min)) %>%
select(any_of(c(player_col, team_col, pos_col,
trb_col, mp_col))) %>%
head(15)
## Player Tm Pos TRB MP
## 1 Willy Hernangómez NOP C 2.0 2.0
## 2 Tony Bradley CHI C 2.0 4.0
## 3 Boban Marjanovi? DAL C 1.0 2.0
## 4 Garrett Temple NOP SG 1.0 2.0
## 5 Jonas Valan?i?nas NOP C 14.3 29.2
## 6 Hassan Whiteside UTA C 5.2 10.8
## 7 Serge Ibaka MIL PF 1.7 3.7
## 8 Ish Wainright PHO PF 1.6 3.7
## 9 Charles Bassey PHI PF 1.7 4.0
## 10 Bobby Portis MIL C 10.0 24.8
## 11 Rudy Gobert UTA C 13.2 32.8
## 12 Steven Adams MEM C 6.4 16.3
## 13 Nikola Joki? DEN C 13.2 34.2
## 14 Giannis Antetokounmpo MIL PF 14.2 37.3
## 15 Clint Capela ATL C 7.5 20.0
Efficiency > Volume Composite Score** This directly answers your last point.
``` r
nba_playoffs %>%
mutate(
Efficiency_Score =
scale(.data[[pts_col]] / pmax(.data[[mp_col]], 1)) + # scoring efficiency
scale(.data[[ast_col]] / pmax(.data[[tov_col]], 1)) + # decision-making
scale(.data[[trb_col]] / pmax(.data[[mp_col]], 1)) # hustle
) %>%
arrange(desc(Efficiency_Score)) %>%
select(any_of(c(player_col, team_col, pos_col,
pts_col, ast_col, trb_col, mp_col))) %>%
head(20)
## Player Tm Pos PTS AST TRB MP
## 1 Willy Hernangómez NOP C 2.0 0.0 2.0 2.0
## 2 Kevin Knox ATL SF 11.0 0.0 1.0 4.5
## 3 Tony Bradley CHI C 5.0 0.5 2.0 4.0
## 4 Fred VanVleet TOR PG 13.8 6.3 3.0 35.0
## 5 Giannis Antetokounmpo MIL PF 31.7 6.8 14.2 37.3
## 6 Nikola Joki? DEN C 31.0 5.8 13.2 34.2
## 7 Jimmy Butler MIA SF 27.4 4.6 7.4 37.0
## 8 Jonas Valan?i?nas NOP C 14.5 3.0 14.3 29.2
## 9 Ja Morant MEM PG 27.1 9.8 8.0 37.6
## 10 Luka Don?i? DAL PG 31.7 6.4 9.8 36.8
## 11 Scottie Barnes TOR PF 12.8 4.3 9.0 33.3
## 12 Tyus Jones MEM PG 9.2 4.5 3.3 21.8
## 13 DeMarcus Cousins DEN C 10.6 1.2 3.4 11.4
## 14 Nikola Vu?evi? CHI C 19.4 3.2 12.4 36.2
## 15 Aaron Holiday PHO PG 3.5 1.5 0.5 3.3
## 16 Jalen Brunson DAL PG 21.6 3.7 4.6 34.9
## 17 Boban Marjanovi? DAL C 1.3 0.0 1.0 2.0
## 18 Stephen Curry GSW PG 27.4 5.9 5.2 34.7
## 19 Bogdan Bogdanovi? ATL SG 14.3 3.0 4.8 26.8
## 20 Nickeil Alexander-Walker UTA SG 5.0 1.0 1.0 5.0
find_col <- function(options) intersect(names(nba_playoffs), options)[1]
player_col <- find_col(c("Player", "PlayerName", "Name"))
team_col <- find_col(c("Tm", "Team"))
pos_col <- find_col(c("Pos", "Position"))
pts_col <- find_col(c("PTS", "Pts", "points"))
ast_col <- find_col(c("AST", "Ast", "assists"))
trb_col <- find_col(c("TRB", "REB", "Rebounds"))
mp_col <- find_col(c("MP", "Minutes", "MIN"))
This shows usage vs scoring return.
``` r
nba_playoffs %>%
ggplot(aes(
x = .data[[mp_col]],
y = .data[[pts_col]]
)) +
geom_point(alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
labs(
title = "Minutes Played vs Points (Playoffs)",
x = "Minutes Played",
y = "Total Points"
) +
theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'
GM read - Above trend line → efficient scorers - Below trend line → empty minutes
Total Contributions = PTS + AST + TRB
``` r
nba_playoffs %>%
mutate(
Total_Contrib =
.data[[pts_col]] +
.data[[ast_col]] +
.data[[trb_col]]
) %>%
ggplot(aes(
x = .data[[mp_col]],
y = Total_Contrib
)) +
geom_point(alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
labs(
title = "Minutes Played vs Total Contributions",
x = "Minutes Played",
y = "PTS + AST + TRB"
) +
theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'
``` r
nba_playoffs %>%
mutate(
Total_Contrib =
.data[[pts_col]] +
.data[[ast_col]] +
.data[[trb_col]]
) %>%
filter(
.data[[mp_col]] > median(.data[[mp_col]], na.rm = TRUE),
Total_Contrib < median(Total_Contrib, na.rm = TRUE)
) %>%
arrange(desc(.data[[mp_col]])) %>%
select(any_of(c(player_col, team_col, pos_col,
mp_col, pts_col, ast_col, trb_col)))
## Player Tm Pos MP PTS AST TRB
## 1 Wesley Matthews MIL SG 28.8 6.2 1.2 3.1
## 2 Jeff Green DEN C 22.6 3.8 0.4 3.6
## 3 Austin Rivers DEN SG 21.6 4.2 1.2 0.6
## 4 Clint Capela ATL C 20.0 2.0 0.0 7.5
## 5 Trey Murphy III NOP SF 20.0 5.2 0.5 2.5
## 6 Otto Porter Jr. GSW PF 19.5 5.4 1.8 3.4
## 7 Danuel House Jr. UTA SF 18.7 4.3 0.7 2.8
Low Minutes, High Output → Rotation Upside
``` r
nba_playoffs %>%
mutate(
Total_Contrib =
.data[[pts_col]] +
.data[[ast_col]] +
.data[[trb_col]]
) %>%
filter(
.data[[mp_col]] < median(.data[[mp_col]], na.rm = TRUE),
Total_Contrib > median(Total_Contrib, na.rm = TRUE)
) %>%
arrange(desc(Total_Contrib)) %>%
select(any_of(c(player_col, team_col, pos_col,
mp_col, pts_col, ast_col, trb_col)))
## Player Tm Pos MP PTS AST TRB
## 1 DeMarcus Cousins DEN C 11.4 10.6 1.2 3.4
## 2 Bones Hyland DEN PG 17.4 9.2 3.2 2.0
## 3 Kevin Knox ATL SF 4.5 11.0 0.0 1.0
## 4 Jordan McLaughlin MIN PG 16.6 6.2 3.4 2.4
## 5 Steven Adams MEM C 16.3 3.4 2.1 6.4
## 6 JaVale McGee PHO C 11.1 6.8 0.6 4.0
## 7 Gary Payton II GSW PG 16.9 6.5 1.3 3.1
nba_pm <- nba_playoffs %>%
mutate(
PTS_PM = .data[[pts_col]] / pmax(.data[[mp_col]], 1),
AST_PM = .data[[ast_col]] / pmax(.data[[mp_col]], 1),
TRB_PM = .data[[trb_col]] / pmax(.data[[mp_col]], 1)
)
``` r
nba_pm %>%
group_by(.data[[pos_col]]) %>%
summarise(
Players = n(),
Avg_PTS_PM = mean(PTS_PM, na.rm = TRUE),
Avg_AST_PM = mean(AST_PM, na.rm = TRUE),
Avg_TRB_PM = mean(TRB_PM, na.rm = TRUE),
.groups = "drop"
) %>%
arrange(desc(Avg_PTS_PM))
## # A tibble: 5 × 5
## Pos Players Avg_PTS_PM Avg_AST_PM Avg_TRB_PM
## <chr> <int> <dbl> <dbl> <dbl>
## 1 C 40 0.448 0.0558 0.314
## 2 PG 39 0.445 0.151 0.132
## 3 SF 39 0.425 0.0775 0.153
## 4 SG 49 0.363 0.0800 0.144
## 5 PF 50 0.351 0.0627 0.197
``` r
nba_pm %>%
ggplot(aes(x = .data[[pos_col]], y = PTS_PM)) +
geom_boxplot() +
labs(
title = "Points per Minute by Position (Playoffs)",
x = "Position",
y = "PTS per Minute"
) +
theme_minimal()
``` r
nba_pm %>%
ggplot(aes(x = .data[[pos_col]], y = AST_PM)) +
geom_boxplot() +
labs(
title = "Assists per Minute by Position (Playoffs)",
x = "Position",
y = "AST per Minute"
) +
theme_minimal()
``` r
nba_pm %>%
ggplot(aes(x = .data[[pos_col]], y = TRB_PM)) +
geom_boxplot() +
labs(
title = "Rebounds per Minute by Position (Playoffs)",
x = "Position",
y = "TRB per Minute"
) +
theme_minimal()
``` r
nba_pm %>%
group_by(.data[[pos_col]]) %>%
summarise(
Efficiency_Score =
mean(scale(PTS_PM), na.rm = TRUE) +
mean(scale(AST_PM), na.rm = TRUE) +
mean(scale(TRB_PM), na.rm = TRUE),
.groups = "drop"
) %>%
arrange(desc(Efficiency_Score))
## # A tibble: 5 × 2
## Pos Efficiency_Score
## <chr> <dbl>
## 1 PG 1.30e-15
## 2 PF 6.82e-16
## 3 SG 1.22e-16
## 4 SF -1.30e-16
## 5 C -1.14e-15
library(tidyverse)
# Build efficiency table (must exist before outlier chunks)
nba_eff <- nba_playoffs %>%
mutate(
Total_Contrib = .data[[pts_col]] + .data[[ast_col]] + .data[[trb_col]],
Contrib_per_Min = Total_Contrib / pmax(.data[[mp_col]], 1)
)
``` r
nba_eff %>%
filter(
.data[[mp_col]] < median(.data[[mp_col]], na.rm = TRUE),
Contrib_per_Min > median(Contrib_per_Min, na.rm = TRUE)
) %>%
arrange(desc(Contrib_per_Min)) %>%
select(any_of(c(
player_col, team_col, pos_col,
mp_col, pts_col, ast_col, trb_col
)))
## Player Tm Pos MP PTS AST TRB
## 1 Kevin Knox ATL SF 4.5 11.0 0.0 1.0
## 2 Willy Hernangómez NOP C 2.0 2.0 0.0 2.0
## 3 Tony Bradley CHI C 4.0 5.0 0.5 2.0
## 4 Aaron Holiday PHO PG 3.3 3.5 1.5 0.5
## 5 Nickeil Alexander-Walker UTA SG 5.0 5.0 1.0 1.0
## 6 DeMarcus Cousins DEN C 11.4 10.6 1.2 3.4
## 7 Dalano Banton TOR PG 2.0 1.8 0.3 0.5
## 8 Boban Marjanovi? DAL C 2.0 1.3 0.0 1.0
## 9 Trey Burke DAL PG 3.7 3.2 0.4 0.3
## 10 JaVale McGee PHO C 11.1 6.8 0.6 4.0
## 11 Isaac Bonga TOR SF 3.0 2.0 0.0 1.0
## 12 Greg Monroe MIN C 3.5 2.0 0.5 1.0
## 13 Ish Wainright PHO PF 3.7 1.9 0.1 1.6
## 14 Omer Yurtseven MIA C 4.2 2.8 0.3 0.8
## 15 Jaden Springer PHI SG 2.6 1.2 0.4 0.8
## 16 Nik Stauskas BOS SG 1.8 1.0 0.3 0.3
## 17 Vlatko ?an?ar DEN PF 4.5 2.5 0.5 1.0
## 18 Elfrid Payton PHO PG 4.0 2.0 1.5 0.0
## 19 Serge Ibaka MIL PF 3.7 1.5 0.0 1.7
## 20 Jonathan Kuminga GSW SF 8.6 5.2 0.5 1.7
## 21 Sterling Brown DAL SG 2.9 1.2 0.3 0.9
## 22 Bones Hyland DEN PG 17.4 9.2 3.2 2.0
## 23 Marquese Chriss DAL PF 3.8 1.8 0.0 1.1
## 24 Steven Adams MEM C 16.3 3.4 2.1 6.4
## 25 Dewayne Dedmon MIA C 9.9 3.8 0.4 3.0
## 26 Jordan McLaughlin MIN PG 16.6 6.2 3.4 2.4
## 27 Luke Kornet BOS C 2.1 0.8 0.1 0.6
## 28 Paul Reed PHI C 11.7 3.7 0.8 3.8
## 29 Furkan Korkmaz PHI SG 6.8 3.1 0.4 1.3
## 30 Naz Reid MIN C 10.8 4.8 0.0 2.8
## 31 Taurean Prince MIN PF 13.0 6.0 1.2 1.6
## 32 Charles Bassey PHI PF 4.0 0.7 0.3 1.7
## 33 Jarrett Culver MEM SG 7.3 2.3 0.3 2.3
## 34 Luca Vildoza MIL PG 2.4 0.7 0.6 0.3
## 35 Daniel Theis BOS C 12.5 4.3 0.7 3.3
## 36 Hassan Whiteside UTA C 10.8 1.8 0.0 5.2
## 37 Gary Payton II GSW PG 16.9 6.5 1.3 3.1
## 38 Payton Pritchard BOS SG 12.9 4.8 1.6 1.9
## 39 Blake Griffin BRK PF 12.5 4.0 2.0 2.0
``` r
nba_eff %>%
filter(
.data[[mp_col]] > median(.data[[mp_col]], na.rm = TRUE),
Contrib_per_Min < median(Contrib_per_Min, na.rm = TRUE)
) %>%
arrange(.data[[mp_col]]) %>%
select(any_of(c(
player_col, team_col, pos_col,
mp_col, pts_col, ast_col, trb_col
)))
## Player Tm Pos MP PTS AST TRB
## 1 Danuel House Jr. UTA SF 18.7 4.3 0.7 2.8
## 2 Jose Alvarado NOP PG 19.5 8.0 1.5 1.3
## 3 Otto Porter Jr. GSW PF 19.5 5.4 1.8 3.4
## 4 Malik Beasley MIN SG 19.8 8.5 0.7 3.3
## 5 Clint Capela ATL C 20.0 2.0 0.0 7.5
## 6 Trey Murphy III NOP SF 20.0 5.2 0.5 2.5
## 7 Jarred Vanderbilt MIN PF 21.5 5.5 0.7 7.2
## 8 Onyeka Okongwu ATL C 21.6 5.2 0.4 5.4
## 9 Austin Rivers DEN SG 21.6 4.2 1.2 0.6
## 10 Jaden McDaniels MIN PF 21.7 9.3 0.7 2.8
## 11 Jeff Green DEN C 22.6 3.8 0.4 3.6
## 12 Gabe Vincent MIA PG 23.5 8.0 3.2 1.9
## 13 John Collins ATL PF 24.4 9.4 1.2 4.6
## 14 Grayson Allen MIL SG 25.4 8.3 1.3 2.9
## 15 Maxi Kleber DAL PF 25.4 8.7 1.1 4.6
## 16 Derrick White BOS SG 25.4 8.5 2.7 3.0
## 17 Pat Connaughton MIL SG 26.5 9.5 0.9 4.3
## 18 Danny Green PHI SF 26.6 8.6 0.8 3.1
## 19 Grant Williams BOS PF 27.3 8.6 0.8 3.8
## 20 Delon Wright ATL SG 27.4 8.2 2.8 4.8
## 21 Brook Lopez MIL C 27.7 10.6 0.7 5.9
## 22 Precious Achiuwa TOR C 27.8 10.2 1.0 4.8
## 23 Alex Caruso CHI SG 28.3 6.3 4.3 2.8
## 24 P.J. Tucker MIA PF 28.3 7.9 1.8 5.7
## 25 Wesley Matthews MIL SG 28.8 6.2 1.2 3.1
## 26 Mike Conley UTA PG 29.0 9.2 4.8 3.2
## 27 Max Strus MIA SF 29.1 10.9 2.1 4.1
## 28 Jae Crowder PHO PF 29.5 9.4 2.4 4.7
## 29 Kyle Lowry MIA PG 29.5 7.8 4.7 3.6
## 30 Patrick Williams CHI PF 30.6 11.8 0.8 5.4
## 31 Kevin Huerter ATL SG 30.8 9.2 3.8 3.0
## 32 Royce O'Neale UTA SF 31.3 6.2 1.5 5.7
## 33 Patrick Beverley MIN PG 32.3 11.0 4.8 3.2
## 34 Seth Curry BRK SG 33.0 14.5 3.0 2.5
## 35 Gary Trent Jr. TOR SG 33.2 15.3 1.3 1.7
## 36 Bruce Brown BRK SF 34.8 14.0 2.8 4.8
## 37 Herbert Jones NOP PF 37.7 10.7 1.8 3.3
## 38 Dorian Finney-Smith DAL PF 38.2 11.7 1.9 5.5
## 39 Mikal Bridges PHO SF 38.5 13.3 2.8 4.7
## 40 Reggie Bullock DAL SF 39.3 10.6 1.7 4.6
Good outliers represent players who deliver above-average impact despite limited minutes, suggesting strong efficiency and potential upside if their role expands.
Bad outliers are players receiving heavy minutes but producing below-average impact per minute, indicating possible inefficiency that becomes more exposed in playoff basketball where rotations tighten and possessions matter more.
Playoff basketball rewards efficiency over volume, elevating low-usage, high-impact contributors while exposing high-minute players who fail to convert usage into value.
If you want, I can: - Label these players directly on a scatterplot - Rank teams by rotation efficiency - Help you write a final conclusion section