Shameless Plug Section

If you like Fantasy Football and have an interest in learning how to code, check out our Ultimate Guide on Learning Python with Fantasy Football Online Course. Here is a link to purchase for 15% off. The course includes 15 chapters of material, 14 hours of video, hundreds of data sets, lifetime updates, and a Slack channel invite to join the Fantasy Football with Python community.

Finding overdrafted and underdrafted players in our VOR model

In this part of the intermediate series, we are going to continue to work on our 2021 VOR draft model. Last time we simply created our VOR rankings. This time, we are going to be comparing those rankings to ADP to find players who are currently being underdrafted or overdrated.

For example, if a player is being drafted at pick #100 and our VOR model has them ranked player #120, they are being overdrafted. We should avoid them, until we can grab at pick #120 or greater. Truth be told, you can do this with any ranking model - but the exercise is good practice in joining DataFrames together (and actually is pretty useful for your drafts). In previous editions of our draft model, I've gone in to more detail in how looking at ADP can help give you an edge in your drafts.

The Code

As always, we are going to start out this tutorial by importing our standard libraries.

If the below block of code confuses you, check out the last post in this series! Here, we're simply regenerating the VOR model we came up with last time in one, single block of code.

Player Team POS RUSH_ATT RUSH_YD RUSH_TD REC REC_YD REC_TD FL PASS_ATT CMP PASS_YD PASS_TD INTS STANDARD HALF_PPR PPR REPLACEMENT_VALUE VOR
0 Christian McCaffrey CAR RB 297.2 1301.9 12.0 93.8 760.1 4.3 2.5 0.0 0.0 0.0 0.0 0.0 299.00 345.90 392.80 139.66 253.14
1 Dalvin Cook MIN RB 316.6 1487.7 12.9 54.8 472.9 2.0 2.9 0.0 0.0 0.0 0.0 0.0 279.66 307.06 334.46 139.66 194.80
2 Alvin Kamara NO RB 206.9 941.6 9.7 81.0 689.3 3.5 1.9 0.0 0.0 0.0 0.0 0.0 238.49 278.99 319.49 139.66 179.83
3 Derrick Henry TEN RB 343.1 1703.7 14.2 23.8 178.6 1.1 2.7 0.0 0.0 0.0 0.0 0.0 274.63 286.53 298.43 139.66 158.77
4 Travis Kelce KC TE 0.0 0.0 0.0 112.4 1399.3 10.2 0.8 0.0 0.0 0.0 0.0 0.0 199.53 255.73 311.93 154.76 157.17

Now, we need to doo two things differently than we did last time. We need to join the ADP dataframe with the VOR dataframe generated above, and also calculate the difference between our VOR rank and ADP. We can do this all in a single line of code using merge and assign. The assign method isn't a method I've used frequently in my content. It's simply another way of assigning a column to a DataFrame, and can be useful in certain situations.

