James McCool commited on
Commit
2b7d407
·
1 Parent(s): 69418e8

Add 'top_x_presence' calculation to predict_dupes.py for enhanced duplicate prediction accuracy. This new metric counts the presence of top ownership players in the portfolio, refining the dupes calculation by incorporating player ownership dynamics across various contest types.

Browse files
Files changed (1) hide show
  1. global_func/predict_dupes.py +34 -13
global_func/predict_dupes.py CHANGED
@@ -4,6 +4,7 @@ import pandas as pd
4
  import time
5
  import math
6
  from difflib import SequenceMatcher
 
7
 
8
  def calculate_weighted_ownership_vectorized(ownership_array):
9
  """
@@ -280,6 +281,7 @@ def predict_dupes_vectorized(portfolio, maps_dict, site_var, type_var, Contest_S
280
 
281
  # Keep the original function for backward compatibility
282
  def predict_dupes(portfolio, maps_dict, site_var, type_var, Contest_Size, strength_var, sport_var):
 
283
  if strength_var == 'Weak':
284
  dupes_multiplier = .75
285
  percentile_multiplier = .90
@@ -290,12 +292,13 @@ def predict_dupes(portfolio, maps_dict, site_var, type_var, Contest_Size, streng
290
  dupes_multiplier = 1.25
291
  percentile_multiplier = 1.10
292
  max_ownership = max(maps_dict['own_map'].values()) / 100
 
293
  average_ownership = np.mean(list(maps_dict['own_map'].values())) / 100
294
  if site_var == 'Fanduel':
295
  if type_var == 'Showdown':
296
  dup_count_columns = ['CPT_Own_percent_rank', 'FLEX1_Own_percent_rank', 'FLEX2_Own_percent_rank', 'FLEX3_Own_percent_rank', 'FLEX4_Own_percent_rank']
297
  own_columns = ['CPT_Own', 'FLEX1_Own', 'FLEX2_Own', 'FLEX3_Own', 'FLEX4_Own']
298
- calc_columns = ['own_product', 'own_average', 'own_sum', 'avg_own_rank', 'own_rank_percentile', 'dupes_calc', 'low_own_count', 'own_ratio', 'Ref_Proj', 'Max_Proj', 'Min_Proj', 'Avg_Ref', 'own_ratio']
299
  # Get the original player columns (first 5 columns excluding salary, median, Own)
300
  player_columns = [col for col in portfolio.columns[:5] if col not in ['salary', 'median', 'Own']]
301
 
@@ -327,10 +330,13 @@ def predict_dupes(portfolio, maps_dict, site_var, type_var, Contest_Size, streng
327
  portfolio['own_sum'] = portfolio[own_columns].sum(axis=1)
328
  portfolio['avg_own_rank'] = portfolio[dup_count_columns].mean(axis=1)
329
  portfolio['own_rank_percentile'] = portfolio['Own'].rank(pct=True)
 
 
 
330
 
331
  # Calculate dupes formula
332
  portfolio['dupes_calc'] = (portfolio['own_product'] * portfolio['avg_own_rank']) * (portfolio['Own'] / 100) * Contest_Size + ((portfolio['salary'] - (60000 - portfolio['Own'])) / 100) - ((60000 - portfolio['salary']) / 100)
333
- portfolio['dupes_calc'] = portfolio['dupes_calc'] * dupes_multiplier * (portfolio['Own'] / (100 + (Contest_Size / 1000)))
334
 
335
  # Round and handle negative values
336
  portfolio['Dupes'] = np.where(
@@ -343,7 +349,7 @@ def predict_dupes(portfolio, maps_dict, site_var, type_var, Contest_Size, streng
343
  num_players = len([col for col in portfolio.columns if col not in ['salary', 'median', 'Own']])
344
  dup_count_columns = [f'player_{i}_percent_rank' for i in range(1, num_players + 1)]
345
  own_columns = [f'player_{i}_own' for i in range(1, num_players + 1)]
346
- calc_columns = ['own_product', 'own_average', 'own_sum', 'avg_own_rank', 'own_rank_percentile', 'dupes_calc', 'low_own_count', 'own_ratio', 'Ref_Proj', 'Max_Proj', 'Min_Proj', 'Avg_Ref', 'own_ratio']
347
  # Get the original player columns (first num_players columns excluding salary, median, Own)
348
  player_columns = [col for col in portfolio.columns[:num_players] if col not in ['salary', 'median', 'Own']]
349
 
@@ -356,9 +362,12 @@ def predict_dupes(portfolio, maps_dict, site_var, type_var, Contest_Size, streng
356
  portfolio['own_sum'] = portfolio[own_columns].sum(axis=1)
357
  portfolio['avg_own_rank'] = portfolio[dup_count_columns].mean(axis=1)
358
  portfolio['own_rank_percentile'] = portfolio['Own'].rank(pct=True)
 
 
 
359
 
360
  portfolio['dupes_calc'] = (portfolio['own_product'] * portfolio['avg_own_rank']) * Contest_Size + ((portfolio['salary'] - (60000 - portfolio['Own'])) / 100) - ((60000 - portfolio['salary']) / 100)
361
- portfolio['dupes_calc'] = portfolio['dupes_calc'] * dupes_multiplier * (portfolio['Own'] / (100 + (Contest_Size / 1000)))
362
  # Round and handle negative values
363
  portfolio['Dupes'] = np.where(
364
  np.round(portfolio['dupes_calc'], 0) <= 0,
@@ -374,7 +383,7 @@ def predict_dupes(portfolio, maps_dict, site_var, type_var, Contest_Size, streng
374
  else:
375
  dup_count_columns = ['CPT_Own_percent_rank', 'FLEX1_Own_percent_rank', 'FLEX2_Own_percent_rank', 'FLEX3_Own_percent_rank', 'FLEX4_Own_percent_rank', 'FLEX5_Own_percent_rank']
376
  own_columns = ['CPT_Own', 'FLEX1_Own', 'FLEX2_Own', 'FLEX3_Own', 'FLEX4_Own', 'FLEX5_Own']
377
- calc_columns = ['own_product', 'own_average', 'own_sum', 'avg_own_rank', 'own_rank_percentile', 'dupes_calc', 'low_own_count', 'Ref_Proj', 'Max_Proj', 'Min_Proj', 'Avg_Ref', 'own_ratio']
378
  # Get the original player columns (first 6 columns excluding salary, median, Own)
379
  player_columns = [col for col in portfolio.columns[:6] if col not in ['salary', 'median', 'Own']]
380
  if sport_var == 'GOLF':
@@ -431,10 +440,13 @@ def predict_dupes(portfolio, maps_dict, site_var, type_var, Contest_Size, streng
431
  portfolio['own_sum'] = portfolio[own_columns].sum(axis=1)
432
  portfolio['avg_own_rank'] = portfolio[dup_count_columns].mean(axis=1)
433
  portfolio['own_rank_percentile'] = portfolio['Own'].rank(pct=True)
 
 
 
434
 
435
  # Calculate dupes formula
436
  portfolio['dupes_calc'] = (portfolio['own_product'] * portfolio['avg_own_rank']) * Contest_Size + ((portfolio['salary'] - (50000 - portfolio['Own'])) / 100) - ((50000 - portfolio['salary']) / 100)
437
- portfolio['dupes_calc'] = portfolio['dupes_calc'] * dupes_multiplier * (portfolio['Own'] / (100 + (Contest_Size / 1000)))
438
 
439
  # Round and handle negative values
440
  portfolio['Dupes'] = np.where(
@@ -446,7 +458,7 @@ def predict_dupes(portfolio, maps_dict, site_var, type_var, Contest_Size, streng
446
  if sport_var == 'CS2':
447
  dup_count_columns = ['CPT_Own_percent_rank', 'FLEX1_Own_percent_rank', 'FLEX2_Own_percent_rank', 'FLEX3_Own_percent_rank', 'FLEX4_Own_percent_rank', 'FLEX5_Own_percent_rank']
448
  own_columns = ['CPT_Own', 'FLEX1_Own', 'FLEX2_Own', 'FLEX3_Own', 'FLEX4_Own', 'FLEX5_Own']
449
- calc_columns = ['own_product', 'own_average', 'own_sum', 'avg_own_rank', 'own_rank_percentile', 'dupes_calc', 'low_own_count', 'Ref_Proj', 'Max_Proj', 'Min_Proj', 'Avg_Ref', 'own_ratio']
450
  # Get the original player columns (first 6 columns excluding salary, median, Own)
451
  player_columns = [col for col in portfolio.columns[:6] if col not in ['salary', 'median', 'Own']]
452
 
@@ -479,10 +491,13 @@ def predict_dupes(portfolio, maps_dict, site_var, type_var, Contest_Size, streng
479
  portfolio['own_sum'] = portfolio[own_columns].sum(axis=1)
480
  portfolio['avg_own_rank'] = portfolio[dup_count_columns].mean(axis=1)
481
  portfolio['own_rank_percentile'] = portfolio['Own'].rank(pct=True)
 
 
 
482
 
483
  # Calculate dupes formula
484
  portfolio['dupes_calc'] = ((portfolio['own_product'] * 10) * portfolio['avg_own_rank']) * Contest_Size + ((portfolio['salary'] - (50000 - portfolio['Own'])) / 50) - ((50000 - portfolio['salary']) / 50)
485
- portfolio['dupes_calc'] = portfolio['dupes_calc'] * dupes_multiplier * (portfolio['Own'] / (100 + (Contest_Size / 1000)))
486
 
487
  # Round and handle negative values
488
  portfolio['Dupes'] = np.where(
@@ -493,7 +508,7 @@ def predict_dupes(portfolio, maps_dict, site_var, type_var, Contest_Size, streng
493
  if sport_var == 'LOL':
494
  dup_count_columns = ['CPT_Own_percent_rank', 'TOP_Own_percent_rank', 'JNG_Own_percent_rank', 'MID_Own_percent_rank', 'ADC_Own_percent_rank', 'SUP_Own_percent_rank', 'Team_Own_percent_rank']
495
  own_columns = ['CPT_Own', 'TOP_Own', 'JNG_Own', 'MID_Own', 'ADC_Own', 'SUP_Own', 'Team_Own']
496
- calc_columns = ['own_product', 'own_average', 'own_sum', 'avg_own_rank', 'own_rank_percentile', 'dupes_calc', 'low_own_count', 'Ref_Proj', 'Max_Proj', 'Min_Proj', 'Avg_Ref', 'own_ratio']
497
  # Get the original player columns (first 6 columns excluding salary, median, Own)
498
  player_columns = [col for col in portfolio.columns[:7] if col not in ['salary', 'median', 'Own']]
499
 
@@ -529,10 +544,13 @@ def predict_dupes(portfolio, maps_dict, site_var, type_var, Contest_Size, streng
529
  portfolio['own_sum'] = portfolio[own_columns].sum(axis=1)
530
  portfolio['avg_own_rank'] = portfolio[dup_count_columns].mean(axis=1)
531
  portfolio['own_rank_percentile'] = portfolio['Own'].rank(pct=True)
532
-
 
 
 
533
  # Calculate dupes formula
534
  portfolio['dupes_calc'] = ((portfolio['own_product'] * 10) * portfolio['avg_own_rank']) * Contest_Size + ((portfolio['salary'] - (50000 - portfolio['Own'])) / 50) - ((50000 - portfolio['salary']) / 50)
535
- portfolio['dupes_calc'] = portfolio['dupes_calc'] * dupes_multiplier * (portfolio['Own'] / (100 + (Contest_Size / 1000)))
536
 
537
  # Round and handle negative values
538
  portfolio['Dupes'] = np.where(
@@ -544,7 +562,7 @@ def predict_dupes(portfolio, maps_dict, site_var, type_var, Contest_Size, streng
544
  num_players = len([col for col in portfolio.columns if col not in ['salary', 'median', 'Own']])
545
  dup_count_columns = [f'player_{i}_percent_rank' for i in range(1, num_players + 1)]
546
  own_columns = [f'player_{i}_own' for i in range(1, num_players + 1)]
547
- calc_columns = ['own_product', 'own_average', 'own_sum', 'avg_own_rank', 'own_rank_percentile', 'dupes_calc', 'low_own_count', 'Ref_Proj', 'Max_Proj', 'Min_Proj', 'Avg_Ref', 'own_ratio']
548
  # Get the original player columns (first num_players columns excluding salary, median, Own)
549
  player_columns = [col for col in portfolio.columns[:num_players] if col not in ['salary', 'median', 'Own']]
550
 
@@ -557,9 +575,12 @@ def predict_dupes(portfolio, maps_dict, site_var, type_var, Contest_Size, streng
557
  portfolio['own_sum'] = portfolio[own_columns].sum(axis=1)
558
  portfolio['avg_own_rank'] = portfolio[dup_count_columns].mean(axis=1)
559
  portfolio['own_rank_percentile'] = portfolio['Own'].rank(pct=True)
 
 
 
560
 
561
  portfolio['dupes_calc'] = (portfolio['own_product'] * portfolio['avg_own_rank']) * Contest_Size + ((portfolio['salary'] - (50000 - portfolio['Own'])) / 100) - ((50000 - portfolio['salary']) / 100)
562
- portfolio['dupes_calc'] = portfolio['dupes_calc'] * dupes_multiplier * (portfolio['Own'] / (100 + (Contest_Size / 1000)))
563
  # Round and handle negative values
564
  portfolio['Dupes'] = np.where(
565
  np.round(portfolio['dupes_calc'], 0) <= 0,
 
4
  import time
5
  import math
6
  from difflib import SequenceMatcher
7
+ import heapq
8
 
9
  def calculate_weighted_ownership_vectorized(ownership_array):
10
  """
 
