Problem: Coach Malone wants a top 5 defense.
Recommendations: Force teams to increase their average two point distance. This can be done in two ways; increase ratio of mid range to paint field goal attempts and force each individual shot to be taken from further out.
Decreasing opponent Three Point Field Goal Percentage would be another route but that stat is largely luck influenced and Nuggets were top 10 last year already.
Important Notes: To jump from 10th to 5th in Defensive Rating (NBA.com) the Nuggets would have to decrease their rating by 3 points, a feat accomplished by about 5 teams.
Publicly available defensive stats and ratings leave a lot to be desired, so I believe that the development of versatility in coverages and schemes that Malone mentioned is a higher priority than the end of the year ranking in a flawed stat.
Method: Pulled and cleaned data from basketball-reference to measure how teams changed in terms of defensive ratings between the 21 and 22 seasons. Identified the teams that improved the most, and than categorized them in terms of how the organization changed (ie coaching change). Pulled and cleaned data from pbpstats.com to than analyze how those teams made their improvement.
library(tidyverse)
library(rvest)
library(dplyr)
library(reactable)
library(plotly)
library(ggplot2)
library(reactablefmtr)
library(jsonlite)
library(gt)
#'22 def rating table
url_bball <- "https://www.basketball-reference.com/leagues/NBA_2022_ratings.html"
def_rat_22 <- url_bball %>%
read_html() %>%
html_elements("table") %>%
html_table %>%
.[[1]]
names(def_rat_22) <- def_rat_22[1,]
def_rat_22 <- def_rat_22[-1,]
def_rat_22 <- def_rat_22 %>%
select(Team,
`DRtg/A`) %>%
rename(Adj_DRate_22 = 'DRtg/A')
#'21 def rating table
url_21 <- "https://www.basketball-reference.com/leagues/NBA_2021_ratings.html"
def_rat_21 <- url_21 %>%
read_html() %>%
html_elements("table") %>%
html_table %>%
.[[1]]
names(def_rat_21) <- def_rat_21[1,]
def_rat_21 <- def_rat_21[-1,]
def_rat_21 <- def_rat_21 %>%
select(Team,
`DRtg/A`) %>%
rename(Adj_DRate_21 = 'DRtg/A')
#combining tables
def_rating <- right_join(def_rat_21,def_rat_22)
def_rating$Adj_DRate_21 <- as.numeric(def_rating$Adj_DRate_21)
def_rating$Adj_DRate_22 <- as.numeric(def_rating$Adj_DRate_22)
def_rating <- def_rating %>%
mutate(difference = Adj_DRate_21 - Adj_DRate_22) %>%
arrange(-difference) %>%
mutate( change = case_when(
difference > 0 ~ 'positive',
TRUE ~ 'negative'
)) %>%
mutate_at(vars(difference), funs(round(.,4)))
def_rating %>% reactable(filterable = TRUE,
pagination = FALSE,
sortable = TRUE,
searchable = TRUE) %>%
add_title("Defeneive Rating Improvements from '21 to '22")
plot_ly(def_rating, x = ~Team, y = ~difference, color = ~change) %>%
add_bars() %>%
layout(title = 'Difference in Def Rating 21 to 22')
signif_change <- def_rating %>%
filter( change == "positive") %>%
filter( difference > 1.5)
signif_change %>%
reactable(sortable = TRUE) %>%
add_title("Top Defensive Rating Improvements '21 to '22")
#pulling and cleaning data from pbp for 22 season
pbp_url_suns <- "https://api.pbpstats.com/get-totals/nba?Season=2021-22&SeasonType=Regular%2BSeason&StartType=All&Type=Opponent"
suns_json <- read_json(pbp_url_suns)
suns_22 <- suns_json[["multi_row_table_data"]] %>%
bind_rows() %>%
filter( Name == "PHX") %>%
mutate( Year = 22) %>%
select(Year,
Avg3ptShotDistance,
Avg2ptShotDistance,
PenaltyPointsPct,
SecondChancePointsPct,
TsPct,
EfgPct,
Fg2Pct,
Fg3Pct) %>%
mutate_at(vars(Avg3ptShotDistance,
Avg2ptShotDistance,
PenaltyPointsPct,
SecondChancePointsPct,
TsPct,
EfgPct,
Fg2Pct,
Fg3Pct), funs(round(.,4)))
#pulling and cleaning data from pbp for 21 season
pbp_url_suns_2 <- "https://api.pbpstats.com/get-totals/nba?Season=2020-21&SeasonType=Regular%2BSeason&StartType=All&Type=Opponent"
suns_json_2 <- read_json(pbp_url_suns_2)
suns_21 <- suns_json_2[["multi_row_table_data"]] %>%
bind_rows() %>%
filter( Name == "PHX") %>%
mutate( Year = 21) %>%
select(Year,
Avg3ptShotDistance,
Avg2ptShotDistance,
PenaltyPointsPct,
SecondChancePointsPct,
TsPct,
EfgPct,
Fg2Pct,
Fg3Pct) %>%
mutate( Year = 21) %>%
mutate_at(vars(Avg3ptShotDistance,
Avg2ptShotDistance,
PenaltyPointsPct,
SecondChancePointsPct,
TsPct,
EfgPct,
Fg2Pct,
Fg3Pct), funs(round(.,4)))
#combining dataframes
suns <- bind_rows(suns_22, suns_21)
reactable(suns,filterable = TRUE,
pagination = FALSE,
sortable = TRUE,
searchable = TRUE) %>%
add_title("Suns Defensive Metrics for '21 to '22")
suns[1, ] - suns[2, ]
## Year Avg3ptShotDistance Avg2ptShotDistance PenaltyPointsPct
## 1 1 0.0214 0.4367 0.001
## SecondChancePointsPct TsPct EfgPct Fg2Pct Fg3Pct
## 1 0.0136 -0.0224 -0.0238 -0.0249 -0.0146
Above is the differences for the Suns in ’21 and ’22 seasons
The Suns decreased their opponent True Shooting Percentage by 2.24% and Effective Field Goal Percentages by 2.38%
The Suns forced opponents to take their 2pt FGs from .4 feet further
A decrease of 2.24% to TS% would move the Nuggets from 18th to 5th league-wide
A decrease of 2.38% to EFG% would move the Nuggets from 20th to 4th league-wide
#timberwolves
pbp_url_suns <- "https://api.pbpstats.com/get-totals/nba?Season=2021-22&SeasonType=Regular%2BSeason&StartType=All&Type=Opponent"
suns_json <- read_json(pbp_url_suns)
wolves_22 <- suns_json[["multi_row_table_data"]] %>%
bind_rows() %>%
filter( Name == "MIN") %>%
mutate( Year = 22) %>%
select(Year,
Avg3ptShotDistance,
Avg2ptShotDistance,
PenaltyPointsPct,
SecondChancePointsPct,
TsPct,
EfgPct,
Fg2Pct,
Fg3Pct) %>%
mutate_at(vars(Avg3ptShotDistance,
Avg2ptShotDistance,
PenaltyPointsPct,
SecondChancePointsPct,
TsPct,
EfgPct,
Fg2Pct,
Fg3Pct), funs(round(.,4)))
pbp_url_suns_2 <- "https://api.pbpstats.com/get-totals/nba?Season=2020-21&SeasonType=Regular%2BSeason&StartType=All&Type=Opponent"
suns_json_2 <- read_json(pbp_url_suns_2)
wolves_21 <- suns_json_2[["multi_row_table_data"]] %>%
bind_rows() %>%
filter( Name == "MIN") %>%
mutate( Year = 21) %>%
select(Year,
Avg3ptShotDistance,
Avg2ptShotDistance,
PenaltyPointsPct,
SecondChancePointsPct,
TsPct,
EfgPct,
Fg2Pct,
Fg3Pct) %>%
mutate( Year = 21) %>%
mutate_at(vars(Avg3ptShotDistance,
Avg2ptShotDistance,
PenaltyPointsPct,
SecondChancePointsPct,
TsPct,
EfgPct,
Fg2Pct,
Fg3Pct), funs(round(.,4)))
wolves <- bind_rows(wolves_22, wolves_21)
reactable(wolves,filterable = TRUE,
pagination = FALSE,
sortable = TRUE,
searchable = TRUE) %>%
add_title("Wolves Defensive Metrics for '21 to '22")
wolves[1, ] - wolves[2, ]
## Year Avg3ptShotDistance Avg2ptShotDistance PenaltyPointsPct
## 1 1 -0.1149 -0.2122 0.0273
## SecondChancePointsPct TsPct EfgPct Fg2Pct Fg3Pct
## 1 0.0019 -0.0182 -0.0204 0.0066 -0.042
Above is the differences for the Wolves in ’21 and ’22 seasons
The Wolves decreased their opponents True Shooting Percentage by 1.82% and Effective Field Goal Percentage by 2.04%
Opponents shot 4.2% worse than the previous year
A 4.2% decrease in opponent 3pt% would move the Nuggets from 10th to 1st (and by a wide margin), but opponent 3pt% is one of the most luck influenced of stats
#heat
pbp_url_suns <- "https://api.pbpstats.com/get-totals/nba?Season=2021-22&SeasonType=Regular%2BSeason&StartType=All&Type=Opponent"
suns_json <- read_json(pbp_url_suns)
heat_22 <- suns_json[["multi_row_table_data"]] %>%
bind_rows() %>%
filter( Name == "MIA") %>%
mutate( Year = 22) %>%
select(Year,
Avg3ptShotDistance,
Avg2ptShotDistance,
PenaltyPointsPct,
SecondChancePointsPct,
TsPct,
EfgPct,
Fg2Pct,
Fg3Pct) %>%
mutate_at(vars(Avg3ptShotDistance,
Avg2ptShotDistance,
PenaltyPointsPct,
SecondChancePointsPct,
TsPct,
EfgPct,
Fg2Pct,
Fg3Pct), funs(round(.,4)))
pbp_url_suns_2 <- "https://api.pbpstats.com/get-totals/nba?Season=2020-21&SeasonType=Regular%2BSeason&StartType=All&Type=Opponent"
suns_json_2 <- read_json(pbp_url_suns_2)
heat_21 <- suns_json_2[["multi_row_table_data"]] %>%
bind_rows() %>%
filter( Name == "MIA") %>%
mutate( Year = 21) %>%
select(Year,
Avg3ptShotDistance,
Avg2ptShotDistance,
PenaltyPointsPct,
SecondChancePointsPct,
TsPct,
EfgPct,
Fg2Pct,
Fg3Pct) %>%
mutate( Year = 21) %>%
mutate_at(vars(Avg3ptShotDistance,
Avg2ptShotDistance,
PenaltyPointsPct,
SecondChancePointsPct,
TsPct,
EfgPct,
Fg2Pct,
Fg3Pct), funs(round(.,4)))
heat <- bind_rows(heat_22, heat_21)
reactable(heat,filterable = TRUE,
pagination = FALSE,
sortable = TRUE,
searchable = TRUE) %>%
add_title("Heat Defensive Metrics for '21 to '22")
heat[1, ] - heat[2, ]
## Year Avg3ptShotDistance Avg2ptShotDistance PenaltyPointsPct
## 1 1 0.0123 0.2841 0.0303
## SecondChancePointsPct TsPct EfgPct Fg2Pct Fg3Pct
## 1 0.0067 -0.0142 -0.0181 -7e-04 -0.0259
Above is the differences for the Heat in ’21 and ’22 seasons
The Heat decreased their opponent’s True Shooting Percentage by 1.42% and Effective Field Goal Percentage by 1.81%
Opponents shot 2.59% worse than the year before, the Heat also forced teams to take marginally deeper 2s and 3s
A 2.59% decrease to opponent 3pt% would again move the from 10th to 1st
#kings
pbp_url_suns <- "https://api.pbpstats.com/get-totals/nba?Season=2021-22&SeasonType=Regular%2BSeason&StartType=All&Type=Opponent"
suns_json <- read_json(pbp_url_suns)
kings_22 <- suns_json[["multi_row_table_data"]] %>%
bind_rows() %>%
filter( Name == "SAC") %>%
mutate( Year = 22) %>%
select(Year,
Avg3ptShotDistance,
Avg2ptShotDistance,
PenaltyPointsPct,
SecondChancePointsPct,
TsPct,
EfgPct,
Fg2Pct,
Fg3Pct) %>%
mutate_at(vars(Avg3ptShotDistance,
Avg2ptShotDistance,
PenaltyPointsPct,
SecondChancePointsPct,
TsPct,
EfgPct,
Fg2Pct,
Fg3Pct), funs(round(.,4)))
pbp_url_suns_2 <- "https://api.pbpstats.com/get-totals/nba?Season=2020-21&SeasonType=Regular%2BSeason&StartType=All&Type=Opponent"
suns_json_2 <- read_json(pbp_url_suns_2)
kings_21 <- suns_json_2[["multi_row_table_data"]] %>%
bind_rows() %>%
filter( Name == "SAC") %>%
mutate( Year = 21) %>%
select(Year,
Avg3ptShotDistance,
Avg2ptShotDistance,
PenaltyPointsPct,
SecondChancePointsPct,
TsPct,
EfgPct,
Fg2Pct,
Fg3Pct) %>%
mutate( Year = 21) %>%
mutate_at(vars(Avg3ptShotDistance,
Avg2ptShotDistance,
PenaltyPointsPct,
SecondChancePointsPct,
TsPct,
EfgPct,
Fg2Pct,
Fg3Pct), funs(round(.,4)))
kings <- bind_rows(kings_22, kings_21)
reactable(kings,filterable = TRUE,
pagination = FALSE,
sortable = TRUE,
searchable = TRUE) %>%
add_title("Kings Defensive Metrics for '21 to '22")
kings[1, ] - kings[2, ]
## Year Avg3ptShotDistance Avg2ptShotDistance PenaltyPointsPct
## 1 1 0.0849 0.0668 -0.0181
## SecondChancePointsPct TsPct EfgPct Fg2Pct Fg3Pct
## 1 0.0089 -0.0089 -0.0059 -0.0025 -0.0083
Above is the differences for the Kings in ’21 and ’22 seasons
sessionInfo()
## R version 4.1.0 (2021-05-18)
## Platform: x86_64-apple-darwin17.0 (64-bit)
## Running under: macOS Big Sur 10.16
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRblas.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRlapack.dylib
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] gt_0.3.1 jsonlite_1.7.2 reactablefmtr_2.0.0
## [4] plotly_4.10.0 reactable_0.3.0 rvest_1.0.2
## [7] forcats_0.5.1 stringr_1.4.0 dplyr_1.0.7
## [10] purrr_0.3.4 readr_2.0.1 tidyr_1.2.0
## [13] tibble_3.1.4 ggplot2_3.3.5 tidyverse_1.3.1
##
## loaded via a namespace (and not attached):
## [1] Rcpp_1.0.7 lubridate_1.7.10 assertthat_0.2.1 digest_0.6.27
## [5] utf8_1.2.2 reactR_0.4.4 R6_2.5.1 cellranger_1.1.0
## [9] backports_1.2.1 reprex_2.0.0 evaluate_0.14 httr_1.4.2
## [13] pillar_1.6.2 rlang_0.4.11 curl_4.3.2 lazyeval_0.2.2
## [17] readxl_1.3.1 rstudioapi_0.13 data.table_1.14.0 jquerylib_0.1.4
## [21] rmarkdown_2.14 selectr_0.4-2 htmlwidgets_1.5.4 munsell_0.5.0
## [25] broom_0.7.8 compiler_4.1.0 modelr_0.1.8 xfun_0.31
## [29] pkgconfig_2.0.3 htmltools_0.5.2 tidyselect_1.1.1 fansi_0.5.0
## [33] viridisLite_0.4.0 crayon_1.4.1 tzdb_0.1.2 dbplyr_2.1.1
## [37] withr_2.4.2 grid_4.1.0 gtable_0.3.0 lifecycle_1.0.0
## [41] DBI_1.1.1 magrittr_2.0.1 scales_1.1.1 cli_3.0.1
## [45] stringi_1.7.4 farver_2.1.0 fs_1.5.0 xml2_1.3.2
## [49] bslib_0.3.1 ellipsis_0.3.2 generics_0.1.0 vctrs_0.3.8
## [53] RColorBrewer_1.1-2 tools_4.1.0 glue_1.4.2 crosstalk_1.2.0
## [57] hms_1.1.0 fastmap_1.1.0 yaml_2.2.1 colorspace_2.0-2
## [61] knitr_1.33 haven_2.4.1 sass_0.4.0