spforge 0.8.19__tar.gz → 0.8.20__tar.gz
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-0.8.19/spforge.egg-info → spforge-0.8.20}/PKG-INFO +1 -1
- {spforge-0.8.19 → spforge-0.8.20}/pyproject.toml +1 -1
- {spforge-0.8.19 → spforge-0.8.20}/spforge/ratings/_player_rating.py +65 -50
- {spforge-0.8.19 → spforge-0.8.20/spforge.egg-info}/PKG-INFO +1 -1
- {spforge-0.8.19 → spforge-0.8.20}/tests/ratings/test_player_rating_generator.py +192 -0
- {spforge-0.8.19 → spforge-0.8.20}/LICENSE +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/MANIFEST.in +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/README.md +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/examples/__init__.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/examples/game_level_example.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/examples/lol/__init__.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/examples/lol/data/__init__.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/examples/lol/data/subsample_lol_data.parquet +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/examples/lol/data/utils.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/examples/lol/pipeline_transformer_example.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/examples/nba/__init__.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/examples/nba/cross_validation_example.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/examples/nba/data/__init__.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/examples/nba/data/game_player_subsample.parquet +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/examples/nba/data/utils.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/examples/nba/feature_engineering_example.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/examples/nba/game_winner_example.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/examples/nba/predictor_transformers_example.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/setup.cfg +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/__init__.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/autopipeline.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/base_feature_generator.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/cross_validator/__init__.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/cross_validator/_base.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/cross_validator/cross_validator.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/data_structures.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/distributions/__init__.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/distributions/_negative_binomial_estimator.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/distributions/_normal_distribution_predictor.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/distributions/_student_t_distribution_estimator.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/estimator/__init__.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/estimator/_conditional_estimator.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/estimator/_frequency_bucketing_classifier.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/estimator/_granularity_estimator.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/estimator/_group_by_estimator.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/estimator/_ordinal_classifier.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/estimator/_sklearn_enhancer_estimator.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/feature_generator/__init__.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/feature_generator/_base.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/feature_generator/_lag.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/feature_generator/_net_over_predicted.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/feature_generator/_regressor_feature_generator.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/feature_generator/_rolling_against_opponent.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/feature_generator/_rolling_mean_binary.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/feature_generator/_rolling_mean_days.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/feature_generator/_rolling_window.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/feature_generator/_utils.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/features_generator_pipeline.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/hyperparameter_tuning/__init__.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/hyperparameter_tuning/_default_search_spaces.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/hyperparameter_tuning/_tuner.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/performance_transformers/__init__.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/performance_transformers/_performance_manager.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/performance_transformers/_performances_transformers.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/ratings/__init__.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/ratings/_base.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/ratings/_team_rating.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/ratings/enums.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/ratings/league_identifier.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/ratings/league_start_rating_optimizer.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/ratings/player_performance_predictor.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/ratings/start_rating_generator.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/ratings/team_performance_predictor.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/ratings/team_start_rating_generator.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/ratings/utils.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/scorer/__init__.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/scorer/_score.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/transformers/__init__.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/transformers/_base.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/transformers/_net_over_predicted.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/transformers/_operator.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/transformers/_other_transformer.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/transformers/_predictor.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/transformers/_simple_transformer.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/transformers/_team_ratio_predictor.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge/utils.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge.egg-info/SOURCES.txt +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge.egg-info/dependency_links.txt +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge.egg-info/requires.txt +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/spforge.egg-info/top_level.txt +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/cross_validator/test_cross_validator.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/distributions/test_distribution.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/end_to_end/test_estimator_hyperparameter_tuning.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/end_to_end/test_league_start_rating_optimizer.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/end_to_end/test_lol_player_kills.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/end_to_end/test_nba_player_points.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/end_to_end/test_nba_player_ratings_hyperparameter_tuning.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/end_to_end/test_nba_prediction_consistency.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/estimator/test_sklearn_estimator.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/feature_generator/test_lag.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/feature_generator/test_regressor_feature_generator.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/feature_generator/test_rolling_against_opponent.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/feature_generator/test_rolling_mean_binary.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/feature_generator/test_rolling_mean_days.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/feature_generator/test_rolling_window.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/hyperparameter_tuning/test_estimator_tuner.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/hyperparameter_tuning/test_rating_tuner.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/performance_transformers/test_performance_manager.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/performance_transformers/test_performances_transformers.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/ratings/test_player_rating_no_mutation.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/ratings/test_ratings_property.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/ratings/test_team_rating_generator.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/ratings/test_utils_scaled_weights.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/scorer/test_score.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/scorer/test_score_aggregation_granularity.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/test_autopipeline.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/test_autopipeline_context.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/test_feature_generator_pipeline.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/transformers/test_estimator_transformer_context.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/transformers/test_net_over_predicted.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/transformers/test_other_transformer.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/transformers/test_predictor_transformer.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/transformers/test_simple_transformer.py +0 -0
- {spforge-0.8.19 → spforge-0.8.20}/tests/transformers/test_team_ratio_predictor.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spforge
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.20
|
|
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
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "spforge"
|
|
7
|
-
version = "0.8.
|
|
7
|
+
version = "0.8.20"
|
|
8
8
|
description = "A flexible framework for generating features, ratings, and building machine learning or other models for training and inference on sports data."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -484,26 +484,31 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
484
484
|
),
|
|
485
485
|
)
|
|
486
486
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
487
|
+
perf_value = pre_player.match_performance.performance_value
|
|
488
|
+
if perf_value is None:
|
|
489
|
+
off_change = 0.0
|
|
490
|
+
def_change = 0.0
|
|
491
|
+
else:
|
|
492
|
+
off_perf = float(perf_value)
|
|
493
|
+
def_perf = float(team1_def_perf)
|
|
494
|
+
|
|
495
|
+
if not self.use_off_def_split:
|
|
496
|
+
pred_def = pred_off
|
|
497
|
+
def_perf = off_perf
|
|
498
|
+
|
|
499
|
+
mult_off = self._applied_multiplier_off(off_state)
|
|
500
|
+
mult_def = self._applied_multiplier_def(def_state)
|
|
501
|
+
|
|
502
|
+
off_change = (
|
|
503
|
+
(off_perf - float(pred_off))
|
|
504
|
+
* mult_off
|
|
505
|
+
* float(pre_player.match_performance.participation_weight)
|
|
506
|
+
)
|
|
507
|
+
def_change = (
|
|
508
|
+
(def_perf - float(pred_def))
|
|
509
|
+
* mult_def
|
|
510
|
+
* float(pre_player.match_performance.participation_weight)
|
|
511
|
+
)
|
|
507
512
|
|
|
508
513
|
if math.isnan(off_change) or math.isnan(def_change):
|
|
509
514
|
raise ValueError(
|
|
@@ -562,32 +567,37 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
562
567
|
),
|
|
563
568
|
)
|
|
564
569
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
570
|
+
perf_value = pre_player.match_performance.performance_value
|
|
571
|
+
if perf_value is None:
|
|
572
|
+
off_change = 0.0
|
|
573
|
+
def_change = 0.0
|
|
574
|
+
else:
|
|
575
|
+
off_perf = float(perf_value)
|
|
576
|
+
def_perf = float(team2_def_perf)
|
|
577
|
+
|
|
578
|
+
if not self.use_off_def_split:
|
|
579
|
+
pred_def = pred_off
|
|
580
|
+
def_perf = off_perf
|
|
581
|
+
|
|
582
|
+
mult_off = self._applied_multiplier_off(off_state)
|
|
583
|
+
mult_def = self._applied_multiplier_def(def_state)
|
|
584
|
+
|
|
585
|
+
off_change = (
|
|
586
|
+
(off_perf - float(pred_off))
|
|
587
|
+
* mult_off
|
|
588
|
+
* float(pre_player.match_performance.participation_weight)
|
|
589
|
+
)
|
|
590
|
+
def_change = (
|
|
591
|
+
(def_perf - float(pred_def))
|
|
592
|
+
* mult_def
|
|
593
|
+
* float(pre_player.match_performance.participation_weight)
|
|
589
594
|
)
|
|
590
595
|
|
|
596
|
+
if math.isnan(off_change) or math.isnan(def_change):
|
|
597
|
+
raise ValueError(
|
|
598
|
+
f"NaN player rating change for player_id={pid}, match_id={r[cn.match_id]}"
|
|
599
|
+
)
|
|
600
|
+
|
|
591
601
|
player_updates.append(
|
|
592
602
|
(
|
|
593
603
|
pid,
|
|
@@ -933,7 +943,7 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
933
943
|
self.performance_column in team_player
|
|
934
944
|
and team_player[self.performance_column] is not None
|
|
935
945
|
)
|
|
936
|
-
else
|
|
946
|
+
else None
|
|
937
947
|
)
|
|
938
948
|
|
|
939
949
|
mp = MatchPerformance(
|
|
@@ -1023,18 +1033,22 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
1023
1033
|
|
|
1024
1034
|
def _team_off_perf_from_collection(self, c: PreMatchPlayersCollection) -> float:
|
|
1025
1035
|
# observed offense perf = weighted mean of player performance_value using participation_weight if present
|
|
1036
|
+
# skip players with null performance
|
|
1026
1037
|
cn = self.column_names
|
|
1027
1038
|
if not c.pre_match_player_ratings:
|
|
1028
1039
|
return 0.0
|
|
1029
1040
|
wsum = 0.0
|
|
1030
1041
|
psum = 0.0
|
|
1031
1042
|
for pre in c.pre_match_player_ratings:
|
|
1043
|
+
perf_val = pre.match_performance.performance_value
|
|
1044
|
+
if perf_val is None:
|
|
1045
|
+
continue
|
|
1032
1046
|
w = (
|
|
1033
1047
|
float(pre.match_performance.participation_weight)
|
|
1034
1048
|
if cn.participation_weight
|
|
1035
1049
|
else 1.0
|
|
1036
1050
|
)
|
|
1037
|
-
psum += float(
|
|
1051
|
+
psum += float(perf_val) * w
|
|
1038
1052
|
wsum += w
|
|
1039
1053
|
return psum / wsum if wsum else 0.0
|
|
1040
1054
|
|
|
@@ -1101,13 +1115,13 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
1101
1115
|
self.PLAYER_PRED_PERF_COL: [],
|
|
1102
1116
|
}
|
|
1103
1117
|
|
|
1104
|
-
def get_perf_value(team_player: dict) -> float:
|
|
1118
|
+
def get_perf_value(team_player: dict) -> float | None:
|
|
1105
1119
|
if (
|
|
1106
1120
|
self.performance_column in team_player
|
|
1107
1121
|
and team_player[self.performance_column] is not None
|
|
1108
1122
|
):
|
|
1109
1123
|
return float(team_player[self.performance_column])
|
|
1110
|
-
return
|
|
1124
|
+
return None
|
|
1111
1125
|
|
|
1112
1126
|
def ensure_new_player(
|
|
1113
1127
|
pid: str,
|
|
@@ -1187,8 +1201,9 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
1187
1201
|
)
|
|
1188
1202
|
off_vals.append(float(local_off[pid].rating_value))
|
|
1189
1203
|
|
|
1190
|
-
|
|
1191
|
-
|
|
1204
|
+
if mp.performance_value is not None:
|
|
1205
|
+
psum += float(mp.performance_value) * float(pw)
|
|
1206
|
+
wsum += float(pw)
|
|
1192
1207
|
|
|
1193
1208
|
team_off_perf = psum / wsum if wsum else 0.0
|
|
1194
1209
|
return pre_list, player_ids, off_vals, proj_w, team_off_perf
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spforge
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.20
|
|
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
|
|
@@ -722,6 +722,198 @@ def test_fit_transform_null_performance_handling(base_cn, sample_df):
|
|
|
722
722
|
assert len(res) == 4
|
|
723
723
|
|
|
724
724
|
|
|
725
|
+
def test_fit_transform_null_performance__no_rating_change(base_cn):
|
|
726
|
+
"""Players with null performance should have zero rating change, not be treated as 0.0 perf."""
|
|
727
|
+
# Match 1: Both players have performance (P1=0.6, P2=0.4)
|
|
728
|
+
# Match 2: P1 has null performance, P2 has 0.6
|
|
729
|
+
# Match 3: Both players have performance again
|
|
730
|
+
df = pl.DataFrame(
|
|
731
|
+
{
|
|
732
|
+
"pid": ["P1", "P2", "P1", "P2", "P1", "P2"],
|
|
733
|
+
"tid": ["T1", "T2", "T1", "T2", "T1", "T2"],
|
|
734
|
+
"mid": ["M1", "M1", "M2", "M2", "M3", "M3"],
|
|
735
|
+
"dt": [
|
|
736
|
+
"2024-01-01",
|
|
737
|
+
"2024-01-01",
|
|
738
|
+
"2024-01-02",
|
|
739
|
+
"2024-01-02",
|
|
740
|
+
"2024-01-03",
|
|
741
|
+
"2024-01-03",
|
|
742
|
+
],
|
|
743
|
+
"perf": [0.6, 0.4, None, 0.6, 0.6, 0.4], # P1 has null in M2
|
|
744
|
+
"pw": [1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
|
|
745
|
+
}
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
gen = PlayerRatingGenerator(
|
|
749
|
+
performance_column="perf",
|
|
750
|
+
column_names=base_cn,
|
|
751
|
+
features_out=[RatingKnownFeatures.PLAYER_OFF_RATING],
|
|
752
|
+
)
|
|
753
|
+
result = gen.fit_transform(df)
|
|
754
|
+
|
|
755
|
+
# Get P1's pre-match rating for M2 (after M1) and M3 (after M2 with null perf)
|
|
756
|
+
p1_rating_before_m2 = result.filter(
|
|
757
|
+
(pl.col("pid") == "P1") & (pl.col("mid") == "M2")
|
|
758
|
+
)["player_off_rating_perf"][0]
|
|
759
|
+
p1_rating_before_m3 = result.filter(
|
|
760
|
+
(pl.col("pid") == "P1") & (pl.col("mid") == "M3")
|
|
761
|
+
)["player_off_rating_perf"][0]
|
|
762
|
+
|
|
763
|
+
# Key assertion: P1's rating before M3 should equal rating before M2
|
|
764
|
+
# because null performance in M2 means NO rating change
|
|
765
|
+
assert p1_rating_before_m3 == p1_rating_before_m2, (
|
|
766
|
+
f"P1's rating changed after null performance game! "
|
|
767
|
+
f"Before M2={p1_rating_before_m2}, Before M3={p1_rating_before_m3}"
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
# Also verify null is not treated as 0.0 by comparing with explicit 0.0
|
|
771
|
+
df_with_zero = df.with_columns(
|
|
772
|
+
pl.when((pl.col("pid") == "P1") & (pl.col("mid") == "M2"))
|
|
773
|
+
.then(0.0)
|
|
774
|
+
.otherwise(pl.col("perf"))
|
|
775
|
+
.alias("perf")
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
gen_zero = PlayerRatingGenerator(
|
|
779
|
+
performance_column="perf",
|
|
780
|
+
column_names=base_cn,
|
|
781
|
+
features_out=[RatingKnownFeatures.PLAYER_OFF_RATING],
|
|
782
|
+
)
|
|
783
|
+
result_zero = gen_zero.fit_transform(df_with_zero)
|
|
784
|
+
|
|
785
|
+
p1_rating_before_m3_with_zero = result_zero.filter(
|
|
786
|
+
(pl.col("pid") == "P1") & (pl.col("mid") == "M3")
|
|
787
|
+
)["player_off_rating_perf"][0]
|
|
788
|
+
|
|
789
|
+
# With 0.0 perf, rating should drop (different from null)
|
|
790
|
+
assert p1_rating_before_m3 > p1_rating_before_m3_with_zero, (
|
|
791
|
+
f"Null performance is being treated as 0.0! "
|
|
792
|
+
f"Rating with null={p1_rating_before_m3}, rating with 0.0={p1_rating_before_m3_with_zero}"
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
def test_fit_transform_null_performance__still_outputs_player_rating(base_cn):
|
|
797
|
+
"""Players with null performance should still have their pre-match rating in output."""
|
|
798
|
+
df = pl.DataFrame(
|
|
799
|
+
{
|
|
800
|
+
"pid": ["P1", "P2", "P3", "P4"],
|
|
801
|
+
"tid": ["T1", "T1", "T2", "T2"],
|
|
802
|
+
"mid": ["M1", "M1", "M1", "M1"],
|
|
803
|
+
"dt": ["2024-01-01"] * 4,
|
|
804
|
+
"perf": [0.6, None, 0.4, 0.5], # P2 has null performance
|
|
805
|
+
"pw": [1.0, 1.0, 1.0, 1.0],
|
|
806
|
+
}
|
|
807
|
+
)
|
|
808
|
+
|
|
809
|
+
gen = PlayerRatingGenerator(
|
|
810
|
+
performance_column="perf",
|
|
811
|
+
column_names=base_cn,
|
|
812
|
+
features_out=[RatingKnownFeatures.PLAYER_OFF_RATING],
|
|
813
|
+
)
|
|
814
|
+
result = gen.fit_transform(df)
|
|
815
|
+
|
|
816
|
+
# P2 should still be in output with their pre-match rating
|
|
817
|
+
assert len(result) == 4
|
|
818
|
+
p2_row = result.filter(pl.col("pid") == "P2")
|
|
819
|
+
assert len(p2_row) == 1
|
|
820
|
+
assert "player_off_rating_perf" in result.columns
|
|
821
|
+
# P2's rating should be the start rating (1000.0) since they're new and had no update
|
|
822
|
+
assert p2_row["player_off_rating_perf"][0] == 1000.0
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
def test_transform_null_performance__no_rating_change(base_cn):
|
|
826
|
+
"""In transform (historical), null performance should result in no rating change."""
|
|
827
|
+
# First fit with some data
|
|
828
|
+
fit_df = pl.DataFrame(
|
|
829
|
+
{
|
|
830
|
+
"pid": ["P1", "P2"],
|
|
831
|
+
"tid": ["T1", "T2"],
|
|
832
|
+
"mid": ["M1", "M1"],
|
|
833
|
+
"dt": ["2024-01-01", "2024-01-01"],
|
|
834
|
+
"perf": [0.6, 0.4],
|
|
835
|
+
"pw": [1.0, 1.0],
|
|
836
|
+
}
|
|
837
|
+
)
|
|
838
|
+
|
|
839
|
+
gen = PlayerRatingGenerator(
|
|
840
|
+
performance_column="perf",
|
|
841
|
+
column_names=base_cn,
|
|
842
|
+
features_out=[RatingKnownFeatures.PLAYER_OFF_RATING],
|
|
843
|
+
)
|
|
844
|
+
gen.fit_transform(fit_df)
|
|
845
|
+
|
|
846
|
+
p1_rating_before = gen._player_off_ratings["P1"].rating_value
|
|
847
|
+
|
|
848
|
+
# Now transform with P1 having null performance
|
|
849
|
+
transform_df = pl.DataFrame(
|
|
850
|
+
{
|
|
851
|
+
"pid": ["P1", "P2"],
|
|
852
|
+
"tid": ["T1", "T2"],
|
|
853
|
+
"mid": ["M2", "M2"],
|
|
854
|
+
"dt": ["2024-01-02", "2024-01-02"],
|
|
855
|
+
"perf": [None, 0.6], # P1 has null
|
|
856
|
+
"pw": [1.0, 1.0],
|
|
857
|
+
}
|
|
858
|
+
)
|
|
859
|
+
|
|
860
|
+
gen.transform(transform_df)
|
|
861
|
+
|
|
862
|
+
p1_rating_after = gen._player_off_ratings["P1"].rating_value
|
|
863
|
+
|
|
864
|
+
# P1's rating should not change significantly (only confidence decay, not performance-based)
|
|
865
|
+
# Since null perf means no rating change from performance
|
|
866
|
+
assert abs(p1_rating_after - p1_rating_before) < 0.01, (
|
|
867
|
+
f"P1's rating changed significantly with null performance: "
|
|
868
|
+
f"before={p1_rating_before}, after={p1_rating_after}"
|
|
869
|
+
)
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
def test_future_transform_null_performance__outputs_projections(base_cn):
|
|
873
|
+
"""In future_transform, null performance should still output rating projections."""
|
|
874
|
+
# First fit with some data
|
|
875
|
+
fit_df = pl.DataFrame(
|
|
876
|
+
{
|
|
877
|
+
"pid": ["P1", "P2"],
|
|
878
|
+
"tid": ["T1", "T2"],
|
|
879
|
+
"mid": ["M1", "M1"],
|
|
880
|
+
"dt": ["2024-01-01", "2024-01-01"],
|
|
881
|
+
"perf": [0.6, 0.4],
|
|
882
|
+
"pw": [1.0, 1.0],
|
|
883
|
+
}
|
|
884
|
+
)
|
|
885
|
+
|
|
886
|
+
gen = PlayerRatingGenerator(
|
|
887
|
+
performance_column="perf",
|
|
888
|
+
column_names=base_cn,
|
|
889
|
+
features_out=[RatingKnownFeatures.PLAYER_OFF_RATING],
|
|
890
|
+
)
|
|
891
|
+
gen.fit_transform(fit_df)
|
|
892
|
+
|
|
893
|
+
p1_rating_before = gen._player_off_ratings["P1"].rating_value
|
|
894
|
+
|
|
895
|
+
# Future transform (no performance needed, but if null it shouldn't affect anything)
|
|
896
|
+
future_df = pl.DataFrame(
|
|
897
|
+
{
|
|
898
|
+
"pid": ["P1", "P2"],
|
|
899
|
+
"tid": ["T1", "T2"],
|
|
900
|
+
"mid": ["M2", "M2"],
|
|
901
|
+
"dt": ["2024-01-02", "2024-01-02"],
|
|
902
|
+
"pw": [1.0, 1.0],
|
|
903
|
+
# No perf column - this is a future match
|
|
904
|
+
}
|
|
905
|
+
)
|
|
906
|
+
|
|
907
|
+
result = gen.future_transform(future_df)
|
|
908
|
+
|
|
909
|
+
# Should output projections for all players
|
|
910
|
+
assert len(result) == 2
|
|
911
|
+
assert "player_off_rating_perf" in result.columns
|
|
912
|
+
|
|
913
|
+
# Ratings should NOT be updated (future_transform doesn't update state)
|
|
914
|
+
assert gen._player_off_ratings["P1"].rating_value == p1_rating_before
|
|
915
|
+
|
|
916
|
+
|
|
725
917
|
# --- transform & future_transform Tests ---
|
|
726
918
|
|
|
727
919
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spforge-0.8.19 → spforge-0.8.20}/spforge/distributions/_student_t_distribution_estimator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spforge-0.8.19 → spforge-0.8.20}/spforge/performance_transformers/_performances_transformers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spforge-0.8.19 → spforge-0.8.20}/tests/end_to_end/test_nba_player_ratings_hyperparameter_tuning.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spforge-0.8.19 → spforge-0.8.20}/tests/feature_generator/test_regressor_feature_generator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spforge-0.8.19 → spforge-0.8.20}/tests/performance_transformers/test_performance_manager.py
RENAMED
|
File without changes
|
{spforge-0.8.19 → spforge-0.8.20}/tests/performance_transformers/test_performances_transformers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|