Problem/Question: Is Bruce Brown a threat from 3pt range?
Results: While he did shoot 40% from 3pt range last year, it was largely due to a extremely hot streak from the right corner. Post All-Star break he shot 76% from the right corner compared to 30% pre All-Star break and 19% in the ’21 season. He also shot better from above the arc but shot worse from the left corner and mid range, suggesting that the uptick is not due to an internal factor like an adjustment to mechanics or mental. There was also only a slight uptick in number of attempts (17 post all star, 10 pre and 21 the year prior).
All the evidence suggests that the increase in 3pt% was due to variance magnified by a small sample size.
library(ggplot2)
library(tidyverse)
library(nbastatR)
library(devtools)
library(extrafont)
library(cowplot)
library(dplyr)
library(plotly)
library(lubridate)
library(reactable)
library(reactablefmtr)
library(rpubs)
Sys.setenv("VROOM_CONNECTION_SIZE" = 131072 * 2)
#court
circle_points = function(center = c(0, 0), radius = 1, npoints = 360) {
angles = seq(0, 2 * pi, length.out = npoints)
return(data_frame(x = center[1] + radius * cos(angles),
y = center[2] + radius * sin(angles)))
}
width = 50
height = 94 / 2
key_height = 19
inner_key_width = 12
outer_key_width = 16
backboard_width = 6
backboard_offset = 4
neck_length = 0.5
hoop_radius = 0.75
hoop_center_y = backboard_offset + neck_length + hoop_radius
three_point_radius = 23.75
three_point_side_radius = 22
three_point_side_height = 14
court_themes = list(
light = list(
court = 'floralwhite',
lines = '#999999',
text = '#222222',
made = '#00bfc4',
missed = '#f8766d',
hex_border_size = 1,
hex_border_color = "#000000"
),
dark = list(
court = '#000004',
lines = '#999999',
text = '#f0f0f0',
made = '#00bfc4',
missed = '#f8766d',
hex_border_size = 0,
hex_border_color = "#000000"
)
)
plot_court = function(court_theme = court_themes$light, use_short_three = FALSE) {
if (use_short_three) {
three_point_radius = 22
three_point_side_height = 0
}
court_points = data_frame(
x = c(width / 2, width / 2, -width / 2, -width / 2, width / 2),
y = c(height, 0, 0, height, height),
desc = "perimeter"
)
court_points = bind_rows(court_points , data_frame(
x = c(outer_key_width / 2, outer_key_width / 2, -outer_key_width / 2, -outer_key_width / 2),
y = c(0, key_height, key_height, 0),
desc = "outer_key"
))
court_points = bind_rows(court_points , data_frame(
x = c(-backboard_width / 2, backboard_width / 2),
y = c(backboard_offset, backboard_offset),
desc = "backboard"
))
court_points = bind_rows(court_points , data_frame(
x = c(0, 0), y = c(backboard_offset, backboard_offset + neck_length), desc = "neck"
))
foul_circle = circle_points(center = c(0, key_height), radius = inner_key_width / 2)
foul_circle_top = filter(foul_circle, y > key_height) %>%
mutate(desc = "foul_circle_top")
foul_circle_bottom = filter(foul_circle, y < key_height) %>%
mutate(
angle = atan((y - key_height) / x) * 180 / pi,
angle_group = floor((angle - 5.625) / 11.25),
desc = paste0("foul_circle_bottom_", angle_group)
) %>%
filter(angle_group %% 2 == 0) %>%
select(x, y, desc)
hoop = circle_points(center = c(0, hoop_center_y), radius = hoop_radius) %>%
mutate(desc = "hoop")
restricted = circle_points(center = c(0, hoop_center_y), radius = 4) %>%
filter(y >= hoop_center_y) %>%
mutate(desc = "restricted")
three_point_circle = circle_points(center = c(0, hoop_center_y), radius = three_point_radius) %>%
filter(y >= three_point_side_height, y >= hoop_center_y)
three_point_line = data_frame(
x = c(three_point_side_radius, three_point_side_radius, three_point_circle$x, -three_point_side_radius, -three_point_side_radius),
y = c(0, three_point_side_height, three_point_circle$y, three_point_side_height, 0),
desc = "three_point_line"
)
court_points = bind_rows(
court_points,
foul_circle_top,
foul_circle_bottom,
hoop,
restricted,
three_point_line
)
court_points <- court_points
ggplot() +
geom_path(
data = court_points,
aes(x = x, y = y, group = desc),
color = court_theme$lines
) +
coord_fixed(ylim = c(0, 45), xlim = c(-25, 25)) +
theme_minimal(base_size = 22) +
theme(
text = element_text(color = court_theme$text),
plot.background = element_rect(fill = 'floralwhite', color = 'floralwhite'),
panel.background = element_rect(fill = court_theme$court, color = court_theme$court),
panel.grid = element_blank(),
panel.border = element_blank(),
axis.text = element_blank(),
axis.title = element_blank(),
axis.ticks = element_blank(),
legend.background = element_rect(fill = court_theme$court, color = court_theme$court),
legend.position = "bottom",
legend.key = element_blank(),
legend.text = element_text(size = rel(1.0))
)
}
team <- "Brooklyn Nets"
player <- "Bruce Brown"
#pull data
df <- teams_shots(teams = team, seasons = 2022) %>%
filter(namePlayer == player) %>%
mutate( x = as.numeric(as.character(locationX))/10, y = as.numeric(as.character(locationY))/10 + hoop_center_y)
## Brooklyn Nets 2021-22 shot data
df$x <- df$x * -1
df$dateGame <- ymd(df$dateGame)
df_pre <- df %>%
filter( dateGame < '2022-2-20')
#Pre All Star break shot chart and table
shot_chart_1 <- plot_court() +
geom_point( data = df_pre, aes( x = x , y = y,
color = isShotMade,
fill = isShotMade),
size = 2, shape = 21, stroke = .2) +
scale_color_manual( values = c("green4", "red3"),
aesthetics = "color",
breaks = c("TRUE", "FALSE"),
labels = c("Made", "Missed")) +
scale_fill_manual( values = c("green2", "grey20"),
aesthetics = "fill",
breaks = c("TRUE", "FALSE"),
labels = c("Made", "Missed")) +
scale_x_continuous( limits = c(-27.5, 27.5)) +
scale_y_continuous( limits = c(0,45)) +
theme( legend.title = element_blank()) +
ggtitle( label = "Bruce Brown: Pre All Star Break")
ggplotly(shot_chart_1) %>%
highlight( selectize = TRUE) %>%
hide_legend()
df_pre %>%
mutate( make_miss = case_when(
typeEvent == "Made Shot" ~ 1,
TRUE ~ 0
)) %>%
group_by(zoneBasic) %>%
summarise(shooting = mean(make_miss)) %>%
mutate_at(vars(shooting), funs(round(.,4))) %>%
reactable() %>%
add_title("Pre All Star Break Shooting by Zone")
df_pre_clean <- df_pre %>%
mutate( make_miss = case_when(
typeEvent == "Made Shot" ~ 1,
TRUE ~ 0
)) %>%
group_by(zoneBasic) %>%
summarise(shooting = mean(make_miss)) %>%
mutate_at(vars(shooting), funs(round(.,4))) %>%
as.data.frame() %>%
rename( Pre_all_star = shooting)
df_post <- df %>%
filter( dateGame > '2022-2-20')
#Post All Star Break shot chart and table
shot_chart_2 <- plot_court() +
geom_point( data = df_post, aes( x = x , y = y,
color = isShotMade,
fill = isShotMade),
size = 2, shape = 21, stroke = .2) +
scale_color_manual( values = c("green4", "red3"),
aesthetics = "color",
breaks = c("TRUE", "FALSE"),
labels = c("Made", "Missed")) +
scale_fill_manual( values = c("green2", "grey20"),
aesthetics = "fill",
breaks = c("TRUE", "FALSE"),
labels = c("Made", "Missed")) +
scale_x_continuous( limits = c(-27.5, 27.5)) +
scale_y_continuous( limits = c(0,45)) +
theme( legend.title = element_blank()) +
ggtitle( label = "Bruce Brown: Post All Star Break")
ggplotly(shot_chart_2) %>%
highlight( selectize = TRUE) %>%
hide_legend()
df_post %>%
mutate( make_miss = case_when(
typeEvent == "Made Shot" ~ 1,
TRUE ~ 0
)) %>%
group_by(zoneBasic) %>%
summarise(shooting = mean(make_miss)) %>%
mutate_at(vars(shooting), funs(round(.,4))) %>%
reactable() %>%
add_title("Post All Star Break Shooting by Zone")
df_post_clean <- df_post %>%
mutate( make_miss = case_when(
typeEvent == "Made Shot" ~ 1,
TRUE ~ 0
)) %>%
group_by(zoneBasic) %>%
summarise(shooting = mean(make_miss)) %>%
mutate_at(vars(shooting), funs(round(.,4))) %>%
as.data.frame() %>%
rename( Post_all_star = shooting)
df_21 <- teams_shots(teams = team, seasons = 2021) %>%
filter(namePlayer == player) %>%
mutate( x = as.numeric(as.character(locationX))/10, y = as.numeric(as.character(locationY))/10 + hoop_center_y)
## Brooklyn Nets 2020-21 shot data
df_21$x <- df_21$x * -1
df_21_clean <- df_21 %>%
mutate( make_miss = case_when(
typeEvent == "Made Shot" ~ 1,
TRUE ~ 0
)) %>%
group_by(zoneBasic) %>%
summarise(shooting = mean(make_miss)) %>%
mutate_at(vars(shooting), funs(round(.,4))) %>%
as.data.frame() %>%
rename( '21 Shooting' = shooting)
#Shooitng compared to ’21 season
right_join(df_21_clean, df_pre_clean) %>%
right_join(df_post_clean) %>%
reactable() %>%
add_title("Tracking Shooting")
Right Corner 3 attempts in ’21
df_21 %>%
filter(zoneBasic == "Right Corner 3") %>%
summarise(n_distinct(idEvent))
## # A tibble: 1 × 1
## `n_distinct(idEvent)`
## <int>
## 1 21
Right Corner 3 attempts pre all star break
df_pre %>%
filter(zoneBasic == "Right Corner 3") %>%
summarise(n_distinct(idEvent))
## # A tibble: 1 × 1
## `n_distinct(idEvent)`
## <int>
## 1 10
Right Corner 3 attempts post all star break
df_post %>%
filter(zoneBasic == "Right Corner 3") %>%
summarise(n_distinct(idEvent))
## # A tibble: 1 × 1
## `n_distinct(idEvent)`
## <int>
## 1 17
sessionInfo()
## R version 4.2.1 (2022-06-23)
## 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.2/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.2/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] rpubs_0.2.2 reactablefmtr_2.0.0 reactable_0.3.0
## [4] lubridate_1.8.0 plotly_4.10.0 cowplot_1.1.1
## [7] extrafont_0.18 devtools_2.4.4 usethis_2.1.6
## [10] nbastatR_0.1.151 forcats_0.5.2 stringr_1.4.1
## [13] dplyr_1.0.10 purrr_0.3.4 readr_2.1.3
## [16] tidyr_1.2.1 tibble_3.1.8 tidyverse_1.3.2
## [19] ggplot2_3.3.6
##
## loaded via a namespace (and not attached):
## [1] googledrive_2.0.0 colorspace_2.0-3 ellipsis_0.3.2
## [4] fs_1.5.2 rstudioapi_0.14 listenv_0.8.0
## [7] furrr_0.3.1 remotes_2.4.2 bit64_4.0.5
## [10] fansi_1.0.3 xml2_1.3.3 codetools_0.2-18
## [13] cachem_1.0.6 knitr_1.40 pkgload_1.3.0
## [16] jsonlite_1.8.2 broom_1.0.1 Rttf2pt1_1.3.10
## [19] dbplyr_2.2.1 shiny_1.7.2 compiler_4.2.1
## [22] httr_1.4.4 backports_1.4.1 assertthat_0.2.1
## [25] fastmap_1.1.0 lazyeval_0.2.2 gargle_1.2.1
## [28] cli_3.4.1 later_1.3.0 htmltools_0.5.3
## [31] prettyunits_1.1.1 tools_4.2.1 gtable_0.3.1
## [34] glue_1.6.2 Rcpp_1.0.9 cellranger_1.1.0
## [37] jquerylib_0.1.4 vctrs_0.4.2 extrafontdb_1.0
## [40] crosstalk_1.2.0 xfun_0.33 globals_0.16.1
## [43] ps_1.7.1 rvest_1.0.3 mime_0.12
## [46] miniUI_0.1.1.1 lifecycle_1.0.2 googlesheets4_1.0.1
## [49] future_1.28.0 scales_1.2.1 vroom_1.6.0
## [52] hms_1.1.2 promises_1.2.0.1 parallel_4.2.1
## [55] yaml_2.3.5 curl_4.3.2 memoise_2.0.1
## [58] sass_0.4.2 stringi_1.7.8 pkgbuild_1.3.1
## [61] rlang_1.0.6 pkgconfig_2.0.3 evaluate_0.16
## [64] htmlwidgets_1.5.4 labeling_0.4.2 bit_4.0.4
## [67] processx_3.7.0 tidyselect_1.1.2 parallelly_1.32.1
## [70] magrittr_2.0.3 R6_2.5.1 generics_0.1.3
## [73] profvis_0.3.7 DBI_1.1.3 pillar_1.8.1
## [76] haven_2.5.1 withr_2.5.0 modelr_0.1.9
## [79] crayon_1.5.2 utf8_1.2.2 tzdb_0.3.0
## [82] rmarkdown_2.16 urlchecker_1.0.1 grid_4.2.1
## [85] readxl_1.4.1 data.table_1.14.2 reactR_0.4.4
## [88] callr_3.7.2 reprex_2.0.2 digest_0.6.29
## [91] xtable_1.8-4 httpuv_1.6.6 munsell_0.5.0
## [94] viridisLite_0.4.1 bslib_0.4.0 sessioninfo_1.2.2