If you have any questions about the code here, feel free to reach out to me on Twitter or on Reddit.

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.

In this part of the intermediate series, we are going to be talking about and introducing a new concept on the blog called WOPR, which stands for weighted opportunity rating. The goal of this post will be to find a way to measure receiver "opportunity" and find those player's who are getting the most "opportunity" on their respective offenses.

How do we measure opportunity in FF?

Measuring opportunity is vital in optimizing your roster for fantasy football. Simply put, if your players are not getting opportunities, then they are not going to score you points, and watching redzone on Sunday is going to be frustrating as hell. The key to winning consistently at fantasy football is to prioritize selecting, trading for, and picking up players who are consistently involved in their respective offenses. But what does being involved mean?

Air yards is the best measure we have in fantasy football of intent. Air yards are how far a ball went during a particular throw, regardless if it was caught or not. A player's total air yards for a given week is his average depth of target times times his number of targets that week. Later in this post, we'll look at the relationship between air yards and fantasy football performance using scipy.stats and matplotlib.

How can air yards help us measure opportunity? Well, a naive measure of measuring opportunity in fantasy football is to simply look at raw targets or targets per game. This approach simply isn't adequete enough when you consider a target near the line of scrimmage doesn't have the same amount of opportunity as a 50 yard bomb that could potentially be a 70 yard play. Adding air yards to the mix can help us account for this and come up with a more nuanced measure of fantasy football opportunity.