281
 
282
  # Keep the original function for backward compatibility
283
  def predict_dupes(portfolio, maps_dict, site_var, type_var, Contest_Size, strength_var, sport_var):
284
+ player_columns = [col for col in portfolio.columns[:5] if col not in ['salary', 'median', 'Own']]
285
  if strength_var == 'Weak':
286
  dupes_multiplier = .75
287
  percentile_multiplier = .90
 
292
  dupes_multiplier = 1.25
293
  percentile_multiplier = 1.10
294
  max_ownership = max(maps_dict['own_map'].values()) / 100
295
+ top_x_ownership_keys = heapq.nlargest(len(player_columns), maps_dict['own_map'], key=maps_dict['own_map'].get)
296
  average_ownership = np.mean(list(maps_dict['own_map'].values())) / 100
297
  if site_var == 'Fanduel':
298
  if type_var == 'Showdown':
299
  dup_count_columns = ['CPT_Own_percent_rank', 'FLEX1_Own_percent_rank', 'FLEX2_Own_percent_rank', 'FLEX3_Own_percent_rank', 'FLEX4_Own_percent_rank']
300
  own_columns = ['CPT_Own', 'FLEX1_Own', 'FLEX2_Own', 'FLEX3_Own', 'FLEX4_Own']
301
+ calc_columns = ['own_product', 'own_average', 'own_sum', 'avg_own_rank', 'own_rank_percentile', 'dupes_calc', 'low_own_count', 'own_ratio', 'Ref_Proj', 'Max_Proj', 'Min_Proj', 'Avg_Ref', 'own_ratio', 'top_x_presence']
302
  # Get the original player columns (first 5 columns excluding salary, median, Own)
