This repository has been archived by the owner on Aug 8, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
/
league.py
279 lines (210 loc) · 9.41 KB
/
league.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
import hosts.fleaflicker as site
import hosts.db as db
import sqlite3
import pandas as pd
from os import path
from pathlib import Path
import seaborn as sns
from textwrap import dedent
from pandas import DataFrame
from utilities import (get_sims, generate_token, LICENSE_KEY, DB_PATH,
OUTPUT_PATH, master_player_lookup, get_players,
schedule_long)
def summarize_matchup(sims_a, sims_b):
"""
Given two teams of sims (A and B), summarize a matchup with win
probability, over-under, betting line, etc
"""
# start by getting team totals
total_a = sims_a.sum(axis=1)
total_b = sims_b.sum(axis=1)
# get win prob
winprob_a = (total_a > total_b).mean().round(2)
winprob_b = 1 - winprob_a.round(2)
# get over-under
over_under = (total_a + total_b).median().round(2)
# line
line = (total_a - total_b).median().round(2)
line = round(line*2)/2
return {'wp_a': winprob_a, 'wp_b': winprob_b, 'over_under': over_under,
'line': line}
def summarize_team(sims):
"""
Calculate summary stats on one set of teams.
"""
totals = sims.sum(axis=1)
# note: dropping count, min, max since those aren't that useful
stats = (totals.describe(percentiles=[.05, .25, .5, .75, .95])
[['mean', 'std', '5%', '25%', '50%', '75%', '95%']].to_dict())
# maybe share of points by each pos? commented out now but could look if
# interesting
# stats['qb'] = sims.iloc[:,0].mean()
# stats['rb'] = sims.iloc[:,1:3].sum(axis=1).mean()
# stats['flex'] = sims.iloc[:,3].mean()
# stats['wr'] = sims.iloc[:,4:6].sum(axis=1).mean()
# stats['te'] = sims.iloc[:,6].mean()
# stats['k'] = sims.iloc[:,7].mean()
# stats['dst'] = sims.iloc[:,8].mean()
return stats
def lineup_by_team(team_id):
return rosters.query(f"team_id == {team_id} & fantasymath_id.notnull()")['fantasymath_id']
def lock_of_week(df):
# team a
wp_a = df[['team_a', 'wp_a', 'team_b']]
wp_a.columns = ['team', 'wp', 'opp']
# team b
wp_b = df[['team_b', 'wp_b', 'team_a']]
wp_b.columns = ['team', 'wp', 'opp']
# combine
stacked = pd.concat([wp_a, wp_b], ignore_index=True)
# sort highest to low, pick out top
lock = stacked.sort_values('wp', ascending=False).iloc[0]
return lock.to_dict()
def photo_finish(df):
# get the std dev of win probs, lowest will be cloest matchup
wp_std = df[['wp_a', 'wp_b']].std(axis=1)
# idxmin "index min" returns the index of the lowest value
closest_matchup_id = wp_std.idxmin()
return df.loc[closest_matchup_id].to_dict()
if __name__ == '__main__':
# set parameters here
LEAGUE_ID = 316893
WEEK = 1
# first: get league data from DB + roster data by connecting to site
conn = sqlite3.connect(DB_PATH)
teams = db.read_league('teams', LEAGUE_ID, conn)
schedule = db.read_league('schedule', LEAGUE_ID, conn)
league = db.read_league('league', LEAGUE_ID, conn)
# set other parameters
TEAM_ID = league.iloc[0]['team_id']
HOST = league.iloc[0]['host']
SCORING = {}
SCORING['qb'] = league.iloc[0]['qb_scoring']
SCORING['skill'] = league.iloc[0]['skill_scoring']
SCORING['dst'] = league.iloc[0]['dst_scoring']
# then load rosters
token = generate_token(LICENSE_KEY)['token']
player_lookup = master_player_lookup(token)
rosters = (site.get_league_rosters(player_lookup, LEAGUE_ID)
.query("start"))
# making sure we query only valid players
available_players = get_players(token, **SCORING)
sims = get_sims(token, (set(rosters['fantasymath_id']) &
set(available_players['fantasymath_id'])),
nsims=1000, **SCORING)
players_w_pts = rosters.query("actual.notnull()")
for player, pts in zip(players_w_pts['fantasymath_id'], players_w_pts['actual']):
sims[player] = pts
########################################################
# load weekly lineup, matchup info
########################################################
schedule_this_week = schedule.query(f"week == {WEEK}")
# apply summarize matchup to every matchup in the data
matchup_list = [] # empty matchup list, where all our dicts will go
for a, b in zip(schedule_this_week['team1_id'], schedule_this_week['team2_id']):
# gives us Series of starting lineups for each team in matchup
lineup_a = lineup_by_team(a)
lineup_b = lineup_by_team(b)
# use lineups to grab right sims, feed into summarize_matchup function
working_matchup_dict = summarize_matchup(
sims[lineup_a], sims[lineup_b])
# add some other info to working_matchup_dict
working_matchup_dict['team_a'] = a
working_matchup_dict['team_b'] = b
# add working dict to list of matchups, then loop around to next
# matchup
matchup_list.append(working_matchup_dict)
matchup_df = DataFrame(matchup_list)
team_to_owner = {team: owner for team, owner in zip(teams['team_id'],
teams['owner_name'])}
matchup_df[['team_a', 'team_b']] = matchup_df[['team_a', 'team_b']].replace(team_to_owner)
#################
# analyzing teams
#################
team_list = []
for team_id in teams['team_id']:
team_lineup = lineup_by_team(team_id)
working_team_dict = summarize_team(sims[team_lineup])
working_team_dict['team_id'] = team_id
team_list.append(working_team_dict)
team_df = DataFrame(team_list).set_index('team_id')
# high low
# first step: get totals for each team in one DataFrame
totals_by_team = pd.concat(
[(sims[lineup_by_team(team_id)].sum(axis=1)
.to_frame(team_id)) for team_id in teams['team_id']], axis=1)
team_df['p_high'] = (totals_by_team.idxmax(axis=1)
.value_counts(normalize=True))
team_df['p_low'] = (totals_by_team.idxmin(axis=1)
.value_counts(normalize=True))
# lets see what those high and lows are, on average
# first step: get high score of every sim (max, not idxmax, we don't care
# who got it)
high_score = totals_by_team.max(axis=1)
# same for low score
low_score = totals_by_team.min(axis=1)
# then analyze
pd.concat([
high_score.describe(percentiles=[.05, .25, .5, .75, .95]).to_frame('high'),
low_score.describe(percentiles=[.05, .25, .5, .75, .95]).to_frame('low')], axis=1)
# add owner
team_df = (pd.merge(team_df, teams[['team_id', 'owner_name']], left_index=True,
right_on = 'team_id')
.set_index('owner_name')
.drop('team_id', axis=1))
league_wk_output_dir = path.join(
OUTPUT_PATH, f'{HOST}_{LEAGUE_ID}_2021-{str(WEEK).zfill(2)}')
Path(league_wk_output_dir).mkdir(exist_ok=True, parents=True)
output_file = path.join(league_wk_output_dir, 'league_analysis.txt')
# print results
with open(output_file, 'w') as f:
print(dedent(
f"""
**********************************
Matchup Projections, Week {WEEK} - 2021
**********************************
"""), file=f)
print(matchup_df, file=f)
print(dedent(
f"""
********************************
Team Projections, Week {WEEK} - 2021
********************************
"""), file=f)
print(team_df.round(2).sort_values('mean', ascending=False),
file=f)
lock = lock_of_week(matchup_df)
close = photo_finish(matchup_df)
meh = matchup_df.sort_values('over_under').iloc[0]
print(dedent("""
Lock of the week:"""), file=f)
print(f"{lock['team']} over {lock['opp']} — {lock['wp']}", file=f)
print(dedent("""
Photo-finish of the week::"""), file=f)
print(f"{close['team_a']} vs {close['team_b']}, {close['wp_a']}-{close['wp_b']}", file=f)
print(dedent("""
Most unexciting game of the week:"""), file=f)
print(f"{meh['team_a']} vs {meh['team_b']}, {meh['over_under']}", file=f)
################################################################################
# plot section
################################################################################
teams_long = totals_by_team.stack().reset_index()
teams_long.columns = ['sim', 'team_id', 'pts']
# now to link this to teams_long
schedule_team = schedule_long(schedule).query(f"week == {WEEK}")
teams_long_w_matchup = pd.merge(teams_long, schedule_team[['team_id', 'matchup_id']])
schedule_this_week['desc'] = (schedule_this_week['team2_id'].replace(team_to_owner)
+ ' v ' +
schedule_this_week['team1_id'].replace(team_to_owner))
# and plot it
teams_long_w_desc = pd.merge(teams_long_w_matchup,
schedule_this_week[['matchup_id', 'desc']])
teams_long_w_desc.head()
g = sns.FacetGrid(teams_long_w_desc.replace(team_to_owner), hue='team_id',
col='desc', col_wrap=2, aspect=2)
g = g.map(sns.kdeplot, 'pts', shade=True)
g.add_legend()
g.fig.subplots_adjust(top=0.9)
g.fig.suptitle(f'Team Points Distributions by Matchup 2 - Week {WEEK}')
g.fig.savefig(path.join(league_wk_output_dir, 'team_dist_by_matchup.png'),
bbox_inches='tight', dpi=500)