WOPR is defined as a weighted average of a player's target market share (their individual targets / team total targets for a particular week) and a player's air yard market share (an individual's air yards / team total air yards for a particular week).

WOPR = 1.5 * target_share + 0.7 * air_yards_share

What we'll be coding in this post

In this post, we are going to be pulling NFL play by play data for the past 5 weeks and finding each player's WOPR for each week. We are going to also be throwing some matplotlib in to the mix and visualizing the relationship between air yards, yards after catch, and WOPR and receiving fantasy football points.

First things first, load up either a Google Colab notebook or jupyter ipynb notebook and import the libraries we'll need in the first cell.

Next, let's load in 2020 play by play data via nflfastR.

The next thing we need to do is to filter our DataFrame to only include pass plays and grab the relevant columns to our analysis. We're going to calculate the receiving fantasy points scored on each play, then groupby player id, player name, team, and week and consolidate the rows in to a weekly receiving stat line for each player.

From there, we'll use groupby again to find team targets and team team air yards, merge that with our weekly stat line DataFrame, and then calculate WOPR.

receiver_player_id receiver_player_name posteam week target_ind complete_pass yards_after_catch yards_gained touchdown air_yards_ind rec_fp air_yards_team target_team weekly_wopr
0 32013030-2d30-3032-3231-32373ce51f62 J.Witten LV 1 1.0 1.0 0.0 2.0 0.0 2.0 1.2 161.0 28.0 0.062267
1 32013030-2d30-3032-3231-32373ce51f62 J.Witten LV 2 1.0 1.0 0.0 3.0 0.0 3.0 1.3 239.0 34.0 0.052904
2 32013030-2d30-3032-3231-32373ce51f62 J.Witten LV 4 2.0 2.0 0.0 18.0 1.0 18.0 9.8 267.0 43.0 0.116958
3 32013030-2d30-3032-3231-32373ce51f62 J.Witten LV 5 2.0 2.0 7.0 6.0 0.0 -1.0 2.6 271.0 31.0 0.094191
4 32013030-2d30-3032-3239-323176c2a1fa L.Fitzgerald ARI 1 5.0 4.0 23.0 34.0 0.0 15.0 7.4 192.0 37.0 0.257390

Next, let's look at the relationship between air yards, yards after catch, and WOPR and receiving fantasy points. We're going to be using the stats package to find the R^2 and using that as our plot titles.

As we can see here, WOPR was more predictive of fantasy football performance (0.746 R^2) than both yards after catch and air yards.

Finally, let's group by player and sum up their weekly WOPR to get a season-long WOPR number.

receiver_player_id receiver_player_name posteam season_wopr
41 32013030-2d30-3033-3030-3335960ad201 A.Thielen MIN 4.284696
344 32013030-2d30-3033-3536-353952c3dc5d T.McLaurin WAS 3.717295
72 32013030-2d30-3033-3132-3335cebc9f07 O.Beckham CLE 3.512059
345 32013030-2d30-3033-3536-36327e9d96c5 M.Brown BAL 3.493854
61 32013030-2d30-3033-3035-3634b926c47f D.Hopkins ARI 3.454431
48 32013030-2d30-3033-3032-3739a5751069 K.Allen LAC 3.420057
341 32013030-2d30-3033-3536-343097915ff1 D.Metcalf SEA 3.400057
86 32013030-2d30-3033-3134-3238120ea790 A.Robinson CHI 3.389711
289 32013030-2d30-3033-3438-333761eb5105 C.Ridley ATL 3.373140
333 32013030-2d30-3033-3535-33356c42f49d D.Slayton NYG 3.336261
126 32013030-2d30-3033-3236-3838f52d40a0 R.Anderson CAR 3.298369
93 32013030-2d30-3033-3135-383848cdfbb6 S.Diggs BUF 3.172083
286 32013030-2d30-3033-3438-3237a7c47510 D.Moore CAR 2.950833
96 32013030-2d30-3033-3136-3130db0aa3c4 D.Waller LV 2.870532
13 32013030-2d30-3032-3731-35304d0e9eb8 J.Edelman NE 2.771220
142 32013030-2d30-3033-3330-3430e890f1ff T.Hill KC 2.743511
88 32013030-2d30-3033-3135-3434ca99a9bc A.Cooper DAL 2.726952
36 32013030-2d30-3032-3936-30385788c9a5 T.Hilton IND 2.671263
210 32013030-2d30-3033-3339-3038a825a9da C.Kupp LA 2.652673
115 32013030-2d30-3033-3232-31312f766863 T.Lockett SEA 2.626906
58 32013030-2d30-3033-3035-3036654ef292 T.Kelce KC 2.608714
15 32013030-2d30-3032-3736-383548509814 E.Sanders NO 2.596778
274 32013030-2d30-3033-3437-353395a63095 M.Andrews BAL 2.516684
73 32013030-2d30-3033-3132-333658a3c4ba B.Cooks HOU 2.436638
152 32013030-2d30-3033-3331-3237332aee05 W.Fuller HOU 2.394942
382 32013030-2d30-3033-3633-32324e92bd12 J.Jefferson MIN 2.344751
140 32013030-2d30-3033-3330-303984f9bb0c T.Boyd CIN 2.329129
145 32013030-2d30-3033-3330-393046e5de90 H.Henry LAC 2.295694
51 32013030-2d30-3033-3034-333120799932 R.Woods LA 2.276193
89 32013030-2d30-3033-3135-3437b94ee00a D.Parker MIA 2.256515

And there you have it, our top 30 receivers this year in terms of WOPR. These are the players that are receiving the most fantasy football opportunity this year (based on how we defined opportunity).

Some players here are unsurprising. Hollywood Brown receives a ton of air yards and probably a decent market share of his teams targets considering the Ravens don't have many receiving options.

One player that is a bit surprising and that has shown up two weeks in a row near the top of the list is Darius Slayton. Given last week's performance, it appears that Darius Slayton may be primed to finish as a WR3 this year, and maybe even WR2 if he becomes a bit more consistent.

Another player that has shown up 2 weeks in a row high up on this list is DJ Moore. He finally had his "breakout" game last Sunday, recording about 19 PPR fantasy points. The bulk of his fantasy points came on a 50+ yard touchdown that went 4 air yards.

desc air_yards yards_gained touchdown
11907 (2:40) (Shotgun) 5-T.Bridgewater pass short left to 12-D.Moore for 57 yards, TOUCHDOWN. 4.0 57.0 1.0

DJ Moore's usage is concerning for sure. He's getting a ton of air yards, but those high opportunity passes haven't really been catchable thus far. It looks like it might be the Robby Anderson show in CAR from now on.

Thanks for reading! Hopefully you found this analysis useful to your fantasy football team and more importantly, you were able to improve your pandas/python/matplotlib skills.