Determining Fantasy Football Optimal Rosters in R
I found it easier to tackle this with some reference tables. The FLEX slot makes things a little tricky in that we have to find the optimal selections first and then pick the best remaining eligible player.
Anyway, this is probably overkill but should scale nicely.
library(tidyverse)
#ASSUME ORIGINAL DATA IS STORED AS df
#To start, it might be helpful to have some reference tables.
#First, what positions are eligible to be placed in what slots?
possible_slots <- tibble(Position = c("QB", "RB", "RB", "WR", "WR", "TE", "TE", "D/ST", "K"),
possible_slot = c("QB", "RB", "FLEX", "WR/TE", "FLEX", "WR/TE", "FLEX", "D/ST", "K"))
#Next, for each slot, how many players can be selected?
topn <- tibble(possible_slot = c("QB", "RB", "WR/TE", "D/ST", "K"),
n_slots = c(1, 2, 3, 1, 1))
#Ok, now let's create a record for every potential slot that a player could conceivably take
all_possible_slots <- df %>%
left_join(possible_slots, by = "Position") %>%
left_join(topn, by = "possible_slot") %>%
#For each team, week and possible slot, rank the possiblity by points (descending)
group_by(Team, Week, possible_slot) %>%
#Note the use of ties.method = first to ensure only one record per rank
mutate(rank = rank(-FPTS, ties.method = "first")) %>%
ungroup()
#Now limit just to those records where the rank is <= the number of available slots
optimal_slots <- all_possible_slots %>%
filter(rank <= n_slots)
#The FLEX slot is special case, we need to select the best REMAINING player,
#i.e. one not selected in optimal_slots
flex_slot <- all_possible_slots %>%
#limit just to the FLEX slot and remove anyone selected in optimal_slots
filter(possible_slot == "FLEX") %>%
anti_join(optimal_slots %>% select(Team, Week, PLAYER)) %>%
#Among those remaining, take the highest scoring
group_by(Team, Week) %>%
filter(rank(-FPTS, ties.method = "first") == 1) %>%
ungroup()
#Now let's bring it all together with the original data.
df %>%
#append the optimal slot (including flex)
left_join({
optimal_slots %>%
bind_rows(flex_slot) %>%
#concatenate the slot and the rank in that slot
mutate(optimal_slot = paste0(possible_slot,
ifelse(possible_slot %in% c("RB", "WR/TE"), rank, ""))) %>%
select(Team, Week, PLAYER, optimal_slot)
}, by = c("Team", "Week", "PLAYER")) %>%
#Anyone without an optimal slot should be benched
mutate(optimal_slot = coalesce(optimal_slot, "BENCH"))
# A tibble: 32 x 7
Team Week SLOT PLAYER Position FPTS optimal_slot
<chr> <dbl> <chr> <chr> <chr> <dbl> <chr>
1 Washington Beersnake 1 QB Justin Herbert QB 15.4 QB
2 Washington Beersnake 1 RB Alvin Kamara RB 15.1 RB1
3 Washington Beersnake 1 RB Saquon Barkley RB 2.7 BENCH
4 Washington Beersnake 1 WR/TE Terry McLaurin WR 6.2 FLEX
5 Washington Beersnake 1 WR/TE Keenan Allen WR 10 WR/TE3
6 Washington Beersnake 1 WR/TE Brandon Aiyuk WR 0 BENCH
7 Washington Beersnake 1 FLEX Myles Gaskin RB 7.6 RB2
8 Washington Beersnake 1 D/ST Buccaneers D/ST D/ST 3 D/ST
9 Washington Beersnake 1 K Matt Gay K 12 K
10 Washington Beersnake 1 Bench William Fuller V WR 0 BENCH
# ... with 22 more rows