-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathon_fire.py
274 lines (216 loc) · 12.2 KB
/
on_fire.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
from discord.ext import commands
import config #create a config file with your relevant keys or in a VENV file and do NOT share them!!!!!!!
import discord
import requests
import pandas as pd
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
import seaborn.objects as s
import io
from datetime import date
#importing league
from espn_api.basketball import League
league = League(league_id=config.leagueid, year=2025, espn_s2=config.espn_s2config, swid=config.swid)
#note that you WILL need to reload the league if you want to refresh data; i.e. if you use an older instance, it won't understand if a player was added to someone's team afterwards
#importing line module
import lines
#get date
today = date.today()
yeardate = today.strftime("%Y-%m-%d")
monthdate = today.strftime("%m-%d")
#create list for all ball don't lie (BDL) json scrape lists to go into
jsonlist = []
next_cursor_page = None
#create scraping loop
while True:
params = {'start_date':yeardate, 'end_date':yeardate, 'per_page':'100'}
if next_cursor_page:
params['cursor'] = next_cursor_page #we get the first page by default, so this won't be added on the first run. later runs get the cursor when applicable
try:
r = requests.get('https://api.balldontlie.io/v1/stats', headers=config.bdltoken, params=params)
r.raise_for_status()
except requests.exceptions.HTTPError as err:
print(f'Error: {err}')
break
rjson = r.json()
jsonlist.append(rjson)
next_cursor_page = rjson['meta'].get('next_cursor', None) #if next cursor number exists, get next cursor
if not next_cursor_page:
print('There are no more pages available. All games for specified day logged. Check the "mergedpd" dataframe for all lines.')
break
dfs = []
for i, jsons in enumerate(jsonlist):
df = pd.json_normalize(jsons, record_path='data') #normalize all jsons into dfs
dfs.append(df)
mergedpd = pd.concat(dfs, ignore_index=True) #this will also work on days where there is only one dataframe to take in; best to use this instead of "mergedpd = dfs[0]" given that we want to modify a "full" dataframe rather than a copy of it
gms = league.teams #fetch all team names
abbrevs = []
for i in range(len(gms)):
abbrev = league.teams[i].team_abbrev
abbrevs.append(abbrev)
playerslist = []
for i in range(len(gms)):
players = league.teams[i].roster #get roster via index obtained with len(); each gm number corresponds to their respective roster
playerslist.append(players) #each entry is a list of rosters; this will be separated when we use explode() with Pandas
def getstatus(playerlist):
for i, lists in enumerate(playerlist):
for z, players in enumerate(lists):
status = league.teams[i].roster[z].lineupSlot
players = str(players)
players = players + ', ' + status
lists[z] = players
getstatus(playerslist)
df = pd.DataFrame((zip(gms, playerslist, abbrevs)),
columns = ['GM', 'Player', 'Abbrev'])
df2 = df.explode('Player')
df2 = df2.astype(str)
df2[['Player', 'Status']] = df2['Player'].str.split(', ', n=1, expand=True)
df2['GM'] = df2['GM'].str.replace('Team(', '')
df2['GM'] = df2['GM'].str.replace(')', '')
df2['Player'] = df2['Player'].str.replace('Player(', '')
df2['Player'] = df2['Player'].str.replace(')', '')
playerdict = df2.set_index('Player')['GM'].to_dict()
statusdict = df2.set_index('Player')['Status'].to_dict()
abbrevdict = df2.set_index('GM')['Abbrev'].to_dict()
#create emoji criteria for "mindblowing stats"
def zcheck(bdldf, zcolumn, x) -> str:
"""Checks if the counting Z-score is equal to or above 3.50. If it is, it returns the mindblown emoji."""
if bdldf[zcolumn].iloc[x] >= 3.50:
return ' 🤯'
else:
return ''
def stlcheck(bdldf, stlcol, x) -> str:
"""Checks if the steal z-score is equal to or above 8.46557978778486 (or, four steals). If it is, it returns the mindblown emoji."""
if bdldf[stlcol].iloc[x] >= 8.46557978778486:
return ' 🤯'
else:
return ''
def volcheck(bdldf, zcolumn, x) -> str:
"""Checks if the player's efficiency (FGARZ or FTARZ) was at or above/below 3.25/-3.25. Will either return target emoji (great accuracy) or brick emoji (awful accuracy). """
if bdldf[zcolumn].iloc[x] >= 3.25:
return ' 🎯'
elif bdldf[zcolumn].iloc[x] <= -3.25:
return ' 🧱'
else:
return ''
def tocheck(bdldf, tozcolumn, x) -> str:
"""Checks if the player's TOZ was at -1.08150924234275 or below (or, five turnovers or more). If so, return the vomit emoji. """
if bdldf[tozcolumn].iloc[x] <= -1.08150924234275:
return ' 🤮 '
else:
return ' '
def printout(bdldf, maxlines) -> str:
"""Forms the printout of the selected range of lines from the Ball Don't Lie dataframe. Also checks if a player's line included a triple double.
Note: 'maxlines' will determine how many lines from the dataframe to print out. This can be modified depending on how many lines you want to print depending on any conditions (i.e. game volume, etc.)"""
alllines = []
for i in range (0, maxlines):
if sum([(bdldf['pts'].iloc[i]>=10), (bdldf['reb'].iloc[i]>=10), (bdldf['ast'].iloc[i]>=10), (bdldf['stl'].iloc[i]>=10), (bdldf['blk'].iloc[i]>=10)]) == 3: #make sure to add paranthesis to each Pandas index or else Python will incorrectly assume you're closing off the command
statdubline = str(' 3️⃣🚀')
elif sum([(bdldf['pts'].iloc[i]>=10), (bdldf['reb'].iloc[i]>=10), (bdldf['ast'].iloc[i]>=10), (bdldf['stl'].iloc[i]>=10), (bdldf['blk'].iloc[i]>=10)]) == 4:
statdubline = str(' 4️⃣🎆') #make sure to add paranthesis to each Pandas index or else Python will incorrectly assume you're closing off the command
else:
statdubline = None
line = str(f"{i+1}. **{bdldf['PlayerName'].iloc[i]}**{statdubline if statdubline is not None else ''} (*{bdldf['GM'].iloc[i]}*) with {bdldf['pts_s'].iloc[i]}{zcheck(bdldf, 'PTSZ', i)}, {bdldf['reb_s'].iloc[i]}{zcheck(bdldf, 'REBZ', i)}, {bdldf['ast_s'].iloc[i]}{zcheck(bdldf, 'ASTZ', i)}, {bdldf['fg3m_s'].iloc[i]}{zcheck(bdldf, 'FG3Z', i)}, {bdldf['stl_s'].iloc[i]}{stlcheck(bdldf, 'STLZ', i)}, {bdldf['blk_s'].iloc[i]}{zcheck(bdldf, 'blk', i)}, and {bdldf['tov_s'].iloc[i]}{(tocheck(bdldf, 'TOVZ', i))}on {bdldf['fgm'].iloc[i]}/{bdldf['fga'].iloc[i]} FG{volcheck(bdldf, 'FGARZ', i)} and {bdldf['ftm'].iloc[i]}/{bdldf['fta'].iloc[i]} FT{volcheck(bdldf, 'FTARZ', i)} splits in {bdldf['min'].iloc[i]} min.")
alllines.append(line)
printed = "\n".join([str(playerline) for playerline in alllines])
print(printed)
return printed
if mergedpd.empty is False:
mergedpd['PlayerName'] = mergedpd['player.first_name'] + " " + mergedpd['player.last_name']
mergedpd['GM'] = mergedpd['PlayerName'].map(playerdict)
mergedpd['Status'] = mergedpd['PlayerName'].map(statusdict)
mergedpd['Abbrev'] = mergedpd['GM'].map(abbrevdict)
mergedpd = mergedpd.dropna(subset=["GM"])
mergedpd['min'] = mergedpd['min'].astype('int64')
mergedpd = mergedpd.query('min > 0')
mergedpd = mergedpd.query("Status != 'BE'")
mergedpd = mergedpd.query("Status != 'IR'")
mergedpd['FGAR'] = (mergedpd['fgm']-(0.4834302*mergedpd['fga']))
mergedpd['FTAR'] = (mergedpd['ftm']-(0.7940223*mergedpd['fta']))
mergedpd['REBZ'] = ((mergedpd['reb']-5.451973)/2.525530)
mergedpd['FG3Z'] = (((mergedpd['fg3m']-1.7496388)/0.9290426)*0.80)
mergedpd['ASTZ'] = ((mergedpd['ast']-3.700322)/2.067065)
mergedpd['TOVZ'] = (((mergedpd['turnover']-1.758401)/0.749323)*-0.25)
mergedpd['STLZ'] = (((mergedpd['stl']-0.9037216)/0.2925993)*0.80)
mergedpd['BLKZ'] = (((mergedpd['blk']-0.6413044)/0.5216844)*0.80)
mergedpd['PTSZ'] = ((mergedpd['pts']-16.192476)/5.956778)
mergedpd['FGARZ'] = (((mergedpd['FGAR']-(-0.03505124))/0.65614770)*0.90)
mergedpd['FTARZ'] = (((mergedpd['FTAR']-0.02214733)/0.28761030)*0.80)
col_list = list(mergedpd)
zcols = col_list[55:64] #rebz through ftarz
mergedpd['ZSUM'] = mergedpd[zcols].sum(axis=1)
#convert score cols to strings; this will be passed into the string while allowing us to keep int versions for checking for a triple dub
mergedpd['pts_s'] = mergedpd['pts'].astype(str) + ' PTS'
mergedpd['blk_s'] = mergedpd['blk'].astype(str) + ' BLK'
mergedpd['reb_s'] = mergedpd['reb'].astype(str) + ' REB'
mergedpd['ast_s'] = mergedpd['ast'].astype(str) + ' AST'
mergedpd['stl_s'] = mergedpd['stl'].astype(str) + ' STL'
mergedpd['pts_s'] = mergedpd['pts'].astype(str) + ' PTS'
mergedpd['fg3m_s'] = mergedpd['fg3m'].astype(str) + ' 3PM'
mergedpd['tov_s'] = mergedpd['turnover'].astype(str) + ' TO'
mergedpd.loc[mergedpd['REBZ'] > 2.5, 'reb_s'] = '**' + mergedpd['reb_s'] + '**'
mergedpd.loc[mergedpd['FG3Z'] > 2.5, 'fg3m_s'] = '**' + mergedpd['fg3m_s'] + '**'
mergedpd.loc[mergedpd['ASTZ'] > 2.5, 'ast_s'] = '**' + mergedpd['ast_s'] + '**'
mergedpd.loc[mergedpd['BLKZ'] > 2.5, 'blk_s'] = '**' + mergedpd['blk_s'] + '**'
mergedpd.loc[mergedpd['PTSZ'] > 2.5, 'pts_s'] = '**' + mergedpd['pts_s'] + '**'
mergedpd.loc[mergedpd['STLZ'] > 2.5, 'stl_s'] = '**' + mergedpd['stl_s'] + '**'
top = mergedpd.sort_values(by='ZSUM', ascending=False)
if len(top.index) < 30:
topprintout = printout(top, 5)
else:
topprintout = printout(top, 10)
#finding worst lines
bottom = mergedpd.sort_values(by='ZSUM', ascending=True)
bottom = bottom.query('min >= 14') #players must play 14 min to make it into the worst list (excluding injured players)
bottomprintout = printout(bottom, 5)
mergedpd = mergedpd.rename(columns={
'min':'MIN'
})
gmsums = mergedpd.groupby('Abbrev').agg({
'MIN':'sum',
'ZSUM':'sum'
}).round(decimals=2
).reset_index()
#creating daily graph
data_stream = io.BytesIO()
plt.style.use("dark_background")
fig, ax = plt.subplots(figsize=(12,9))
ax.set(ylim=(-30, 30))
ax.set_title(str(f"Daily Z-Sum for {yeardate}"), fontsize=25)
barplot = sns.barplot(
gmsums,
x='Abbrev',
y='ZSUM',
hue='Abbrev',
order=gmsums.sort_values(by='ZSUM', ascending=False).Abbrev
)
for i, (containers, values) in enumerate(zip(ax.containers, gmsums['ZSUM'])):
if 2.5 >= values >= -2.5: #if value is below 2.5 and above -2.5...
ax.bar_label(ax.containers[i], fontsize=11.5, padding=1.5) #values go outside bar due to narrow length in this case
else:
ax.bar_label(ax.containers[i], fontsize=11.5, padding=1.5, label_type='center', color='k')
plt.xlabel('Team', labelpad=13, fontsize=20)
plt.ylabel('Z-Sum', fontsize=20)
plt.xticks(fontsize=16)
plt.yticks(fontsize=16)
#if you need to debug the image with the actual width/height before it gets sent out, download the simply-view-image-for-python-debugging extension and input "fig" to use it once the plot is done: https://marketplace.visualstudio.com/items?itemName=elazarcoh.simply-view-image-for-python-debugging
fig
plt.savefig(data_stream, format='png', bbox_inches="tight", dpi = 80) #bbox inches insures that our graph margins aren't too big
plt.close()
data_stream.seek(0)
chart=discord.File(data_stream, filename='dailyzsum.png')
#debug note: when you're running this query set to 24 or more minutes, it won't work well if it's the beginning of the game.
#running bot
bot = commands.Bot(command_prefix="!", intents=discord.Intents.all())
@bot.event
async def on_ready():
channel = bot.get_channel(config.channel_id)
if mergedpd.empty is True:
await channel.send("If you're seeing this message, On Fire did not retrieve any lines for today. This could be because there were no games or something wrong happened while scraping. If it's the latter, please contact On Fire's owner!")
else:
await channel.send(f"{lines.intro(monthdate)}\n{topprintout}")
await channel.send(f"{lines.worstintro(monthdate)}\n{bottomprintout}")
await channel.send("Let\'s end today\'s episode of On Fire with a visualization of how each team did as a whole relative to their active player\'s Z-scores:", file=chart)
bot.run(config.discord_token)