Player Team POS VOR VOR_RANK ADP_RANK OVER_UNDER_DRAFTED
0 Christian McCaffrey CAR RB 253.140 1.0 1.0 0.0
1 Dalvin Cook MIN RB 194.800 2.0 2.0 0.0
2 Alvin Kamara NO RB 179.830 3.0 3.0 0.0
3 Derrick Henry TEN RB 158.770 4.0 4.0 0.0
4 Travis Kelce KC TE 157.170 5.0 8.0 3.0
5 Davante Adams GB WR 150.650 6.0 6.0 0.0
6 Saquon Barkley NYG RB 142.990 7.0 7.0 0.0
7 Austin Ekeler LAC RB 142.000 8.0 13.0 5.0
8 Ezekiel Elliott DAL RB 138.940 9.0 5.0 -4.0
9 Aaron Jones GB RB 137.520 10.0 10.0 0.0
10 Tyreek Hill KC WR 135.960 11.0 12.0 1.0
11 Jonathan Taylor IND RB 132.200 12.0 11.0 -1.0
12 Joe Mixon CIN RB 125.340 13.0 21.0 8.0
13 Stefon Diggs BUF WR 123.380 14.0 14.0 0.0
14 Najee Harris PIT RB 119.730 15.0 15.0 0.0
15 DeAndre Hopkins ARI WR 119.240 16.0 16.0 0.0
16 Nick Chubb CLE RB 116.980 17.0 9.0 -8.0
17 Antonio Gibson WAS RB 112.120 18.0 17.0 -1.0
18 Clyde Edwards-Helaire KC RB 103.160 19.0 24.0 5.0
19 Calvin Ridley ATL WR 102.100 20.0 20.0 0.0
20 D'Andre Swift DET RB 101.380 21.0 34.0 13.0
21 Darren Waller LV TE 100.790 22.0 22.0 0.0
22 George Kittle SF TE 100.380 23.0 26.0 3.0
23 Chris Carson SEA RB 96.160 24.0 33.0 9.0
24 David Montgomery CHI RB 90.440 25.0 30.0 5.0
25 Miles Sanders PHI RB 87.970 26.0 37.0 11.0
26 Keenan Allen LAC WR 86.550 27.0 27.0 0.0
27 Justin Jefferson MIN WR 85.310 28.0 23.0 -5.0
28 Josh Jacobs LV RB 81.200 29.0 36.0 7.0
29 D.K. Metcalf SEA WR 80.130 30.0 19.0 -11.0
30 A.J. Brown TEN WR 75.770 31.0 25.0 -6.0
31 Josh Allen BUF QB 75.224 32.0 29.0 -3.0
32 Mike Davis ATL RB 75.170 33.0 54.0 21.0
33 Darrell Henderson LAR RB 73.460 34.0 59.0 25.0
34 Patrick Mahomes KC QB 72.166 35.0 18.0 -17.0
35 James Robinson JAC RB 70.260 36.0 42.0 6.0
36 Chase Edmonds ARI RB 70.080 37.0 63.0 26.0
37 Allen Robinson CHI WR 68.690 38.0 31.0 -7.0
38 Robert Woods LAR WR 62.820 39.0 38.0 -1.0
39 Terry McLaurin WAS WR 61.300 40.0 28.0 -12.0
40 Amari Cooper DAL WR 59.650 41.0 40.0 -1.0
41 Tyler Lockett SEA WR 55.650 42.0 48.0 6.0
42 Kyler Murray ARI QB 53.538 43.0 39.0 -4.0
43 Myles Gaskin MIA RB 52.530 44.0 49.0 5.0
44 Cooper Kupp LAR WR 50.680 45.0 45.0 0.0
45 Mike Evans TB WR 49.180 46.0 35.0 -11.0
46 Mark Andrews BAL TE 48.640 47.0 50.0 3.0
47 CeeDee Lamb DAL WR 47.350 48.0 32.0 -16.0
48 Kareem Hunt CLE RB 45.670 49.0 53.0 4.0
49 Adam Thielen MIN WR 45.350 50.0 47.0 -3.0
50 Lamar Jackson BAL QB 43.020 51.0 44.0 -7.0
51 D.J. Moore CAR WR 41.020 52.0 51.0 -1.0
52 Chris Godwin TB WR 39.170 53.0 41.0 -12.0
53 Diontae Johnson PIT WR 37.030 54.0 52.0 -2.0
54 Julio Jones TEN WR 36.650 55.0 43.0 -12.0
55 Dak Prescott DAL QB 36.486 56.0 56.0 0.0
56 Kyle Pitts ATL TE 34.660 57.0 46.0 -11.0
57 T.J. Hockenson DET TE 33.730 58.0 58.0 0.0
58 Tee Higgins CIN WR 29.760 59.0 64.0 5.0
59 Russell Wilson SEA QB 28.106 60.0 55.0 -5.0
60 Aaron Rodgers GB QB 27.790 61.0 57.0 -4.0
61 Raheem Mostert SF RB 27.410 62.0 73.0 11.0
62 Javonte Williams DEN RB 25.650 63.0 61.0 -2.0
63 Justin Herbert LAC QB 24.618 64.0 62.0 -2.0
64 JuJu Smith-Schuster PIT WR 24.580 65.0 68.0 3.0
65 Courtland Sutton DEN WR 23.500 66.0 77.0 11.0
66 Kenny Golladay NYG WR 21.770 67.0 69.0 2.0
67 Odell Beckham CLE WR 21.690 68.0 65.0 -3.0
68 Brandon Aiyuk SF WR 19.130 69.0 60.0 -9.0
69 Ja'Marr Chase CIN WR 18.420 70.0 66.0 -4.0
70 Tom Brady TB QB 17.972 71.0 72.0 1.0
71 Brandin Cooks HOU WR 17.950 72.0 89.0 17.0
72 Robby Anderson CAR WR 17.660 73.0 76.0 3.0
73 Tyler Boyd CIN WR 14.810 74.0 86.0 12.0
74 Damien Harris NE RB 14.520 75.0 74.0 -1.0
75 Leonard Fournette TB RB 14.480 76.0 81.0 5.0
76 Chase Claypool PIT WR 12.530 77.0 67.0 -10.0
77 D.J. Chark JAC WR 12.230 78.0 95.0 17.0
78 Deebo Samuel SF WR 7.750 79.0 82.0 3.0
79 Logan Thomas WAS TE 7.540 80.0 80.0 0.0
80 Michael Carter NYJ RB 6.120 81.0 84.0 3.0
81 Kenyan Drake LV RB 6.000 82.0 102.0 20.0
82 Jerry Jeudy DEN WR 5.790 83.0 71.0 -12.0
83 David Johnson HOU RB 5.490 84.0 114.0 30.0
84 Zack Moss BUF RB 5.360 85.0 96.0 11.0
85 Tyler Higbee LAR TE 5.310 86.0 110.0 24.0
86 Melvin Gordon DEN RB 4.550 87.0 78.0 -9.0
87 J.D. McKissic WAS RB 4.390 88.0 138.0 50.0
88 Jamaal Williams DET RB 3.080 89.0 116.0 27.0
89 Noah Fant DEN TE 2.260 90.0 79.0 -11.0
90 Dallas Goedert PHI TE 1.640 91.0 87.0 -4.0
91 Jarvis Landry CLE WR 0.000 93.5 100.0 6.5
92 James Conner ARI RB 0.000 93.5 99.0 5.5
93 Robert Tonyan GB TE 0.000 93.5 90.0 -3.5
94 Jalen Hurts PHI QB 0.000 93.5 97.0 3.5
95 Laviska Shenault JAC WR -0.480 96.0 93.0 -3.0
96 Michael Gallup DAL WR -0.680 97.0 115.0 18.0
97 Ryan Tannehill TEN QB -0.942 98.0 92.0 -6.0
98 James White NE RB -3.220 99.0 160.0 61.0
99 Trey Sermon SF RB -3.600 100.0 85.0 -15.0

