In this notebook, we will familiarize ourselves with weekly player data and design a system to aid in drafting a team for the upcoming season.
The general idea is to train a model that predicts total fantasy points given previous season fantasy data. We can use these predictions to rank players for the upcoming season.
The Notebook can be found here
Data Prep
The first step is to load the CSV containing our data. We will be using the famous pandas package for reading and manipulating data as DataFrames.
import pandas as pd
df = pd.read_csv("../data/draft/player_data.csv")
with pd.option_context("display.max_columns", None):
display(df)
<div>
<style scoped>
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
</style>
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>player_id</th>
<th>team_id</th>
<th>franchise_id</th>
<th>position_id</th>
<th>name</th>
<th>NFL_draft_year</th>
<th>season</th>
<th>week</th>
<th>vegas_line</th>
<th>beat_line</th>
<th>offensive_snapcount</th>
<th>offensive_snapcount_percentage</th>
<th>defensive_snapcount</th>
<th>defensive_snapcount_percentage</th>
<th>special_team_snapcount</th>
<th>special_team_snapcount_percentage</th>
<th>game_starter</th>
<th>game_location</th>
<th>playoffs</th>
<th>passing_int</th>
<th>passing_rating</th>
<th>passing_yds</th>
<th>passing_two_pt_md</th>
<th>passing_td</th>
<th>passing_att</th>
<th>passing_cmp</th>
<th>receiving_yds</th>
<th>receiving_td</th>
<th>receiving_rec</th>
<th>receiving_tar</th>
<th>rushing_att</th>
<th>rushing_td</th>
<th>rushing_yds</th>
<th>PC01_player_gi</th>
<th>PC02_player_gi</th>
<th>PC03_player_gi</th>
<th>PC04_player_gi</th>
<th>PC05_player_gi</th>
<th>PC06_player_gi</th>
<th>PC07_player_gi</th>
<th>PC08_player_gi</th>
<th>PC09_player_gi</th>
<th>PC10_player_gi</th>
<th>PC11_player_gi</th>
<th>PC12_player_gi</th>
<th>PC13_player_gi</th>
<th>PC14_player_gi</th>
<th>PC15_player_gi</th>
<th>PC16_player_gi</th>
<th>PC17_player_gi</th>
<th>PC18_player_gi</th>
<th>PC19_player_gi</th>
<th>PC20_player_gi</th>
<th>PC21_player_gi</th>
<th>PC22_player_gi</th>
<th>PC23_player_gi</th>
<th>PC24_player_gi</th>
<th>PC25_player_gi</th>
<th>PC26_player_gi</th>
<th>PC27_player_gi</th>
<th>PC28_player_gi</th>
<th>PC29_player_gi</th>
<th>PC30_player_gi</th>
<th>PC31_player_gi</th>
<th>PC32_player_gi</th>
<th>PC33_player_gi</th>
<th>PC34_player_gi</th>
<th>PC35_player_gi</th>
<th>PC36_player_gi</th>
<th>PC37_player_gi</th>
<th>PC38_player_gi</th>
<th>PC39_player_gi</th>
<th>PC40_player_gi</th>
<th>PC41_player_gi</th>
<th>PC42_player_gi</th>
<th>PC43_player_gi</th>
<th>PC44_player_gi</th>
<th>PC45_player_gi</th>
<th>PC46_player_gi</th>
<th>PC47_player_gi</th>
<th>PC48_player_gi</th>
<th>PC49_player_gi</th>
<th>PC50_player_gi</th>
<th>PC51_player_gi</th>
<th>PC52_player_gi</th>
<th>PC53_player_gi</th>
<th>PC54_player_gi</th>
<th>PC55_player_gi</th>
<th>PC56_player_gi</th>
<th>PC57_player_gi</th>
<th>PC58_player_gi</th>
<th>PC59_player_gi</th>
<th>PC60_player_gi</th>
<th>PC61_player_gi</th>
<th>PC62_player_gi</th>
<th>PC63_player_gi</th>
<th>PC64_player_gi</th>
<th>PC65_player_gi</th>
<th>PC66_player_gi</th>
<th>PC67_player_gi</th>
<th>PC1_team_gi</th>
<th>PC2_team_gi</th>
<th>PC3_team_gi</th>
<th>PC1_team_ra</th>
<th>PC2_team_ra</th>
<th>PC3_team_ra</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>3935</td>
<td>8</td>
<td>PHI</td>
<td>OL</td>
<td>Jordan Hicks</td>
<td>2015.0</td>
<td>2018</td>
<td>1</td>
<td>Philadelphia Eagles -1.0</td>
<td>win</td>
<td>0.0</td>
<td>0.0</td>
<td>70.0</td>
<td>100.0</td>
<td>3.0</td>
<td>11.0</td>
<td>1</td>
<td>home</td>
<td>0</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>3.535720</td>
<td>10.863385</td>
<td>-7.036347</td>
<td>4.300724</td>
<td>0.689312</td>
<td>-1.798071</td>
<td>-3.086855</td>
<td>-2.179636</td>
<td>-4.609598</td>
<td>-0.693027</td>
<td>-6.717216</td>
<td>0.923689</td>
<td>-1.232957</td>
<td>-3.148344</td>
<td>-2.244902</td>
<td>1.920721</td>
<td>-2.125896</td>
<td>1.738476</td>
<td>4.650081</td>
<td>-0.268336</td>
<td>0.963603</td>
<td>0.552902</td>
<td>-0.261111</td>
<td>-0.443801</td>
<td>0.204616</td>
<td>0.132891</td>
<td>0.219798</td>
<td>0.071654</td>
<td>-0.049206</td>
<td>0.245301</td>
<td>-0.287886</td>
<td>0.030157</td>
<td>0.177201</td>
<td>-0.038076</td>
<td>-1.011855</td>
<td>-0.464725</td>
<td>0.249131</td>
<td>0.451876</td>
<td>-0.163232</td>
<td>0.745244</td>
<td>-1.084854</td>
<td>0.006992</td>
<td>1.813439</td>
<td>-0.282491</td>
<td>-0.274181</td>
<td>-0.463251</td>
<td>-0.458841</td>
<td>-0.118861</td>
<td>-1.370323</td>
<td>3.534788</td>
<td>-0.703772</td>
<td>0.550098</td>
<td>0.554589</td>
<td>-0.788663</td>
<td>1.146566</td>
<td>-0.236809</td>
<td>0.141377</td>
<td>-0.054344</td>
<td>0.201426</td>
<td>-0.534781</td>
<td>-0.040884</td>
<td>0.016196</td>
<td>0.008085</td>
<td>-0.614726</td>
<td>2.898660</td>
<td>-2.111861</td>
<td>3.741353</td>
<td>-1.726173</td>
<td>1.391358</td>
<td>0.066640</td>
<td>-1.726173</td>
<td>1.391358</td>
<td>0.066640</td>
</tr>
<tr>
<th>1</th>
<td>1515</td>
<td>8</td>
<td>PHI</td>
<td>DEF</td>
<td>Michael Bennett</td>
<td>NaN</td>
<td>2018</td>
<td>1</td>
<td>Philadelphia Eagles -1.0</td>
<td>win</td>
<td>0.0</td>
<td>0.0</td>
<td>45.0</td>
<td>64.0</td>
<td>3.0</td>
<td>11.0</td>
<td>0</td>
<td>home</td>
<td>0</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>2.748709</td>
<td>6.746809</td>
<td>-2.322118</td>
<td>0.476724</td>
<td>-0.462738</td>
<td>1.544923</td>
<td>9.714965</td>
<td>-0.649572</td>
<td>-2.150350</td>
<td>-0.116542</td>
<td>-1.975305</td>
<td>-2.145616</td>
<td>1.494741</td>
<td>0.287055</td>
<td>2.413830</td>
<td>-3.246971</td>
<td>5.788888</td>
<td>6.736237</td>
<td>-5.601859</td>
<td>-0.240620</td>
<td>-3.640138</td>
<td>-2.603294</td>
<td>1.045954</td>
<td>3.401883</td>
<td>-0.135151</td>
<td>0.223147</td>
<td>-1.046409</td>
<td>-0.014465</td>
<td>0.652950</td>
<td>-0.525753</td>
<td>-0.330486</td>
<td>1.093748</td>
<td>3.533005</td>
<td>-0.052667</td>
<td>-1.714427</td>
<td>-0.055342</td>
<td>0.527733</td>
<td>0.406042</td>
<td>0.854149</td>
<td>2.761618</td>
<td>3.363040</td>
<td>0.376782</td>
<td>1.277694</td>
<td>0.478980</td>
<td>-1.416322</td>
<td>-0.733746</td>
<td>-4.036942</td>
<td>0.103636</td>
<td>0.312838</td>
<td>-1.644587</td>
<td>-0.198461</td>
<td>0.048204</td>
<td>-0.007157</td>
<td>0.063867</td>
<td>-0.181326</td>
<td>-0.095199</td>
<td>-0.050519</td>
<td>-0.025132</td>
<td>0.046915</td>
<td>0.370380</td>
<td>0.012867</td>
<td>-0.073527</td>
<td>-0.060662</td>
<td>-0.426245</td>
<td>-1.210598</td>
<td>2.701304</td>
<td>-2.990713</td>
<td>-1.726173</td>
<td>1.391358</td>
<td>0.066640</td>
<td>-1.726173</td>
<td>1.391358</td>
<td>0.066640</td>
</tr>
<tr>
<th>2</th>
<td>179</td>
<td>8</td>
<td>PHI</td>
<td>DEF</td>
<td>Haloti Ngata</td>
<td>2006.0</td>
<td>2018</td>
<td>1</td>
<td>Philadelphia Eagles -1.0</td>
<td>win</td>
<td>0.0</td>
<td>0.0</td>
<td>17.0</td>
<td>24.0</td>
<td>3.0</td>
<td>11.0</td>
<td>0</td>
<td>home</td>
<td>0</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>1.789653</td>
<td>2.304759</td>
<td>1.215537</td>
<td>-1.297874</td>
<td>-0.423926</td>
<td>0.867490</td>
<td>2.002093</td>
<td>1.781336</td>
<td>2.791333</td>
<td>0.486860</td>
<td>-0.010164</td>
<td>-1.632627</td>
<td>1.138438</td>
<td>0.820813</td>
<td>0.078269</td>
<td>-0.114516</td>
<td>-0.397927</td>
<td>-0.602089</td>
<td>-0.369751</td>
<td>0.176040</td>
<td>0.705845</td>
<td>0.044659</td>
<td>-0.034894</td>
<td>0.329787</td>
<td>0.334217</td>
<td>-0.041404</td>
<td>-1.158495</td>
<td>0.072382</td>
<td>0.691789</td>
<td>-0.263864</td>
<td>1.069238</td>
<td>0.371333</td>
<td>0.911599</td>
<td>0.019992</td>
<td>-0.326847</td>
<td>-0.473590</td>
<td>0.334983</td>
<td>0.500752</td>
<td>-0.224700</td>
<td>0.077506</td>
<td>-1.748041</td>
<td>-0.175123</td>
<td>-0.067239</td>
<td>-0.395301</td>
<td>0.782571</td>
<td>0.171799</td>
<td>-1.539406</td>
<td>-0.081606</td>
<td>-0.728919</td>
<td>1.915300</td>
<td>-0.260874</td>
<td>0.422833</td>
<td>0.241504</td>
<td>-0.151636</td>
<td>0.013344</td>
<td>-0.303983</td>
<td>-0.105880</td>
<td>-0.069470</td>
<td>0.148806</td>
<td>-0.708698</td>
<td>-0.032114</td>
<td>0.013877</td>
<td>-0.067330</td>
<td>-0.817217</td>
<td>0.038557</td>
<td>0.175604</td>
<td>0.859298</td>
<td>-1.726173</td>
<td>1.391358</td>
<td>0.066640</td>
<td>-1.726173</td>
<td>1.391358</td>
<td>0.066640</td>
</tr>
<tr>
<th>3</th>
<td>22179</td>
<td>8</td>
<td>PHI</td>
<td>TE</td>
<td>Joshua Perkins</td>
<td>NaN</td>
<td>2018</td>
<td>1</td>
<td>Philadelphia Eagles -1.0</td>
<td>win</td>
<td>4.0</td>
<td>6.0</td>
<td>0.0</td>
<td>0.0</td>
<td>2.0</td>
<td>7.0</td>
<td>0</td>
<td>home</td>
<td>0</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>1.022219</td>
<td>-0.953015</td>
<td>3.373580</td>
<td>-2.623424</td>
<td>-0.537060</td>
<td>0.832064</td>
<td>-0.573623</td>
<td>1.278664</td>
<td>1.928379</td>
<td>0.219778</td>
<td>-1.048999</td>
<td>-0.134169</td>
<td>-0.036062</td>
<td>-0.584959</td>
<td>-0.412576</td>
<td>0.162623</td>
<td>0.017021</td>
<td>0.134334</td>
<td>-0.289155</td>
<td>-0.245973</td>
<td>-0.687904</td>
<td>-0.531346</td>
<td>0.081883</td>
<td>-0.704624</td>
<td>0.412585</td>
<td>0.004844</td>
<td>0.249108</td>
<td>0.286632</td>
<td>-0.335414</td>
<td>1.212832</td>
<td>0.084229</td>
<td>-0.093466</td>
<td>0.342370</td>
<td>-0.147375</td>
<td>-0.121444</td>
<td>0.149777</td>
<td>-0.459783</td>
<td>-0.324988</td>
<td>-0.323842</td>
<td>-0.543447</td>
<td>-1.038952</td>
<td>0.006878</td>
<td>1.538538</td>
<td>0.029748</td>
<td>-0.847884</td>
<td>-0.493720</td>
<td>0.511203</td>
<td>0.096011</td>
<td>0.056313</td>
<td>-0.737094</td>
<td>-0.392438</td>
<td>-0.187846</td>
<td>-0.013210</td>
<td>0.108160</td>
<td>-0.106351</td>
<td>0.006150</td>
<td>-0.105108</td>
<td>-0.063913</td>
<td>-0.159572</td>
<td>0.048019</td>
<td>0.014827</td>
<td>-0.092652</td>
<td>-0.078526</td>
<td>-0.572362</td>
<td>-0.002818</td>
<td>0.459932</td>
<td>0.379027</td>
<td>-1.726173</td>
<td>1.391358</td>
<td>0.066640</td>
<td>-1.726173</td>
<td>1.391358</td>
<td>0.066640</td>
</tr>
<tr>
<th>4</th>
<td>4302</td>
<td>9</td>
<td>CIN</td>
<td>DEF</td>
<td>Clayton Fejedelem</td>
<td>2016.0</td>
<td>2018</td>
<td>1</td>
<td>Cincinnati Bengals -1.0</td>
<td>win</td>
<td>0.0</td>
<td>0.0</td>
<td>69.0</td>
<td>84.0</td>
<td>15.0</td>
<td>52.0</td>
<td>0</td>
<td>away</td>
<td>0</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>3.372494</td>
<td>9.837706</td>
<td>-5.935739</td>
<td>3.679207</td>
<td>0.603694</td>
<td>-1.983825</td>
<td>-5.386802</td>
<td>-0.299403</td>
<td>-0.806881</td>
<td>0.126271</td>
<td>0.146932</td>
<td>-2.175899</td>
<td>0.962287</td>
<td>-1.723188</td>
<td>1.212069</td>
<td>-2.014358</td>
<td>0.936028</td>
<td>-0.933937</td>
<td>3.589505</td>
<td>-1.471002</td>
<td>-2.365888</td>
<td>-1.385554</td>
<td>0.582651</td>
<td>3.425764</td>
<td>-0.642531</td>
<td>0.905792</td>
<td>1.865832</td>
<td>0.026917</td>
<td>-0.162030</td>
<td>0.134704</td>
<td>1.590234</td>
<td>-0.465839</td>
<td>-1.243372</td>
<td>0.013164</td>
<td>2.198607</td>
<td>0.365580</td>
<td>-0.083901</td>
<td>-0.021357</td>
<td>0.098064</td>
<td>0.477089</td>
<td>0.803575</td>
<td>0.278499</td>
<td>1.618447</td>
<td>-0.775406</td>
<td>1.103549</td>
<td>0.218561</td>
<td>0.465142</td>
<td>0.027666</td>
<td>-0.424653</td>
<td>1.356162</td>
<td>0.068339</td>
<td>0.245725</td>
<td>0.368422</td>
<td>-0.525630</td>
<td>0.906365</td>
<td>-0.085225</td>
<td>-0.085241</td>
<td>0.065102</td>
<td>0.791779</td>
<td>-0.982474</td>
<td>-0.040189</td>
<td>-0.058141</td>
<td>-0.085053</td>
<td>-0.709191</td>
<td>-1.305165</td>
<td>0.584274</td>
<td>-0.026329</td>
<td>-2.538826</td>
<td>1.487925</td>
<td>0.056361</td>
<td>-2.538826</td>
<td>1.487925</td>
<td>0.056361</td>
</tr>
<tr>
<th>...</th>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
</tr>
<tr>
<th>470065</th>
<td>4027</td>
<td>5</td>
<td>KAN</td>
<td>OL</td>
<td>Cameron Erving</td>
<td>2015.0</td>
<td>2019</td>
<td>21</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>0</td>
<td>neutral</td>
<td>1</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>-1.719160</td>
<td>1.425549</td>
<td>0.060789</td>
<td>-1.719160</td>
<td>1.425549</td>
<td>0.060789</td>
</tr>
<tr>
<th>470066</th>
<td>4032</td>
<td>5</td>
<td>KAN</td>
<td>OL</td>
<td>Laurent Duvernay-Tardif</td>
<td>2014.0</td>
<td>2019</td>
<td>21</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>1</td>
<td>neutral</td>
<td>1</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>0.711232</td>
<td>-2.706948</td>
<td>6.070095</td>
<td>-6.737409</td>
<td>0.441579</td>
<td>2.760096</td>
<td>-3.024215</td>
<td>-4.370738</td>
<td>-5.602105</td>
<td>-0.112730</td>
<td>0.961827</td>
<td>-2.432304</td>
<td>0.967559</td>
<td>-1.115356</td>
<td>-0.113858</td>
<td>-0.595954</td>
<td>0.553324</td>
<td>-0.191477</td>
<td>0.368375</td>
<td>-0.071238</td>
<td>0.524849</td>
<td>0.759992</td>
<td>-0.287851</td>
<td>0.164879</td>
<td>-0.162278</td>
<td>0.420756</td>
<td>0.033723</td>
<td>0.269628</td>
<td>1.139692</td>
<td>0.096668</td>
<td>0.035041</td>
<td>-0.614317</td>
<td>0.642383</td>
<td>-0.033457</td>
<td>-0.074783</td>
<td>0.092886</td>
<td>0.151741</td>
<td>0.061689</td>
<td>-0.271042</td>
<td>-0.209212</td>
<td>-0.986466</td>
<td>-0.110825</td>
<td>1.144430</td>
<td>-0.134360</td>
<td>-0.340122</td>
<td>-0.002752</td>
<td>0.003898</td>
<td>-0.125020</td>
<td>-0.107894</td>
<td>-0.093359</td>
<td>-0.321306</td>
<td>-1.174522</td>
<td>-0.218333</td>
<td>-0.578088</td>
<td>-0.481611</td>
<td>-3.027314</td>
<td>0.064950</td>
<td>2.382225</td>
<td>-0.605067</td>
<td>-0.205805</td>
<td>0.398673</td>
<td>0.115188</td>
<td>-1.530465</td>
<td>-0.205555</td>
<td>0.257696</td>
<td>-0.438114</td>
<td>-0.394591</td>
<td>-1.719160</td>
<td>1.425549</td>
<td>0.060789</td>
<td>-1.719160</td>
<td>1.425549</td>
<td>0.060789</td>
</tr>
<tr>
<th>470067</th>
<td>2320</td>
<td>5</td>
<td>KAN</td>
<td>RB</td>
<td>Anthony Sherman</td>
<td>2011.0</td>
<td>2019</td>
<td>21</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>0</td>
<td>neutral</td>
<td>1</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>1.045972</td>
<td>-1.058669</td>
<td>3.788021</td>
<td>-2.618796</td>
<td>-0.627308</td>
<td>0.828843</td>
<td>-0.751982</td>
<td>1.899916</td>
<td>2.137360</td>
<td>0.345046</td>
<td>-1.669284</td>
<td>-0.986870</td>
<td>0.138782</td>
<td>-1.852258</td>
<td>-0.342583</td>
<td>-0.279100</td>
<td>0.385427</td>
<td>-0.024391</td>
<td>0.006177</td>
<td>-0.266299</td>
<td>-0.496115</td>
<td>-0.311855</td>
<td>0.035841</td>
<td>-0.778776</td>
<td>0.297407</td>
<td>-0.126684</td>
<td>0.460549</td>
<td>0.279632</td>
<td>-0.389513</td>
<td>1.110255</td>
<td>-0.234410</td>
<td>-0.115659</td>
<td>0.147468</td>
<td>-0.164860</td>
<td>-0.000102</td>
<td>0.198371</td>
<td>-0.444333</td>
<td>-0.360522</td>
<td>-0.166243</td>
<td>-0.481752</td>
<td>-0.381935</td>
<td>0.064887</td>
<td>1.109545</td>
<td>0.066845</td>
<td>-0.747525</td>
<td>-0.378900</td>
<td>0.733700</td>
<td>0.062122</td>
<td>0.173102</td>
<td>-0.780280</td>
<td>-0.106150</td>
<td>-0.215877</td>
<td>-0.107670</td>
<td>0.215004</td>
<td>-0.292890</td>
<td>0.053910</td>
<td>0.126591</td>
<td>0.011165</td>
<td>0.109495</td>
<td>0.123205</td>
<td>0.037887</td>
<td>-0.043227</td>
<td>-0.016686</td>
<td>0.068853</td>
<td>-0.000430</td>
<td>-0.030101</td>
<td>-0.030931</td>
<td>-1.719160</td>
<td>1.425549</td>
<td>0.060789</td>
<td>-1.719160</td>
<td>1.425549</td>
<td>0.060789</td>
</tr>
<tr>
<th>470068</th>
<td>4667</td>
<td>22</td>
<td>SFO</td>
<td>TE</td>
<td>George Kittle</td>
<td>2017.0</td>
<td>2019</td>
<td>21</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>1</td>
<td>neutral</td>
<td>1</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>36.0</td>
<td>0.0</td>
<td>4.0</td>
<td>7.0</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>0.842550</td>
<td>-13.122832</td>
<td>-4.088571</td>
<td>1.852074</td>
<td>-3.285834</td>
<td>2.294237</td>
<td>-0.832641</td>
<td>-3.089211</td>
<td>-0.842915</td>
<td>0.026280</td>
<td>-0.051970</td>
<td>3.784157</td>
<td>1.493267</td>
<td>-1.354379</td>
<td>3.124836</td>
<td>-2.473662</td>
<td>-1.871570</td>
<td>0.203658</td>
<td>-0.111899</td>
<td>-0.027448</td>
<td>0.430446</td>
<td>-0.366949</td>
<td>0.109762</td>
<td>-0.460697</td>
<td>-0.131481</td>
<td>-0.037325</td>
<td>1.313475</td>
<td>1.556137</td>
<td>2.397228</td>
<td>1.519191</td>
<td>-0.077297</td>
<td>-0.859433</td>
<td>0.240215</td>
<td>0.041032</td>
<td>0.195061</td>
<td>-0.562296</td>
<td>0.683436</td>
<td>1.272695</td>
<td>0.275140</td>
<td>0.729299</td>
<td>0.426671</td>
<td>0.435326</td>
<td>-1.134460</td>
<td>-0.187551</td>
<td>0.927761</td>
<td>0.612026</td>
<td>-0.836891</td>
<td>-0.495131</td>
<td>-0.205772</td>
<td>0.979795</td>
<td>0.216956</td>
<td>-0.390508</td>
<td>1.275999</td>
<td>0.941684</td>
<td>-0.116991</td>
<td>-0.269824</td>
<td>0.870249</td>
<td>0.011603</td>
<td>0.424628</td>
<td>-0.179069</td>
<td>0.040994</td>
<td>0.159381</td>
<td>-0.353711</td>
<td>1.848837</td>
<td>0.165918</td>
<td>-0.132029</td>
<td>-0.222428</td>
<td>-1.832115</td>
<td>-1.708921</td>
<td>0.052305</td>
<td>-1.832115</td>
<td>-1.708921</td>
<td>0.052305</td>
</tr>
<tr>
<th>470069</th>
<td>3083</td>
<td>5</td>
<td>KAN</td>
<td>DEF</td>
<td>Tyrann Mathieu</td>
<td>2013.0</td>
<td>2019</td>
<td>21</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>1</td>
<td>neutral</td>
<td>1</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>2.656840</td>
<td>7.121188</td>
<td>-4.259596</td>
<td>2.561531</td>
<td>0.733789</td>
<td>-1.807327</td>
<td>-7.039037</td>
<td>-0.875199</td>
<td>-0.226885</td>
<td>-0.199420</td>
<td>-2.051256</td>
<td>-0.011505</td>
<td>0.755777</td>
<td>3.352856</td>
<td>1.250024</td>
<td>-0.634772</td>
<td>-0.135538</td>
<td>-0.926351</td>
<td>2.022822</td>
<td>-0.633718</td>
<td>-3.180600</td>
<td>-2.235357</td>
<td>0.863624</td>
<td>3.034281</td>
<td>-0.418723</td>
<td>0.591496</td>
<td>0.526399</td>
<td>-0.005649</td>
<td>-0.062816</td>
<td>0.111719</td>
<td>0.748759</td>
<td>-0.353374</td>
<td>-1.313794</td>
<td>0.088983</td>
<td>2.560652</td>
<td>0.272471</td>
<td>-0.196767</td>
<td>-0.107019</td>
<td>-0.081481</td>
<td>-0.242450</td>
<td>0.515116</td>
<td>0.289063</td>
<td>1.319613</td>
<td>-0.265593</td>
<td>0.053577</td>
<td>-0.066615</td>
<td>1.454058</td>
<td>0.044504</td>
<td>0.122188</td>
<td>-0.414043</td>
<td>-0.117110</td>
<td>-0.184616</td>
<td>0.076843</td>
<td>-0.424993</td>
<td>0.965266</td>
<td>0.325937</td>
<td>0.098196</td>
<td>0.095892</td>
<td>0.124498</td>
<td>0.501524</td>
<td>0.014770</td>
<td>-0.060831</td>
<td>-0.017867</td>
<td>0.275021</td>
<td>-1.312281</td>
<td>-0.256490</td>
<td>-0.038697</td>
<td>-1.719160</td>
<td>1.425549</td>
<td>0.060789</td>
<td>-1.719160</td>
<td>1.425549</td>
<td>0.060789</td>
</tr>
</tbody>
</table>
<p>470070 rows × 106 columns</p>
</div>The dataset contains a row for each player for each week they have played.
There are 106 columns, some are informational (names, IDs, etc.), some are raw numeric player stats, and the rest are the principal components of a more extensive (more comprehensive) dataset.
We want to predict total fantasy points for the season, so we will need to create a column that follows our league rules (PPR, pure yardage, etc.).
For the sake of simplicity, let's stick to regular-season games and previously earned fantasy points. We will also ignore defensive players, offensive linemen, and kickers.
regular_season_df = df[(df["playoffs"] == 0) &
~df["position_id"].isin({"OL",
"DEF",
"P",
"K"})].
reset_index(drop=True)
# how many fantasy points is 1 of each worth
fantasy_weights = {
"passing_int": -2,
"passing_yds": 0.04,
"passing_two_pt_md": 2,
"passing_td": 4,
"receiving_yds": 0.1,
"receiving_td": 6,
"receiving_rec": 1, # set to 0 for non-PPR
"rushing_td": 6,
"rushing_yds": 0.1,
}
def get_fantasy_points(game):
return sum(game[column] * weight for column, weight in fantasy_weights.items()
if not pd.isna(game[column]))
# apply with axis="columns" will apply our fantasy points calculation
# to each row in the DataFrame
regular_season_df["fantasy_points"] = regular_season_df.apply(
get_fantasy_points, axis="columns")
regular_season_df = regular_season_df[
["player_id", "name", "position_id", "season", "fantasy_points"]
]
We now have the rows and columns we care about, but weekly. To make season level predictions, we will need to aggregate our features by season. Let's compute the seasonal average and total for the fantasy_points column.
seasonal_df = regular_season_df.groupby(
["player_id", "name", "position_id", "season"]).sum()
seasonal_df = seasonal_df.reset_index()
seasonal_df
<div>
<style scoped>
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
</style>
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>player_id</th>
<th>name</th>
<th>position_id</th>
<th>season</th>
<th>fantasy_points</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>2</td>
<td>Justin Gage</td>
<td>WR</td>
<td>2003</td>
<td>62.8</td>
</tr>
<tr>
<th>1</th>
<td>2</td>
<td>Justin Gage</td>
<td>WR</td>
<td>2004</td>
<td>27.6</td>
</tr>
<tr>
<th>2</th>
<td>2</td>
<td>Justin Gage</td>
<td>WR</td>
<td>2005</td>
<td>77.6</td>
</tr>
<tr>
<th>3</th>
<td>2</td>
<td>Justin Gage</td>
<td>WR</td>
<td>2006</td>
<td>10.8</td>
</tr>
<tr>
<th>4</th>
<td>2</td>
<td>Justin Gage</td>
<td>WR</td>
<td>2007</td>
<td>142.0</td>
</tr>
<tr>
<th>...</th>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
</tr>
<tr>
<th>11719</th>
<td>27397</td>
<td>Jeff Smith</td>
<td>WR</td>
<td>2019</td>
<td>2.2</td>
</tr>
<tr>
<th>11720</th>
<td>27400</td>
<td>Ishmael Hyman</td>
<td>WR</td>
<td>2019</td>
<td>5.4</td>
</tr>
<tr>
<th>11721</th>
<td>27404</td>
<td>Wes Hills</td>
<td>RB</td>
<td>2019</td>
<td>16.2</td>
</tr>
<tr>
<th>11722</th>
<td>27410</td>
<td>Spencer Schnell</td>
<td>WR</td>
<td>2019</td>
<td>0.0</td>
</tr>
<tr>
<th>11723</th>
<td>27417</td>
<td>Trenton Irwin</td>
<td>WR</td>
<td>2019</td>
<td>0.0</td>
</tr>
</tbody>
</table>
<p>11724 rows × 5 columns</p>
</div>We can explore the target distribution by position.
import matplotlib.pyplot as plt
seasonal_df.hist("fantasy_points", by="position_id", figsize=(8, 8))
plt.show()

