diff --git a/DESCRIPTION b/DESCRIPTION index 6eabccfd..26aa140f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: nflfastR Title: Functions to Efficiently Access NFL Play by Play Data -Version: 4.6.1.9018 +Version: 4.6.1.9019 Authors@R: c(person(given = "Sebastian", family = "Carl", @@ -51,6 +51,7 @@ Imports: future, glue, janitor, + lifecycle, mgcv, nflreadr (>= 1.2.0), progressr (>= 0.6.0), @@ -63,7 +64,6 @@ Imports: Suggests: DBI, gsisdecoder, - lifecycle (>= 0.2.0), nflseedR (>= 1.0.2), purrr (>= 0.3.0), rmarkdown, diff --git a/NAMESPACE b/NAMESPACE index 31fddacc..bd8741de 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -10,6 +10,7 @@ export(calculate_player_stats_def) export(calculate_player_stats_kicking) export(calculate_series_conversion_rates) export(calculate_standings) +export(calculate_stats) export(calculate_win_probability) export(clean_pbp) export(decode_player_ids) @@ -27,6 +28,7 @@ import(dplyr) import(fastrmodels) importFrom(cli,rule) importFrom(curl,curl_fetch_memory) +importFrom(data.table,"%between%") importFrom(data.table,"%chin%") importFrom(data.table,setDT) importFrom(furrr,future_map) @@ -36,11 +38,13 @@ importFrom(future,plan) importFrom(glue,glue) importFrom(glue,glue_sql) importFrom(janitor,clean_names) +importFrom(lifecycle,deprecated) importFrom(mgcv,predict.bam) importFrom(nflreadr,nflverse_sitrep) importFrom(progressr,progressor) importFrom(rlang,":=") importFrom(rlang,.data) +importFrom(rlang,.env) importFrom(rlang,dots_list) importFrom(rlang,inform) importFrom(stats,na.omit) diff --git a/NEWS.md b/NEWS.md index f6e558bb..6675ae98 100644 --- a/NEWS.md +++ b/NEWS.md @@ -12,6 +12,9 @@ - `punter_player_id`, and `punter_player_name` are filled for blocked punt attempts. (#463) - Fixed an issue affecting scores of 2022 games involving a return touchdown (#466) - Added identification of scrambles from 1999 through 2004 with thank to Aaron Schatz (#468, #489) +- Added new function `calculate_stats()` that combines the output of all `calculate_player_stats*()` functions with a more robust and faster approach. The `calculate_player_stats*()` function will be deprecated in a future release. (#470) +- Updated the dataframe `stat_ids` with some IDs that were previously missing. (#470) +- Added new exported dataframe `nfl_stats_variables`. It lists and explains all variables returned by `calculate_stats()`. A searchable table is available at . (#470) - nflfastR tried to fix bugs in the underlying pbp data of JAX home games prior to the 2016 season. An update of the raw pbp data resolved those bugs so nflfastR needs to remove the hard coded adjustments. This means that nflfastR <= v4.6.1 will return incorrect pbp data for all Jacksonville home games prior to the 2016 season! (#478) - Fixed a problem where `clean_pbp()` returned `pass = 1` in actual rush plays in very rare cases. (#479) - Removed extra lines for injury timeouts that were breaking `fixed_drive` (#482) diff --git a/R/aggregate_game_stats.R b/R/aggregate_game_stats.R index 80d76ef7..8137eecf 100644 --- a/R/aggregate_game_stats.R +++ b/R/aggregate_game_stats.R @@ -5,6 +5,12 @@ #' Get Official Game Stats #' +#' @description +#' `r lifecycle::badge("deprecated")` +#' +#' This function was deprecated because we have a new, much better and +#' harmonized approach in [`calculate_stats()`]. +#' #' @param pbp A Data frame of NFL play-by-play data typically loaded with #' [load_pbp()] or [build_nflfastR_pbp()]. If the data doesn't include the variable #' `qb_epa`, the function `add_qb_epa()` will be called to add it. @@ -84,6 +90,7 @@ #' \item{fantasy_points_ppr}{PPR fantasy points.} #' } #' @export +#' @keywords internal #' @seealso The function [load_player_stats()] and the corresponding examples #' on [the nflfastR website](https://www.nflfastr.com/articles/nflfastR.html#example-11-replicating-official-stats) #' @examples @@ -100,6 +107,12 @@ #' } calculate_player_stats <- function(pbp, weekly = FALSE) { + lifecycle::deprecate_warn( + "5.0", + "calculate_player_stats()", + "calculate_stats()" + ) + # need newer version of nflreadr to use load_players rlang::check_installed("nflreadr (>= 1.3.0)", "to join player information.") diff --git a/R/aggregate_game_stats_def.R b/R/aggregate_game_stats_def.R index a47c7bab..8b5eda65 100644 --- a/R/aggregate_game_stats_def.R +++ b/R/aggregate_game_stats_def.R @@ -5,6 +5,12 @@ #' Get Official Game Stats on Defense #' +#' @description +#' `r lifecycle::badge("deprecated")` +#' +#' This function was deprecated because we have a new, much better and +#' harmonized approach in [`calculate_stats()`]. +#' #' @param pbp A Data frame of NFL play-by-play data typically loaded with #' [load_pbp()] or [build_nflfastR_pbp()]. If the data doesn't include the variable #' `qb_epa`, the function `add_qb_epa()` will be called to add it. @@ -14,6 +20,7 @@ #' either at the game level or at the level of the entire data frame passed. #' @return A data frame of defensive player stats. See dictionary (# TODO) #' @export +#' @keywords internal #' @seealso The function [load_player_stats()] and the corresponding examples #' on [the nflfastR website](https://www.nflfastr.com/articles/nflfastR.html#example-11-replicating-official-stats) #' @examples @@ -59,6 +66,12 @@ calculate_player_stats_def <- function(pbp, weekly = FALSE) { + lifecycle::deprecate_warn( + "5.0", + "calculate_player_stats_def()", + "calculate_stats()" + ) + # need newer version of nflreadr to use load_players rlang::check_installed("nflreadr (>= 1.3.0)") diff --git a/R/aggregate_game_stats_kicking.R b/R/aggregate_game_stats_kicking.R index 9ae34dfa..7979840e 100644 --- a/R/aggregate_game_stats_kicking.R +++ b/R/aggregate_game_stats_kicking.R @@ -1,6 +1,12 @@ #' Summarize Kicking Stats #' -#' @description Build columns that aggregate kicking stats at the game level. +#' @description +#' `r lifecycle::badge("deprecated")` +#' +#' This function was deprecated because we have a new, much better and +#' harmonized approach in [`calculate_stats()`]. +#' +#' Build columns that aggregate kicking stats at the game level. #' #' @param pbp A Data frame of NFL play-by-play data typically loaded with #' [load_pbp()] or [build_nflfastR_pbp()]. @@ -22,8 +28,15 @@ #' @return a dataframe of kicking stats #' @seealso for the nflreadr function to download this from repo (`stat_type = "kicking"`) #' @export +#' @keywords internal calculate_player_stats_kicking <- function(pbp, weekly = FALSE) { + lifecycle::deprecate_warn( + "5.0", + "calculate_player_stats_kicking()", + "calculate_stats()" + ) + # need newer version of nflreadr to use load_players rlang::check_installed("nflreadr (>= 1.3.0)") diff --git a/R/calculate_stats.R b/R/calculate_stats.R new file mode 100644 index 00000000..11ebcd83 --- /dev/null +++ b/R/calculate_stats.R @@ -0,0 +1,495 @@ +################################################################################ +# Author: Sebastian Carl +################################################################################ + +#' Calculate NFL Stats +#' +#' Compute various NFL stats based off nflverse Play-by-Play data. +#' +#' @param seasons A numeric vector of 4-digit years associated with given NFL +#' seasons - defaults to latest season. If set to TRUE, returns all available +#' data since 1999. +#' @param summary_level Summarize stats by `"season"` or `"week"`. +#' @param stat_type Calculate `"player"` level stats or `"team"` level stats. +#' @param season_type One of `"REG"`, `"POST"`, or `"REG+POST"`. Filters +#' data to regular season ("REG"), post season ("POST") or keeps all data. +#' +#' @return A tibble of player/team stats summarized by season/week. +#' @seealso [nfl_stats_variables] for a description of all variables. +#' @seealso for a searchable +#' table of the stats variable descriptions. +#' @export +#' +#' @examples +#' \donttest{ +#' try({# to avoid CRAN test problems +#' stats <- calculate_stats(2023, "season", "player") +#' dplyr::glimpse(stats) +#' }) +#' } +calculate_stats <- function(seasons = nflreadr::most_recent_season(), + summary_level = c("season", "week"), + stat_type = c("player", "team"), + season_type = c("REG", "POST", "REG+POST")){ + + summary_level <- rlang::arg_match(summary_level) + stat_type <- rlang::arg_match(stat_type) + season_type <- rlang::arg_match(season_type) + + pbp <- nflreadr::load_pbp(seasons = seasons) + if (season_type %in% c("REG", "POST")) { + pbp <- dplyr::filter(pbp, .data$season_type == .env$season_type) + } + + # defensive stats require knowledge of which team is on defense + # special teams stats require knowledge of which plays were special teams plays + playinfo <- pbp %>% + dplyr::group_by(.data$game_id, .data$play_id) %>% + dplyr::summarise( + off = .data$posteam, + def = .data$defteam, + special = as.integer(.data$special == 1) + ) %>% + dplyr::ungroup() %>% + dplyr::mutate_at( + .vars = dplyr::vars("off", "def"), + .funs = team_name_fn + ) + + season_type_from_pbp <- pbp %>% + dplyr::select("game_id", "season_type") %>% + dplyr::distinct() + s_type_vctr <- season_type_from_pbp$season_type %>% + rlang::set_names(season_type_from_pbp$game_id) + + gwfg_attempts_from_pbp <- pbp %>% + dplyr::mutate( + # final_posteam_score = data.table::fifelse(.data$posteam_type == "home", .data$home_score, .data$away_score), + final_defteam_score = data.table::fifelse(.data$posteam_type == "home", .data$away_score, .data$home_score), + identifier = paste(.data$game_id, .data$play_id, sep = "_") + ) %>% + dplyr::group_by(.data$game_id, .data$posteam) %>% + dplyr::mutate( + # A game winning field goal attempt is + # - a field goal attempt, + # - in the posteam's final drive, + # - where the posteam trailed the defteam by 2 points or less prior to the kick, + # - and the defteam did not score afterwards + is_gwfg_attempt = dplyr::case_when( + .data$field_goal_attempt == 1 & + .data$fixed_drive == max(.data$fixed_drive) & + dplyr::between(.data$score_differential, -2, 0) & + .data$defteam_score == .data$final_defteam_score ~ 1L, + TRUE ~ 0L + ) + ) %>% + dplyr::ungroup() %>% + dplyr::filter( + is_gwfg_attempt == 1L + ) %>% + dplyr::select("identifier", "is_gwfg_attempt") + gwfg_vctr <- gwfg_attempts_from_pbp$is_gwfg_attempt %>% + rlang::set_names(gwfg_attempts_from_pbp$identifier) + + # load_playstats defined below + # more_stats = all stat IDs of one player in a single play + # team_stats = all stat IDs of one team in a single play + # we need those to identify things like fumbles depending on playtype or + # first downs depending on playtype + playstats <- load_playstats(seasons = seasons) %>% + # if season_type is REG or POST, we filter pbp. + # That's why we have to filter playstats as well + dplyr::filter(.data$game_id %in% pbp$game_id) %>% + dplyr::rename("player_id" = "gsis_player_id", "team" = "team_abbr") %>% + dplyr::group_by(.data$season, .data$week, .data$play_id, .data$player_id) %>% + dplyr::mutate( + # we append a collapse separator to the string in order to search for matches + # including the separator to avoid 1 matching 10 + more_stats = paste0(paste(stat_id, collapse = ";"), ";") + ) %>% + dplyr::group_by(.data$season, .data$week, .data$play_id, .data$team) %>% + dplyr::mutate( + # we append a collapse separator to the string in order to search for matches + # including the separator to avoid 1 matching 10 + team_stats = paste0(paste(stat_id, collapse = ";"), ";"), + team_play_air_yards = sum((stat_id %in% 111:112) * yards) + ) %>% + dplyr::group_by(.data$season, .data$week, .data$team) %>% + dplyr::mutate( + # for calculation of target share and air yard share + team_targets = sum(stat_id == 115), + team_air_yards = sum((stat_id %in% 111:112) * yards) + ) %>% + dplyr::ungroup() %>% + dplyr::left_join( + playinfo, by = c("game_id", "play_id") + ) %>% + dplyr::mutate( + season_type = unname(s_type_vctr[.data$game_id]), + is_gwfg_attempt = unname(gwfg_vctr[paste(.data$game_id, .data$play_id, sep = "_")]) %ifna% 0L + ) + + # Check combination of summary_level and stat_type to set a helper that is + # used to create the grouping variables + grp_id <- data.table::fcase( + summary_level == "season" && stat_type == "player", "10", + summary_level == "season" && stat_type == "team", "20", + summary_level == "week" && stat_type == "player", "30", + summary_level == "week" && stat_type == "team", "40" + ) + # grp_vctr is used as character vector for joining pbp stats + grp_vctr <- switch (grp_id, + "10" = c("season", "player_id"), + "20" = c("season", "team"), + "30" = c("season", "week", "player_id"), + "40" = c("season", "week", "team") + ) + # grp_vars is used as grouping variables + grp_vars <- rlang::data_syms(grp_vctr) + + # Stats from PBP ##################### + # we want passing epa, rushing epa, and receiving epa + # since these depend on different player id variables and filters, + # we create separate dfs for these stats + passing_stats_from_pbp <- pbp %>% + dplyr::filter(.data$play_type %in% c("pass", "qb_spike")) %>% + dplyr::select( + "season", "week", "team" = "posteam", + "player_id" = "passer_player_id", "qb_epa", "cpoe" + ) %>% + dplyr::group_by(!!!grp_vars) %>% + dplyr::summarise( + passing_epa = sum(.data$qb_epa, na.rm = TRUE), + # mean will return NaN if all values are NA, because we remove NA + passing_cpoe = if (any(!is.na(.data$cpoe))) mean(.data$cpoe, na.rm = TRUE) else NA_real_ + ) %>% + dplyr::ungroup() + + rushing_stats_from_pbp <- pbp %>% + dplyr::filter(.data$play_type %in% c("run", "qb_kneel")) %>% + dplyr::select( + "season", "week", "team" = "posteam", + "player_id" = "rusher_player_id", "epa" + ) %>% + dplyr::group_by(!!!grp_vars) %>% + dplyr::summarise( + rushing_epa = sum(.data$epa, na.rm = TRUE) + ) %>% + dplyr::ungroup() + + receiving_stats_from_pbp <- pbp %>% + dplyr::filter(!is.na(.data$receiver_player_id)) %>% + dplyr::select( + "season", "week", "team" = "posteam", + "player_id" = "receiver_player_id", "epa" + ) %>% + dplyr::group_by(!!!grp_vars) %>% + dplyr::summarise( + receiving_epa = sum(.data$epa, na.rm = TRUE) + ) %>% + dplyr::ungroup() + + stats <- playstats %>% + dplyr::group_by(!!!grp_vars) %>% + dplyr::summarise( + player_name = if (.env$stat_type == "player") custom_mode(.data$player_name, na.rm = TRUE) else NULL, + # Season Type ##################### + # if summary level is week, then we have to use the season type variable + # from playstats as it could be REG or POST depending on the value of + # the argument season_type + # if summary level is season, then we collapse the values of season_type + # this will make sure that season_type is only REG+POST if the user asked + # for it AND if postseason data is available + season_type = if (.env$summary_level == "week") dplyr::first(.data$season_type) else paste(unique(.data$season_type), collapse = "+"), + + # Team Info ##################### + # recent_team if we do a season summary of player stats + # team if we do a week summary of player stats + recent_team = if (.env$grp_id == "10") dplyr::last(.data$team) else NULL, + team = if (.env$grp_id == "30") dplyr::first(.data$team) else NULL, + # opponent team if we do week summaries + opponent_team = if (.env$summary_level == "week"){ + data.table::fifelse( + dplyr::first(.data$team) == dplyr::first(.data$off), + dplyr::first(.data$def), + dplyr::first(.data$off) + ) + } else NULL, + + # number of games is only relevant if we summarise the season + games = if (.env$summary_level == "season") dplyr::n_distinct(.data$game_id) else NULL, + + # Offense ##################### + completions = sum(stat_id %in% 15:16), + attempts = sum(stat_id %in% c(14:16, 19)), + passing_yards = sum((stat_id %in% 15:16) * yards), + passing_tds = sum(stat_id == 16), + passing_interceptions = sum(stat_id == 19), + sacks_suffered = sum(stat_id == 20), + sack_yards_lost = sum((stat_id == 20) * yards), + sack_fumbles = sum(stat_id == 20 & any(has_id(52, more_stats), has_id(53, more_stats), has_id(54, more_stats))), + sack_fumbles_lost = sum(stat_id == 20 & has_id(106, more_stats)), + # includes incompletions (111 = complete, 112 = incomplete) + passing_air_yards = sum((stat_id %in% 111:112) * yards), + # passing yac equals passing yards - air yards on completed passes + passing_yards_after_catch = .data$passing_yards - sum((stat_id == 111) * yards), + passing_first_downs = sum((stat_id %in% 15:16) & has_id(4, team_stats)), + passing_2pt_conversions = sum(stat_id == 77), + # this is a player stat and we skip it in team stats + pacr = if (.env$stat_type == "player") .data$passing_yards / .data$passing_air_yards else NULL, + # dakota = requires pbp, + + carries = sum(stat_id %in% 10:11), + rushing_yards = sum((stat_id %in% 10:13) * yards), + rushing_tds = sum(stat_id %in% c(11,13)), + rushing_fumbles = sum((stat_id %in% 10:11) & any(has_id(52, more_stats), has_id(53, more_stats), has_id(54, more_stats))), + rushing_fumbles_lost = sum((stat_id %in% 10:11) & has_id(106, more_stats)), + rushing_first_downs = sum((stat_id %in% 10:11) & has_id(3, team_stats)), + rushing_2pt_conversions = sum(stat_id == 75), + + receptions = sum(stat_id %in% 21:22), + targets = sum(stat_id == 115), + receiving_yards = sum((stat_id %in% 21:24) * yards), + receiving_tds = sum(stat_id %in% c(22,24)), + receiving_fumbles = sum((stat_id %in% 21:22) & any(has_id(52, more_stats), has_id(53, more_stats), has_id(54, more_stats))), + receiving_fumbles_lost = sum((stat_id %in% 21:22) & has_id(106, more_stats)), + # air_yards are counted in 111:112 but it is a passer stat not a receiver stat + # so we count team air yards when a player accounted for a reception + # team air yards will always equal the correct air yards as 111 and 112 + # cannot appear more than once per play. + # If this ever changes, we can use pbp instead. + receiving_air_yards = if (.env$stat_type == "player"){ + sum( (stat_id %in% 21:22) * dplyr::first(.data$team_play_air_yards)) + } else .data$passing_air_yards, + receiving_yards_after_catch = sum((stat_id == 113) * yards), + receiving_first_downs = sum((stat_id %in% 21:22) & has_id(4, team_stats)), + receiving_2pt_conversions = sum(stat_id == 104), + # these are player stats and we skip them in team stats + racr = if (.env$stat_type == "player") .data$receiving_yards / .data$receiving_air_yards else NULL, + target_share = if (.env$stat_type == "player") .data$targets / dplyr::first(.data$team_targets) else NULL, + air_yards_share = if (.env$stat_type == "player") .data$receiving_air_yards / dplyr::first(.data$team_air_yards) else NULL, + wopr = if (.env$stat_type == "player") 1.5 * .data$target_share + 0.7 * .data$air_yards_share else NULL, + + special_teams_tds = sum((special == 1) & stat_id %in% td_ids()), + + # Defense ##################### + # def_tackles = , + def_tackles_solo = sum(stat_id == 79), + def_tackles_with_assist = sum(stat_id == 80), + def_tackle_assists = sum(stat_id == 82), + def_tackles_for_loss = sum(stat_id == 402), + def_tackles_for_loss_yards = sum((stat_id == 402) * yards), + def_fumbles_forced = sum(stat_id == 91), + def_sacks = sum(stat_id == 83) + 1 / 2 * sum(stat_id == 84), + def_sack_yards = sum((stat_id == 83) * -yards) + 1 / 2 * sum((stat_id == 84) * -yards), + def_qb_hits = sum(stat_id == 110), + def_interceptions = sum(stat_id %in% 25:26), + def_interception_yards = sum((stat_id %in% 25:28) * yards), + def_pass_defended = sum(stat_id == 85), + def_tds = sum(team == def & special != 1 & stat_id %in% td_ids()), + def_fumbles = sum((team == def) & stat_id %in% 52:54), + def_safeties = sum(stat_id == 89), + + # Misc ##################### + # mostly yards gained after blocked punts or fgs + misc_yards = sum((stat_id %in% 63:64) * yards), + fumble_recovery_own = sum(stat_id %in% 55:56), + # 57, 58 don't count as recovery because player received a + # lateral after recovery by other player + fumble_recovery_yards_own = sum(stat_id %in% 55:58), + fumble_recovery_opp = sum(stat_id %in% 59:60), + # 61, 62 don't count as recovery because player received a + # lateral after recovery by other player + fumble_recovery_yards_opp = sum(stat_id %in% 59:62), + fumble_recovery_tds = sum(stat_id %in% c(56, 58, 60, 62)), + penalties = sum(stat_id == 93), + penalty_yards = sum((stat_id == 93) * yards), + timeouts = if (.env$stat_type == "team") sum(stat_id == 68) else NULL, + + # Returning ##################### + punt_returns = sum(stat_id %in% 33:34), + punt_return_yards = sum((stat_id %in% 33:36) * yards), + # punt return tds are counted in special teams tds atm + # punt_return_tds = sum(stat_id %in% c(34, 36)), + kickoff_returns = sum(stat_id %in% 45:46), + kickoff_return_yards = sum((stat_id %in% 45:48) * yards), + # kickoff return tds are counted in special teams tds atm + # kickoff_return_tds = sum(stat_id %in% c(46, 48)), + + # Kicking ##################### + fg_made = sum(stat_id == 70), + fg_att = sum(stat_id %in% 69:71), + fg_missed = sum(stat_id == 69), + fg_blocked = sum(stat_id == 71), + fg_long = max((stat_id == 70) * yards) %0% NA_integer_, + # avoid 0/0 = NaN + fg_pct = if (.data$fg_att > 0) .data$fg_made / .data$fg_att else NA_real_, + fg_made_0_19 = sum((stat_id == 70) * (yards %between% c(0, 19))), + fg_made_20_29 = sum((stat_id == 70) * (yards %between% c(20, 29))), + fg_made_30_39 = sum((stat_id == 70) * (yards %between% c(30, 39))), + fg_made_40_49 = sum((stat_id == 70) * (yards %between% c(40, 49))), + fg_made_50_59 = sum((stat_id == 70) * (yards %between% c(50, 59))), + fg_made_60_ = sum((stat_id == 70) * (yards > 60)), + fg_missed_0_19 = sum((stat_id == 69) * (yards %between% c(0, 19))), + fg_missed_20_29 = sum((stat_id == 69) * (yards %between% c(20, 29))), + fg_missed_30_39 = sum((stat_id == 69) * (yards %between% c(30, 39))), + fg_missed_40_49 = sum((stat_id == 69) * (yards %between% c(40, 49))), + fg_missed_50_59 = sum((stat_id == 69) * (yards %between% c(50, 59))), + fg_missed_60_ = sum((stat_id == 69) * (yards > 60)), + fg_made_list = fg_list(stat_id, yards, collapse_id = 70), + fg_missed_list = fg_list(stat_id, yards, collapse_id = 69), + fg_blocked_list = fg_list(stat_id, yards, collapse_id = 71), + fg_made_distance = sum((stat_id == 70) * yards), + fg_missed_distance = sum((stat_id == 69) * yards), + fg_blocked_distance = sum((stat_id == 71) * yards), + pat_made = sum(stat_id == 72), + pat_att = sum(stat_id %in% 72:74), + pat_missed = sum(stat_id == 73), + pat_blocked = sum(stat_id == 74), + # avoid 0/0 = NaN + pat_pct = if (.data$pat_att > 0) .data$pat_made / .data$pat_att else NA_real_, + gwfg_made = sum((stat_id == 70) * is_gwfg_attempt), + gwfg_att = sum((stat_id %in% 69:71) * is_gwfg_attempt), + gwfg_missed = sum((stat_id == 69) * is_gwfg_attempt), + gwfg_blocked = sum((stat_id == 71) * is_gwfg_attempt), + gwfg_distance = if (.env$summary_level == "week") sum((stat_id %in% 69:71) * is_gwfg_attempt * yards) else NULL, + gwfg_distance_list = if (.env$summary_level == "season") fg_list(stat_id, yards, collapse_id = 69:71, gwfg = is_gwfg_attempt) else NULL, + ) %>% + dplyr::ungroup() %>% + dplyr::mutate_if( + .predicate = is.character, + .funs = ~ dplyr::na_if(.x, "") + ) %>% + # Join PBP Stats ##################### + dplyr::left_join(passing_stats_from_pbp, by = grp_vctr) %>% + dplyr::left_join(rushing_stats_from_pbp, by = grp_vctr) %>% + dplyr::left_join(receiving_stats_from_pbp, by = grp_vctr) %>% + # relocate epa variables. This could be done with dplyr::relocate + # but we want to be compatible with older dplyr versions + dplyr::select( + "season":"passing_first_downs", + "passing_epa", "passing_cpoe", + "passing_2pt_conversions":"rushing_first_downs", + "rushing_epa", + "rushing_2pt_conversions":"receiving_first_downs", + "receiving_epa", + dplyr::everything() + ) %>% + dplyr::arrange(!!!grp_vars) + + # Apply Player Modifications ##################### + if (stat_type == "player"){ + # need newer version of nflreadr to use load_players + rlang::check_installed("nflreadr (>= 1.3.0)", "to join player information.") + + player_info <- nflreadr::load_players() %>% + dplyr::select( + "player_id" = "gsis_id", + "player_display_name" = "display_name", + # "player_name" = "short_name", + "position", + "position_group", + "headshot_url" = "headshot" + ) + + # load gsis_ids of RBs, FBs and HBs for RACR + racr_ids <- player_info %>% + dplyr::filter(.data$position %in% c("RB", "FB", "HB")) %>% + dplyr::pull("player_id") + + stats <- stats %>% + dplyr::mutate( + pacr = dplyr::case_when( + is.nan(.data$pacr) ~ NA_real_, + .data$passing_air_yards <= 0 ~ 0, + TRUE ~ .data$pacr + ), + racr = dplyr::case_when( + is.nan(.data$racr) ~ NA_real_, + .data$receiving_air_yards == 0 ~ 0, + # following Josh Hermsmeyer's definition, RACR stays < 0 for RBs (and FBs) and is set to + # 0 for Receivers. The list "racr_ids" includes all known RB and FB gsis_ids + .data$receiving_air_yards < 0 & !.data$player_id %in% racr_ids ~ 0, + TRUE ~ .data$racr + ), + # Fantasy ##################### + fantasy_points = + 1 / 25 * .data$passing_yards + + 4 * .data$passing_tds + + -2 * .data$passing_interceptions + + 1 / 10 * (.data$rushing_yards + .data$receiving_yards) + + 6 * (.data$rushing_tds + .data$receiving_tds + .data$special_teams_tds) + + 2 * (.data$passing_2pt_conversions + .data$rushing_2pt_conversions + .data$receiving_2pt_conversions) + + -2 * (.data$sack_fumbles_lost + .data$rushing_fumbles_lost + .data$receiving_fumbles_lost), + + fantasy_points_ppr = .data$fantasy_points + .data$receptions + ) %>% + dplyr::left_join(player_info, by = "player_id") %>% + dplyr::select( + "player_id", + "player_name", + "player_display_name", + "position", + "position_group", + "headshot_url", + dplyr::everything() + ) + } + + stats +} + +# Silence global vars NOTE +# We do this differently here because it's only a bunch of variables +# and the code is more readable +utils::globalVariables(c( + "stat_id", "yards", "more_stats", "team_stats", "team", + "def", "off", "special", "is_gwfg_attempt" +)) + +load_playstats <- function(seasons = nflreadr::most_recent_season()) { + + if(isTRUE(seasons)) seasons <- seq(1999, nflreadr::most_recent_season()) + + stopifnot(is.numeric(seasons), + seasons >= 1999, + seasons <= nflreadr::most_recent_season()) + + urls <- paste0("https://github.com/nflverse/nflverse-pbp/releases/download/playstats/play_stats_", + seasons, ".rds") + + out <- nflreadr::load_from_url(urls, seasons = TRUE, nflverse = FALSE) + + out +} + +fg_list <- function(stat_ids, yards, collapse_id, gwfg = NULL){ + if (is.null(gwfg)) { + paste( + yards[stat_ids == collapse_id], + collapse = ";" + ) + } else { + paste( + yards[stat_ids %in% collapse_id & gwfg == 1L], + collapse = ";" + ) + } +} + +`%0%` <- function(lhs, rhs) if (lhs != 0) lhs else rhs + +`%ifna%` <- function(lhs, rhs) data.table::fifelse(is.na(lhs), rhs, lhs) + +has_id <- function(id, all_ids){ + grepl(paste0(id, ";"), all_ids, fixed = TRUE, useBytes = TRUE) +} + +td_ids <- function(){ + c( + 11, 13, 16, 18, 22, 24, 26, 28, 34, + 36, 46, 48, + # 56, 58, 60, 62, # 56-62 are separately counted in fumble_recovery_tds + 64, 108 + ) +} diff --git a/R/data_documentation.R b/R/data_documentation.R index 913f59e2..cf980197 100644 --- a/R/data_documentation.R +++ b/R/data_documentation.R @@ -62,3 +62,14 @@ #' stat_ids #' } "stat_ids" + +#' NFL Stats Variables +#' +#' @docType data +#' @format A data frame explaining all variables returned by the function +#' [calculate_stats()]. +#' @examples +#' \donttest{ +#' nfl_stats_variables +#' } +"nfl_stats_variables" diff --git a/R/nflfastR-package.R b/R/nflfastR-package.R index 42d405c4..da506a41 100644 --- a/R/nflfastR-package.R +++ b/R/nflfastR-package.R @@ -104,18 +104,18 @@ # roxygen namespace tags. Modify with care! ## usethis namespace: start #' @import dplyr +#' @import fastrmodels #' @importFrom cli rule #' @importFrom curl curl_fetch_memory -#' @importFrom data.table setDT %chin% -#' @import fastrmodels +#' @importFrom data.table setDT %between% %chin% #' @importFrom furrr future_map_chr future_map_dfr future_map #' @importFrom future plan #' @importFrom glue glue glue_sql #' @importFrom janitor clean_names -# @importFrom lifecycle deprecated is_present deprecate_warn +#' @importFrom lifecycle deprecated #' @importFrom mgcv predict.bam #' @importFrom progressr progressor -#' @importFrom rlang .data inform dots_list := +#' @importFrom rlang .data inform dots_list := .env #' @importFrom stats predict na.omit #' @importFrom stringr str_sub str_replace_all str_length str_extract str_detect str_trim str_remove_all str_split str_extract_all #' @importFrom tibble as_tibble tibble diff --git a/data-raw/build_stat_id_df.R b/data-raw/build_stat_id_df.R index fdb0b098..a6af26e6 100644 --- a/data-raw/build_stat_id_df.R +++ b/data-raw/build_stat_id_df.R @@ -1,19 +1,15 @@ -library(magrittr) - -stat_ids <- "http://www.nflgsis.com/gsis/Documentation/Partners/StatIDs_files/sheet001.html" %>% - xml2::read_html() %>% - rvest::html_table(fill = TRUE) %>% - as.data.frame() %>% - dplyr::rename("stat_id" = X1, "name" = X2, "comment" = X3) %>% - dplyr::select(1:3) %>% - dplyr::slice(-1) %>% - dplyr::na_if("") %>% - dplyr::filter(!is.na(comment)) %>% - dplyr::mutate(stat_id = as.integer(stat_id)) %>% - dplyr::group_by(stat_id, name) %>% - dplyr::summarise(comment = paste0(comment, collapse = " ")) %>% - dplyr::ungroup() %>% +stat_ids <- "https://www.nflgsis.com/gsis/Documentation/Partners/StatIDs_files/sheet001.html" |> + xml2::read_html() |> + rvest::html_table(fill = TRUE) |> + as.data.frame() |> + dplyr::rename("stat_id" = X1, "name" = X2, "comment" = X3) |> + dplyr::select(1:3) |> + dplyr::slice(-1) |> + dplyr::mutate(stat_id = as.integer(stat_id)) |> + dplyr::filter(!is.na(stat_id)) |> + dplyr::group_by(stat_id, name) |> + dplyr::summarise(comment = paste0(comment, collapse = " ")) |> + dplyr::ungroup() |> dplyr::mutate(comment = stringr::str_squish(comment)) -# save(stat_ids, file = "data-raw/stat_ids.rda") usethis::use_data(stat_ids, overwrite = TRUE) diff --git a/data-raw/nfl_stats_variables.R b/data-raw/nfl_stats_variables.R new file mode 100644 index 00000000..dc9e4b57 --- /dev/null +++ b/data-raw/nfl_stats_variables.R @@ -0,0 +1,63 @@ +s1 <- calculate_stats(2023, "season", "player") +s2 <- calculate_stats(2023, "week", "player") +s3 <- calculate_stats(2023, "season", "team") +s4 <- calculate_stats(2023, "week", "team") + +n1 <- names(s1) +n2 <- names(s2) +n3 <- names(s3) +n4 <- names(s4) + +setdiff(n1, n2) +setdiff(n2, n1) + +setdiff(n1, n3) +setdiff(n3, n1) + +# tibble::tibble( +# variable = c(n1, n2, n3, n4) %>% unique(), +# description = "" +# ) %>% +# jsonlite::write_json("data-raw/nfl_stats_variables.json", pretty = TRUE) + +nfl_stats_variables <- jsonlite::fromJSON("data-raw/nfl_stats_variables.json") + +usethis::use_data(nfl_stats_variables, overwrite = TRUE) + +s_old_1 <- nflreadr::load_player_stats(2023, "offense") +s_old_2 <- nflreadr::load_player_stats(2023, "defense") +s_old_3 <- nflreadr::load_player_stats(2023, "kicking") +n_old_1 <- names(s_old_1) +n_old_2 <- names(s_old_2) +n_old_3 <- names(s_old_3) + + +# Differences to old offense stats ---------------------------------------- + +# recent_team -> team (recent team in weekly data never made sense) +# interceptions -> passing_interceptions (all passing stats have the passing prefix) +# sacks -> sacks_suffered (to make clear it's not on defensive side) +# sack_yards -> sack_yards_lost (to make clear it's not on defensive side) +# dakota -> not implemented at the moment +setdiff(n_old_1, n2) +setdiff(n2, n_old_1) + +# Differences to old defense stats ---------------------------------------- + +# def_tackles -> there is def_tackles_solo and def_tackles_with_assist +# def_fumble_recovery_own -> fumble_recovery_own (it is not exclusive to defense) +# def_fumble_recovery_yards_own -> fumble_recovery_yards_own (it is not exclusive to defense) +# def_fumble_recovery_opp -> fumble_recovery_opp (it is not exclusive to defense) +# def_fumble_recovery_yards_opp -> fumble_recovery_yards_opp (it is not exclusive to defense) +# def_safety -> def_safeties (we use plural everywhere) +# def_penalty -> penalties (it is not exclusive to defense) +# def_penalty_yards -> penalty_yards (it is not exclusive to defense) +setdiff(n_old_2, n2) +setdiff(n2, n_old_2) + + +# Differences to old kicking stats ---------------------------------------- + +# No differences +setdiff(n_old_3, n2) +setdiff(n2, n_old_3) diff --git a/data-raw/nfl_stats_variables.json b/data-raw/nfl_stats_variables.json new file mode 100644 index 00000000..560446e2 --- /dev/null +++ b/data-raw/nfl_stats_variables.json @@ -0,0 +1,474 @@ +[ + { + "variable": "player_id", + "description": "GSIS player ID. Available if stat_type = 'player'." + }, + { + "variable": "player_name", + "description": "Short player name as listed in play-by-play data. Please keep in mind that this name is not always unique for one player and can change from season to season and sometimes even within a season. Do not group by this variable. Available if stat_type = 'player'." + }, + { + "variable": "player_display_name", + "description": "Full name of player. Available if stat_type = 'player'." + }, + { + "variable": "position", + "description": "Position of player. Available if stat_type = 'player'." + }, + { + "variable": "position_group", + "description": "Position group of player. Available if stat_type = 'player'." + }, + { + "variable": "headshot_url", + "description": "URL to a player headshot image. Available if stat_type = 'player'." + }, + { + "variable": "season", + "description": "The NFL season" + }, + { + "variable": "week", + "description": "The NFL week. Available if summary_level = 'week'" + }, + { + "variable": "season_type", + "description": "One of 'REG', 'POST', or 'REG+POST'" + }, + { + "variable": "recent_team", + "description": "Most recent team player appears in data with. Available if stat_type = 'player' & summary_level = 'season'." + }, + { + "variable": "team", + "description": "Team stats are counted for." + }, + { + "variable": "opponent_team", + "description": "The opponent team in that week. Available if summary_level = 'week'." + }, + { + "variable": "games", + "description": "The number of games where stats were counted. Available if summary_level = 'season'." + }, + { + "variable": "completions", + "description": "The number of completed passes." + }, + { + "variable": "attempts", + "description": "The number of pass attempts as defined by the NFL." + }, + { + "variable": "passing_yards", + "description": "Yards gained on pass plays." + }, + { + "variable": "passing_tds", + "description": "The number of passing touchdowns." + }, + { + "variable": "passing_interceptions", + "description": "The number of interceptions thrown." + }, + { + "variable": "sacks_suffered", + "description": "The Number of times sacked." + }, + { + "variable": "sack_yards_lost", + "description": "Yards lost on sack plays." + }, + { + "variable": "sack_fumbles", + "description": "The number of sacks with a fumble." + }, + { + "variable": "sack_fumbles_lost", + "description": "The number of sacks with a lost fumble." + }, + { + "variable": "passing_air_yards", + "description": "Passing air yards (includes incomplete passes)." + }, + { + "variable": "passing_yards_after_catch", + "description": "Yards after the catch gained on plays in which player was the passer (this is an unofficial stat and may differ slightly between different sources)." + }, + { + "variable": "passing_first_downs", + "description": "First downs on pass attempts." + }, + { + "variable": "passing_epa", + "description": "Total expected points added on pass attempts and sacks. NOTE: this uses the variable `qb_epa`, which gives QB credit for EPA for up to the point where a receiver lost a fumble after a completed catch and makes EPA work more like passing yards on plays with fumbles." + }, + { + "variable": "passing_cpoe", + "description": "Completion percentage over expectation" + }, + { + "variable": "passing_2pt_conversions", + "description": "Two-point conversion passes." + }, + { + "variable": "pacr", + "description": "Passing Air Conversion Ratio. PACR = `passing_yards` / `passing_air_yards`. Available if stat_type = 'player'." + }, + { + "variable": "carries", + "description": "The number of official rush attempts (incl. scrambles and kneel downs). Rushes after a lateral reception don't count as carry." + }, + { + "variable": "rushing_yards", + "description": "Yards gained when rushing with the ball (incl. scrambles and kneel downs). Also includes yards gained after obtaining a lateral on a play that started with a rushing attempt." + }, + { + "variable": "rushing_tds", + "description": "The number of rushing touchdowns (incl. scrambles). Also includes touchdowns after obtaining a lateral on a play that started with a rushing attempt." + }, + { + "variable": "rushing_fumbles", + "description": "The number of rushes with a fumble." + }, + { + "variable": "rushing_fumbles_lost", + "description": "The number of rushes with a lost fumble." + }, + { + "variable": "rushing_first_downs", + "description": "First downs on rush attempts (incl. scrambles)." + }, + { + "variable": "rushing_epa", + "description": "Expected points added on rush attempts (incl. scrambles and kneel downs)." + }, + { + "variable": "rushing_2pt_conversions", + "description": "Two-point conversion rushes." + }, + { + "variable": "receptions", + "description": "The number of pass receptions. Lateral receptions officially don't count as reception." + }, + { + "variable": "targets", + "description": "The number of pass plays where the player was the targeted receiver." + }, + { + "variable": "receiving_yards", + "description": "Yards gained after a pass reception. Includes yards gained after receiving a lateral on a play that started as a pass play." + }, + { + "variable": "receiving_tds", + "description": "The number of touchdowns following a pass reception. Also includes touchdowns after receiving a lateral on a play that started as a pass play." + }, + { + "variable": "receiving_fumbles", + "description": "The number of fumbles after a pass reception." + }, + { + "variable": "receiving_fumbles_lost", + "description": "The number of fumbles lost after a pass reception." + }, + { + "variable": "receiving_air_yards", + "description": "Receiving air yards (incl. incomplete passes)." + }, + { + "variable": "receiving_yards_after_catch", + "description": "Yards after the catch gained on plays in which player was receiver (this is an unofficial stat and may differ slightly between different sources)." + }, + { + "variable": "receiving_first_downs", + "description": "First downs on receptions." + }, + { + "variable": "receiving_epa", + "description": "Expected points added on receptions." + }, + { + "variable": "receiving_2pt_conversions", + "description": "Two-point conversion receptions." + }, + { + "variable": "racr", + "description": "Receiver Air Conversion Ratio. RACR = `receiving_yards` / `receiving_air_yards`. Available if stat_type = 'player'." + }, + { + "variable": "target_share", + "description": "The share of targets of the player in all targets of his team. Available if stat_type = 'player'." + }, + { + "variable": "air_yards_share", + "description": "The share of receiving_air_yards of the player in all air_yards of his team. Available if stat_type = 'player'." + }, + { + "variable": "wopr", + "description": "Weighted Opportunity Rating. WOPR = 1.5 × `target_share` + 0.7 × `air_yards_share`. Available if stat_type = 'player'." + }, + { + "variable": "special_teams_tds", + "description": "The number of touchdowns scored in special teams plays." + }, + { + "variable": "def_tackles_solo", + "description": "Solo tackles." + }, + { + "variable": "def_tackles_with_assist", + "description": "Tackles where another player assisted." + }, + { + "variable": "def_tackle_assists", + "description": "Assist to another player's tackle." + }, + { + "variable": "def_tackles_for_loss", + "description": "Tackles for loss." + }, + { + "variable": "def_tackles_for_loss_yards", + "description": "Yards lost by the opposing team through a tackle." + }, + { + "variable": "def_fumbles_forced", + "description": "Forced fumbles." + }, + { + "variable": "def_sacks", + "description": "Number of Sacks." + }, + { + "variable": "def_sack_yards", + "description": "Yards lost by the opposing team through a sack." + }, + { + "variable": "def_qb_hits", + "description": "Number of QB hits" + }, + { + "variable": "def_interceptions", + "description": "Interceptions caught." + }, + { + "variable": "def_interception_yards", + "description": "Yards gained after interceptions." + }, + { + "variable": "def_pass_defended", + "description": "Number of defended passes." + }, + { + "variable": "def_tds", + "description": "Defensive touchdowns." + }, + { + "variable": "def_fumbles", + "description": "Number of fumbles while playing on defense." + }, + { + "variable": "def_safeties", + "description": "Tackles that resulted in a safety." + }, + { + "variable": "misc_yards", + "description": "Yardage gained/lost that doesn't fall into any other category. Examples are blocked field goals or blocked punts." + }, + { + "variable": "fumble_recovery_own", + "description": "Recovered fumbles where the ball was fumbled by own team." + }, + { + "variable": "fumble_recovery_yards_own", + "description": "Yardage gained/lost by a player after he recovered a fumble by his own team. Includes yardage gained/lost where a team mate recovered a fumble and lateraled the ball to the player." + }, + { + "variable": "fumble_recovery_opp", + "description": "Recovered fumbles where the ball was fumbled by opposing team." + }, + { + "variable": "fumble_recovery_yards_opp", + "description": "Yardage gained/lost by a player after he recovered a fumble by the opposing team. Includes yardage gained/lost where a team mate recovered a fumble and lateraled the ball to the player." + }, + { + "variable": "fumble_recovery_tds", + "description": "Touchdowns scored after a fumble recovery. This can be in any unit. And both the own team and the opposing team can have fumbled the ball initially. Includes touchdowns where a team mate recovered a fumble and lateraled the ball to the player." + }, + { + "variable": "penalties", + "description": "Penalties caused." + }, + { + "variable": "penalty_yards", + "description": "Yardage lost through penalties." + }, + { + "variable": "timeouts", + "description": "Number of timeouts taken by team. Available if stat_type = 'team'." + }, + { + "variable": "punt_returns", + "description": "Number of punts returned." + }, + { + "variable": "punt_return_yards", + "description": "Yardage gained/lost by a player during a punt return." + }, + { + "variable": "kickoff_returns", + "description": "Number of punts returned." + }, + { + "variable": "kickoff_return_yards", + "description": "Yardage gained/lost by a player during a kickoff return." + }, + { + "variable": "fg_made", + "description": "Successful field goal attempts." + }, + { + "variable": "fg_att", + "description": "Attempted field goals." + }, + { + "variable": "fg_missed", + "description": "Missed field goals." + }, + { + "variable": "fg_blocked", + "description": "Attempted field goals that were blocked." + }, + { + "variable": "fg_long", + "description": "Distance of longest made field goal." + }, + { + "variable": "fg_pct", + "description": "Percentage of successful field goal attempts." + }, + { + "variable": "fg_made_0_19", + "description": "Successful field goal attempts where distance was between 0 and 19 yards." + }, + { + "variable": "fg_made_20_29", + "description": "Successful field goal attempts where distance was between 20 and 29 yards." + }, + { + "variable": "fg_made_30_39", + "description": "Successful field goal attempts where distance was between 30 and 39 yards." + }, + { + "variable": "fg_made_40_49", + "description": "Successful field goal attempts where distance was between 40 and 49 yards." + }, + { + "variable": "fg_made_50_59", + "description": "Successful field goal attempts where distance was between 50 and 59 yards." + }, + { + "variable": "fg_made_60_", + "description": "Successful field goal attempts where distance was 60+ yards." + }, + { + "variable": "fg_missed_0_19", + "description": "Missed field goal attempts where distance was between 0 and 19 yards." + }, + { + "variable": "fg_missed_20_29", + "description": "Missed field goal attempts where distance was between 20 and 29 yards." + }, + { + "variable": "fg_missed_30_39", + "description": "Missed field goal attempts where distance was between 30 and 39 yards." + }, + { + "variable": "fg_missed_40_49", + "description": "Missed field goal attempts where distance was between 40 and 49 yards." + }, + { + "variable": "fg_missed_50_59", + "description": "Missed field goal attempts where distance was between 50 and 59 yards." + }, + { + "variable": "fg_missed_60_", + "description": "Missed field goal attempts where distance was 60+ yards." + }, + { + "variable": "fg_made_list", + "description": "Distances of all successful field goal attempts." + }, + { + "variable": "fg_missed_list", + "description": "Distances of all missed field goal attempts." + }, + { + "variable": "fg_blocked_list", + "description": "Distances of all blocked field goal attempts." + }, + { + "variable": "fg_made_distance", + "description": "Sum of distances of all made field goals." + }, + { + "variable": "fg_missed_distance", + "description": "Sum of distances of all missed field goals." + }, + { + "variable": "fg_blocked_distance", + "description": "Sum of distances of all blocked field goals." + }, + { + "variable": "pat_made", + "description": "Successful extra point attempts." + }, + { + "variable": "pat_att", + "description": "Attempted extra points." + }, + { + "variable": "pat_missed", + "description": "Missed extra points." + }, + { + "variable": "pat_blocked", + "description": "Extra points blocked by opponent." + }, + { + "variable": "pat_pct", + "description": "Percentage of successful extra point attempts." + }, + { + "variable": "gwfg_made", + "description": "Successful game winning field goal attempts." + }, + { + "variable": "gwfg_att", + "description": "Attempted game winning field goals." + }, + { + "variable": "gwfg_missed", + "description": "Missed game winning field goals." + }, + { + "variable": "gwfg_blocked", + "description": "Game winning field goal attempts blocked by opponent." + }, + { + "variable": "gwfg_distance", + "description": "Distance of game winning field goal attempt. Available if summary_level = 'week'." + }, + { + "variable": "gwfg_distance_list", + "description": "Distances of game winning field goal attempts. Available if summary_level = 'season'." + }, + { + "variable": "fantasy_points", + "description": "Standard fantasy points." + }, + { + "variable": "fantasy_points_ppr", + "description": "PPR fantasy points." + } +] diff --git a/data/nfl_stats_variables.rda b/data/nfl_stats_variables.rda new file mode 100644 index 00000000..1be786a2 Binary files /dev/null and b/data/nfl_stats_variables.rda differ diff --git a/data/stat_ids.rda b/data/stat_ids.rda index e4089847..ed941c89 100644 Binary files a/data/stat_ids.rda and b/data/stat_ids.rda differ diff --git a/man/calculate_player_stats.Rd b/man/calculate_player_stats.Rd index bedce167..92da8797 100644 --- a/man/calculate_player_stats.Rd +++ b/man/calculate_player_stats.Rd @@ -88,6 +88,11 @@ between different sources).} } } \description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} + +This function was deprecated because we have a new, much better and +harmonized approach in \code{\link[=calculate_stats]{calculate_stats()}}. + Build columns that aggregate official passing, rushing, and receiving stats either at the game level or at the level of the entire data frame passed. } @@ -108,3 +113,4 @@ dplyr::glimpse(overall) The function \code{\link[=load_player_stats]{load_player_stats()}} and the corresponding examples on \href{https://www.nflfastr.com/articles/nflfastR.html#example-11-replicating-official-stats}{the nflfastR website} } +\keyword{internal} diff --git a/man/calculate_player_stats_def.Rd b/man/calculate_player_stats_def.Rd index 6d8faf26..dd3995dc 100644 --- a/man/calculate_player_stats_def.Rd +++ b/man/calculate_player_stats_def.Rd @@ -18,6 +18,11 @@ for the entire Data frame.} A data frame of defensive player stats. See dictionary (# TODO) } \description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} + +This function was deprecated because we have a new, much better and +harmonized approach in \code{\link[=calculate_stats]{calculate_stats()}}. + Build columns that aggregate official defense stats either at the game level or at the level of the entire data frame passed. } @@ -39,3 +44,4 @@ try({# to avoid CRAN test problems The function \code{\link[=load_player_stats]{load_player_stats()}} and the corresponding examples on \href{https://www.nflfastr.com/articles/nflfastR.html#example-11-replicating-official-stats}{the nflfastR website} } +\keyword{internal} diff --git a/man/calculate_player_stats_kicking.Rd b/man/calculate_player_stats_kicking.Rd index b2dce1ec..0d28557c 100644 --- a/man/calculate_player_stats_kicking.Rd +++ b/man/calculate_player_stats_kicking.Rd @@ -17,6 +17,11 @@ the entire data frame in argument \code{pbp}.} a dataframe of kicking stats } \description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} + +This function was deprecated because we have a new, much better and +harmonized approach in \code{\link[=calculate_stats]{calculate_stats()}}. + Build columns that aggregate kicking stats at the game level. } \examples{ @@ -35,3 +40,4 @@ try({# to avoid CRAN test problems \seealso{ \url{https://nflreadr.nflverse.com/reference/load_player_stats.html} for the nflreadr function to download this from repo (\code{stat_type = "kicking"}) } +\keyword{internal} diff --git a/man/calculate_stats.Rd b/man/calculate_stats.Rd new file mode 100644 index 00000000..9b6ac314 --- /dev/null +++ b/man/calculate_stats.Rd @@ -0,0 +1,45 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/calculate_stats.R +\name{calculate_stats} +\alias{calculate_stats} +\title{Calculate NFL Stats} +\usage{ +calculate_stats( + seasons = nflreadr::most_recent_season(), + summary_level = c("season", "week"), + stat_type = c("player", "team"), + season_type = c("REG", "POST", "REG+POST") +) +} +\arguments{ +\item{seasons}{A numeric vector of 4-digit years associated with given NFL +seasons - defaults to latest season. If set to TRUE, returns all available +data since 1999.} + +\item{summary_level}{Summarize stats by \code{"season"} or \code{"week"}.} + +\item{stat_type}{Calculate \code{"player"} level stats or \code{"team"} level stats.} + +\item{season_type}{One of \code{"REG"}, \code{"POST"}, or \code{"REG+POST"}. Filters +data to regular season ("REG"), post season ("POST") or keeps all data.} +} +\value{ +A tibble of player/team stats summarized by season/week. +} +\description{ +Compute various NFL stats based off nflverse Play-by-Play data. +} +\examples{ +\donttest{ +try({# to avoid CRAN test problems +stats <- calculate_stats(2023, "season", "player") +dplyr::glimpse(stats) +}) +} +} +\seealso{ +\link{nfl_stats_variables} for a description of all variables. + +\url{https://www.nflfastr.com/articles/stats_variables.html} for a searchable +table of the stats variable descriptions. +} diff --git a/man/figures/lifecycle-deprecated.svg b/man/figures/lifecycle-deprecated.svg index 4baaee01..b61c57c3 100644 --- a/man/figures/lifecycle-deprecated.svg +++ b/man/figures/lifecycle-deprecated.svg @@ -1 +1,21 @@ -lifecyclelifecycledeprecateddeprecated \ No newline at end of file + + lifecycle: deprecated + + + + + + + + + + + + + + + lifecycle + + deprecated + + diff --git a/man/figures/lifecycle-experimental.svg b/man/figures/lifecycle-experimental.svg index d1d060e9..5d88fc2c 100644 --- a/man/figures/lifecycle-experimental.svg +++ b/man/figures/lifecycle-experimental.svg @@ -1 +1,21 @@ -lifecyclelifecycleexperimentalexperimental \ No newline at end of file + + lifecycle: experimental + + + + + + + + + + + + + + + lifecycle + + experimental + + diff --git a/man/figures/lifecycle-stable.svg b/man/figures/lifecycle-stable.svg index e015dc81..9bf21e76 100644 --- a/man/figures/lifecycle-stable.svg +++ b/man/figures/lifecycle-stable.svg @@ -1 +1,29 @@ -lifecyclelifecyclestablestable \ No newline at end of file + + lifecycle: stable + + + + + + + + + + + + + + + + lifecycle + + + + stable + + + diff --git a/man/figures/lifecycle-superseded.svg b/man/figures/lifecycle-superseded.svg index 75f24f55..db8d757f 100644 --- a/man/figures/lifecycle-superseded.svg +++ b/man/figures/lifecycle-superseded.svg @@ -1 +1,21 @@ - lifecyclelifecyclesupersededsuperseded \ No newline at end of file + + lifecycle: superseded + + + + + + + + + + + + + + + lifecycle + + superseded + + diff --git a/man/nfl_stats_variables.Rd b/man/nfl_stats_variables.Rd new file mode 100644 index 00000000..8c8cf6b9 --- /dev/null +++ b/man/nfl_stats_variables.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data_documentation.R +\docType{data} +\name{nfl_stats_variables} +\alias{nfl_stats_variables} +\title{NFL Stats Variables} +\format{ +A data frame explaining all variables returned by the function +\code{\link[=calculate_stats]{calculate_stats()}}. +} +\usage{ +nfl_stats_variables +} +\description{ +NFL Stats Variables +} +\examples{ +\donttest{ +nfl_stats_variables +} +} +\keyword{datasets} diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index 5a012fda..bb85dba2 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -84,6 +84,8 @@ navbar: href: articles/beginners_guide.html - text: Field Descriptions href: articles/field_descriptions.html + - text: Stats Variable Descriptions + href: articles/stats_variables.html - text: nflfastR models href: https://www.opensourcefootball.com/posts/2020-09-28-nflfastr-ep-wp-and-cp-models/ - text: Open Source Football @@ -130,13 +132,7 @@ reference: contents: - save_raw_pbp - missing_raw_pbp - - calculate_expected_points - - calculate_win_probability - - calculate_player_stats - - calculate_player_stats_def - - calculate_player_stats_kicking - - calculate_standings - - calculate_series_conversion_rates + - starts_with("calculate_") - report - title: Documentation contents: @@ -144,6 +140,7 @@ reference: - teams_colors_logos - field_descriptions - stat_ids + - nfl_stats_variables - title: Lower Level Functions desc: > These functions are wrapped in the above listed main functions and diff --git a/tests/testthat/_snaps/stats/calculate_stats.md b/tests/testthat/_snaps/stats/calculate_stats.md new file mode 100644 index 00000000..4a2db8d6 --- /dev/null +++ b/tests/testthat/_snaps/stats/calculate_stats.md @@ -0,0 +1,70 @@ +# calculate_stats works + + { + "type": "character", + "attributes": { + "names": { + "type": "character", + "attributes": {}, + "value": ["player_id", "player_name", "player_display_name", "position", "position_group", "headshot_url", "season", "season_type", "recent_team", "games", "completions", "attempts", "passing_yards", "passing_tds", "passing_interceptions", "sacks_suffered", "sack_yards_lost", "sack_fumbles", "sack_fumbles_lost", "passing_air_yards", "passing_yards_after_catch", "passing_first_downs", "passing_epa", "passing_cpoe", "passing_2pt_conversions", "pacr", "carries", "rushing_yards", "rushing_tds", "rushing_fumbles", "rushing_fumbles_lost", "rushing_first_downs", "rushing_epa", "rushing_2pt_conversions", "receptions", "targets", "receiving_yards", "receiving_tds", "receiving_fumbles", "receiving_fumbles_lost", "receiving_air_yards", "receiving_yards_after_catch", "receiving_first_downs", "receiving_epa", "receiving_2pt_conversions", "racr", "target_share", "air_yards_share", "wopr", "special_teams_tds", "def_tackles_solo", "def_tackles_with_assist", "def_tackle_assists", "def_tackles_for_loss", "def_tackles_for_loss_yards", "def_fumbles_forced", "def_sacks", "def_sack_yards", "def_qb_hits", "def_interceptions", "def_interception_yards", "def_pass_defended", "def_tds", "def_fumbles", "def_safeties", "misc_yards", "fumble_recovery_own", "fumble_recovery_yards_own", "fumble_recovery_opp", "fumble_recovery_yards_opp", "fumble_recovery_tds", "penalties", "penalty_yards", "punt_returns", "punt_return_yards", "kickoff_returns", "kickoff_return_yards", "fg_made", "fg_att", "fg_missed", "fg_blocked", "fg_long", "fg_pct", "fg_made_0_19", "fg_made_20_29", "fg_made_30_39", "fg_made_40_49", "fg_made_50_59", "fg_made_60_", "fg_missed_0_19", "fg_missed_20_29", "fg_missed_30_39", "fg_missed_40_49", "fg_missed_50_59", "fg_missed_60_", "fg_made_list", "fg_missed_list", "fg_blocked_list", "fg_made_distance", "fg_missed_distance", "fg_blocked_distance", "pat_made", "pat_att", "pat_missed", "pat_blocked", "pat_pct", "gwfg_made", "gwfg_att", "gwfg_missed", "gwfg_blocked", "gwfg_distance_list", "fantasy_points", "fantasy_points_ppr"] + } + }, + "value": ["character", "character", "character", "character", "character", "character", "integer", "character", "character", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "numeric", "integer", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "numeric", "numeric", "numeric", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "character", "character", "character", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "integer", "integer", "integer", "character", "numeric", "numeric"] + } + +--- + + { + "type": "character", + "attributes": { + "names": { + "type": "character", + "attributes": {}, + "value": ["player_id", "player_name", "player_display_name", "position", "position_group", "headshot_url", "season", "week", "season_type", "team", "opponent_team", "completions", "attempts", "passing_yards", "passing_tds", "passing_interceptions", "sacks_suffered", "sack_yards_lost", "sack_fumbles", "sack_fumbles_lost", "passing_air_yards", "passing_yards_after_catch", "passing_first_downs", "passing_epa", "passing_cpoe", "passing_2pt_conversions", "pacr", "carries", "rushing_yards", "rushing_tds", "rushing_fumbles", "rushing_fumbles_lost", "rushing_first_downs", "rushing_epa", "rushing_2pt_conversions", "receptions", "targets", "receiving_yards", "receiving_tds", "receiving_fumbles", "receiving_fumbles_lost", "receiving_air_yards", "receiving_yards_after_catch", "receiving_first_downs", "receiving_epa", "receiving_2pt_conversions", "racr", "target_share", "air_yards_share", "wopr", "special_teams_tds", "def_tackles_solo", "def_tackles_with_assist", "def_tackle_assists", "def_tackles_for_loss", "def_tackles_for_loss_yards", "def_fumbles_forced", "def_sacks", "def_sack_yards", "def_qb_hits", "def_interceptions", "def_interception_yards", "def_pass_defended", "def_tds", "def_fumbles", "def_safeties", "misc_yards", "fumble_recovery_own", "fumble_recovery_yards_own", "fumble_recovery_opp", "fumble_recovery_yards_opp", "fumble_recovery_tds", "penalties", "penalty_yards", "punt_returns", "punt_return_yards", "kickoff_returns", "kickoff_return_yards", "fg_made", "fg_att", "fg_missed", "fg_blocked", "fg_long", "fg_pct", "fg_made_0_19", "fg_made_20_29", "fg_made_30_39", "fg_made_40_49", "fg_made_50_59", "fg_made_60_", "fg_missed_0_19", "fg_missed_20_29", "fg_missed_30_39", "fg_missed_40_49", "fg_missed_50_59", "fg_missed_60_", "fg_made_list", "fg_missed_list", "fg_blocked_list", "fg_made_distance", "fg_missed_distance", "fg_blocked_distance", "pat_made", "pat_att", "pat_missed", "pat_blocked", "pat_pct", "gwfg_made", "gwfg_att", "gwfg_missed", "gwfg_blocked", "gwfg_distance", "fantasy_points", "fantasy_points_ppr"] + } + }, + "value": ["character", "character", "character", "character", "character", "character", "integer", "integer", "character", "character", "character", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "numeric", "integer", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "numeric", "numeric", "numeric", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "character", "character", "character", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "integer", "integer", "integer", "integer", "numeric", "numeric"] + } + +--- + + { + "type": "character", + "attributes": { + "names": { + "type": "character", + "attributes": {}, + "value": ["season", "team", "season_type", "games", "completions", "attempts", "passing_yards", "passing_tds", "passing_interceptions", "sacks_suffered", "sack_yards_lost", "sack_fumbles", "sack_fumbles_lost", "passing_air_yards", "passing_yards_after_catch", "passing_first_downs", "passing_epa", "passing_cpoe", "passing_2pt_conversions", "carries", "rushing_yards", "rushing_tds", "rushing_fumbles", "rushing_fumbles_lost", "rushing_first_downs", "rushing_epa", "rushing_2pt_conversions", "receptions", "targets", "receiving_yards", "receiving_tds", "receiving_fumbles", "receiving_fumbles_lost", "receiving_air_yards", "receiving_yards_after_catch", "receiving_first_downs", "receiving_epa", "receiving_2pt_conversions", "special_teams_tds", "def_tackles_solo", "def_tackles_with_assist", "def_tackle_assists", "def_tackles_for_loss", "def_tackles_for_loss_yards", "def_fumbles_forced", "def_sacks", "def_sack_yards", "def_qb_hits", "def_interceptions", "def_interception_yards", "def_pass_defended", "def_tds", "def_fumbles", "def_safeties", "misc_yards", "fumble_recovery_own", "fumble_recovery_yards_own", "fumble_recovery_opp", "fumble_recovery_yards_opp", "fumble_recovery_tds", "penalties", "penalty_yards", "timeouts", "punt_returns", "punt_return_yards", "kickoff_returns", "kickoff_return_yards", "fg_made", "fg_att", "fg_missed", "fg_blocked", "fg_long", "fg_pct", "fg_made_0_19", "fg_made_20_29", "fg_made_30_39", "fg_made_40_49", "fg_made_50_59", "fg_made_60_", "fg_missed_0_19", "fg_missed_20_29", "fg_missed_30_39", "fg_missed_40_49", "fg_missed_50_59", "fg_missed_60_", "fg_made_list", "fg_missed_list", "fg_blocked_list", "fg_made_distance", "fg_missed_distance", "fg_blocked_distance", "pat_made", "pat_att", "pat_missed", "pat_blocked", "pat_pct", "gwfg_made", "gwfg_att", "gwfg_missed", "gwfg_blocked", "gwfg_distance_list"] + } + }, + "value": ["integer", "character", "character", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "character", "character", "character", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "integer", "integer", "integer", "character"] + } + +--- + + { + "type": "character", + "attributes": { + "names": { + "type": "character", + "attributes": {}, + "value": ["season", "week", "team", "season_type", "opponent_team", "completions", "attempts", "passing_yards", "passing_tds", "passing_interceptions", "sacks_suffered", "sack_yards_lost", "sack_fumbles", "sack_fumbles_lost", "passing_air_yards", "passing_yards_after_catch", "passing_first_downs", "passing_epa", "passing_cpoe", "passing_2pt_conversions", "carries", "rushing_yards", "rushing_tds", "rushing_fumbles", "rushing_fumbles_lost", "rushing_first_downs", "rushing_epa", "rushing_2pt_conversions", "receptions", "targets", "receiving_yards", "receiving_tds", "receiving_fumbles", "receiving_fumbles_lost", "receiving_air_yards", "receiving_yards_after_catch", "receiving_first_downs", "receiving_epa", "receiving_2pt_conversions", "special_teams_tds", "def_tackles_solo", "def_tackles_with_assist", "def_tackle_assists", "def_tackles_for_loss", "def_tackles_for_loss_yards", "def_fumbles_forced", "def_sacks", "def_sack_yards", "def_qb_hits", "def_interceptions", "def_interception_yards", "def_pass_defended", "def_tds", "def_fumbles", "def_safeties", "misc_yards", "fumble_recovery_own", "fumble_recovery_yards_own", "fumble_recovery_opp", "fumble_recovery_yards_opp", "fumble_recovery_tds", "penalties", "penalty_yards", "timeouts", "punt_returns", "punt_return_yards", "kickoff_returns", "kickoff_return_yards", "fg_made", "fg_att", "fg_missed", "fg_blocked", "fg_long", "fg_pct", "fg_made_0_19", "fg_made_20_29", "fg_made_30_39", "fg_made_40_49", "fg_made_50_59", "fg_made_60_", "fg_missed_0_19", "fg_missed_20_29", "fg_missed_30_39", "fg_missed_40_49", "fg_missed_50_59", "fg_missed_60_", "fg_made_list", "fg_missed_list", "fg_blocked_list", "fg_made_distance", "fg_missed_distance", "fg_blocked_distance", "pat_made", "pat_att", "pat_missed", "pat_blocked", "pat_pct", "gwfg_made", "gwfg_att", "gwfg_missed", "gwfg_blocked", "gwfg_distance"] + } + }, + "value": ["integer", "integer", "character", "character", "character", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "character", "character", "character", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "integer", "integer", "integer", "integer"] + } + +--- + + { + "type": "character", + "attributes": { + "names": { + "type": "character", + "attributes": {}, + "value": ["player_id", "player_name", "player_display_name", "position", "position_group", "headshot_url", "season", "week", "season_type", "team", "opponent_team", "completions", "attempts", "passing_yards", "passing_tds", "passing_interceptions", "sacks_suffered", "sack_yards_lost", "sack_fumbles", "sack_fumbles_lost", "passing_air_yards", "passing_yards_after_catch", "passing_first_downs", "passing_epa", "passing_cpoe", "passing_2pt_conversions", "pacr", "carries", "rushing_yards", "rushing_tds", "rushing_fumbles", "rushing_fumbles_lost", "rushing_first_downs", "rushing_epa", "rushing_2pt_conversions", "receptions", "targets", "receiving_yards", "receiving_tds", "receiving_fumbles", "receiving_fumbles_lost", "receiving_air_yards", "receiving_yards_after_catch", "receiving_first_downs", "receiving_epa", "receiving_2pt_conversions", "racr", "target_share", "air_yards_share", "wopr", "special_teams_tds", "def_tackles_solo", "def_tackles_with_assist", "def_tackle_assists", "def_tackles_for_loss", "def_tackles_for_loss_yards", "def_fumbles_forced", "def_sacks", "def_sack_yards", "def_qb_hits", "def_interceptions", "def_interception_yards", "def_pass_defended", "def_tds", "def_fumbles", "def_safeties", "misc_yards", "fumble_recovery_own", "fumble_recovery_yards_own", "fumble_recovery_opp", "fumble_recovery_yards_opp", "fumble_recovery_tds", "penalties", "penalty_yards", "punt_returns", "punt_return_yards", "kickoff_returns", "kickoff_return_yards", "fg_made", "fg_att", "fg_missed", "fg_blocked", "fg_long", "fg_pct", "fg_made_0_19", "fg_made_20_29", "fg_made_30_39", "fg_made_40_49", "fg_made_50_59", "fg_made_60_", "fg_missed_0_19", "fg_missed_20_29", "fg_missed_30_39", "fg_missed_40_49", "fg_missed_50_59", "fg_missed_60_", "fg_made_list", "fg_missed_list", "fg_blocked_list", "fg_made_distance", "fg_missed_distance", "fg_blocked_distance", "pat_made", "pat_att", "pat_missed", "pat_blocked", "pat_pct", "gwfg_made", "gwfg_att", "gwfg_missed", "gwfg_blocked", "gwfg_distance", "fantasy_points", "fantasy_points_ppr"] + } + }, + "value": ["character", "character", "character", "character", "character", "character", "integer", "integer", "character", "character", "character", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "numeric", "integer", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "numeric", "numeric", "numeric", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "character", "character", "character", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "numeric", "integer", "integer", "integer", "integer", "integer", "numeric", "numeric"] + } + diff --git a/tests/testthat/test-calculate_stats.R b/tests/testthat/test-calculate_stats.R new file mode 100644 index 00000000..ed94153b --- /dev/null +++ b/tests/testthat/test-calculate_stats.R @@ -0,0 +1,39 @@ +test_that("calculate_stats works", { + skip_on_cran() + skip_if_offline("github.com") + + s1 <- calculate_stats(seasons = 2023, summary_level = "season", stat_type = "player") + s2 <- calculate_stats(seasons = 2023, summary_level = "week", stat_type = "player") + s3 <- calculate_stats(seasons = 2023, summary_level = "season", stat_type = "team") + s4 <- calculate_stats(seasons = 2023, summary_level = "week", stat_type = "team") + s5 <- calculate_stats(seasons = 2023, summary_level = "week", stat_type = "player", season_type = "POST") + + names_and_types_s1 <- vapply(s1, class, FUN.VALUE = character(1L)) + names_and_types_s2 <- vapply(s2, class, FUN.VALUE = character(1L)) + names_and_types_s3 <- vapply(s3, class, FUN.VALUE = character(1L)) + names_and_types_s4 <- vapply(s4, class, FUN.VALUE = character(1L)) + names_and_types_s5 <- vapply(s5, class, FUN.VALUE = character(1L)) + + var_names <- nflfastR::nfl_stats_variables$variable + + # Make sure variable names are listed in nflfastR::nfl_stats_variables$variable + expect_in(names(names_and_types_s1), var_names) + expect_in(names(names_and_types_s2), var_names) + expect_in(names(names_and_types_s3), var_names) + expect_in(names(names_and_types_s4), var_names) + expect_in(names(names_and_types_s5), var_names) + + # Weak row number test + expect_gt(nrow(s1), 1900) + expect_gt(nrow(s2), 17500) + expect_identical(nrow(s3), 32L) + expect_gt(nrow(s4), 500) + expect_gt(nrow(s5), 800) + + # Snapshot variable types and names + expect_snapshot_value(names_and_types_s1, style = "json2", variant = "stats") + expect_snapshot_value(names_and_types_s2, style = "json2", variant = "stats") + expect_snapshot_value(names_and_types_s3, style = "json2", variant = "stats") + expect_snapshot_value(names_and_types_s4, style = "json2", variant = "stats") + expect_snapshot_value(names_and_types_s5, style = "json2", variant = "stats") +}) diff --git a/vignettes/stats_variables.Rmd b/vignettes/stats_variables.Rmd new file mode 100644 index 00000000..c2c188df --- /dev/null +++ b/vignettes/stats_variables.Rmd @@ -0,0 +1,37 @@ +--- +title: "NFL Stats Variables" +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + echo = FALSE, + comment = "#>" +) + +with_dt <- requireNamespace("DT") + +``` + +Below you will find a table that lists and explains all the variables available in `calculate_stats()`. Compared to the old `calculate_player_stats*()` functions that have been deprecated, practically all variables (and their names) have been preserved. However, there are a few differences. These are + +- `recent_team`: renamed to `team` (recent team in weekly data never made sense) +- `interceptions`: renamed to `passing_interceptions` (all passing stats have the passing prefix) +- `sacks`: renamed to `sacks_suffered` (to make clear it's not on defensive side) +- `sack_yards`: renamed to `sack_yards_lost` (to make clear it's not on defensive side) +- `dakota`: not implemented at the moment +- `def_tackles`: there is `def_tackles_solo` and `def_tackles_with_assist` +- `def_fumble_recovery_own`: renamed to `fumble_recovery_own` (it is not exclusive to defense) +- `def_fumble_recovery_yards_own`: renamed to `fumble_recovery_yards_own` (it is not exclusive to defense) +- `def_fumble_recovery_opp`: renamed to `fumble_recovery_opp` (it is not exclusive to defense) +- `def_fumble_recovery_yards_opp`: renamed to `fumble_recovery_yards_opp` (it is not exclusive to defense) +- `def_safety`: renamed to `def_safeties` (we use plural everywhere) +- `def_penalty`: renamed to `penalties` (it is not exclusive to defense) +- `def_penalty_yards`: renamed to `penalty_yards` (it is not exclusive to defense) + +```{r eval = with_dt} +DT::datatable(nflfastR::nfl_stats_variables,options = list(scrollX = TRUE, pageLength = 25), filter = "top", rownames = FALSE) +``` + +```{r eval = !with_dt} +knitr::kable(nflfastR::nfl_stats_variables) +```