Week 5 Fantasy Football Optimizations
We are BACK with optimal lineups from week 5 simulations!
I haven’t changed the script substantially since last week, and as I went over the code for scraping projections and generating lineups last week, I’ll skip some of that this week so we can focus more on the content. If you want to review it, the initial post is here.
Setup
library(data.table)
library(dtplyr)
library(tidyverse)
library(rPref)
library(kableExtra)
week <- 5
proj <- readRDS(paste0('week_', week, '_proj.RDS'))
sal <- read_csv(paste0('DKSalaries_wk_', week, '.csv'))
I’ll start with the optimized lineups pulled for week 4, with the same details as last time: 10,000 lineups, using the standard deviation of projections, completely individually based (still working on that).
sim_lu <- readRDS(paste0('sim_lineups_week_', week, '.RDS')) %>%
rename(pts_base=points) %>%
select(lineup, Name, team, position, pts_base, pts_pred, sd_pts, Salary)
glimpse(sim_lu)
## Observations: 90,000
## Variables: 8
## Groups: Name [148]
## $ lineup <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2,...
## $ Name <chr> "Bengals", "Le'Veon Bell", "Zach Ertz", "Adam Thielen...
## $ team <chr> "CIN", "NYJ", "PHI", "MIN", "DAL", "DAL", "NOS", "CAR...
## $ position <chr> "DST", "RB", "TE", "WR", "WR", "QB", "WR", "RB", "WR"...
## $ pts_base <dbl> 7.186564, 17.660541, 14.598184, 16.137217, 4.449599, ...
## $ pts_pred <dbl> 8.122294, 20.390727, 18.380255, 20.284775, 11.993178,...
## $ sd_pts <dbl> 0.7662241, 1.8326980, 2.5496124, 2.0228734, 2.4638158...
## $ Salary <dbl> 2500, 6800, 6000, 6700, 3400, 6000, 6600, 8700, 3100,...
sim_lu %>%
filter(lineup<=3) %>%
arrange(lineup, position, desc(pts_pred)) %>%
mutate_at(vars(pts_base, pts_pred, sd_pts), function(x) round(x, 2)) %>%
knitr::kable() %>%
kable_styling() %>%
column_spec(1, bold=TRUE) %>%
collapse_rows(columns = 1, valign = 'top') %>%
scroll_box(height = '500px', width = '100%')
lineup | Name | team | position | pts_base | pts_pred | sd_pts | Salary |
---|---|---|---|---|---|---|---|
1 | Bengals | CIN | DST | 7.19 | 8.12 | 0.77 | 2500 |
Dak Prescott | DAL | QB | 19.84 | 21.35 | 1.37 | 6000 | |
Christian McCaffrey | CAR | RB | 24.04 | 23.58 | 0.91 | 8700 | |
Le’Veon Bell | NYJ | RB | 17.66 | 20.39 | 1.83 | 6800 | |
Zach Ertz | PHI | TE | 14.60 | 18.38 | 2.55 | 6000 | |
Michael Thomas | NOS | WR | 18.58 | 23.33 | 2.31 | 6600 | |
Adam Thielen | MIN | WR | 16.14 | 20.28 | 2.02 | 6700 | |
Devin Smith | DAL | WR | 4.45 | 11.99 | 2.46 | 3400 | |
Darius Slayton | NYG | WR | 2.69 | 11.10 | 2.48 | 3100 | |
2 | Buccaneers | TBB | DST | 6.46 | 6.65 | 0.35 | 2200 |
Carson Wentz | PHI | QB | 20.64 | 22.70 | 1.25 | 6100 | |
Christian McCaffrey | CAR | RB | 24.04 | 24.86 | 0.91 | 8700 | |
David Johnson | ARI | RB | 20.06 | 22.34 | 1.95 | 7500 | |
Chris Thompson | WAS | RB | 11.22 | 16.40 | 2.46 | 4600 | |
Tyler Eifert | CIN | TE | 7.99 | 10.11 | 1.07 | 3300 | |
DeAndre Hopkins | HOU | WR | 19.90 | 22.96 | 2.38 | 7800 | |
Larry Fitzgerald | ARI | WR | 14.99 | 16.28 | 1.64 | 6000 | |
Auden Tate | CIN | WR | 10.67 | 11.12 | 0.98 | 3500 | |
3 | Vikings | MIN | DST | 7.38 | 10.35 | 1.36 | 3200 |
Dak Prescott | DAL | QB | 19.84 | 21.21 | 1.37 | 6000 | |
Christian McCaffrey | CAR | RB | 24.04 | 25.13 | 0.91 | 8700 | |
Leonard Fournette | JAC | RB | 18.01 | 20.21 | 1.25 | 6400 | |
Aaron Jones | GBP | RB | 15.37 | 16.79 | 1.20 | 5900 | |
Mark Andrews | BAL | TE | 11.72 | 15.14 | 2.89 | 4800 | |
DeAndre Hopkins | HOU | WR | 19.90 | 22.64 | 2.38 | 7800 | |
Auden Tate | CIN | WR | 10.67 | 11.80 | 0.98 | 3500 | |
KeeSean Johnson | ARI | WR | 7.95 | 10.72 | 2.87 | 3500 |
Who is in Optimal Lineups?
sim_lu %>%
group_by(Name, position, Salary) %>%
dplyr::summarize(lu=n_distinct(lineup)) %>%
ungroup() %>%
group_by(position) %>%
top_n(10, lu) %>%
ungroup() %>%
arrange(position, desc(lu)) %>%
mutate(Name=factor(Name),
Name=fct_reorder(Name, lu)) %>%
ggplot(aes(x=Name, y=lu/1000, fill=Salary)) +
geom_bar(stat='identity') +
facet_wrap(~position, ncol = 3, scales='free') +
coord_flip() +
scale_y_continuous(labels = scales::comma) +
scale_fill_viridis_c() +
xlab('') +
ylab('Lineups (Thousands)') +
ggtitle('Top 10 Players Present by Position')
I’ve added a color dimension, which is the player’s salary, which helps us see if we’re adding studs at high prices, or more value selections. For example, Christian McCaffrey shows up a lot despite the high price, and Auden Tate shows up more due to his lower price.
Some of my observations:
Very flat week this week at the QB position with no QB showing up in more than 2,000 lineups. (Last week, Mahomes was in almost 6,000).
Christian McCaffrey and Leonard Fournette appear to be the favorites at RB, forming a kind of ‘Tier 1’ with David Johnson close behind.
Keenan Allen and Michael Thomas are your high end recievers, and Auden Tate is your big value play, at only $3,500 salary.
TE and D have similar patterns, there’s only 5 or so that you should really consider in your lineups.
Who is getting placed in Lineups?
DraftKings provides scoring for 416 players this week, but only 102 make it into optimized lineups. Why is that? To determine, I’ll plot projected points vs salary, colored by whether or not they make it into optimized lineups, and sized by their projection standard deviation
plyr_lu <- sim_lu %>%
group_by(Name, position) %>%
dplyr::summarize(lu=n_distinct(lineup)) %>%
ungroup()
proj %>%
filter(avg_type=='weighted') %>%
mutate(Name = ifelse(pos=="DST", last_name, paste(first_name, last_name))) %>%
inner_join(sal, by=c("Name")) %>%
select(Name, team, position, points, Salary, sd_pts) %>%
left_join(plyr_lu, by='Name') %>%
replace_na(list(lu=0)) %>%
mutate(lu_bin=ifelse(lu==0, '0 Lineups', '>=1 Lineups'),
lu_5=cut(lu,5, labels = FALSE)) %>%
ggplot(aes(x=Salary, y=points, color=lu_bin, size=sd_pts)) +
geom_point() +
scale_color_manual(values = c('red', 'blue'), name="") +
geom_smooth(inherit.aes = FALSE, aes(x=Salary, y=points), method = 'lm', se=FALSE) +
ylab('Projected Points') +
xlab('Salary') +
ggtitle('Who makes it into Optimized Lineups?') +
scale_x_continuous(labels=scales::dollar)
Interestingly, we see some players below the fitted line make it into optimal lineups, but we can see that these are due to high player uncertainty, so these are players with high potential to blow up, even though their average prediction is fairly low.
Flex Configurations
In DFS lineups, you have an extra spot to use on an RB, WR, and TE of your chosing
sim_lu %>%
group_by(lineup) %>%
mutate(lineup_pts=sum(pts_pred)) %>%
group_by(lineup, position) %>%
mutate(n=n()) %>%
select(lineup, position, n, lineup_pts) %>%
distinct() %>%
spread(key=position, value=n) %>%
filter(RB>=2, TE>=1, WR>=3) %>%
mutate(flex=case_when(RB==3 ~ 'RB',
TE==2 ~ 'TE',
WR==4 ~ 'WR')) %>%
group_by(flex) %>%
dplyr::summarize(pts=median(lineup_pts),
cases=n()) %>%
knitr::kable() %>%
kable_styling(full_width = FALSE)
flex | pts | cases |
---|---|---|
RB | 153.8310 | 3313 |
TE | 155.0864 | 2043 |
WR | 155.0120 | 4644 |
Again, WRs are the flex selection most of the time. Given what we saw with the last graph, this makes sense. There are some clear favorites in the WR spot that you’ll want to get, whereas RBs are fairly replaceable this week. And the situations where you want a TE in FLEX is when one really blows up, hence the higher average score.
Pareto Lineups
lu_df <- sim_lu %>%
group_by(lineup) %>%
dplyr::summarize(lineup_pts=sum(pts_pred),
lineup_sd=sum(sd_pts)) %>%
ungroup()
pto <- psel(lu_df, low(lineup_sd) * high(lineup_pts))
ggplot(lu_df, aes(y=lineup_pts, x=lineup_sd)) +
geom_point() +
geom_point(data=pto, size=5) +
ylab('Lineup Points') +
xlab('Lineup Points St Dev') +
ggtitle('Lineup Points vs Uncertainty',
subtitle = 'Pareto Lineups Bolded')
Here’s a look at the pareto lineups.
psel(lu_df, low(lineup_sd) * high(lineup_pts)) %>%
left_join(sim_lu, by='lineup') %>%
group_by(lineup) %>%
arrange(lineup_pts, position, desc(pts_pred)) %>%
select(lineup, lineup_pts, lineup_sd, Name, team, position, pts_pred, sd_pts, Salary) %>%
mutate_at(vars(lineup_pts, lineup_sd, pts_pred, sd_pts), function(x) round(x, 2)) %>%
knitr::kable() %>%
kable_styling(fixed_thead = T) %>%
column_spec(1:3, bold=TRUE) %>%
collapse_rows(columns = 1:3, valign = 'top') %>%
scroll_box(height = '500px', width = '100%')
lineup | lineup_pts | lineup_sd | Name | team | position | pts_pred | sd_pts | Salary |
---|---|---|---|---|---|---|---|---|
5249 | 152.08 | 9.32 | Panthers | CAR | DST | 8.94 | 0.84 | 2600 |
Deshaun Watson | HOU | QB | 25.27 | 0.90 | 6700 | |||
Christian McCaffrey | CAR | RB | 24.78 | 0.91 | 8700 | |||
Alvin Kamara | NOS | RB | 23.59 | 1.42 | 8600 | |||
Leonard Fournette | JAC | RB | 19.33 | 1.25 | 6400 | |||
Tyler Eifert | CIN | TE | 9.33 | 1.07 | 3300 | |||
Larry Fitzgerald | ARI | WR | 17.64 | 1.64 | 6000 | |||
Mohamed Sanu | ATL | WR | 12.20 | 0.31 | 4200 | |||
Auden Tate | CIN | WR | 10.98 | 0.98 | 3500 | |||
6462 | 154.08 | 9.33 | Panthers | CAR | DST | 9.64 | 0.84 | 2600 |
Deshaun Watson | HOU | QB | 24.13 | 0.90 | 6700 | |||
Christian McCaffrey | CAR | RB | 24.39 | 0.91 | 8700 | |||
Leonard Fournette | JAC | RB | 19.58 | 1.25 | 6400 | |||
Greg Olsen | CAR | TE | 11.47 | 0.59 | 4000 | |||
Keenan Allen | LAC | WR | 24.30 | 2.56 | 7300 | |||
Emmanuel Sanders | DEN | WR | 14.33 | 1.00 | 5100 | |||
Courtland Sutton | DEN | WR | 14.31 | 0.99 | 4900 | |||
Mohamed Sanu | ATL | WR | 11.93 | 0.31 | 4200 | |||
594 | 160.55 | 10.64 | Panthers | CAR | DST | 8.98 | 0.84 | 2600 |
Deshaun Watson | HOU | QB | 24.76 | 0.90 | 6700 | |||
Christian McCaffrey | CAR | RB | 25.56 | 0.91 | 8700 | |||
Leonard Fournette | JAC | RB | 19.74 | 1.25 | 6400 | |||
Greg Olsen | CAR | TE | 12.28 | 0.59 | 4000 | |||
Keenan Allen | LAC | WR | 26.04 | 2.56 | 7300 | |||
Michael Thomas | NOS | WR | 20.58 | 2.31 | 6600 | |||
Mohamed Sanu | ATL | WR | 11.43 | 0.31 | 4200 | |||
Auden Tate | CIN | WR | 11.18 | 0.98 | 3500 | |||
4191 | 160.84 | 12.30 | Falcons | ATL | DST | 7.38 | 1.11 | 2500 |
Lamar Jackson | BAL | QB | 24.63 | 1.33 | 7100 | |||
Christian McCaffrey | CAR | RB | 25.34 | 0.91 | 8700 | |||
Leonard Fournette | JAC | RB | 20.17 | 1.25 | 6400 | |||
Devonta Freeman | ATL | RB | 14.78 | 1.58 | 5300 | |||
Darren Waller | OAK | TE | 21.01 | 2.27 | 5000 | |||
Keenan Allen | LAC | WR | 24.43 | 2.56 | 7300 | |||
Mohamed Sanu | ATL | WR | 12.19 | 0.31 | 4200 | |||
Auden Tate | CIN | WR | 10.91 | 0.98 | 3500 | |||
1002 | 161.38 | 12.87 | Steelers | PIT | DST | 7.95 | 1.03 | 2100 |
Carson Wentz | PHI | QB | 22.54 | 1.25 | 6100 | |||
Christian McCaffrey | CAR | RB | 24.28 | 0.91 | 8700 | |||
Leonard Fournette | JAC | RB | 19.19 | 1.25 | 6400 | |||
Darren Waller | OAK | TE | 18.33 | 2.27 | 5000 | |||
Keenan Allen | LAC | WR | 26.98 | 2.56 | 7300 | |||
Michael Thomas | NOS | WR | 19.40 | 2.31 | 6600 | |||
Mohamed Sanu | ATL | WR | 11.91 | 0.31 | 4200 | |||
Auden Tate | CIN | WR | 10.80 | 0.98 | 3500 | |||
7012 | 161.86 | 13.19 | Steelers | PIT | DST | 7.82 | 1.03 | 2100 |
Matt Ryan | ATL | QB | 20.70 | 1.02 | 5900 | |||
Christian McCaffrey | CAR | RB | 24.03 | 0.91 | 8700 | |||
Leonard Fournette | JAC | RB | 19.72 | 1.25 | 6400 | |||
Austin Hooper | ATL | TE | 14.58 | 1.25 | 4500 | |||
Michael Thomas | NOS | WR | 23.57 | 2.31 | 6600 | |||
Larry Fitzgerald | ARI | WR | 18.91 | 1.64 | 6000 | |||
Calvin Ridley | ATL | WR | 17.29 | 1.25 | 4900 | |||
Curtis Samuel | CAR | WR | 15.24 | 2.54 | 4500 | |||
3347 | 162.22 | 13.29 | Panthers | CAR | DST | 8.52 | 0.84 | 2600 |
Lamar Jackson | BAL | QB | 23.92 | 1.33 | 7100 | |||
Christian McCaffrey | CAR | RB | 26.18 | 0.91 | 8700 | |||
Aaron Jones | GBP | RB | 16.65 | 1.20 | 5900 | |||
Zach Ertz | PHI | TE | 20.90 | 2.55 | 6000 | |||
Michael Thomas | NOS | WR | 22.71 | 2.31 | 6600 | |||
Robby Anderson | NYJ | WR | 15.52 | 1.92 | 4500 | |||
Calvin Ridley | ATL | WR | 15.42 | 1.25 | 4900 | |||
Auden Tate | CIN | WR | 12.39 | 0.98 | 3500 | |||
1021 | 162.56 | 13.41 | Panthers | CAR | DST | 7.83 | 0.84 | 2600 |
Matt Ryan | ATL | QB | 21.04 | 1.02 | 5900 | |||
Christian McCaffrey | CAR | RB | 25.42 | 0.91 | 8700 | |||
Leonard Fournette | JAC | RB | 20.01 | 1.25 | 6400 | |||
Darren Waller | OAK | TE | 17.44 | 2.27 | 5000 | |||
Austin Hooper | ATL | TE | 15.11 | 1.25 | 4500 | |||
Keenan Allen | LAC | WR | 26.79 | 2.56 | 7300 | |||
Courtland Sutton | DEN | WR | 14.77 | 0.99 | 4900 | |||
Nelson Agholor | PHI | WR | 14.14 | 2.35 | 4700 | |||
8620 | 164.19 | 13.62 | Redskins | WAS | DST | 5.52 | 0.36 | 1800 |
Dak Prescott | DAL | QB | 21.56 | 1.37 | 6000 | |||
Christian McCaffrey | CAR | RB | 25.31 | 0.91 | 8700 | |||
Joe Mixon | CIN | RB | 20.25 | 1.26 | 6100 | |||
Mark Andrews | BAL | TE | 17.85 | 2.89 | 4800 | |||
Michael Thomas | NOS | WR | 23.97 | 2.31 | 6600 | |||
Keenan Allen | LAC | WR | 23.96 | 2.56 | 7300 | |||
Emmanuel Sanders | DEN | WR | 14.98 | 1.00 | 5100 | |||
Auden Tate | CIN | WR | 10.81 | 0.98 | 3500 | |||
5278 | 167.00 | 13.68 | Redskins | WAS | DST | 5.32 | 0.36 | 1800 |
Matt Ryan | ATL | QB | 20.39 | 1.02 | 5900 | |||
David Johnson | ARI | RB | 24.22 | 1.95 | 7500 | |||
Leonard Fournette | JAC | RB | 20.66 | 1.25 | 6400 | |||
Zach Ertz | PHI | TE | 20.76 | 2.55 | 6000 | |||
Austin Hooper | ATL | TE | 15.36 | 1.25 | 4500 | |||
Keenan Allen | LAC | WR | 24.87 | 2.56 | 7300 | |||
Mike Evans | TBB | WR | 23.14 | 1.78 | 7100 | |||
Auden Tate | CIN | WR | 12.27 | 0.98 | 3500 | |||
9837 | 167.04 | 14.52 | Falcons | ATL | DST | 8.25 | 1.11 | 2500 |
Matt Ryan | ATL | QB | 20.78 | 1.02 | 5900 | |||
Christian McCaffrey | CAR | RB | 25.34 | 0.91 | 8700 | |||
Leonard Fournette | JAC | RB | 20.30 | 1.25 | 6400 | |||
Mark Andrews | BAL | TE | 20.70 | 2.89 | 4800 | |||
Austin Hooper | ATL | TE | 14.98 | 1.25 | 4500 | |||
Chris Godwin | TBB | WR | 25.04 | 2.48 | 6900 | |||
Amari Cooper | DAL | WR | 19.06 | 2.66 | 6800 | |||
Auden Tate | CIN | WR | 12.59 | 0.98 | 3500 | |||
2699 | 167.08 | 15.24 | Steelers | PIT | DST | 8.39 | 1.03 | 2100 |
Teddy Bridgewater | NOS | QB | 18.38 | 1.32 | 5200 | |||
Christian McCaffrey | CAR | RB | 25.33 | 0.91 | 8700 | |||
Leonard Fournette | JAC | RB | 20.51 | 1.25 | 6400 | |||
Devonta Freeman | ATL | RB | 16.27 | 1.58 | 5300 | |||
Austin Hooper | ATL | TE | 13.43 | 1.25 | 4500 | |||
Keenan Allen | LAC | WR | 28.62 | 2.56 | 7300 | |||
Chris Godwin | TBB | WR | 21.09 | 2.48 | 6900 | |||
KeeSean Johnson | ARI | WR | 15.06 | 2.87 | 3500 | |||
5758 | 167.84 | 15.41 | Steelers | PIT | DST | 7.29 | 1.03 | 2100 |
Matt Ryan | ATL | QB | 20.50 | 1.02 | 5900 | |||
Christian McCaffrey | CAR | RB | 25.21 | 0.91 | 8700 | |||
Leonard Fournette | JAC | RB | 18.67 | 1.25 | 6400 | |||
Mark Andrews | BAL | TE | 15.46 | 2.89 | 4800 | |||
Keenan Allen | LAC | WR | 26.95 | 2.56 | 7300 | |||
Julio Jones | ATL | WR | 23.59 | 1.92 | 7700 | |||
KeeSean Johnson | ARI | WR | 17.78 | 2.87 | 3500 | |||
Auden Tate | CIN | WR | 12.40 | 0.98 | 3500 | |||
1497 | 170.49 | 16.92 | Saints | NOS | DST | 10.77 | 1.21 | 3400 |
Carson Wentz | PHI | QB | 22.94 | 1.25 | 6100 | |||
Christian McCaffrey | CAR | RB | 22.89 | 0.91 | 8700 | |||
Aaron Jones | GBP | RB | 17.20 | 1.20 | 5900 | |||
David Montgomery | CHI | RB | 15.66 | 1.17 | 5200 | |||
Mark Andrews | BAL | TE | 16.07 | 2.89 | 4800 | |||
Michael Gallup | DAL | WR | 29.86 | 4.76 | 5000 | |||
Keenan Allen | LAC | WR | 22.65 | 2.56 | 7300 | |||
Auden Tate | CIN | WR | 12.45 | 0.98 | 3500 | |||
816 | 173.76 | 18.59 | Panthers | CAR | DST | 9.01 | 0.84 | 2600 |
Deshaun Watson | HOU | QB | 23.67 | 0.90 | 6700 | |||
Leonard Fournette | JAC | RB | 18.50 | 1.25 | 6400 | |||
Joe Mixon | CIN | RB | 17.02 | 1.26 | 6100 | |||
Darren Waller | OAK | TE | 17.41 | 2.27 | 5000 | |||
Michael Gallup | DAL | WR | 25.44 | 4.76 | 5000 | |||
Michael Thomas | NOS | WR | 23.39 | 2.31 | 6600 | |||
Amari Cooper | DAL | WR | 21.62 | 2.66 | 6800 | |||
Nelson Agholor | PHI | WR | 17.69 | 2.35 | 4700 |