Modeling
The model we are building is called an autoregressive model. It predicts future values based on past values. In our case, that means predicting next year's total fantasy points with one or more past season totals.
We will add several past season averages and totals (or "lags") to each row.
lag_features = ["fantasy_points"]
for lag in range(1, 6):
shifted = seasonal_df.groupby("player_id").shift(lag)
seasonal_df = seasonal_df.assign(**{f"lag_{column}_{lag}": shifted[column]
for column in lag_features})
seasonal_df = seasonal_df.fillna(-1)
seasonal_df
<div>
<style scoped>
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
</style>
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>player_id</th>
<th>name</th>
<th>position_id</th>
<th>season</th>
<th>fantasy_points</th>
<th>lag_fantasy_points_1</th>
<th>lag_fantasy_points_2</th>
<th>lag_fantasy_points_3</th>
<th>lag_fantasy_points_4</th>
<th>lag_fantasy_points_5</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>2</td>
<td>Justin Gage</td>
<td>WR</td>
<td>2003</td>
<td>62.8</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
</tr>
<tr>
<th>1</th>
<td>2</td>
<td>Justin Gage</td>
<td>WR</td>
<td>2004</td>
<td>27.6</td>
<td>62.8</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
</tr>
<tr>
<th>2</th>
<td>2</td>
<td>Justin Gage</td>
<td>WR</td>
<td>2005</td>
<td>77.6</td>
<td>27.6</td>
<td>62.8</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
</tr>
<tr>
<th>3</th>
<td>2</td>
<td>Justin Gage</td>
<td>WR</td>
<td>2006</td>
<td>10.8</td>
<td>77.6</td>
<td>27.6</td>
<td>62.8</td>
<td>-1.0</td>
<td>-1.0</td>
</tr>
<tr>
<th>4</th>
<td>2</td>
<td>Justin Gage</td>
<td>WR</td>
<td>2007</td>
<td>142.0</td>
<td>10.8</td>
<td>77.6</td>
<td>27.6</td>
<td>62.8</td>
<td>-1.0</td>
</tr>
<tr>
<th>...</th>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
</tr>
<tr>
<th>11719</th>
<td>27397</td>
<td>Jeff Smith</td>
<td>WR</td>
<td>2019</td>
<td>2.2</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
</tr>
<tr>
<th>11720</th>
<td>27400</td>
<td>Ishmael Hyman</td>
<td>WR</td>
<td>2019</td>
<td>5.4</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
</tr>
<tr>
<th>11721</th>
<td>27404</td>
<td>Wes Hills</td>
<td>RB</td>
<td>2019</td>
<td>16.2</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
</tr>
<tr>
<th>11722</th>
<td>27410</td>
<td>Spencer Schnell</td>
<td>WR</td>
<td>2019</td>
<td>0.0</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
</tr>
<tr>
<th>11723</th>
<td>27417</td>
<td>Trenton Irwin</td>
<td>WR</td>
<td>2019</td>
<td>0.0</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
<td>-1.0</td>
</tr>
</tbody>
</table>
<p>11724 rows × 10 columns</p>
</div>We now have all the data we need for modeling. To fairly evaluate our future model, we must split the data into train and test sets.
We will use the 2018 and 2019 seasons for testing.
train_df = seasonal_df[seasonal_df["season"] < 2018].reset_index(drop=True)
test_df = seasonal_df[seasonal_df["season"] >= 2018].reset_index(drop=True)
We can look at the correlation plot to determine how many lags should be included in the model.
import seaborn as sns
sns.heatmap(train_df.corr()[["fantasy_points"]], annot=True)
plt.show()

