import pandas as pd
np=pd.np
from sdd_api.api import Api
from credentials import *
import matplotlib.pyplot as plt
%matplotlib inline
api = Api(username=username, password=password, client_id=client_id, client_secret=client_secret)
salaries=api.get_dataframe("dfs_salaries",season_start=2017)
salaries.sample(5)
season | week_num | player_name | team_name | position | opp_name | fd_points | fd_salary | dk_points | dk_salary | yh_points | yh_salary | player_id | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
7098 | 2017 | 16 | Blair Walsh | SEA | K | DAL | 3.0 | 4700.0 | NaN | NaN | NaN | NaN | 23083 |
6785 | 2017 | 15 | Ameer Abdullah | DET | RB | CHI | 3.6 | 5400.0 | 5.1 | 3800.0 | 3.6 | 13.0 | 9 |
7279 | 2017 | 16 | Chad Hansen | NYJ | WR | LAC | 0.0 | 4500.0 | 0.0 | 3000.0 | 0.0 | 10.0 | 8941 |
5139 | 2017 | 12 | Maxx Williams | BAL | TE | HOU | 1.7 | 4500.0 | 2.2 | 2500.0 | 1.7 | 10.0 | 24073 |
973 | 2017 | 3 | Travis Kelce | KC | TE | LAC | 0.6 | 7100.0 | 1.1 | 6000.0 | 0.6 | 24.0 | 11963 |
We're working on creating our own projections but for this example we'll use the ones published by Fantasy Football Analytics.
For your convenience I've already downloaded week 1 for draft kings. The whole process is as follows
Go to http://apps.fantasyfootballanalytics.net
salaries=salaries[salaries['week_num']==1]
dk_projections=pd.read_csv("data/FFA-projs-2017-week1-dk.csv",index_col=False)[['player','position','points','risk','team','lower','upper']]
dk_projections.rename(columns={'player':'player_name','points':'predicted_pts',
'team':'team_name'},inplace=True)
dk_projections['team_name']=dk_projections['team_name'].replace("SD","LAC").replace("LA","LAR")
dk_projections['week_num']=1
dk_projections.head()
player_name | position | predicted_pts | risk | team_name | lower | upper | week_num | |
---|---|---|---|---|---|---|---|---|
0 | Aaron Rodgers | QB | 23.496123 | 3.593419 | GB | 22.630000 | 23.897135 | 1 |
1 | Tom Brady | QB | 22.642342 | 3.253799 | NE | 19.827000 | 23.975722 | 1 |
2 | David Johnson | RB | 22.546692 | 0.753315 | ARI | 20.610000 | 23.898348 | 1 |
3 | LeVeon Bell | RB | 21.916783 | 1.098975 | PIT | 18.828765 | 23.708131 | 1 |
4 | Matt Ryan | QB | 21.850069 | 4.874363 | ATL | 20.600000 | 23.096079 | 1 |
def cleanNames(row):
name=row['player_name']
name=name.upper()
if row['position'] in ['Def','DST']:
#print(row)
return row['team_name']
else:
name=name.replace(" JR","").replace(".","").replace(" III","").replace("'","")
#fix a few nicknames
name=name.replace("PHILLY BROWN","COREY BROWN").replace("KEITH SMITH", "ROD SMITH").replace("ROBERT KELLEY", "ROB KELLEY")
return name
salaries['merge_name']=salaries.apply(cleanNames,axis=1)
dk_projections['merge_name']=dk_projections.apply(cleanNames,axis=1)
merged=salaries[salaries['week_num']==1].merge(dk_projections, how="left", on=['merge_name','week_num'], suffixes=["","_ffa"])
merged=merged.drop_duplicates(subset=["player_name", "position","team_name"])
merged[(pd.isnull(merged['predicted_pts']))&(merged['position']!='K')]#no kickers in draft kings/dk predictions
season | week_num | player_name | team_name | position | opp_name | fd_points | fd_salary | dk_points | dk_salary | ... | yh_salary | player_id | merge_name | player_name_ffa | position_ffa | predicted_pts | risk | team_name_ffa | lower | upper | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
237 | 2017 | 1 | Eric Tomlinson | NYJ | TE | BUF | 3.5 | 4500.0 | 4.5 | 2500.0 | ... | 10.0 | 22300 | ERIC TOMLINSON | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
259 | 2017 | 1 | Ben Watson | BAL | TE | CIN | 0.0 | 4900.0 | 0.0 | 2800.0 | ... | 11.0 | 23295 | BEN WATSON | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
323 | 2017 | 1 | Keelan Cole | JAC | WR | HOU | 0.0 | 4500.0 | 0.0 | 3000.0 | ... | 10.0 | 4191 | KEELAN COLE | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
366 | 2017 | 1 | Trey Edmunds | NO | RB | MIN | 0.0 | 4500.0 | 0.0 | 3000.0 | ... | 10.0 | 6221 | TREY EDMUNDS | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
4 rows × 21 columns
Eric Tomlinson doesn't have a prediction. Given his dk salary is 2500 we will just ignore him
from itertools import combinations,product
from collections import defaultdict
from operator import itemgetter, attrgetter
def mapPos(row):
pos=row['position']
args=[]
#Vectorize to make position groups easier to handle
if pos=='QB':
args.append(np.array((row['salary'], 1,0,0,0,0,0)))
elif pos=='RB':
args.append(np.array((row['salary'], 0,1,0,0,0,0)))
args.append(np.array((row['salary'], 0,0,0,0,1,0)))
elif pos=='WR':
args.append(np.array((row['salary'], 0,0,1,0,0,0)))
args.append(np.array((row['salary'], 0,0,0,0,1,0)))
elif pos=='TE':
args.append(np.array((row['salary'], 0,0,0,1,0,0)))
args.append(np.array((row['salary'], 0,0,0,0,1,0)))
elif pos=='DST':
args.append(np.array((row['salary'], 0,0,0,0,0,1)))
return args
class Lineup:#'W/R/T':1
def __init__(self,players=[]):
self.rem_lookup={'QB':1,'WR':3,'RB':2,'TE':1,'DST':1}#draft kings settings
self.score=0.
self.predictedScore=0.
self.players=players
self.salary=50000
for player in players:
self.addFull(player)
def isValid(self):
if len(self.rem_lookup.keys())<=0:
return True
else:
return False
def nextPos(self):
return list(self.rem_lookup.keys())[0]
def addFull(self,player):
pos=player['position']
self.salary-=player['salary']
try:
self.score+=player['score']
except:
pass
try:
self.predictedScore+=player['predictedScore']
except:
pass
def add(self,player):
self.players.append(player)
pos=player['position']
self.rem_lookup[pos]-=1
if self.rem_lookup[pos]<=0:
self.rem_lookup.pop(pos)
self.salary-=player['salary']
print(player['predictedScore'])
self.score+=player['predictedScore']
def __repr__(self):
names=', '.join([p['player_name'] for p in self.players])
return str(self.score)+' '+str(self.predictedScore)+" "+str(self.salary)+' '+names
def __hash__(self):
l=sorted([p['player_name'] for p in self.players])
strings=','.join(l)
return hash(strings)
def hashPlayer(p):
return p['player_name']+p['position']
class lineupGenerator:
def __init__(self,pool,locks=[],goodDef=[]):
self.rem_lookup={'QB':1,'WR':3,'RB':2,'TE':1,'DST':1}
self.score=0.
keys=['QB','WR','RB','TE','DST']
self.players=[]
self.pool=set()
self.lineups=[]
self.goodDef=goodDef
pDict={}
self.locks=pool[pool['player_name'].isin(locks)].to_dict(orient="records")
for lock in self.locks:
self.rem_lookup[lock['position']]-=1
pool=pool[~pool['player_name'].isin(locks)]
for pos,group in pool.groupby("position"):
pDict[pos]=group.to_dict(orient="records")
for p in pDict[pos]:
self.pool.add(hashPlayer(p))
options={pos:combinations(pDict[pos],self.rem_lookup[pos]) for pos in self.rem_lookup}
self.pDict=pDict
self.salary=50000
lineups=[]
lineups=options[keys[0]]
for idx in range(1,len(keys)):
lineups=product(lineups,options[keys[idx]])
self.lineups=lineups
def genLineups(self):
for l in self.lineups:
fromTeam=defaultdict(int)
fromSkill=defaultdict(int)
fromPos=defaultdict(int)
oppDST=defaultdict(int)
attempt=list(l[1]+l[0][1]+l[0][0][1]+l[0][0][0][1]+l[0][0][0][0])
for lock in self.locks:
attempt.append(lock)
sal=0
valid=True
for p in attempt:
if p['position']!='DST':
fromTeam[p['team_name']]+=1
fromPos[hashPlayer(p)]+=1
oppDST[p['opp_name']]+=1
if p['position']!='QB':
fromSkill[p['team_name']]+=1
else:
if oppDST[p['team_name']]>5:#more than 1 player from team facing defense in lineup
valid=False
sal+=p['salary']
#add flex generator that checks if in pool
#unique player
unique_player=all(fromPos[pos]<=1 for pos in fromPos)
#3 from same team
from_same=all(oppDST[t]<=4 for t in oppDST)
if sal<=48000\
and unique_player\
and from_same\
and all(p['opp_name'] not in self.goodDef for p in attempt) and valid:
yield attempt
def prune(this):
'''
Prunes player pool to generate maximum expected value follows:
if a player has a higher predicted score and lower salary, remove all players with a higher salary than the player
Additionally
if all position spots are unused, keep up to the remaining number of slots left
with a lower score than the player we are on but higher predicted point total than the next best option
Here's an example:
Odell beckam salary 8000 predicted 19
Alshon Jeffrey salary 7900 predicted 20
Mohammed sanu salary 5100 predicted 15
Because there are 3+1 spots for WR (including FLEX), we can't remove odell beckham because he would contribute more
to our point total than removing him. If there was only 1 WR slot we'd be safe to remove him.
'''
FLEX=['WR', 'RB', 'TE']
best=pd.DataFrame()
this=this.sort_values(by="predictedScore",ascending=False)
for pos, group in this.groupby('position'):
group=group.to_dict(orient='records')
bad=[]
for idx,best_player in enumerate(group):
salary=best_player['salary']
rem_lookup={'QB':1,'WR':3,'RB':2,'W/R/T':1,'TE':1,'DST':1}
rem_lookup['W/R/T']+=rem_lookup[pos]
for player in group[idx+1:]:
if player['salary']>=salary:
remove_player=False
if rem_lookup[pos]<=1:
remove_player=True
if pos in FLEX :
if rem_lookup['W/R/T']<1:
remove_player=True
else:
remove_player=False
rem_lookup['W/R/T']-=1
else:
rem_lookup[pos]-=1
if remove_player:
group.remove(player)
best=pd.concat([best,pd.DataFrame(group)])
best=best[this.columns]#maintain passed col order
return best.sort_values(by='predictedScore', ascending=False)
dfs=merged.copy()
dfs=dfs[dfs['position']!="K"][['player_name', 'team_name', 'position',
'opp_name', 'dk_salary',
'player_id', 'predicted_pts', 'lower',
'upper']]
dfs['predictedScore']=dfs['predicted_pts']#you could change this to upper or lower depending on the type of lineup your looking for
dfs['salary']=dfs['dk_salary']
dfs=dfs[['team_name', 'player_name', 'position',
'opp_name', 'salary','predictedScore']]
dfs.head(1)
team_name | player_name | position | opp_name | salary | predictedScore | |
---|---|---|---|---|---|---|
0 | ARI | Arizona Cardinals | DST | DET | 2800.0 | 7.260014 |
In order to speed up our lineup generator we can prune out overly expensive players. Read the comments in the prune function to get the exact description
pruned=prune(dfs)
pruned
team_name | player_name | position | opp_name | salary | predictedScore | |
---|---|---|---|---|---|---|
0 | GB | Aaron Rodgers | QB | SEA | 7000.0 | 23.496123 |
0 | ARI | David Johnson | RB | DET | 9400.0 | 22.546692 |
1 | PIT | Le'Veon Bell | RB | CLE | 9800.0 | 21.916783 |
1 | ATL | Matt Ryan | QB | CHI | 6900.0 | 21.850069 |
2 | WAS | Kirk Cousins | QB | PHI | 6400.0 | 21.002631 |
0 | PIT | Antonio Brown | WR | CLE | 8800.0 | 20.669101 |
1 | ATL | Julio Jones | WR | CHI | 8500.0 | 20.014099 |
3 | BUF | Tyrod Taylor | QB | NYJ | 6200.0 | 18.318134 |
4 | DET | Matthew Stafford | QB | ARI | 6100.0 | 18.122957 |
2 | GB | Jordy Nelson | WR | SEA | 7600.0 | 17.889505 |
5 | CIN | Andy Dalton | QB | BAL | 5700.0 | 17.515457 |
2 | BUF | LeSean McCoy | RB | NYJ | 8200.0 | 17.165540 |
3 | CIN | A.J. Green | WR | BAL | 8000.0 | 16.705215 |
4 | NO | Michael Thomas | WR | MIN | 7400.0 | 16.630797 |
3 | ATL | Devonta Freeman | RB | CHI | 7000.0 | 16.515971 |
6 | NYG | Eli Manning | QB | DAL | 5600.0 | 16.209205 |
4 | LAC | Melvin Gordon | RB | DEN | 6600.0 | 15.697834 |
5 | SEA | Doug Baldwin | WR | GB | 6700.0 | 15.669086 |
5 | CHI | Jordan Howard | RB | ATL | 6300.0 | 15.435095 |
6 | DAL | Dez Bryant | WR | NYG | 7500.0 | 15.405977 |
7 | KC | Alex Smith | QB | NE | 5400.0 | 15.373665 |
7 | OAK | Amari Cooper | WR | TEN | 7200.0 | 15.356374 |
8 | NE | Brandin Cooks | WR | KC | 7700.0 | 15.240044 |
8 | PHI | Carson Wentz | QB | WAS | 5300.0 | 15.016477 |
9 | WAS | Terrelle Pryor | WR | PHI | 6100.0 | 14.843060 |
10 | LAC | Keenan Allen | WR | DEN | 6100.0 | 14.780777 |
11 | DEN | Demaryius Thomas | WR | LAC | 6300.0 | 14.753063 |
12 | ARI | Larry Fitzgerald | WR | DET | 5900.0 | 14.708424 |
13 | HOU | DeAndre Hopkins | WR | JAC | 5900.0 | 14.629065 |
14 | OAK | Michael Crabtree | WR | TEN | 6000.0 | 14.621326 |
... | ... | ... | ... | ... | ... | ... |
46 | LAC | Travis Benjamin | WR | DEN | 3400.0 | 6.707451 |
16 | ARI | Jermaine Gresham | TE | DET | 2700.0 | 6.691666 |
47 | OAK | Seth Roberts | WR | TEN | 3300.0 | 6.659285 |
17 | CHI | Zach Miller | TE | ATL | 2800.0 | 6.652618 |
21 | IND | Marlon Mack | RB | LAR | 3400.0 | 6.650975 |
5 | KC | Kansas City Chiefs | DST | NE | 2300.0 | 6.398443 |
18 | LAR | Tyler Higbee | TE | IND | 2600.0 | 6.180053 |
22 | NO | Alvin Kamara | RB | MIN | 3500.0 | 5.926684 |
48 | BUF | Andre Holmes | WR | NYJ | 3000.0 | 5.878436 |
19 | CHI | Dion Sims | TE | ATL | 2500.0 | 5.808481 |
20 | OAK | Clive Walford | TE | TEN | 2500.0 | 5.662776 |
49 | OAK | Cordarrelle Patterson | WR | TEN | 3000.0 | 5.617378 |
21 | SF | Garrett Celek | TE | CAR | 2600.0 | 5.515040 |
6 | CHI | Chicago Bears | DST | ATL | 2200.0 | 5.391771 |
7 | SF | San Francisco 49ers | DST | CAR | 2100.0 | 5.348293 |
23 | MIN | Jerick McKinnon | RB | NO | 3200.0 | 5.305845 |
24 | CHI | Tarik Cohen | RB | ATL | 3000.0 | 5.062552 |
50 | WAS | Brian Quick | WR | PHI | 3100.0 | 4.515122 |
25 | BAL | Javorius Allen | RB | CIN | 3000.0 | 4.376774 |
51 | HOU | Bruce Ellington | WR | JAC | 3000.0 | 4.280907 |
26 | JAC | Chris Ivory | RB | HOU | 3600.0 | 4.245769 |
13 | IND | Scott Tolzien | QB | LAR | 4500.0 | 4.053128 |
52 | DAL | Brice Butler | WR | NYG | 3000.0 | 3.868858 |
8 | CLE | Cleveland Browns | DST | PIT | 2000.0 | 3.625214 |
27 | DEN | De'Angelo Henderson | RB | LAC | 3600.0 | 3.555085 |
53 | SEA | Luke Willson | WR | GB | NaN | 2.948455 |
28 | SEA | Chris Carson | RB | GB | NaN | 2.113196 |
54 | DET | T.J. Jones | WR | ARI | NaN | 1.645612 |
55 | SEA | Nick Vannett | WR | GB | NaN | 0.389983 |
14 | IND | Jacoby Brissett | QB | LAR | 4000.0 | 0.298763 |
131 rows × 6 columns
I like to use this pruned set as a starting point and then tweak my final player pool using a custom web app I've developed -- not ready for release as of now
#example
def toIncl(s):
return s.split(", ")
inclusions=toIncl("Le'Veon Bell, David Johnson, Antonio Brown, Pittsburgh Steelers, Julio Jones, AJ Green, Dez Bryant, Amari Cooper, Kirk Cousins, Sammy Watkins, Terrelle Pryor, Martavis Bryant, Michael Crabtree, Todd Gurley, Larry Fitzgerald, DeAndre Hopkins, Jordan Reed, Alshon Jeffery, Pierre Garcon, Allen Robinson, Lamar Miller, Jimmy Graham, Jeremy Maclin, Eric Decker, Matt Forte, Bilal Powell, John Brown, Jared Goff, Kevin White, Rishard Matthews, Marvin Jones, Sterling Shepard, Robert Woods, Eli Rogers, Jason Witten, Sammie Coates, Will Fuller, Tavon Austin, Terrance West, Jordan Matthews, Mohamed Sanu, JJ Nelson, Torrey Smith, Jaguars, Zay Jones, Zach Ertz, Jared Cook, Phillip Dorsett, Los Angeles Rams, Brian Cook, Marquess Wilson, Austin Hooper, Vernon Davis, Lance Dunbar, Evan Engram, Zach Miller, Vance McDonald, Austin Seferian-Jenkins, Jermaine Gresham, Detroit Lions, Dion Sims, Adam Shaheen, San Francisco 49ers")
player_pool=dfs[dfs['player_name'].isin(inclusions)]
print(len(player_pool))
player_pool[player_pool['position']=='WR']
54
team_name | player_name | position | opp_name | salary | predictedScore | |
---|---|---|---|---|---|---|
35 | HOU | DeAndre Hopkins | WR | JAC | 5900.0 | 14.629065 |
47 | PHI | Alshon Jeffery | WR | WAS | 5800.0 | 14.134176 |
53 | ATL | Julio Jones | WR | CHI | 8500.0 | 20.014099 |
54 | DET | Marvin Jones | WR | ARI | 4100.0 | 10.560241 |
58 | BUF | Zay Jones | WR | NYJ | 3500.0 | 10.168998 |
88 | BAL | Jeremy Maclin | WR | CIN | 5000.0 | 12.654458 |
96 | BUF | Jordan Matthews | WR | NYJ | 4400.0 | 11.564966 |
97 | TEN | Rishard Matthews | WR | OAK | 4200.0 | 11.564457 |
156 | WAS | Terrelle Pryor | WR | PHI | 6100.0 | 14.843060 |
168 | JAC | Allen Robinson | WR | HOU | 5200.0 | 13.162698 |
174 | PIT | Eli Rogers | WR | CLE | 3900.0 | 7.916191 |
183 | ATL | Mohamed Sanu | WR | CHI | 4200.0 | 10.467134 |
188 | NYG | Sterling Shepard | WR | DAL | 4100.0 | 10.951218 |
203 | PHI | Torrey Smith | WR | WAS | 3700.0 | 8.048873 |
258 | LAR | Sammy Watkins | WR | IND | 6200.0 | 12.617658 |
269 | CHI | Kevin White | WR | ATL | 4200.0 | 11.486123 |
288 | LAR | Robert Woods | WR | IND | 4000.0 | 9.236753 |
292 | PIT | Antonio Brown | WR | CLE | 8800.0 | 20.669101 |
295 | ARI | John Brown | WR | DET | 4800.0 | 10.361549 |
297 | DAL | Dez Bryant | WR | NYG | 7500.0 | 15.405977 |
299 | PIT | Martavis Bryant | WR | CLE | 6000.0 | 8.538977 |
319 | CLE | Sammie Coates | WR | PIT | 3800.0 | 4.603813 |
333 | OAK | Amari Cooper | WR | TEN | 7200.0 | 15.356374 |
337 | OAK | Michael Crabtree | WR | TEN | 6000.0 | 14.621326 |
354 | TEN | Eric Decker | WR | OAK | 5000.0 | 11.569751 |
362 | NE | Phillip Dorsett | WR | KC | 3300.0 | 5.168265 |
382 | ARI | Larry Fitzgerald | WR | DET | 5900.0 | 14.708424 |
395 | LAR | Tavon Austin | WR | IND | 3800.0 | 7.921712 |
397 | SF | Pierre Garcon | WR | CAR | 5300.0 | 12.918005 |
411 | SEA | Jimmy Graham | WR | GB | NaN | 12.641802 |
Next, we can impose constrains on our lineups. I'll walk through the types of supported constraints:
Last season this was great for dealing with position groups like the Falcons Offense or the Redskins Receiving core. It was tough to say who will score but you can be quite confident they will have a touchdown. Constraints help you take advantage of that
#last years example
tweaked=pruned
ONE_OF=[]
ONE_OF.append(['Ty Montgomery'])#forces Ty Montgomery in each lineup
ONE_OF.append(["LeGarrette Blount","Dion Lewis"])
ONE_OF.append(["Devonta Freeman","Tevin Coleman"])
ONLY_ONE=[]
ONLY_ONE.append(['Devonta Freeman', 'Tevin Coleman'])
ONLY_ONE.append(['Antonio Brown','Eli Rogers'])#never more than one of these guys
ONLY_ONE.append(['Julio Jones','Mohamed Sanu'])
AT_LEAST=[]
#AT_LEAST.append()
locks=[]#["Julien Edelman"]
tweaked[tweaked['position']=='DST']
team_name | player_name | position | opp_name | salary | predictedScore | |
---|---|---|---|---|---|---|
0 | DEN | Denver Broncos | DST | LAC | 3600.0 | 8.655786 |
1 | CAR | Carolina Panthers | DST | SF | 3500.0 | 7.520136 |
2 | BAL | Baltimore Ravens | DST | CIN | 2800.0 | 7.454143 |
3 | IND | Indianapolis Colts | DST | LAR | 2700.0 | 7.285907 |
4 | OAK | Oakland Raiders | DST | TEN | 2500.0 | 6.774250 |
5 | KC | Kansas City Chiefs | DST | NE | 2300.0 | 6.398443 |
6 | CHI | Chicago Bears | DST | ATL | 2200.0 | 5.391771 |
7 | SF | San Francisco 49ers | DST | CAR | 2100.0 | 5.348293 |
8 | CLE | Cleveland Browns | DST | PIT | 2000.0 | 3.625214 |
We'll just use locks for this example. Besides that we have some standard GPP constraints like:
#gen lineups without flex
tweaked=prune(dfs)
locks=["Matt Ryan","David Johnson","Baltimore Ravens", "Rob Gronkowski"]
lineGen=lineupGenerator(tweaked, locks, [])
lineups=lineGen.genLineups()
lineups=list(lineups)
HIGH_RISK_SWAPS=[]
ONE_OF=[]
AT_LEAST=[]
ONLY_ONE=[]
len(lineups)
42921
Add flex player
finals=[]
flex=tweaked[tweaked['position'].isin(["WR","TE","RB"])]
for run,l in enumerate(lineups):
if run%1000==0:
print(float(run)/len(lineups),len(finals))
names=[player['player_name'] for player in l]
for f in flex[~flex['player_name'].isin(names)].to_dict(orient="records"):
final=l[:]
originalPos=f['position']
f['position']='FLEX'
final.append(f)
fromTeam=defaultdict(int)
fromSkill=defaultdict(int)
fromPos=defaultdict(int)
fromTeamTE=defaultdict(int)
oppDST=defaultdict(int)
sal=0
valid=True
swap=0
one_of=[0]*len(ONE_OF)
at_least=[0]*len(AT_LEAST)
only_one=[0]*len(ONLY_ONE)
#ADD SWAPS.
ONE_OF_COPY=ONE_OF.copy()
AT_LEAST_COPY=AT_LEAST.copy()
ONLY_ONE_COPY=ONLY_ONE.copy()
dst=None
for p in final:
if p['position']=='FLEX':
p["position"]=originalPos
if p['position']!='DST':
fromTeam[p['team_name']]+=1
if p['position']=='TE':
fromTeamTE[p['team_name']]+=1
fromPos[hashPlayer(p)]+=1
oppDST[p['opp_name'].replace('@','')]+=1
if p['position']!='QB':
fromSkill[p['team_name']]+=1
else:
dst=p['team_name']
sal+=p['salary']
if p['player_name'] in HIGH_RISK_SWAPS:
swap+=1
for idx, players in enumerate(ONE_OF_COPY):
if p['player_name'] in players:
one_of[idx]+=1
#ONE_OF_COPY.remove(players)
for idx, players in enumerate(AT_LEAST_COPY):
if p['player_name'] in players:
at_least[idx]+=1
#AT_LEAST_COPY.remove(players)
for idx, players in enumerate(ONLY_ONE_COPY):
if p['player_name'] in players:
only_one[idx]+=1
#ONLY_ONE_COPY.remove(players)
#add flex generator that checks if in pool
final[-1]["position"]="FLEX"
if sal<=50000 and all(fromSkill[pos]<=2 for pos in fromSkill) \
and all(fromTeamTE[team]<=1 for team in fromTeamTE)\
and all(fromPos[pos]<=1 for pos in fromPos)\
and sal>=40000 and valid and swap<=1 \
and all(one_of==1 for one_of in one_of)\
and all(at>=1 for at in at_least)\
and all(one<=1 for one in only_one):
if oppDST[dst]<=1:
#print(oppDST)
#print(lineup)
lineup=Lineup(final)
finals.append(lineup)
0.0 0 0.023298618391929357 6390 0.046597236783858714 19955 0.06989585517578807 30532 0.09319447356771743 44339 0.1164930919596468 55705 0.13979171035157614 70795 0.1630903287435055 82151 0.18638894713543486 103313 0.20968756552736423 116089 0.2329861839192936 129248 0.25628480231122297 143191 0.2795834207031523 155311 0.30288203909508166 176609 0.326180657487011 195047 0.3494792758789404 212601 0.3727778942708697 237882 0.3960765126627991 259376 0.41937513105472846 273899 0.4426737494466578 299379 0.4659723678385872 317347 0.4892709862305165 341994 0.5125696046224459 375628 0.5358682230143752 396803 0.5591668414063046 422707 0.5824654597982339 457043 0.6057640781901633 479318 0.6290626965820927 508150 0.652361314974022 539459 0.6756599333659514 566522 0.6989585517578808 602524 0.7222571701498102 629243 0.7455557885417394 664840 0.7688544069336688 696454 0.7921530253255982 735515 0.8154516437175275 768074 0.8387502621094569 806197 0.8620488805013863 842271 0.8853474988933157 880323 0.908646117285245 921004 0.9319447356771744 962802 0.9552433540691037 1007226 0.978541972461033 1052553
allHash=set()
for f in finals:
phash=set()
for p in f.players:
phash.add(p["player_name"]+","+p["team_name"]+","+str(p["predictedScore"]))
allHash.add(frozenset(phash))
len(allHash)
577321
500,000 lineups! The lower number is without duplicates caused by flex vs normal spot
#Grab top 10 lineups
finals=sorted(list(allHash)[:10],key=lambda x: sum([float(xi.split(",")[-1]) for xi in x]),reverse=True)
new=[]
for idx, f in enumerate(finals):
if idx%1000==0:
print(idx,len(finals), idx/len(finals))
l=[]
posis={"WR": 3,"TE":1,"RB":2}
for i,n in enumerate(list(f)):
p={}
p["player_name"]=n.split(",")[0]
p["team_name"]=n.split(",")[1]
p['predictedScore']=float(n.split(",")[2])
filtered=pruned[(pruned["player_name"]==p["player_name"])&(pruned["team_name"]==p["team_name"])].iloc[0]
p["position"]=filtered["position"]
p["salary"]=filtered["salary"]
if p["position"] in posis.keys():
posis[p["position"]]-=1
if p["position"] in posis.keys() and posis[p["position"]]<0:
p["position"]="FLEX"
l.append(p)
new.append(l)
new_lineups= [Lineup(l) for l in new]
sort=sorted(new_lineups,key=attrgetter("predictedScore"),reverse=True)
playerCounts=defaultdict(int)
for l in new:
for p in l:
playerCounts[p["player_name"]]+=1
print("Player count", len(playerCounts.keys()))
print("Players used",sorted(tweaked['player_name'].unique()))
print(len(tweaked['player_name'].unique()))
{key: playerCounts[key]/float(len(new)) for key in playerCounts}
0 10 0.0 Player count 32 Players used ['A.J. Green', 'Aaron Rodgers', 'Alex Smith', 'Alshon Jeffery', 'Alvin Kamara', 'Andre Holmes', 'Andy Dalton', 'Antonio Brown', 'Baltimore Ravens', 'Bilal Powell', 'Brandon LaFell', 'Brandon Marshall', 'Brian Hoyer', 'C.J. Anderson', 'C.J. Fiedorowicz', 'Carlos Hyde', 'Carolina Panthers', 'Carson Wentz', 'Chicago Bears', 'Chris Thompson', 'Cleveland Browns', 'Clive Walford', 'Coby Fleener', 'Cordarrelle Patterson', 'Davante Adams', 'David Johnson', 'DeAndre Hopkins', 'DeShone Kizer', 'Denver Broncos', 'Devonta Freeman', 'Dez Bryant', 'Donte Moncrief', 'Doug Baldwin', 'Eli Manning', 'Indianapolis Colts', 'James White', 'Jared Cook', 'Jarvis Landry', 'Javorius Allen', 'Jay Ajayi', 'Jeremy Maclin', 'Jerick McKinnon', 'Jermaine Gresham', 'Jordan Howard', 'Jordy Nelson', 'Josh McCown', 'Julio Jones', 'Kansas City Chiefs', 'Keenan Allen', 'Kendall Wright', 'Kenny Britt', 'Kirk Cousins', 'Kyle Rudolph', 'Larry Fitzgerald', "Le'Veon Bell", 'LeSean McCoy', 'Marlon Mack', 'Marquise Goodwin', 'Matt Ryan', 'Matthew Stafford', 'Melvin Gordon', 'Michael Thomas', 'Mike Glennon', 'Mike Wallace', 'Oakland Raiders', 'Odell Beckham', 'Paul Perkins', 'Rob Gronkowski', 'Robby Anderson', 'Robert Kelley', 'San Francisco 49ers', 'Scott Tolzien', 'Shane Vereen', 'Sterling Shepard', 'Tarik Cohen', 'Ted Ginn', 'Terrelle Pryor', 'Todd Gurley', 'Travis Kelce', 'Tyler Higbee', 'Tyrod Taylor', 'Zach Ertz', 'Zay Jones'] 83
{'Alshon Jeffery': 0.1, 'Alvin Kamara': 0.2, 'Andre Holmes': 0.3, 'Baltimore Ravens': 1.0, 'Brandon LaFell': 0.1, 'Brandon Marshall': 0.3, 'C.J. Fiedorowicz': 0.1, 'Chris Thompson': 0.2, 'Clive Walford': 0.3, 'Davante Adams': 0.1, 'David Johnson': 1.0, 'DeAndre Hopkins': 0.2, 'Devonta Freeman': 0.1, 'Dez Bryant': 0.1, 'Donte Moncrief': 0.2, 'James White': 0.2, 'Jermaine Gresham': 0.1, 'Julio Jones': 0.1, 'Keenan Allen': 0.1, 'Kenny Britt': 0.2, 'Larry Fitzgerald': 0.2, 'Marlon Mack': 0.1, 'Marquise Goodwin': 0.4, 'Matt Ryan': 1.0, 'Mike Wallace': 0.1, 'Rob Gronkowski': 1.0, 'Robby Anderson': 0.2, 'Shane Vereen': 0.2, 'Sterling Shepard': 0.2, 'Tarik Cohen': 0.2, 'Terrelle Pryor': 0.2, 'Zay Jones': 0.2}
Our constraints we're honored. Matt Ryan, Rob Gronkowski and the Baltimore Ravens are in all of our lineups
new_lineups
[0.0 127.83853357199999 300.0 David Johnson, Zay Jones, Terrelle Pryor, Matt Ryan, C.J. Fiedorowicz, Baltimore Ravens, Devonta Freeman, Sterling Shepard, Rob Gronkowski, 0.0 122.81643964300002 600.0 Larry Fitzgerald, David Johnson, Matt Ryan, Marquise Goodwin, Alshon Jeffery, Kenny Britt, Baltimore Ravens, Shane Vereen, Rob Gronkowski, 0.0 121.79694265100001 2500.0 David Johnson, Brandon Marshall, Matt Ryan, Kenny Britt, Baltimore Ravens, Chris Thompson, Sterling Shepard, Rob Gronkowski, James White, 0.0 120.00725186100001 2700.0 David Johnson, Marlon Mack, Matt Ryan, Marquise Goodwin, Baltimore Ravens, Brandon Marshall, Robby Anderson, Rob Gronkowski, DeAndre Hopkins, 0.0 118.747398385 1100.0 David Johnson, Dez Bryant, Alvin Kamara, Matt Ryan, Marquise Goodwin, Brandon LaFell, Baltimore Ravens, Brandon Marshall, Rob Gronkowski, 0.0 117.52101673800001 2500.0 David Johnson, Clive Walford, Tarik Cohen, Matt Ryan, Davante Adams, Baltimore Ravens, Rob Gronkowski, Donte Moncrief, DeAndre Hopkins, 0.0 117.518967094 1200.0 Larry Fitzgerald, David Johnson, Clive Walford, Julio Jones, Tarik Cohen, Matt Ryan, Baltimore Ravens, Andre Holmes, Rob Gronkowski, 0.0 117.447469314 2800.0 David Johnson, Terrelle Pryor, Matt Ryan, Mike Wallace, Baltimore Ravens, Andre Holmes, Shane Vereen, Rob Gronkowski, James White, 0.0 112.57136247100001 6000.0 David Johnson, Jermaine Gresham, Zay Jones, Matt Ryan, Marquise Goodwin, Baltimore Ravens, Chris Thompson, Rob Gronkowski, Donte Moncrief, 0.0 109.646602266 5200.0 David Johnson, Clive Walford, Alvin Kamara, Matt Ryan, Baltimore Ravens, Keenan Allen, Andre Holmes, Robby Anderson, Rob Gronkowski]
Our highest expected value with our constraints is 127.838 points
Download the draft kings lineup template by going to the lineups tab on Draft Kings.com. We've included the all game template for week 1 for your convenience
id_table=pd.read_csv("data/dk-lineup-template-week1.csv",skiprows=7)
id_table.index=range(len(id_table))
id_table.columns=range(len(id_table.columns))
id_table=id_table[[9,10,11,12,13,14,15]]
id_table.columns=["position","Name + ID","player_name","ID","Salary","Game Info","team_name"]
def cleanName(name):
#sites handle surnames/nicknames differently. We will remove them and have verified this is ok at the time of writing
return name.replace(' Jr.', '').replace("'","").replace(" Sr.","")
id_table['player_name']=id_table['player_name'].apply(cleanName)
id_table.head(3)
position | Name + ID | player_name | ID | Salary | Game Info | team_name | |
---|---|---|---|---|---|---|---|
0 | RB | Le'Veon Bell (9359211) | LeVeon Bell | 9359211 | 9800 | PIT@CLE 01:00PM ET | PIT |
1 | RB | David Johnson (9359235) | David Johnson | 9359235 | 9400 | ARI@DET 01:00PM ET | ARI |
2 | WR | Antonio Brown (9358987) | Antonio Brown | 9358987 | 8800 | PIT@CLE 01:00PM ET | PIT |
rows=[]
POS_START={"QB":0,"RB":1,"WR":3,"TE":6,"FLEX":7,"DST":8}
for l in new_lineups:
row=[0]*9
for p in l.players[:]:
idx=POS_START[p['position']]
if(idx==8):
name=p["player_name"].split(" ")[-1]+" "#Draft kings DST's have a space after them for some reason.
else:
name=p["player_name"]
player=id_table[(id_table['player_name']==name)&(id_table['team_name']==p['team_name'])].iloc[0]
if row[idx]<1:
row[idx]=player['ID']
else:
idx+=1
if row[idx]<1:
row[idx]=player['ID']
else:
idx+=1
row[idx]=player['ID']
rows.append(row)
output_lineups=pd.DataFrame(rows, columns=["QB","RB","RB","WR","WR","WR","TE","FLEX","DST"])
output_lineups
QB | RB | RB | WR | WR | WR | TE | FLEX | DST | |
---|---|---|---|---|---|---|---|---|---|
0 | 9358787 | 9359235 | 9359384 | 9359993 | 9359005 | 9359688 | 9359251 | 9358612 | 9358762 |
1 | 9358787 | 9359235 | 9358968 | 9358814 | 9359127 | 9359122 | 9358612 | 9358910 | 9358762 |
2 | 9358787 | 9359235 | 9359150 | 9358773 | 9358910 | 9359688 | 9358612 | 9358576 | 9358762 |
3 | 9358787 | 9359235 | 9360142 | 9359127 | 9358773 | 9359545 | 9358612 | 9359331 | 9358762 |
4 | 9358787 | 9359235 | 9358631 | 9358936 | 9359127 | 9358874 | 9358612 | 9358773 | 9358762 |
5 | 9358787 | 9359235 | 9360065 | 9359572 | 9359514 | 9359331 | 9359249 | 9358612 | 9358762 |
6 | 9358787 | 9359235 | 9360065 | 9358814 | 9359003 | 9359310 | 9359249 | 9358612 | 9358762 |
7 | 9358787 | 9359235 | 9358968 | 9359005 | 9358914 | 9359310 | 9358612 | 9358576 | 9358762 |
8 | 9358787 | 9359235 | 9359150 | 9359993 | 9359127 | 9359514 | 9358900 | 9358612 | 9358762 |
9 | 9358787 | 9359235 | 9358631 | 9358696 | 9359310 | 9359545 | 9359249 | 9358612 | 9358762 |
output_lineups.to_csv("data/lineupsweek1-example.csv",index=False)
There you have it. All you have to do now is go to the lineups tab on draft kings and upload this sheet.
I hope this served as a good example. The code needs serious refactoring, probably should just be scrapped, but this should give you a great starting point for your own lineup optimizer