Spaces:
Running
Running
James McCool
Update VORP position limiters in Streamlit app to increase scoring values for WR, RB, QB, and TE across all formats, enhancing consistency in player evaluations.
4ec0f31
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 = 'halfPpr' | |
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 = 'halfPpr' | |
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() | |