Nothing
## ----setup, include = FALSE---------------------------------------------------
knitr::opts_chunk$set(
collapse = TRUE,
comment = '#>',
fig.align = 'center',
out.width = '92%',
fig.width = 7,
fig.height = 4.6
)
make_table <- function(x, caption, digits = 3) {
knitr::kable(x, caption = caption, digits = digits)
}
## ----data---------------------------------------------------------------------
# Pull scoring and bio tables.
playoff_stats <- nhlscraper::skater_playoff_statistics()
career_stats <- nhlscraper::skater_statistics()[, c(
'playerId',
'rsGamesPlayed',
'rsPoints',
'positionCode'
)]
player_bios <- nhlscraper::players()[, c(
'playerId',
'playerFullName',
'height',
'weight'
)]
# Join player-level sources.
analysis_tbl <- merge(
playoff_stats,
career_stats,
by = c('playerId', 'positionCode'),
all.x = TRUE
)
analysis_tbl <- merge(
analysis_tbl,
player_bios,
by = 'playerId',
all.x = TRUE
)
# Keep modern skaters with stable samples.
analysis_tbl <- analysis_tbl[
!is.na(analysis_tbl[['height']]) &
!is.na(analysis_tbl[['weight']]) &
analysis_tbl[['firstSeasonForGameType']] >= 20052006 &
analysis_tbl[['gamesPlayed']] >= 20 &
analysis_tbl[['rsGamesPlayed']] >= 200,
,
drop = FALSE
]
# Fill names and compute rates.
analysis_tbl[['playerFullName']] <- ifelse(
is.na(analysis_tbl[['playerFullName']]) |
analysis_tbl[['playerFullName']] == '',
paste(
analysis_tbl[['skaterFirstName']],
analysis_tbl[['skaterLastName']]
),
analysis_tbl[['playerFullName']]
)
analysis_tbl[['regularPPG']] <-
analysis_tbl[['rsPoints']] / analysis_tbl[['rsGamesPlayed']]
analysis_tbl[['playoffPPG']] <-
analysis_tbl[['points']] / analysis_tbl[['gamesPlayed']]
analysis_tbl[['playoffLift']] <-
analysis_tbl[['playoffPPG']] - analysis_tbl[['regularPPG']]
analysis_tbl[['positionBucket']] <- ifelse(
analysis_tbl[['positionCode']] == 'D',
'Defense',
'Forward'
)
# Assign equal-count weight quartiles.
weight_rank <- rank(
analysis_tbl[['weight']],
ties.method = 'first'
) / nrow(analysis_tbl)
analysis_tbl[['weightQuartile']] <- cut(
weight_rank,
breaks = c(0, 0.25, 0.50, 0.75, 1),
include.lowest = TRUE,
labels = c('Lightest', 'Second', 'Third', 'Heaviest')
)
nrow(analysis_tbl)
## ----quartile-table-----------------------------------------------------------
# Summarize scoring by weight quartile.
quartile_summary <- aggregate(
cbind(regularPPG, playoffPPG, playoffLift) ~ weightQuartile,
data = analysis_tbl,
FUN = mean
)
quartile_counts <- as.data.frame(table(analysis_tbl[['weightQuartile']]))
names(quartile_counts) <- c('weightQuartile', 'n')
quartile_summary <- merge(
quartile_summary,
quartile_counts,
by = 'weightQuartile'
)
quartile_summary <- quartile_summary[
match(levels(analysis_tbl[['weightQuartile']]), quartile_summary[['weightQuartile']]),
c('weightQuartile', 'n', 'regularPPG', 'playoffPPG', 'playoffLift')
]
make_table(
quartile_summary,
caption = 'Regular-season scoring, playoff scoring, and playoff lift by weight quartile.',
digits = 3
)
## ----quartile-plot, fig.cap = 'Playoff scoring level and playoff lift by weight quartile.'----
# Plot playoff scoring and playoff lift.
old_par <- graphics::par(no.readonly = TRUE)
graphics::par(mfrow = c(1, 2), mar = c(8, 4, 3, 1))
graphics::boxplot(
playoffPPG ~ weightQuartile,
data = analysis_tbl,
col = c('#d8f3dc', '#b7e4c7', '#74c69d', '#2d6a4f'),
border = '#1b4332',
las = 2,
xlab = '',
ylab = 'Playoff Points Per Game'
)
graphics::barplot(
quartile_summary[['playoffLift']],
names.arg = quartile_summary[['weightQuartile']],
col = c('#fcbf49', '#f77f00', '#d62828', '#6a4c93'),
border = NA,
las = 2,
xlab = '',
ylab = 'Playoff Lift'
)
graphics::abline(h = 0, lty = 2, col = '#495057')
graphics::par(old_par)
## ----position-summary---------------------------------------------------------
# Summarize rates by position and quartile.
position_summary <- aggregate(
cbind(regularPPG, playoffPPG, playoffLift) ~ positionBucket + weightQuartile,
data = analysis_tbl,
FUN = mean
)
position_counts <- aggregate(
playerId ~ positionBucket + weightQuartile,
data = analysis_tbl,
FUN = length
)
names(position_counts)[names(position_counts) == 'playerId'] <- 'n'
position_summary <- merge(
position_summary,
position_counts,
by = c('positionBucket', 'weightQuartile')
)
make_table(
position_summary,
caption = 'Scoring translation by position family and weight quartile.',
digits = 3
)
## ----risers-------------------------------------------------------------------
# Show largest positive playoff lifts.
risers_tbl <- analysis_tbl[
analysis_tbl[['gamesPlayed']] >= 40,
c(
'playerFullName',
'positionBucket',
'weight',
'regularPPG',
'playoffPPG',
'playoffLift',
'gamesPlayed'
)
]
risers_tbl <- risers_tbl[order(-risers_tbl[['playoffLift']]), ]
risers_tbl <- utils::head(risers_tbl, 10)
make_table(
risers_tbl,
caption = 'Largest playoff scoring lifts among skaters with at least 40 playoff games.',
digits = 3
)
## ----fallers------------------------------------------------------------------
# Show largest negative playoff lifts.
fallers_tbl <- analysis_tbl[
analysis_tbl[['gamesPlayed']] >= 40,
c(
'playerFullName',
'positionBucket',
'weight',
'regularPPG',
'playoffPPG',
'playoffLift',
'gamesPlayed'
)
]
fallers_tbl <- fallers_tbl[order(fallers_tbl[['playoffLift']]), ]
fallers_tbl <- utils::head(fallers_tbl, 10)
make_table(
fallers_tbl,
caption = 'Largest playoff scoring drops among skaters with at least 40 playoff games.',
digits = 3
)
## ----model--------------------------------------------------------------------
# Fit playoff-lift model.
lift_fit <- stats::lm(
playoffLift ~ height + weight + I(positionCode == 'D'),
data = analysis_tbl
)
lift_fit_tbl <- as.data.frame(summary(lift_fit)$coefficients)
lift_fit_tbl[['term']] <- rownames(lift_fit_tbl)
rownames(lift_fit_tbl) <- NULL
lift_fit_tbl[['term']] <- c(
'Intercept',
'Height',
'Weight',
'Defense indicator'
)
lift_fit_tbl <- lift_fit_tbl[, c(
'term',
'Estimate',
'Std. Error',
't value',
'Pr(>|t|)'
)]
make_table(
lift_fit_tbl,
caption = 'Linear model of playoff scoring lift.',
digits = 4
)
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.