├── .Rbuildignore ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── R ├── espnscrapeR-package.R ├── get_538_elo.R ├── get_athlete.R ├── get_college_qbr.R ├── get_depth_chart.R ├── get_espn_wr_metrics.R ├── get_nfl_boxscore.R ├── get_nfl_boxscore_players.R ├── get_nfl_odds.R ├── get_nfl_pbp.R ├── get_nfl_qbr.R ├── get_nfl_qbr_helper.R ├── get_nfl_schedule.R ├── get_nfl_standings.R ├── get_nfl_teams.R ├── get_nfl_win_prob.R ├── get_sharpe_data.R ├── gt_hulk_color.R ├── gt_theme_538.R ├── gt_theme_espn.R ├── scrape_espn_stats.R ├── scrape_espn_win_rate.R ├── scrape_fpi.R ├── scrape_nfl_qbr.R ├── scrape_nfl_standings.R ├── scrape_nfl_weekly_standings.R ├── scrape_superbowls.R ├── scrape_team_stats_nfl.R └── scrape_weekly_leaders.R ├── README.Rmd ├── README.md ├── docs ├── 404.html ├── LICENSE-text.html ├── LICENSE.html ├── apple-touch-icon-120x120.png ├── apple-touch-icon-152x152.png ├── apple-touch-icon-180x180.png ├── apple-touch-icon-60x60.png ├── apple-touch-icon-76x76.png ├── apple-touch-icon.png ├── articles │ ├── index.html │ ├── qbr_example.html │ └── qbr_example_files │ │ ├── figure-html │ │ └── unnamed-chunk-7-1.png │ │ ├── header-attrs-2.10 │ │ └── header-attrs.js │ │ └── header-attrs-2.9.7 │ │ └── header-attrs.js ├── authors.html ├── bootstrap-toc.css ├── bootstrap-toc.js ├── docsearch.css ├── docsearch.js ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── index.html ├── link.svg ├── logo.png ├── pkgdown.css ├── pkgdown.js ├── pkgdown.yml ├── reference │ ├── Rplot001.png │ ├── espnscrapeR-package.html │ ├── get_538_elo.html │ ├── get_538_elo_historical.html │ ├── get_538_wr_metrics.html │ ├── get_athlete.html │ ├── get_college_qbr.html │ ├── get_depth_chart.html │ ├── get_espn_college_qbr.html │ ├── get_espn_nfl_standings.html │ ├── get_espn_standings.html │ ├── get_espn_win_prob.html │ ├── get_espn_wr_metrics.html │ ├── get_nfl_boxscore.html │ ├── get_nfl_boxscore_players.html │ ├── get_nfl_odds.html │ ├── get_nfl_pbp.html │ ├── get_nfl_qbr.html │ ├── get_nfl_qbr_helper.html │ ├── get_nfl_schedule.html │ ├── get_nfl_standings.html │ ├── get_nfl_teams.html │ ├── get_sharpe_data.html │ ├── gt_hulk_color.html │ ├── gt_theme_538.html │ ├── gt_theme_espn.html │ ├── hello.html │ ├── index.html │ ├── scrape_espn_stats.html │ ├── scrape_espn_win_rate.html │ ├── scrape_fpi.html │ ├── scrape_nfl_qbr.html │ ├── scrape_nfl_standings.html │ ├── scrape_nfl_weekly_standings.html │ ├── scrape_standings.html │ ├── scrape_superbowls.html │ ├── scrape_team_stats_nfl.html │ └── scrape_weekly_leaders.html └── sitemap.xml ├── espnscrapeR.Rproj ├── inst └── figures │ └── espnscrapeR-logo.png ├── man ├── espnscrapeR-package.Rd ├── get_538_elo.Rd ├── get_538_elo_historical.Rd ├── get_athlete.Rd ├── get_college_qbr.Rd ├── get_depth_chart.Rd ├── get_espn_win_prob.Rd ├── get_espn_wr_metrics.Rd ├── get_nfl_boxscore.Rd ├── get_nfl_boxscore_players.Rd ├── get_nfl_odds.Rd ├── get_nfl_pbp.Rd ├── get_nfl_qbr.Rd ├── get_nfl_qbr_helper.Rd ├── get_nfl_schedule.Rd ├── get_nfl_standings.Rd ├── get_nfl_teams.Rd ├── get_sharpe_data.Rd ├── gt_hulk_color.Rd ├── gt_theme_538.Rd ├── gt_theme_espn.Rd ├── scrape_espn_stats.Rd ├── scrape_espn_win_rate.Rd ├── scrape_fpi.Rd ├── scrape_nfl_qbr.Rd ├── scrape_nfl_standings.Rd ├── scrape_nfl_weekly_standings.Rd ├── scrape_superbowls.Rd ├── scrape_team_stats_nfl.Rd └── scrape_weekly_leaders.Rd ├── pkgdown └── favicon │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-180x180.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── favicon.ico ├── qbr-pdf.pdf └── vignettes ├── .gitignore └── qbr_example.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | ^README\.Rmd$ 5 | ^doc$ 6 | ^Meta$ 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rhistory 2 | .RData 3 | .Rproj.user 4 | inst/doc 5 | /doc/ 6 | /Meta/ 7 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: espnscrapeR 2 | Type: Package 3 | Title: Scrapes Or Collects NFL Data From ESPN 4 | Version: 0.8.0 5 | Author: Thomas Mock 6 | Maintainer: Thomas Mock 7 | Description: Main use case is to collect ESPN QBR for NFL and college football. 8 | Alternative functions include getting NFL standings and scraping NFL season-level stats. 9 | License: MIT + file LICENSE 10 | Encoding: UTF-8 11 | LazyData: true 12 | RoxygenNote: 7.2.1 13 | URL: https://github.com/jthomasmock/espnscrapeR 14 | BugReports: https://github.com/jthomasmock/espnscrapeR/issues 15 | Imports: 16 | dplyr (>= 1.0), 17 | tidyr (>= 1.0), 18 | httr, 19 | rvest, 20 | tibble, 21 | xml2, 22 | purrr, 23 | glue, 24 | jsonlite, 25 | stringr, 26 | janitor, 27 | readr, 28 | gt (>= 0.3), 29 | scales 30 | Suggests: 31 | rmarkdown, 32 | knitr 33 | VignetteBuilder: knitr 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2020 2 | COPYRIGHT HOLDER: Thomas Mock 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2020 Thomas Mock 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(get_538_elo) 4 | export(get_538_elo_historical) 5 | export(get_athlete) 6 | export(get_college_qbr) 7 | export(get_depth_chart) 8 | export(get_espn_win_prob) 9 | export(get_espn_wr_metrics) 10 | export(get_nfl_boxscore) 11 | export(get_nfl_boxscore_players) 12 | export(get_nfl_odds) 13 | export(get_nfl_pbp) 14 | export(get_nfl_qbr) 15 | export(get_nfl_schedule) 16 | export(get_nfl_standings) 17 | export(get_nfl_teams) 18 | export(get_sharpe_data) 19 | export(gt_hulk_color) 20 | export(gt_theme_538) 21 | export(gt_theme_espn) 22 | export(scrape_espn_stats) 23 | export(scrape_espn_win_rate) 24 | export(scrape_fpi) 25 | export(scrape_nfl_standings) 26 | export(scrape_nfl_weekly_standings) 27 | export(scrape_superbowls) 28 | export(scrape_team_stats_nfl) 29 | export(scrape_weekly_leaders) 30 | import(dplyr) 31 | import(gt) 32 | import(httr) 33 | import(purrr) 34 | import(stringr) 35 | import(tidyr) 36 | importFrom(dplyr,"%>%") 37 | importFrom(glue,glue) 38 | importFrom(httr,GET) 39 | importFrom(httr,content) 40 | importFrom(httr,stop_for_status) 41 | importFrom(janitor,clean_names) 42 | importFrom(jsonlite,fromJSON) 43 | importFrom(purrr,map2_dfr) 44 | importFrom(readr,parse_number) 45 | importFrom(readr,read_csv) 46 | importFrom(readr,type_convert) 47 | importFrom(rvest,html_attr) 48 | importFrom(rvest,html_node) 49 | importFrom(rvest,html_nodes) 50 | importFrom(rvest,html_table) 51 | importFrom(rvest,html_text) 52 | importFrom(scales,col_numeric) 53 | importFrom(stringr,str_detect) 54 | importFrom(stringr,str_remove) 55 | importFrom(tibble,enframe) 56 | importFrom(tidyr,separate) 57 | importFrom(xml2,read_html) 58 | -------------------------------------------------------------------------------- /R/espnscrapeR-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | "_PACKAGE" 3 | 4 | # The following block is used by usethis to automatically manage 5 | # roxygen namespace tags. Modify with care! 6 | ## usethis namespace: start 7 | ## usethis namespace: end 8 | NULL 9 | -------------------------------------------------------------------------------- /R/get_538_elo.R: -------------------------------------------------------------------------------- 1 | #' Get ELO ratings from FiveThirtyEight 2 | #' 3 | #' @param season character or numeric, must be 2015 or greater 4 | #' @param stat character, must be one of "games", "weekly_elo", "weekly_rating", "distance", "qb_adj_playoff", "qb_adj" 5 | #' @return Returns a tibble 6 | #' @export 7 | #' @import tidyr dplyr purrr httr 8 | #' @importFrom dplyr %>% 9 | #' @importFrom jsonlite fromJSON 10 | #' @importFrom glue glue 11 | #' @examples 12 | #' # Get elo for specific season and stat 13 | #' get_538_elo(season = 2020, stat = "weekly_elo") 14 | 15 | 16 | get_538_elo <- function(season = 2020, stat = "weekly_elo") { 17 | 18 | season <- as.integer(season) 19 | 20 | stat_types <- c( 21 | "games", 22 | "weekly_elo", 23 | "weekly_rating", 24 | "distance", 25 | "qb_adj_playoff", 26 | "qb_adj" 27 | ) 28 | 29 | if (!(stat %in% stat_types)) { 30 | message(paste0("Stat must be one of ", paste0(stat_types, collapse = ", "))) 31 | } 32 | 33 | if (season < 2015) { 34 | # stats only available from 2015 on 35 | message("Stats only available for years 2015 to current year") 36 | 37 | } else if (season == 2015) { 38 | 39 | url_2015 <- "https://projects.fivethirtyeight.com/2015-nfl-predictions/data.json" 40 | 41 | json_2015 <- url_2015 %>% 42 | httr::GET() %>% 43 | httr::content() 44 | 45 | } else if (season >= 2015) { 46 | 47 | in_url <- glue::glue("https://projects.fivethirtyeight.com/{season}-nfl-predictions/data.json") 48 | 49 | raw_json <- httr::GET(in_url) %>% 50 | httr::content() 51 | } 52 | 53 | # 2015 has a different version 54 | if (season == 2015 && stat == "games") { 55 | json_2015$games %>% 56 | dplyr::tibble(value = .) %>% 57 | tidyr::unnest_wider(value) 58 | } else if (season == 2015 && stat == "weekly_elo") { 59 | json_2015$weekly_forecasts$forecasts %>% 60 | dplyr::tibble(value = .) %>% 61 | tidyr::unnest_wider(value) %>% 62 | tidyr::unnest_longer(teams) %>% 63 | tidyr::unnest_wider(teams) 64 | } else if(season %in% c(2016:2018) && stat %in% c("qb_adj", "weekly_rating", "distance", "qb_adj_playoff")){ 65 | 66 | message(paste0(stat, " not available for ", season)) 67 | 68 | } else if (season >= 2019 && stat == "qb_adj_playoff") { 69 | raw_json$playoff_qb_adjustments %>% 70 | dplyr::tibble(data = .) %>% 71 | tidyr::unnest_wider(data) 72 | } else if (season >= 2019 && stat == "weekly_rating") { 73 | rating_df <- raw_json$weekly_forecasts$forecasts %>% 74 | dplyr::tibble(data = .) %>% 75 | tidyr::unnest_wider(data) %>% 76 | tidyr::unnest_wider(types) %>% 77 | dplyr::select(-elo) %>% 78 | tidyr::unnest_longer(rating) %>% 79 | tidyr::unnest_wider(rating) 80 | 81 | rating_df 82 | 83 | } else if (season >= 2019 && stat == "weekly_elo") { 84 | elo_df <- raw_json$weekly_forecasts$forecasts %>% 85 | tibble::enframe() %>% 86 | tidyr::unnest_wider(value) %>% 87 | tidyr::unnest_wider(types) %>% 88 | dplyr::select(-name, -rating) %>% 89 | tidyr::unnest_longer(elo) %>% 90 | tidyr::unnest_wider(elo) 91 | 92 | elo_df 93 | } else if (season >= 2019 && stat == "games") { 94 | games_df <- raw_json$games %>% 95 | dplyr::tibble(data = .) %>% 96 | tidyr::unnest_wider(data) 97 | 98 | games_df 99 | } else if (season >= 2019 && stat == "distance") { 100 | distance_df <- raw_json$distances %>% 101 | dplyr::tibble(data = .) %>% 102 | tidyr::unnest_wider(data) %>% 103 | tidyr::unnest_longer(distances) 104 | distance_df 105 | } else if (season >= 2019 && stat == "qb_adj") { 106 | qb_adj <- raw_json$qbs %>% 107 | dplyr::tibble(data = .) %>% 108 | tidyr::unnest_wider(data) 109 | qb_adj 110 | } 111 | } 112 | 113 | #' Get historical ELO ratings from FiveThirtyEight 114 | #' 115 | #' @param team character, team abbreviated name, must be one of "ari", "atl", "bal", "buf", "car", "chi", "cin", "cle", "dal", "den", "det", "gb", "hou", "ind", "jax", "kc", "oak", "lac", "lar", "mia", "min", "ne", "no", "nyg", "nyj", "phi", "pit", "sf", "sea", "tb", "ten", "wsh" 116 | #' @return Returns a tibble 117 | #' @export 118 | #' @import tidyr dplyr purrr 119 | #' @importFrom dplyr %>% 120 | #' @importFrom jsonlite fromJSON 121 | #' @importFrom glue glue 122 | #' @examples 123 | #' 124 | #' # Get historic elo for specific team 125 | #' get_538_elo_historical(team = "pit") 126 | 127 | get_538_elo_historical <- function(team = "pit"){ 128 | 129 | all_teams <- c( 130 | "ari", "atl", "bal", "buf", "car", "chi", "cin", "cle", "dal", "den", "det", 131 | "gb", "hou", "ind", "jax", "kc", "oak", "lac", "lar", "mia", "min", "ne", 132 | "no", "nyg", "nyj", "phi", "pit", "sf", "sea", "tb", "ten", "wsh" 133 | ) 134 | 135 | if(!(team %in% all_teams)){ 136 | message( 137 | glue::glue( 138 | "Team not found, please use one of {paste0(all_teams, collapse = ', ')}" 139 | ) 140 | ) 141 | } 142 | 143 | 144 | 145 | join_teams <- dplyr::tibble( 146 | team = c( 147 | "ari","atl","bal","buf", 148 | "car","chi","cin","cle","dal","den","det","gb","hou", 149 | "ind","jax","kc","oak","lac","lar","mia","min", 150 | "ne","no","nyg","nyj","phi","pit","sf","sea","tb", 151 | "ten","wsh" 152 | ), 153 | espn_uid = c( 154 | "22","1","33","2","29","3", 155 | "4","5","6","7","8","9","34","11","30","12", 156 | "13","24","14","15","16","17","18","19","20","21", 157 | "23","25","26","27","10","28" 158 | ), 159 | espn_abb = c( 160 | "ARI","ATL","BAL","BUF", 161 | "CAR","CHI","CIN","CLE","DAL","DEN","DET","GB","HOU", 162 | "IND","JAX","KC","LV","LAC","LAR","MIA","MIN", 163 | "NE","NO","NYG","NYJ","PHI","PIT","SF","SEA","TB", 164 | "TEN","WSH" 165 | ) 166 | ) 167 | 168 | url_team <- dplyr::case_when( 169 | team == "lar" ~ "stl", 170 | team == "lac" ~ "sd", 171 | TRUE ~ team 172 | ) 173 | 174 | url_in <- glue::glue("https://projects.fivethirtyeight.com/complete-history-of-the-nfl/data/{url_team}.json") 175 | 176 | elo_json <- httr::GET(url_in) %>% 177 | httr::content() 178 | 179 | match_weeks <- tibble( 180 | week = c( 181 | NA, "Week 1", "Week 2", "Week 3", "Week 4", "Week 5", "Week 6", "Week 7", 182 | "Week 8", "Week 9", "Week 10", "Week 11", "Week 12", "Week 13", "Week 14", 183 | "Week 15", "Week 16", "Week 17", "Week 18", 184 | "Wild card", "Divisional", "Conference", "Super Bowl"), 185 | week_num = c(NA, 1:18, 18,19,20,21) 186 | ) 187 | 188 | elo_json$value %>% 189 | dplyr::tibble(data= .) %>% 190 | tidyr::hoist( 191 | data, 192 | year = "x", 193 | elo = "y", 194 | opp = "t", 195 | pts_for = "p", 196 | pts_against = "o", 197 | week = "w", 198 | date = "d" 199 | ) %>% 200 | dplyr::mutate(pts_for = suppressWarnings(unlist(pts_for) %>% as.double())) %>% 201 | dplyr::mutate(team = team, .before = year, date = as.Date(date, "%m/%d/%Y")) %>% 202 | dplyr::left_join(match_weeks, by = "week") %>% 203 | dplyr::left_join(join_teams, by = "team") %>% 204 | dplyr::select(team, espn_uid, espn_abb, dplyr::everything()) 205 | 206 | 207 | } 208 | -------------------------------------------------------------------------------- /R/get_athlete.R: -------------------------------------------------------------------------------- 1 | #' Get ESPN athlete info for NFL players 2 | #' 3 | #' @param athlete_id The player's unique athlete id 4 | #' @return Returns a tibble 5 | #' @export 6 | #' @import tidyr dplyr purrr httr 7 | #' @importFrom dplyr %>% 8 | #' @importFrom janitor clean_names 9 | #' @importFrom glue glue 10 | #' @examples 11 | #' # Get ALL Playoff QBR from 2016 season 12 | #' get_athlete("2580") 13 | #' 14 | get_athlete <- function(athlete_id){ 15 | 16 | season <- Sys.Date() %>% substr(1, 4) 17 | 18 | base_url <- "https://sports.core.api.espn.com/v2/sports/football/leagues/nfl/seasons/{season}/athletes/{athlete_id}" 19 | 20 | raw_get <- base_url %>% 21 | glue::glue() %>% 22 | httr::GET() 23 | 24 | httr::stop_for_status(raw_get) 25 | 26 | raw_json <- content(raw_get) 27 | 28 | athlete_df <- raw_json %>% 29 | tibble::enframe() %>% 30 | dplyr::filter(name != "$ref") %>% 31 | tidyr::pivot_wider(names_from = name, values_from = value) %>% 32 | janitor::clean_names() %>% 33 | tidyr::hoist(position, pos = "abbreviation") %>% 34 | tidyr::hoist(headshot, headshot_url = "href") %>% 35 | tidyr::hoist( 36 | draft, 37 | draft_txt = "displayText", 38 | draft_year = "year", 39 | draft_round = "round", 40 | draft_slot = "selection", 41 | draft_team_id = list("team", "$ref") 42 | ) %>% 43 | tidyr::hoist(experience, nfl_exp = "years") %>% 44 | tidyr::hoist(team, team_id = list(1, "$ref")) %>% 45 | dplyr::select( 46 | player_id = id, 47 | player_guid = guid, 48 | team_id, 49 | pos, 50 | player_first_name = first_name, 51 | player_last_name = last_name, 52 | player_full_name = full_name, 53 | player_short_name = short_name, 54 | weight, 55 | height, 56 | age, 57 | dob = date_of_birth, 58 | debut_year, 59 | headshot_url, 60 | jersey, 61 | nfl_exp, 62 | dplyr::contains("draft"), 63 | -draft 64 | ) %>% 65 | dplyr::mutate( 66 | draft_team_id = stringr::str_remove(draft_team_id, "http://sports.core.api.espn.com/v2/sports/football/leagues/nfl/seasons/[:digit:]+/teams/"), 67 | draft_team_id = stringr::str_remove(draft_team_id, "\\?lang=en®ion=us"), 68 | team_id = stringr::str_remove(team_id, "http://sports.core.api.espn.com/v2/sports/football/leagues/nfl/seasons/[:digit:]+/teams/"), 69 | team_id = stringr::str_remove(team_id, "\\?lang=en®ion=us") 70 | ) %>% 71 | tidyr::unchop(tidyselect:::where(is.list)) 72 | 73 | athlete_df 74 | 75 | } 76 | -------------------------------------------------------------------------------- /R/get_college_qbr.R: -------------------------------------------------------------------------------- 1 | #' Get ESPN QBR for College football 2 | #' 3 | #' @param season Numeric or character - greater than 2004 4 | #' @param type character - "season" or "weekly" 5 | #' @import tidyr dplyr purrr httr 6 | #' @importFrom dplyr %>% 7 | #' @importFrom tibble enframe 8 | #' @importFrom glue glue 9 | #' @importFrom janitor clean_names 10 | #' @return tibble 11 | #' @export 12 | #' 13 | #' @examples 14 | #' 15 | #' # Get college QBR from 2014 for players at season level 16 | #' get_college_qbr(season = 2014, type = "season") 17 | #' 18 | #' # Get weekly college QBR from 2019 19 | #' # Note the mix of playoffs/bowls and regular season 20 | #' get_college_qbr(2019, type = "weekly") 21 | get_college_qbr <- function(season = 2020, type = "season") { 22 | current_year <- as.double(substr(Sys.Date(), 1, 4)) 23 | 24 | # Small error handling to guide the limits on years 25 | if (!dplyr::between(as.numeric(season), 2004, current_year)) { 26 | stop(paste("Please choose season between 2004 and", current_year)) 27 | } 28 | 29 | if (!(type %in% c("season", "weekly"))) { 30 | stop("Please choose `season` or `weekly` for the `type`") 31 | } 32 | 33 | # Add message according to totals or weeks 34 | message( 35 | dplyr::if_else( 36 | type == "season", 37 | glue::glue("Scraping QBR totals for {season}!"), 38 | glue::glue("Scraping QBR for all weeks of {season}!") 39 | ) 40 | ) 41 | 42 | # Build base url 43 | base_url <- "https://site.web.api.espn.com/apis/fitt/v3/sports/football/college-football/qbr" 44 | 45 | query_type <- 46 | if (type == "weekly") { 47 | list( 48 | qbrType = "weeks", 49 | season = season, 50 | limit = 200 51 | ) 52 | } else if (type == "season") { 53 | list( 54 | qbrType = "seasons", 55 | season = season, 56 | limit = 200 57 | ) 58 | } 59 | 60 | raw_get <- httr::GET( 61 | url = base_url, 62 | query = query_type 63 | ) 64 | 65 | httr::stop_for_status(raw_get) 66 | 67 | raw_json <- httr::content(raw_get) 68 | 69 | if (!("athletes" %in% names(raw_json))) { 70 | stop("ESPN has missing data") 71 | } 72 | 73 | # check pagination 74 | n_pages <- raw_json[["pagination"]][["pages"]] 75 | 76 | if (type == "weekly") { 77 | 78 | # loop through pages 79 | raw_data <- 1:n_pages %>% 80 | paste0(raw_get$url, "&page=", .) %>% 81 | purrr::map(~ httr::GET(.x) %>% httr::content()) %>% 82 | purrr::map("athletes") %>% 83 | tibble::enframe() %>% 84 | tidyr::unnest_longer(value) 85 | } else if (type == "season") { 86 | raw_data <- raw_json[["athletes"]] %>% 87 | tibble::enframe() 88 | } 89 | 90 | # qbr names 91 | qbr_names <- c( 92 | "qbr_total", 93 | "pts_added", 94 | "qb_plays", 95 | "epa_total", 96 | "pass", 97 | "run", 98 | "exp_sack", 99 | "penalty", 100 | "qbr_raw", 101 | "sack" 102 | ) 103 | 104 | if (type == "weekly") { 105 | raw_data %>% 106 | tidyr::unnest_wider(value) %>% 107 | tidyr::unnest_wider(athlete) %>% 108 | tidyr::hoist( 109 | categories, 110 | qbr_values = list(1, "totals") 111 | ) %>% 112 | { 113 | if ("headshot" %in% names(.)) { 114 | tidyr::hoist( 115 | ., 116 | headshot, 117 | headshot_href = "href" 118 | ) 119 | } else { 120 | . 121 | } 122 | } %>% 123 | tidyr::hoist( 124 | game, 125 | game_id = "id", 126 | game_date = "date", 127 | week = "weekNumber", 128 | week_text = "weekText", 129 | player_home_away = "homeAway", 130 | score = "score", 131 | opp_team_id = list("teamOpponent", "id"), 132 | opp_team_name = list("teamOpponent", "name"), 133 | opp_team_short_name = list("teamOpponent", "abbreviation"), 134 | ) %>% 135 | janitor::clean_names() %>% 136 | dplyr::rename( 137 | player_id = id, 138 | player_uid = uid, 139 | player_guid = guid, 140 | team_uid = team_u_id 141 | ) %>% 142 | dplyr::select( 143 | -any_of( 144 | c( 145 | "headshot", 146 | "name", 147 | "categories", 148 | "teams", 149 | "position", 150 | "status", 151 | "links", 152 | "type" 153 | ) 154 | ) 155 | ) %>% 156 | dplyr::mutate(qbr_names = list(qbr_names)) %>% 157 | tidyr::unchop(c(qbr_values, qbr_names)) %>% 158 | tidyr::unchop(qbr_values) %>% 159 | tidyr::pivot_wider(names_from = qbr_names, values_from = qbr_values) %>% 160 | janitor::clean_names() %>% 161 | dplyr::mutate(season = as.integer(season)) %>% 162 | dplyr::mutate(across(qbr_total:sack, as.double)) %>% 163 | dplyr::mutate( 164 | week_type = dplyr::if_else( 165 | grepl(x = week_text, pattern = "Bowl"), 166 | "Bowls", 167 | "Regular" 168 | ) 169 | ) %>% 170 | dplyr::select(season, contains("week"), dplyr::everything(), -game) %>% 171 | dplyr::arrange(desc(week_type), week, desc(qbr_total)) %>% 172 | dplyr::rename( 173 | name_first = first_name, name_last = last_name, 174 | name_display = display_name, name_short = short_name 175 | ) 176 | } else if (type == "season") { 177 | raw_data %>% 178 | tidyr::unnest_wider(value) %>% 179 | tidyr::unnest_wider(athlete) %>% 180 | tidyr::hoist( 181 | categories, 182 | qbr_values = list(1, "totals") 183 | ) %>% 184 | { 185 | if ("headshot" %in% names(.)) { 186 | tidyr::hoist( 187 | ., 188 | headshot, 189 | headshot_href = "href" 190 | ) 191 | } else { 192 | . 193 | } 194 | } %>% 195 | dplyr::select( 196 | -any_of( 197 | c( 198 | "headshot", 199 | "name", 200 | "categories", 201 | "teams", 202 | "position", 203 | "status", 204 | "links", 205 | "type" 206 | ) 207 | ) 208 | ) %>% 209 | dplyr::mutate(qbr_names = list(qbr_names)) %>% 210 | tidyr::unchop(qbr_values:qbr_names) %>% 211 | tidyr::unchop(qbr_values) %>% 212 | tidyr::pivot_wider(names_from = qbr_names, values_from = qbr_values) %>% 213 | janitor::clean_names() %>% 214 | dplyr::rename( 215 | player_id = id, 216 | player_uid = uid, 217 | player_guid = guid, 218 | team_uid = team_u_id 219 | ) %>% 220 | dplyr::mutate( 221 | season = as.integer(season), 222 | week_text = "Season Level", 223 | week = NA 224 | ) %>% 225 | dplyr::mutate(across(qbr_total:sack, as.double)) %>% 226 | dplyr::select(season, week, week_text, dplyr::everything()) %>% 227 | dplyr::rename( 228 | name_first = first_name, name_last = last_name, 229 | name_display = display_name, name_short = short_name 230 | ) 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /R/get_depth_chart.R: -------------------------------------------------------------------------------- 1 | #' Get ESPN NFL depth chart by year and team 2 | #' 3 | #' @param season Either numeric or character 4 | #' @param team team_id or team_abb, can be retrieved via get_nfl_teams() 5 | #' @return Returns a tibble 6 | #' @export 7 | #' @import tidyr dplyr purrr httr 8 | #' @importFrom dplyr %>% 9 | #' @importFrom janitor clean_names 10 | #' @importFrom glue glue 11 | #' @examples 12 | #' # Get depth chart for 2017 for Pittsburgh 13 | #' get_depth_chart("2016", team = "PIT") 14 | #' 15 | 16 | get_depth_chart <- function(season = 2020, team = 23){ 17 | 18 | team_ids <- c( 19 | "22", "1", "33", "2", "29", "3", "4", "5", "6", 20 | "7", "8", "9", "34", "11", "30", "12", "13", "24", 21 | "14", "15", "16", "17", "18", "19", "20", "21", 22 | "23", "25", "26", "27", "10", "28" 23 | ) 24 | 25 | team_abb <- c("ARI", "ATL", "BAL", "BUF", "CAR", "CHI", "CIN", "CLE", 26 | "DAL", "DEN", "DET", "GB", "HOU", "IND", "JAX", "KC", 27 | "LV", "LAC", "LAR", "MIA", "MIN", "NE", "NO", "NYG", 28 | "NYJ", "PHI", "PIT", "SF", "SEA", "TB", "TEN", "WSH") 29 | 30 | stopifnot("Please use a valid numeric team id or a team abbreviation - these can be found with get_nfl_teams()" = 31 | any(team %in% team_ids, team %in% team_abb)) 32 | 33 | if(team %in% team_abb){ 34 | team <- team_ids[team_abb == team] 35 | } 36 | 37 | base_url <- "https://sports.core.api.espn.com/v2/sports/football/leagues/nfl/seasons/{season}/teams/{team}/depthcharts" 38 | 39 | raw_get <- base_url %>% 40 | glue::glue() %>% 41 | httr::GET() 42 | 43 | httr::stop_for_status(raw_get) 44 | 45 | raw_json <- httr::content(raw_get) 46 | 47 | depth_chart <- raw_json$items %>% 48 | tibble(data = .) %>% 49 | unnest_wider(data) %>% 50 | unnest_longer(positions) %>% 51 | unnest_wider(positions) %>% 52 | unnest_longer(athletes) %>% 53 | unnest_wider(athletes) %>% 54 | hoist(athlete, athlete_id = "$ref") %>% 55 | mutate( 56 | athlete_id = str_remove(athlete_id, "http://sports.core.api.espn.com/v2/sports/football/leagues/nfl/seasons/[:digit:]+/athletes/"), 57 | athlete_id = str_remove(athlete_id, "\\?lang=en®ion=us") 58 | ) %>% 59 | # glimpse() 60 | rename( 61 | pos_grp_id = id, pos_grp = name, pos_slot = slot, pos_rank = rank, 62 | pos_id = positions_id 63 | ) %>% 64 | hoist( 65 | position, 66 | pos_id = "id", 67 | pos_name = "name", 68 | pos_abb = "abbreviation" 69 | ) %>% 70 | select(-position) %>% 71 | mutate(season = season, 72 | team_id = team) %>% 73 | select(season, team_id, athlete_id, contains("pos")) 74 | 75 | depth_chart 76 | 77 | } 78 | -------------------------------------------------------------------------------- /R/get_espn_wr_metrics.R: -------------------------------------------------------------------------------- 1 | #' Get ESPN's WR metrics from FiveThirtyEight 2 | #' 3 | #' 4 | #' @return tibble 5 | #' 6 | #' @examples 7 | #' raw_metrics <- espnscrapeR::get_espn_wr_metrics() 8 | #' 9 | #' dplyr::glimpse(raw_metrics) 10 | #' @export 11 | 12 | get_espn_wr_metrics <- function(){ 13 | 14 | in_url <- "https://projects.fivethirtyeight.com/nfl-receiver-rankings/data.json" 15 | 16 | raw_json <- jsonlite::fromJSON(in_url, simplifyVector = FALSE) 17 | 18 | dplyr::tibble(data = raw_json[["rtm_data"]]) |> 19 | tidyr::unnest_wider(data) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /R/get_nfl_odds.R: -------------------------------------------------------------------------------- 1 | #' Get NFL odds for a specific game from ESPN's API 2 | #' 3 | #' @param game_id character 4 | #' 5 | #' @return Returns a tibble 6 | #' @export 7 | #' @import tidyr dplyr 8 | #' @importFrom dplyr %>% 9 | #' @importFrom httr GET content stop_for_status 10 | #' @importFrom glue glue 11 | #' @examples 12 | #' # Get odds from a specific game 13 | #' get_nfl_odds(game_id = "400791550") 14 | 15 | get_nfl_odds <- function(game_id) { 16 | 17 | game_url <- glue::glue("http://site.api.espn.com/apis/site/v2/sports/football/nfl/summary") 18 | 19 | raw_get <- httr::GET(game_url, query = list(event = game_id, enable = "ranks,odds,linescores,logos")) 20 | 21 | httr::stop_for_status(raw_get) 22 | 23 | raw_json <- httr::content(raw_get) 24 | 25 | team_df <- raw_json[["boxscore"]][["teams"]] %>% 26 | tibble(data = .) %>% 27 | unnest_wider(data) %>% 28 | unnest_wider(team) %>% 29 | select(name:displayName, logo) %>% 30 | mutate(location = c("away", "home")) %>% 31 | rename( 32 | team_name = name, team_full = displayName, 33 | team_abb = abbreviation, team_logo = logo 34 | ) 35 | 36 | odds_df <- raw_json[["pickcenter"]] %>% 37 | tibble(data = .) %>% 38 | unnest_wider(data) %>% 39 | rename(over_under = overUnder, spread_team = details) %>% 40 | hoist( 41 | awayTeamOdds, 42 | away_ml = "moneyLine", 43 | away_odds = "spreadOdds" 44 | ) %>% 45 | hoist( 46 | homeTeamOdds, 47 | home_ml = "moneyLine", 48 | home_odds = "spreadOdds" 49 | ) %>% 50 | hoist( 51 | provider, 52 | odds_source = "name", 53 | odds_source_id = "id" 54 | ) %>% 55 | select(!where(is.list)) %>% 56 | pivot_longer( 57 | cols = c(away_ml, away_odds, home_ml, home_odds), 58 | names_to = "stat", values_to = "odds" 59 | ) %>% 60 | separate(stat, into = c("location", "stat"), sep = "_") %>% 61 | pivot_wider(names_from = stat, values_from = odds) %>% 62 | left_join( 63 | tibble( 64 | location = c("home", "away"), 65 | win_proj_fpi = c( 66 | as.double(raw_json$predictor$homeTeam$gameProjection), 67 | as.double(raw_json$predictor$awayTeam$gameProjection) 68 | ) 69 | ), 70 | by = "location" 71 | ) %>% 72 | left_join(team_df, by = "location") %>% 73 | mutate(game_id = game_id, win_proj_fpi = as.double(win_proj_fpi)) 74 | 75 | odds_df 76 | } 77 | -------------------------------------------------------------------------------- /R/get_nfl_pbp.R: -------------------------------------------------------------------------------- 1 | #' Get NFL play-by-play for a specific game from ESPN's API 2 | #' 3 | #' @param game_id character 4 | #' 5 | #' @return Returns a tibble 6 | #' @export 7 | #' @import tidyr dplyr purrr 8 | #' @importFrom dplyr %>% 9 | #' @importFrom httr GET content stop_for_status 10 | #' @importFrom glue glue 11 | #' @examples 12 | #' # Get NFL play-by-play for a specific game 13 | #' get_nfl_pbp(game_id = "300912027") 14 | 15 | 16 | get_nfl_pbp <- function(game_id){ 17 | 18 | game_url <- glue::glue("http://site.api.espn.com/apis/site/v2/sports/football/nfl/summary") 19 | 20 | raw_get <- httr::GET(game_url, query = list(event = game_id, enable = "ranks,odds,linescores,logos")) 21 | 22 | httr::stop_for_status(raw_get) 23 | 24 | raw_json <- httr::content(raw_get) 25 | 26 | nfl_pbp <- raw_json[["drives"]][["previous"]] %>% 27 | tibble(data = .) %>% 28 | unnest_wider(data) %>% 29 | rename(drive_id = id) %>% 30 | unnest_wider(team) %>% 31 | select(-shortDisplayName) %>% 32 | hoist(logos, logo = list(1, "href")) %>% 33 | rename( 34 | pos_team_name = name, pos_team_abb = abbreviation, 35 | pos_team_full = displayName, drive_desc = description 36 | ) %>% 37 | unnest_wider(start) %>% 38 | rename(drive_start_yardline = yardLine, drive_start_text = text) %>% 39 | hoist(period, 40 | drive_start_qtr = "number" 41 | ) %>% 42 | select(-period, -logos) %>% 43 | hoist(clock, 44 | drive_start_clock = "displayValue" 45 | ) %>% 46 | unnest_wider(end) %>% 47 | hoist(period, 48 | drive_end_qtr = "number" 49 | ) %>% 50 | select(-period) %>% 51 | hoist(clock, 52 | drive_end_clock = "displayValue" 53 | ) %>% 54 | rename(drive_end_yardline = yardLine, drive_end_text = text) %>% 55 | hoist(timeElapsed, drive_time = "displayValue") %>% 56 | rename( 57 | drive_yds = yards, drive_result_score = isScore, 58 | drive_plays = offensivePlays, drive_result = result 59 | ) %>% 60 | select(-shortDisplayResult, -displayResult) %>% 61 | unnest_longer(plays) %>% 62 | unnest_wider(plays) %>% 63 | rename( 64 | play_id = id, play_desc = text, away_score = awayScore, 65 | home_score = homeScore, scoring_play = scoringPlay, 66 | yards_gained = statYardage, scoring_type = scoringType 67 | ) %>% 68 | hoist(type, play_type = "text") %>% select(-type) %>% 69 | hoist(period, quarter = "number") %>% 70 | hoist(clock, clock_text = "displayValue") %>% 71 | select(-priority) %>% 72 | mutate(across(c(drive_result_score, scoring_play), as.integer)) %>% 73 | hoist( 74 | start, 75 | start_posteam_id = list("team", "id"), 76 | start_down = "down", 77 | start_ydstogo = "distance", 78 | start_yardline = "yardLine", 79 | start_ydsto_ez = "yardsToEndzone", 80 | start_text = "downDistanceText", 81 | start_down_text = "shortDownDistanceText", 82 | start_possess_text = "possessionText", 83 | ) %>% 84 | hoist( 85 | end, 86 | end_posteam_id = list("team", "id"), 87 | end_down = "down", 88 | end_ydstogo = "distance", 89 | end_yardline = "yardLine", 90 | end_ydsto_ez = "yardsToEndzone", 91 | end_text = "downDistanceText", 92 | end_down_text = "shortDownDistanceText", 93 | end_possess_text = "possessionText", 94 | ) %>% 95 | hoist(scoring_type, score_type = "abbreviation") %>% 96 | relocate(yards_gained, .after = "play_type") %>% 97 | select(-start, -end, -modified, -scoring_type) 98 | 99 | game_header <- raw_json %>% 100 | keep(names(raw_json) %in% "header") %>% 101 | tibble(data = .) %>% 102 | unnest_wider(data) %>% 103 | unchop(competitions) %>% 104 | rename(game_id = id, game_uid = uid) %>% 105 | unnest_wider(competitions) %>% 106 | unnest_wider(season) %>% 107 | select(-id, -uid) %>% 108 | rename(season = year, season_type = type) %>% 109 | select( 110 | !any_of( 111 | c( 112 | "neutralSite", "conferenceCompetition", "boxscoreAvailable", 113 | "commentaryAvailable", "liveAvailable", "onWatchESPN", "recent", 114 | "boxscoreSource", "playByPlaySource" 115 | ) 116 | ) 117 | ) %>% 118 | hoist( 119 | competitors, 120 | home_team_name = list(1, "team", "name"), 121 | home_team_logo = list(1, "team", "logos", 1, "href"), 122 | home_team_abb = list(1, "team", "abbreviation"), 123 | home_team_id = list(1, "team", "id"), 124 | home_team_location = list(1, "team", "location"), 125 | home_team_full = list(1, "team", "displayName"), 126 | home_team_color = list(1, "team", "color"), 127 | home_team_color_alt = list(1, "team", "alternateColor"), 128 | home_score_final = list(1, "score"), 129 | home_win = list(1, "winner"), 130 | home_record = list(1, "record", 1, "summary"), 131 | # away team 132 | away_team_name = list(2, "team", "name"), 133 | away_team_logo = list(2, "team", "logos", 1, "href"), 134 | away_team_abb = list(2, "team", "abbreviation"), 135 | away_team_id = list(2, "team", "id"), 136 | away_team_location = list(2, "team", "location"), 137 | away_team_full = list(2, "team", "displayName"), 138 | away_team_color = list(2, "team", "color"), 139 | away_team_color_alt = list(2, "team", "alternateColor"), 140 | away_score_final = list(2, "score"), 141 | away_win = list(2, "winner"), 142 | away_record = list(2, "record", 1, "summary"), 143 | ) %>% 144 | mutate(home_win = as.integer(home_win), 145 | away_win = as.integer(away_win),) %>% 146 | select(!where(is.list), -timeValid) 147 | 148 | 149 | 150 | combo_df <- bind_cols(nfl_pbp, game_header) %>% 151 | select( 152 | contains("game"), contains("season"), date, week, drive_id,pos_team_abb, 153 | play_id, play_type, yards_gained, play_desc, everything() 154 | ) 155 | if(raw_json[["header"]][["season"]][["year"]] >= 2015){ 156 | 157 | wp_df <- raw_json[["winprobability"]] %>% 158 | tibble(data = .) %>% 159 | hoist( 160 | data, 161 | play_id = "playId", 162 | home_wp = "homeWinPercentage", 163 | tie_percentage = "tiePercentage", 164 | game_sec_remaining = "secondsLeft" 165 | ) 166 | 167 | combo_df %>% 168 | left_join(wp_df, by = "play_id") 169 | 170 | } else { 171 | combo_df 172 | } 173 | 174 | 175 | } 176 | -------------------------------------------------------------------------------- /R/get_nfl_qbr.R: -------------------------------------------------------------------------------- 1 | #' Get ESPN QBR for NFL football 2 | #' 3 | #' @param season Either numeric or character 4 | #' @param week Either NA to return season or week 1 to 4 for playoffs or 1 to 17 for regular 5 | #' @param season_type Character - either "Regular" or "Playoffs" 6 | #' @return Returns a tibble 7 | #' @export 8 | #' @import tidyr dplyr purrr httr 9 | #' @importFrom dplyr %>% 10 | #' @importFrom jsonlite fromJSON 11 | #' @importFrom janitor clean_names 12 | #' @importFrom glue glue 13 | #' @examples 14 | #' # Get ALL Playoff QBR from 2016 season 15 | #' get_nfl_qbr("2016", season_type = "Playoffs", week = NA) 16 | #' 17 | #' # Get Regular season QBR for week 4 of 2019 18 | #' get_nfl_qbr("2019", season_type = "Regular", week = 4) 19 | get_nfl_qbr <- function(season = 2020, week = NA, season_type = "Regular") { 20 | 21 | current_year <- as.double(substr(Sys.Date(), 1, 4)) 22 | 23 | # Error handling to correct season type 24 | if (!season_type %in% c("Regular", "Playoffs")) { 25 | stop("Please choose season_type of 'Regular' or 'Playoffs'") 26 | } 27 | 28 | # Error handling for limits on season 29 | if (!dplyr::between(as.numeric(season), 2006, current_year)) { 30 | stop(paste("Please choose season between 2006 and", current_year)) 31 | } 32 | 33 | # Error handling for limits on regular season weeks 34 | if (!is.na(week) & season_type == "Regular" & !dplyr::between(as.numeric(week), 1, 18)) { 35 | stop("Please choose regular season week between 1 and 18") 36 | } 37 | 38 | # Error handling for limits on playoff weeks 39 | if (!is.na(week) & season_type == "Playoffs" & !dplyr::between(as.numeric(week), 1, 4)) { 40 | stop("Please choose Playoff week between 1 and 4") 41 | } 42 | 43 | # Error handling for missing data from ESPN 44 | if (!is.na(week) & season_type == "Playoffs" & as.numeric(season) == 2017) { 45 | warning("ESPN has some missing Playoff data for 2017", call. = FALSE) 46 | } 47 | 48 | week_current <- dplyr::if_else( 49 | # Logic check to fix years where superbowl = 5 50 | season_type == "Playoffs" & as.numeric(week) == 4 & season >= 2009, 51 | # outcome = 5 52 | 5L, 53 | # default to normal week 54 | as.integer(week) 55 | ) 56 | 57 | # Add useful messages - separated by week 58 | message(dplyr::if_else( 59 | is.na(week), 60 | glue::glue("Scraping QBR totals for {season}!"), 61 | glue::glue("Scraping weekly QBR for week {week} of {season}!") 62 | )) 63 | 64 | # Build up URL - not sure if this makes it easier to read, but it makes it 65 | # closer to 80ish characters per line 66 | url_start <- "https://site.web.api.espn.com/apis/fitt/v3/sports/football/nfl/qbr" 67 | 68 | query_type <- if (is.na(week) & season_type == "Regular") { 69 | list( 70 | qbrType = "seasons", 71 | seasontype = 2, 72 | isqualified = "true", 73 | season = season 74 | ) 75 | } else if (!is.na(week) & season_type == "Regular") { 76 | list( 77 | qbrType = "weeks", 78 | seasontype = 2, 79 | isqualified = "true", 80 | season = season, 81 | week = week 82 | ) 83 | } else if (is.na(week) & season_type == "Playoffs") { 84 | list( 85 | qbrType = "seasons", 86 | seasontype = 3, 87 | isqualified = "true", 88 | season = season 89 | ) 90 | } else if (!is.na(week) & season_type == "Playoffs") { 91 | list( 92 | qbrType = "weeks", 93 | seasontype = 3, 94 | isqualified = "true", 95 | season = season, 96 | week = week_current 97 | ) 98 | } 99 | 100 | get_and_content <- function(query_type){ 101 | 102 | raw_get <- httr::GET( 103 | url = url_start, 104 | query = query_type 105 | ) 106 | 107 | httr::stop_for_status(raw_get) 108 | 109 | raw_json <- httr::content(raw_get) 110 | 111 | raw_json 112 | 113 | } 114 | 115 | raw_json <- get_and_content(query_type) 116 | 117 | final_df <- get_nfl_qbr_helper(raw_json, week, season, season_type) %>% 118 | mutate(qualified = TRUE) 119 | 120 | if(is.na(week)){ 121 | 122 | query_type$isqualified <- 'false' 123 | raw_json_all <- get_and_content(query_type) 124 | 125 | final_df_all <- get_nfl_qbr_helper(raw_json_all, week, season, season_type) %>% 126 | mutate(rank = NA_real_, qualified = FALSE) 127 | 128 | output_df <- bind_rows( 129 | final_df, 130 | final_df_all %>% filter(!(player_id %in% final_df$player_id)) 131 | ) 132 | 133 | output_df 134 | 135 | } else { 136 | final_df 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /R/get_nfl_qbr_helper.R: -------------------------------------------------------------------------------- 1 | #' Get ESPN QBR for NFL football 2 | #' 3 | #' @param season Either numeric or character 4 | #' @param week Either NA to return season or week 1 to 4 for playoffs or 1 to 17 for regular 5 | #' @param season_type Character - either "Regular" or "Playoffs" 6 | #' @return Returns a tibble 7 | #' @import tidyr dplyr purrr httr 8 | #' @importFrom dplyr %>% 9 | #' @importFrom jsonlite fromJSON 10 | #' @importFrom janitor clean_names 11 | #' @importFrom glue glue 12 | 13 | get_nfl_qbr_helper <- function(raw_json, week, season, season_type) { 14 | 15 | in_nm <- c( 16 | "qbr_total", 17 | "pts_added", 18 | "qb_plays", 19 | "epa_total", 20 | "pass", 21 | "run", 22 | "exp_sack", 23 | "penalty", 24 | "qbr_raw", 25 | "sack" 26 | ) 27 | 28 | 29 | if (is.na(week)) { 30 | final_df <- raw_json[["athletes"]] %>% 31 | tibble::enframe() %>% 32 | tidyr::unnest_wider(value) %>% 33 | tidyr::hoist(categories, vals = list(1, "totals")) %>% 34 | dplyr::select(-name, -categories) %>% 35 | tidyr::unnest_wider(athlete) %>% 36 | tidyr::hoist( 37 | headshot, 38 | headshot_href = "href" 39 | ) %>% 40 | dplyr::select( 41 | team_abb = teamShortName, 42 | player_id = id, 43 | name_short = shortName, 44 | vals, 45 | name_first = firstName, 46 | name_last = lastName, 47 | name_display = displayName, 48 | headshot_href, 49 | team = teamName, 50 | team_id = teamId, 51 | team_uid = teamUId 52 | ) %>% 53 | dplyr::mutate(vals = purrr::map(vals, ~ purrr::set_names(.x, in_nm))) %>% 54 | tidyr::unnest_wider(vals) %>% 55 | dplyr::mutate(dplyr::across(qbr_total:sack, as.double)) %>% 56 | dplyr::mutate( 57 | rank = rank(desc(qbr_total)), 58 | game_week = "Season Total", 59 | season = season, 60 | season_type = season_type 61 | ) %>% 62 | dplyr::select( 63 | season, 64 | season_type, 65 | game_week, 66 | team_abb, 67 | player_id, 68 | name_short, 69 | rank, 70 | qbr_total:sack, 71 | name_first, 72 | name_last, 73 | name_display, 74 | headshot_href, 75 | team 76 | ) 77 | } else { 78 | final_df <- raw_json[["athletes"]] %>% 79 | tibble::enframe() %>% 80 | tidyr::unnest_wider(value) %>% 81 | tidyr::hoist(categories, vals = list(1, "totals")) %>% 82 | tidyr::hoist( 83 | game, 84 | game_id = "id", 85 | game_date = "date", 86 | game_score = "score", 87 | home_away = "homeAway", 88 | week_num = "weekNumber", 89 | week_text = "weekText", 90 | opp_id = list("teamOpponent", "id"), 91 | opp_abb = list("teamOpponent", "abbreviation"), 92 | opp_team = list("teamOpponent", "displayName"), 93 | opp_name = list("teamOpponent", "name") 94 | ) %>% 95 | dplyr::select(-name, -categories, -game) %>% 96 | tidyr::unnest_wider(athlete) %>% 97 | tidyr::hoist( 98 | headshot, 99 | headshot_href = "href" 100 | ) %>% 101 | dplyr::select( 102 | game_id, 103 | week_num, 104 | week_text, 105 | team_abb = teamShortName, 106 | player_id = id, 107 | name_short = shortName, 108 | vals, 109 | name_first = firstName, 110 | name_last = lastName, 111 | name_display = displayName, 112 | headshot_href, 113 | team = teamName, 114 | opp_id:opp_name, 115 | ) %>% 116 | dplyr::mutate(vals = purrr::map(vals, ~ purrr::set_names(.x, in_nm))) %>% 117 | tidyr::unnest_wider(vals) %>% 118 | dplyr::mutate(dplyr::across(qbr_total:sack, as.double)) %>% 119 | dplyr::mutate( 120 | rank = rank(desc(qbr_total)), 121 | game_week = as.integer(week), 122 | season = season, 123 | season_type = season_type 124 | ) %>% 125 | dplyr::select( 126 | season, 127 | season_type, 128 | game_id, 129 | game_week, 130 | week_text, 131 | team_abb, 132 | player_id, 133 | name_short, 134 | rank, 135 | qbr_total:sack, 136 | name_first, 137 | name_last, 138 | name_display, 139 | headshot_href, 140 | team, 141 | opp_id:opp_name, 142 | week_num 143 | ) 144 | 145 | } 146 | 147 | final_df 148 | } 149 | -------------------------------------------------------------------------------- /R/get_nfl_schedule.R: -------------------------------------------------------------------------------- 1 | #' Get NFL schedule for a specific year from ESPN's API 2 | #' 3 | #' @param season Either numeric or character 4 | #' 5 | #' @return Returns a tibble 6 | #' @export 7 | #' @import tidyr dplyr purrr 8 | #' @importFrom dplyr %>% 9 | #' @importFrom httr stop_for_status GET content 10 | #' @importFrom glue glue 11 | #' @examples 12 | #' # Get all games from 2018 season, note that this will have some overlap 13 | #' # between seasons, for example 2018 returns 2018-01-01 to 2018-12-31 14 | #' get_nfl_schedule(season = "2018") 15 | 16 | get_nfl_schedule <- function(season){ 17 | 18 | message(glue::glue("Returning data for {season}!")) 19 | 20 | max_year <- substr(Sys.Date(), 1,4) 21 | 22 | if(!(as.integer(substr(season, 1, 4)) %in% c(1969:max_year))){ 23 | message(paste("Error: Season must be between 1969 and", max_year)) 24 | } 25 | 26 | # year > 1969 27 | season <- as.character(season) 28 | if(nchar(season) > 4){ 29 | season_dates <- season 30 | } else { 31 | season_dates <- glue::glue("{season}0101-{season}1231") 32 | } 33 | 34 | raw_url <- "http://site.api.espn.com/apis/site/v2/sports/football/nfl/scoreboard" 35 | 36 | raw_get <- httr::GET( 37 | raw_url, 38 | query = list( 39 | limit = 1000, 40 | dates = season_dates 41 | ) 42 | ) 43 | 44 | httr::stop_for_status(raw_get) 45 | 46 | raw_sched <- httr::content(raw_get) 47 | 48 | nfl_data <- raw_sched[["events"]] %>% 49 | tibble(data = .) %>% 50 | unnest_wider(data) %>% 51 | unchop(competitions) %>% 52 | select(-id, -uid, -date, -status) %>% 53 | unnest_wider(competitions) %>% 54 | rename(matchup = name, matchup_short = shortName, game_id = id, game_uid = uid, game_date = date) %>% 55 | hoist(status, 56 | status_name = list("type", "name")) %>% 57 | select(!any_of(c("timeValid", "neutralSite", "conferenceCompetition","recent", "type"))) %>% 58 | unnest_wider(season) %>% 59 | rename(season = year) %>% 60 | select(-any_of("status")) %>% 61 | hoist( 62 | competitors, 63 | home_team_name = list(1, "team", "name"), 64 | home_team_logo = list(1, "team", "logo"), 65 | home_team_abb = list(1, "team", "abbreviation"), 66 | home_team_id = list(1, "team", "id"), 67 | home_team_location = list(1, "team", "location"), 68 | home_team_full = list(1, "team", "displayName"), 69 | home_team_color = list(1, "team", "color"), 70 | home_score = list(1, "score"), 71 | home_win = list(1, "winner"), 72 | home_record = list(1, "records", 1, "summary"), 73 | # away team 74 | away_team_name = list(2, "team", "name"), 75 | away_team_logo = list(2, "team", "logo"), 76 | away_team_abb = list(2, "team", "abbreviation"), 77 | away_team_id = list(2, "team", "id"), 78 | away_team_location = list(2, "team", "location"), 79 | away_team_full = list(2, "team", "displayName"), 80 | away_team_color = list(2, "team", "color"), 81 | away_score = list(2, "score"), 82 | away_win = list(2, "winner"), 83 | away_record = list(2, "records", 1, "summary") 84 | ) %>% 85 | mutate(home_win = as.integer(home_win), 86 | away_win = as.integer(away_win), 87 | home_score = as.integer(home_score), 88 | away_score = as.integer(away_score)) 89 | 90 | if("leaders" %in% names(nfl_data)){ 91 | schedule_out <- nfl_data %>% 92 | hoist( 93 | leaders, 94 | pass_leader_yards = list(1, "leaders", 1, "value"), 95 | pass_leader_stat = list(1, "leaders", 1, "displayValue"), 96 | pass_leader_name = list(1, "leaders", 1, "athlete", "displayName"), 97 | pass_leader_shortname = list(1, "leaders", 1, "athlete", "shortName"), 98 | pass_leader_headshot = list(1, "leaders", 1, "athlete", "headshot"), 99 | pass_leader_team_id = list(1, "leaders", 1, "team", "id"), 100 | pass_leader_pos = list(1, "leaders", 1, "athlete", "position", "abbreviation"), 101 | # rushing 102 | rush_leader_yards = list(2, "leaders", 1, "value"), 103 | rush_leader_stat = list(2, "leaders", 1, "displayValue"), 104 | rush_leader_name = list(2, "leaders", 1, "athlete", "displayName"), 105 | rush_leader_shortname = list(2, "leaders", 1, "athlete", "shortName"), 106 | rush_leader_headshot = list(2, "leaders", 1, "athlete", "headshot"), 107 | rush_leader_team_id = list(2, "leaders", 1, "team", "id"), 108 | rush_leader_pos = list(2, "leaders", 1, "athlete", "position", "abbreviation"), 109 | # receiving 110 | rec_leader_yards = list(3, "leaders", 1, "value"), 111 | rec_leader_stat = list(3, "leaders", 1, "displayValue"), 112 | rec_leader_name = list(3, "leaders", 1, "athlete", "displayName"), 113 | rec_leader_shortname = list(3, "leaders", 1, "athlete", "shortName"), 114 | rec_leader_headshot = list(3, "leaders", 1, "athlete", "headshot"), 115 | rec_leader_team_id = list(3, "leaders", 1, "team", "id"), 116 | rec_leader_pos = list(3, "leaders", 1, "athlete", "position", "abbreviation") 117 | ) 118 | 119 | if('venue' %in% names(schedule_out)){ 120 | schedule_out = schedule_out %>% 121 | hoist( 122 | venue, 123 | venue_id = 'id', 124 | venue_name = 'fullName', 125 | venue_city = list('address', 'city'), 126 | venue_state = list('address', 'state'), 127 | capacity = 'capacity', 128 | indoor = 'indoor' 129 | ) %>% 130 | select(-venue) 131 | } 132 | if("broadcasts" %in% names(schedule_out)) { 133 | schedule_out %>% 134 | hoist( 135 | broadcasts, 136 | broadcast_market = list(1, "market"), 137 | broadcast_name = list(1, "names", 1) 138 | ) %>% 139 | select(!where(is.list)) 140 | } else { 141 | schedule_out 142 | } 143 | } else { 144 | nfl_data %>% select(!where(is.list)) 145 | } 146 | 147 | } 148 | 149 | 150 | -------------------------------------------------------------------------------- /R/get_nfl_standings.R: -------------------------------------------------------------------------------- 1 | #' Get NFL standings for a specific season from ESPN's API 2 | #' 3 | #' @param season Either numeric or character 4 | #' 5 | #' @return Returns a tibble 6 | #' @export 7 | #' @import tidyr dplyr purrr httr 8 | #' @importFrom dplyr %>% 9 | #' @importFrom jsonlite fromJSON 10 | #' @examples 11 | #' # Get standings from 2018 season 12 | #' get_nfl_standings(season = "2018") 13 | #' 14 | #' # Get standings from 2010 season 15 | #' get_nfl_standings(2010) 16 | #' 17 | #' 18 | get_nfl_standings <- function(season = 2019, quiet = FALSE) { 19 | current_year <- as.double(substr(Sys.Date(), 1, 4)) 20 | 21 | # Small error handling to guide the limits on years 22 | if (!between(as.numeric(season), 1990, current_year)) { 23 | stop(paste("Please choose season between 1990 and", current_year)) 24 | } 25 | 26 | if(isFALSE(quiet)){ 27 | message(glue::glue("Returning {season}")) 28 | } 29 | 30 | 31 | 32 | # Working version (no choosing season though) 33 | raw_url <- "https://site.api.espn.com/apis/v2/sports/football/nfl/standings" 34 | 35 | request <- httr::GET( 36 | raw_url, 37 | query = list( 38 | season = season 39 | ) 40 | ) 41 | 42 | if (httr::http_error(request)) { 43 | stop( 44 | sprintf( 45 | "ESPN API request failed [%s]\n%s\n<%s>", 46 | httr::status_code(resp), 47 | parsed$message, 48 | parsed$documentation_url 49 | ), 50 | call. = FALSE 51 | ) 52 | } 53 | 54 | resp <- httr::content(request, as = "text", encoding = "UTF-8") 55 | 56 | raw_standings <- jsonlite::parse_json(resp) 57 | 58 | names_fix <- tibble( 59 | type = c( 60 | "playoffseed", 61 | "wins", 62 | "losses", 63 | "winpercent", 64 | "gamesbehind", 65 | "ties", 66 | "pointsfor", 67 | "pointsagainst", 68 | "differential", 69 | "streak", 70 | "clincher", 71 | "divisionrecord", 72 | "divisionwins", 73 | "divisionties", 74 | "divisionlosses", 75 | "total", 76 | "home", 77 | "road", 78 | "vsdiv", 79 | "vsconf" 80 | ), 81 | abb = c( 82 | "seed", 83 | "wins", 84 | "losses", 85 | "win_pct", 86 | "g_behind", 87 | "ties", 88 | "pts_for", 89 | "pts_against", 90 | "pts_diff", 91 | "streak", 92 | "div_clincher", 93 | "record_div", 94 | "div_wins", 95 | "div_ties", 96 | "div_losses", 97 | "record", 98 | "record_home", 99 | "record_away", 100 | "record_div", 101 | "record_conf" 102 | ) 103 | ) 104 | 105 | full_stand <- raw_standings[["children"]] %>% 106 | tibble(data = .) %>% 107 | unnest_wider(data) %>% 108 | select(conf = abbreviation, standings) %>% 109 | unnest_wider(standings) %>% 110 | unnest_longer(entries) %>% 111 | unnest_wider(entries) %>% 112 | select(conf, season, team, stats) %>% 113 | unnest_wider(team) %>% 114 | hoist(logos, team_logo = list(1, "href")) %>% 115 | select( 116 | conf, 117 | season, 118 | team_id = id, 119 | team_location = location, 120 | team_name = name, 121 | team_abb = abbreviation, 122 | team_full = displayName, 123 | team_logo, 124 | stats 125 | ) %>% 126 | unnest_longer(stats) %>% 127 | unnest_wider(stats) %>% 128 | mutate(value = as.character(value)) %>% 129 | mutate(value = if_else(is.na(value), displayValue, value)) %>% 130 | select(conf:team_logo, type, value) %>% 131 | left_join(names_fix, by = "type") %>% 132 | filter(!(abb == "record_div" & str_length(value) < 2)) %>% 133 | filter(!is.na(abb), abb != "NA") %>% 134 | filter(abb != "div_clincher") %>% 135 | select(-type) %>% 136 | pivot_wider( 137 | names_from = abb, 138 | values_from = value, 139 | id_cols = conf:team_logo 140 | ) %>% 141 | separate(record_home, c("home_wins", "home_losses"), convert = TRUE, extra = "drop") %>% 142 | separate(record_away, c("away_wins", "away_losses"), convert = TRUE, extra = "drop") %>% 143 | separate(record_div, c("div_wins", "div_losses"), convert = TRUE, extra = "drop") %>% 144 | separate(record_conf, c("conf_wins", "conf_losses"), convert = TRUE, extra = "drop") %>% 145 | mutate(across(c(seed:div_losses, -record), as.double)) %>% 146 | group_by(conf) %>% 147 | arrange(conf, desc(win_pct), g_behind) %>% 148 | mutate(seed = row_number()) %>% 149 | ungroup() 150 | 151 | full_stand 152 | } 153 | -------------------------------------------------------------------------------- /R/get_nfl_teams.R: -------------------------------------------------------------------------------- 1 | #' Return all NFL teams with their name, abbreviation, 2 | #' 3 | #' @return tibble 4 | #' @export 5 | #' @importFrom httr content GET 6 | #' @import dplyr purrr 7 | #' @examples 8 | #' 9 | #' get_nfl_teams() 10 | get_nfl_teams <- function() { 11 | message("Getting NFL teams!") 12 | 13 | team_url <- "https://site.api.espn.com/apis/site/v2/sports/football/nfl/teams" 14 | 15 | raw_teams <- httr::GET(team_url, query = list(limit = "50")) %>% 16 | httr::content() 17 | 18 | purrr::pluck(raw_teams, "sports", 1, "leagues", 1, "teams") %>% 19 | dplyr::tibble(value = .) %>% 20 | tidyr::unnest_wider(value) %>% 21 | tidyr::unnest_wider(team) %>% 22 | tidyr::hoist( 23 | logos, 24 | logo = list(1, "href") 25 | ) %>% 26 | dplyr::select(id, name, nickname, abbreviation, displayName, color, alternateColor, logo) %>% 27 | purrr::set_names( 28 | nm = c( 29 | "team_id", "team_name", "team_nickname", "team_abb", "team_full_name", "team_color", 30 | "team_alt_color", "logo" 31 | ) 32 | ) %>% 33 | dplyr::mutate( 34 | team_color = paste0("#", team_color), 35 | team_alt_color = paste0("#", team_alt_color) 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /R/get_nfl_win_prob.R: -------------------------------------------------------------------------------- 1 | #' Get NFL in-game win probabilities for a specific game from ESPN 2 | #' 3 | #' @param game_id Character string - can be acquired from the website of a specific game, or from espnscrapeR::get_nfl_schedule() 4 | #' 5 | #' @return Returns a tibble 6 | #' @export 7 | #' @import tidyr dplyr purrr 8 | #' @importFrom dplyr %>% 9 | #' @importFrom httr GET content stop_for_status 10 | #' @importFrom glue glue 11 | #' @importFrom stringr str_remove 12 | #' @importFrom tibble enframe 13 | #' @examples 14 | #' # Get win prob from specific game 15 | #' get_espn_win_prob(game_id = "401030956") 16 | 17 | 18 | get_espn_win_prob <- function(game_id){ 19 | 20 | raw_url <- glue::glue("https://sports.core.api.espn.com/v2/sports/football/leagues/nfl/events/{game_id}/competitions/{game_id}/probabilities?limit=1000") 21 | 22 | extract_play_text <- function(text_in) { 23 | text_in %>% 24 | str_remove("\\?lang=en®ion=us") %>% 25 | str_remove("http://sports.core.api.espn.com/v2/sports/football/leagues/nfl/events/[:digit:]+/competitions/[:digit:]+/probabilities/") 26 | 27 | } 28 | 29 | extract_team_text <- function(text_in){ 30 | text_in %>% 31 | str_remove("http://sports.core.api.espn.com/v2/sports/football/leagues/nfl/seasons/[:digit:]+/teams/") %>% 32 | str_remove("\\?lang=en®ion=us") 33 | } 34 | 35 | raw_get <- httr::GET(raw_url) 36 | 37 | httr::stop_for_status(raw_get) 38 | 39 | raw_json <- httr::content(raw_get) 40 | 41 | raw_df <- raw_json[["items"]] %>% 42 | enframe() %>% 43 | unnest_wider(value) %>% 44 | rename(row_id = name, play_url_ref = `$ref`) %>% 45 | mutate(play_id = extract_play_text(play_url_ref)) %>% 46 | hoist(homeTeam, home_team_id = "$ref") %>% 47 | hoist(awayTeam, away_team_id = "$ref") %>% 48 | select(-play_url_ref, -where(is.list), -any_of(c("lastModified", "secondsLeft"))) %>% 49 | mutate( 50 | home_team_id = extract_team_text(home_team_id), 51 | away_team_id = extract_team_text(away_team_id) 52 | ) %>% 53 | janitor::clean_names() %>% 54 | mutate(game_id = game_id) 55 | 56 | raw_df 57 | 58 | } 59 | -------------------------------------------------------------------------------- /R/get_sharpe_data.R: -------------------------------------------------------------------------------- 1 | #' Get some of Lee Sharpe's datasets from GitHub 2 | #' 3 | #' @param dataset Character only, one of "airports", "closing_lines", "draft_picks", "draft_values", "games", "logos", "pff_pfr_map_v1", "positions", "rosters", "sc_lines", "standings", "teamcolors", "teams", "trades", "win_totals" 4 | #' @importFrom glue glue 5 | #' @importFrom readr read_csv 6 | #' @return tibble 7 | #' @export 8 | #' 9 | #' @examples 10 | #' 11 | #' # Get NFL games and their outcomes 12 | #' get_sharpe_data(dataset = "games") 13 | #' 14 | #' # Get team abbreviations/names/etc by year 15 | #' get_sharpe_data(dataset = "teams") 16 | 17 | get_sharpe_data <- function(dataset = "games"){ 18 | 19 | datasets <- c( 20 | "airports", 21 | "closing_lines", 22 | "draft_picks", 23 | "draft_values", 24 | "games", 25 | "logos", 26 | "pff_pfr_map_v1", 27 | "positions", 28 | "rosters", 29 | "sc_lines", 30 | "standings", 31 | "teamcolors", 32 | "teams", 33 | "trades", 34 | "win_totals" 35 | ) 36 | 37 | collapsed_data <- paste0(datasets, collapse = ", ") 38 | 39 | if(!(dataset %in% datasets)){ 40 | stop(c("Dataset not found, please use one of: ", collapsed_data)) 41 | } 42 | 43 | read_url <- glue::glue("https://raw.githubusercontent.com/leesharpe/nfldata/master/data/{dataset}.csv") 44 | 45 | raw_df <- read_csv(read_url, guess_max = 10000) 46 | 47 | raw_df 48 | } 49 | -------------------------------------------------------------------------------- /R/gt_hulk_color.R: -------------------------------------------------------------------------------- 1 | #' Apply 'hulk' palette to specific columns in a gt table. The hulk names comes from the idea of a divergin purple and green theme that is colorblind safe and visually appealing. It is a useful alternative to the red/green palette. 2 | #' 3 | #' @param gt_object An existing gt table object 4 | #' @param ... columns to apply color to 5 | #' @param trim trim the palette to give less intense maximal colors 6 | #' @param reverse reverse the color palette. The default is green = high and purple = low, but reverse = TRUE will make purple high and green low. 7 | #' @return Returns a gt table 8 | #' @importFrom dplyr %>% 9 | #' @importFrom scales col_numeric 10 | #' @export 11 | #' @import gt 12 | #' @examples 13 | #' # basic use 14 | #' mtcars |> 15 | #' head() |> 16 | #' gt::gt() |> 17 | #' gt_hulk_color(mpg) 18 | #' 19 | #' mtcars |> 20 | #' head() |> 21 | #' gt::gt() |> 22 | #' # trim gives small range of colors 23 | #' gt_hulk_color(mpg:disp, trim = TRUE) 24 | #' 25 | #' # option to reverse the color palette 26 | #' mtcars |> 27 | #' head() |> 28 | #' gt::gt() |> 29 | #' # trim gives small range of colors 30 | #' gt_hulk_color(mpg:disp, rev = TRUE) 31 | 32 | 33 | gt_hulk_color <- function(gt_object, ..., domain = NULL, trim = FALSE, reverse = FALSE){ 34 | 35 | pal_hex <- c("#1b7837", "#7fbf7b", "#d9f0d3", "#f7f7f7", 36 | "#e7d4e8", "#af8dc3", "#762a83") 37 | 38 | pal_hex <- if(isTRUE(trim)){ 39 | pal_hex[2:6] 40 | }else{ 41 | pal_hex 42 | } 43 | 44 | pal_hex <- if(isTRUE(reverse)){ 45 | rev(pal_hex) 46 | }else{ 47 | pal_hex 48 | } 49 | 50 | hulk_pal <- function(x){ 51 | scales::col_numeric( 52 | pal_hex, 53 | domain = domain 54 | )(x) 55 | } 56 | 57 | gt::data_color( 58 | gt_object, 59 | columns = ..., 60 | colors = hulk_pal 61 | ) 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /R/gt_theme_538.R: -------------------------------------------------------------------------------- 1 | #' Apply FiveThirtyEight theme to a gt table 2 | #' 3 | #' @param data An existing gt table object 4 | #' @param ... Optional additional arguments to gt::table_options() 5 | #' @return Returns a tibble 6 | #' @importFrom dplyr %>% 7 | #' @export 8 | #' @import gt 9 | 10 | gt_theme_538 <- function(data,...) { 11 | data %>% 12 | opt_all_caps() %>% 13 | opt_table_font( 14 | font = list( 15 | google_font("Chivo"), 16 | default_fonts() 17 | ) 18 | ) %>% 19 | tab_style( 20 | style = cell_borders( 21 | sides = "bottom", color = "#00000000", weight = px(2) 22 | ), 23 | locations = cells_body( 24 | columns = everything(), 25 | # This is a relatively sneaky way of changing the bottom border 26 | # Regardless of data size 27 | rows = nrow(data$`_data`) 28 | ) 29 | ) %>% 30 | tab_options( 31 | column_labels.background.color = "white", 32 | table.border.top.width = px(3), 33 | table.border.top.color = "#00000000", 34 | table.border.bottom.color = "#00000000", 35 | table.border.bottom.width = px(3), 36 | column_labels.border.top.width = px(3), 37 | column_labels.border.top.color = "#00000000", 38 | column_labels.border.bottom.width = px(3), 39 | column_labels.border.bottom.color = "black", 40 | data_row.padding = px(3), 41 | source_notes.font.size = 12, 42 | table.font.size = 16, 43 | heading.align = "left", 44 | ... 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /R/gt_theme_espn.R: -------------------------------------------------------------------------------- 1 | #' Apply ESPN theme to a gt table 2 | #' 3 | #' @param data An existing gt table object 4 | #' @param ... Optional additional arguments to gt::table_options() 5 | #' @return Returns a tibble 6 | #' @importFrom dplyr %>% 7 | #' @export 8 | #' @import gt 9 | 10 | 11 | gt_theme_espn <- function(data, ...){ 12 | data %>% 13 | opt_all_caps() %>% 14 | opt_table_font( 15 | font = list( 16 | google_font("Lato"), 17 | default_fonts() 18 | ) 19 | ) %>% 20 | opt_row_striping() %>% 21 | tab_options( 22 | row.striping.background_color = "#fafafa", 23 | table_body.hlines.color = "#f6f7f7", 24 | source_notes.font.size = 12, 25 | table.font.size = 16, 26 | heading.align = "left", 27 | heading.title.font.size = 24, 28 | table.border.top.color = "#00000000", 29 | table.border.top.width = px(3), 30 | data_row.padding = px(7), 31 | ... 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /R/scrape_espn_stats.R: -------------------------------------------------------------------------------- 1 | #' Scrape NFL stats from ESPN 2 | #' 3 | #' @param stats character - either receiving, passing, or rushing 4 | #' @param season character or numeric - greater than 1990 5 | #' @param season_type character - either Regular or Playoffs 6 | #' @import purrr tidyr dplyr stringr 7 | #' @importFrom dplyr %>% 8 | #' @importFrom rvest html_table 9 | #' @importFrom xml2 read_html 10 | #' @importFrom glue glue 11 | #' @importFrom readr parse_number 12 | #' @return tibble 13 | #' @export 14 | #' 15 | #' @examples 16 | #' scrape_espn_stats(season = 2000, stats = "passing") 17 | #' 18 | 19 | scrape_espn_stats <- function(season = 2019, stats = "receiving", season_type = "Regular") { 20 | current_year <- as.double(substr(Sys.Date(), 1, 4)) 21 | 22 | if (!season_type %in% c("Regular", "Playoffs")) { 23 | stop("Please choose season_type of 'Regular' or 'Playoffs'") 24 | } 25 | 26 | if (!stats %in% c("receiving", "rushing", "passing")) { 27 | stop("Please choose season_type of 'receiving', 'rushing', or 'passing'!") 28 | } 29 | 30 | if (!dplyr::between(as.numeric(season), 1990, current_year)) { 31 | stop(paste("Please choose season between 1990 and", current_year)) 32 | } 33 | 34 | message( 35 | dplyr::if_else( 36 | season_type == "Regular", 37 | glue::glue("Scraping {stats} stats from {season} {season_type} season!"), 38 | glue::glue("Scraping {stats} stats from {season} {season_type}!") 39 | ) 40 | ) 41 | 42 | season_type <- dplyr::if_else(season_type == "Regular", "2", "3") 43 | 44 | url_in <- glue::glue("https://www.espn.com/nfl/stats/player/_/stat/{stats}/season/{season}/seasontype/{season_type}&limit=500") 45 | 46 | pass_n <- c( 47 | "season", "season_type", "pass_rank", "name", "team", "pos", 48 | "games_played", "pass_completed", "pass_attempts", "comp_percent", 49 | "pass_yards", "pass_avg", "pass_yards_game", "pass_long", "pass_td", 50 | "pass_int", "sack", "sack_yards", "qbr", "pass_rating" 51 | ) 52 | 53 | rush_n <- c( 54 | "season", "season_type", "rush_rank", "name", "team", "pos", 55 | "games_played", "rush_att", "rush_yards", "rush_avg", "rush_long", 56 | "rush_20plus", "rush_td", "rush_yards_game", "fumble", "fumble_lost", 57 | "rush_first_down" 58 | ) 59 | 60 | 61 | rec_n <- c( 62 | "season", "season_type", "rec_rank", "name", "team", "pos", 63 | "games_played", "receptions", "targets", "rec_yards", "rec_avg", 64 | "rec_td", "rec_long", "rec_20plus", "rec_yards_game", "fumble", 65 | "fumble_lost", "yards_after_catch", "rec_first_down" 66 | ) 67 | 68 | 69 | fix_names <- dplyr::case_when( 70 | stats == "passing" ~ list(pass_n), 71 | stats == "rushing" ~ list(rush_n), 72 | stats == "receiving" ~ list(rec_n) 73 | )[[1]] 74 | 75 | url_in %>% 76 | xml2::read_html() %>% 77 | rvest::html_table(fill = TRUE) %>% 78 | dplyr::bind_cols() %>% 79 | janitor::clean_names() %>% 80 | dplyr::as_tibble() %>% 81 | dplyr::mutate( 82 | # get rid of the comma and convert to numeric 83 | yds = readr::parse_number(as.character(yds)), 84 | # if there is a player with multiple teams 85 | # We need to figure out how many teams and then 86 | slash_ct = stringr::str_count(name, "/"), 87 | chr_ct = slash_ct * (-4) - 3, 88 | team = stringr::str_sub(name, chr_ct), 89 | team = stringr::str_remove(team, "[::a-z::]"), 90 | team = dplyr::if_else( 91 | # Find players with Jr/Sr/II/III/IV etc and drop them 92 | stringr::str_sub(team, 1) %in% c("I", "V", ".") & !stringr::str_detect(team, "IND"), 93 | # TRUE 94 | stringr::str_sub(team, 2, stringr::str_length(team)), 95 | # FALSE 96 | team 97 | ), 98 | team = stringr::str_remove(team, "\\."), 99 | name = stringr::str_remove(name, team) 100 | ) %>% 101 | dplyr::arrange(desc(yds)) %>% 102 | dplyr::mutate( 103 | rank = dplyr::row_number(), 104 | season = season, 105 | season_type = dplyr::if_else(season_type == 2, "Regular", "Playoffs") 106 | ) %>% 107 | dplyr::select( 108 | season, season_type, rank, name, team, dplyr::everything(), 109 | -rk, -slash_ct, -chr_ct 110 | ) %>% 111 | purrr::set_names(nm = fix_names) 112 | } 113 | -------------------------------------------------------------------------------- /R/scrape_espn_win_rate.R: -------------------------------------------------------------------------------- 1 | #' Scrape ESPN Pass/Run Block/Rush Win Rates ratings for a specific season from ESPN's site 2 | #' 3 | #' @return Returns a tibble 4 | #' @export 5 | #' @import dplyr stringr 6 | #' @importFrom dplyr %>% 7 | #' @importFrom readr parse_number 8 | #' @importFrom tidyr separate pivot_longer 9 | #' @importFrom xml2 read_html 10 | #' @importFrom purrr map2_dfr 11 | #' @importFrom rvest html_node html_text 12 | #' @importFrom tibble enframe 13 | #' @examples 14 | #' # Get off and def pass/run win rates 15 | #' scrape_espn_win_rate() 16 | 17 | scrape_espn_win_rate <- function(season = 2023){ 18 | 19 | if(!(as.numeric(season) %in% c(2019:2023))) stop("Data available for 2019-2023") 20 | 21 | pbwr_2022 <- "https://www.espn.com/nfl/story/_/id/34536376/2022-nfl-pass-rushing-run-stopping-blocking-leaderboard-win-rate-rankings-top-players-teams" 22 | pbwr_2021 <- "https://www.espn.com/nfl/story/_/id/32176833/2021-nfl-pass-rushing-run-stopping-blocking-leaderboard-win-rate-rankings" 23 | pbwr_2020 <- "https://www.espn.com/nfl/story/_/id/29939464/2020-nfl-pass-rushing-run-stopping-blocking-leaderboard-win-rate-rankings" 24 | pbwr_2019 <- "https://www.espn.com/nfl/story/_/id/27584726/nfl-pass-blocking-pass-rushing-rankings-2019-pbwr-prwr-leaderboard#prwrteam" 25 | pbwr_2018 <- "https://www.espn.com/nfl/story/_/id/25074144/nfl-pass-blocking-pass-rushing-stats-final-leaderboard-pass-block-win-rate-pass-rush-win-rate" 26 | stats_in <- c( 27 | "Pass Rush Win Rate", 28 | "Run Stop Win Rate", 29 | "Pass Block Win Rate", 30 | "Run Block Win Rate" 31 | ) 32 | 33 | stat_2019 <- c( 34 | "Pass Rush Win Rate", 35 | "Pass Block Win Rate" 36 | ) 37 | 38 | # 2023 specific code: 39 | 40 | if(season == 2023){ 41 | url_2023 <- "https://www.espn.com/nfl/story/_/id/38356170/2023-nfl-pass-rush-run-stop-blocking-win-rate-rankings-top-players-teams" 42 | 43 | raw_html <- read_html(url_2023) 44 | 45 | tab_23 <- raw_html %>% 46 | html_table() %>% 47 | .[[9]] %>% 48 | pivot_longer(cols = -1, names_to = "stat", values_to = "win_rate") %>% 49 | mutate( 50 | stat = case_when( 51 | stat == "PRWR" ~ "Pass Rush Win Rate", 52 | stat == "RSWR" ~ "Run Stop Win Rate", 53 | stat == "PBWR" ~ "Pass Block Win Rate", 54 | stat == "RBWR" ~ "Run Block Win Rate" 55 | )) %>% 56 | mutate( 57 | # extract just the string that is before a '%' 58 | win_rate = str_extract(win_rate, "^[^%]+"), 59 | # convert to a number 60 | win_pct = as.numeric(win_rate) 61 | ) %>% 62 | mutate(date_updated = NA, season = 2023) %>% 63 | arrange(stat, desc(win_pct)) %>% 64 | group_by(stat) %>% 65 | mutate(stat_rank = row_number()) %>% 66 | ungroup() %>% 67 | select(stat, stat_rank, team = Team, win_pct, date_updated, season) 68 | 69 | return(tab_23) 70 | } 71 | 72 | 73 | raw_html <- read_html( 74 | case_when( 75 | season == 2019 ~ pbwr_2019, 76 | season == 2020 ~ pbwr_2020, 77 | season == 2021 ~ pbwr_2021, 78 | season == 2022 ~ pbwr_2022 79 | ) 80 | ) 81 | 82 | date_updated <- raw_html %>% 83 | html_node("#article-feed > article:nth-child(1) > div > div.article-body > div.article-meta > span > span") %>% 84 | html_text() 85 | 86 | raw_text <- raw_html %>% 87 | html_nodes("#article-feed > article:nth-child(1) > div > div.article-body > p") %>% 88 | html_text() 89 | 90 | tibble::enframe(raw_text) %>% 91 | filter(str_detect(value, "1. ")) %>% 92 | mutate(name = if_else(season == 2019, list(stat_2019), list(stats_in))[[1]]) %>% 93 | mutate(value = str_split(value, "\n")) %>% 94 | unnest_longer(value) %>% 95 | separate(value, into = c("rank", "team", "win_pct"), sep = "\\. |, ") %>% 96 | mutate( 97 | rank = as.integer(rank), 98 | win_pct = str_remove(win_pct, "%"), 99 | win_pct = as.double(win_pct), 100 | date_updated = date_updated, 101 | season = season 102 | ) %>% 103 | rename(stat = name, stat_rank = rank) 104 | 105 | } 106 | -------------------------------------------------------------------------------- /R/scrape_fpi.R: -------------------------------------------------------------------------------- 1 | #' Scrape ESPN FPI ratings for a specific season from ESPN's site 2 | #' 3 | #' @param season Either numeric or character 4 | #' @param stat One of 'FPI', 'EFF' or 'PROJ' 5 | #' 6 | #' @return Returns a tibble 7 | #' @export 8 | #' @import dplyr stringr 9 | #' @importFrom dplyr %>% 10 | #' @importFrom xml2 read_html 11 | #' @importFrom rvest html_node html_nodes html_text html_table html_attr 12 | #' @importFrom glue glue 13 | #' @examples 14 | #' # Get team offensive and defensive efficiency from 2020 season 15 | #' scrape_fpi(2020, stat = "EFF") 16 | 17 | scrape_fpi <- function(season = 2020, stat = "FPI"){ 18 | 19 | # season must be >= 2015 20 | current_year <- str_sub(Sys.Date(), 1, 4) %>% as.integer() 21 | 22 | if (between(season, 2015, current_year) == FALSE) 23 | stop(glue::glue("Season must be between 2015 and {current_year}")) 24 | 25 | if (!(stat %in% c("FPI", "EFF", "PROJ")) == TRUE) 26 | stop(glue::glue("Stat must be one of 'FPI', 'EFF' or 'PROJ'")) 27 | 28 | message(glue::glue("Scraping {stat} for {season}!")) 29 | 30 | url_fpi <- glue::glue("https://www.espn.com/nfl/fpi/_/season/{season}") 31 | url_proj <- glue::glue("https://www.espn.com/nfl/fpi/_/view/projections/season/{season}") 32 | url_eff <- glue::glue("https://www.espn.com/nfl/fpi/_/view/efficiencies/season/{season}") 33 | 34 | if (stat == "FPI"){ 35 | fpi_html <- read_html(url_fpi) 36 | 37 | fpi_names <- c( 38 | "team", "w_l", "fpi", "rk", "trend", "off", 39 | "def", "st", "sos", "rem_sos", "avgwp" 40 | ) 41 | 42 | trend_data <- fpi_html %>% 43 | html_nodes("td:nth-child(4) > div") %>% 44 | html_attr("class") 45 | 46 | table_fpi <- suppressWarnings( 47 | fpi_html %>% 48 | html_table() %>% 49 | cbind.data.frame() %>% 50 | set_names(nm = fpi_names) %>% 51 | slice(-1) %>% 52 | mutate( 53 | across(c(fpi, off:st), as.double), 54 | across(c(rk, trend, sos:avgwp), as.integer), 55 | season = season 56 | ) %>% 57 | mutate( 58 | trend = if_else( 59 | trend_data[rk] == "trend negative", 60 | as.integer(trend * -1), 61 | trend) 62 | ) %>% 63 | select(season, everything()) %>% 64 | tibble() 65 | ) 66 | 67 | table_fpi 68 | 69 | } else if (stat == "PROJ"){ 70 | proj_html <- read_html(url_proj) 71 | 72 | table_proj <- proj_html %>% 73 | html_table() %>% 74 | bind_cols() %>% 75 | set_names(nm = names(.) %>% 76 | tolower() %>% 77 | str_replace_all("-| ", "_") %>% 78 | str_replace("%", "_pct")) %>% 79 | mutate(season = season) %>% 80 | select(season, everything()) %>% 81 | tibble() 82 | 83 | table_proj 84 | } else if (stat == "EFF"){ 85 | eff_html <- read_html(url_eff) 86 | 87 | eff_names <- c( 88 | "team", "w_l", "eff_ove", "rnk_ove", "eff_off", "rnk_off", 89 | "eff_def", "rnk_def", "eff_spe", "rnk_spe" 90 | ) 91 | 92 | table_eff <- 93 | eff_html %>% 94 | html_table() %>% 95 | cbind.data.frame() %>% 96 | set_names(nm=eff_names) %>% 97 | slice(-1) %>% 98 | mutate( 99 | across(contains("rnk"), as.integer), 100 | across(contains("eff"), as.double), 101 | season = season 102 | ) %>% 103 | select(season, everything()) %>% 104 | tibble() 105 | 106 | table_eff 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /R/scrape_nfl_qbr.R: -------------------------------------------------------------------------------- 1 | #' Scrape ESPN QBR for NFL football 2 | #' 3 | #' Scrapes QBR tables for NFL QBs by specific week or at season level. 4 | #' 5 | #' This function supercedes `get_nfl_qbr` due to the ESPN API altering past data. 6 | #' 7 | #' @param season Either numeric or character. Must be between 2006 and current season. 8 | #' @param week Either NA to return season, week 1 to 4 for playoffs, or 1 to 17 for regular season. 9 | #' @param season_type Character - either "Regular" or "Playoffs" 10 | #' @return Returns a tibble 11 | #' @import tidyr dplyr purrr 12 | #' @importFrom dplyr %>% 13 | #' @importFrom jsonlite fromJSON 14 | #' @importFrom glue glue 15 | #' @examples 16 | #' # Scrape ALL Playoff QBR from 2016 season 17 | #' scrape_nfl_qbr("2016", season_type = "Playoffs", week = NA) 18 | #' 19 | #' # Scrape Regular season QBR for week 4 of 2019 20 | #' scrape_nfl_qbr("2019", season_type = "Regular", week = 4) 21 | 22 | scrape_nfl_qbr <- function(season = 2019, week = NA, season_type = "Regular") { 23 | 24 | current_year <- as.double(substr(Sys.Date(), 1, 4)) 25 | 26 | # Error handling to correct season type 27 | if (!season_type %in% c("Regular", "Playoffs")) { 28 | stop("Please choose season_type of 'Regular' or 'Playoffs'") 29 | } 30 | 31 | # Error handling for limits on season 32 | if (!dplyr::between(as.numeric(season), 2006, current_year)) { 33 | stop(paste("Please choose season between 2006 and", current_year)) 34 | } 35 | 36 | # Error handling for limits on regular season weeks 37 | if (!is.na(week) & season_type == "Regular" & !dplyr::between(as.numeric(week), 1, 17)) { 38 | stop("Please choose regular season week between 1 and 17") 39 | } 40 | 41 | # Error handling for limits on playoff weeks 42 | if (!is.na(week) & season_type == "Playoffs" & !dplyr::between(as.numeric(week), 1, 4)) { 43 | stop("Please choose Playoff week between 1 and 4") 44 | } 45 | 46 | # Error handling for missing data from ESPN 47 | if (!is.na(week) & season_type == "Playoffs" & as.numeric(season) == 2017) { 48 | stop("ESPN has missing Playoff data for 2017") 49 | } 50 | 51 | week_current <- dplyr::if_else( 52 | # Logic check to fix years where superbowl = 5 53 | season_type == "Playoffs" & as.numeric(week) == 4 & season >= 2009, 54 | # outcome = 5 55 | 5, 56 | # default to normal week 57 | as.numeric(week) 58 | ) 59 | 60 | # Add useful messages - separated by week 61 | # message( 62 | # dplyr::if_else( 63 | # is.na(week), 64 | # glue::glue("Scraping QBR totals for {season}!"), 65 | # glue::glue("Scraping weekly QBR for week {week} of {season}!") 66 | # ) 67 | # ) 68 | 69 | # Build up URL 70 | url <- dplyr::case_when( 71 | season_type == "Playoffs" & is.na(week) ~ glue::glue("https://www.espn.com/nfl/qbr/_/season/{season}/seasontype/3"), 72 | season_type == "Playoffs" & !is.na(week) ~ glue::glue("https://www.espn.com/nfl/qbr/_/view/weekly/season/{season}/seasontype/3/week/{week_current}"), 73 | season_type == "Regular" & is.na(week) ~ glue::glue("https://www.espn.com/nfl/qbr/_/season/{season}/seasontype/2"), 74 | season_type == "Regular" & !is.na(week) ~ glue::glue("https://www.espn.com/nfl/qbr/_/view/weekly/season/{season}/seasontype/2/week/{week}"), 75 | TRUE ~ NA_character_ 76 | ) 77 | 78 | raw_tables <- url %>% 79 | xml2::read_html() %>% 80 | rvest::html_table() 81 | 82 | comb_df <- raw_tables[[1]] 83 | 84 | # comb_df <- cbind(raw_tables[[1]], raw_tables[[2]]) 85 | 86 | comb_df %>% 87 | janitor::clean_names() %>% 88 | dplyr::as_tibble() %>% 89 | dplyr::mutate(season = season) %>% 90 | dplyr::mutate( 91 | team = dplyr::if_else( 92 | stringr::str_detect(name, "/"), 93 | stringr::str_sub(name, 7), 94 | stringr::str_sub(name, -3) 95 | ) 96 | ) %>% 97 | dplyr::select(name, team) %>% 98 | dplyr::mutate(team = stringr::str_remove(team, "[:lower:]+"), 99 | name = stringr::str_remove(name, team)) 100 | } 101 | 102 | 103 | -------------------------------------------------------------------------------- /R/scrape_nfl_standings.R: -------------------------------------------------------------------------------- 1 | #' Scrape NFL standings for a specific season from ESPN's site 2 | #' 3 | #' @param season Either numeric or character 4 | #' @param add_superbowls Join superbowl winners (appropriate for historical data or after season over) 5 | #' 6 | #' @return Returns a tibble 7 | #' @export 8 | #' @import dplyr 9 | #' @importFrom dplyr %>% 10 | #' @importFrom xml2 read_html 11 | #' @importFrom rvest html_node html_nodes html_text html_table html_attr 12 | #' @importFrom glue glue 13 | #' @examples 14 | #' # Get standings from 2018 season 15 | #' scrape_nfl_standings(season = "2018") 16 | #' 17 | #' # Get standings from 2010 season 18 | #' scrape_nfl_standings(2000, add_superbowls = TRUE) 19 | scrape_nfl_standings <- function(season, add_superbowls = FALSE) { 20 | current_year <- as.double(substr(Sys.Date(), 1, 4)) 21 | 22 | # Small error handling to guide the limits on years 23 | if (!between(as.numeric(season), 1994, current_year)) { 24 | stop(paste("Please choose season between 1994 and", current_year)) 25 | } 26 | 27 | message(glue::glue("Scraping {season}")) 28 | 29 | season_url <- glue::glue("https://www.espn.com/nfl/standings/_/season/{season}/group/conference") 30 | 31 | raw_season <- season_url %>% 32 | xml2::read_html() 33 | 34 | # Small error handling for failure 35 | if (length(html_table(raw_season)) == 0) { 36 | stop(paste("Table not found for", current_year)) 37 | } 38 | 39 | team_names <- raw_season %>% 40 | rvest::html_node("div.tabs__content") %>% 41 | rvest::html_nodes("span.pr4.TeamLink__Logo") %>% 42 | rvest::html_nodes("img") %>% 43 | rvest::html_attr("alt") 44 | 45 | team_abb <- raw_season %>% 46 | rvest::html_nodes("abbr") %>% 47 | rvest::html_text() 48 | 49 | raw_tables <- raw_season %>% 50 | rvest::html_nodes("div.Table__Scroller") %>% 51 | rvest::html_nodes("table") %>% 52 | rvest::html_table() 53 | 54 | nfc_table <- raw_tables[[1]] %>% 55 | dplyr::mutate(conf_name = "AFC") 56 | 57 | 58 | afc_table <- raw_tables[[2]] %>% 59 | dplyr::mutate(conf_name = "NFC") 60 | 61 | raw_standings <- dplyr::bind_rows(nfc_table, afc_table) %>% 62 | dplyr::as_tibble() %>% 63 | dplyr::group_by(conf_name) %>% 64 | dplyr::mutate(conf_rank = dplyr::row_number()) %>% 65 | dplyr::ungroup() %>% 66 | dplyr::mutate( 67 | team_abb = team_abb, 68 | team = team_names, 69 | season = as.integer(season) 70 | ) %>% 71 | dplyr::mutate( 72 | playoffs = dplyr::if_else(conf_rank <= 6, "Made Playoffs", "Missed Playoffs"), 73 | playoff_rank = dplyr::case_when( 74 | conf_rank == 1 ~ "Division, Home Field and Bye", 75 | conf_rank == 2 ~ "Division and Bye", 76 | conf_rank == 2 & season >= 2020 ~ "Division", 77 | conf_rank == 3 ~ "Division", 78 | conf_rank == 4 ~ "Division", 79 | conf_rank == 5 ~ "Wild Card", 80 | conf_rank == 6 ~ "Wild Card", 81 | conf_rank == 7 & season >= 2020 ~ "Wild Card", 82 | TRUE ~ "Missed Playoffs" 83 | ) 84 | ) %>% 85 | dplyr::mutate(team_logo = glue::glue( 86 | "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nfl/500/scoreboard/{team_abb}.png&h=500&w=500" 87 | )) %>% 88 | dplyr::select(team, team_abb, season, conf_name, conf_rank, dplyr::everything()) 89 | 90 | if (add_superbowls == TRUE) { 91 | message("Adding Superbowls!") 92 | 93 | sb_out <- scrape_superbowls() 94 | 95 | raw_standings %>% 96 | purrr::set_names(nm = tolower(names(raw_standings))) %>% 97 | dplyr::left_join(sb_out, by = c("team", "season")) 98 | } else { 99 | raw_standings %>% 100 | purrr::set_names(nm = tolower(names(raw_standings))) 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /R/scrape_nfl_weekly_standings.R: -------------------------------------------------------------------------------- 1 | #' Scrape NFL weekly outcomes by week 2 | #' 3 | #' @param season character or numeric - greater than 1990 4 | #' @param tidy logical - either TRUE to stack data by game/week or FALSE to return table as is 5 | #' @import purrr tidyr dplyr stringr 6 | #' @importFrom dplyr %>% 7 | #' @importFrom rvest html_table html_node 8 | #' @importFrom xml2 read_html 9 | #' @importFrom glue glue 10 | #' @return tibble 11 | #' @export 12 | #' 13 | #' @examples 14 | #' 15 | #' # Here we run w/ tidy = FALSE to get the exact table from PFR 16 | #' scrape_nfl_weekly_standings(season = 2020, tidy = FALSE) 17 | #' 18 | #' # Here we scrape the outcome and stack the games on top of eachother 19 | #' scrape_nfl_weekly_standings(season = 2020, tidy = TRUE) 20 | 21 | scrape_nfl_weekly_standings <- function(season = 2020, tidy = FALSE) { 22 | 23 | current_year <- as.double(substr(Sys.Date(), 1, 4)) 24 | 25 | if (!dplyr::between(as.numeric(season), 1990, current_year)) { 26 | stop(paste("Please choose season between 1990 and", current_year)) 27 | } 28 | 29 | url <- glue::glue("https://www.pro-football-reference.com/years/{season}/games.htm") 30 | 31 | message(glue::glue("Scraping standings from {season}!")) 32 | 33 | raw_html <- read_html(url) 34 | 35 | replace_names <- c( 36 | "week", "day", "date", "time", "winner_tie", "home_team", "loser_tie", 37 | "boxscore", "pts_winner", "pts_loser", "yds_winner", "turnovers_winner", 38 | "yds_loser", "turnovers_loser" 39 | ) 40 | 41 | 42 | raw_df <- raw_html %>% 43 | html_node("#games") %>% 44 | html_table() %>% 45 | set_names(nm = replace_names) %>% 46 | tibble() 47 | 48 | playoff_teams <- c( 49 | raw_df %>% filter(grepl(x = week, pattern = "Wild|Div|Conf|Super")) %>% 50 | pull(winner_tie), 51 | raw_df %>% filter(grepl(x = week, pattern = "Wild|Div|Conf|Super")) %>% 52 | pull(loser_tie) 53 | ) %>% 54 | unique() 55 | 56 | clean_df <- raw_df %>% 57 | filter(!week %in% c("", "Week")) %>% 58 | mutate(season = as.integer(season)) %>% 59 | select(season, everything(), -boxscore) %>% 60 | mutate_at(vars(pts_winner:turnovers_loser), as.double) %>% 61 | mutate(game_num = row_number()) %>% 62 | mutate( 63 | winner = case_when( 64 | pts_winner > pts_loser ~ 1, 65 | pts_winner < pts_loser ~ 0, 66 | pts_winner == pts_loser ~ NA_real_, 67 | TRUE ~ NA_real_ 68 | ) 69 | ) %>% 70 | select(game_num, everything()) 71 | 72 | 73 | tidy_df <- clean_df %>% 74 | select(game_num, season:winner_tie, home_team, contains("winner")) %>% 75 | mutate(home_team = if_else(home_team == "@", 0, 1)) %>% 76 | set_names( 77 | nm = c( 78 | "game_num", "season", "week", "day", "date", "time", "team", "home_team", 79 | "points", "yards", "turnovers", "winner" 80 | ) 81 | ) %>% 82 | bind_rows( 83 | clean_df %>% 84 | select(game_num, season:time, loser_tie, home_team, contains("loser"), winner) %>% 85 | mutate(home_team = if_else(home_team == "@", 1, 0)) %>% 86 | mutate(winner = if_else(!is.na(winner), 0, NA_real_)) %>% 87 | set_names( 88 | nm = c( 89 | "game_num", "season", "week", "day", "date", "time", "team", "home_team", 90 | "points", "yards", "turnovers", "winner" 91 | ) 92 | ) 93 | ) %>% 94 | mutate(playoffs = if_else(team %in% c(playoff_teams), 1, 0)) 95 | 96 | 97 | if (tidy == TRUE) { 98 | tidy_df 99 | } else if (tidy == FALSE) { 100 | clean_df 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /R/scrape_superbowls.R: -------------------------------------------------------------------------------- 1 | #' Scrape NFL superbowl winners for all years 2 | #' @return Returns a tibble 3 | #' @export 4 | #' @import dplyr 5 | #' @importFrom dplyr %>% 6 | #' @importFrom xml2 read_html 7 | #' @importFrom rvest html_node html_nodes html_text html_table html_attr 8 | #' @examples 9 | #' # Get superbowls 10 | #' scrape_superbowls() 11 | scrape_superbowls <- function() { 12 | 13 | # Table of all SB winners 14 | sb_url <- "https://en.wikipedia.org/wiki/List_of_Super_Bowl_champions" 15 | 16 | raw_wiki_sb <- sb_url %>% 17 | xml2::read_html() 18 | 19 | win_team <- raw_wiki_sb %>% 20 | rvest::html_nodes("td:nth-child(3)") %>% 21 | rvest::html_node("[title]") %>% 22 | rvest::html_attr("title") %>% 23 | gsub(" season", "", .) 24 | 25 | 26 | lose_team <- raw_wiki_sb %>% 27 | rvest::html_nodes("td:nth-child(5)") %>% 28 | rvest::html_node("[title]") %>% 29 | rvest::html_attr("title") %>% 30 | gsub(" season", "", .) 31 | 32 | raw_scores <- raw_wiki_sb %>% 33 | rvest::html_nodes("#mw-content-text > div > table:nth-child(16) > tbody > tr") %>% 34 | rvest::html_nodes("td:nth-child(4) > span") %>% 35 | rvest::html_text() 36 | 37 | sb_scores <- raw_scores[grepl("[1-9]", raw_scores)] 38 | 39 | win_df <- dplyr::tibble(team = win_team[grepl("[1-9]", win_team)]) %>% 40 | dplyr::mutate( 41 | season = substr(team, 1, 4), 42 | team = substr(team, 6, nchar(team)), 43 | superbowl = "Won Superbowl", 44 | sb_points = as.integer(substr(sb_scores, 1, 2)) 45 | ) 46 | 47 | lose_df <- dplyr::tibble(team = lose_team[grepl("[1-9]", lose_team)]) %>% 48 | dplyr::mutate( 49 | season = substr(team, 1, 4), 50 | team = substr(team, 6, nchar(team)), 51 | superbowl = "Lost Superbowl", 52 | sb_points = as.integer(substr(sb_scores, 4, 5)) 53 | ) 54 | 55 | all_sb <- dplyr::bind_rows(win_df, lose_df) %>% 56 | dplyr::mutate(season = as.integer(season)) 57 | 58 | all_sb 59 | } 60 | -------------------------------------------------------------------------------- /R/scrape_team_stats_nfl.R: -------------------------------------------------------------------------------- 1 | #' Scrape NFL stats from nfl.com at the team level. 2 | #' 3 | #' Please note that the column names are identical between offense/defense and you should reference the 'role' column for offensive or defensive stats. 4 | #' 5 | #' @param stats character - either "passing", "rushing", "receiving", "scoring", "downs" 6 | #' @param season character or numeric - greater than 1971, note that some data is missing for earlier seasons. 7 | #' @param role character - "offense" or "defense" 8 | #' @import purrr tidyr dplyr 9 | #' @importFrom dplyr %>% 10 | #' @importFrom rvest html_table 11 | #' @importFrom xml2 read_html 12 | #' @importFrom glue glue 13 | #' @importFrom readr parse_number type_convert 14 | #' @importFrom stringr str_remove 15 | #' @export 16 | #' @return tibble 17 | #' 18 | #' @examples 19 | #' 20 | #' # Get the NFL.com game-level stats for offense in 2018 21 | #' scrape_team_stats_nfl(season = 2018, stats = "passing", role = "offense") 22 | #' 23 | #' # Get the NFL.com team-level passing stats for defense in 2014 24 | #' scrape_team_stats_nfl(season = "2014", stats = "rushing", role = "defense") 25 | scrape_team_stats_nfl <- function(season = 2019, stats = "passing", role = "offense") { 26 | current_year <- as.double(substr(Sys.Date(), 1, 4)) 27 | 28 | if (!role %in% c("offense", "defense")) { 29 | stop("Please choose role of 'offense' or 'defense'") 30 | } 31 | 32 | if (!stats %in% c("passing", "rushing", "receiving", "scoring", "downs")) { 33 | stop("Please choose stats for 'passing', 'rushing', 'receiving', 'scoring', 'downs'!") 34 | } 35 | 36 | if (!dplyr::between(as.numeric(season), 1971, current_year)) { 37 | stop(paste("Please choose season between 1971 and", current_year)) 38 | } 39 | 40 | message(glue::glue("Scraping {stats} for {role} from {season}!")) 41 | 42 | url <- dplyr::if_else( 43 | role == "offense", 44 | glue::glue("https://www.nfl.com/stats/team-stats/offense/{stats}/{season}/reg/all"), 45 | glue::glue("https://www.nfl.com/stats/team-stats/defense/{stats}/{season}/reg/all") 46 | ) 47 | 48 | raw_html <- xml2::read_html(url) 49 | 50 | cleaned_tibble <- rvest::html_table(raw_html, fill = TRUE)[[1]] %>% 51 | janitor::clean_names() %>% 52 | dplyr::as_tibble() 53 | 54 | clean_scoring <- function(input_df) { 55 | suppressMessages( 56 | input_df %>% 57 | purrr::set_names( 58 | nm = c("team", "rush_td", "rec_td", "total_td", "two_pt_conv") 59 | ) %>% 60 | dplyr::mutate(team = word(team, 1), team = str_remove(team, "\\n")) 61 | ) 62 | } 63 | 64 | pass_off_names <- c("team", "pass_att", "pass_comp", "pass_comp_pct", "yds_att", "pass_yds", 65 | "pass_td", "int", "pass_rating", "first_downs", 66 | "pass_first_pct", "pass_20plus", "pass_40plus", 67 | "pass_long", "sacks", "sack_yds" 68 | ) 69 | 70 | pass_def_names <- c("team", "pass_att", "pass_comp", "pass_comp_pct", "yds_att", "pass_yds", 71 | "pass_td", "int", "first_downs", 72 | "pass_first_pct", "sacks" 73 | ) 74 | 75 | passing_names <- dplyr::if_else(role == "defense", 76 | list(pass_def_names), 77 | list(pass_off_names) 78 | )[[1]] 79 | 80 | clean_passing <- function(input_df) { 81 | 82 | suppressMessages(input_df %>% 83 | purrr::set_names( 84 | nm = passing_names 85 | ) %>% 86 | dplyr::mutate( 87 | pass_comp_pct = pass_comp_pct / 100, 88 | pass_first_pct = pass_first_pct / 100, 89 | team = word(team, 1), team = str_remove(team, "\\n") 90 | ) 91 | ) 92 | } 93 | 94 | clean_receiving <- function(input_df) { 95 | suppressMessages(input_df %>% 96 | purrr::set_names( 97 | nm = c("team", "rec", "rec_yds", "rec_ypr", "rec_td", "rec_20plus", 98 | "rec_40plus", "rec_lng", "rec_first", "rec_first_pct", "rec_fum") 99 | ) %>% 100 | dplyr::mutate( 101 | rec_first_pct = rec_first_pct / 100, 102 | team = word(team, 1), team = str_remove(team, "\\n") 103 | ) 104 | ) 105 | } 106 | 107 | clean_rushing <- function(input_df) { 108 | clean_rush_names <- c("team", "rush_att", "rush_yds", "rush_ypc", "rush_td", 109 | "rush_20plus", "rush_40plus", "rush_lng", "rush_first", 110 | "rush_first_pct", "rush_fum") 111 | 112 | renamed_tibble <- purrr::set_names(input_df, nm = clean_rush_names) 113 | 114 | suppressMessages( 115 | dplyr::mutate(renamed_tibble, 116 | rush_first_pct = rush_first_pct / 100, 117 | team = word(team, 1), team = str_remove(team, "\\n") 118 | ) 119 | ) 120 | } 121 | 122 | clean_downs <- function(input_df) { 123 | clean_downs_names <- c("team", "third_att", "third_conv", "fourth_att", 124 | "fourth_conv", "rec_first", "rec_first_pct", 125 | "rush_first", "rush_first_pct", "scrim_plays") 126 | 127 | suppressMessages( 128 | input_df %>% 129 | purrr::set_names( 130 | nm = clean_downs_names 131 | ) %>% 132 | dplyr::mutate( 133 | third_pct = third_conv / (third_att + third_conv), 134 | fourth_pct = fourth_conv / (fourth_att + fourth_conv), 135 | team = word(team, 1), team = str_remove(team, "\\n") 136 | ) 137 | ) 138 | } 139 | 140 | return_table <- if (stats == "scoring") { 141 | clean_scoring(cleaned_tibble) 142 | } else if (stats == "passing") { 143 | clean_passing(cleaned_tibble) 144 | } else if (stats == "rushing") { 145 | clean_rushing(cleaned_tibble) 146 | } else if (stats == "downs") { 147 | clean_downs(cleaned_tibble) 148 | } else if (stats == "receiving") { 149 | clean_receiving(cleaned_tibble) 150 | } else { 151 | stop("TABLE NOT FOUND") 152 | } 153 | 154 | return_table %>% 155 | dplyr::mutate( 156 | stat = toupper(stats), 157 | season = as.integer(season), 158 | role = toupper(role) 159 | ) %>% 160 | dplyr::select(season, role, stat, team, everything()) 161 | } 162 | -------------------------------------------------------------------------------- /R/scrape_weekly_leaders.R: -------------------------------------------------------------------------------- 1 | #' Scrape weekly NFL leaders stats from ESPN 2 | #' 3 | #' @param stats character - either receiving, passing, or rushing 4 | #' @param season character or numeric - greater than 2002 5 | #' @param week character or numeric - 1 to 17 for regular season 6 | #' @import purrr tidyr dplyr stringr 7 | #' @importFrom dplyr %>% 8 | #' @importFrom rvest html_table 9 | #' @importFrom xml2 read_html 10 | #' @importFrom glue glue 11 | #' @importFrom readr parse_number 12 | #' @return tibble 13 | #' @export 14 | #' 15 | #' @examples 16 | #' scrape_weekly_leaders(season = 2002, stats = "passing", week = 1) 17 | scrape_weekly_leaders <- function(season = 2019, week = 1, stats = "passing") { 18 | current_year <- as.double(substr(Sys.Date(), 1, 4)) 19 | 20 | if (!stats %in% c("receiving", "rushing", "passing")) { 21 | stop("Please choose stats of 'receiving', 'rushing', or 'passing'!") 22 | } 23 | 24 | if (!dplyr::between(as.numeric(season), 2002, current_year)) { 25 | stop(paste("Please choose season between 2002 and", current_year)) 26 | } 27 | 28 | if (!dplyr::between(as.numeric(week), 1, 18)) { 29 | stop("Please choose a week between 1 and 18") 30 | } 31 | 32 | message( 33 | glue::glue("Scraping {stats} stats for week {week} from {season} season!") 34 | ) 35 | 36 | url <- glue::glue("http://www.espn.com/nfl/weekly/leaders/_/week/{week}/year/{season}/seasontype/2/type/{stats}") 37 | 38 | pass_n <- c( 39 | "rank", "name", "team", "result", "pass_comp", "pass_att", 40 | "pass_yds", "pass_td", "pass_int", "sack", "pass_fumbles", "pass_rating" 41 | ) 42 | 43 | rush_n <- c( 44 | "rank", "name", "team", "result", "rush_att", "rush_yds", 45 | "rush_avg", "rush_td", "rush_long", "rush_fumbles", "drop" 46 | ) 47 | 48 | 49 | rec_n <- c( 50 | "rank", "name", "team", "result", "rec", "rec_yds", "rec_avg", 51 | "rec_td", "rec_long", "rec_fumbles", "drop" 52 | ) 53 | 54 | 55 | fix_names <- dplyr::case_when( 56 | stats == "passing" ~ list(pass_n), 57 | stats == "rushing" ~ list(rush_n), 58 | stats == "receiving" ~ list(rec_n) 59 | )[[1]] 60 | 61 | raw_df <- url %>% 62 | xml2::read_html() %>% 63 | rvest::html_node(xpath = '//*[@id="my-players-table"]/div[1]/div[1]/table[2]') %>% 64 | rvest::html_table(fill = TRUE) %>% 65 | purrr::set_names(nm = fix_names) %>% 66 | slice(3:n()) %>% 67 | dplyr::as_tibble() 68 | 69 | clean_df <- raw_df %>% 70 | dplyr::mutate( 71 | position = stringr::str_extract(name, stringr::str_sub(name, -2)), 72 | name = stringr::str_remove(name, position), 73 | name = stringr::str_remove(name, ",") 74 | ) %>% 75 | dplyr::mutate( 76 | rank = dplyr::row_number(), 77 | season = season, 78 | week = week 79 | ) %>% 80 | dplyr::select(season, week, rank, name, team:dplyr::contains("fumbles")) 81 | 82 | suppressMessages(readr::type_convert(clean_df)) 83 | } 84 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | ``` 15 | # espnscrapeR 16 | 17 | 18 | 19 | 20 | The goal of espnscrapeR is to collect or scrape QBR, NFL standings, and stats from ESPN. 21 | 22 | ## Installation 23 | 24 | The development version from [GitHub](https://github.com/) with: 25 | 26 | ``` r 27 | # install.packages("remotes") 28 | remotes::install_github("jthomasmock/espnscrapeR") 29 | ``` 30 | ## Example 31 | 32 | ```{r example} 33 | library(espnscrapeR) 34 | ``` 35 | 36 | ```{r} 37 | # Get NFL QBR for the 2019 regular season week 4 38 | get_nfl_qbr("2019", season_type = "Regular", week = 4) 39 | ``` 40 | 41 | ```{r} 42 | # Get NFL standings for 2010 43 | get_nfl_standings(2010) 44 | ``` 45 | 46 | 47 | ```{r} 48 | # Get NFL 49 | scrape_espn_stats(2019, stat = "rushing") 50 | ``` 51 | 52 | ```{r} 53 | # Get college QBR for 2014 week 5 54 | get_college_qbr(season = 2014, type = "weekly") 55 | ``` 56 | 57 | ```{r} 58 | # Get NFL teams with logos, colors, alternatives, etc 59 | get_nfl_teams() 60 | ``` 61 | 62 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Page not found (404) • espnscrapeR 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 |
30 |
76 | 77 | 78 | 79 | 80 |
81 |
82 | 85 | 86 | Content not found. Please use links in the navbar. 87 | 88 |
89 | 90 | 94 | 95 |
96 | 97 | 98 | 99 |
103 | 104 |
105 |

