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:

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:

sucioboys <- espn_connect(season = 2020, league_id = 899513)

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.

sucioboys_summary <- ff_league(sucioboys)
#> Using request.R from "ffscrapr"
#> No encoding supplied: defaulting to UTF-8.

str(sucioboys_summary)
#> tibble[,15] [1 × 15] (S3: tbl_df/tbl/data.frame)
#>  $ league_id      : chr "899513"
#>  $ league_name    : chr "Sucio Boys"
#>  $ 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.

sucioboys_rosters <- ff_rosters(sucioboys)
#> No encoding supplied: defaulting to UTF-8.
#> No encoding supplied: defaulting to UTF-8.

head(sucioboys_rosters) # quick snapshot of rosters
#> # A tibble: 6 x 10
#>   franchise_id franchise_name player_id player_name     team  pos   eligible_pos
#>          <int> <chr>              <int> <chr>           <chr> <chr> <list>      
#> 1            1 The Early GGod   4036348 Michael Gallup  DAL   WR    <chr [7]>   
#> 2            1 The Early GGod   4036131 Noah Fant       DEN   TE    <chr [6]>   
#> 3            1 The Early GGod    -16003 Bears D/ST      CHI   DST   <chr [3]>   
#> 4            1 The Early GGod     15920 Latavius Murray NOS   RB    <chr [6]>   
#> 5            1 The Early GGod   3055899 Harrison Butker KCC   K     <chr [3]>   
#> 6            1 The Early GGod   4241372 Marquise Brown  BAL   WR    <chr [7]>   
#> # … with 3 more variables: status <chr>, acquisition_type <chr>,
#> #   acquisition_date <dttm>

Values

Cool! Let’s pull in some additional context by adding DynastyProcess player values.

player_values <- dp_values("values-players.csv")
#> No encoding supplied: defaulting to UTF-8.

# 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.

player_ids <- dp_playerids() %>% 
  select(espn_id,fantasypros_id) %>% 
  filter(!is.na(espn_id),!is.na(fantasypros_id))
#> No encoding supplied: defaulting to UTF-8.

# 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_values <- sucioboys_rosters %>% 
  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 x 14
#>   franchise_id franchise_name player_id player_name     team  pos   eligible_pos
#>          <int> <chr>          <chr>     <chr>           <chr> <chr> <list>      
#> 1            1 The Early GGod 4242335   Jonathan Taylor IND   RB    <chr [7]>   
#> 2            1 The Early GGod 4241985   J.K. Dobbins    BAL   RB    <chr [7]>   
#> 3            1 The Early GGod 2976316   Michael Thomas  NOS   WR    <chr [7]>   
#> 4            1 The Early GGod 4241479   Tua Tagovailoa  MIA   QB    <chr [5]>   
#> 5            1 The Early GGod 4040715   Jalen Hurts     PHI   QB    <chr [5]>   
#> 6            1 The Early GGod 4239993   Tee Higgins     CIN   WR    <chr [8]>   
#> # … with 7 more variables: status <chr>, acquisition_type <chr>,
#> #   acquisition_date <dttm>, age <dbl>, ecr_2qb <dbl>, ecr_pos <dbl>,
#> #   value_2qb <int>

Let’s do some team summaries now!

value_summary <- sucioboys_values %>% 
  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 x 7
#>    franchise_id franchise_name               team_value    QB    RB    WR    TE
#>           <int> <chr>                             <int> <int> <int> <int> <int>
#>  1            5 "The Juggernaut"                  51211  7661 20007 17334  6209
#>  2            6 "OBJ's Personal Porta Potty"      47172 19545 23252  1394  2981
#>  3            2 "Coom  Dumpster"                  46885 16317  4032 24880  1656
#>  4            7 "Tony El Tigre"                   43408 14142 17399  4988  6879
#>  5            4 "I'm Also Sad "                   42807  2951 17737 17570  4549
#>  6            1 "The Early GGod"                  39982 11452 14925 10705  2900
#>  7            3 "PAKI STANS"                      39137 12951 11897 12607  1682
#>  8            9 "RAFI CUNADO"                     36116 10172 11676 13015  1253
#>  9           10 "Austin 🐐Drew Lock🐐"            25240 10278   414 14444   104
#> 10            8 "Big Coomers"                     20671  7894  1336 11281   160

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_pct <- value_summary %>% 
  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 x 7
#>    franchise_id franchise_name               team_value    QB    RB    WR    TE
#>           <int> <chr>                             <dbl> <dbl> <dbl> <dbl> <dbl>
#>  1            5 "The Juggernaut"                  0.13  0.068 0.163 0.135 0.219
#>  2            6 "OBJ's Personal Porta Potty"      0.12  0.172 0.19  0.011 0.105
#>  3            2 "Coom  Dumpster"                  0.119 0.144 0.033 0.194 0.058
#>  4            7 "Tony El Tigre"                   0.111 0.125 0.142 0.039 0.242
#>  5            4 "I'm Also Sad "                   0.109 0.026 0.145 0.137 0.16 
#>  6            1 "The Early GGod"                  0.102 0.101 0.122 0.083 0.102
#>  7            3 "PAKI STANS"                      0.1   0.114 0.097 0.098 0.059
#>  8            9 "RAFI CUNADO"                     0.092 0.09  0.095 0.102 0.044
#>  9           10 "Austin 🐐Drew Lock🐐"            0.064 0.091 0.003 0.113 0.004
#> 10            8 "Big Coomers"                     0.053 0.07  0.011 0.088 0.006

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.

Age

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!

age_summary <- sucioboys_values %>% 
  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 x 10
#> # Groups:   franchise_id, franchise_name [10]
#>    franchise_id franchise_name     age_QB age_RB age_TE age_WR count_QB count_RB
#>           <int> <chr>               <dbl>  <dbl>  <dbl>  <dbl>    <int>    <int>
#>  1            1 "The Early GGod"     23.7   22.3   24.7   25.8        4        6
#>  2            2 "Coom  Dumpster"     28.4   26.5   26.8   25.7        4        7
#>  3            3 "PAKI STANS"         28.3   24.9   23.6   25.7        3        6
#>  4            4 "I'm Also Sad "      35.8   24.6   28.4   27.1        2        5
#>  5            5 "The Juggernaut"     24.5   24.2   31.3   25.2        3        8
#>  6            6 "OBJ's Personal P…   24.5   24.4   25     24.3        3        6
#>  7            7 "Tony El Tigre"      24.5   24.7   27.5   26.2        3        5
#>  8            8 "Big Coomers"        23.8   27     28.7   27.2        3        7
#>  9            9 "RAFI CUNADO"        34.6   25.5   26     24.1        3        5
#> 10           10 "Austin 🐐Drew Lo…   31.4   27.8   31.8   24.9        3        5
#> # … with 2 more variables: count_TE <int>, count_WR <int>

Next steps

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?