Skip to content

Commit

Permalink
[0.2.0] Refactor: Use states for managing lineups and scores (#2)
Browse files Browse the repository at this point in the history
* update example and usage

* modify example

* add setup to avoid build issue PEP660

* return id to salary info

* bump version to 0.2.0

* bump version to 0.2.0

* add help for args, modify methods

* use states for lineups and scores
  • Loading branch information
KengoA authored Mar 4, 2023
1 parent ff20405 commit 9894ece
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 60 deletions.
30 changes: 22 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ Alternatively, you can provide a `numpy.array` where the columns correspond to p
import numpy as np
from fantasy_ga import LineupGenerator, read_csv

model = LineupGenerator(m, n_pop, n_gen, n_breed, n_mutate, n_compound)
lineups, scores = model.fit()
optimal_lineups, top_n_scores = model.get_top_n_lineups(1)

# load data from DraftKings salary csv
id_to_name, m = read_csv("examples/DraftKings/DKSalaries.csv", site="DraftKings")
id_to_name, id_to_salary, m = read_csv("examples/DraftKings/DKSalaries.csv", site="DraftKings")

# initial population of random lineups
n_pop = 1000
Expand All @@ -33,19 +37,29 @@ n_mutate = 30
n_compound = 5

model = LineupGenerator(m, n_pop, n_gen, n_breed, n_mutate, n_compound)
lineups, scores = model.fit()
optimal_lineups, top_n_scores = model.get_top_n_lineups(lineups, scores, 1)
model.fit()
optimal_lineups, top_n_scores = model.get_top_n_lineups(1)

print(
f"Players: {[id_to_name[id] for id in optimal_lineups[0]]}\nSalary Total: {sum([id_to_salary[id] for id in optimal_lineups[0]])}\nExpected FPTS: {top_n_scores[0]}"
)
```

### CLI

As a module
As a Python module
```
$ python -m fantasy_ga --filepath=examples/DraftKings/DKSalaries.csv --site=DraftKings --n_pop=100 --n_gen=5 --n_breed=100 --n_mutate=100 --n_compound=10 --top_n_lineups=1
> PlayerIDs: [ 17. 106. 70. 0. 63. 33. 1. 108.], FPTS: 308.62082
```
or a command
or a CLI command
```
$ fantasy-ga --filepath=examples/DraftKings/DKSalaries.csv --site=DraftKings --n_pop=100 --n_gen=5 --n_breed=100 --n_mutate=100 --n_compound=10 --top_n_lineups=1
```
$ fantasy-ga --filepath=examples/DraftKings/DKSalaries.csv --site=DraftKings --n_pop=100 --n_gen=5 --n_breed=100 --n_mutate=100 --n_compound=10 --top_n_lineups=1
> PlayerIDs: [ 17. 106. 70. 0. 63. 33. 1. 108.], FPTS: 308.62082
which generates
```
Generated Top 1 lineups
Players: ['Russell Westbrook', 'Bruce Brown', 'Michael Porter Jr.', 'Jerami Grant', 'Mason Plumlee', 'Paul George', 'Aaron Gordon', 'Marcus Morris Sr.']
Salary Total: 49100
Expected FPTS: 254.11
```
11 changes: 6 additions & 5 deletions examples/generate_lineups.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@

if __name__ == "__main__":
# load data from DraftKings salary csv
id_to_name, m = read_csv("examples/DraftKings/DKSalaries.csv", site="DraftKings")
id_to_name, id_to_salary, m = read_csv(
"examples/DraftKings/DKSalaries.csv", site="DraftKings"
)
n_pop = 1000
n_breed = 30
n_mutate = 30
n_gen = 16
n_compound = 5
pos_start_idx = 3

model = LineupGenerator(m, n_pop, n_gen, n_breed, n_mutate, n_compound)
lineups, scores = model.fit()
optimal_lineups, top_n_scores = model.get_top_n_lineups(lineups, scores, 1)
model.fit()
optimal_lineups, top_n_scores = model.get_top_n_lineups(1)

print(
f"Players: {[id_to_name[id] for id in optimal_lineups[0]]}\nFPTS: {top_n_scores[0]}"
f"Players: {[id_to_name[id] for id in optimal_lineups[0]]}\nSalary Total: {sum([id_to_salary[id] for id in optimal_lineups[0]])}\nExpected FPTS: {top_n_scores[0]}"
)
2 changes: 1 addition & 1 deletion fantasy_ga/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
from fantasy_ga.utils import read_csv
from fantasy_ga.constants import *

__version__ = "0.1.0"
__version__ = "0.2.0"
34 changes: 19 additions & 15 deletions fantasy_ga/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@

def main():
parser = argparse.ArgumentParser()
parser.add_argument("--filepath", help="filepath")
parser.add_argument("--site", help="site")
parser.add_argument("--top_n_lineups", help="filepath")
parser.add_argument("--n_pop", help="Do the bar option")
parser.add_argument("--n_gen", help="Foo the program")
parser.add_argument("--n_breed", help="Foo the program")
parser.add_argument("--n_mutate", help="Foo the program")
parser.add_argument("--n_compound", help="Foo the program")
parser.add_argument("--sal_cap", help="Foo the program")
parser.add_argument(
"--filepath",
help="File path to the csv file exported from daily fantasy platforms",
)
parser.add_argument("--site", help="Site type. Currently supports (DraftKings)")
parser.add_argument("--top_n_lineups", help="top N lineups to display")
parser.add_argument("--n_pop", help="Number of initial population to sample from")
parser.add_argument("--n_gen", help="Number of generations to run evolution cycles")
parser.add_argument("--n_breed", help="Number of children lineups to generate")
parser.add_argument("--n_mutate", help="Number of mutations for lineups")
parser.add_argument("--n_compound", help="Number of compound evolution cycles")
parser.add_argument("--sal_cap", help="Salary cap for the lineup.")

args = parser.parse_args()

id_to_name, m = read_csv(args.filepath, args.site)
id_to_name, id_to_salary, m = read_csv(args.filepath, args.site)
model = LineupGenerator(
m=m,
n_pop=int(args.n_pop),
Expand All @@ -27,12 +30,13 @@ def main():
n_compound=int(args.n_compound),
)

lineups, scores = model.fit()
lineups, scores = model.get_top_n_lineups(lineups, scores, int(args.top_n_lineups))
print(f"generated top {args.top_n_lineups} lineups")
model.fit()
lineups, scores = model.get_top_n_lineups(int(args.top_n_lineups))
print(f"Generated Top {args.top_n_lineups} lineups")
for lineup, score in zip(lineups, scores):
print(f"Players: {[id_to_name[id] for id in lineup]}, FPTS: {score}")

print(
f"\nPlayers: {[id_to_name[id] for id in lineup]}\nSalary Total: {sum([id_to_salary[id] for id in lineup])}\nExpected FPTS: {score}"
)

if __name__ == "__main__":
main()
56 changes: 27 additions & 29 deletions fantasy_ga/lineup_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,25 @@ def __init__(
self.n_compound = n_compound
self.pos_start_idx = pos_start_idx
self.sal_cap = sal_cap
self.lineups = []
self.scores = []

@staticmethod
def get_top_n_lineups(lineups, scores, n):
top_n_scores = (-scores).argsort()[:n]
return lineups.take(top_n_scores.astype(int), 0), scores.take(
def get_top_n_lineups(self, n):
top_n_scores = (-self.scores).argsort()[:n]
return self.lineups.take(top_n_scores.astype(int), 0), self.scores.take(
top_n_scores.astype(int), 0
)

def calc_scores(self, lineups: np.array):
def calc_scores(self):
scores = []
for lineup in lineups:
for lineup in self.lineups:
sal, fpts = self.m[np.in1d(self.m[:, 0], lineup.astype(int))][
:, [1, 2]
].sum(axis=0)
scores.append(fpts) if sal <= self.sal_cap and len(
np.unique(lineup)
) == 8 else scores.append(-1)
return np.array(scores)
self.scores = np.array(scores)

def create_random_lineups(self):
lineups = []
Expand All @@ -56,48 +57,45 @@ def create_random_lineups(self):
lineups.append(lineup)
return np.array(lineups)

def breed(self, lineups, scores):
def breed(self):
new_lineups = []
parents_idx = (-scores).argsort()[:2]
parents = lineups.take(parents_idx, 0)
parents_idx = (-self.scores).argsort()[:2]
parents = self.lineups.take(parents_idx, 0)

for _ in range(self.n_breed):
if np.array_equal(*parents):
return parents
self.lineups = parents
return
else:
breed_idx = np.random.choice(2, 8)
new_lineups.append([parents[p, idx] for idx, p in enumerate(breed_idx)])
return np.vstack([parents, np.array(new_lineups)])
self.lineups = np.vstack([parents, np.array(new_lineups)])

def mutate(self, lineups):
mutate_idx = np.random.choice(lineups.shape[0], self.n_mutate)
def mutate(self):
mutate_idx = np.random.choice(self.lineups.shape[0], self.n_mutate)

for idx in mutate_idx:
mutant = self.m[np.random.choice(self.m.shape[0]), :]
original = self.m[
self.m[:, 0] == np.random.choice(lineups[idx]).astype(int), :
self.m[:, 0] == np.random.choice(self.lineups[idx]).astype(int), :
][0]
eligible_pos = np.where(
mutant[self.pos_start_idx :].astype(bool)
& original[self.pos_start_idx :].astype(bool)
)[0]
swap_pos = np.random.choice(eligible_pos)
lineups = np.vstack([lineups, lineups[idx]])
lineups[-1, swap_pos] = mutant[0]
return lineups
self.lineups = np.vstack([self.lineups, self.lineups[idx]])
self.lineups[-1, swap_pos] = mutant[0]

def evolve(self, lineups, scores):
def evolve(self):
for _ in range(self.n_gen):
lineups = self.breed(lineups, scores)
lineups = self.mutate(lineups)
scores = self.calc_scores(lineups)
return lineups, scores
self.breed()
self.mutate()
self.calc_scores()

def fit(self):
lineups = self.create_random_lineups()
scores = self.calc_scores(lineups)
self.lineups = self.create_random_lineups()
for _ in range(self.n_compound):
lineups, scores = self.evolve(lineups, scores)
lineups = np.vstack([lineups, self.create_random_lineups()])
scores = self.calc_scores(lineups)
return lineups, scores
self.calc_scores()
self.evolve()
self.lineups = np.vstack([self.lineups, self.create_random_lineups()])
4 changes: 3 additions & 1 deletion fantasy_ga/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ def read_csv(filepath: str, site: str) -> tuple[dict, list]:
_ = next(reader)
players = []
id_to_name = {}
id_to_salary = {}
if site == "DraftKings":
for row in reader:
id_to_name[int(row[3])] = row[2]
id_to_salary[int(row[3])] = int(row[5])
players.append(
[int(row[3]), int((row[5])), float(row[8])]
+ encode_position(row[4])
)
elif site == "FanDuel":
raise NotImplementedError
return id_to_name, np.array(players)
return id_to_name, id_to_salary, np.array(players)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "fantasy-ga"
version = "0.1.0"
version = "0.2.0"
description = "Genetic algorithm library to generate fantasy basketball lineups"
readme = "README.md"
authors = [{ name = "Kengo Arao", email = "kengo@hey.com" }]
Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from setuptools import setup

setup()

0 comments on commit 9894ece

Please sign in to comment.