106 |

Site built with pkgdown 2.0.3.

107 |
108 | 109 |
110 |
111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /docs/LICENSE-text.html: -------------------------------------------------------------------------------- 1 | 2 | License • espnscrapeR 6 | 7 | 8 |
9 |
47 | 48 | 49 | 50 |
51 |
52 | 55 | 56 |
YEAR: 2020
57 | COPYRIGHT HOLDER: Thomas Mock
58 | 
59 | 60 |
61 | 62 | 65 | 66 |
67 | 68 | 69 | 70 |
73 | 74 |
75 |

Site built with pkgdown 2.0.3.

76 |
77 | 78 |
79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /docs/LICENSE.html: -------------------------------------------------------------------------------- 1 | 2 | MIT License • espnscrapeR 6 | 7 | 8 |
9 |
47 | 48 | 49 | 50 |
51 |
52 | 55 | 56 |
57 | 58 |

Copyright (c) 2020 Thomas Mock

59 |

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

60 |

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

61 |

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

62 |
63 | 64 |
65 | 66 | 69 | 70 |
71 | 72 | 73 | 74 |
77 | 78 |
79 |

Site built with pkgdown 2.0.3.

80 |
81 | 82 |
83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /docs/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/docs/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/docs/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/docs/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/docs/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/docs/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /docs/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/docs/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/articles/index.html: -------------------------------------------------------------------------------- 1 | 2 | Articles • espnscrapeR 6 | 7 | 8 |
9 |
47 | 48 | 49 | 50 |
51 |
52 | 55 | 56 |
57 |

