Spaces:
Sleeping
Sleeping
James McCool
Update scoring type for TE Premium league settings in Streamlit app to 'ppr' for improved consistency in player evaluations.
41967a3 | import pandas as pd | |
| import numpy as np | |
| import requests | |
| import math | |
| import streamlit as st | |
| st.set_page_config(layout="wide") | |
| Dwain_proj = 'https://sheetdb.io/api/v1/svino07zkd6j6?sheet=2025_NFL_Proj_Dist' | |
| dwain_ranks = 'https://sheetdb.io/api/v1/ax8b1ms11bbzt?sheet=Dwain_Season' | |
| # Default configuration dictionaries | |
| type_flex_percentiles = { | |
| 'Half PPR': { | |
| 'RB': .40, | |
| 'WR': .55, | |
| 'TE': .05, | |
| }, | |
| 'PPR': { | |
| 'RB': .40, | |
| 'WR': .55, | |
| 'TE': .05, | |
| }, | |
| 'Standard': { | |
| 'RB': .40, | |
| 'WR': .55, | |
| 'TE': .05, | |
| }, | |
| 'Superflex': { | |
| 'QB': .95, | |
| 'RB': .02, | |
| 'WR': .03, | |
| 'TE': .00, | |
| }, | |
| 'TE Premium': { | |
| 'RB': .35, | |
| 'WR': .50, | |
| 'TE': .15, | |
| } | |
| } | |
| pos_vorp_limiters = { | |
| 'PPR': { | |
| 'QB': .5, | |
| 'RB': .5, | |
| 'WR': .75, | |
| 'TE': .5, | |
| }, | |
| 'Standard': { | |
| 'QB': .5, | |
| 'RB': .75, | |
| 'WR': .5, | |
| 'TE': .5, | |
| }, | |
| 'Superflex': { | |
| 'QB': .75, | |
| 'RB': .5, | |
| 'WR': .5, | |
| 'TE': .5, | |
| }, | |
| 'TE Premium': { | |
| 'QB': .5, | |
| 'RB': .5, | |
| 'WR': .5, | |
| 'TE': .75, | |
| }, | |
| 'Half PPR': { | |
| 'QB': .5, | |
| 'RB': .75, | |
| 'WR': .75, | |
| 'TE': .5, | |
| }, | |
| } | |
| flex_multipliers = { | |
| 'Half PPR': { | |
| 'QB': 2, | |
| 'RB': 2, | |
| 'WR': 2, | |
| 'TE': 2, | |
| }, | |
| 'PPR': { | |
| 'QB': 2, | |
| 'RB': 2, | |
| 'WR': 2, | |
| 'TE': 2, | |
| }, | |
| 'Standard': { | |
| 'QB': 2, | |
| 'RB': 2, | |
| 'WR': 2, | |
| 'TE': 2, | |
| }, | |
| 'Superflex': { | |
| 'QB': 4, | |
| 'RB': 2, | |
| 'WR': 2, | |
| 'TE': 2, | |
| }, | |
| 'TE Premium': { | |
| 'QB': 2, | |
| 'RB': 2, | |
| 'WR': 2, | |
| 'TE': 4, | |
| }, | |
| } | |
| base_settings = { | |
| 'TEAMS': 12, | |
| 'QB': 1, | |
| 'RB': 2, | |
| 'WR': 3, | |
| 'TE': 1, | |
| 'FLEX': 1, | |
| 'BENCH': 6, | |
| 'TYPE': 'Half PPR' | |
| } | |
| league_settings = { | |
| 'TEAMS': 12, | |
| 'QB': 1, | |
| 'RB': 2, | |
| 'WR': 3, | |
| 'TE': 1, | |
| 'FLEX': 1, | |
| 'BENCH': 6, | |
| 'TYPE': 'PPR' | |
| } | |
| def create_user_config_interface(): | |
| """Create Streamlit interface for user configuration""" | |
| st.sidebar.header("League Configuration") | |
| # League Type Selection | |
| league_type = st.sidebar.selectbox( | |
| "League Type", | |
| ['Half PPR', 'PPR', 'Standard', 'Superflex', 'TE Premium'], | |
| index=0 | |
| ) | |
| # League Settings | |
| st.sidebar.subheader("League Settings") | |
| teams = st.sidebar.number_input("Number of Teams", min_value=8, max_value=16, value=12) | |
| qb_starters = st.sidebar.number_input("QB Starters", min_value=1, max_value=2, value=1) | |
| rb_starters = st.sidebar.number_input("RB Starters", min_value=1, max_value=3, value=2) | |
| wr_starters = st.sidebar.number_input("WR Starters", min_value=1, max_value=4, value=3) | |
| te_starters = st.sidebar.number_input("TE Starters", min_value=1, max_value=2, value=1) | |
| flex_spots = st.sidebar.number_input("Flex Spots", min_value=0, max_value=3, value=1) | |
| bench_spots = st.sidebar.number_input("Bench Spots", min_value=0, max_value=10, value=6) | |
| # Update league settings based on user input | |
| user_league_settings = { | |
| 'TEAMS': teams, | |
| 'QB': qb_starters, | |
| 'RB': rb_starters, | |
| 'WR': wr_starters, | |
| 'TE': te_starters, | |
| 'FLEX': flex_spots, | |
| 'BENCH': bench_spots, | |
| 'TYPE': league_type | |
| } | |
| # Flex Percentiles Configuration | |
| st.sidebar.subheader("Flex Position Percentiles") | |
| if league_type == 'Superflex': | |
| qb_flex_pct = st.sidebar.slider("QB Flex %", 0.0, 1.0, 0.95, 0.01) | |
| rb_flex_pct = st.sidebar.slider("RB Flex %", 0.0, 1.0, 0.02, 0.01) | |
| wr_flex_pct = st.sidebar.slider("WR Flex %", 0.0, 1.0, 0.03, 0.01) | |
| te_flex_pct = st.sidebar.slider("TE Flex %", 0.0, 1.0, 0.00, 0.01) | |
| user_flex_percentiles = { | |
| 'QB': qb_flex_pct, | |
| 'RB': rb_flex_pct, | |
| 'WR': wr_flex_pct, | |
| 'TE': te_flex_pct, | |
| } | |
| else: | |
| # Get default values for the selected league type | |
| default_rb = type_flex_percentiles.get(league_type, {}).get('RB', 0.4) | |
| default_wr = type_flex_percentiles.get(league_type, {}).get('WR', 0.55) | |
| default_te = type_flex_percentiles.get(league_type, {}).get('TE', 0.05) | |
| rb_flex_pct = st.sidebar.slider("RB Flex %", 0.0, 1.0, default_rb, 0.01) | |
| wr_flex_pct = st.sidebar.slider("WR Flex %", 0.0, 1.0, default_wr, 0.01) | |
| te_flex_pct = st.sidebar.slider("TE Flex %", 0.0, 1.0, default_te, 0.01) | |
| user_flex_percentiles = { | |
| 'RB': rb_flex_pct, | |
| 'WR': wr_flex_pct, | |
| 'TE': te_flex_pct, | |
| } | |
| # Flex Multipliers Configuration | |
| st.sidebar.subheader("Position Multipliers") | |
| default_qb_mult = flex_multipliers.get(league_type, {}).get('QB', 2.0) | |
| default_rb_mult = flex_multipliers.get(league_type, {}).get('RB', 2.0) | |
| default_wr_mult = flex_multipliers.get(league_type, {}).get('WR', 2.0) | |
| default_te_mult = flex_multipliers.get(league_type, {}).get('TE', 2.0) | |
| qb_mult = st.sidebar.number_input("QB Multiplier", min_value=1.0, max_value=5.0, value=float(default_qb_mult), step=0.5) | |
| rb_mult = st.sidebar.number_input("RB Multiplier", min_value=1.0, max_value=5.0, value=float(default_rb_mult), step=0.5) | |
| wr_mult = st.sidebar.number_input("WR Multiplier", min_value=1.0, max_value=5.0, value=float(default_wr_mult), step=0.5) | |
| te_mult = st.sidebar.number_input("TE Multiplier", min_value=1.0, max_value=5.0, value=float(default_te_mult), step=0.5) | |
| user_flex_multipliers = { | |
| 'QB': qb_mult, | |
| 'RB': rb_mult, | |
| 'WR': wr_mult, | |
| 'TE': te_mult, | |
| } | |
| # VORP Limiters Configuration | |
| st.sidebar.subheader("VORP Rank Adjustments") | |
| default_qb_vorp = pos_vorp_limiters.get(league_type, {}).get('QB', 0.5) | |
| default_rb_vorp = pos_vorp_limiters.get(league_type, {}).get('RB', 0.75) | |
| default_wr_vorp = pos_vorp_limiters.get(league_type, {}).get('WR', 0.75) | |
| default_te_vorp = pos_vorp_limiters.get(league_type, {}).get('TE', 0.5) | |
| qb_vorp_lim = st.sidebar.slider("QB VORP Limiter", 0.0, 1.0, default_qb_vorp, 0.01) | |
| rb_vorp_lim = st.sidebar.slider("RB VORP Limiter", 0.0, 1.0, default_rb_vorp, 0.01) | |
| wr_vorp_lim = st.sidebar.slider("WR VORP Limiter", 0.0, 1.0, default_wr_vorp, 0.01) | |
| te_vorp_lim = st.sidebar.slider("TE VORP Limiter", 0.0, 1.0, default_te_vorp, 0.01) | |
| user_pos_vorp_limiters = { | |
| 'QB': qb_vorp_lim, | |
| 'RB': rb_vorp_lim, | |
| 'WR': wr_vorp_lim, | |
| 'TE': te_vorp_lim, | |
| } | |
| return user_league_settings, user_flex_percentiles, user_flex_multipliers, user_pos_vorp_limiters | |
| def load_projections_data(api: str) -> pd.DataFrame: | |
| calc_columns = ['Ru Yds', 'Ru TDs', 'Rec', 'Rec Yds', 'Rec TDs', 'P Yds', 'P TDs', 'INTs'] | |
| ppr_values = [.1, 6, 1, .1, 6, .04, 4, -1] | |
| halfPpr_values = [.1, 6, .5, .1, 6, .04, 4, -1] | |
| standard_values = [.1, 6, 0, .1, 6, .04, 4, -1] | |
| init_data = requests.get(api) | |
| proj_dataframe = pd.DataFrame(init_data.json()) | |
| for col in calc_columns: | |
| proj_dataframe[col] = proj_dataframe[col].astype(float) | |
| proj_dataframe['halfPpr'] = proj_dataframe[calc_columns].dot(halfPpr_values) | |
| proj_dataframe['ppr'] = proj_dataframe[calc_columns].dot(ppr_values) | |
| proj_dataframe['standard'] = proj_dataframe[calc_columns].dot(standard_values) | |
| fpts_df = proj_dataframe[['Name', 'SR_ID', 'Pos', 'halfPpr', 'ppr', 'standard']] | |
| return fpts_df | |
| def load_ranks_data(api: str) -> pd.DataFrame: | |
| init_data = requests.get(api) | |
| ranks_dataframe = pd.DataFrame(init_data.json()) | |
| ranks_dict = dict(zip(ranks_dataframe['SR_ID'], ranks_dataframe['Rank'])) | |
| return ranks_dict | |
| def create_position_frames(frame: pd.DataFrame, ranks: dict) -> pd.DataFrame: | |
| qb_frame = frame[frame['Pos'] == 'QB'].sort_values(by='halfPpr', ascending=False) | |
| rb_frame = frame[frame['Pos'] == 'RB'].sort_values(by='halfPpr', ascending=False) | |
| wr_frame = frame[frame['Pos'] == 'WR'].sort_values(by='halfPpr', ascending=False) | |
| te_frame = frame[frame['Pos'] == 'TE'].sort_values(by='halfPpr', ascending=False) | |
| for slice in [qb_frame, rb_frame, wr_frame, te_frame]: | |
| slice['Rank'] = slice['SR_ID'].map(ranks).replace(np.nan, 0).astype(int) | |
| slice = slice[slice['Rank'] != 0] | |
| slice = slice.sort_values(by='Rank', ascending=True) | |
| overall_frame = pd.concat([qb_frame, rb_frame, wr_frame, te_frame]).reset_index(drop=True) | |
| return overall_frame | |
| def designate_custom_position_reqs(league_settings: dict, flex_percentiles: dict, flex_multipliers: dict) -> dict: | |
| qb_base = league_settings['QB'] * league_settings['TEAMS'] | |
| rb_base = league_settings['RB'] * league_settings['TEAMS'] | |
| wr_base = league_settings['WR'] * league_settings['TEAMS'] | |
| te_base = league_settings['TE'] * league_settings['TEAMS'] | |
| qb_rv_index = min(math.ceil((qb_base) * flex_multipliers['QB']), 48) | |
| rb_rv_index = min(math.ceil((rb_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles['RB'])) * flex_multipliers['RB']), 60) | |
| wr_rv_index = min(math.ceil((wr_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles['WR'])) * flex_multipliers['WR']), 90) | |
| te_rv_index = min(math.ceil((te_base + ((league_settings['TEAMS'] * league_settings['FLEX']) * flex_percentiles['TE'])) * flex_multipliers['TE']), 30) | |
| print(f"Need {qb_rv_index} for QB in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}") | |
| print(f"Need {rb_rv_index} for RB in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}") | |
| print(f"Need {wr_rv_index} for WR in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}") | |
| print(f"Need {te_rv_index} for TE in {league_settings['TEAMS']} teams with type {league_settings['TYPE']}") | |
| pos_reqs = { | |
| 'QB': qb_rv_index, | |
| 'RB': rb_rv_index, | |
| 'WR': wr_rv_index, | |
| 'TE': te_rv_index, | |
| } | |
| return pos_reqs | |
| def designate_base_position_reqs() -> dict: | |
| qb_base = 1 * 12 | |
| rb_base = 2 * 12 | |
| wr_base = 3 * 12 | |
| te_base = 1 * 12 | |
| qb_rv_index = min(math.ceil(qb_base * 2), 48) | |
| rb_rv_index = min(math.ceil((rb_base + ((12 * 1) * .40)) * 2), 60) | |
| wr_rv_index = min(math.ceil((wr_base + ((12 * 1) * .55)) * 2), 90) | |
| te_rv_index = min(math.ceil((te_base + ((12 * 1) * .05)) * 2), 30) | |
| print(f"Need {qb_rv_index} for QB in {12} teams with type {league_settings['TYPE']}") | |
| print(f"Need {rb_rv_index} for RB in {12} teams with type {league_settings['TYPE']}") | |
| print(f"Need {wr_rv_index} for WR in {12} teams with type {league_settings['TYPE']}") | |
| print(f"Need {te_rv_index} for TE in {12} teams with type {league_settings['TYPE']}") | |
| pos_reqs = { | |
| 'QB': qb_rv_index, | |
| 'RB': rb_rv_index, | |
| 'WR': wr_rv_index, | |
| 'TE': te_rv_index, | |
| } | |
| return pos_reqs | |
| def create_halfPpr_rv(frame: pd.DataFrame, pos_reqs: dict) -> dict: | |
| rv_dict = {} | |
| for positions in ['QB', 'RB', 'WR', 'TE']: | |
| rv_dict[f'{positions}'] = frame[frame['Pos'] == positions].head(pos_reqs[positions]).reset_index(drop=True)['halfPpr'].tail(1).values[0] | |
| return rv_dict | |
| def create_custom_rv(frame: pd.DataFrame, pos_reqs: dict, league_settings: dict) -> dict: | |
| if league_settings['TYPE'] == 'Half PPR': | |
| rv_type = 'halfPpr' | |
| elif league_settings['TYPE'] == 'PPR': | |
| rv_type = 'ppr' | |
| elif league_settings['TYPE'] == 'Standard': | |
| rv_type = 'standard' | |
| elif league_settings['TYPE'] == 'Superflex': | |
| rv_type = 'halfPpr' | |
| elif league_settings['TYPE'] == 'TE Premium': | |
| rv_type = 'ppr' | |
| rv_dict = {} | |
| for positions in ['QB', 'RB', 'WR', 'TE']: | |
| rv_dict[f'{positions}'] = frame[frame['Pos'] == positions].head(pos_reqs[positions]).reset_index(drop=True)[rv_type].tail(1).values[0] | |
| return rv_dict | |
| def assign_vorp_scoring(frame: pd.DataFrame, halfPpr_rv: dict, custom_rv: dict, league_settings: dict, pos_vorp_limiters: dict) -> pd.DataFrame: | |
| if league_settings['TYPE'] == 'Half PPR': | |
| rv_type = 'halfPpr' | |
| elif league_settings['TYPE'] == 'PPR': | |
| rv_type = 'ppr' | |
| elif league_settings['TYPE'] == 'Standard': | |
| rv_type = 'standard' | |
| elif league_settings['TYPE'] == 'Superflex': | |
| rv_type = 'halfPpr' | |
| elif league_settings['TYPE'] == 'TE Premium': | |
| rv_type = 'ppr' | |
| vorp_frame = pd.DataFrame() | |
| for positions in ['QB', 'RB', 'WR', 'TE']: | |
| pos_frame = frame[frame['Pos'] == positions] | |
| pos_frame = pos_frame[pos_frame['Rank'] != 0] | |
| pos_frame = pos_frame.sort_values(by='Rank', ascending=True).reset_index(drop=True) | |
| pos_frame['halfPpr_rv'] = halfPpr_rv[positions] | |
| pos_frame['custom_rv'] = custom_rv[positions] | |
| pos_frame['halfPpr_VORP'] = pos_frame['halfPpr'] - halfPpr_rv[positions] | |
| pos_frame['custom_VORP'] = pos_frame[rv_type] - custom_rv[positions] | |
| print(pos_frame[['Name', 'halfPpr', 'custom_rv', 'halfPpr_VORP', 'custom_VORP']].head(20)) | |
| vorp_frame = pd.concat([vorp_frame, pos_frame]).reset_index(drop=True) | |
| vorp_frame['halfPpr_vorp_rank'] = vorp_frame['halfPpr_VORP'].rank(method='max', ascending=False) | |
| vorp_frame['custom_vorp_rank'] = vorp_frame['custom_VORP'].rank(method='max', ascending=False) | |
| vorp_frame['vorp_diff'] = np.where(vorp_frame['halfPpr_VORP'] == vorp_frame['custom_VORP'], 0, vorp_frame['halfPpr_vorp_rank'] - vorp_frame['custom_vorp_rank']) | |
| for positions in ['QB', 'RB', 'WR', 'TE']: | |
| vorp_frame.loc[vorp_frame['Pos'] == positions, 'Rank_Adjust'] = (vorp_frame['Rank'] - (vorp_frame['vorp_diff'] * pos_vorp_limiters[positions])).astype(float) | |
| vorp_frame['custom_rank'] = vorp_frame['Rank_Adjust'].rank(method='first', ascending=True).astype(int) | |
| vorp_frame['pos_rank'] = vorp_frame.groupby('Pos')['custom_rank'].rank(method='first', ascending=True).astype(int) | |
| vorp_frame['pos_designation'] = vorp_frame['Pos'] + vorp_frame['pos_rank'].astype(str) | |
| pos_des_dict = dict(zip(vorp_frame['pos_designation'], vorp_frame['Name'])) | |
| orig_rank_dict = dict(zip(vorp_frame['Name'], vorp_frame['pos_designation'])) | |
| half_ppr_match_dict = dict(zip(vorp_frame['pos_designation'], vorp_frame['halfPpr'])) | |
| custom_match_dict = dict(zip(vorp_frame['pos_designation'], vorp_frame[rv_type])) | |
| for pos in ['QB', 'RB', 'WR', 'TE']: | |
| print(vorp_frame[vorp_frame['Pos'] == pos].head(20)) | |
| return pos_des_dict, orig_rank_dict, half_ppr_match_dict, custom_match_dict | |
| def assign_vorp_roster(frame: pd.DataFrame, halfPpr_rv: dict, custom_rv: dict, pos_vorp_limiters: dict, half_ppr_match_dict: dict, custom_match_dict: dict, orig_rank_dict: dict) -> pd.DataFrame: | |
| vorp_frame = pd.DataFrame() | |
| for positions in ['QB', 'RB', 'WR', 'TE']: | |
| pos_frame = frame[frame['Pos'] == positions] | |
| pos_frame = pos_frame[pos_frame['Rank'] != 0] | |
| pos_frame = pos_frame.sort_values(by='Rank', ascending=True).reset_index(drop=True) | |
| pos_frame['ranker_rank'] = pos_frame['Rank'].rank(method='first', ascending=True).astype(int) | |
| pos_frame['pos_rank_init'] = pos_frame['Pos'] + pos_frame['ranker_rank'].astype(str) | |
| pos_frame['scoring_rank'] = pos_frame['Name'].map(orig_rank_dict) | |
| pos_frame['halfPpr_lu'] = pos_frame['pos_rank_init'].map(half_ppr_match_dict) | |
| pos_frame['custom_lu'] = pos_frame['pos_rank_init'].map(custom_match_dict) | |
| pos_frame['halfPpr_rv'] = halfPpr_rv[positions] | |
| pos_frame['custom_rv'] = custom_rv[positions] | |
| pos_frame['halfPpr_VORP'] = pos_frame['halfPpr_lu'] - pos_frame['halfPpr_rv'] | |
| pos_frame['custom_VORP'] = pos_frame['custom_lu'] - pos_frame['custom_rv'] | |
| vorp_frame = pd.concat([vorp_frame, pos_frame]).reset_index(drop=True) | |
| vorp_frame['halfPpr_vorp_rank'] = vorp_frame['halfPpr_VORP'].rank(method='max', ascending=False) | |
| vorp_frame['custom_vorp_rank'] = vorp_frame['custom_VORP'].rank(method='max', ascending=False) | |
| vorp_frame['vorp_diff'] = np.where(vorp_frame['halfPpr_VORP'] == vorp_frame['custom_VORP'], 0, vorp_frame['halfPpr_vorp_rank'] - vorp_frame['custom_vorp_rank']) | |
| for positions in ['QB', 'RB', 'WR', 'TE']: | |
| vorp_frame.loc[vorp_frame['Pos'] == positions, 'Rank_Adjust'] = (vorp_frame['Rank'] - (vorp_frame['vorp_diff'] * pos_vorp_limiters[positions])).astype(float) | |
| vorp_frame['custom_rank'] = vorp_frame['Rank_Adjust'].rank(method='first', ascending=True).astype(int) | |
| vorp_frame['pos_rank'] = vorp_frame.groupby('Pos')['custom_rank'].rank(method='first', ascending=True).astype(int) | |
| vorp_frame['pos_des'] = vorp_frame['Pos'] + vorp_frame['pos_rank'].astype(str) | |
| return vorp_frame.sort_values(by='custom_rank', ascending=True) | |
| def main(): | |
| st.title("Fantasy Football VORP Calculator") | |
| st.write("Configure your league settings and analyze player values") | |
| # Get user configuration | |
| user_league_settings, user_flex_percentiles, user_flex_multipliers, user_pos_vorp_limiters = create_user_config_interface() | |
| projections_df = load_projections_data(Dwain_proj) | |
| ranks_dict = load_ranks_data(dwain_ranks) | |
| # Create position frames | |
| position_df = create_position_frames(projections_df, ranks_dict) | |
| # Calculate position requirements | |
| base_pos_reqs = designate_base_position_reqs() | |
| custom_pos_reqs = designate_custom_position_reqs(user_league_settings, user_flex_percentiles, user_flex_multipliers) | |
| # Calculate replacement values | |
| halfPpr_rv = create_halfPpr_rv(position_df, base_pos_reqs) | |
| custom_scoring_rv = create_custom_rv(position_df, base_pos_reqs, user_league_settings) | |
| custom_roster_rv = create_custom_rv(position_df, custom_pos_reqs, user_league_settings) | |
| # Calculate VORP and rankings | |
| pos_des_dict, orig_rank_dict, half_ppr_match_dict, custom_match_dict = assign_vorp_scoring(position_df, halfPpr_rv, custom_scoring_rv, user_league_settings, user_pos_vorp_limiters) | |
| final_df = assign_vorp_roster(position_df, halfPpr_rv, custom_roster_rv, user_pos_vorp_limiters, half_ppr_match_dict, custom_match_dict, orig_rank_dict) | |
| final_df = final_df.drop(columns=['SR_ID'], axis=1) | |
| final_df.insert(1, 'new_name', final_df['pos_des'].map(pos_des_dict)) | |
| final_df = final_df.drop(columns=['pos_rank', 'pos_des', 'pos_rank_init'], axis=1) | |
| # Display results | |
| st.header("Player Rankings") | |
| st.dataframe(final_df, use_container_width=True) | |
| # Position breakdown | |
| st.header("Position Breakdown") | |
| for pos in ['QB', 'RB', 'WR', 'TE']: | |
| pos_df = final_df[final_df['Pos'] == pos].head(50) | |
| st.subheader(f"Top {pos}s") | |
| st.dataframe(pos_df, use_container_width=True) | |
| if __name__ == "__main__": | |
| main() | |