spforge 0.8.14__tar.gz → 0.8.16__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.14/spforge.egg-info → spforge-0.8.16}/PKG-INFO +1 -1
- {spforge-0.8.14 → spforge-0.8.16}/pyproject.toml +1 -1
- {spforge-0.8.14 → spforge-0.8.16}/spforge/autopipeline.py +20 -4
- {spforge-0.8.14 → spforge-0.8.16}/spforge/ratings/_player_rating.py +30 -8
- {spforge-0.8.14 → spforge-0.8.16}/spforge/ratings/start_rating_generator.py +1 -1
- {spforge-0.8.14 → spforge-0.8.16}/spforge/ratings/team_start_rating_generator.py +1 -1
- {spforge-0.8.14 → spforge-0.8.16/spforge.egg-info}/PKG-INFO +1 -1
- {spforge-0.8.14 → spforge-0.8.16}/tests/ratings/test_player_rating_generator.py +70 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/test_autopipeline.py +22 -0
- {spforge-0.8.14 → spforge-0.8.16}/LICENSE +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/MANIFEST.in +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/README.md +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/examples/__init__.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/examples/game_level_example.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/examples/lol/__init__.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/examples/lol/data/__init__.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/examples/lol/data/subsample_lol_data.parquet +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/examples/lol/data/utils.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/examples/lol/pipeline_transformer_example.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/examples/nba/__init__.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/examples/nba/cross_validation_example.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/examples/nba/data/__init__.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/examples/nba/data/game_player_subsample.parquet +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/examples/nba/data/utils.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/examples/nba/feature_engineering_example.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/examples/nba/game_winner_example.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/examples/nba/predictor_transformers_example.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/setup.cfg +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/__init__.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/base_feature_generator.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/cross_validator/__init__.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/cross_validator/_base.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/cross_validator/cross_validator.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/data_structures.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/distributions/__init__.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/distributions/_negative_binomial_estimator.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/distributions/_normal_distribution_predictor.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/distributions/_student_t_distribution_estimator.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/estimator/__init__.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/estimator/_conditional_estimator.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/estimator/_frequency_bucketing_classifier.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/estimator/_granularity_estimator.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/estimator/_group_by_estimator.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/estimator/_ordinal_classifier.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/estimator/_sklearn_enhancer_estimator.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/feature_generator/__init__.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/feature_generator/_base.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/feature_generator/_lag.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/feature_generator/_net_over_predicted.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/feature_generator/_regressor_feature_generator.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/feature_generator/_rolling_against_opponent.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/feature_generator/_rolling_mean_binary.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/feature_generator/_rolling_mean_days.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/feature_generator/_rolling_window.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/feature_generator/_utils.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/features_generator_pipeline.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/hyperparameter_tuning/__init__.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/hyperparameter_tuning/_default_search_spaces.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/hyperparameter_tuning/_tuner.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/performance_transformers/__init__.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/performance_transformers/_performance_manager.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/performance_transformers/_performances_transformers.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/ratings/__init__.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/ratings/_base.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/ratings/_team_rating.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/ratings/enums.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/ratings/league_identifier.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/ratings/league_start_rating_optimizer.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/ratings/player_performance_predictor.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/ratings/team_performance_predictor.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/ratings/utils.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/scorer/__init__.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/scorer/_score.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/transformers/__init__.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/transformers/_base.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/transformers/_net_over_predicted.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/transformers/_operator.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/transformers/_other_transformer.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/transformers/_predictor.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/transformers/_simple_transformer.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/transformers/_team_ratio_predictor.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge/utils.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge.egg-info/SOURCES.txt +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge.egg-info/dependency_links.txt +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge.egg-info/requires.txt +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/spforge.egg-info/top_level.txt +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/cross_validator/test_cross_validator.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/distributions/test_distribution.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/end_to_end/test_estimator_hyperparameter_tuning.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/end_to_end/test_league_start_rating_optimizer.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/end_to_end/test_lol_player_kills.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/end_to_end/test_nba_player_points.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/end_to_end/test_nba_player_ratings_hyperparameter_tuning.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/end_to_end/test_nba_prediction_consistency.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/estimator/test_sklearn_estimator.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/feature_generator/test_lag.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/feature_generator/test_regressor_feature_generator.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/feature_generator/test_rolling_against_opponent.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/feature_generator/test_rolling_mean_binary.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/feature_generator/test_rolling_mean_days.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/feature_generator/test_rolling_window.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/hyperparameter_tuning/test_estimator_tuner.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/hyperparameter_tuning/test_rating_tuner.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/performance_transformers/test_performance_manager.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/performance_transformers/test_performances_transformers.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/ratings/test_player_rating_no_mutation.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/ratings/test_ratings_property.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/ratings/test_team_rating_generator.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/ratings/test_utils_scaled_weights.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/scorer/test_score.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/scorer/test_score_aggregation_granularity.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/test_autopipeline_context.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/test_feature_generator_pipeline.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/transformers/test_estimator_transformer_context.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/transformers/test_net_over_predicted.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/transformers/test_other_transformer.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/transformers/test_predictor_transformer.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/tests/transformers/test_simple_transformer.py +0 -0
- {spforge-0.8.14 → spforge-0.8.16}/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.16
|
|
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.16"
|
|
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"
|
|
@@ -264,6 +264,7 @@ class AutoPipeline(BaseEstimator):
|
|
|
264
264
|
self.numeric_features = numeric_features
|
|
265
265
|
self.remainder = remainder
|
|
266
266
|
self._cat_feats = []
|
|
267
|
+
self._filter_feature_names: list[str] = []
|
|
267
268
|
|
|
268
269
|
# Auto-compute context features
|
|
269
270
|
self.context_feature_names = self._compute_context_features()
|
|
@@ -276,11 +277,12 @@ class AutoPipeline(BaseEstimator):
|
|
|
276
277
|
self._resolved_categorical_handling: CategoricalHandling | None = None
|
|
277
278
|
|
|
278
279
|
def _compute_context_features(self) -> list[str]:
|
|
279
|
-
"""Auto-compute context features from estimator
|
|
280
|
+
"""Auto-compute context features from estimator and granularity.
|
|
280
281
|
|
|
281
282
|
Note: Context from predictor_transformers is tracked separately in
|
|
282
283
|
context_predictor_transformer_feature_names and is dropped before
|
|
283
|
-
the final estimator.
|
|
284
|
+
the final estimator. Filter columns are tracked separately and are
|
|
285
|
+
dropped before the final estimator.
|
|
284
286
|
"""
|
|
285
287
|
from spforge.transformers._base import PredictorTransformer
|
|
286
288
|
|
|
@@ -325,8 +327,10 @@ class AutoPipeline(BaseEstimator):
|
|
|
325
327
|
context.extend(self.granularity)
|
|
326
328
|
|
|
327
329
|
# Add filter columns
|
|
330
|
+
self._filter_feature_names = []
|
|
328
331
|
for f in self.filters:
|
|
329
|
-
|
|
332
|
+
if f.column_name not in self._filter_feature_names:
|
|
333
|
+
self._filter_feature_names.append(f.column_name)
|
|
330
334
|
|
|
331
335
|
# Dedupe while preserving order, excluding estimator_features
|
|
332
336
|
seen = set()
|
|
@@ -540,8 +544,10 @@ class AutoPipeline(BaseEstimator):
|
|
|
540
544
|
prev_transformer_feats_out.extend(feats_out)
|
|
541
545
|
|
|
542
546
|
# Use FunctionTransformer with global function for serializability
|
|
547
|
+
drop_filter_cols = set(self._filter_feature_names)
|
|
548
|
+
drop_cols = drop_ctx_set | drop_filter_cols
|
|
543
549
|
final = FunctionTransformer(
|
|
544
|
-
_drop_columns_transformer, validate=False, kw_args={"drop_cols":
|
|
550
|
+
_drop_columns_transformer, validate=False, kw_args={"drop_cols": drop_cols}
|
|
545
551
|
)
|
|
546
552
|
steps.append(("final", final))
|
|
547
553
|
|
|
@@ -572,6 +578,7 @@ class AutoPipeline(BaseEstimator):
|
|
|
572
578
|
self.feature_names
|
|
573
579
|
+ self.context_feature_names
|
|
574
580
|
+ self.context_predictor_transformer_feature_names
|
|
581
|
+
+ self._filter_feature_names
|
|
575
582
|
+ self.granularity
|
|
576
583
|
)
|
|
577
584
|
)
|
|
@@ -660,6 +667,11 @@ class AutoPipeline(BaseEstimator):
|
|
|
660
667
|
if ctx not in all_features:
|
|
661
668
|
all_features.append(ctx)
|
|
662
669
|
|
|
670
|
+
# Add filter columns (needed for fit-time filtering)
|
|
671
|
+
for col in self._filter_feature_names:
|
|
672
|
+
if col not in all_features:
|
|
673
|
+
all_features.append(col)
|
|
674
|
+
|
|
663
675
|
return all_features
|
|
664
676
|
|
|
665
677
|
def _get_estimator_feature_names(self) -> list[str]:
|
|
@@ -679,6 +691,10 @@ class AutoPipeline(BaseEstimator):
|
|
|
679
691
|
context_set = set(self.context_feature_names)
|
|
680
692
|
features = [f for f in features if f not in context_set]
|
|
681
693
|
|
|
694
|
+
# Remove filter columns (used only for fit-time filtering)
|
|
695
|
+
filter_set = set(self._filter_feature_names)
|
|
696
|
+
features = [f for f in features if f not in filter_set]
|
|
697
|
+
|
|
682
698
|
return features
|
|
683
699
|
|
|
684
700
|
def _resolve_importance_feature_names(self, estimator, n_features: int) -> list[str]:
|
|
@@ -16,6 +16,7 @@ from spforge.data_structures import (
|
|
|
16
16
|
MatchPerformance,
|
|
17
17
|
MatchPlayer,
|
|
18
18
|
PlayerRating,
|
|
19
|
+
PlayerRatingChange,
|
|
19
20
|
PlayerRatingsResult,
|
|
20
21
|
PreMatchPlayerRating,
|
|
21
22
|
PreMatchPlayersCollection,
|
|
@@ -78,7 +79,7 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
78
79
|
start_min_count_for_percentiles: int = 50,
|
|
79
80
|
start_team_rating_subtract: float = 80,
|
|
80
81
|
start_team_weight: float = 0,
|
|
81
|
-
start_max_days_ago_league_entities: int =
|
|
82
|
+
start_max_days_ago_league_entities: int = 600,
|
|
82
83
|
start_min_match_count_team_rating: int = 2,
|
|
83
84
|
start_harcoded_start_rating: float | None = None,
|
|
84
85
|
column_names: ColumnNames | None = None,
|
|
@@ -442,9 +443,9 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
442
443
|
team1_off_rating, team1_def_rating = self._team_off_def_rating_from_collection(c1)
|
|
443
444
|
team2_off_rating, team2_def_rating = self._team_off_def_rating_from_collection(c2)
|
|
444
445
|
|
|
445
|
-
player_updates: list[
|
|
446
|
-
[]
|
|
447
|
-
|
|
446
|
+
player_updates: list[
|
|
447
|
+
tuple[str, str, float, float, float, float, float, float, int, str | None]
|
|
448
|
+
] = []
|
|
448
449
|
|
|
449
450
|
for pre_player in c1.pre_match_player_ratings:
|
|
450
451
|
pid = pre_player.id
|
|
@@ -520,6 +521,7 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
520
521
|
float(off_change),
|
|
521
522
|
float(def_change),
|
|
522
523
|
day_number,
|
|
524
|
+
pre_player.league,
|
|
523
525
|
)
|
|
524
526
|
)
|
|
525
527
|
|
|
@@ -597,6 +599,7 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
597
599
|
float(off_change),
|
|
598
600
|
float(def_change),
|
|
599
601
|
day_number,
|
|
602
|
+
pre_player.league,
|
|
600
603
|
)
|
|
601
604
|
)
|
|
602
605
|
|
|
@@ -611,6 +614,7 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
611
614
|
_off_change,
|
|
612
615
|
_def_change,
|
|
613
616
|
_dn,
|
|
617
|
+
_league,
|
|
614
618
|
) in player_updates:
|
|
615
619
|
out[cn.player_id].append(pid)
|
|
616
620
|
out[cn.match_id].append(match_id)
|
|
@@ -627,15 +631,18 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
627
631
|
for (
|
|
628
632
|
pid,
|
|
629
633
|
team_id,
|
|
630
|
-
|
|
634
|
+
off_pre,
|
|
631
635
|
_def_pre,
|
|
632
636
|
_pred_off,
|
|
633
637
|
_pred_def,
|
|
634
638
|
off_change,
|
|
635
639
|
def_change,
|
|
636
640
|
dn,
|
|
641
|
+
league,
|
|
637
642
|
) in player_updates:
|
|
638
|
-
pending_team_updates.append(
|
|
643
|
+
pending_team_updates.append(
|
|
644
|
+
(pid, team_id, off_pre, off_change, def_change, dn, league)
|
|
645
|
+
)
|
|
639
646
|
|
|
640
647
|
if last_update_id is None:
|
|
641
648
|
last_update_id = update_id
|
|
@@ -645,9 +652,11 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
645
652
|
|
|
646
653
|
return pl.DataFrame(out, strict=False)
|
|
647
654
|
|
|
648
|
-
def _apply_player_updates(
|
|
655
|
+
def _apply_player_updates(
|
|
656
|
+
self, updates: list[tuple[str, str, float, float, float, int, str | None]]
|
|
657
|
+
) -> None:
|
|
649
658
|
|
|
650
|
-
for player_id, team_id, off_change, def_change, day_number in updates:
|
|
659
|
+
for player_id, team_id, pre_rating, off_change, def_change, day_number, league in updates:
|
|
651
660
|
off_state = self._player_off_ratings[player_id]
|
|
652
661
|
off_state.confidence_sum = self._calculate_post_match_confidence_sum(
|
|
653
662
|
entity_rating=off_state,
|
|
@@ -670,6 +679,19 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
670
679
|
def_state.last_match_day_number = int(day_number)
|
|
671
680
|
def_state.most_recent_team_id = team_id
|
|
672
681
|
|
|
682
|
+
self.start_rating_generator.update_players_to_leagues(
|
|
683
|
+
PlayerRatingChange(
|
|
684
|
+
id=player_id,
|
|
685
|
+
day_number=day_number,
|
|
686
|
+
league=league,
|
|
687
|
+
participation_weight=1.0,
|
|
688
|
+
predicted_performance=0.0,
|
|
689
|
+
performance=0.0,
|
|
690
|
+
pre_match_rating_value=pre_rating,
|
|
691
|
+
rating_change_value=off_change,
|
|
692
|
+
)
|
|
693
|
+
)
|
|
694
|
+
|
|
673
695
|
def _add_rating_features(self, df: pl.DataFrame) -> pl.DataFrame:
|
|
674
696
|
cols_to_add = set((self._features_out or []) + (self.non_predictor_features_out or []))
|
|
675
697
|
|
|
@@ -28,7 +28,7 @@ class StartRatingGenerator:
|
|
|
28
28
|
min_count_for_percentiles: int = 50,
|
|
29
29
|
team_rating_subtract: float = 80,
|
|
30
30
|
team_weight: float = 0,
|
|
31
|
-
max_days_ago_league_entities: int =
|
|
31
|
+
max_days_ago_league_entities: int = 600,
|
|
32
32
|
min_match_count_team_rating: int = 2,
|
|
33
33
|
harcoded_start_rating: float | None = None,
|
|
34
34
|
):
|
|
@@ -24,7 +24,7 @@ class TeamStartRatingGenerator:
|
|
|
24
24
|
league_ratings: dict[str, float] | None = None,
|
|
25
25
|
league_quantile: float = 0.2,
|
|
26
26
|
min_count_for_percentiles: int = 50,
|
|
27
|
-
max_days_ago_league_entities: int =
|
|
27
|
+
max_days_ago_league_entities: int = 600,
|
|
28
28
|
min_match_count_team_rating: int = 2,
|
|
29
29
|
harcoded_start_rating: float | None = None,
|
|
30
30
|
):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spforge
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.16
|
|
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
|
|
@@ -1746,3 +1746,73 @@ def test_fit_transform__player_rating_difference_from_team_projected_feature(bas
|
|
|
1746
1746
|
for row in result.iter_rows(named=True):
|
|
1747
1747
|
expected = row[player_col] - row[team_col]
|
|
1748
1748
|
assert row[diff_col] == pytest.approx(expected, rel=1e-9)
|
|
1749
|
+
|
|
1750
|
+
|
|
1751
|
+
def test_fit_transform__start_league_quantile_uses_existing_player_ratings(base_cn):
|
|
1752
|
+
"""
|
|
1753
|
+
Bug reproduction: start_league_quantile should use percentile of existing player
|
|
1754
|
+
ratings for new players, but update_players_to_leagues is never called so
|
|
1755
|
+
_league_player_ratings stays empty and all new players get default rating.
|
|
1756
|
+
|
|
1757
|
+
Expected: New player P_NEW should start at 5th percentile of existing ratings (~920)
|
|
1758
|
+
Actual: New player starts at default 1000 because _league_player_ratings is empty
|
|
1759
|
+
"""
|
|
1760
|
+
import numpy as np
|
|
1761
|
+
|
|
1762
|
+
num_existing_players = 60
|
|
1763
|
+
player_ids = [f"P{i}" for i in range(num_existing_players)]
|
|
1764
|
+
team_ids = [f"T{i % 2 + 1}" for i in range(num_existing_players)]
|
|
1765
|
+
|
|
1766
|
+
df1 = pl.DataFrame(
|
|
1767
|
+
{
|
|
1768
|
+
"pid": player_ids,
|
|
1769
|
+
"tid": team_ids,
|
|
1770
|
+
"mid": ["M1"] * num_existing_players,
|
|
1771
|
+
"dt": ["2024-01-01"] * num_existing_players,
|
|
1772
|
+
"perf": [0.3 + (i % 10) * 0.07 for i in range(num_existing_players)],
|
|
1773
|
+
"pw": [1.0] * num_existing_players,
|
|
1774
|
+
}
|
|
1775
|
+
)
|
|
1776
|
+
|
|
1777
|
+
gen = PlayerRatingGenerator(
|
|
1778
|
+
performance_column="perf",
|
|
1779
|
+
column_names=base_cn,
|
|
1780
|
+
auto_scale_performance=True,
|
|
1781
|
+
start_league_quantile=0.05,
|
|
1782
|
+
start_min_count_for_percentiles=50,
|
|
1783
|
+
features_out=[RatingKnownFeatures.PLAYER_OFF_RATING],
|
|
1784
|
+
)
|
|
1785
|
+
gen.fit_transform(df1)
|
|
1786
|
+
|
|
1787
|
+
existing_ratings = [
|
|
1788
|
+
gen._player_off_ratings[pid].rating_value for pid in player_ids
|
|
1789
|
+
]
|
|
1790
|
+
expected_quantile_rating = np.percentile(existing_ratings, 5)
|
|
1791
|
+
|
|
1792
|
+
srg = gen.start_rating_generator
|
|
1793
|
+
assert len(srg._league_player_ratings.get(None, [])) >= 50, (
|
|
1794
|
+
f"Expected _league_player_ratings to have >=50 entries but got "
|
|
1795
|
+
f"{len(srg._league_player_ratings.get(None, []))}. "
|
|
1796
|
+
"update_players_to_leagues is never called."
|
|
1797
|
+
)
|
|
1798
|
+
|
|
1799
|
+
df2 = pl.DataFrame(
|
|
1800
|
+
{
|
|
1801
|
+
"pid": ["P_NEW", "P0"],
|
|
1802
|
+
"tid": ["T1", "T2"],
|
|
1803
|
+
"mid": ["M2", "M2"],
|
|
1804
|
+
"dt": ["2024-01-02", "2024-01-02"],
|
|
1805
|
+
"pw": [1.0, 1.0],
|
|
1806
|
+
}
|
|
1807
|
+
)
|
|
1808
|
+
result = gen.future_transform(df2)
|
|
1809
|
+
|
|
1810
|
+
new_player_start_rating = result.filter(pl.col("pid") == "P_NEW")[
|
|
1811
|
+
"player_off_rating_perf"
|
|
1812
|
+
][0]
|
|
1813
|
+
|
|
1814
|
+
assert new_player_start_rating == pytest.approx(expected_quantile_rating, rel=0.1), (
|
|
1815
|
+
f"New player should start at 5th percentile ({expected_quantile_rating:.1f}) "
|
|
1816
|
+
f"but got {new_player_start_rating:.1f}. "
|
|
1817
|
+
"start_league_quantile has no effect because update_players_to_leagues is never called."
|
|
1818
|
+
)
|
|
@@ -12,6 +12,7 @@ from sklearn.linear_model import LinearRegression, LogisticRegression
|
|
|
12
12
|
|
|
13
13
|
from spforge import AutoPipeline
|
|
14
14
|
from spforge.estimator import SkLearnEnhancerEstimator
|
|
15
|
+
from spforge.scorer import Filter, Operator
|
|
15
16
|
from spforge.transformers import EstimatorTransformer
|
|
16
17
|
|
|
17
18
|
|
|
@@ -231,6 +232,27 @@ def test_predict_proba(df_clf):
|
|
|
231
232
|
assert np.allclose(proba.sum(axis=1), 1.0, atol=1e-6)
|
|
232
233
|
|
|
233
234
|
|
|
235
|
+
def test_filter_columns_not_passed_to_estimator(frame):
|
|
236
|
+
df_pd = pd.DataFrame(
|
|
237
|
+
{"x": [1.0, 2.0, 3.0, 4.0], "keep": [1, 0, 1, 0], "y": [1.0, 2.0, 3.0, 4.0]}
|
|
238
|
+
)
|
|
239
|
+
df = df_pd if frame == "pd" else pl.from_pandas(df_pd)
|
|
240
|
+
|
|
241
|
+
model = AutoPipeline(
|
|
242
|
+
estimator=CaptureEstimator(),
|
|
243
|
+
estimator_features=["x"],
|
|
244
|
+
filters=[Filter(column_name="keep", value=1, operator=Operator.EQUALS)],
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
X = _select(df, ["x", "keep"])
|
|
248
|
+
y = _col(df, "y")
|
|
249
|
+
model.fit(X, y=y)
|
|
250
|
+
|
|
251
|
+
est = _inner_estimator(model)
|
|
252
|
+
assert "keep" in model.required_features
|
|
253
|
+
assert "keep" not in est.fit_columns
|
|
254
|
+
|
|
255
|
+
|
|
234
256
|
def test_predict_proba_raises_if_not_supported(df_reg):
|
|
235
257
|
model = AutoPipeline(
|
|
236
258
|
estimator=LinearRegression(),
|
|
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.14 → spforge-0.8.16}/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.14 → spforge-0.8.16}/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
|
{spforge-0.8.14 → spforge-0.8.16}/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.14 → spforge-0.8.16}/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.14 → spforge-0.8.16}/tests/performance_transformers/test_performance_manager.py
RENAMED
|
File without changes
|
{spforge-0.8.14 → spforge-0.8.16}/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
|