303
  player_columns = [col for col in portfolio.columns[:5] if col not in ['salary', 'median', 'Own']]
304
 
 
330
  portfolio['own_sum'] = portfolio[own_columns].sum(axis=1)
331
  portfolio['avg_own_rank'] = portfolio[dup_count_columns].mean(axis=1)
332
  portfolio['own_rank_percentile'] = portfolio['Own'].rank(pct=True)
333
+ portfolio['top_x_presence'] = portfolio[player_columns].apply(
334
+ lambda row: sum(1 for player in row if player in top_x_ownership_keys), axis=1
335
+ )
336
 
337
  # Calculate dupes formula
338
  portfolio['dupes_calc'] = (portfolio['own_product'] * portfolio['avg_own_rank']) * (portfolio['Own'] / 100) * Contest_Size + ((portfolio['salary'] - (60000 - portfolio['Own'])) / 100) - ((60000 - portfolio['salary']) / 100)
339
+ portfolio['dupes_calc'] = portfolio['dupes_calc'] * dupes_multiplier * (portfolio['Own'] / (100 + (Contest_Size / 1000))) * portfolio['top_x_presence']
340
 
341
  # Round and handle negative values
342
  portfolio['Dupes'] = np.where(
 
349
  num_players = len([col for col in portfolio.columns if col not in ['salary', 'median', 'Own']])
350
  dup_count_columns = [f'player_{i}_percent_rank' for i in range(1, num_players + 1)]
351
  own_columns = [f'player_{i}_own' for i in range(1, num_players + 1)]
352
+ calc_columns = ['own_product', 'own_average', 'own_sum', 'avg_own_rank', 'own_rank_percentile', 'dupes_calc', 'low_own_count', 'own_ratio', 'Ref_Proj', 'Max_Proj', 'Min_Proj', 'Avg_Ref', 'own_ratio', 'top_x_presence']
353
  # Get the original player columns (first num_players columns excluding salary, median, Own)
354
  player_columns = [col for col in portfolio.columns[:num_players] if col not in ['salary', 'median', 'Own']]
355
 
 
362
  portfolio['own_sum'] = portfolio[own_columns].sum(axis=1)
363
  portfolio['avg_own_rank'] = portfolio[dup_count_columns].mean(axis=1)
364
  portfolio['own_rank_percentile'] = portfolio['Own'].rank(pct=True)
365
+ portfolio['top_x_presence'] = portfolio[player_columns].apply(
366
+ lambda row: sum(1 for player in row if player in top_x_ownership_keys), axis=1
367
+ )
368
 
369
  portfolio['dupes_calc'] = (portfolio['own_product'] * portfolio['avg_own_rank']) * Contest_Size + ((portfolio['salary'] - (60000 - portfolio['Own'])) / 100) - ((60000 - portfolio['salary']) / 100)
370
+ portfolio['dupes_calc'] = portfolio['dupes_calc'] * dupes_multiplier * (portfolio['Own'] / (100 + (Contest_Size / 1000))) * portfolio['top_x_presence']
371
  # Round and handle negative values
372
  portfolio['Dupes'] = np.where(
373
  np.round(portfolio['dupes_calc'], 0) <= 0,
 
383
  else:
384
  dup_count_columns = ['CPT_Own_percent_rank', 'FLEX1_Own_percent_rank', 'FLEX2_Own_percent_rank', 'FLEX3_Own_percent_rank', 'FLEX4_Own_percent_rank', 'FLEX5_Own_percent_rank']
385
  own_columns = ['CPT_Own', 'FLEX1_Own', 'FLEX2_Own', 'FLEX3_Own', 'FLEX4_Own', 'FLEX5_Own']
386
+ calc_columns = ['own_product', 'own_average', 'own_sum', 'avg_own_rank', 'own_rank_percentile', 'dupes_calc', 'low_own_count', 'Ref_Proj', 'Max_Proj', 'Min_Proj', 'Avg_Ref', 'own_ratio', 'top_x_presence']
387
  # Get the original player columns (first 6 columns excluding salary, median, Own)
388
  player_columns = [col for col in portfolio.columns[:6] if col not in ['salary', 'median', 'Own']]
389
  if sport_var == 'GOLF':
 
440
  portfolio['own_sum'] = portfolio[own_columns].sum(axis=1)
441
  portfolio['avg_own_rank'] = portfolio[dup_count_columns].mean(axis=1)
442
  portfolio['own_rank_percentile'] = portfolio['Own'].rank(pct=True)
443
+ portfolio['top_x_presence'] = portfolio[player_columns].apply(
444
+ lambda row: sum(1 for player in row if player in top_x_ownership_keys), axis=1
445
+ )
446
 
447
  # Calculate dupes formula
448
  portfolio['dupes_calc'] = (portfolio['own_product'] * portfolio['avg_own_rank']) * Contest_Size + ((portfolio['salary'] - (50000 - portfolio['Own'])) / 100) - ((50000 - portfolio['salary']) / 100)
449
+ portfolio['dupes_calc'] = portfolio['dupes_calc'] * dupes_multiplier * (portfolio['Own'] / (100 + (Contest_Size / 1000))) * portfolio['top_x_presence']
450
 
451
  # Round and handle negative values
452
  portfolio['Dupes'] = np.where(
 
458
  if sport_var == 'CS2':
459
  dup_count_columns = ['CPT_Own_percent_rank', 'FLEX1_Own_percent_rank', 'FLEX2_Own_percent_rank', 'FLEX3_Own_percent_rank', 'FLEX4_Own_percent_rank', 'FLEX5_Own_percent_rank']
460
  own_columns = ['CPT_Own', 'FLEX1_Own', 'FLEX2_Own', 'FLEX3_Own', 'FLEX4_Own', 'FLEX5_Own']
461
+ calc_columns = ['own_product', 'own_average', 'own_sum', 'avg_own_rank', 'own_rank_percentile', 'dupes_calc', 'low_own_count', 'Ref_Proj', 'Max_Proj', 'Min_Proj', 'Avg_Ref', 'own_ratio', 'top_x_presence']
462
  # Get the original player columns (first 6 columns excluding salary, median, Own)
463
  player_columns = [col for col in portfolio.columns[:6] if col not in ['salary', 'median', 'Own']]
464
 
 
491
  portfolio['own_sum'] = portfolio[own_columns].sum(axis=1)
492
  portfolio['avg_own_rank'] = portfolio[dup_count_columns].mean(axis=1)
493
  portfolio['own_rank_percentile'] = portfolio['Own'].rank(pct=True)
494
+ portfolio['top_x_presence'] = portfolio[player_columns].apply(
495
+ lambda row: sum(1 for player in row if player in top_x_ownership_keys), axis=1
496
+ )
497
 
498
  # Calculate dupes formula
499
  portfolio['dupes_calc'] = ((portfolio['own_product'] * 10) * portfolio['avg_own_rank']) * Contest_Size + ((portfolio['salary'] - (50000 - portfolio['Own'])) / 50) - ((50000 - portfolio['salary']) / 50)
500
+ portfolio['dupes_calc'] = portfolio['dupes_calc'] * dupes_multiplier * (portfolio['Own'] / (100 + (Contest_Size / 1000))) * portfolio['top_x_presence']
501
 
502
  # Round and handle negative values
503
  portfolio['Dupes'] = np.where(
 
508
  if sport_var == 'LOL':
509
  dup_count_columns = ['CPT_Own_percent_rank', 'TOP_Own_percent_rank', 'JNG_Own_percent_rank', 'MID_Own_percent_rank', 'ADC_Own_percent_rank', 'SUP_Own_percent_rank', 'Team_Own_percent_rank']
510
  own_columns = ['CPT_Own', 'TOP_Own', 'JNG_Own', 'MID_Own', 'ADC_Own', 'SUP_Own', 'Team_Own']
511
+ calc_columns = ['own_product', 'own_average', 'own_sum', 'avg_own_rank', 'own_rank_percentile', 'dupes_calc', 'low_own_count', 'Ref_Proj', 'Max_Proj', 'Min_Proj', 'Avg_Ref', 'own_ratio', 'top_x_presence']
512
  # Get the original player columns (first 6 columns excluding salary, median, Own)
513
  player_columns = [col for col in portfolio.columns[:7] if col not in ['salary', 'median', 'Own']]
514
 
 
544
  portfolio['own_sum'] = portfolio[own_columns].sum(axis=1)
545
  portfolio['avg_own_rank'] = portfolio[dup_count_columns].mean(axis=1)
546
  portfolio['own_rank_percentile'] = portfolio['Own'].rank(pct=True)
547
+ portfolio['top_x_presence'] = portfolio[player_columns].apply(
548
+ lambda row: sum(1 for player in row if player in top_x_ownership_keys), axis=1
549
+ )
550
+
551
  # Calculate dupes formula
552
  portfolio['dupes_calc'] = ((portfolio['own_product'] * 10) * portfolio['avg_own_rank']) * Contest_Size + ((portfolio['salary'] - (50000 - portfolio['Own'])) / 50) - ((50000 - portfolio['salary']) / 50)
553
+ portfolio['dupes_calc'] = portfolio['dupes_calc'] * dupes_multiplier * (portfolio['Own'] / (100 + (Contest_Size / 1000))) * portfolio['top_x_presence']
554
 
555
  # Round and handle negative values
556
  portfolio['Dupes'] = np.where(
 
562
  num_players = len([col for col in portfolio.columns if col not in ['salary', 'median', 'Own']])
563
  dup_count_columns = [f'player_{i}_percent_rank' for i in range(1, num_players + 1)]
564
  own_columns = [f'player_{i}_own' for i in range(1, num_players + 1)]
565
+ calc_columns = ['own_product', 'own_average', 'own_sum', 'avg_own_rank', 'own_rank_percentile', 'dupes_calc', 'low_own_count', 'Ref_Proj', 'Max_Proj', 'Min_Proj', 'Avg_Ref', 'own_ratio', 'top_x_presence']
566
  # Get the original player columns (first num_players columns excluding salary, median, Own)
567
  player_columns = [col for col in portfolio.columns[:num_players] if col not in ['salary', 'median', 'Own']]
568
 
 
575
  portfolio['own_sum'] = portfolio[own_columns].sum(axis=1)
576
  portfolio['avg_own_rank'] = portfolio[dup_count_columns].mean(axis=1)
577
  portfolio['own_rank_percentile'] = portfolio['Own'].rank(pct=True)
578
+ portfolio['top_x_presence'] = portfolio[player_columns].apply(
579
+ lambda row: sum(1 for player in row if player in top_x_ownership_keys), axis=1
580
+ )
581
 
582
  portfolio['dupes_calc'] = (portfolio['own_product'] * portfolio['avg_own_rank']) * Contest_Size + ((portfolio['salary'] - (50000 - portfolio['Own'])) / 100) - ((50000 - portfolio['salary']) / 100)
583
+ portfolio['dupes_calc'] = portfolio['dupes_calc'] * dupes_multiplier * (portfolio['Own'] / (100 + (Contest_Size / 1000))) * portfolio['top_x_presence']
584
  # Round and handle negative values
585
  portfolio['Dupes'] = np.where(
586
  np.round(portfolio['dupes_calc'], 0) <= 0,