All vignettes

58 |

59 | 60 |
QBR Examples
61 |
62 |
63 |
64 |
65 | 66 | 67 |
70 | 71 |
72 |

Site built with pkgdown 2.0.3.

73 |
74 | 75 |
76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /docs/articles/qbr_example_files/figure-html/unnamed-chunk-7-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/docs/articles/qbr_example_files/figure-html/unnamed-chunk-7-1.png -------------------------------------------------------------------------------- /docs/articles/qbr_example_files/header-attrs-2.10/header-attrs.js: -------------------------------------------------------------------------------- 1 | // Pandoc 2.9 adds attributes on both header and div. We remove the former (to 2 | // be compatible with the behavior of Pandoc < 2.8). 3 | document.addEventListener('DOMContentLoaded', function(e) { 4 | var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); 5 | var i, h, a; 6 | for (i = 0; i < hs.length; i++) { 7 | h = hs[i]; 8 | if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 9 | a = h.attributes; 10 | while (a.length > 0) h.removeAttribute(a[0].name); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /docs/articles/qbr_example_files/header-attrs-2.9.7/header-attrs.js: -------------------------------------------------------------------------------- 1 | // Pandoc 2.9 adds attributes on both header and div. We remove the former (to 2 | // be compatible with the behavior of Pandoc < 2.8). 3 | document.addEventListener('DOMContentLoaded', function(e) { 4 | var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); 5 | var i, h, a; 6 | for (i = 0; i < hs.length; i++) { 7 | h = hs[i]; 8 | if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 9 | a = h.attributes; 10 | while (a.length > 0) h.removeAttribute(a[0].name); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /docs/authors.html: -------------------------------------------------------------------------------- 1 | 2 | Authors and Citation • espnscrapeR 6 | 7 | 8 |
9 |
47 | 48 | 49 | 50 |
51 |
52 |
53 | 56 | 57 | 58 |
  • 59 |

    Thomas Mock. Maintainer. 60 |

    61 |
  • 62 |