How we use this information is simple. The more negative a number, the less we want to focus on a certain player. For example, Ezekiel Elliot ranks #9 on our VOR ranking list, but is currently being drafted, on average, at the #5 pick. If we are strictly following our model, we don't want to draft Zeke unless he drops to the #9 pick or farther. However, if Zeke does fall beyond #9, drafting him comes in to play again. In other words, we want to keep these values in mind, but we also want to consider where WE are currently drafting. Average draft position values are just that - average draft positions. Our league could play out differently, even though ADP gives us some indication of what is "most likely" to occur.

The likelihood that a player being drafted above value comes in to value is proportional to the spread between VOR rank and ADP rank. The more negative the difference between these two numbers, the less likely a player is to come in to a draftable position at some point during the draft.

The more postive the difference between these two numbers, the more likely it is a player will remain draftable for the majority or entirety of the draft. When targeting a player with a high positive VOR and ADP rank spread, our goal shouldn't be to snatch them up as quickly as possible, though. Our goal should be to use ADP data to pick them as close to ADP (or even beyond ADP) as possible, maximizing the value we grab for the player. For example, Deandre Swift has a positive spread of +13 and is currenty being drafted, on average, at pick #34. Let's say you are pick #4 in a snake draft, 12-man league. This league format would render you pick #4, #21, #28, and #45 for your first 4 picks. At your second pick (#21), Swift is technically draftable because his VOR rank is at least equal to your current draft position. However, using ADP data, we can see that Swift is being drafted around #34, and your next pick is #28. Because there is such a large cushion (6 picks, which represents roughly 20% of the draft that has already taken place) between Swift's ADP and your next draft pick, you can probably take the risk of passing on Swift now and try to grab him at an even better value in the next round. Pick #28, your third pick, will probably be the last chance you'll have to draft Swift, though. By the time of your fourth pick, #45, Swift will most likely already be drafted.

Instead of drafting Swift with your #21 pick, it would probably be smarter to draft Waller, who is unlikely to be available by the next round (even though you would technically be over-reaching by 1 pick, which doesn't really matter).

Using ADP data is the final puzzle piece in putting together a comprehensive draft plan. Ranking models tell you who to prioritize over who, which is only part of the equation. Using ADP allows us to execute on our ranking model and gives us the best chance of finding "steals" or sleepers that will maximize our pick value at each draft turn.

That's it for this part of the intermediate series - thank you for reading!