In this vignette, I’ll walk through how to get started with a basic dynasty value analysis on ESPN, pulling in roster data.
We’ll start by loading the packages:
library(ffscrapr)
library(dplyr)
library(tidyr)
In ESPN, you can find the league ID by looking in the URL - it’s the number immediately after ?leagueId in this example URL: https://fantasy.espn.com/football/team?leagueId=899513&seasonId=2020
Let’s set up a connection to this league:
<- espn_connect(season = 2020, league_id = 899513)
sucioboys
sucioboys#> <ESPN connection 2020_899513>
#> List of 4
#> $ platform : chr "ESPN"
#> $ season : chr "2020"
#> $ league_id: chr "899513"
#> $ cookies : NULL
#> - attr(*, "class")= chr "espn_conn"
I’ve done this with the espn_connect()
function,
although you can also do this from the ff_connect()
call -
they are equivalent. Most if not all of the remaining functions after
this point are prefixed with “ff_”.
Cool! Let’s have a quick look at what this league is like.
<- ff_league(sucioboys)
sucioboys_summary #> Using request.R from "ffscrapr"
str(sucioboys_summary)
#> tibble [1 × 16] (S3: tbl_df/tbl/data.frame)
#> $ league_id : chr "899513"
#> $ league_name : chr "Sucio Boys"
#> $ season : int 2020
#> $ league_type : chr "keeper"
#> $ franchise_count: int 10
#> $ qb_type : chr "2QB/SF"
#> $ idp : logi FALSE
#> $ scoring_flags : chr "0.5_ppr"
#> $ best_ball : logi FALSE
#> $ salary_cap : logi FALSE
#> $ player_copies : num 1
#> $ years_active : chr "2018-2020"
#> $ qb_count : chr "1-2"
#> $ roster_size : int 24
#> $ league_depth : num 240
#> $ keeper_count : int 22
Okay, so it’s the Sucio Boys league, it’s a 2QB league with 12 teams, half ppr scoring, and rosters about 240 players.
Let’s grab the rosters now.
<- ff_rosters(sucioboys)
sucioboys_rosters
head(sucioboys_rosters) # quick snapshot of rosters
#> # A tibble: 6 × 10
#> franchise_id franchise_name playe…¹ playe…² team pos eligi…³ status acqui…⁴
#> <int> <chr> <int> <chr> <chr> <chr> <list> <chr> <chr>
#> 1 1 The Early GGod 4036348 Michae… DAL WR <chr> NORMAL DRAFT
#> 2 1 The Early GGod 4036131 Noah F… DEN TE <chr> NORMAL DRAFT
#> 3 1 The Early GGod -16003 Bears … CHI DST <chr> NORMAL DRAFT
#> 4 1 The Early GGod 15920 Latavi… NOS RB <chr> NORMAL DRAFT
#> 5 1 The Early GGod 3055899 Harris… KCC K <chr> NORMAL DRAFT
#> 6 1 The Early GGod 4241372 Marqui… BAL WR <chr> NORMAL DRAFT
#> # … with 1 more variable: acquisition_date <dttm>, and abbreviated variable
#> # names ¹player_id, ²player_name, ³eligible_pos, ⁴acquisition_type
#> # ℹ Use `colnames()` to see all variable names
Cool! Let’s pull in some additional context by adding DynastyProcess player values.
<- dp_values("values-players.csv")
player_values
# The values are stored by fantasypros ID since that's where the data comes from.
# To join it to our rosters, we'll need playerID mappings.
<- dp_playerids() %>%
player_ids select(espn_id,fantasypros_id) %>%
filter(!is.na(espn_id),!is.na(fantasypros_id))
# We'll be joining it onto rosters, so we can trim down the values dataframe
# to just IDs, age, and values
<- player_values %>%
player_values left_join(player_ids, by = c("fp_id" = "fantasypros_id")) %>%
select(espn_id,age,ecr_2qb,ecr_pos,value_2qb)
# we can join the roster's player_ids on the values' espn_id, with a bit of a type conversion first
<- sucioboys_rosters %>%
sucioboys_values mutate(player_id = as.character(player_id)) %>%
left_join(player_values, by = c("player_id"="espn_id")) %>%
arrange(franchise_id,desc(value_2qb))
head(sucioboys_values)
#> # A tibble: 6 × 14
#> franchise_id franchise_name playe…¹ playe…² team pos eligi…³ status acqui…⁴
#> <int> <chr> <chr> <chr> <chr> <chr> <list> <chr> <chr>
#> 1 1 The Early GGod 4242335 Jonath… IND RB <chr> NORMAL DRAFT
#> 2 1 The Early GGod 4241985 J.K. D… BAL RB <chr> NORMAL DRAFT
#> 3 1 The Early GGod 2976316 Michae… NOS WR <chr> NORMAL DRAFT
#> 4 1 The Early GGod 4040715 Jalen … PHI QB <chr> NORMAL ADD
#> 5 1 The Early GGod 4239993 Tee Hi… CIN WR <chr> NORMAL ADD
#> 6 1 The Early GGod 4241479 Tua Ta… MIA QB <chr> NORMAL DRAFT
#> # … with 5 more variables: acquisition_date <dttm>, age <dbl>, ecr_2qb <dbl>,
#> # ecr_pos <dbl>, value_2qb <int>, and abbreviated variable names ¹player_id,
#> # ²player_name, ³eligible_pos, ⁴acquisition_type
#> # ℹ Use `colnames()` to see all variable names
Let’s do some team summaries now!
<- sucioboys_values %>%
value_summary group_by(franchise_id,franchise_name,pos) %>%
summarise(total_value = sum(value_2qb,na.rm = TRUE)) %>%
ungroup() %>%
group_by(franchise_id,franchise_name) %>%
mutate(team_value = sum(total_value)) %>%
ungroup() %>%
pivot_wider(names_from = pos, values_from = total_value) %>%
arrange(desc(team_value)) %>%
select(franchise_id,franchise_name,team_value,QB,RB,WR,TE)
value_summary#> # A tibble: 10 × 7
#> franchise_id franchise_name team_value QB RB WR TE
#> <int> <chr> <int> <int> <int> <int> <int>
#> 1 5 "The Juggernaut" 49693 8628 18592 16747 5726
#> 2 6 "OBJ's Personal Porta Potty" 46447 20424 22738 997 2288
#> 3 7 "Tony El Tigre" 44547 15508 17020 5921 6098
#> 4 2 "Coom Dumpster" 41599 13314 2329 24668 1288
#> 5 4 "I'm Also Sad " 37216 1467 15597 16565 3587
#> 6 3 "PAKI STANS" 32697 6048 11980 12829 1840
#> 7 1 "The Early GGod" 32247 6642 13916 9406 2283
#> 8 9 "RAFI CUNADO" 31958 6878 10726 13365 989
#> 9 8 "Big Coomers" 21493 7416 1453 12408 216
#> 10 10 "Austin 🐐Drew Lock🐐" 20832 7153 276 13324 79
So with that, we’ve got a team summary of values! I like applying some context, so let’s turn these into percentages - this helps normalise it to your league environment.
<- value_summary %>%
value_summary_pct mutate_at(c("team_value","QB","RB","WR","TE"),~.x/sum(.x)) %>%
mutate_at(c("team_value","QB","RB","WR","TE"),round, 3)
value_summary_pct#> # A tibble: 10 × 7
#> franchise_id franchise_name team_value QB RB WR TE
#> <int> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 5 "The Juggernaut" 0.139 0.092 0.162 0.133 0.235
#> 2 6 "OBJ's Personal Porta Potty" 0.129 0.218 0.198 0.008 0.094
#> 3 7 "Tony El Tigre" 0.124 0.166 0.148 0.047 0.25
#> 4 2 "Coom Dumpster" 0.116 0.142 0.02 0.195 0.053
#> 5 4 "I'm Also Sad " 0.104 0.016 0.136 0.131 0.147
#> 6 3 "PAKI STANS" 0.091 0.065 0.105 0.102 0.075
#> 7 1 "The Early GGod" 0.09 0.071 0.121 0.075 0.094
#> 8 9 "RAFI CUNADO" 0.089 0.074 0.094 0.106 0.041
#> 9 8 "Big Coomers" 0.06 0.079 0.013 0.098 0.009
#> 10 10 "Austin 🐐Drew Lock🐐" 0.058 0.077 0.002 0.106 0.003
Armed with a value summary like this, we can see team strengths and weaknesses pretty quickly, and figure out who might be interested in your positional surpluses and who might have a surplus at a position you want to look at.
Another question you might ask: what is the average age of any given team?
I like looking at average age by position, but weighted by dynasty value. This helps give a better idea of age for each team - including who might be looking to offload an older veteran!
<- sucioboys_values %>%
age_summary filter(pos %in% c("QB","RB","WR","TE")) %>%
group_by(franchise_id,pos) %>%
mutate(position_value = sum(value_2qb,na.rm=TRUE)) %>%
ungroup() %>%
mutate(weighted_age = age*value_2qb/position_value,
weighted_age = round(weighted_age, 1)) %>%
group_by(franchise_id,franchise_name,pos) %>%
summarise(count = n(),
age = sum(weighted_age,na.rm = TRUE)) %>%
pivot_wider(names_from = pos,
values_from = c(age,count))
age_summary#> # A tibble: 10 × 10
#> # Groups: franchise_id, franchise_name [10]
#> franchi…¹ franc…² age_QB age_RB age_TE age_WR count…³ count…⁴ count…⁵ count…⁶
#> <int> <chr> <dbl> <dbl> <dbl> <dbl> <int> <int> <int> <int>
#> 1 1 "The E… 23.5 22.4 24.7 26 4 6 3 7
#> 2 2 "Coom … 28.7 25.9 26.8 25.6 4 7 3 6
#> 3 3 "PAKI … 29.1 25.3 23.9 25.9 3 6 2 9
#> 4 4 "I'm A… 35.4 24.8 28.7 27.4 2 5 2 8
#> 5 5 "The J… 25 24.6 31.6 25.3 3 8 2 7
#> 6 6 "OBJ's… 24.8 24.7 25.3 23.4 3 6 2 7
#> 7 7 "Tony … 24.8 25.3 27.8 26.6 3 5 3 6
#> 8 8 "Big C… 23.5 26 27.2 27.1 3 7 2 6
#> 9 9 "RAFI … 35.1 25.9 26.4 24.4 3 5 3 8
#> 10 10 "Austi… 32.2 24.4 32.2 25.5 3 5 3 5
#> # … with abbreviated variable names ¹franchise_id, ²franchise_name, ³count_QB,
#> # ⁴count_RB, ⁵count_TE, ⁶count_WR
In this vignette, I’ve used only a few functions: ff_connect, ff_league, ff_rosters, and dp_values. Now that you’ve gotten this far, why not check out some of the other possibilities?