63 |
64 |
65 |

Citation

66 | Source: DESCRIPTION 67 |
68 |
69 | 70 | 71 |

Mock T (2022). 72 | espnscrapeR: Scrapes Or Collects NFL Data From ESPN. 73 | R package version 0.7.1, https://github.com/jthomasmock/espnscrapeR. 74 |

75 |
@Manual{,
 76 |   title = {espnscrapeR: Scrapes Or Collects NFL Data From ESPN},
 77 |   author = {Thomas Mock},
 78 |   year = {2022},
 79 |   note = {R package version 0.7.1},
 80 |   url = {https://github.com/jthomasmock/espnscrapeR},
 81 | }
82 | 83 |
84 | 85 |
86 | 87 | 88 | 89 |
92 | 93 |
94 |

Site built with pkgdown 2.0.3.

95 |
96 | 97 |
98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /docs/bootstrap-toc.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) 3 | * Copyright 2015 Aidan Feldman 4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ 5 | 6 | /* modified from https://github.com/twbs/bootstrap/blob/94b4076dd2efba9af71f0b18d4ee4b163aa9e0dd/docs/assets/css/src/docs.css#L548-L601 */ 7 | 8 | /* All levels of nav */ 9 | nav[data-toggle='toc'] .nav > li > a { 10 | display: block; 11 | padding: 4px 20px; 12 | font-size: 13px; 13 | font-weight: 500; 14 | color: #767676; 15 | } 16 | nav[data-toggle='toc'] .nav > li > a:hover, 17 | nav[data-toggle='toc'] .nav > li > a:focus { 18 | padding-left: 19px; 19 | color: #563d7c; 20 | text-decoration: none; 21 | background-color: transparent; 22 | border-left: 1px solid #563d7c; 23 | } 24 | nav[data-toggle='toc'] .nav > .active > a, 25 | nav[data-toggle='toc'] .nav > .active:hover > a, 26 | nav[data-toggle='toc'] .nav > .active:focus > a { 27 | padding-left: 18px; 28 | font-weight: bold; 29 | color: #563d7c; 30 | background-color: transparent; 31 | border-left: 2px solid #563d7c; 32 | } 33 | 34 | /* Nav: second level (shown on .active) */ 35 | nav[data-toggle='toc'] .nav .nav { 36 | display: none; /* Hide by default, but at >768px, show it */ 37 | padding-bottom: 10px; 38 | } 39 | nav[data-toggle='toc'] .nav .nav > li > a { 40 | padding-top: 1px; 41 | padding-bottom: 1px; 42 | padding-left: 30px; 43 | font-size: 12px; 44 | font-weight: normal; 45 | } 46 | nav[data-toggle='toc'] .nav .nav > li > a:hover, 47 | nav[data-toggle='toc'] .nav .nav > li > a:focus { 48 | padding-left: 29px; 49 | } 50 | nav[data-toggle='toc'] .nav .nav > .active > a, 51 | nav[data-toggle='toc'] .nav .nav > .active:hover > a, 52 | nav[data-toggle='toc'] .nav .nav > .active:focus > a { 53 | padding-left: 28px; 54 | font-weight: 500; 55 | } 56 | 57 | /* from https://github.com/twbs/bootstrap/blob/e38f066d8c203c3e032da0ff23cd2d6098ee2dd6/docs/assets/css/src/docs.css#L631-L634 */ 58 | nav[data-toggle='toc'] .nav > .active > ul { 59 | display: block; 60 | } 61 | -------------------------------------------------------------------------------- /docs/bootstrap-toc.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) 3 | * Copyright 2015 Aidan Feldman 4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ 5 | (function() { 6 | 'use strict'; 7 | 8 | window.Toc = { 9 | helpers: { 10 | // return all matching elements in the set, or their descendants 11 | findOrFilter: function($el, selector) { 12 | // http://danielnouri.org/notes/2011/03/14/a-jquery-find-that-also-finds-the-root-element/ 13 | // http://stackoverflow.com/a/12731439/358804 14 | var $descendants = $el.find(selector); 15 | return $el.filter(selector).add($descendants).filter(':not([data-toc-skip])'); 16 | }, 17 | 18 | generateUniqueIdBase: function(el) { 19 | var text = $(el).text(); 20 | var anchor = text.trim().toLowerCase().replace(/[^A-Za-z0-9]+/g, '-'); 21 | return anchor || el.tagName.toLowerCase(); 22 | }, 23 | 24 | generateUniqueId: function(el) { 25 | var anchorBase = this.generateUniqueIdBase(el); 26 | for (var i = 0; ; i++) { 27 | var anchor = anchorBase; 28 | if (i > 0) { 29 | // add suffix 30 | anchor += '-' + i; 31 | } 32 | // check if ID already exists 33 | if (!document.getElementById(anchor)) { 34 | return anchor; 35 | } 36 | } 37 | }, 38 | 39 | generateAnchor: function(el) { 40 | if (el.id) { 41 | return el.id; 42 | } else { 43 | var anchor = this.generateUniqueId(el); 44 | el.id = anchor; 45 | return anchor; 46 | } 47 | }, 48 | 49 | createNavList: function() { 50 | return $(''); 51 | }, 52 | 53 | createChildNavList: function($parent) { 54 | var $childList = this.createNavList(); 55 | $parent.append($childList); 56 | return $childList; 57 | }, 58 | 59 | generateNavEl: function(anchor, text) { 60 | var $a = $(''); 61 | $a.attr('href', '#' + anchor); 62 | $a.text(text); 63 | var $li = $('
  • '); 64 | $li.append($a); 65 | return $li; 66 | }, 67 | 68 | generateNavItem: function(headingEl) { 69 | var anchor = this.generateAnchor(headingEl); 70 | var $heading = $(headingEl); 71 | var text = $heading.data('toc-text') || $heading.text(); 72 | return this.generateNavEl(anchor, text); 73 | }, 74 | 75 | // Find the first heading level (`

    `, then `

    `, etc.) that has more than one element. Defaults to 1 (for `

    `). 76 | getTopLevel: function($scope) { 77 | for (var i = 1; i <= 6; i++) { 78 | var $headings = this.findOrFilter($scope, 'h' + i); 79 | if ($headings.length > 1) { 80 | return i; 81 | } 82 | } 83 | 84 | return 1; 85 | }, 86 | 87 | // returns the elements for the top level, and the next below it 88 | getHeadings: function($scope, topLevel) { 89 | var topSelector = 'h' + topLevel; 90 | 91 | var secondaryLevel = topLevel + 1; 92 | var secondarySelector = 'h' + secondaryLevel; 93 | 94 | return this.findOrFilter($scope, topSelector + ',' + secondarySelector); 95 | }, 96 | 97 | getNavLevel: function(el) { 98 | return parseInt(el.tagName.charAt(1), 10); 99 | }, 100 | 101 | populateNav: function($topContext, topLevel, $headings) { 102 | var $context = $topContext; 103 | var $prevNav; 104 | 105 | var helpers = this; 106 | $headings.each(function(i, el) { 107 | var $newNav = helpers.generateNavItem(el); 108 | var navLevel = helpers.getNavLevel(el); 109 | 110 | // determine the proper $context 111 | if (navLevel === topLevel) { 112 | // use top level 113 | $context = $topContext; 114 | } else if ($prevNav && $context === $topContext) { 115 | // create a new level of the tree and switch to it 116 | $context = helpers.createChildNavList($prevNav); 117 | } // else use the current $context 118 | 119 | $context.append($newNav); 120 | 121 | $prevNav = $newNav; 122 | }); 123 | }, 124 | 125 | parseOps: function(arg) { 126 | var opts; 127 | if (arg.jquery) { 128 | opts = { 129 | $nav: arg 130 | }; 131 | } else { 132 | opts = arg; 133 | } 134 | opts.$scope = opts.$scope || $(document.body); 135 | return opts; 136 | } 137 | }, 138 | 139 | // accepts a jQuery object, or an options object 140 | init: function(opts) { 141 | opts = this.helpers.parseOps(opts); 142 | 143 | // ensure that the data attribute is in place for styling 144 | opts.$nav.attr('data-toggle', 'toc'); 145 | 146 | var $topContext = this.helpers.createChildNavList(opts.$nav); 147 | var topLevel = this.helpers.getTopLevel(opts.$scope); 148 | var $headings = this.helpers.getHeadings(opts.$scope, topLevel); 149 | this.helpers.populateNav($topContext, topLevel, $headings); 150 | } 151 | }; 152 | 153 | $(function() { 154 | $('nav[data-toggle="toc"]').each(function(i, el) { 155 | var $nav = $(el); 156 | Toc.init($nav); 157 | }); 158 | }); 159 | })(); 160 | -------------------------------------------------------------------------------- /docs/docsearch.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | // register a handler to move the focus to the search bar 4 | // upon pressing shift + "/" (i.e. "?") 5 | $(document).on('keydown', function(e) { 6 | if (e.shiftKey && e.keyCode == 191) { 7 | e.preventDefault(); 8 | $("#search-input").focus(); 9 | } 10 | }); 11 | 12 | $(document).ready(function() { 13 | // do keyword highlighting 14 | /* modified from https://jsfiddle.net/julmot/bL6bb5oo/ */ 15 | var mark = function() { 16 | 17 | var referrer = document.URL ; 18 | var paramKey = "q" ; 19 | 20 | if (referrer.indexOf("?") !== -1) { 21 | var qs = referrer.substr(referrer.indexOf('?') + 1); 22 | var qs_noanchor = qs.split('#')[0]; 23 | var qsa = qs_noanchor.split('&'); 24 | var keyword = ""; 25 | 26 | for (var i = 0; i < qsa.length; i++) { 27 | var currentParam = qsa[i].split('='); 28 | 29 | if (currentParam.length !== 2) { 30 | continue; 31 | } 32 | 33 | if (currentParam[0] == paramKey) { 34 | keyword = decodeURIComponent(currentParam[1].replace(/\+/g, "%20")); 35 | } 36 | } 37 | 38 | if (keyword !== "") { 39 | $(".contents").unmark({ 40 | done: function() { 41 | $(".contents").mark(keyword); 42 | } 43 | }); 44 | } 45 | } 46 | }; 47 | 48 | mark(); 49 | }); 50 | }); 51 | 52 | /* Search term highlighting ------------------------------*/ 53 | 54 | function matchedWords(hit) { 55 | var words = []; 56 | 57 | var hierarchy = hit._highlightResult.hierarchy; 58 | // loop to fetch from lvl0, lvl1, etc. 59 | for (var idx in hierarchy) { 60 | words = words.concat(hierarchy[idx].matchedWords); 61 | } 62 | 63 | var content = hit._highlightResult.content; 64 | if (content) { 65 | words = words.concat(content.matchedWords); 66 | } 67 | 68 | // return unique words 69 | var words_uniq = [...new Set(words)]; 70 | return words_uniq; 71 | } 72 | 73 | function updateHitURL(hit) { 74 | 75 | var words = matchedWords(hit); 76 | var url = ""; 77 | 78 | if (hit.anchor) { 79 | url = hit.url_without_anchor + '?q=' + escape(words.join(" ")) + '#' + hit.anchor; 80 | } else { 81 | url = hit.url + '?q=' + escape(words.join(" ")); 82 | } 83 | 84 | return url; 85 | } 86 | -------------------------------------------------------------------------------- /docs/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/docs/favicon-16x16.png -------------------------------------------------------------------------------- /docs/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/docs/favicon-32x32.png -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/docs/favicon.ico -------------------------------------------------------------------------------- /docs/link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/docs/logo.png -------------------------------------------------------------------------------- /docs/pkgdown.js: -------------------------------------------------------------------------------- 1 | /* http://gregfranko.com/blog/jquery-best-practices/ */ 2 | (function($) { 3 | $(function() { 4 | 5 | $('.navbar-fixed-top').headroom(); 6 | 7 | $('body').css('padding-top', $('.navbar').height() + 10); 8 | $(window).resize(function(){ 9 | $('body').css('padding-top', $('.navbar').height() + 10); 10 | }); 11 | 12 | $('[data-toggle="tooltip"]').tooltip(); 13 | 14 | var cur_path = paths(location.pathname); 15 | var links = $("#navbar ul li a"); 16 | var max_length = -1; 17 | var pos = -1; 18 | for (var i = 0; i < links.length; i++) { 19 | if (links[i].getAttribute("href") === "#") 20 | continue; 21 | // Ignore external links 22 | if (links[i].host !== location.host) 23 | continue; 24 | 25 | var nav_path = paths(links[i].pathname); 26 | 27 | var length = prefix_length(nav_path, cur_path); 28 | if (length > max_length) { 29 | max_length = length; 30 | pos = i; 31 | } 32 | } 33 | 34 | // Add class to parent
  • , and enclosing
  • if in dropdown 35 | if (pos >= 0) { 36 | var menu_anchor = $(links[pos]); 37 | menu_anchor.parent().addClass("active"); 38 | menu_anchor.closest("li.dropdown").addClass("active"); 39 | } 40 | }); 41 | 42 | function paths(pathname) { 43 | var pieces = pathname.split("/"); 44 | pieces.shift(); // always starts with / 45 | 46 | var end = pieces[pieces.length - 1]; 47 | if (end === "index.html" || end === "") 48 | pieces.pop(); 49 | return(pieces); 50 | } 51 | 52 | // Returns -1 if not found 53 | function prefix_length(needle, haystack) { 54 | if (needle.length > haystack.length) 55 | return(-1); 56 | 57 | // Special case for length-0 haystack, since for loop won't run 58 | if (haystack.length === 0) { 59 | return(needle.length === 0 ? 0 : -1); 60 | } 61 | 62 | for (var i = 0; i < haystack.length; i++) { 63 | if (needle[i] != haystack[i]) 64 | return(i); 65 | } 66 | 67 | return(haystack.length); 68 | } 69 | 70 | /* Clipboard --------------------------*/ 71 | 72 | function changeTooltipMessage(element, msg) { 73 | var tooltipOriginalTitle=element.getAttribute('data-original-title'); 74 | element.setAttribute('data-original-title', msg); 75 | $(element).tooltip('show'); 76 | element.setAttribute('data-original-title', tooltipOriginalTitle); 77 | } 78 | 79 | if(ClipboardJS.isSupported()) { 80 | $(document).ready(function() { 81 | var copyButton = ""; 82 | 83 | $("div.sourceCode").addClass("hasCopyButton"); 84 | 85 | // Insert copy buttons: 86 | $(copyButton).prependTo(".hasCopyButton"); 87 | 88 | // Initialize tooltips: 89 | $('.btn-copy-ex').tooltip({container: 'body'}); 90 | 91 | // Initialize clipboard: 92 | var clipboardBtnCopies = new ClipboardJS('[data-clipboard-copy]', { 93 | text: function(trigger) { 94 | return trigger.parentNode.textContent.replace(/\n#>[^\n]*/g, ""); 95 | } 96 | }); 97 | 98 | clipboardBtnCopies.on('success', function(e) { 99 | changeTooltipMessage(e.trigger, 'Copied!'); 100 | e.clearSelection(); 101 | }); 102 | 103 | clipboardBtnCopies.on('error', function() { 104 | changeTooltipMessage(e.trigger,'Press Ctrl+C or Command+C to copy'); 105 | }); 106 | }); 107 | } 108 | })(window.jQuery || window.$) 109 | -------------------------------------------------------------------------------- /docs/pkgdown.yml: -------------------------------------------------------------------------------- 1 | pandoc: 2.19.2 2 | pkgdown: 2.0.3 3 | pkgdown_sha: ~ 4 | articles: 5 | qbr_example: qbr_example.html 6 | last_built: 2022-11-01T23:33Z 7 | 8 | -------------------------------------------------------------------------------- /docs/reference/Rplot001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/docs/reference/Rplot001.png -------------------------------------------------------------------------------- /docs/reference/espnscrapeR-package.html: -------------------------------------------------------------------------------- 1 | 2 | espnscrapeR: Scrapes Or Collects NFL Data From ESPN — espnscrapeR-package • espnscrapeR 6 | 7 | 8 |
    9 |
    47 | 48 | 49 | 50 |
    51 |
    52 | 57 | 58 |
    59 |

    Main use case is to collect ESPN QBR for NFL and college football. Alternative functions include getting NFL standings and scraping NFL season-level stats.

    60 |
    61 | 62 | 63 |
    64 |

    See also

    65 | 68 |
    69 | 70 |
    71 | 74 |
    75 | 76 | 77 |
    80 | 81 |
    82 |

    Site built with pkgdown 2.0.3.

    83 |
    84 | 85 |
    86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /docs/reference/get_espn_standings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Collect NFL standings for a specific season from ESPN's API — get_espn_standings • espnscrapeR 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
    57 |
    58 | 98 | 99 | 100 | 101 |
    102 | 103 |
    104 |
    105 | 110 | 111 |
    112 | 113 |

    Collect NFL standings for a specific season from ESPN's API

    114 | 115 |
    116 | 117 |
    get_espn_standings(season = 2019)
    118 | 119 |

    Arguments

    120 | 121 | 122 | 123 | 124 | 125 | 126 |
    season

    Either numeric or character

    127 | 128 |

    Value

    129 | 130 |

    Returns a tibble

    131 | 132 | 133 |
    134 | 143 |
    144 | 145 | 146 |
    147 | 150 | 151 |
    152 |

    Site built with pkgdown 1.4.1.

    153 |
    154 | 155 |
    156 |
    157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /docs/reference/get_nfl_qbr_helper.html: -------------------------------------------------------------------------------- 1 | 2 | Get ESPN QBR for NFL football — get_nfl_qbr_helper • espnscrapeR 6 | 7 | 8 |
    9 |
    47 | 48 | 49 | 50 |
    51 |
    52 | 57 | 58 |
    59 |

    Get ESPN QBR for NFL football

    60 |
    61 | 62 |
    63 |
    get_nfl_qbr_helper(raw_json, week, season, season_type)
    64 |
    65 | 66 |
    67 |

    Arguments

    68 |
    week
    69 |

    Either NA to return season or week 1 to 4 for playoffs or 1 to 17 for regular

    70 |
    season
    71 |

    Either numeric or character

    72 |
    season_type
    73 |

    Character - either "Regular" or "Playoffs"

    74 |
    75 |
    76 |

    Value

    77 |

    Returns a tibble

    78 |
    79 | 80 |
    81 | 84 |
    85 | 86 | 87 |
    90 | 91 |
    92 |

    Site built with pkgdown 2.0.3.

    93 |
    94 | 95 |
    96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /docs/reference/gt_theme_538.html: -------------------------------------------------------------------------------- 1 | 2 | Apply FiveThirtyEight theme to a gt table — gt_theme_538 • espnscrapeR 6 | 7 | 8 |
    9 |
    47 | 48 | 49 | 50 |
    51 |
    52 | 57 | 58 |
    59 |

    Apply FiveThirtyEight theme to a gt table

    60 |
    61 | 62 |
    63 |
    gt_theme_538(data, ...)
    64 |
    65 | 66 |
    67 |

    Arguments

    68 |
    data
    69 |

    An existing gt table object

    70 |
    ...
    71 |

    Optional additional arguments to gt::table_options()

    72 |
    73 |
    74 |

    Value

    75 |

    Returns a tibble

    76 |
    77 | 78 |
    79 | 82 |
    83 | 84 | 85 |
    88 | 89 |
    90 |

    Site built with pkgdown 2.0.3.

    91 |
    92 | 93 |
    94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /docs/reference/gt_theme_espn.html: -------------------------------------------------------------------------------- 1 | 2 | Apply ESPN theme to a gt table — gt_theme_espn • espnscrapeR 6 | 7 | 8 |
    9 |
    47 | 48 | 49 | 50 |
    51 |
    52 | 57 | 58 |
    59 |

    Apply ESPN theme to a gt table

    60 |
    61 | 62 |
    63 |
    gt_theme_espn(data, ...)
    64 |
    65 | 66 |
    67 |

    Arguments

    68 |
    data
    69 |

    An existing gt table object

    70 |
    ...
    71 |

    Optional additional arguments to gt::table_options()

    72 |
    73 |
    74 |

    Value

    75 |

    Returns a tibble

    76 |
    77 | 78 |
    79 | 82 |
    83 | 84 | 85 |
    88 | 89 |
    90 |

    Site built with pkgdown 2.0.3.

    91 |
    92 | 93 |
    94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /docs/reference/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Hello, World! — hello • espnscrapeR 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
    57 |
    58 | 98 | 99 | 100 | 101 |
    102 | 103 |
    104 |
    105 | 110 | 111 |
    112 | 113 |

    Prints 'Hello, world!'.

    114 | 115 |
    116 | 117 |
    hello()
    118 | 119 | 120 |

    Examples

    121 |
    hello()
    #> Error in hello(): could not find function "hello"
    122 |
    123 | 131 |
    132 | 133 | 134 |
    135 | 138 | 139 |
    140 |

    Site built with pkgdown 1.4.1.

    141 |
    142 | 143 |
    144 |
    145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /docs/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /404.html 5 | 6 | 7 | /LICENSE-text.html 8 | 9 | 10 | /LICENSE.html 11 | 12 | 13 | /articles/index.html 14 | 15 | 16 | /articles/qbr_example.html 17 | 18 | 19 | /authors.html 20 | 21 | 22 | /index.html 23 | 24 | 25 | /reference/espnscrapeR-package.html 26 | 27 | 28 | /reference/get_538_elo.html 29 | 30 | 31 | /reference/get_538_elo_historical.html 32 | 33 | 34 | /reference/get_538_wr_metrics.html 35 | 36 | 37 | /reference/get_athlete.html 38 | 39 | 40 | /reference/get_college_qbr.html 41 | 42 | 43 | /reference/get_depth_chart.html 44 | 45 | 46 | /reference/get_espn_college_qbr.html 47 | 48 | 49 | /reference/get_espn_nfl_standings.html 50 | 51 | 52 | /reference/get_espn_standings.html 53 | 54 | 55 | /reference/get_espn_win_prob.html 56 | 57 | 58 | /reference/get_espn_wr_metrics.html 59 | 60 | 61 | /reference/get_nfl_boxscore.html 62 | 63 | 64 | /reference/get_nfl_boxscore_players.html 65 | 66 | 67 | /reference/get_nfl_odds.html 68 | 69 | 70 | /reference/get_nfl_pbp.html 71 | 72 | 73 | /reference/get_nfl_qbr.html 74 | 75 | 76 | /reference/get_nfl_qbr_helper.html 77 | 78 | 79 | /reference/get_nfl_schedule.html 80 | 81 | 82 | /reference/get_nfl_standings.html 83 | 84 | 85 | /reference/get_nfl_teams.html 86 | 87 | 88 | /reference/get_sharpe_data.html 89 | 90 | 91 | /reference/gt_hulk_color.html 92 | 93 | 94 | /reference/gt_theme_538.html 95 | 96 | 97 | /reference/gt_theme_espn.html 98 | 99 | 100 | /reference/hello.html 101 | 102 | 103 | /reference/index.html 104 | 105 | 106 | /reference/scrape_espn_stats.html 107 | 108 | 109 | /reference/scrape_espn_win_rate.html 110 | 111 | 112 | /reference/scrape_fpi.html 113 | 114 | 115 | /reference/scrape_nfl_qbr.html 116 | 117 | 118 | /reference/scrape_nfl_standings.html 119 | 120 | 121 | /reference/scrape_nfl_weekly_standings.html 122 | 123 | 124 | /reference/scrape_standings.html 125 | 126 | 127 | /reference/scrape_superbowls.html 128 | 129 | 130 | /reference/scrape_team_stats_nfl.html 131 | 132 | 133 | /reference/scrape_weekly_leaders.html 134 | 135 | 136 | -------------------------------------------------------------------------------- /espnscrapeR.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | -------------------------------------------------------------------------------- /inst/figures/espnscrapeR-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/inst/figures/espnscrapeR-logo.png -------------------------------------------------------------------------------- /man/espnscrapeR-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/espnscrapeR-package.R 3 | \docType{package} 4 | \name{espnscrapeR-package} 5 | \alias{espnscrapeR} 6 | \alias{espnscrapeR-package} 7 | \title{espnscrapeR: Scrapes Or Collects NFL Data From ESPN} 8 | \description{ 9 | Main use case is to collect ESPN QBR for NFL and college football. Alternative functions include getting NFL standings and scraping NFL season-level stats. 10 | } 11 | \seealso{ 12 | Useful links: 13 | \itemize{ 14 | \item \url{https://github.com/jthomasmock/espnscrapeR} 15 | \item Report bugs at \url{https://github.com/jthomasmock/espnscrapeR/issues} 16 | } 17 | 18 | } 19 | \keyword{internal} 20 | -------------------------------------------------------------------------------- /man/get_538_elo.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_538_elo.R 3 | \name{get_538_elo} 4 | \alias{get_538_elo} 5 | \title{Get ELO ratings from FiveThirtyEight} 6 | \usage{ 7 | get_538_elo(season = 2020, stat = "weekly_elo") 8 | } 9 | \arguments{ 10 | \item{season}{character or numeric, must be 2015 or greater} 11 | 12 | \item{stat}{character, must be one of "games", "weekly_elo", "weekly_rating", "distance", "qb_adj_playoff", "qb_adj"} 13 | } 14 | \value{ 15 | Returns a tibble 16 | } 17 | \description{ 18 | Get ELO ratings from FiveThirtyEight 19 | } 20 | \examples{ 21 | # Get elo for specific season and stat 22 | get_538_elo(season = 2020, stat = "weekly_elo") 23 | } 24 | -------------------------------------------------------------------------------- /man/get_538_elo_historical.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_538_elo.R 3 | \name{get_538_elo_historical} 4 | \alias{get_538_elo_historical} 5 | \title{Get historical ELO ratings from FiveThirtyEight} 6 | \usage{ 7 | get_538_elo_historical(team = "pit") 8 | } 9 | \arguments{ 10 | \item{team}{character, team abbreviated name, must be one of "ari", "atl", "bal", "buf", "car", "chi", "cin", "cle", "dal", "den", "det", "gb", "hou", "ind", "jax", "kc", "oak", "lac", "lar", "mia", "min", "ne", "no", "nyg", "nyj", "phi", "pit", "sf", "sea", "tb", "ten", "wsh"} 11 | } 12 | \value{ 13 | Returns a tibble 14 | } 15 | \description{ 16 | Get historical ELO ratings from FiveThirtyEight 17 | } 18 | \examples{ 19 | 20 | # Get historic elo for specific team 21 | get_538_elo_historical(team = "pit") 22 | } 23 | -------------------------------------------------------------------------------- /man/get_athlete.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_athlete.R 3 | \name{get_athlete} 4 | \alias{get_athlete} 5 | \title{Get ESPN athlete info for NFL players} 6 | \usage{ 7 | get_athlete(athlete_id) 8 | } 9 | \arguments{ 10 | \item{athlete_id}{The player's unique athlete id} 11 | } 12 | \value{ 13 | Returns a tibble 14 | } 15 | \description{ 16 | Get ESPN athlete info for NFL players 17 | } 18 | \examples{ 19 | # Get ALL Playoff QBR from 2016 season 20 | get_athlete("2580") 21 | 22 | } 23 | -------------------------------------------------------------------------------- /man/get_college_qbr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_college_qbr.R 3 | \name{get_college_qbr} 4 | \alias{get_college_qbr} 5 | \title{Get ESPN QBR for College football} 6 | \usage{ 7 | get_college_qbr(season = 2020, type = "season") 8 | } 9 | \arguments{ 10 | \item{season}{Numeric or character - greater than 2004} 11 | 12 | \item{type}{character - "season" or "weekly"} 13 | } 14 | \value{ 15 | tibble 16 | } 17 | \description{ 18 | Get ESPN QBR for College football 19 | } 20 | \examples{ 21 | 22 | # Get college QBR from 2014 for players at season level 23 | get_college_qbr(season = 2014, type = "season") 24 | 25 | # Get weekly college QBR from 2019 26 | # Note the mix of playoffs/bowls and regular season 27 | get_college_qbr(2019, type = "weekly") 28 | } 29 | -------------------------------------------------------------------------------- /man/get_depth_chart.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_depth_chart.R 3 | \name{get_depth_chart} 4 | \alias{get_depth_chart} 5 | \title{Get ESPN NFL depth chart by year and team} 6 | \usage{ 7 | get_depth_chart(season = 2020, team = 23) 8 | } 9 | \arguments{ 10 | \item{season}{Either numeric or character} 11 | 12 | \item{team}{team_id or team_abb, can be retrieved via get_nfl_teams()} 13 | } 14 | \value{ 15 | Returns a tibble 16 | } 17 | \description{ 18 | Get ESPN NFL depth chart by year and team 19 | } 20 | \examples{ 21 | # Get depth chart for 2017 for Pittsburgh 22 | get_depth_chart("2016", team = "PIT") 23 | 24 | } 25 | -------------------------------------------------------------------------------- /man/get_espn_win_prob.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_nfl_win_prob.R 3 | \name{get_espn_win_prob} 4 | \alias{get_espn_win_prob} 5 | \title{Get NFL in-game win probabilities for a specific game from ESPN} 6 | \usage{ 7 | get_espn_win_prob(game_id) 8 | } 9 | \arguments{ 10 | \item{game_id}{Character string - can be acquired from the website of a specific game, or from espnscrapeR::get_nfl_schedule()} 11 | } 12 | \value{ 13 | Returns a tibble 14 | } 15 | \description{ 16 | Get NFL in-game win probabilities for a specific game from ESPN 17 | } 18 | \examples{ 19 | # Get win prob from specific game 20 | get_espn_win_prob(game_id = "401030956") 21 | } 22 | -------------------------------------------------------------------------------- /man/get_espn_wr_metrics.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_espn_wr_metrics.R 3 | \name{get_espn_wr_metrics} 4 | \alias{get_espn_wr_metrics} 5 | \title{Get ESPN's WR metrics from FiveThirtyEight} 6 | \usage{ 7 | get_espn_wr_metrics() 8 | } 9 | \value{ 10 | tibble 11 | } 12 | \description{ 13 | Get ESPN's WR metrics from FiveThirtyEight 14 | } 15 | \examples{ 16 | raw_metrics <- espnscrapeR::get_espn_wr_metrics() 17 | 18 | dplyr::glimpse(raw_metrics) 19 | } 20 | -------------------------------------------------------------------------------- /man/get_nfl_boxscore.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_nfl_boxscore.R 3 | \name{get_nfl_boxscore} 4 | \alias{get_nfl_boxscore} 5 | \title{Get NFL boxscore for a specific game from ESPN's API} 6 | \usage{ 7 | get_nfl_boxscore(game_id) 8 | } 9 | \arguments{ 10 | \item{game_id}{character} 11 | } 12 | \value{ 13 | Returns a tibble 14 | } 15 | \description{ 16 | Get NFL boxscore for a specific game from ESPN's API 17 | } 18 | \examples{ 19 | # Get NFL play-by-play for a specific game 20 | get_nfl_boxscore(game_id = "300912027") 21 | 22 | } 23 | -------------------------------------------------------------------------------- /man/get_nfl_boxscore_players.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_nfl_boxscore_players.R 3 | \name{get_nfl_boxscore_players} 4 | \alias{get_nfl_boxscore_players} 5 | \title{Get NFL boxscore for players for a specific game from ESPN's API} 6 | \usage{ 7 | get_nfl_boxscore_players(game_id) 8 | } 9 | \arguments{ 10 | \item{game_id}{character} 11 | } 12 | \value{ 13 | Returns a tibble 14 | } 15 | \description{ 16 | Get NFL boxscore for players for a specific game from ESPN's API 17 | } 18 | \examples{ 19 | # Get NFL play-by-play for a specific game 20 | get_nfl_boxscore_players(game_id = "300912027") 21 | 22 | } 23 | -------------------------------------------------------------------------------- /man/get_nfl_odds.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_nfl_odds.R 3 | \name{get_nfl_odds} 4 | \alias{get_nfl_odds} 5 | \title{Get NFL odds for a specific game from ESPN's API} 6 | \usage{ 7 | get_nfl_odds(game_id) 8 | } 9 | \arguments{ 10 | \item{game_id}{character} 11 | } 12 | \value{ 13 | Returns a tibble 14 | } 15 | \description{ 16 | Get NFL odds for a specific game from ESPN's API 17 | } 18 | \examples{ 19 | # Get odds from a specific game 20 | get_nfl_odds(game_id = "400791550") 21 | } 22 | -------------------------------------------------------------------------------- /man/get_nfl_pbp.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_nfl_pbp.R 3 | \name{get_nfl_pbp} 4 | \alias{get_nfl_pbp} 5 | \title{Get NFL play-by-play for a specific game from ESPN's API} 6 | \usage{ 7 | get_nfl_pbp(game_id) 8 | } 9 | \arguments{ 10 | \item{game_id}{character} 11 | } 12 | \value{ 13 | Returns a tibble 14 | } 15 | \description{ 16 | Get NFL play-by-play for a specific game from ESPN's API 17 | } 18 | \examples{ 19 | # Get NFL play-by-play for a specific game 20 | get_nfl_pbp(game_id = "300912027") 21 | } 22 | -------------------------------------------------------------------------------- /man/get_nfl_qbr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_nfl_qbr.R 3 | \name{get_nfl_qbr} 4 | \alias{get_nfl_qbr} 5 | \title{Get ESPN QBR for NFL football} 6 | \usage{ 7 | get_nfl_qbr(season = 2020, week = NA, season_type = "Regular") 8 | } 9 | \arguments{ 10 | \item{season}{Either numeric or character} 11 | 12 | \item{week}{Either NA to return season or week 1 to 4 for playoffs or 1 to 17 for regular} 13 | 14 | \item{season_type}{Character - either "Regular" or "Playoffs"} 15 | } 16 | \value{ 17 | Returns a tibble 18 | } 19 | \description{ 20 | Get ESPN QBR for NFL football 21 | } 22 | \examples{ 23 | # Get ALL Playoff QBR from 2016 season 24 | get_nfl_qbr("2016", season_type = "Playoffs", week = NA) 25 | 26 | # Get Regular season QBR for week 4 of 2019 27 | get_nfl_qbr("2019", season_type = "Regular", week = 4) 28 | } 29 | -------------------------------------------------------------------------------- /man/get_nfl_qbr_helper.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_nfl_qbr_helper.R 3 | \name{get_nfl_qbr_helper} 4 | \alias{get_nfl_qbr_helper} 5 | \title{Get ESPN QBR for NFL football} 6 | \usage{ 7 | get_nfl_qbr_helper(raw_json, week, season, season_type) 8 | } 9 | \arguments{ 10 | \item{week}{Either NA to return season or week 1 to 4 for playoffs or 1 to 17 for regular} 11 | 12 | \item{season}{Either numeric or character} 13 | 14 | \item{season_type}{Character - either "Regular" or "Playoffs"} 15 | } 16 | \value{ 17 | Returns a tibble 18 | } 19 | \description{ 20 | Get ESPN QBR for NFL football 21 | } 22 | -------------------------------------------------------------------------------- /man/get_nfl_schedule.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_nfl_schedule.R 3 | \name{get_nfl_schedule} 4 | \alias{get_nfl_schedule} 5 | \title{Get NFL schedule for a specific year from ESPN's API} 6 | \usage{ 7 | get_nfl_schedule(season) 8 | } 9 | \arguments{ 10 | \item{season}{Either numeric or character} 11 | } 12 | \value{ 13 | Returns a tibble 14 | } 15 | \description{ 16 | Get NFL schedule for a specific year from ESPN's API 17 | } 18 | \examples{ 19 | # Get all games from 2018 season, note that this will have some overlap 20 | # between seasons, for example 2018 returns 2018-01-01 to 2018-12-31 21 | get_nfl_schedule(season = "2018") 22 | } 23 | -------------------------------------------------------------------------------- /man/get_nfl_standings.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_nfl_standings.R 3 | \name{get_nfl_standings} 4 | \alias{get_nfl_standings} 5 | \title{Get NFL standings for a specific season from ESPN's API} 6 | \usage{ 7 | get_nfl_standings(season = 2019, quiet = FALSE) 8 | } 9 | \arguments{ 10 | \item{season}{Either numeric or character} 11 | } 12 | \value{ 13 | Returns a tibble 14 | } 15 | \description{ 16 | Get NFL standings for a specific season from ESPN's API 17 | } 18 | \examples{ 19 | # Get standings from 2018 season 20 | get_nfl_standings(season = "2018") 21 | 22 | # Get standings from 2010 season 23 | get_nfl_standings(2010) 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /man/get_nfl_teams.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_nfl_teams.R 3 | \name{get_nfl_teams} 4 | \alias{get_nfl_teams} 5 | \title{Return all NFL teams with their name, abbreviation,} 6 | \usage{ 7 | get_nfl_teams() 8 | } 9 | \value{ 10 | tibble 11 | } 12 | \description{ 13 | Return all NFL teams with their name, abbreviation, 14 | } 15 | \examples{ 16 | 17 | get_nfl_teams() 18 | } 19 | -------------------------------------------------------------------------------- /man/get_sharpe_data.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_sharpe_data.R 3 | \name{get_sharpe_data} 4 | \alias{get_sharpe_data} 5 | \title{Get some of Lee Sharpe's datasets from GitHub} 6 | \usage{ 7 | get_sharpe_data(dataset = "games") 8 | } 9 | \arguments{ 10 | \item{dataset}{Character only, one of "airports", "closing_lines", "draft_picks", "draft_values", "games", "logos", "pff_pfr_map_v1", "positions", "rosters", "sc_lines", "standings", "teamcolors", "teams", "trades", "win_totals"} 11 | } 12 | \value{ 13 | tibble 14 | } 15 | \description{ 16 | Get some of Lee Sharpe's datasets from GitHub 17 | } 18 | \examples{ 19 | 20 | # Get NFL games and their outcomes 21 | get_sharpe_data(dataset = "games") 22 | 23 | # Get team abbreviations/names/etc by year 24 | get_sharpe_data(dataset = "teams") 25 | } 26 | -------------------------------------------------------------------------------- /man/gt_hulk_color.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/gt_hulk_color.R 3 | \name{gt_hulk_color} 4 | \alias{gt_hulk_color} 5 | \title{Apply 'hulk' palette to specific columns in a gt table. The hulk names comes from the idea of a divergin purple and green theme that is colorblind safe and visually appealing. It is a useful alternative to the red/green palette.} 6 | \usage{ 7 | gt_hulk_color(gt_object, ..., domain = NULL, trim = FALSE, reverse = FALSE) 8 | } 9 | \arguments{ 10 | \item{gt_object}{An existing gt table object} 11 | 12 | \item{...}{columns to apply color to} 13 | 14 | \item{trim}{trim the palette to give less intense maximal colors} 15 | 16 | \item{reverse}{reverse the color palette. The default is green = high and purple = low, but reverse = TRUE will make purple high and green low.} 17 | } 18 | \value{ 19 | Returns a gt table 20 | } 21 | \description{ 22 | Apply 'hulk' palette to specific columns in a gt table. The hulk names comes from the idea of a divergin purple and green theme that is colorblind safe and visually appealing. It is a useful alternative to the red/green palette. 23 | } 24 | \examples{ 25 | # basic use 26 | mtcars |> 27 | head() |> 28 | gt::gt() |> 29 | gt_hulk_color(mpg) 30 | 31 | mtcars |> 32 | head() |> 33 | gt::gt() |> 34 | # trim gives small range of colors 35 | gt_hulk_color(mpg:disp, trim = TRUE) 36 | 37 | # option to reverse the color palette 38 | mtcars |> 39 | head() |> 40 | gt::gt() |> 41 | # trim gives small range of colors 42 | gt_hulk_color(mpg:disp, rev = TRUE) 43 | } 44 | -------------------------------------------------------------------------------- /man/gt_theme_538.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/gt_theme_538.R 3 | \name{gt_theme_538} 4 | \alias{gt_theme_538} 5 | \title{Apply FiveThirtyEight theme to a gt table} 6 | \usage{ 7 | gt_theme_538(data, ...) 8 | } 9 | \arguments{ 10 | \item{data}{An existing gt table object} 11 | 12 | \item{...}{Optional additional arguments to gt::table_options()} 13 | } 14 | \value{ 15 | Returns a tibble 16 | } 17 | \description{ 18 | Apply FiveThirtyEight theme to a gt table 19 | } 20 | -------------------------------------------------------------------------------- /man/gt_theme_espn.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/gt_theme_espn.R 3 | \name{gt_theme_espn} 4 | \alias{gt_theme_espn} 5 | \title{Apply ESPN theme to a gt table} 6 | \usage{ 7 | gt_theme_espn(data, ...) 8 | } 9 | \arguments{ 10 | \item{data}{An existing gt table object} 11 | 12 | \item{...}{Optional additional arguments to gt::table_options()} 13 | } 14 | \value{ 15 | Returns a tibble 16 | } 17 | \description{ 18 | Apply ESPN theme to a gt table 19 | } 20 | -------------------------------------------------------------------------------- /man/scrape_espn_stats.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/scrape_espn_stats.R 3 | \name{scrape_espn_stats} 4 | \alias{scrape_espn_stats} 5 | \title{Scrape NFL stats from ESPN} 6 | \usage{ 7 | scrape_espn_stats(season = 2019, stats = "receiving", season_type = "Regular") 8 | } 9 | \arguments{ 10 | \item{season}{character or numeric - greater than 1990} 11 | 12 | \item{stats}{character - either receiving, passing, or rushing} 13 | 14 | \item{season_type}{character - either Regular or Playoffs} 15 | } 16 | \value{ 17 | tibble 18 | } 19 | \description{ 20 | Scrape NFL stats from ESPN 21 | } 22 | \examples{ 23 | scrape_espn_stats(season = 2000, stats = "passing") 24 | 25 | } 26 | -------------------------------------------------------------------------------- /man/scrape_espn_win_rate.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/scrape_espn_win_rate.R 3 | \name{scrape_espn_win_rate} 4 | \alias{scrape_espn_win_rate} 5 | \title{Scrape ESPN Pass/Run Block/Rush Win Rates ratings for a specific season from ESPN's site} 6 | \usage{ 7 | scrape_espn_win_rate(season = 2022) 8 | } 9 | \value{ 10 | Returns a tibble 11 | } 12 | \description{ 13 | Scrape ESPN Pass/Run Block/Rush Win Rates ratings for a specific season from ESPN's site 14 | } 15 | \examples{ 16 | # Get off and def pass/run win rates 17 | scrape_espn_win_rate() 18 | } 19 | -------------------------------------------------------------------------------- /man/scrape_fpi.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/scrape_fpi.R 3 | \name{scrape_fpi} 4 | \alias{scrape_fpi} 5 | \title{Scrape ESPN FPI ratings for a specific season from ESPN's site} 6 | \usage{ 7 | scrape_fpi(season = 2020, stat = "FPI") 8 | } 9 | \arguments{ 10 | \item{season}{Either numeric or character} 11 | 12 | \item{stat}{One of 'FPI', 'EFF' or 'PROJ'} 13 | } 14 | \value{ 15 | Returns a tibble 16 | } 17 | \description{ 18 | Scrape ESPN FPI ratings for a specific season from ESPN's site 19 | } 20 | \examples{ 21 | # Get team offensive and defensive efficiency from 2020 season 22 | scrape_fpi(2020, stat = "EFF") 23 | } 24 | -------------------------------------------------------------------------------- /man/scrape_nfl_qbr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/scrape_nfl_qbr.R 3 | \name{scrape_nfl_qbr} 4 | \alias{scrape_nfl_qbr} 5 | \title{Scrape ESPN QBR for NFL football} 6 | \usage{ 7 | scrape_nfl_qbr(season = 2019, week = NA, season_type = "Regular") 8 | } 9 | \arguments{ 10 | \item{season}{Either numeric or character. Must be between 2006 and current season.} 11 | 12 | \item{week}{Either NA to return season, week 1 to 4 for playoffs, or 1 to 17 for regular season.} 13 | 14 | \item{season_type}{Character - either "Regular" or "Playoffs"} 15 | } 16 | \value{ 17 | Returns a tibble 18 | } 19 | \description{ 20 | Scrapes QBR tables for NFL QBs by specific week or at season level. 21 | } 22 | \details{ 23 | This function supercedes `get_nfl_qbr` due to the ESPN API altering past data. 24 | } 25 | \examples{ 26 | # Scrape ALL Playoff QBR from 2016 season 27 | scrape_nfl_qbr("2016", season_type = "Playoffs", week = NA) 28 | 29 | # Scrape Regular season QBR for week 4 of 2019 30 | scrape_nfl_qbr("2019", season_type = "Regular", week = 4) 31 | } 32 | -------------------------------------------------------------------------------- /man/scrape_nfl_standings.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/scrape_nfl_standings.R 3 | \name{scrape_nfl_standings} 4 | \alias{scrape_nfl_standings} 5 | \title{Scrape NFL standings for a specific season from ESPN's site} 6 | \usage{ 7 | scrape_nfl_standings(season, add_superbowls = FALSE) 8 | } 9 | \arguments{ 10 | \item{season}{Either numeric or character} 11 | 12 | \item{add_superbowls}{Join superbowl winners (appropriate for historical data or after season over)} 13 | } 14 | \value{ 15 | Returns a tibble 16 | } 17 | \description{ 18 | Scrape NFL standings for a specific season from ESPN's site 19 | } 20 | \examples{ 21 | # Get standings from 2018 season 22 | scrape_nfl_standings(season = "2018") 23 | 24 | # Get standings from 2010 season 25 | scrape_nfl_standings(2000, add_superbowls = TRUE) 26 | } 27 | -------------------------------------------------------------------------------- /man/scrape_nfl_weekly_standings.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/scrape_nfl_weekly_standings.R 3 | \name{scrape_nfl_weekly_standings} 4 | \alias{scrape_nfl_weekly_standings} 5 | \title{Scrape NFL weekly outcomes by week} 6 | \usage{ 7 | scrape_nfl_weekly_standings(season = 2020, tidy = FALSE) 8 | } 9 | \arguments{ 10 | \item{season}{character or numeric - greater than 1990} 11 | 12 | \item{tidy}{logical - either TRUE to stack data by game/week or FALSE to return table as is} 13 | } 14 | \value{ 15 | tibble 16 | } 17 | \description{ 18 | Scrape NFL weekly outcomes by week 19 | } 20 | \examples{ 21 | 22 | # Here we run w/ tidy = FALSE to get the exact table from PFR 23 | scrape_nfl_weekly_standings(season = 2020, tidy = FALSE) 24 | 25 | # Here we scrape the outcome and stack the games on top of eachother 26 | scrape_nfl_weekly_standings(season = 2020, tidy = TRUE) 27 | } 28 | -------------------------------------------------------------------------------- /man/scrape_superbowls.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/scrape_superbowls.R 3 | \name{scrape_superbowls} 4 | \alias{scrape_superbowls} 5 | \title{Scrape NFL superbowl winners for all years} 6 | \usage{ 7 | scrape_superbowls() 8 | } 9 | \value{ 10 | Returns a tibble 11 | } 12 | \description{ 13 | Scrape NFL superbowl winners for all years 14 | } 15 | \examples{ 16 | # Get superbowls 17 | scrape_superbowls() 18 | } 19 | -------------------------------------------------------------------------------- /man/scrape_team_stats_nfl.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/scrape_team_stats_nfl.R 3 | \name{scrape_team_stats_nfl} 4 | \alias{scrape_team_stats_nfl} 5 | \title{Scrape NFL stats from nfl.com at the team level.} 6 | \usage{ 7 | scrape_team_stats_nfl(season = 2019, stats = "passing", role = "offense") 8 | } 9 | \arguments{ 10 | \item{season}{character or numeric - greater than 1971, note that some data is missing for earlier seasons.} 11 | 12 | \item{stats}{character - either "passing", "rushing", "receiving", "scoring", "downs"} 13 | 14 | \item{role}{character - "offense" or "defense"} 15 | } 16 | \value{ 17 | tibble 18 | } 19 | \description{ 20 | Please note that the column names are identical between offense/defense and you should reference the 'role' column for offensive or defensive stats. 21 | } 22 | \examples{ 23 | 24 | # Get the NFL.com game-level stats for offense in 2018 25 | scrape_team_stats_nfl(season = 2018, stats = "passing", role = "offense") 26 | 27 | # Get the NFL.com team-level passing stats for defense in 2014 28 | scrape_team_stats_nfl(season = "2014", stats = "rushing", role = "defense") 29 | } 30 | -------------------------------------------------------------------------------- /man/scrape_weekly_leaders.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/scrape_weekly_leaders.R 3 | \name{scrape_weekly_leaders} 4 | \alias{scrape_weekly_leaders} 5 | \title{Scrape weekly NFL leaders stats from ESPN} 6 | \usage{ 7 | scrape_weekly_leaders(season = 2019, week = 1, stats = "passing") 8 | } 9 | \arguments{ 10 | \item{season}{character or numeric - greater than 2002} 11 | 12 | \item{week}{character or numeric - 1 to 17 for regular season} 13 | 14 | \item{stats}{character - either receiving, passing, or rushing} 15 | } 16 | \value{ 17 | tibble 18 | } 19 | \description{ 20 | Scrape weekly NFL leaders stats from ESPN 21 | } 22 | \examples{ 23 | scrape_weekly_leaders(season = 2002, stats = "passing", week = 1) 24 | } 25 | -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /qbr-pdf.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jthomasmock/espnscrapeR/790487f2b5cf243148ded112c5a6dc897aee7d74/qbr-pdf.pdf -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /vignettes/qbr_example.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "QBR Examples" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{QBR Examples} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | comment = "#>" 14 | ) 15 | ``` 16 | 17 | ## Introduction 18 | 19 | This short guide focuses on using `espnscrapeR` or the `nflverse/espnscrapeR-data` repo to access QBR data. 20 | 21 | ## Setup 22 | 23 | If you have never installed the necessary R packages, go ahead and expand the collapsed section below, otherwise skip ahead to the "Load and Prep" stage. 24 | 25 |
    Package Installation 26 | 27 | You'll need the following packages to get started. Note that as of now, `espnscrapeR` is not on CRAN so you'll need to install it from GitHub as seen below. 28 | 29 | ```{r install, eval = FALSE} 30 | install.packages(c("tidyverse", "gt", "remotes"), type = "binary") 31 | remotes::install_github("espnscrapeR") 32 | ``` 33 | 34 | 35 |
    36 | 37 | ## Load and Prep 38 | 39 | Go ahead and load the packages to get started. 40 | 41 | ```{r setup} 42 | library(espnscrapeR) 43 | library(tidyverse) 44 | library(gt) 45 | ``` 46 | 47 | You can get the data directly from ESPN's API. 48 | 49 | ```{r} 50 | # season level data (1x row per QB per season) 51 | qbr_2020 <- get_nfl_qbr(2020, week = NA) 52 | ``` 53 | 54 | But it'll be easier and recommended to just read in the data directly with either `nflreadr` or just the raw URL. 55 | 56 | ```{r, message=FALSE, warning=FALSE} 57 | nfl_qbr_season <- readr::read_csv("https://raw.githubusercontent.com/nflverse/espnscrapeR-data/master/data/qbr-nfl-season.csv") 58 | nfl_qbr_season <- nflreadr::load_espn_qbr("nfl", seasons = 2006:2020) 59 | ``` 60 | 61 | This is the QBR values for all QBs at the season level from 2006 to now. The `dplyr::glimpse()` function can be used to quickly see the type of the columns (IE numeric, character, etc) and the top few values. You can think of it as a beefed up version of the `str()` function. 62 | 63 | ```{r} 64 | nfl_qbr_season %>% 65 | glimpse() 66 | ``` 67 | 68 | ## Work with the data 69 | 70 | ### Group By 71 | 72 | We can `group_by()` the season and find the median QBR per season. 73 | 74 | ```{r} 75 | nfl_qbr_season %>% 76 | group_by(season) %>% 77 | summarize(qbr_median = median(qbr_total), .groups = "drop") 78 | ``` 79 | 80 | We can also `group_by()` the season and find the max `n` values per season. 81 | 82 | ```{r} 83 | top_16_per_yr <- nfl_qbr_season %>% 84 | filter(qb_plays >= 100) %>% 85 | select(season, team_abb, name_short, qbr_total) %>% 86 | # group by season 87 | group_by(season) %>% 88 | # get top 16 89 | slice_max(order_by = qbr_total, n = 16) %>% 90 | # add the grouped median 91 | mutate(qbr_median = median(qbr_total)) %>% 92 | ungroup() 93 | 94 | top_16_per_yr 95 | ``` 96 | 97 | We can then visualize this with a quick `ggplot`. 98 | 99 | ```{r} 100 | top_16_per_yr %>% 101 | ggplot(aes(x = season, y = qbr_total, group = season)) + 102 | geom_boxplot(alpha = 0.5) + 103 | geom_jitter(width = 0.2, alpha = 0.5) + 104 | geom_point(aes(y = qbr_median), color = "red", size = 3) + 105 | theme_minimal() 106 | ``` 107 | 108 | Alternatively you can also find the median by quarterback. 109 | 110 | ```{r} 111 | nfl_qbr_season %>% 112 | filter(qb_plays >= 100) %>% 113 | group_by(name_short) %>% 114 | summarize( 115 | median = median(qbr_total), 116 | years = range(season) %>% paste0(collapse = "-"), 117 | active = if_else(max(season) == 2020, "Active", "Retired"), 118 | .groups = "drop" 119 | ) %>% 120 | arrange(desc(median)) 121 | ``` 122 | 123 | 124 | ```{r, echo=FALSE, eval = FALSE} 125 | nfl_qbr_season %>% 126 | group_by(name_short) %>% 127 | mutate(median = median(qbr_total), q5 = quantile(qbr_total, 0.5)) %>% 128 | filter(n() > 5) %>% 129 | ungroup() %>% #select(median, q5) 130 | filter(median >= median(qbr_total)) %>% 131 | # %>% 132 | # # mutate(qu = quantile(qbr_total)) 133 | # filter(median <= quantile(qbr_total, 0.15) | median >= quantile(qbr_total, 0.75)) %>% 134 | ggplot(aes(x = qbr_total, y = fct_reorder(short_name, qbr_total), color = season)) + 135 | geom_boxplot() + 136 | geom_jitter() + 137 | scale_color_viridis_c(direction = -1) 138 | ``` 139 | --------------------------------------------------------------------------------