James McCool
commited on
Commit
·
2e3cd9d
1
Parent(s):
46a28f1
Add stratification functionality and enhance portfolio handling in app.py
Browse files- Introduced a new stratification_function to allow users to generate lineups based on target similarity scores, enhancing lineup optimization capabilities.
- Updated app.py to integrate the new stratification feature, including user interface elements for selecting sorting criteria and lineup targets.
- Improved portfolio handling by converting the portfolio to a parquet format for better performance and memory efficiency.
- Enhanced data processing by ensuring the correct mapping of stack and size information from the portfolio, improving the accuracy of lineup analysis.
- app.py +212 -199
- global_func/stratification_function.py +32 -0
app.py
CHANGED
@@ -4,6 +4,7 @@ import pandas as pd
|
|
4 |
from rapidfuzz import process
|
5 |
import random
|
6 |
from collections import Counter
|
|
|
7 |
|
8 |
## import global functions
|
9 |
from global_func.clean_player_name import clean_player_name
|
@@ -23,6 +24,7 @@ from global_func.hedging_preset import hedging_preset
|
|
23 |
from global_func.volatility_preset import volatility_preset
|
24 |
from global_func.reduce_volatility_preset import reduce_volatility_preset
|
25 |
from global_func.analyze_player_combos import analyze_player_combos
|
|
|
26 |
|
27 |
freq_format = {'Finish_percentile': '{:.2%}', 'Lineup Edge': '{:.2%}', 'Win%': '{:.2%}'}
|
28 |
stacking_sports = ['MLB', 'NHL', 'NFL']
|
@@ -128,6 +130,7 @@ with tab1:
|
|
128 |
else:
|
129 |
stack_dict = None
|
130 |
if st.session_state['portfolio'] is not None:
|
|
|
131 |
st.success('Portfolio file loaded successfully!')
|
132 |
st.session_state['portfolio'] = st.session_state['portfolio'].apply(lambda x: x.replace(player_wrong_names_mlb, player_right_names_mlb))
|
133 |
st.dataframe(st.session_state['portfolio'].head(10))
|
@@ -180,9 +183,10 @@ with tab1:
|
|
180 |
|
181 |
projections = projections.apply(lambda x: x.replace(player_wrong_names_mlb, player_right_names_mlb))
|
182 |
st.dataframe(projections.head(10))
|
183 |
-
|
184 |
if portfolio_file and projections_file:
|
185 |
if st.session_state['portfolio'] is not None and projections is not None:
|
|
|
186 |
st.subheader("Name Matching Analysis")
|
187 |
# Initialize projections_df in session state if it doesn't exist
|
188 |
# Get unique names from portfolio
|
@@ -281,15 +285,82 @@ with tab1:
|
|
281 |
).most_common(1)[0][1] if any(team_dict.get(player, '') for player in row[2:]) else 0,
|
282 |
axis=1
|
283 |
)
|
284 |
-
stack_dict = dict(zip(st.session_state['portfolio'].index, st.session_state['portfolio']['Stack']))
|
285 |
-
size_dict = dict(zip(st.session_state['portfolio'].index, st.session_state['portfolio']['Size']))
|
286 |
|
287 |
-
working_frame = st.session_state['portfolio'].copy()
|
288 |
try:
|
289 |
st.session_state['export_dict'] = dict(zip(st.session_state['csv_file']['Name'], st.session_state['csv_file']['Name + ID']))
|
290 |
except:
|
291 |
st.session_state['export_dict'] = dict(zip(st.session_state['csv_file']['Nickname'], st.session_state['csv_file']['Id']))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
292 |
st.session_state['origin_portfolio'] = st.session_state['portfolio'].copy()
|
|
|
|
|
|
|
|
|
293 |
|
294 |
# with tab2:
|
295 |
# if st.button('Clear data', key='reset2'):
|
@@ -804,7 +875,7 @@ with tab1:
|
|
804 |
# )
|
805 |
|
806 |
with tab2:
|
807 |
-
if '
|
808 |
with st.container():
|
809 |
col1, col2 = st.columns(2)
|
810 |
with col1:
|
@@ -828,70 +899,8 @@ with tab2:
|
|
828 |
|
829 |
if 'working_frame' not in st.session_state:
|
830 |
st.session_state['settings_base'] = True
|
831 |
-
st.session_state['working_frame'] = st.session_state['origin_portfolio']
|
832 |
-
|
833 |
-
if type_var == 'Classic':
|
834 |
-
if sport_var == 'CS2':
|
835 |
-
st.session_state['map_dict'] = {
|
836 |
-
'pos_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['position'])),
|
837 |
-
'team_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['team'])),
|
838 |
-
'salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'])),
|
839 |
-
'proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'])),
|
840 |
-
'own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'])),
|
841 |
-
'own_percent_rank':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'].rank(pct=True))),
|
842 |
-
'cpt_salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'] * 1.5)),
|
843 |
-
'cpt_proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'] * 1.5)),
|
844 |
-
'cpt_own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['captain ownership']))
|
845 |
-
}
|
846 |
-
elif sport_var != 'CS2':
|
847 |
-
st.session_state['map_dict'] = {
|
848 |
-
'pos_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['position'])),
|
849 |
-
'team_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['team'])),
|
850 |
-
'salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'])),
|
851 |
-
'proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'])),
|
852 |
-
'own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'])),
|
853 |
-
'own_percent_rank':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'].rank(pct=True))),
|
854 |
-
'cpt_salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'])),
|
855 |
-
'cpt_proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'] * 1.5)),
|
856 |
-
'cpt_own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['captain ownership']))
|
857 |
-
}
|
858 |
-
elif type_var == 'Showdown':
|
859 |
-
if sport_var == 'GOLF':
|
860 |
-
st.session_state['map_dict'] = {
|
861 |
-
'pos_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['position'])),
|
862 |
-
'team_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['team'])),
|
863 |
-
'salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'])),
|
864 |
-
'proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'])),
|
865 |
-
'own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'])),
|
866 |
-
'own_percent_rank':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'].rank(pct=True))),
|
867 |
-
'cpt_salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'])),
|
868 |
-
'cpt_proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'])),
|
869 |
-
'cpt_own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership']))
|
870 |
-
}
|
871 |
-
if sport_var != 'GOLF':
|
872 |
-
st.session_state['map_dict'] = {
|
873 |
-
'pos_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['position'])),
|
874 |
-
'team_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['team'])),
|
875 |
-
'salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'])),
|
876 |
-
'proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'])),
|
877 |
-
'own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'])),
|
878 |
-
'own_percent_rank':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'].rank(pct=True))),
|
879 |
-
'cpt_salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'] * 1.5)),
|
880 |
-
'cpt_proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'] * 1.5)),
|
881 |
-
'cpt_own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['captain ownership']))
|
882 |
-
}
|
883 |
-
elif site_var == 'Fanduel':
|
884 |
-
st.session_state['map_dict'] = {
|
885 |
-
'pos_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['position'])),
|
886 |
-
'team_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['team'])),
|
887 |
-
'salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'])),
|
888 |
-
'proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'])),
|
889 |
-
'own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'])),
|
890 |
-
'own_percent_rank':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'].rank(pct=True))),
|
891 |
-
'cpt_salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'])),
|
892 |
-
'cpt_proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'] * 1.5)),
|
893 |
-
'cpt_own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['captain ownership']))
|
894 |
-
}
|
895 |
if type_var == 'Classic':
|
896 |
if sport_var == 'CS2':
|
897 |
# Calculate salary (CPT uses cpt_salary_map, others use salary_map)
|
@@ -919,9 +928,9 @@ with tab2:
|
|
919 |
st.session_state['working_frame']['salary'] = st.session_state['working_frame'].apply(lambda row: sum(st.session_state['map_dict']['salary_map'].get(player, 0) for player in row), axis=1)
|
920 |
st.session_state['working_frame']['median'] = st.session_state['working_frame'].apply(lambda row: sum(st.session_state['map_dict']['proj_map'].get(player, 0) for player in row), axis=1)
|
921 |
st.session_state['working_frame']['Own'] = st.session_state['working_frame'].apply(lambda row: sum(st.session_state['map_dict']['own_map'].get(player, 0) for player in row), axis=1)
|
922 |
-
if stack_dict
|
923 |
-
st.session_state['working_frame']['Stack'] = st.session_state['working_frame'].index.map(stack_dict)
|
924 |
-
st.session_state['working_frame']['Size'] = st.session_state['working_frame'].index.map(size_dict)
|
925 |
elif type_var == 'Showdown':
|
926 |
# Calculate salary (CPT uses cpt_salary_map, others use salary_map)
|
927 |
st.session_state['working_frame']['salary'] = st.session_state['working_frame'].apply(
|
@@ -943,20 +952,14 @@ with tab2:
|
|
943 |
sum(st.session_state['map_dict']['own_map'].get(player, 0) for player in row.iloc[1:]),
|
944 |
axis=1
|
945 |
)
|
|
|
|
|
|
|
|
|
946 |
st.session_state['base_frame'] = predict_dupes(st.session_state['working_frame'], st.session_state['map_dict'], site_var, type_var, Contest_Size, strength_var, sport_var)
|
947 |
st.session_state['working_frame'] = st.session_state['base_frame'].copy()
|
948 |
# st.session_state['highest_owned_teams'] = st.session_state['projections_df'][~st.session_state['projections_df']['position'].isin(['P', 'SP'])].groupby('team')['ownership'].sum().sort_values(ascending=False).head(3).index.tolist()
|
949 |
# st.session_state['highest_owned_pitchers'] = st.session_state['projections_df'][st.session_state['projections_df']['position'].isin(['P', 'SP'])]['player_names'].sort_values(by='ownership', ascending=False).head(3).tolist()
|
950 |
-
if 'info_columns_dict' not in st.session_state:
|
951 |
-
st.session_state['info_columns_dict'] = {
|
952 |
-
'Dupes': st.session_state['working_frame']['Dupes'],
|
953 |
-
'Finish_percentile': st.session_state['working_frame']['Finish_percentile'],
|
954 |
-
'Win%': st.session_state['working_frame']['Win%'],
|
955 |
-
'Lineup Edge': st.session_state['working_frame']['Lineup Edge'],
|
956 |
-
'Weighted Own': st.session_state['working_frame']['Weighted Own'],
|
957 |
-
'Geomean': st.session_state['working_frame']['Geomean'],
|
958 |
-
'Diversity': st.session_state['working_frame']['Diversity']
|
959 |
-
}
|
960 |
|
961 |
if 'trimming_dict_maxes' not in st.session_state:
|
962 |
st.session_state['trimming_dict_maxes'] = {
|
@@ -987,10 +990,10 @@ with tab2:
|
|
987 |
min_lineup_edge = st.number_input("Min acceptable Lineup Edge?", value=-.5, min_value=-1.00, step=.001)
|
988 |
if sport_var in ['NFL', 'MLB', 'NHL']:
|
989 |
stack_include_toggle = st.selectbox("Include specific stacks?", options=['All Stacks', 'Specific Stacks'], index=0)
|
990 |
-
stack_selections = st.multiselect("If Specific Stacks, Which to include?", options=sorted(list(set(stack_dict.values()))), default=[])
|
991 |
|
992 |
stack_remove_toggle = st.selectbox("Remove specific stacks?", options=['No', 'Yes'], index=0)
|
993 |
-
stack_remove = st.multiselect("If Specific Stacks, Which to remove?", options=sorted(list(set(stack_dict.values()))), default=[])
|
994 |
|
995 |
submitted = st.form_submit_button("Submit")
|
996 |
|
@@ -1096,14 +1099,14 @@ with tab2:
|
|
1096 |
with min_sort:
|
1097 |
performance_threshold_low = st.number_input("Min", value=0.0, min_value=0.0, step=1.0, key='min_sort')
|
1098 |
with max_sort:
|
1099 |
-
performance_threshold_high = st.number_input("Max", value=st.session_state['trimming_dict_maxes'][performance_type], min_value=0.0, step=1.0, key='max_sort')
|
1100 |
|
1101 |
st.write("Trimming threshold range:")
|
1102 |
min_trim, max_trim = st.columns(2)
|
1103 |
with min_trim:
|
1104 |
own_threshold_low = st.number_input("Min", value=0.0, min_value=0.0, step=1.0, key='min_trim')
|
1105 |
with max_trim:
|
1106 |
-
own_threshold_high = st.number_input("Max", value=st.session_state['trimming_dict_maxes'][own_type], min_value=0.0, step=1.0, key='max_trim')
|
1107 |
|
1108 |
submitted = st.form_submit_button("Trim")
|
1109 |
if submitted:
|
@@ -1134,7 +1137,16 @@ with tab2:
|
|
1134 |
parsed_frame = reduce_volatility_preset(st.session_state['working_frame'], lineup_target, excluded_cols, sport_var)
|
1135 |
st.session_state['working_frame'] = parsed_frame.reset_index(drop=True)
|
1136 |
st.session_state['export_merge'] = st.session_state['working_frame'].copy()
|
1137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1138 |
with st.container():
|
1139 |
if 'export_base' not in st.session_state:
|
1140 |
st.session_state['export_base'] = pd.DataFrame(columns=st.session_state['working_frame'].columns)
|
@@ -1180,7 +1192,7 @@ with tab2:
|
|
1180 |
display_frame = st.session_state['export_base']
|
1181 |
|
1182 |
total_rows = len(display_frame)
|
1183 |
-
rows_per_page =
|
1184 |
total_pages = (total_rows + rows_per_page - 1) // rows_per_page # Ceiling division
|
1185 |
|
1186 |
# Initialize page number in session state if not exists
|
@@ -1243,49 +1255,14 @@ with tab2:
|
|
1243 |
)
|
1244 |
player_stats_col, stack_stats_col, combos_col = st.tabs(['Player Stats', 'Stack Stats', 'Combos'])
|
1245 |
with player_stats_col:
|
1246 |
-
|
1247 |
-
|
1248 |
-
|
1249 |
-
|
1250 |
-
|
1251 |
-
|
1252 |
-
else:
|
1253 |
-
if type_var == 'Showdown':
|
1254 |
-
for player in player_names:
|
1255 |
-
# Create mask for lineups where this player is Captain (first column)
|
1256 |
-
cpt_mask = display_frame[player_columns[0]] == player
|
1257 |
-
|
1258 |
-
if cpt_mask.any():
|
1259 |
-
player_stats.append({
|
1260 |
-
'Player': f"{player} (CPT)",
|
1261 |
-
'Lineup Count': cpt_mask.sum(),
|
1262 |
-
'Exposure': cpt_mask.sum() / len(display_frame),
|
1263 |
-
'Avg Median': display_frame[cpt_mask]['median'].mean(),
|
1264 |
-
'Avg Own': display_frame[cpt_mask]['Own'].mean(),
|
1265 |
-
'Avg Dupes': display_frame[cpt_mask]['Dupes'].mean(),
|
1266 |
-
'Avg Finish %': display_frame[cpt_mask]['Finish_percentile'].mean(),
|
1267 |
-
'Avg Lineup Edge': display_frame[cpt_mask]['Lineup Edge'].mean(),
|
1268 |
-
})
|
1269 |
-
|
1270 |
-
# Create mask for lineups where this player is FLEX (other columns)
|
1271 |
-
flex_mask = display_frame[player_columns[1:]].apply(
|
1272 |
-
lambda row: player in list(row), axis=1
|
1273 |
-
)
|
1274 |
-
|
1275 |
-
if flex_mask.any():
|
1276 |
-
player_stats.append({
|
1277 |
-
'Player': f"{player} (FLEX)",
|
1278 |
-
'Lineup Count': flex_mask.sum(),
|
1279 |
-
'Exposure': flex_mask.sum() / len(display_frame),
|
1280 |
-
'Avg Median': display_frame[flex_mask]['median'].mean(),
|
1281 |
-
'Avg Own': display_frame[flex_mask]['Own'].mean(),
|
1282 |
-
'Avg Dupes': display_frame[flex_mask]['Dupes'].mean(),
|
1283 |
-
'Avg Finish %': display_frame[flex_mask]['Finish_percentile'].mean(),
|
1284 |
-
'Avg Lineup Edge': display_frame[flex_mask]['Lineup Edge'].mean(),
|
1285 |
-
})
|
1286 |
else:
|
1287 |
-
if
|
1288 |
-
# Handle Captain positions
|
1289 |
for player in player_names:
|
1290 |
# Create mask for lineups where this player is Captain (first column)
|
1291 |
cpt_mask = display_frame[player_columns[0]] == player
|
@@ -1318,77 +1295,69 @@ with tab2:
|
|
1318 |
'Avg Finish %': display_frame[flex_mask]['Finish_percentile'].mean(),
|
1319 |
'Avg Lineup Edge': display_frame[flex_mask]['Lineup Edge'].mean(),
|
1320 |
})
|
1321 |
-
|
1322 |
-
|
1323 |
-
|
1324 |
-
|
1325 |
-
|
1326 |
-
|
1327 |
-
|
1328 |
-
|
1329 |
-
|
1330 |
-
|
1331 |
-
|
1332 |
-
|
1333 |
-
|
1334 |
-
|
1335 |
-
|
1336 |
-
|
1337 |
-
|
1338 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1339 |
|
1340 |
-
|
1341 |
-
player_summary = player_summary.sort_values('Lineup Count', ascending=False)
|
1342 |
-
st.session_state['player_summary'] = player_summary.copy()
|
1343 |
-
if 'origin_player_exposures' not in st.session_state:
|
1344 |
-
st.session_state['origin_player_exposures'] = player_summary.copy()
|
1345 |
-
|
1346 |
-
st.subheader("Player Summary")
|
1347 |
-
st.dataframe(
|
1348 |
-
st.session_state['player_summary'].style
|
1349 |
-
.background_gradient(axis=0).background_gradient(cmap='RdYlGn').background_gradient(cmap='RdYlGn_r', subset=['Avg Finish %', 'Avg Own', 'Avg Dupes'])
|
1350 |
-
.format({
|
1351 |
-
'Avg Median': '{:.2f}',
|
1352 |
-
'Avg Own': '{:.2f}',
|
1353 |
-
'Avg Dupes': '{:.2f}',
|
1354 |
-
'Avg Finish %': '{:.2%}',
|
1355 |
-
'Avg Lineup Edge': '{:.2%}',
|
1356 |
-
'Exposure': '{:.2%}'
|
1357 |
-
}),
|
1358 |
-
height=400,
|
1359 |
-
use_container_width=True
|
1360 |
-
)
|
1361 |
-
|
1362 |
-
with stack_stats_col:
|
1363 |
-
if 'Stack' in display_frame.columns:
|
1364 |
-
stack_stats = []
|
1365 |
-
stack_columns = [col for col in display_frame.columns if col.startswith('Stack')]
|
1366 |
-
|
1367 |
-
if st.session_state['settings_base'] and 'origin_stack_exposures' in st.session_state and display_frame_source == 'Portfolio':
|
1368 |
-
st.session_state['stack_summary'] = st.session_state['origin_stack_exposures']
|
1369 |
-
else:
|
1370 |
-
for stack in stack_dict.values():
|
1371 |
-
stack_mask = display_frame['Stack'] == stack
|
1372 |
-
if stack_mask.any():
|
1373 |
-
stack_stats.append({
|
1374 |
-
'Stack': stack,
|
1375 |
-
'Lineup Count': stack_mask.sum(),
|
1376 |
-
'Exposure': stack_mask.sum() / len(display_frame),
|
1377 |
-
'Avg Median': display_frame[stack_mask]['median'].mean(),
|
1378 |
-
'Avg Own': display_frame[stack_mask]['Own'].mean(),
|
1379 |
-
'Avg Dupes': display_frame[stack_mask]['Dupes'].mean(),
|
1380 |
-
'Avg Finish %': display_frame[stack_mask]['Finish_percentile'].mean(),
|
1381 |
-
'Avg Lineup Edge': display_frame[stack_mask]['Lineup Edge'].mean(),
|
1382 |
-
})
|
1383 |
-
stack_summary = pd.DataFrame(stack_stats)
|
1384 |
-
stack_summary = stack_summary.sort_values('Lineup Count', ascending=False).drop_duplicates()
|
1385 |
-
st.session_state['stack_summary'] = stack_summary.copy()
|
1386 |
-
if 'origin_stack_exposures' not in st.session_state:
|
1387 |
-
st.session_state['origin_stack_exposures'] = stack_summary.copy()
|
1388 |
-
|
1389 |
-
st.subheader("Stack Summary")
|
1390 |
st.dataframe(
|
1391 |
-
st.session_state['
|
1392 |
.background_gradient(axis=0).background_gradient(cmap='RdYlGn').background_gradient(cmap='RdYlGn_r', subset=['Avg Finish %', 'Avg Own', 'Avg Dupes'])
|
1393 |
.format({
|
1394 |
'Avg Median': '{:.2f}',
|
@@ -1401,6 +1370,50 @@ with tab2:
|
|
1401 |
height=400,
|
1402 |
use_container_width=True
|
1403 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1404 |
else:
|
1405 |
stack_summary = pd.DataFrame(columns=['Stack', 'Lineup Count', 'Avg Median', 'Avg Own', 'Avg Dupes', 'Avg Finish %', 'Avg Lineup Edge'])
|
1406 |
|
|
|
4 |
from rapidfuzz import process
|
5 |
import random
|
6 |
from collections import Counter
|
7 |
+
import io
|
8 |
|
9 |
## import global functions
|
10 |
from global_func.clean_player_name import clean_player_name
|
|
|
24 |
from global_func.volatility_preset import volatility_preset
|
25 |
from global_func.reduce_volatility_preset import reduce_volatility_preset
|
26 |
from global_func.analyze_player_combos import analyze_player_combos
|
27 |
+
from global_func.stratification_function import stratification_function
|
28 |
|
29 |
freq_format = {'Finish_percentile': '{:.2%}', 'Lineup Edge': '{:.2%}', 'Win%': '{:.2%}'}
|
30 |
stacking_sports = ['MLB', 'NHL', 'NFL']
|
|
|
130 |
else:
|
131 |
stack_dict = None
|
132 |
if st.session_state['portfolio'] is not None:
|
133 |
+
|
134 |
st.success('Portfolio file loaded successfully!')
|
135 |
st.session_state['portfolio'] = st.session_state['portfolio'].apply(lambda x: x.replace(player_wrong_names_mlb, player_right_names_mlb))
|
136 |
st.dataframe(st.session_state['portfolio'].head(10))
|
|
|
183 |
|
184 |
projections = projections.apply(lambda x: x.replace(player_wrong_names_mlb, player_right_names_mlb))
|
185 |
st.dataframe(projections.head(10))
|
186 |
+
|
187 |
if portfolio_file and projections_file:
|
188 |
if st.session_state['portfolio'] is not None and projections is not None:
|
189 |
+
|
190 |
st.subheader("Name Matching Analysis")
|
191 |
# Initialize projections_df in session state if it doesn't exist
|
192 |
# Get unique names from portfolio
|
|
|
285 |
).most_common(1)[0][1] if any(team_dict.get(player, '') for player in row[2:]) else 0,
|
286 |
axis=1
|
287 |
)
|
288 |
+
st.session_state['stack_dict'] = dict(zip(st.session_state['portfolio'].index, st.session_state['portfolio']['Stack']))
|
289 |
+
st.session_state['size_dict'] = dict(zip(st.session_state['portfolio'].index, st.session_state['portfolio']['Size']))
|
290 |
|
|
|
291 |
try:
|
292 |
st.session_state['export_dict'] = dict(zip(st.session_state['csv_file']['Name'], st.session_state['csv_file']['Name + ID']))
|
293 |
except:
|
294 |
st.session_state['export_dict'] = dict(zip(st.session_state['csv_file']['Nickname'], st.session_state['csv_file']['Id']))
|
295 |
+
if 'map_dict' not in st.session_state:
|
296 |
+
if site_var == 'Draftkings':
|
297 |
+
if type_var == 'Classic':
|
298 |
+
if sport_var == 'CS2':
|
299 |
+
st.session_state['map_dict'] = {
|
300 |
+
'pos_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['position'])),
|
301 |
+
'team_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['team'])),
|
302 |
+
'salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'])),
|
303 |
+
'proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'])),
|
304 |
+
'own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'])),
|
305 |
+
'own_percent_rank':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'].rank(pct=True))),
|
306 |
+
'cpt_salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'] * 1.5)),
|
307 |
+
'cpt_proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'] * 1.5)),
|
308 |
+
'cpt_own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['captain ownership']))
|
309 |
+
}
|
310 |
+
elif sport_var != 'CS2':
|
311 |
+
st.session_state['map_dict'] = {
|
312 |
+
'pos_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['position'])),
|
313 |
+
'team_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['team'])),
|
314 |
+
'salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'])),
|
315 |
+
'proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'])),
|
316 |
+
'own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'])),
|
317 |
+
'own_percent_rank':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'].rank(pct=True))),
|
318 |
+
'cpt_salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'])),
|
319 |
+
'cpt_proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'] * 1.5)),
|
320 |
+
'cpt_own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['captain ownership']))
|
321 |
+
}
|
322 |
+
elif type_var == 'Showdown':
|
323 |
+
if sport_var == 'GOLF':
|
324 |
+
st.session_state['map_dict'] = {
|
325 |
+
'pos_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['position'])),
|
326 |
+
'team_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['team'])),
|
327 |
+
'salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'])),
|
328 |
+
'proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'])),
|
329 |
+
'own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'])),
|
330 |
+
'own_percent_rank':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'].rank(pct=True))),
|
331 |
+
'cpt_salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'])),
|
332 |
+
'cpt_proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'])),
|
333 |
+
'cpt_own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership']))
|
334 |
+
}
|
335 |
+
if sport_var != 'GOLF':
|
336 |
+
st.session_state['map_dict'] = {
|
337 |
+
'pos_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['position'])),
|
338 |
+
'team_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['team'])),
|
339 |
+
'salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'])),
|
340 |
+
'proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'])),
|
341 |
+
'own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'])),
|
342 |
+
'own_percent_rank':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'].rank(pct=True))),
|
343 |
+
'cpt_salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'] * 1.5)),
|
344 |
+
'cpt_proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'] * 1.5)),
|
345 |
+
'cpt_own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['captain ownership']))
|
346 |
+
}
|
347 |
+
elif site_var == 'Fanduel':
|
348 |
+
st.session_state['map_dict'] = {
|
349 |
+
'pos_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['position'])),
|
350 |
+
'team_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['team'])),
|
351 |
+
'salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'])),
|
352 |
+
'proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'])),
|
353 |
+
'own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'])),
|
354 |
+
'own_percent_rank':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['ownership'].rank(pct=True))),
|
355 |
+
'cpt_salary_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['salary'])),
|
356 |
+
'cpt_proj_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['median'] * 1.5)),
|
357 |
+
'cpt_own_map':dict(zip(st.session_state['projections_df']['player_names'], st.session_state['projections_df']['captain ownership']))
|
358 |
+
}
|
359 |
st.session_state['origin_portfolio'] = st.session_state['portfolio'].copy()
|
360 |
+
buffer = io.BytesIO()
|
361 |
+
st.session_state['portfolio'].to_parquet(buffer, compression='snappy')
|
362 |
+
st.session_state['origin_portfolio'] = buffer.getvalue()
|
363 |
+
del st.session_state['portfolio']
|
364 |
|
365 |
# with tab2:
|
366 |
# if st.button('Clear data', key='reset2'):
|
|
|
875 |
# )
|
876 |
|
877 |
with tab2:
|
878 |
+
if 'origin_portfolio' in st.session_state and 'projections_df' in st.session_state:
|
879 |
with st.container():
|
880 |
col1, col2 = st.columns(2)
|
881 |
with col1:
|
|
|
899 |
|
900 |
if 'working_frame' not in st.session_state:
|
901 |
st.session_state['settings_base'] = True
|
902 |
+
st.session_state['working_frame'] = pd.read_parquet(io.BytesIO(st.session_state['origin_portfolio']))
|
903 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
904 |
if type_var == 'Classic':
|
905 |
if sport_var == 'CS2':
|
906 |
# Calculate salary (CPT uses cpt_salary_map, others use salary_map)
|
|
|
928 |
st.session_state['working_frame']['salary'] = st.session_state['working_frame'].apply(lambda row: sum(st.session_state['map_dict']['salary_map'].get(player, 0) for player in row), axis=1)
|
929 |
st.session_state['working_frame']['median'] = st.session_state['working_frame'].apply(lambda row: sum(st.session_state['map_dict']['proj_map'].get(player, 0) for player in row), axis=1)
|
930 |
st.session_state['working_frame']['Own'] = st.session_state['working_frame'].apply(lambda row: sum(st.session_state['map_dict']['own_map'].get(player, 0) for player in row), axis=1)
|
931 |
+
if 'stack_dict' in st.session_state:
|
932 |
+
st.session_state['working_frame']['Stack'] = st.session_state['working_frame'].index.map(st.session_state['stack_dict'])
|
933 |
+
st.session_state['working_frame']['Size'] = st.session_state['working_frame'].index.map(st.session_state['size_dict'])
|
934 |
elif type_var == 'Showdown':
|
935 |
# Calculate salary (CPT uses cpt_salary_map, others use salary_map)
|
936 |
st.session_state['working_frame']['salary'] = st.session_state['working_frame'].apply(
|
|
|
952 |
sum(st.session_state['map_dict']['own_map'].get(player, 0) for player in row.iloc[1:]),
|
953 |
axis=1
|
954 |
)
|
955 |
+
st.session_state['working_frame']['Own'] = st.session_state['working_frame']['Own'].astype('float32')
|
956 |
+
st.session_state['working_frame']['median'] = st.session_state['working_frame']['median'].astype('float32')
|
957 |
+
st.session_state['working_frame']['salary'] = st.session_state['working_frame']['salary'].astype('uint16')
|
958 |
+
|
959 |
st.session_state['base_frame'] = predict_dupes(st.session_state['working_frame'], st.session_state['map_dict'], site_var, type_var, Contest_Size, strength_var, sport_var)
|
960 |
st.session_state['working_frame'] = st.session_state['base_frame'].copy()
|
961 |
# st.session_state['highest_owned_teams'] = st.session_state['projections_df'][~st.session_state['projections_df']['position'].isin(['P', 'SP'])].groupby('team')['ownership'].sum().sort_values(ascending=False).head(3).index.tolist()
|
962 |
# st.session_state['highest_owned_pitchers'] = st.session_state['projections_df'][st.session_state['projections_df']['position'].isin(['P', 'SP'])]['player_names'].sort_values(by='ownership', ascending=False).head(3).tolist()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
963 |
|
964 |
if 'trimming_dict_maxes' not in st.session_state:
|
965 |
st.session_state['trimming_dict_maxes'] = {
|
|
|
990 |
min_lineup_edge = st.number_input("Min acceptable Lineup Edge?", value=-.5, min_value=-1.00, step=.001)
|
991 |
if sport_var in ['NFL', 'MLB', 'NHL']:
|
992 |
stack_include_toggle = st.selectbox("Include specific stacks?", options=['All Stacks', 'Specific Stacks'], index=0)
|
993 |
+
stack_selections = st.multiselect("If Specific Stacks, Which to include?", options=sorted(list(set(st.session_state['stack_dict'].values()))), default=[])
|
994 |
|
995 |
stack_remove_toggle = st.selectbox("Remove specific stacks?", options=['No', 'Yes'], index=0)
|
996 |
+
stack_remove = st.multiselect("If Specific Stacks, Which to remove?", options=sorted(list(set(st.session_state['stack_dict'].values()))), default=[])
|
997 |
|
998 |
submitted = st.form_submit_button("Submit")
|
999 |
|
|
|
1099 |
with min_sort:
|
1100 |
performance_threshold_low = st.number_input("Min", value=0.0, min_value=0.0, step=1.0, key='min_sort')
|
1101 |
with max_sort:
|
1102 |
+
performance_threshold_high = st.number_input("Max", value=float(st.session_state['trimming_dict_maxes'][performance_type]), min_value=0.0, step=1.0, key='max_sort')
|
1103 |
|
1104 |
st.write("Trimming threshold range:")
|
1105 |
min_trim, max_trim = st.columns(2)
|
1106 |
with min_trim:
|
1107 |
own_threshold_low = st.number_input("Min", value=0.0, min_value=0.0, step=1.0, key='min_trim')
|
1108 |
with max_trim:
|
1109 |
+
own_threshold_high = st.number_input("Max", value=float(st.session_state['trimming_dict_maxes'][own_type]), min_value=0.0, step=1.0, key='max_trim')
|
1110 |
|
1111 |
submitted = st.form_submit_button("Trim")
|
1112 |
if submitted:
|
|
|
1137 |
parsed_frame = reduce_volatility_preset(st.session_state['working_frame'], lineup_target, excluded_cols, sport_var)
|
1138 |
st.session_state['working_frame'] = parsed_frame.reset_index(drop=True)
|
1139 |
st.session_state['export_merge'] = st.session_state['working_frame'].copy()
|
1140 |
+
with st.expander('Stratify'):
|
1141 |
+
with st.form(key='Stratification'):
|
1142 |
+
sorting_choice = st.selectbox("Stat Choice", options=['median', 'Own', 'Weighted Own', 'Geomean', 'Lineup Edge', 'Finish_percentile', 'Diversity'], index=0)
|
1143 |
+
lineup_target = st.number_input("Lineups to produce", value=150, min_value=1, step=1)
|
1144 |
+
submitted = st.form_submit_button("Submit")
|
1145 |
+
if submitted:
|
1146 |
+
st.session_state['settings_base'] = False
|
1147 |
+
parsed_frame = stratification_function(st.session_state['working_frame'], lineup_target, excluded_cols, sport_var, sorting_choice)
|
1148 |
+
st.session_state['working_frame'] = parsed_frame.reset_index(drop=True)
|
1149 |
+
st.session_state['export_merge'] = st.session_state['working_frame'].copy()
|
1150 |
with st.container():
|
1151 |
if 'export_base' not in st.session_state:
|
1152 |
st.session_state['export_base'] = pd.DataFrame(columns=st.session_state['working_frame'].columns)
|
|
|
1192 |
display_frame = st.session_state['export_base']
|
1193 |
|
1194 |
total_rows = len(display_frame)
|
1195 |
+
rows_per_page = 100
|
1196 |
total_pages = (total_rows + rows_per_page - 1) // rows_per_page # Ceiling division
|
1197 |
|
1198 |
# Initialize page number in session state if not exists
|
|
|
1255 |
)
|
1256 |
player_stats_col, stack_stats_col, combos_col = st.tabs(['Player Stats', 'Stack Stats', 'Combos'])
|
1257 |
with player_stats_col:
|
1258 |
+
if st.button("Analyze Players", key='analyze_players'):
|
1259 |
+
player_stats = []
|
1260 |
+
player_columns = [col for col in display_frame.columns if col not in excluded_cols]
|
1261 |
+
|
1262 |
+
if st.session_state['settings_base'] and 'origin_player_exposures' in st.session_state and display_frame_source == 'Portfolio':
|
1263 |
+
st.session_state['player_summary'] = st.session_state['origin_player_exposures']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1264 |
else:
|
1265 |
+
if type_var == 'Showdown':
|
|
|
1266 |
for player in player_names:
|
1267 |
# Create mask for lineups where this player is Captain (first column)
|
1268 |
cpt_mask = display_frame[player_columns[0]] == player
|
|
|
1295 |
'Avg Finish %': display_frame[flex_mask]['Finish_percentile'].mean(),
|
1296 |
'Avg Lineup Edge': display_frame[flex_mask]['Lineup Edge'].mean(),
|
1297 |
})
|
1298 |
+
else:
|
1299 |
+
if sport_var == 'CS2':
|
1300 |
+
# Handle Captain positions
|
1301 |
+
for player in player_names:
|
1302 |
+
# Create mask for lineups where this player is Captain (first column)
|
1303 |
+
cpt_mask = display_frame[player_columns[0]] == player
|
1304 |
+
|
1305 |
+
if cpt_mask.any():
|
1306 |
+
player_stats.append({
|
1307 |
+
'Player': f"{player} (CPT)",
|
1308 |
+
'Lineup Count': cpt_mask.sum(),
|
1309 |
+
'Exposure': cpt_mask.sum() / len(display_frame),
|
1310 |
+
'Avg Median': display_frame[cpt_mask]['median'].mean(),
|
1311 |
+
'Avg Own': display_frame[cpt_mask]['Own'].mean(),
|
1312 |
+
'Avg Dupes': display_frame[cpt_mask]['Dupes'].mean(),
|
1313 |
+
'Avg Finish %': display_frame[cpt_mask]['Finish_percentile'].mean(),
|
1314 |
+
'Avg Lineup Edge': display_frame[cpt_mask]['Lineup Edge'].mean(),
|
1315 |
+
})
|
1316 |
+
|
1317 |
+
# Create mask for lineups where this player is FLEX (other columns)
|
1318 |
+
flex_mask = display_frame[player_columns[1:]].apply(
|
1319 |
+
lambda row: player in list(row), axis=1
|
1320 |
+
)
|
1321 |
+
|
1322 |
+
if flex_mask.any():
|
1323 |
+
player_stats.append({
|
1324 |
+
'Player': f"{player} (FLEX)",
|
1325 |
+
'Lineup Count': flex_mask.sum(),
|
1326 |
+
'Exposure': flex_mask.sum() / len(display_frame),
|
1327 |
+
'Avg Median': display_frame[flex_mask]['median'].mean(),
|
1328 |
+
'Avg Own': display_frame[flex_mask]['Own'].mean(),
|
1329 |
+
'Avg Dupes': display_frame[flex_mask]['Dupes'].mean(),
|
1330 |
+
'Avg Finish %': display_frame[flex_mask]['Finish_percentile'].mean(),
|
1331 |
+
'Avg Lineup Edge': display_frame[flex_mask]['Lineup Edge'].mean(),
|
1332 |
+
})
|
1333 |
+
elif sport_var != 'CS2':
|
1334 |
+
# Original Classic format processing
|
1335 |
+
for player in player_names:
|
1336 |
+
player_mask = display_frame[player_columns].apply(
|
1337 |
+
lambda row: player in list(row), axis=1
|
1338 |
+
)
|
1339 |
+
|
1340 |
+
if player_mask.any():
|
1341 |
+
player_stats.append({
|
1342 |
+
'Player': player,
|
1343 |
+
'Lineup Count': player_mask.sum(),
|
1344 |
+
'Exposure': player_mask.sum() / len(display_frame),
|
1345 |
+
'Avg Median': display_frame[player_mask]['median'].mean(),
|
1346 |
+
'Avg Own': display_frame[player_mask]['Own'].mean(),
|
1347 |
+
'Avg Dupes': display_frame[player_mask]['Dupes'].mean(),
|
1348 |
+
'Avg Finish %': display_frame[player_mask]['Finish_percentile'].mean(),
|
1349 |
+
'Avg Lineup Edge': display_frame[player_mask]['Lineup Edge'].mean(),
|
1350 |
+
})
|
1351 |
+
|
1352 |
+
player_summary = pd.DataFrame(player_stats)
|
1353 |
+
player_summary = player_summary.sort_values('Lineup Count', ascending=False)
|
1354 |
+
st.session_state['player_summary'] = player_summary.copy()
|
1355 |
+
if 'origin_player_exposures' not in st.session_state:
|
1356 |
+
st.session_state['origin_player_exposures'] = player_summary.copy()
|
1357 |
|
1358 |
+
st.subheader("Player Summary")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1359 |
st.dataframe(
|
1360 |
+
st.session_state['player_summary'].style
|
1361 |
.background_gradient(axis=0).background_gradient(cmap='RdYlGn').background_gradient(cmap='RdYlGn_r', subset=['Avg Finish %', 'Avg Own', 'Avg Dupes'])
|
1362 |
.format({
|
1363 |
'Avg Median': '{:.2f}',
|
|
|
1370 |
height=400,
|
1371 |
use_container_width=True
|
1372 |
)
|
1373 |
+
|
1374 |
+
with stack_stats_col:
|
1375 |
+
if 'Stack' in display_frame.columns:
|
1376 |
+
if st.button("Analyze Stacks", key='analyze_stacks'):
|
1377 |
+
stack_stats = []
|
1378 |
+
stack_columns = [col for col in display_frame.columns if col.startswith('Stack')]
|
1379 |
+
|
1380 |
+
if st.session_state['settings_base'] and 'origin_stack_exposures' in st.session_state and display_frame_source == 'Portfolio':
|
1381 |
+
st.session_state['stack_summary'] = st.session_state['origin_stack_exposures']
|
1382 |
+
else:
|
1383 |
+
for stack in st.session_state['stack_dict'].values():
|
1384 |
+
stack_mask = display_frame['Stack'] == stack
|
1385 |
+
if stack_mask.any():
|
1386 |
+
stack_stats.append({
|
1387 |
+
'Stack': stack,
|
1388 |
+
'Lineup Count': stack_mask.sum(),
|
1389 |
+
'Exposure': stack_mask.sum() / len(display_frame),
|
1390 |
+
'Avg Median': display_frame[stack_mask]['median'].mean(),
|
1391 |
+
'Avg Own': display_frame[stack_mask]['Own'].mean(),
|
1392 |
+
'Avg Dupes': display_frame[stack_mask]['Dupes'].mean(),
|
1393 |
+
'Avg Finish %': display_frame[stack_mask]['Finish_percentile'].mean(),
|
1394 |
+
'Avg Lineup Edge': display_frame[stack_mask]['Lineup Edge'].mean(),
|
1395 |
+
})
|
1396 |
+
stack_summary = pd.DataFrame(stack_stats)
|
1397 |
+
stack_summary = stack_summary.sort_values('Lineup Count', ascending=False).drop_duplicates()
|
1398 |
+
st.session_state['stack_summary'] = stack_summary.copy()
|
1399 |
+
if 'origin_stack_exposures' not in st.session_state:
|
1400 |
+
st.session_state['origin_stack_exposures'] = stack_summary.copy()
|
1401 |
+
|
1402 |
+
st.subheader("Stack Summary")
|
1403 |
+
st.dataframe(
|
1404 |
+
st.session_state['stack_summary'].style
|
1405 |
+
.background_gradient(axis=0).background_gradient(cmap='RdYlGn').background_gradient(cmap='RdYlGn_r', subset=['Avg Finish %', 'Avg Own', 'Avg Dupes'])
|
1406 |
+
.format({
|
1407 |
+
'Avg Median': '{:.2f}',
|
1408 |
+
'Avg Own': '{:.2f}',
|
1409 |
+
'Avg Dupes': '{:.2f}',
|
1410 |
+
'Avg Finish %': '{:.2%}',
|
1411 |
+
'Avg Lineup Edge': '{:.2%}',
|
1412 |
+
'Exposure': '{:.2%}'
|
1413 |
+
}),
|
1414 |
+
height=400,
|
1415 |
+
use_container_width=True
|
1416 |
+
)
|
1417 |
else:
|
1418 |
stack_summary = pd.DataFrame(columns=['Stack', 'Lineup Count', 'Avg Median', 'Avg Own', 'Avg Dupes', 'Avg Finish %', 'Avg Lineup Edge'])
|
1419 |
|
global_func/stratification_function.py
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import numpy as np
|
3 |
+
|
4 |
+
def stratification_function(portfolio: pd.DataFrame, lineup_target: int, exclude_cols: list, sport: str, sorting_choice: str):
|
5 |
+
excluded_cols = ['salary', 'median', 'Own', 'Finish_percentile', 'Dupes', 'Stack', 'Size', 'Win%', 'Lineup Edge', 'Weighted Own', 'Geomean', 'Diversity']
|
6 |
+
player_columns = [col for col in portfolio.columns if col not in excluded_cols]
|
7 |
+
|
8 |
+
concat_portfolio = portfolio.copy()
|
9 |
+
if sorting_choice == 'Finish_percentile':
|
10 |
+
concat_portfolio = concat_portfolio.sort_values(by=sorting_choice, ascending=True).reset_index(drop=True)
|
11 |
+
else:
|
12 |
+
concat_portfolio = concat_portfolio.sort_values(by=sorting_choice, ascending=False).reset_index(drop=True)
|
13 |
+
|
14 |
+
# Calculate target similarity scores for linear progression
|
15 |
+
similarity_floor = concat_portfolio[sorting_choice].min()
|
16 |
+
similarity_ceiling = concat_portfolio[sorting_choice].max()
|
17 |
+
|
18 |
+
# Create evenly spaced target similarity scores
|
19 |
+
target_similarities = np.linspace(similarity_floor, similarity_ceiling, lineup_target)
|
20 |
+
|
21 |
+
# Find the closest lineup to each target similarity score
|
22 |
+
selected_indices = []
|
23 |
+
for target_sim in target_similarities:
|
24 |
+
# Find the index of the closest similarity score
|
25 |
+
closest_idx = (concat_portfolio[sorting_choice] - target_sim).abs().idxmin()
|
26 |
+
if closest_idx not in selected_indices: # Avoid duplicates
|
27 |
+
selected_indices.append(closest_idx)
|
28 |
+
|
29 |
+
# Select the lineups
|
30 |
+
concat_portfolio = concat_portfolio.loc[selected_indices].reset_index(drop=True)
|
31 |
+
|
32 |
+
return concat_portfolio.sort_values(by=sorting_choice, ascending=False)
|