James McCool commited on
Commit
3573b17
·
1 Parent(s): 28a66aa

Implement Fantasy Football VORP Calculator with user-configurable league settings and data loading from external APIs. Added functions for calculating player rankings, replacement values, and VORP metrics. Enhanced user interface for league configuration and data display.

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +405 -37
src/streamlit_app.py CHANGED
@@ -1,40 +1,408 @@
1
- import altair as alt
2
- import numpy as np
3
  import pandas as pd
 
 
 
4
  import streamlit as st
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import pandas as pd
2
+ import numpy as np
3
+ import requests
4
+ import math
5
  import streamlit as st
6
 
7
+ Dwain_proj = 'https://sheetdb.io/api/v1/svino07zkd6j6?sheet=2025_NFL_Proj_Dist'
8
+ dwain_ranks = 'https://sheetdb.io/api/v1/ax8b1ms11bbzt?sheet=Dwain_Season'
9
+
10
+ # Default configuration dictionaries
11
+ type_flex_percentiles = {
12
+ 'Half PPR': {
13
+ 'RB': .40,
14
+ 'WR': .55,
15
+ 'TE': .05,
16
+ },
17
+ 'PPR': {
18
+ 'RB': .40,
19
+ 'WR': .55,
20
+ 'TE': .05,
21
+ },
22
+ 'Standard': {
23
+ 'RB': .40,
24
+ 'WR': .55,
25
+ 'TE': .05,
26
+ },
27
+ 'Superflex': {
28
+ 'QB': .95,
29
+ 'RB': .02,
30
+ 'WR': .03,
31
+ 'TE': .00,
32
+ },
33
+ 'TE Premium': {
34
+ 'RB': .35,
35
+ 'WR': .50,
36
+ 'TE': .15,
37
+ }
38
+ }
39
+
40
+ pos_vorp_limiters = {
41
+ 'PPR': {
42
+ 'QB': .5,
43
+ 'RB': .75,
44
+ 'WR': .75,
45
+ 'TE': .5,
46
+ },
47
+ 'Standard': {
48
+ 'QB': .25,
49
+ 'RB': .75,
50
+ 'WR': .75,
51
+ 'TE': .5,
52
+ },
53
+ 'Superflex': {
54
+ 'QB': .5,
55
+ 'RB': .75,
56
+ 'WR': .75,
57
+ 'TE': .5,
58
+ },
59
+ 'TE Premium': {
60
+ 'QB': .5,
61
+ 'RB': .75,
62
+ 'WR': .75,
63
+ 'TE': .5,
64
+ },
65
+ 'Half PPR': {
66
+ 'QB': .5,
67
+ 'RB': .75,
68
+ 'WR': .75,
69
+ 'TE': .5,
70
+ },
71
+ }
72
+
73
+ flex_multipliers = {
74
+ 'Half PPR': {
75
+ 'QB': 2,
76
+ 'RB': 2,
77
+ 'WR': 2,
78
+ 'TE': 2,
79
+ },
80
+ 'PPR': {
81
+ 'QB': 2,
82
+ 'RB': 2,
83
+ 'WR': 2,
84
+ 'TE': 2,
85
+ },
86
+ 'Standard': {
87
+ 'QB': 2,
88
+ 'RB': 2,
89
+ 'WR': 2,
90
+ 'TE': 2,
91
+ },
92
+ 'Superflex': {
93
+ 'QB': 4,
94
+ 'RB': 2,
95
+ 'WR': 2,
96
+ 'TE': 2,
97
+ },
98
+ 'TE Premium': {
99
+ 'QB': 2,
100
+ 'RB': 2,
101
+ 'WR': 2,
102
+ 'TE': 2,
103
+ },
104
+ }
105
+
106
+ base_settings = {
107
+ 'TEAMS': 12,
108
+ 'QB': 1,
109
+ 'RB': 2,
110
+ 'WR': 3,
111
+ 'TE': 1,
112
+ 'FLEX': 1,
113
+ 'BENCH': 6,
114
+ 'TYPE': 'Half PPR'
115
+ }
116
+
117
+ league_settings = {
118
+ 'TEAMS': 12,
119
+ 'QB': 1,
120
+ 'RB': 2,
121
+ 'WR': 3,
122
+ 'TE': 1,
123
+ 'FLEX': 2,
124
+ 'BENCH': 6,
125
+ 'TYPE': 'Superflex'
126
+ }
127
+
128
+ def create_user_config_interface():
129
+ """Create Streamlit interface for user configuration"""
130
+ st.sidebar.header("League Configuration")
131
+
132
+ # League Type Selection
133
+ league_type = st.sidebar.selectbox(
134
+ "League Type",
135
+ ['Half PPR', 'PPR', 'Standard', 'Superflex', 'TE Premium'],
136
+ index=0
137
+ )
138
+
139
+ # League Settings
140
+ st.sidebar.subheader("League Settings")
141
+ teams = st.sidebar.number_input("Number of Teams", min_value=8, max_value=16, value=12)
142
+ qb_starters = st.sidebar.number_input("QB Starters", min_value=1, max_value=2, value=1)
143
+ rb_starters = st.sidebar.number_input("RB Starters", min_value=1, max_value=3, value=2)
144
+ wr_starters = st.sidebar.number_input("WR Starters", min_value=1, max_value=4, value=3)
145
+ te_starters = st.sidebar.number_input("TE Starters", min_value=1, max_value=2, value=1)
146
+ flex_spots = st.sidebar.number_input("Flex Spots", min_value=0, max_value=3, value=1)
147
+ bench_spots = st.sidebar.number_input("Bench Spots", min_value=0, max_value=10, value=6)
148
+
149
+ # Update league settings based on user input
150
+ user_league_settings = {
151
+ 'TEAMS': teams,
152
+ 'QB': qb_starters,
153
+ 'RB': rb_starters,
154
+ 'WR': wr_starters,
155
+ 'TE': te_starters,
156
+ 'FLEX': flex_spots,
157
+ 'BENCH': bench_spots,
158
+ 'TYPE': league_type
159
+ }
160
+
161
+ # Flex Percentiles Configuration
162
+ st.sidebar.subheader("Flex Position Percentiles")
163
+ if league_type == 'Superflex':
164
+ qb_flex_pct = st.sidebar.slider("QB Flex %", 0.0, 1.0, 0.95, 0.01)
165
+ rb_flex_pct = st.sidebar.slider("RB Flex %", 0.0, 1.0, 0.02, 0.01)
166
+ wr_flex_pct = st.sidebar.slider("WR Flex %", 0.0, 1.0, 0.03, 0.01)
167
+ te_flex_pct = st.sidebar.slider("TE Flex %", 0.0, 1.0, 0.00, 0.01)
168
+
169
+ user_flex_percentiles = {
170
+ 'QB': qb_flex_pct,
171
+ 'RB': rb_flex_pct,
172
+ 'WR': wr_flex_pct,
173
+ 'TE': te_flex_pct,
174
+ }
175
+ else:
176
+ rb_flex_pct = st.sidebar.slider("RB Flex %", 0.0, 1.0, type_flex_percentiles[league_type]['RB'], 0.01)
177
+ wr_flex_pct = st.sidebar.slider("WR Flex %", 0.0, 1.0, type_flex_percentiles[league_type]['WR'], 0.01)
178
+ te_flex_pct = st.sidebar.slider("TE Flex %", 0.0, 1.0, type_flex_percentiles[league_type]['TE'], 0.01)
179
+
180
+ user_flex_percentiles = {
181
+ 'RB': rb_flex_pct,
182
+ 'WR': wr_flex_pct,
183
+ 'TE': te_flex_pct,
184
+ }
185
+
186
+ # Flex Multipliers Configuration
187
+ st.sidebar.subheader("Position Multipliers")
188
+ qb_mult = st.sidebar.number_input("QB Multiplier", min_value=1.0, max_value=5.0, value=float(flex_multipliers[league_type]['QB']), step=0.5)
189
+ rb_mult = st.sidebar.number_input("RB Multiplier", min_value=1.0, max_value=5.0, value=float(flex_multipliers[league_type]['RB']), step=0.5)
190
+ wr_mult = st.sidebar.number_input("WR Multiplier", min_value=1.0, max_value=5.0, value=float(flex_multipliers[league_type]['WR']), step=0.5)
191
+ te_mult = st.sidebar.number_input("TE Multiplier", min_value=1.0, max_value=5.0, value=float(flex_multipliers[league_type]['TE']), step=0.5)
192
+
193
+ user_flex_multipliers = {
194
+ 'QB': qb_mult,
195
+ 'RB': rb_mult,
196
+ 'WR': wr_mult,
197
+ 'TE': te_mult,
198
+ }
199
+
200
+ # VORP Limiters Configuration
201
+ st.sidebar.subheader("VORP Rank Adjustments")
202
+ qb_vorp_lim = st.sidebar.slider("QB VORP Limiter", 0.0, 1.0, pos_vorp_limiters[league_type]['QB'], 0.01)
203
+ rb_vorp_lim = st.sidebar.slider("RB VORP Limiter", 0.0, 1.0, pos_vorp_limiters[league_type]['RB'], 0.01)
204
+ wr_vorp_lim = st.sidebar.slider("WR VORP Limiter", 0.0, 1.0, pos_vorp_limiters[league_type]['WR'], 0.01)
205
+ te_vorp_lim = st.sidebar.slider("TE VORP Limiter", 0.0, 1.0, pos_vorp_limiters[league_type]['TE'], 0.01)
206
+
207
+ user_pos_vorp_limiters = {
208
+ 'QB': qb_vorp_lim,
209
+ 'RB': rb_vorp_lim,
210
+ 'WR': wr_vorp_lim,
211
+ 'TE': te_vorp_lim,
212
+ }
213
+
214
+ return user_league_settings, user_flex_percentiles, user_flex_multipliers, user_pos_vorp_limiters
215
+
216
+ def load_projections_data(api: str) -> pd.DataFrame:
217
+ calc_columns = ['Ru Yds', 'Ru TDs', 'Rec', 'Rec Yds', 'Rec TDs', 'P Yds', 'P TDs', 'INTs']
218
+ ppr_values = [.1, 6, 1, .1, 6, .04, 4, -1]
219
+ halfPpr_values = [.1, 6, .5, .1, 6, .04, 4, -1]
220
+ standard_values = [.1, 6, 0, .1, 6, .04, 4, -1]
221
+ init_data = requests.get(api)
222
+ proj_dataframe = pd.DataFrame(init_data.json())
223
+ for col in calc_columns:
224
+ proj_dataframe[col] = proj_dataframe[col].astype(float)
225
+ proj_dataframe['halfPpr'] = proj_dataframe[calc_columns].dot(halfPpr_values)
226
+ proj_dataframe['ppr'] = proj_dataframe[calc_columns].dot(ppr_values)
227
+ proj_dataframe['standard'] = proj_dataframe[calc_columns].dot(standard_values)
228
+
229
+ fpts_df = proj_dataframe[['Name', 'SR_ID', 'Pos', 'halfPpr', 'ppr', 'standard']]
230
+
231
+ return fpts_df
232
+
233
+ def load_ranks_data(api: str) -> pd.DataFrame:
234
+ init_data = requests.get(api)
235
+ ranks_dataframe = pd.DataFrame(init_data.json())
236
+
237
+ ranks_dict = dict(zip(ranks_dataframe['SR_ID'], ranks_dataframe['Rank']))
238
+ return ranks_dict
239
+
240
+ def create_position_frames(frame: pd.DataFrame, ranks: dict) -> pd.DataFrame:
241
+ qb_frame = frame[frame['Pos'] == 'QB'].sort_values(by='halfPpr', ascending=False)
242
+ rb_frame = frame[frame['Pos'] == 'RB'].sort_values(by='halfPpr', ascending=False)
243
+ wr_frame = frame[frame['Pos'] == 'WR'].sort_values(by='halfPpr', ascending=False)
244
+ te_frame = frame[frame['Pos'] == 'TE'].sort_values(by='halfPpr', ascending=False)
245
+
246
+ for slice in [qb_frame, rb_frame, wr_frame, te_frame]:
247
+ slice['Rank'] = slice['SR_ID'].map(ranks).replace(np.nan, 0).astype(int)
248
+ slice = slice[slice['Rank'] != 0]
249
+ slice = slice.sort_values(by='Rank', ascending=True)
250
+
251
+ overall_frame = pd.concat([qb_frame, rb_frame, wr_frame, te_frame]).reset_index(drop=True)
252
+
253
+ return overall_frame
254
+
255
+ def designate_custom_position_reqs(league_settings: dict, flex_percentiles: dict, flex_multipliers: dict) -> dict:
256
+ qb_base = league_settings['QB'] * league_settings['TEAMS']
257
+ rb_base = league_settings['RB'] * league_settings['TEAMS']
258
+ wr_base = league_settings['WR'] * league_settings['TEAMS']
259
+ te_base = league_settings['TE'] * league_settings['TEAMS']
260
+
261
+ qb_rv_index = math.ceil((qb_base) * flex_multipliers[league_settings['TYPE']]['QB'])
262
+ rb_rv_index = math.ceil((rb_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles[league_settings['TYPE']]['RB'])) * flex_multipliers[league_settings['TYPE']]['RB'])
263
+ wr_rv_index = math.ceil((wr_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles[league_settings['TYPE']]['WR'])) * flex_multipliers[league_settings['TYPE']]['WR'])
264
+ te_rv_index = math.ceil((te_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles[league_settings['TYPE']]['TE'])) * flex_multipliers[league_settings['TYPE']]['TE'])
265
+
266
+ print(f"Need {qb_rv_index} for QB in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}")
267
+ print(f"Need {rb_rv_index} for RB in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}")
268
+ print(f"Need {wr_rv_index} for WR in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}")
269
+ print(f"Need {te_rv_index} for TE in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}")
270
+
271
+ pos_reqs = {
272
+ 'QB': qb_rv_index,
273
+ 'RB': rb_rv_index,
274
+ 'WR': wr_rv_index,
275
+ 'TE': te_rv_index,
276
+ }
277
+
278
+ return pos_reqs
279
+
280
+ def designate_base_position_reqs(league_settings: dict, flex_percentiles: dict, flex_multipliers: dict) -> dict:
281
+ qb_base = league_settings['QB'] * league_settings['TEAMS']
282
+ rb_base = league_settings['RB'] * league_settings['TEAMS']
283
+ wr_base = league_settings['WR'] * league_settings['TEAMS']
284
+ te_base = league_settings['TE'] * league_settings['TEAMS']
285
+
286
+ qb_rv_index = math.ceil(qb_base * flex_multipliers[league_settings['TYPE']]['QB'])
287
+ rb_rv_index = math.ceil((rb_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles[league_settings['TYPE']]['RB'])) * flex_multipliers[league_settings['TYPE']]['RB'])
288
+ wr_rv_index = math.ceil((wr_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles[league_settings['TYPE']]['WR'])) * flex_multipliers[league_settings['TYPE']]['WR'])
289
+ te_rv_index = math.ceil((te_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles[league_settings['TYPE']]['TE'])) * flex_multipliers[league_settings['TYPE']]['TE'])
290
+
291
+ print(f"Need {qb_rv_index} for QB in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}")
292
+ print(f"Need {rb_rv_index} for RB in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}")
293
+ print(f"Need {wr_rv_index} for WR in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}")
294
+ print(f"Need {te_rv_index} for TE in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}")
295
+
296
+ pos_reqs = {
297
+ 'QB': qb_rv_index,
298
+ 'RB': rb_rv_index,
299
+ 'WR': wr_rv_index,
300
+ 'TE': te_rv_index,
301
+ }
302
+
303
+ return pos_reqs
304
+
305
+ def create_halfPpr_rv(frame: pd.DataFrame, pos_reqs: dict) -> dict:
306
+
307
+ rv_dict = {}
308
+
309
+ for positions in ['QB', 'RB', 'WR', 'TE']:
310
+ rv_dict[f'{positions}'] = frame[frame['Pos'] == positions].head(pos_reqs[positions]).reset_index(drop=True)['halfPpr'].tail(1).values[0]
311
+
312
+ return rv_dict
313
+
314
+ def create_custom_rv(frame: pd.DataFrame, pos_reqs: dict, league_settings: dict) -> dict:
315
+
316
+ if league_settings['TYPE'] == 'Half PPR':
317
+ rv_type = 'halfPpr'
318
+ elif league_settings['TYPE'] == 'PPR':
319
+ rv_type = 'ppr'
320
+ elif league_settings['TYPE'] == 'Standard':
321
+ rv_type = 'standard'
322
+ elif league_settings['TYPE'] == 'Superflex':
323
+ rv_type = 'halfPpr'
324
+
325
+ rv_dict = {}
326
+
327
+ for positions in ['QB', 'RB', 'WR', 'TE']:
328
+ rv_dict[f'{positions}'] = frame[frame['Pos'] == positions].head(pos_reqs[positions]).reset_index(drop=True)[rv_type].tail(1).values[0]
329
+
330
+ return rv_dict
331
+
332
+ def assign_vorp(frame: pd.DataFrame, halfPpr_rv: dict, custom_rv: dict, league_settings: dict, pos_vorp_limiters: dict) -> pd.DataFrame:
333
+ if league_settings['TYPE'] == 'Half PPR':
334
+ rv_type = 'halfPpr'
335
+ elif league_settings['TYPE'] == 'PPR':
336
+ rv_type = 'ppr'
337
+ elif league_settings['TYPE'] == 'Standard':
338
+ rv_type = 'standard'
339
+ elif league_settings['TYPE'] == 'Superflex':
340
+ rv_type = 'halfPpr'
341
+
342
+ vorp_frame = pd.DataFrame()
343
+ for positions in ['QB', 'RB', 'WR', 'TE']:
344
+ pos_frame = frame[frame['Pos'] == positions]
345
+ pos_frame = pos_frame[pos_frame['Rank'] != 0].reset_index(drop=True)
346
+ pos_frame = pos_frame.sort_values(by='Rank', ascending=True)
347
+
348
+ pos_frame['halfPpr_rv'] = halfPpr_rv[positions]
349
+ pos_frame['custom_rv'] = custom_rv[positions]
350
+ pos_frame['halfPpr_VORP'] = pos_frame['halfPpr'] - halfPpr_rv[positions]
351
+ pos_frame['custom_VORP'] = pos_frame[rv_type] - custom_rv[positions]
352
+
353
+ vorp_frame = pd.concat([vorp_frame, pos_frame]).reset_index(drop=True)
354
+
355
+ vorp_frame['halfPpr_vorp_rank'] = vorp_frame['halfPpr_VORP'].rank(method='max', ascending=False)
356
+ vorp_frame['custom_vorp_rank'] = vorp_frame['custom_VORP'].rank(method='max', ascending=False)
357
+ vorp_frame['vorp_diff'] = vorp_frame['halfPpr_vorp_rank'] - vorp_frame['custom_vorp_rank']
358
+ for positions in ['QB', 'RB', 'WR', 'TE']:
359
+ vorp_frame.loc[vorp_frame['Pos'] == positions, 'Rank_Adjust'] = (vorp_frame['Rank'] - (vorp_frame['vorp_diff'] * pos_vorp_limiters[league_settings['TYPE']][positions])).astype(float)
360
+ vorp_frame['custom_rank'] = vorp_frame['Rank_Adjust'].rank(method='first', ascending=True)
361
+
362
+ print(vorp_frame.sort_values(by='custom_vorp_rank', ascending=True).head(50))
363
+
364
+ return vorp_frame.sort_values(by='custom_rank', ascending=True)
365
+
366
+ def main():
367
+ st.title("Fantasy Football VORP Calculator")
368
+ st.write("Configure your league settings and analyze player values")
369
+
370
+ # Get user configuration
371
+ user_league_settings, user_flex_percentiles, user_flex_multipliers, user_pos_vorp_limiters = create_user_config_interface()
372
+
373
+ # Load data
374
+ try:
375
+ projections_df = load_projections_data(Dwain_proj)
376
+ ranks_dict = load_ranks_data(dwain_ranks)
377
+
378
+ # Create position frames
379
+ position_df = create_position_frames(projections_df, ranks_dict)
380
+
381
+ # Calculate position requirements
382
+ pos_reqs = designate_custom_position_reqs(user_league_settings, user_flex_percentiles, user_flex_multipliers)
383
+
384
+ # Calculate replacement values
385
+ halfPpr_rv = create_halfPpr_rv(position_df, pos_reqs)
386
+ custom_rv = create_custom_rv(position_df, pos_reqs, user_league_settings)
387
+
388
+ # Calculate VORP and rankings
389
+ final_df = assign_vorp(position_df, halfPpr_rv, custom_rv, user_league_settings, user_pos_vorp_limiters)
390
+
391
+ # Display results
392
+ st.header("Player Rankings")
393
+ st.dataframe(final_df[['Name', 'Pos', 'Rank', 'custom_rank', 'halfPpr', 'custom_VORP', 'halfPpr_VORP']].head(50))
394
+
395
+ # Position breakdown
396
+ st.header("Position Breakdown")
397
+ for pos in ['QB', 'RB', 'WR', 'TE']:
398
+ pos_df = final_df[final_df['Pos'] == pos].head(20)
399
+ st.subheader(f"Top {pos}s")
400
+ st.dataframe(pos_df[['Name', 'Rank', 'custom_rank', 'halfPpr', 'custom_VORP']])
401
+
402
+ except Exception as e:
403
+ st.error(f"Error loading data: {str(e)}")
404
+ st.info("Please check your internet connection and try again.")
405
+
406
+ if __name__ == "__main__":
407
+ main()
408
+