Skip to content

Commit

Permalink
6 request season stats for all positions (#9)
Browse files Browse the repository at this point in the history
- [x] Get play-by-play data with `nflfastR`
  - Uses helper functions to either use a stored database of using `nflreadr::load_pbp()`
  - Obtaining play-by-play data function available in `R/get_pbp_data.R`
- [x] Parse data to create season stats for QB/RB/WR/TE
  - Stats are pulled with functions in `R/get_pbp_stats`
  - Stats available for season or multiple seasons
  - Stats available for all weeks or a range of weeks with `week_min` and `week_max`
  - Stats are used to calculate fantasy points for common scoring formats with `R/calculate_fpts.R`
    - Including: 4/6 PT Pass TD, Standard, Half PPR, Full PPR
  - Stats can be pulled for QB/RB/WR/TE
- [x] Exports to dataframe
- [x] Include documentation for each function using `Roxygen2`
  - Documentation includes title, description, details, params, seealso, and list of output vars (data dictionary)
- [x] Code styled with Tidyverse style
- [x] Added tidyverse linter with `.lintr` file to clean up code 
- [x] Created test directory to build future tests with `testthat`
  • Loading branch information
nolmacdonald authored Oct 5, 2024
1 parent a42297f commit 5967869
Show file tree
Hide file tree
Showing 57 changed files with 4,989 additions and 710 deletions.
13 changes: 12 additions & 1 deletion .github/workflows/R-CMD-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,18 @@ jobs:
extra-packages: any::rcmdcheck
needs: check

- name: Run Tests
run: |
Rscript -e 'devtools::test()'
# Automatically accept snapshots if there are differences
# Doesn't work on Windows - can't determine "check" on powershell
# - name: Run R CMD Check (Ignore Warnings)
# run: |
# R CMD check . --no-manual --compact-vignettes=gs+qpdf --as-cran || true

- uses: r-lib/actions/check-r-package@v2
with:
upload-snapshots: true
upload-snapshots: false
build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")'
error-on: '"error"'
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ rsconnect/
*.DS_Store

# nflfastR play-by-play database
data/pbp_db/
data/

# Outputs figures/tables
output/figures
Expand Down
2 changes: 2 additions & 0 deletions .lintr
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
linters: linters_with_defaults() # see vignette("lintr")
encoding: "UTF-8"
15 changes: 11 additions & 4 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,36 @@ Imports:
nflplotR,
nflreadr,
nflseedR,
pkgdown,
purrr,
rcmdcheck,
remotes,
rlang,
rmarkdown,
roxygen2,
RSQLite,
styler,
tidyr,
tidyverse,
usethis,
webshot2
Remotes:
dynastyprocess/ffpros,
FantasyFootballAnalytics/ffanalytics
FantasyFootballAnalytics/ffanalytics,
andreweatherman/gtUtils
Suggests:
badger,
extrafont,
gitcreds,
hexSticker,
knitr,
pkgdown,
roxygen2,
lintr,
showtext,
sysfonts,
usethis
testthat (>= 3.0.0)
Encoding: UTF-8
LazyData: true
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.2
VignetteBuilder: knitr
Config/testthat/edition: 3
16 changes: 13 additions & 3 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Generated by roxygen2: do not edit by hand

export(get_fpts_qb_season)
export(get_rb_adv_stats_season)
export(get_snap_pct)
export(calc_fpts)
export(calc_fpts_common_formats)
export(calc_fpts_ppg_common_formats)
export(get_pbp_data)
export(get_qb_pbp_stats)
export(get_rb_pbp_stats)
export(get_te_pbp_stats)
export(get_wr_pbp_stats)
export(load_data_from_db)
export(load_data_from_nflreadr)
export(validate_pbp_db)
export(validate_pbp_season)
export(validate_pbp_weeks)
126 changes: 126 additions & 0 deletions R/calculate_fpts.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#' Calculate Fantasy Points
#'
#' @param pbp Play-by-play Dataframe
#' @param pass_td_pts Passing Touchdown Points
#' @param pass_int_pts Passing Interception Points
#' @param rec_pts Reception Points
#'
#' @return
#' Calculate fantasy points using play-by-play and data and user-defined
#' scoring settings (e.g., 6PT PASS TD, -2 PASS INT, Full PPR)
#'
#' @author Nolan MacDonald
#'
#' @export
calc_fpts <- function(pbp = pbp,
pass_td_pts = 6,
pass_int_pts = -2,
rec_pts = 1)
{
pbp %>%
dplyr::mutate(
fpts = (0.04 * passing_yards) + # PASS YDS 1 point every 25 yards
(pass_td_pts * passing_tds) + # PASS TD
(2 * passing_2pt_conversions) + # 2PT Conversions (PASS)
(pass_int_pts * interceptions) + # INT
(-2 * sack_fumbles_lost) + # Sack Fumbles Lost
(0.1 * rushing_yards) + # RUSH YD
(6 * rushing_tds) + # RUSH TD
(-2 * rushing_fumbles_lost) + # Rush Fumbles Lost
(2 * rushing_2pt_conversions) + # 2PT Conversions (RUSH)
(rec_pts * receptions) + # REC (STD, HALF PPR, PPR)
(0.1 * receiving_yards) + # REC YD
(6 * receiving_tds) + # REC TD
(-2 * receiving_fumbles_lost) + # REC Fumble Lost
(2 * receiving_2pt_conversions) # 2PT Conversions (REC)
)
}

#' Calculate Fantasy Points for Common Scoring Formats
#'
#' @param pbp Play-by-play Dataframe
#'
#' @return
#' Calculate fantasy points using play-by-play and data and common
#' scoring settings (i.e., 4/6PT PASS TD, -2 PASS INT, STD/Half PPR/Full PPR)
#' @export
calc_fpts_common_formats <- function(pbp = pbp)
{
pbp %>%
# 4 point passing TD ---------------------------------------------------
# Calculate fantasy points for standard scoring
nuclearff::calc_fpts(pass_td_pts = 4,
pass_int_pts = -2,
rec_pts = 0) %>%
# Rename to fpts_std
dplyr::mutate(fpts_std_4pt_td = round(fpts, 2)) %>%
# Remove the default `fpts` column
dplyr::select(-fpts) %>%

# Calculate fantasy points for half PPR scoring
nuclearff::calc_fpts(pass_td_pts = 4,
pass_int_pts = -2,
rec_pts = 0.5) %>%
# Rename to fpts_half_ppr
dplyr::mutate(fpts_half_ppr_4pt_td = round(fpts, 2)) %>%
# Remove the default `fpts` column
dplyr::select(-fpts) %>%

# Calculate fantasy points for full PPR scoring
nuclearff::calc_fpts(pass_td_pts = 4,
pass_int_pts = -2,
rec_pts = 1) %>%
# Rename to fpts_ppr
dplyr::mutate(fpts_ppr_4pt_td = round(fpts, 2)) %>%
# Remove the default `fpts` column
dplyr::select(-fpts) %>%
# 6 point passing TD ---------------------------------------------------
# Calculate fantasy points for standard scoring
nuclearff::calc_fpts(pass_td_pts = 6,
pass_int_pts = -2,
rec_pts = 0) %>%
# Rename to fpts_std
dplyr::mutate(fpts_std_6pt_td = round(fpts, 2)) %>%
# Remove the default `fpts` column
dplyr::select(-fpts) %>%

# Calculate fantasy points for half PPR scoring
nuclearff::calc_fpts(pass_td_pts = 6,
pass_int_pts = -2,
rec_pts = 0.5) %>%
# Rename to fpts_half_ppr
dplyr::mutate(fpts_half_ppr_6pt_td = round(fpts, 2)) %>%
# Remove the default `fpts` column
dplyr::select(-fpts) %>%

# Calculate fantasy points for full PPR scoring
nuclearff::calc_fpts(pass_td_pts = 6,
pass_int_pts = -2,
rec_pts = 1) %>%
# Rename to fpts_ppr
dplyr::mutate(fpts_ppr_6pt_td = round(fpts, 2)) %>%
# Remove the default `fpts` column
dplyr::select(-fpts)
}

#' Calculate Fantasy Points Per Game for Common Scoring Formats
#'
#' @param pbp Play-by-play Dataframe
#'
#' @return
#' Calculate fantasy points per game using play-by-play and data and common
#' scoring settings (i.e., 4/6PT PASS TD, -2 PASS INT, STD/Half PPR/Full PPR)
#' @export
calc_fpts_ppg_common_formats <- function(pbp = pbp)
{
pbp %>%
nuclearff::calc_fpts_common_formats() %>%
dplyr::mutate(
ppg_std_4pt_td = round(fpts_std_4pt_td / games, 2),
ppg_half_ppr_4pt_td = round(fpts_half_ppr_4pt_td / games, 2),
ppg_ppr_4pt_td = round(fpts_ppr_4pt_td / games, 2),
ppg_std_6pt_td = round(fpts_std_6pt_td / games, 2),
ppg_half_ppr_6pt_td = round(fpts_half_ppr_6pt_td / games, 2),
ppg_ppr_6pt_td = round(fpts_ppr_6pt_td / games, 2)
)
}
91 changes: 91 additions & 0 deletions R/get_pbp_data.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
################################################################################
# Author: Nolan MacDonald
# Purpose:
# Obtain NFL play-by-play data with a database or using `nflreadr`
# Code Style Guide:
# styler::tidyverse_style(), lintr::use_lintr(type = "tidyverse")
################################################################################

#' Obtain Play-by-Play Data for Specified Time Frame
#'
#' @description
#' Obtain play-by-play data for a specified time frame from either a saved
#' database or if not defined, using `nflreadr::load_pbp()`.
#'
#' @details
#' The function `get_pbp_data` can be utilized to obtain play-by-play data
#' by either loading from a saved database or using `nflreadr::load_pbp()`.
#' The data is obtained for a user-defined season or multiple seasons.
#' A range of weeks can also be defined with `week_min` and `week_max`.
#' If the entire season is desired, use `week_min = 1` and `week_max` does
#' not need to be defined.
#' To specify loading from a database, define the path to the database with
#' `pbp_db` as well as the name of the table to load with `pbp_db_tbl`.
#' To load from a database, you will need to save play-by-play data to a
#' database using the `nflfastR` function, `update_db()`.
#' For example, the database is saved by default as `pbp_db` with a table that
#' is stored containing all play-by-play information called `nflfastR_pbp`.
#' Assume that the database is saved in `project_name/data/`.
#' Using the default naming scheme, `get_pbp_data` can be defined using the
#' database with `pbp_db = "data/pbp_db"` and `pbp_db_tbl = "nflfastR_pbp"`.
#' Note that these two arguments must be defined as strings.
#' For more information, `nflfastR` provides an example on using the database
#' in [Example 8: Using the built-in database function]
#' (https://www.nflfastr.com/articles/nflfastR.html
#' #example-8-using-the-built-in-database-function)
#'
#'
#' @seealso \code{\link[nflreadr]{load_pbp}}
#' Load play-by-play data,
#' @seealso \code{\link[nflfastR]{update_db}}
#' Update or Create a nflfastR Play-by-Play Database
#'
#' @param pbp_dp Play-by-Play database path (optional)
#' @param pbp_db_tbl Play-by-Play database table name (optional)
#' @param season NFL season (required) to obtain play-by-play data. The
#' season can be defined as a single season, `season = 2024`. For multiple
#' seasons, use either `season = c(2023,2024)` or `season = 2022:2024`.
#' @param week_min Minimum week (required) to define whether pulling a range
#' of weeks or the entire season. Use `week_min = 1` for the first week of
#' the season, must be an integer.
#' @param week_max Maximum week (optional) to define a range of weeks to pull
#' from an NFL season. If not defined, the data will be pulled for all weeks,
#' beginning with `week_min`.
#'
#' @return Dataframe with NFL play-by-play data
#'
#' @author Nolan MacDonald
#'
#' \itemize{
#' \item{\code{connect}}{
#' Connect to the play-by-play database with the `RSQLite` package
#' }
#' \item{\code{pbp_db}}{Load the play-by-play data from the database table}
#' \item{\code{pbp}}{Play-by-play dataframe filtered for season(s) and week(s)}
#' }
#'
#' @export
get_pbp_data <- function(pbp_db = NULL,
pbp_db_tbl = NULL,
season = NULL,
week_min = NULL,
week_max = NULL) {
# Validate play-by-play inputs
nuclearff::validate_pbp_db(pbp_db, pbp_db_tbl)
nuclearff::validate_pbp_season(season)
nuclearff::validate_pbp_weeks(week_min, week_max)

if (!is.null(pbp_db) && !is.null(pbp_db_tbl)) {
pbp <- nuclearff::load_data_from_db(pbp_db,
pbp_db_tbl,
season,
week_min,
week_max)
} else {
pbp <- nuclearff::load_data_from_nflreadr(season,
week_min,
week_max)
}

return(pbp)
}
Loading

0 comments on commit 5967869

Please sign in to comment.