…well, not exactly. But it’s snappier if I put it that way.
What I really mean is: the number of pass attempts (or receptions, or carries) per touchdown is lognormally distributed, and that fact can be used to produce more stable fantasy football forecasts.
In my last two posts, I laid out simple fantasy football forecasting engines in SAS and R. An important component of a fantasy football score is the number of touchdowns scored by each player. Touchdowns can vary considerably among players with otherwise similar performance. For example, let’s look at the top three running backs from my previous post:
LeSean McCoy scored more than twice as many touchdowns as Maurice Jones-Drew. He scored several more than Ray Rice, but otherwise have very similar stats. The gut instinct that drives this post is that I don’t think LeSean McCoy is not going to score that many touchdowns this year!
How can I analyze touchdowns? I could simply draw a histogram of touchdowns per player, but that wouldn’t be very insightful. Players who get the ball more are more likely to score more touchdowns. So let’s control for that by dividing by the number of rushing attempts each player makes: let’s chart the touchdown rate. The histogram of rushing attempts per touchdown for the top 60 running backs in my 2011 dataset is interesting:
To my eye, it looks lognormally distributed. It’s not perfect, but it looks like a very reasonable approximation. A lognormal distribution makes sense – we expect that the distribution would be “heavy tailed” because going towards the left (1 touchdown per rush) is much harder than going to the right. Nobody scores every time they get the ball. Here is the SAS code that produces the histogram and the best fitting lognormal distribution. (I’m not doing this in R because I don’t know how to fit distributions in that environment. I am sure it is easy to do.)
** Plot a histogram, and save the lognormal distribution parameters. **; proc univariate data=rb(obs=60) noprint; var Rush_Per_TD; histogram / lognormal nendpoints=15 cfill=blue outhistogram=rb_hist; ods output ParameterEstimates=rb_fit; run;
The options for the “histogram” statement specify the distribution type, chart style, and an output dataset for the bins (which I then copied over to the free Excel 2013 preview to make a less-crappy looking chart). The “ods output” statement is a fancy way to save the lognormal parameters into a dataset for later use.
I can understand why there is a wide variation of values. Off the top of my head:
- Skill of the RB.
- Skill of the offensive line that blocks for the RB.
- How often the player gets carries near the goalline.
- Some teams call more red zone rush plays than others.
- Quality of opposition.
- Stuff like this. (This moment still burns…)
With these reasons in mind, I certainly don’t expect that all RBs will end up with the same rush/TD ratio in the long run. However, I think that it is likely that players on the ends of the distribution (either way) in 2011 are likely to be closer to the middle in 2012. Here’s what we can do: compute the conditional distribution function (cdf) for the fitted lognormal distribution for each player’s rush/TD ratio. This is a number between 0 and 1 that indicates “how extreme” the player is – 0 means all the way on the left. For example, LeSean McCoy is 0.0553 and is Maurice Jones- Drew is 0.5208. This means that LeSean McCoy is an outlier (close to 0), and MJD is not (close to 1/2).
To project next year’s ratio, I take a weighted average of the player’s binomial CDF and the middle of the distribution (0.5). I somewhat arbitrarily chose to take 2/3 times the CDF and add 1/3 times 0.5. This means that while I believe that players will regress to the mean somewhat, that I do believe that there are significant structural differences between players that will persevere from one season to the next.
Once I have the projected rush/TD figures, I can multiply by rushes and get a projected 2012 TD figure that I can use in fantasy scoring. If I take the rather large leap that touchdowns for all positions behave in this way, I can write a generic “normalizing” function that I can use for touchdowns at all positions.
** Recalibrate a variable with the assumption that it is lognormally distributed. **; ** -- position: a dataset with player information. It should have a variable called **; ** CalibrateVar. **; ** -- obscount: the number of observations to use for analysis. **; ** -- CalibrateVar: the variable under analysis. **; ** The macro will create a new variable ending in _1 with the calibrated values. **; %macro Recalibrate(position, obscount, CalibrateVar); ** Sort the data by the initial score computed in my first post. **; proc sort data=&position; by descending FFPts0; run; ** Plot a histogram, and save the lognormal distribution parameters. **; proc univariate data=&position(obs=&obscount) noprint; var &CalibrateVar; histogram / lognormal nendpoints=15 cfill=blue outhistogram=&position._hist; ods output ParameterEstimates=&position._fit; run; ** Get the lognormal parameters into macro variables so I can use them for computation. **; data _null_; set &position._fit; if Parameter = 'Scale' then call symput('Scale', Estimate); if Parameter = 'Shape' then call symput('Shape', Estimate); run; ** Compute the projected values for each player using the distribution. **; data &position; set &position; LogNormCdf = cdf('LOGNORMAL', &CalibrateVar, &Scale, &Shape); &CalibrateVar._1 = quantile('LOGNORMAL', 0.67 * LogNormCdf + 0.33 * 0.5, &Scale, &Shape); run; %mend;
A call to this macro looks like this:
%Recalibrate(rb, 60, Rush_Per_TD);
After this call I will have a variable called Rush_Per_TD1 in my rb dataset.
I have modified the forecasting engine to recalibrate touchdowns for all positions – see estimate2.sas. You can see below how the rankings change when I recalibrate: here are the top 20 running backs. Players in green moved up in the ratings after recalibration; players in red moved down. Unsurprisingly, LeSean McCoy moved down.
|Pos||Name||Team||G||Rush||Rush_Yds||Rush_YG||Rush_Avg||Rush_TD||Rec||Rec_Yds||Rec_YG||Rec_Avg||Rec_Lng||YAC||Rec_1stD||Rec_TD||Fum||FumL||Rush_Per_TD||Rec_Per_TD||FFPts0||LogNormCdf||Rec_Per_TD_1||Rush_Per_TD_1||Rush_TD_1||Rec_TD_1||FFPts||FFPtsN||Rank New||Rank Old|
I actually used this as draft guidance (I selected Ray Rice with my first pick in a recent draft). Let’s see if it holds water!