As expected, the correlation decays as we look back at past seasons dropping below significance (~0.5) after ~2 seasons.
Let's define a simple modeling pipeline.
First, we need to import modules from scikit-learn (sklearn). sklearn includes a wealth of out-of-the-box machine learning algorithms with consistent interfaces for training and prediction.
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import ElasticNet
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import mean_absolute_error
We will use two previous seasons' fantasy points as features to our model. This is based on the correlation computed from before.
features = [
"lag_fantasy_points_1",
"lag_fantasy_points_2",
]
The Pipeline class is an abstraction that allows us to compose preprocessing and predictive algorithms to ensure consistent handling of train and test data.
Our pipeline will center and scale our inputs and feed them into a regularized Linear Regression.
pipeline = Pipeline([
("scaler", StandardScaler()),
("lr", ElasticNet())
])
The next step defines a grid search over a couple of regularization parameters controlling the strength of regularization as well as the type of regularization.
The grid search will ensure we find the best model to fit our data.
param_grid = {
"lr__alpha": [0.001, 0.01, 0.1, 1, 10], # regularization strength
"lr__l1_ratio": [0.1, 0.2, 0.5, 0.8, 0.9, 1.0] # type of regulariztion
}
grid = GridSearchCV(pipeline,
n_jobs=-1,
param_grid=param_grid,
scoring="neg_mean_absolute_error")
grid.fit(train_df[features], train_df["fantasy_points"])
print(f"Found best parameters: {grid.best_params_}")
Found best parameters: {'lr__alpha': 0.001, 'lr__l1_ratio': 1.0}
Finally, we can evaluate the trained model. We compare the train and test metrics to ensure we have not overfit to our training data.
train_mae = mean_absolute_error(train_df["fantasy_points"]
,grid.predict(train_df[features]))
test_df["predicted_fantasy_points"] = grid.predict(test_df[features])
test_mae = mean_absolute_error(test_df["fantasy_points"]
,test_df["predicted_fantasy_points"])
print(f"train mae: {train_mae}, test mae: {test_mae}")
train mae: 45.242530437086366, test mae: 45.825492952668036
The value of 45.8 indicates that, on average, we are ~46 points off (+-) the actual value.
Draft
All that's left to do is sort our test DataFrame by our predicted fantasy points and group by position.
Here we can see the top 10 projected players by position for the 2019 season.
draft_df = test_df[test_df["season"] == 2019].sort_values("predicted_fantasy_points"
, ascending=False)
positions, position_dfs = zip(*draft_df.groupby("position_id"))
rows = [{} for _ in range(10)]
for position, position_df in draft_df.groupby("position_id"):
for i, (_, player) in enumerate(position_df.head(10).iterrows()):
rows[i][position] = player["name"]
rows[i][f"{position}_predicted"] = player["predicted_fantasy_points"]
rows[i][f"{position}_points"] = player["fantasy_points"]
pd.DataFrame(rows)
<div>
<style scoped>
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
</style>
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>QB</th>
<th>QB_predicted</th>
<th>QB_points</th>
<th>RB</th>
<th>RB_predicted</th>
<th>RB_points</th>
<th>TE</th>
<th>TE_predicted</th>
<th>TE_points</th>
<th>WR</th>
<th>WR_predicted</th>
<th>WR_points</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>Patrick Mahomes</td>
<td>278.927273</td>
<td>289.04</td>
<td>Todd Gurley</td>
<td>288.704311</td>
<td>223.40</td>
<td>Travis Kelce</td>
<td>227.638434</td>
<td>256.3</td>
<td>DeAndre Hopkins</td>
<td>259.580144</td>
<td>268.54</td>
</tr>
<tr>
<th>1</th>
<td>Matt Ryan</td>
<td>265.698497</td>
<td>275.34</td>
<td>Christian McCaffrey</td>
<td>281.020278</td>
<td>471.20</td>
<td>Zach Ertz</td>
<td>215.118126</td>
<td>217.6</td>
<td>Antonio Brown</td>
<td>251.282861</td>
<td>16.10</td>
</tr>
<tr>
<th>2</th>
<td>Ben Roethlisberger</td>
<td>254.511807</td>
<td>12.74</td>
<td>Alvin Kamara</td>
<td>269.898219</td>
<td>250.52</td>
<td>George Kittle</td>
<td>192.621071</td>
<td>222.5</td>
<td>Julio Jones</td>
<td>249.131187</td>
<td>274.10</td>
</tr>
<tr>
<th>3</th>
<td>Deshaun Watson</td>
<td>245.471868</td>
<td>324.98</td>
<td>Le'Veon Bell</td>
<td>265.119065</td>
<td>217.00</td>
<td>Eric Ebron</td>
<td>175.027888</td>
<td>86.5</td>
<td>Tyreek Hill</td>
<td>246.745372</td>
<td>188.30</td>
</tr>
<tr>
<th>4</th>
<td>Russell Wilson</td>
<td>242.071239</td>
<td>330.60</td>
<td>Saquon Barkley</td>
<td>256.966129</td>
<td>244.10</td>
<td>Jared Cook</td>
<td>157.000411</td>
<td>167.5</td>
<td>Davante Adams</td>
<td>246.027969</td>
<td>214.70</td>
</tr>
<tr>
<th>5</th>
<td>Jared Goff</td>
<td>240.863793</td>
<td>257.52</td>
<td>Ezekiel Elliott</td>
<td>245.191806</td>
<td>315.70</td>
<td>Austin Hooper</td>
<td>137.420316</td>
<td>191.7</td>
<td>Michael Thomas</td>
<td>243.647548</td>
<td>374.60</td>
</tr>
<tr>
<th>6</th>
<td>Drew Brees</td>
<td>235.478522</td>
<td>224.76</td>
<td>Melvin Gordon</td>
<td>220.614436</td>
<td>186.80</td>
<td>Kyle Rudolph</td>
<td>134.413014</td>
<td>113.7</td>
<td>Adam Thielen</td>
<td>236.152122</td>
<td>114.40</td>
</tr>
<tr>
<th>7</th>
<td>Dak Prescott</td>
<td>230.525439</td>
<td>339.78</td>
<td>James White</td>
<td>205.926831</td>
<td>202.20</td>
<td>Jason Witten</td>
<td>132.607818</td>
<td>141.9</td>
<td>JuJu Smith-Schuster</td>
<td>224.836212</td>
<td>115.20</td>
</tr>
<tr>
<th>8</th>
<td>Kirk Cousins</td>
<td>229.789246</td>
<td>248.42</td>
<td>James Conner</td>
<td>198.392637</td>
<td>147.50</td>
<td>Trey Burton</td>
<td>125.252113</td>
<td>22.4</td>
<td>Mike Evans</td>
<td>218.590390</td>
<td>232.70</td>
</tr>
<tr>
<th>9</th>
<td>Aaron Rodgers</td>
<td>228.112659</td>
<td>282.38</td>
<td>Kareem Hunt</td>
<td>194.773459</td>
<td>101.40</td>
<td>David Njoku</td>
<td>123.664805</td>
<td>15.1</td>
<td>Keenan Allen</td>
<td>211.717302</td>
<td>261.50</td>
</tr>
</tbody>
</table>
</div>We haven't accounted for injury (Ben Roethlisberger) or controversy (Antonio Brown) in this model. This shows that the real world is full of variables outside of our control, and model results should be considered informed guesses and not always taken at face value.
Other Gridironers have experimented with more complex models capable of quantifying uncertainty in predictions, which can hopefully allow us to begin accounting for the randomness inherent in these kinds of problems.
Next Steps
This is a straightforward model meant to get you up and running on your own. Other things that may be useful include
- Use raw features instead of (or in addition to) the simple autoregressive model may allow the model to exploit more granular patterns.
- Different features may be more useful in predicting performance for certain positions -- it may be worth experimenting with position-specific models.
- Linear regression and its variants are straightforward models, try other algorithms (random forests, gradient boosted trees, neural networks) to get a better sense of latent signal in the data.
- Players can have a meaningful effect on each other's fantasy performance, consider adding things like QB stats to receiver predictions.
- Rookies are left out of this ranking since they have no previous seasons in the NFL. What kind of dataset could we use to build a rookie model?
- We don't care about the raw number of points scored, but rather the ranking. There are models optimized for this specific use case.