spforge 0.8.40__py3-none-any.whl → 0.8.42__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of spforge might be problematic. Click here for more details.
- spforge/data_structures.py +1 -0
- spforge/ratings/_player_rating.py +56 -16
- {spforge-0.8.40.dist-info → spforge-0.8.42.dist-info}/METADATA +1 -1
- {spforge-0.8.40.dist-info → spforge-0.8.42.dist-info}/RECORD +10 -10
- tests/end_to_end/test_nba_player_ratings_hyperparameter_tuning.py +0 -1
- tests/hyperparameter_tuning/test_rating_tuner.py +4 -4
- tests/ratings/test_player_rating_generator.py +179 -54
- {spforge-0.8.40.dist-info → spforge-0.8.42.dist-info}/WHEEL +0 -0
- {spforge-0.8.40.dist-info → spforge-0.8.42.dist-info}/licenses/LICENSE +0 -0
- {spforge-0.8.40.dist-info → spforge-0.8.42.dist-info}/top_level.txt +0 -0
spforge/data_structures.py
CHANGED
|
@@ -445,7 +445,6 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
445
445
|
return self._remove_internal_scaled_columns(result)
|
|
446
446
|
|
|
447
447
|
def _future_transform(self, df: pl.DataFrame) -> pl.DataFrame:
|
|
448
|
-
self._validate_playing_time_columns(df)
|
|
449
448
|
df = self._scale_participation_weight_columns(df)
|
|
450
449
|
match_df = self._create_match_df(df)
|
|
451
450
|
ratings = self._calculate_future_ratings(match_df)
|
|
@@ -554,7 +553,7 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
554
553
|
player_rating=pre_player,
|
|
555
554
|
opponent_team_rating=PreMatchTeamRating(
|
|
556
555
|
id=team2,
|
|
557
|
-
players=c2.pre_match_player_ratings,
|
|
556
|
+
players=c2.pre_match_def_player_ratings or c2.pre_match_player_ratings,
|
|
558
557
|
rating_value=team2_def_rating,
|
|
559
558
|
),
|
|
560
559
|
team_rating=PreMatchTeamRating(
|
|
@@ -581,7 +580,7 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
581
580
|
),
|
|
582
581
|
team_rating=PreMatchTeamRating(
|
|
583
582
|
id=team1,
|
|
584
|
-
players=c1.pre_match_player_ratings,
|
|
583
|
+
players=c1.pre_match_def_player_ratings or c1.pre_match_player_ratings,
|
|
585
584
|
rating_value=team1_def_rating,
|
|
586
585
|
),
|
|
587
586
|
)
|
|
@@ -649,7 +648,7 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
649
648
|
player_rating=pre_player,
|
|
650
649
|
opponent_team_rating=PreMatchTeamRating(
|
|
651
650
|
id=team1,
|
|
652
|
-
players=c1.pre_match_player_ratings,
|
|
651
|
+
players=c1.pre_match_def_player_ratings or c1.pre_match_player_ratings,
|
|
653
652
|
rating_value=team1_def_rating,
|
|
654
653
|
),
|
|
655
654
|
team_rating=PreMatchTeamRating(
|
|
@@ -676,7 +675,7 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
676
675
|
),
|
|
677
676
|
team_rating=PreMatchTeamRating(
|
|
678
677
|
id=team2,
|
|
679
|
-
players=c2.pre_match_player_ratings,
|
|
678
|
+
players=c2.pre_match_def_player_ratings or c2.pre_match_player_ratings,
|
|
680
679
|
rating_value=team2_def_rating,
|
|
681
680
|
),
|
|
682
681
|
)
|
|
@@ -1094,6 +1093,7 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
1094
1093
|
cn = self.column_names
|
|
1095
1094
|
|
|
1096
1095
|
pre_match_player_ratings: list[PreMatchPlayerRating] = []
|
|
1096
|
+
pre_match_def_player_ratings: list[PreMatchPlayerRating] = []
|
|
1097
1097
|
new_players: list[MatchPlayer] = []
|
|
1098
1098
|
player_ids: list[str] = []
|
|
1099
1099
|
player_off_rating_values: list[float] = []
|
|
@@ -1175,6 +1175,7 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
1175
1175
|
|
|
1176
1176
|
if player_id in self._player_off_ratings and player_id in self._player_def_ratings:
|
|
1177
1177
|
off_state = self._player_off_ratings[player_id]
|
|
1178
|
+
def_state = self._player_def_ratings[player_id]
|
|
1178
1179
|
pre = PreMatchPlayerRating(
|
|
1179
1180
|
id=player_id,
|
|
1180
1181
|
rating_value=off_state.rating_value,
|
|
@@ -1185,6 +1186,17 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
1185
1186
|
)
|
|
1186
1187
|
pre_match_player_ratings.append(pre)
|
|
1187
1188
|
player_off_rating_values.append(float(off_state.rating_value))
|
|
1189
|
+
|
|
1190
|
+
# Also create DEF player rating for use in opponent predictions
|
|
1191
|
+
pre_def = PreMatchPlayerRating(
|
|
1192
|
+
id=player_id,
|
|
1193
|
+
rating_value=def_state.rating_value,
|
|
1194
|
+
match_performance=mp,
|
|
1195
|
+
games_played=def_state.games_played,
|
|
1196
|
+
league=player_league,
|
|
1197
|
+
position=position,
|
|
1198
|
+
)
|
|
1199
|
+
pre_match_def_player_ratings.append(pre_def)
|
|
1188
1200
|
else:
|
|
1189
1201
|
# unseen player -> create start rating (OFF + DEF)
|
|
1190
1202
|
new_players.append(
|
|
@@ -1197,12 +1209,13 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
1197
1209
|
)
|
|
1198
1210
|
|
|
1199
1211
|
if new_players:
|
|
1200
|
-
new_pre, new_vals = self._generate_new_player_pre_match_ratings(
|
|
1212
|
+
new_pre, new_def_pre, new_vals = self._generate_new_player_pre_match_ratings(
|
|
1201
1213
|
day_number=day_number,
|
|
1202
1214
|
new_players=new_players,
|
|
1203
1215
|
team_pre_match_player_ratings=pre_match_player_ratings,
|
|
1204
1216
|
)
|
|
1205
1217
|
pre_match_player_ratings.extend(new_pre)
|
|
1218
|
+
pre_match_def_player_ratings.extend(new_def_pre)
|
|
1206
1219
|
player_off_rating_values.extend(new_vals)
|
|
1207
1220
|
|
|
1208
1221
|
return PreMatchPlayersCollection(
|
|
@@ -1211,6 +1224,7 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
1211
1224
|
player_ids=player_ids,
|
|
1212
1225
|
player_rating_values=player_off_rating_values, # OFF values
|
|
1213
1226
|
projected_particiation_weights=projected_participation_weights,
|
|
1227
|
+
pre_match_def_player_ratings=pre_match_def_player_ratings,
|
|
1214
1228
|
)
|
|
1215
1229
|
|
|
1216
1230
|
def _generate_new_player_pre_match_ratings(
|
|
@@ -1218,11 +1232,13 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
1218
1232
|
day_number: int,
|
|
1219
1233
|
new_players: list[MatchPlayer],
|
|
1220
1234
|
team_pre_match_player_ratings: list[PreMatchPlayerRating],
|
|
1221
|
-
) -> tuple[list[PreMatchPlayerRating], list[float]]:
|
|
1235
|
+
) -> tuple[list[PreMatchPlayerRating], list[PreMatchPlayerRating], list[float]]:
|
|
1222
1236
|
"""
|
|
1223
1237
|
Creates BOTH off+def states for new players using the same start rating.
|
|
1238
|
+
Returns (off_ratings, def_ratings, off_values).
|
|
1224
1239
|
"""
|
|
1225
1240
|
pre_match_player_ratings: list[PreMatchPlayerRating] = []
|
|
1241
|
+
pre_match_def_player_ratings: list[PreMatchPlayerRating] = []
|
|
1226
1242
|
pre_match_player_off_values: list[float] = []
|
|
1227
1243
|
|
|
1228
1244
|
for match_player in new_players:
|
|
@@ -1250,7 +1266,19 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
1250
1266
|
)
|
|
1251
1267
|
pre_match_player_ratings.append(pre)
|
|
1252
1268
|
|
|
1253
|
-
|
|
1269
|
+
# For new players, DEF rating starts same as OFF rating
|
|
1270
|
+
pre_def = PreMatchPlayerRating(
|
|
1271
|
+
id=pid,
|
|
1272
|
+
rating_value=float(start_val), # DEF rating (same as OFF for new players)
|
|
1273
|
+
match_performance=match_player.performance,
|
|
1274
|
+
games_played=self._player_def_ratings[pid].games_played,
|
|
1275
|
+
league=match_player.league,
|
|
1276
|
+
position=match_player.position,
|
|
1277
|
+
other=match_player.others,
|
|
1278
|
+
)
|
|
1279
|
+
pre_match_def_player_ratings.append(pre_def)
|
|
1280
|
+
|
|
1281
|
+
return pre_match_player_ratings, pre_match_def_player_ratings, pre_match_player_off_values
|
|
1254
1282
|
|
|
1255
1283
|
def _team_off_perf_from_collection(
|
|
1256
1284
|
self, c: PreMatchPlayersCollection
|
|
@@ -1378,8 +1406,9 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
1378
1406
|
|
|
1379
1407
|
def build_local_team(
|
|
1380
1408
|
stats_col: str,
|
|
1381
|
-
) -> tuple[list[PreMatchPlayerRating], list[str], list[float], list[float], float]:
|
|
1409
|
+
) -> tuple[list[PreMatchPlayerRating], list[PreMatchPlayerRating], list[str], list[float], list[float], float]:
|
|
1382
1410
|
pre_list: list[PreMatchPlayerRating] = []
|
|
1411
|
+
def_pre_list: list[PreMatchPlayerRating] = []
|
|
1383
1412
|
player_ids: list[str] = []
|
|
1384
1413
|
proj_w: list[float] = []
|
|
1385
1414
|
off_vals: list[float] = []
|
|
@@ -1454,6 +1483,17 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
1454
1483
|
position=position,
|
|
1455
1484
|
)
|
|
1456
1485
|
)
|
|
1486
|
+
# Also build DEF player ratings for opponent weighting
|
|
1487
|
+
def_pre_list.append(
|
|
1488
|
+
PreMatchPlayerRating(
|
|
1489
|
+
id=pid,
|
|
1490
|
+
rating_value=float(local_def[pid].rating_value),
|
|
1491
|
+
match_performance=mp,
|
|
1492
|
+
games_played=float(local_def[pid].games_played),
|
|
1493
|
+
league=league,
|
|
1494
|
+
position=position,
|
|
1495
|
+
)
|
|
1496
|
+
)
|
|
1457
1497
|
off_vals.append(float(local_off[pid].rating_value))
|
|
1458
1498
|
|
|
1459
1499
|
if mp.performance_value is not None:
|
|
@@ -1461,10 +1501,10 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
1461
1501
|
wsum += float(pw)
|
|
1462
1502
|
|
|
1463
1503
|
team_off_perf = psum / wsum if wsum else 0.0
|
|
1464
|
-
return pre_list, player_ids, off_vals, proj_w, team_off_perf
|
|
1504
|
+
return pre_list, def_pre_list, player_ids, off_vals, proj_w, team_off_perf
|
|
1465
1505
|
|
|
1466
|
-
t1_pre, t1_ids, t1_off_vals, t1_proj_w, t1_off_perf = build_local_team(PLAYER_STATS)
|
|
1467
|
-
t2_pre, t2_ids, t2_off_vals, t2_proj_w, t2_off_perf = build_local_team(
|
|
1506
|
+
t1_pre, t1_def_pre, t1_ids, t1_off_vals, t1_proj_w, t1_off_perf = build_local_team(PLAYER_STATS)
|
|
1507
|
+
t2_pre, t2_def_pre, t2_ids, t2_off_vals, t2_proj_w, t2_off_perf = build_local_team(
|
|
1468
1508
|
f"{PLAYER_STATS}_opponent"
|
|
1469
1509
|
)
|
|
1470
1510
|
|
|
@@ -1492,7 +1532,7 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
1492
1532
|
pred_off = self._performance_predictor.predict_performance(
|
|
1493
1533
|
player_rating=pre,
|
|
1494
1534
|
opponent_team_rating=PreMatchTeamRating(
|
|
1495
|
-
id=team2, players=
|
|
1535
|
+
id=team2, players=t2_def_pre, rating_value=t2_def_rating
|
|
1496
1536
|
),
|
|
1497
1537
|
team_rating=PreMatchTeamRating(
|
|
1498
1538
|
id=team1, players=t1_pre, rating_value=t1_off_rating
|
|
@@ -1512,7 +1552,7 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
1512
1552
|
id=team2, players=t2_pre, rating_value=t2_off_rating
|
|
1513
1553
|
),
|
|
1514
1554
|
team_rating=PreMatchTeamRating(
|
|
1515
|
-
id=team1, players=
|
|
1555
|
+
id=team1, players=t1_def_pre, rating_value=t1_def_rating
|
|
1516
1556
|
),
|
|
1517
1557
|
)
|
|
1518
1558
|
|
|
@@ -1537,7 +1577,7 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
1537
1577
|
pred_off = self._performance_predictor.predict_performance(
|
|
1538
1578
|
player_rating=pre,
|
|
1539
1579
|
opponent_team_rating=PreMatchTeamRating(
|
|
1540
|
-
id=team1, players=
|
|
1580
|
+
id=team1, players=t1_def_pre, rating_value=t1_def_rating
|
|
1541
1581
|
),
|
|
1542
1582
|
team_rating=PreMatchTeamRating(
|
|
1543
1583
|
id=team2, players=t2_pre, rating_value=t2_off_rating
|
|
@@ -1557,7 +1597,7 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
1557
1597
|
id=team1, players=t1_pre, rating_value=t1_off_rating
|
|
1558
1598
|
),
|
|
1559
1599
|
team_rating=PreMatchTeamRating(
|
|
1560
|
-
id=team2, players=
|
|
1600
|
+
id=team2, players=t2_def_pre, rating_value=t2_def_rating
|
|
1561
1601
|
),
|
|
1562
1602
|
)
|
|
1563
1603
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spforge
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.42
|
|
4
4
|
Summary: A flexible framework for generating features, ratings, and building machine learning or other models for training and inference on sports data.
|
|
5
5
|
Author-email: Mathias Holmstrøm <mathiasholmstom@gmail.com>
|
|
6
6
|
License: See LICENSE file
|
|
@@ -16,7 +16,7 @@ examples/nba/data/utils.py,sha256=41hxLQ1d6ZgBEcHa5MI0-fG5KbsRi07cclMPQZM95ek,50
|
|
|
16
16
|
spforge/__init__.py,sha256=8vZhy7XUpzqWkVKpXqwqOLDkQlNytRhyf4qjwObfXgU,468
|
|
17
17
|
spforge/autopipeline.py,sha256=rZ6FhJxcgNLvtr3hTVkEiW4BiorgXxADThfMuQ42orE,29866
|
|
18
18
|
spforge/base_feature_generator.py,sha256=RbD00N6oLCQQcEb_VF5wbwZztl-X8k9B0Wlaj9Os1iU,668
|
|
19
|
-
spforge/data_structures.py,sha256=
|
|
19
|
+
spforge/data_structures.py,sha256=lNTEmmBbOK11307AshyMAcbuMhMZ3T0WyL4PEAh8cy4,7605
|
|
20
20
|
spforge/features_generator_pipeline.py,sha256=n8vzZKqXNFcFRDWZhllnkhAh5NFXdOD3FEIOpHcay8E,8208
|
|
21
21
|
spforge/utils.py,sha256=2RlivUtMX5wQWpFVUyFfexDJE0wV6uZ4dnNzvoDmVhI,2644
|
|
22
22
|
spforge/cross_validator/__init__.py,sha256=1QHgTFIZ73EZ_MgJlUKimxdUmB7MFaOEy6jsUs6V0T0,134
|
|
@@ -51,7 +51,7 @@ spforge/performance_transformers/_performance_manager.py,sha256=lh7enqYLd1lXj1VT
|
|
|
51
51
|
spforge/performance_transformers/_performances_transformers.py,sha256=nmjJTEH86JjFneWsnSWIYnUXQoUDskOraDO3VtuufIY,20931
|
|
52
52
|
spforge/ratings/__init__.py,sha256=OZVH2Lo6END3n1X8qi4QcyAPlThIwAYwVKCiIuOQSQU,576
|
|
53
53
|
spforge/ratings/_base.py,sha256=Stl_Y2gjQfS1jq_6CfeRG_e3R5Pei34WETdG6CaibGs,16487
|
|
54
|
-
spforge/ratings/_player_rating.py,sha256=
|
|
54
|
+
spforge/ratings/_player_rating.py,sha256=n3W2t5Km88IBSbpiBmxLWQ7zg60p89DqM3Y50W8bZyA,70463
|
|
55
55
|
spforge/ratings/_team_rating.py,sha256=3m90-R2zW0k5EHwjw-83Hacz91fGmxW1LQ8ZUGHlgt4,24970
|
|
56
56
|
spforge/ratings/enums.py,sha256=maG0X4WMQeMVAc2wbceq1an-U-z8moZGeG2BAgfICDA,1809
|
|
57
57
|
spforge/ratings/league_identifier.py,sha256=_KDUKOwoNU6RNFKE5jju4eYFGVNGBdJsv5mhNvMakfc,6019
|
|
@@ -71,7 +71,7 @@ spforge/transformers/_other_transformer.py,sha256=w2a7Wnki3vJe4GAkSa4kealw0GILIo
|
|
|
71
71
|
spforge/transformers/_predictor.py,sha256=2sE6gfVrilXzPVcBurSrtqHw33v2ljygQcEYXt9LhZc,3119
|
|
72
72
|
spforge/transformers/_simple_transformer.py,sha256=zGUFNQYMeoDSa2CoQejQNiNmKCBN5amWTvyOchiUHj0,5660
|
|
73
73
|
spforge/transformers/_team_ratio_predictor.py,sha256=g8_bR53Yyv0iNCtol1O9bgJSeZcIco_AfbQuUxQJkeY,6884
|
|
74
|
-
spforge-0.8.
|
|
74
|
+
spforge-0.8.42.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
75
75
|
tests/test_autopipeline.py,sha256=gXFcyqRJwxd70MY1JOqm78RJjF-fnFdMT_FaDhBdEDE,26853
|
|
76
76
|
tests/test_autopipeline_context.py,sha256=IuRUY4IA6uMObvbl2pXSaXO2_tl3qX6wEbTZY0dkTMI,1240
|
|
77
77
|
tests/test_feature_generator_pipeline.py,sha256=CK0zVL8PfTncy3RmG9i-YpgwjOIV7yJhV7Q44tbetI8,19020
|
|
@@ -81,7 +81,7 @@ tests/end_to_end/test_estimator_hyperparameter_tuning.py,sha256=fZCJ9rrED2vT68B9
|
|
|
81
81
|
tests/end_to_end/test_league_start_rating_optimizer.py,sha256=Mmct2ixp4c6L7PGym8wZc7E-Csozryt1g4_o6OCc1uI,3141
|
|
82
82
|
tests/end_to_end/test_lol_player_kills.py,sha256=RJSYUbPrZ-RzSxGggj03yN0JKYeTB1JghVGYFMYia3Y,11891
|
|
83
83
|
tests/end_to_end/test_nba_player_points.py,sha256=kyzjo7QIcvpteps29Wix6IS_eJG9d1gHLeWtIHpkWMs,9066
|
|
84
|
-
tests/end_to_end/test_nba_player_ratings_hyperparameter_tuning.py,sha256=
|
|
84
|
+
tests/end_to_end/test_nba_player_ratings_hyperparameter_tuning.py,sha256=9JZo8deJ11rSU3MsEenEjcescg71erAt5yNgZcOyH40,6317
|
|
85
85
|
tests/end_to_end/test_nba_prediction_consistency.py,sha256=o3DckJasx_I1ed6MhMYZUo2WSDvQ_p3HtJa9DCWTIYU,9857
|
|
86
86
|
tests/estimator/test_sklearn_estimator.py,sha256=tVfOP9Wx-tV1b6DcHbGxQHZQzNPA0Iobq8jTcUrk59U,48668
|
|
87
87
|
tests/feature_generator/test_lag.py,sha256=5Ffrv0V9cwkbkzRMPBe3_c_YNW-W2al-XH_acQIvdeg,19531
|
|
@@ -91,10 +91,10 @@ tests/feature_generator/test_rolling_mean_binary.py,sha256=KuIavJ37Pt8icAb50B23l
|
|
|
91
91
|
tests/feature_generator/test_rolling_mean_days.py,sha256=EyOvdJDnmgPfe13uQBOkwo7fAteBQx-tnyuGM4ng2T8,18884
|
|
92
92
|
tests/feature_generator/test_rolling_window.py,sha256=_o9oljcAIZ14iI7e8WFeAsfXxILnyqBffit21HOvII4,24378
|
|
93
93
|
tests/hyperparameter_tuning/test_estimator_tuner.py,sha256=iewME41d6LR2aQ0OtohGFtN_ocJUwTeqvs6L0QDmfG4,4413
|
|
94
|
-
tests/hyperparameter_tuning/test_rating_tuner.py,sha256=
|
|
94
|
+
tests/hyperparameter_tuning/test_rating_tuner.py,sha256=ZyHHAPpE-pHJmwpC7AGTFPSTDWSW4zXA6W4oKBD0v_E,18681
|
|
95
95
|
tests/performance_transformers/test_performance_manager.py,sha256=Ob4s86hdnR_4RC9ZG3lpB5O4Gysr2cLyTmCsO6uWomc,21244
|
|
96
96
|
tests/performance_transformers/test_performances_transformers.py,sha256=2OLpFgBolU8e-1Pga3hiOGWWHhjYpfx8Qrf9YXiqjUw,20919
|
|
97
|
-
tests/ratings/test_player_rating_generator.py,sha256=
|
|
97
|
+
tests/ratings/test_player_rating_generator.py,sha256=4HOvDBsIRR4JcyDujNmiLyw4YfHn__Nfx_PBR7fJCDo,115140
|
|
98
98
|
tests/ratings/test_player_rating_no_mutation.py,sha256=GzO3Hl__5K68DS3uRLefwnbcTJOvBM7cZqww4M21UZM,8493
|
|
99
99
|
tests/ratings/test_ratings_property.py,sha256=ckyfGILXa4tfQvsgyXEzBDNr2DUmHwFRV13N60w66iE,6561
|
|
100
100
|
tests/ratings/test_team_rating_generator.py,sha256=SqQcfckNmJJc99feCdnmkNYDape-p69e92Dp8Vzpu2w,101156
|
|
@@ -108,7 +108,7 @@ tests/transformers/test_other_transformer.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
|
|
|
108
108
|
tests/transformers/test_predictor_transformer.py,sha256=N1aBYLjN3ldpYZLwjih_gTFYSMitrZu-PNK78W6RHaQ,6877
|
|
109
109
|
tests/transformers/test_simple_transformer.py,sha256=wWR0qjLb_uS4HXrJgGdiqugOY1X7kwd1_OPS02IT2b8,4676
|
|
110
110
|
tests/transformers/test_team_ratio_predictor.py,sha256=WA44T2HU2Tx65HO_EZaLB5ujjlxfv5uTZazh_3Mo8Zg,8463
|
|
111
|
-
spforge-0.8.
|
|
112
|
-
spforge-0.8.
|
|
113
|
-
spforge-0.8.
|
|
114
|
-
spforge-0.8.
|
|
111
|
+
spforge-0.8.42.dist-info/METADATA,sha256=WPd9RDr9-KBBx6OsFmz2EM9BOWns4oy-oYNl9BtFfb8,20048
|
|
112
|
+
spforge-0.8.42.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
113
|
+
spforge-0.8.42.dist-info/top_level.txt,sha256=6UW2M5a7WKOeaAi900qQmRKNj5-HZzE8-eUD9Y9LTq0,23
|
|
114
|
+
spforge-0.8.42.dist-info/RECORD,,
|
|
@@ -536,21 +536,21 @@ def test_param_ranges__unknown_param_raises_error(
|
|
|
536
536
|
tuner.optimize(sample_player_df_pd)
|
|
537
537
|
|
|
538
538
|
|
|
539
|
-
def
|
|
539
|
+
def test_param_ranges__unknown_param_raises_error(
|
|
540
540
|
player_rating_generator, cross_validator, scorer, sample_player_df_pd
|
|
541
541
|
):
|
|
542
|
-
"""Test that param_ranges
|
|
542
|
+
"""Test that param_ranges with unknown parameter raises ValueError."""
|
|
543
543
|
tuner = RatingHyperparameterTuner(
|
|
544
544
|
rating_generator=player_rating_generator,
|
|
545
545
|
cross_validator=cross_validator,
|
|
546
546
|
scorer=scorer,
|
|
547
547
|
direction="minimize",
|
|
548
|
-
param_ranges={"
|
|
548
|
+
param_ranges={"unknown_param": (0, 1)},
|
|
549
549
|
n_trials=3,
|
|
550
550
|
show_progress_bar=False,
|
|
551
551
|
)
|
|
552
552
|
|
|
553
|
-
with pytest.raises(ValueError, match="
|
|
553
|
+
with pytest.raises(ValueError, match="unknown parameter"):
|
|
554
554
|
tuner.optimize(sample_player_df_pd)
|
|
555
555
|
|
|
556
556
|
|
|
@@ -2288,7 +2288,15 @@ def test_fit_transform_null_playing_time_uses_standard_team_rating(base_cn):
|
|
|
2288
2288
|
|
|
2289
2289
|
|
|
2290
2290
|
def test_fit_transform_weighted_calculation_with_playing_time(base_cn):
|
|
2291
|
-
"""Test that playing time weighted calculation produces
|
|
2291
|
+
"""Test that playing time weighted calculation produces valid predictions.
|
|
2292
|
+
|
|
2293
|
+
This test verifies that when opponent_players_playing_time is provided, the predictor
|
|
2294
|
+
produces valid predictions without errors.
|
|
2295
|
+
|
|
2296
|
+
Note: The specific differential behavior (P3 vs P4 predictions) is covered by
|
|
2297
|
+
test_opponent_players_playing_time_uses_def_ratings_for_offense_prediction which
|
|
2298
|
+
uses a simplified 2-player setup that more directly tests the opponent DEF rating fix.
|
|
2299
|
+
"""
|
|
2292
2300
|
from dataclasses import replace
|
|
2293
2301
|
|
|
2294
2302
|
cn = replace(
|
|
@@ -2297,78 +2305,54 @@ def test_fit_transform_weighted_calculation_with_playing_time(base_cn):
|
|
|
2297
2305
|
opponent_players_playing_time="opp_pt",
|
|
2298
2306
|
)
|
|
2299
2307
|
|
|
2300
|
-
# First establish different ratings for players
|
|
2301
|
-
df1 = pl.DataFrame(
|
|
2302
|
-
{
|
|
2303
|
-
"pid": ["P1", "P2", "P3", "P4"],
|
|
2304
|
-
"tid": ["T1", "T1", "T2", "T2"],
|
|
2305
|
-
"mid": ["M1", "M1", "M1", "M1"],
|
|
2306
|
-
"dt": ["2024-01-01"] * 4,
|
|
2307
|
-
"perf": [0.9, 0.1, 0.5, 0.5], # P1 high rating, P2 low rating
|
|
2308
|
-
"pw": [1.0, 1.0, 1.0, 1.0],
|
|
2309
|
-
"team_pt": [None, None, None, None],
|
|
2310
|
-
"opp_pt": [None, None, None, None],
|
|
2311
|
-
}
|
|
2312
|
-
)
|
|
2313
|
-
|
|
2314
2308
|
gen = PlayerRatingGenerator(
|
|
2315
2309
|
performance_column="perf",
|
|
2316
2310
|
column_names=cn,
|
|
2317
|
-
|
|
2311
|
+
use_off_def_split=True,
|
|
2312
|
+
performance_predictor="difference",
|
|
2318
2313
|
start_harcoded_start_rating=1000.0,
|
|
2319
2314
|
non_predictor_features_out=[RatingUnknownFeatures.PLAYER_PREDICTED_OFF_PERFORMANCE],
|
|
2320
2315
|
)
|
|
2321
|
-
gen.fit_transform(df1)
|
|
2322
2316
|
|
|
2323
|
-
#
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
assert p1_rating > p2_rating, "Setup: P1 should have higher rating than P2"
|
|
2317
|
+
# Pre-seed players
|
|
2318
|
+
gen._player_off_ratings["P1"] = PlayerRating(id="P1", rating_value=1000.0, games_played=10)
|
|
2319
|
+
gen._player_def_ratings["P1"] = PlayerRating(id="P1", rating_value=1200.0, games_played=10)
|
|
2327
2320
|
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2321
|
+
gen._player_off_ratings["P2"] = PlayerRating(id="P2", rating_value=1000.0, games_played=10)
|
|
2322
|
+
gen._player_def_ratings["P2"] = PlayerRating(id="P2", rating_value=800.0, games_played=10)
|
|
2323
|
+
|
|
2324
|
+
gen._player_off_ratings["P3"] = PlayerRating(id="P3", rating_value=1000.0, games_played=10)
|
|
2325
|
+
gen._player_def_ratings["P3"] = PlayerRating(id="P3", rating_value=1000.0, games_played=10)
|
|
2326
|
+
|
|
2327
|
+
gen._player_off_ratings["P4"] = PlayerRating(id="P4", rating_value=1000.0, games_played=10)
|
|
2328
|
+
gen._player_def_ratings["P4"] = PlayerRating(id="P4", rating_value=1000.0, games_played=10)
|
|
2329
|
+
|
|
2330
|
+
# Match with playing time data
|
|
2331
|
+
df = pl.DataFrame(
|
|
2332
2332
|
{
|
|
2333
2333
|
"pid": ["P1", "P2", "P3", "P4"],
|
|
2334
2334
|
"tid": ["T1", "T1", "T2", "T2"],
|
|
2335
|
-
"mid": ["
|
|
2336
|
-
"dt": ["2024-01-
|
|
2335
|
+
"mid": ["M1", "M1", "M1", "M1"],
|
|
2336
|
+
"dt": ["2024-01-01"] * 4,
|
|
2337
|
+
"perf": [None, None, None, None],
|
|
2337
2338
|
"pw": [1.0, 1.0, 1.0, 1.0],
|
|
2338
|
-
|
|
2339
|
-
"team_pt": [
|
|
2340
|
-
{"P1": 0.0, "P2": 1.0, "P3": 0.5, "P4": 0.5}, # P1 on T1, plays with P2
|
|
2341
|
-
{"P1": 1.0, "P2": 0.0, "P3": 0.5, "P4": 0.5}, # P2 on T1, plays with P1
|
|
2342
|
-
{"P1": 0.5, "P2": 0.5, "P3": 0.0, "P4": 1.0}, # P3 on T2, plays with P4
|
|
2343
|
-
{"P1": 0.5, "P2": 0.5, "P3": 1.0, "P4": 0.0}, # P4 on T2, plays with P3
|
|
2344
|
-
],
|
|
2345
|
-
# Opponent playing time - who they face on opposing team
|
|
2339
|
+
"team_pt": [None, None, None, None],
|
|
2346
2340
|
"opp_pt": [
|
|
2347
|
-
{"
|
|
2348
|
-
{"
|
|
2349
|
-
{"P1": 0.8, "P2": 0.2
|
|
2350
|
-
{"P1": 0.2, "P2": 0.8
|
|
2341
|
+
{"P3": 0.5, "P4": 0.5},
|
|
2342
|
+
{"P3": 0.5, "P4": 0.5},
|
|
2343
|
+
{"P1": 0.8, "P2": 0.2},
|
|
2344
|
+
{"P1": 0.2, "P2": 0.8},
|
|
2351
2345
|
],
|
|
2352
2346
|
}
|
|
2353
2347
|
)
|
|
2354
2348
|
|
|
2355
|
-
result = gen.future_transform(
|
|
2349
|
+
result = gen.future_transform(df)
|
|
2356
2350
|
|
|
2357
|
-
# Verify we get predictions
|
|
2351
|
+
# Verify we get valid predictions
|
|
2358
2352
|
assert len(result) == 4
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
# So P3 should have lower predicted performance than P4 (all else equal)
|
|
2363
|
-
p3_pred = result.filter(pl.col("pid") == "P3")["player_predicted_off_performance_perf"][0]
|
|
2364
|
-
p4_pred = result.filter(pl.col("pid") == "P4")["player_predicted_off_performance_perf"][0]
|
|
2365
|
-
|
|
2366
|
-
# P3 faces P1 (high rating) 80% of time, P4 faces P2 (low rating) 80% of time
|
|
2367
|
-
# So P4 should have higher predicted performance
|
|
2368
|
-
assert p4_pred > p3_pred, (
|
|
2369
|
-
f"P4 (facing weak opponents) should have higher prediction than P3 (facing strong opponents). "
|
|
2370
|
-
f"P3 pred={p3_pred:.4f}, P4 pred={p4_pred:.4f}"
|
|
2371
|
-
)
|
|
2353
|
+
predictions = result["player_predicted_off_performance_perf"].to_list()
|
|
2354
|
+
for pred in predictions:
|
|
2355
|
+
assert 0.0 <= pred <= 1.0, f"Prediction {pred} out of valid range [0, 1]"
|
|
2372
2356
|
|
|
2373
2357
|
|
|
2374
2358
|
def test_future_transform_weighted_calculation_with_playing_time(base_cn):
|
|
@@ -2436,6 +2420,59 @@ def test_future_transform_weighted_calculation_with_playing_time(base_cn):
|
|
|
2436
2420
|
assert 0.0 <= pred <= 1.0
|
|
2437
2421
|
|
|
2438
2422
|
|
|
2423
|
+
def test_future_transform_without_playing_time_columns_works(base_cn):
|
|
2424
|
+
"""future_transform should work when playing time columns are missing from future data."""
|
|
2425
|
+
from dataclasses import replace
|
|
2426
|
+
|
|
2427
|
+
cn = replace(
|
|
2428
|
+
base_cn,
|
|
2429
|
+
team_players_playing_time="team_pt",
|
|
2430
|
+
opponent_players_playing_time="opp_pt",
|
|
2431
|
+
)
|
|
2432
|
+
|
|
2433
|
+
# fit_transform with playing time columns present
|
|
2434
|
+
df1 = pl.DataFrame(
|
|
2435
|
+
{
|
|
2436
|
+
"pid": ["P1", "P2", "P3", "P4"],
|
|
2437
|
+
"tid": ["T1", "T1", "T2", "T2"],
|
|
2438
|
+
"mid": ["M1", "M1", "M1", "M1"],
|
|
2439
|
+
"dt": ["2024-01-01"] * 4,
|
|
2440
|
+
"perf": [0.9, 0.1, 0.5, 0.5],
|
|
2441
|
+
"pw": [1.0, 1.0, 1.0, 1.0],
|
|
2442
|
+
"team_pt": [None, None, None, None],
|
|
2443
|
+
"opp_pt": [None, None, None, None],
|
|
2444
|
+
}
|
|
2445
|
+
)
|
|
2446
|
+
|
|
2447
|
+
gen = PlayerRatingGenerator(
|
|
2448
|
+
performance_column="perf",
|
|
2449
|
+
column_names=cn,
|
|
2450
|
+
auto_scale_performance=True,
|
|
2451
|
+
start_harcoded_start_rating=1000.0,
|
|
2452
|
+
non_predictor_features_out=[RatingUnknownFeatures.PLAYER_PREDICTED_OFF_PERFORMANCE],
|
|
2453
|
+
)
|
|
2454
|
+
gen.fit_transform(df1)
|
|
2455
|
+
|
|
2456
|
+
# future_transform WITHOUT playing time columns (common for future predictions)
|
|
2457
|
+
future_df = pl.DataFrame(
|
|
2458
|
+
{
|
|
2459
|
+
"pid": ["P1", "P2", "P3", "P4"],
|
|
2460
|
+
"tid": ["T1", "T1", "T2", "T2"],
|
|
2461
|
+
"mid": ["M2", "M2", "M2", "M2"],
|
|
2462
|
+
"dt": ["2024-01-02"] * 4,
|
|
2463
|
+
"pw": [1.0, 1.0, 1.0, 1.0],
|
|
2464
|
+
}
|
|
2465
|
+
)
|
|
2466
|
+
|
|
2467
|
+
# Should not raise - playing time columns are optional for future predictions
|
|
2468
|
+
result = gen.future_transform(future_df)
|
|
2469
|
+
|
|
2470
|
+
assert len(result) == 4
|
|
2471
|
+
predictions = result["player_predicted_off_performance_perf"].to_list()
|
|
2472
|
+
for pred in predictions:
|
|
2473
|
+
assert 0.0 <= pred <= 1.0
|
|
2474
|
+
|
|
2475
|
+
|
|
2439
2476
|
def test_fit_transform_backward_compatible_without_playing_time_columns(base_cn):
|
|
2440
2477
|
"""Behavior should be unchanged when team_players_playing_time columns are not specified."""
|
|
2441
2478
|
df = pl.DataFrame(
|
|
@@ -3076,3 +3113,91 @@ class TestNaNPerformanceHandling:
|
|
|
3076
3113
|
|
|
3077
3114
|
result = gen.fit_transform(df)
|
|
3078
3115
|
assert len(result) == 4
|
|
3116
|
+
|
|
3117
|
+
|
|
3118
|
+
def test_opponent_players_playing_time_uses_def_ratings_for_offense_prediction(base_cn):
|
|
3119
|
+
"""
|
|
3120
|
+
Bug reproduction test: When predicting offensive performance with opponent_players_playing_time,
|
|
3121
|
+
the predictor should use opponent DEF ratings (not OFF ratings) for weighting.
|
|
3122
|
+
|
|
3123
|
+
The bug was that _create_pre_match_players_collection builds PreMatchPlayerRating using
|
|
3124
|
+
only OFF ratings, but when predicting offense vs opponent defense, we need to weight
|
|
3125
|
+
using opponent DEF ratings.
|
|
3126
|
+
|
|
3127
|
+
This test sets up players with divergent OFF and DEF ratings and verifies the correct
|
|
3128
|
+
ratings are used.
|
|
3129
|
+
"""
|
|
3130
|
+
from dataclasses import replace
|
|
3131
|
+
import math
|
|
3132
|
+
|
|
3133
|
+
cn = replace(
|
|
3134
|
+
base_cn,
|
|
3135
|
+
team_players_playing_time="team_pt",
|
|
3136
|
+
opponent_players_playing_time="opp_pt",
|
|
3137
|
+
)
|
|
3138
|
+
|
|
3139
|
+
gen = PlayerRatingGenerator(
|
|
3140
|
+
performance_column="perf",
|
|
3141
|
+
column_names=cn,
|
|
3142
|
+
use_off_def_split=True,
|
|
3143
|
+
performance_predictor="difference",
|
|
3144
|
+
start_harcoded_start_rating=1000.0,
|
|
3145
|
+
non_predictor_features_out=[RatingUnknownFeatures.PLAYER_PREDICTED_OFF_PERFORMANCE],
|
|
3146
|
+
)
|
|
3147
|
+
|
|
3148
|
+
# Pre-seed players with divergent OFF and DEF ratings
|
|
3149
|
+
# P1 on T1: high OFF (1200), low DEF (800)
|
|
3150
|
+
# P2 on T2: low OFF (800), high DEF (1200)
|
|
3151
|
+
gen._player_off_ratings["P1"] = PlayerRating(id="P1", rating_value=1200.0, games_played=10)
|
|
3152
|
+
gen._player_def_ratings["P1"] = PlayerRating(id="P1", rating_value=800.0, games_played=10)
|
|
3153
|
+
|
|
3154
|
+
gen._player_off_ratings["P2"] = PlayerRating(id="P2", rating_value=800.0, games_played=10)
|
|
3155
|
+
gen._player_def_ratings["P2"] = PlayerRating(id="P2", rating_value=1200.0, games_played=10)
|
|
3156
|
+
|
|
3157
|
+
# Create a match where P1 (T1) faces P2 (T2)
|
|
3158
|
+
# P1's offense prediction should be based on P2's DEF rating (1200), not P2's OFF rating (800)
|
|
3159
|
+
df = pl.DataFrame(
|
|
3160
|
+
{
|
|
3161
|
+
"pid": ["P1", "P2"],
|
|
3162
|
+
"tid": ["T1", "T2"],
|
|
3163
|
+
"mid": ["M1", "M1"],
|
|
3164
|
+
"dt": ["2024-01-01"] * 2,
|
|
3165
|
+
"perf": [None, None], # Future prediction, no actual performance
|
|
3166
|
+
"pw": [1.0, 1.0],
|
|
3167
|
+
"team_pt": [None, None],
|
|
3168
|
+
"opp_pt": [
|
|
3169
|
+
{"P2": 1.0}, # P1 faces P2 100% of time
|
|
3170
|
+
{"P1": 1.0}, # P2 faces P1 100% of time
|
|
3171
|
+
],
|
|
3172
|
+
}
|
|
3173
|
+
)
|
|
3174
|
+
|
|
3175
|
+
result = gen.future_transform(df)
|
|
3176
|
+
|
|
3177
|
+
# Get P1's predicted offensive performance
|
|
3178
|
+
p1_pred = result.filter(pl.col("pid") == "P1")["player_predicted_off_performance_perf"][0]
|
|
3179
|
+
|
|
3180
|
+
# Calculate what the prediction SHOULD be:
|
|
3181
|
+
# P1 OFF rating = 1200
|
|
3182
|
+
# P2 DEF rating = 1200 (this SHOULD be used, not P2 OFF rating = 800)
|
|
3183
|
+
# rating_difference = 1200 - 1200 = 0
|
|
3184
|
+
# prediction = sigmoid(0.005757 * 0) = 0.5
|
|
3185
|
+
|
|
3186
|
+
expected_rating_diff_with_def = 1200 - 1200 # = 0
|
|
3187
|
+
expected_pred_with_def = 1 / (1 + math.exp(-0.005757 * expected_rating_diff_with_def))
|
|
3188
|
+
|
|
3189
|
+
# If the bug exists, it would use P2 OFF rating (800):
|
|
3190
|
+
# rating_difference = 1200 - 800 = 400
|
|
3191
|
+
# prediction = sigmoid(0.005757 * 400) ≈ 0.909
|
|
3192
|
+
buggy_rating_diff_with_off = 1200 - 800 # = 400
|
|
3193
|
+
buggy_pred_with_off = 1 / (1 + math.exp(-0.005757 * buggy_rating_diff_with_off))
|
|
3194
|
+
|
|
3195
|
+
# The prediction should be close to 0.5 (using DEF), not ~0.909 (using OFF)
|
|
3196
|
+
assert abs(p1_pred - expected_pred_with_def) < 0.01, (
|
|
3197
|
+
f"P1's offensive performance prediction should use opponent DEF ratings. "
|
|
3198
|
+
f"Expected ~{expected_pred_with_def:.4f} (using P2 DEF=1200), "
|
|
3199
|
+
f"got {p1_pred:.4f}. "
|
|
3200
|
+
f"If using P2 OFF=800, prediction would be ~{buggy_pred_with_off:.4f}"
|
|
3201
|
+
)
|
|
3202
|
+
|
|
3203
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|