spforge 0.8.8__py3-none-any.whl → 0.8.10__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/performance_transformers/_performance_manager.py +2 -4
- spforge/ratings/_player_rating.py +33 -0
- {spforge-0.8.8.dist-info → spforge-0.8.10.dist-info}/METADATA +1 -1
- {spforge-0.8.8.dist-info → spforge-0.8.10.dist-info}/RECORD +9 -9
- tests/performance_transformers/test_performance_manager.py +15 -0
- tests/ratings/test_player_rating_generator.py +57 -0
- {spforge-0.8.8.dist-info → spforge-0.8.10.dist-info}/WHEEL +0 -0
- {spforge-0.8.8.dist-info → spforge-0.8.10.dist-info}/licenses/LICENSE +0 -0
- {spforge-0.8.8.dist-info → spforge-0.8.10.dist-info}/top_level.txt +0 -0
|
@@ -250,8 +250,6 @@ class PerformanceWeightsManager(PerformanceManager):
|
|
|
250
250
|
)
|
|
251
251
|
)
|
|
252
252
|
|
|
253
|
-
sum_weight = sum([w.weight for w in self.weights])
|
|
254
|
-
|
|
255
253
|
for column_weight in self.weights:
|
|
256
254
|
weight_col = f"weight__{column_weight.name}"
|
|
257
255
|
feature_col = column_weight.name
|
|
@@ -261,14 +259,14 @@ class PerformanceWeightsManager(PerformanceManager):
|
|
|
261
259
|
df = df.with_columns(
|
|
262
260
|
(
|
|
263
261
|
nw.col(tmp_out_performance_colum_name)
|
|
264
|
-
+ (nw.col(weight_col)
|
|
262
|
+
+ (nw.col(weight_col) * (1 - nw.col(feature_name)))
|
|
265
263
|
).alias(tmp_out_performance_colum_name)
|
|
266
264
|
)
|
|
267
265
|
else:
|
|
268
266
|
df = df.with_columns(
|
|
269
267
|
(
|
|
270
268
|
nw.col(tmp_out_performance_colum_name)
|
|
271
|
-
+ (nw.col(weight_col)
|
|
269
|
+
+ (nw.col(weight_col) * nw.col(feature_name))
|
|
272
270
|
).alias(tmp_out_performance_colum_name)
|
|
273
271
|
)
|
|
274
272
|
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import copy
|
|
5
5
|
import math
|
|
6
|
+
import logging
|
|
6
7
|
from typing import Any, Literal
|
|
7
8
|
|
|
8
9
|
import narwhals.stable.v2 as nw
|
|
@@ -81,6 +82,7 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
81
82
|
column_names: ColumnNames | None = None,
|
|
82
83
|
output_suffix: str | None = None,
|
|
83
84
|
scale_participation_weights: bool = False,
|
|
85
|
+
auto_scale_participation_weights: bool = True,
|
|
84
86
|
**kwargs: Any,
|
|
85
87
|
):
|
|
86
88
|
super().__init__(
|
|
@@ -164,6 +166,7 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
164
166
|
|
|
165
167
|
self.use_off_def_split = bool(use_off_def_split)
|
|
166
168
|
self.scale_participation_weights = bool(scale_participation_weights)
|
|
169
|
+
self.auto_scale_participation_weights = bool(auto_scale_participation_weights)
|
|
167
170
|
self._participation_weight_max: float | None = None
|
|
168
171
|
self._projected_participation_weight_max: float | None = None
|
|
169
172
|
|
|
@@ -189,9 +192,39 @@ class PlayerRatingGenerator(RatingGenerator):
|
|
|
189
192
|
column_names: ColumnNames | None = None,
|
|
190
193
|
) -> DataFrame | IntoFrameT:
|
|
191
194
|
self.column_names = column_names if column_names else self.column_names
|
|
195
|
+
self._maybe_enable_participation_weight_scaling(df)
|
|
192
196
|
self._set_participation_weight_max(df)
|
|
193
197
|
return super().fit_transform(df, column_names)
|
|
194
198
|
|
|
199
|
+
def _maybe_enable_participation_weight_scaling(self, df: DataFrame) -> None:
|
|
200
|
+
if self.scale_participation_weights or not self.auto_scale_participation_weights:
|
|
201
|
+
return
|
|
202
|
+
cn = self.column_names
|
|
203
|
+
if not cn:
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
pl_df = df.to_native() if df.implementation.is_polars() else df.to_polars().to_native()
|
|
207
|
+
|
|
208
|
+
def _out_of_bounds(col_name: str | None) -> bool:
|
|
209
|
+
if not col_name or col_name not in df.columns:
|
|
210
|
+
return False
|
|
211
|
+
col = pl_df[col_name]
|
|
212
|
+
min_val = col.min()
|
|
213
|
+
max_val = col.max()
|
|
214
|
+
if min_val is None or max_val is None:
|
|
215
|
+
return False
|
|
216
|
+
eps = 1e-6
|
|
217
|
+
return min_val < -eps or max_val > (1.0 + eps)
|
|
218
|
+
|
|
219
|
+
if _out_of_bounds(cn.participation_weight) or _out_of_bounds(
|
|
220
|
+
cn.projected_participation_weight
|
|
221
|
+
):
|
|
222
|
+
self.scale_participation_weights = True
|
|
223
|
+
logging.warning(
|
|
224
|
+
"Auto-scaling participation weights because values exceed [0, 1]. "
|
|
225
|
+
"Set scale_participation_weights=True explicitly to silence this warning."
|
|
226
|
+
)
|
|
227
|
+
|
|
195
228
|
def _ensure_player_off(self, player_id: str) -> PlayerRating:
|
|
196
229
|
if player_id not in self._player_off_ratings:
|
|
197
230
|
# create with start generator later; initialize to 0 now; overwritten when needed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spforge
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.10
|
|
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
|
|
@@ -47,11 +47,11 @@ spforge/hyperparameter_tuning/__init__.py,sha256=N2sKG4SvG41hlsFT2kx_DQYMmXsQr-8
|
|
|
47
47
|
spforge/hyperparameter_tuning/_default_search_spaces.py,sha256=Sm5IrHAW0-vRC8jqCPX0pDi_C-W3L_MoEKGA8bx1Zbc,7546
|
|
48
48
|
spforge/hyperparameter_tuning/_tuner.py,sha256=uovhGqhe8-fdhi79aErUmE2h5NCycFQEIRv5WCjpC7E,16732
|
|
49
49
|
spforge/performance_transformers/__init__.py,sha256=U6d7_kltbUMLYCGBk4QAFVPJTxXD3etD9qUftV-O3q4,422
|
|
50
|
-
spforge/performance_transformers/_performance_manager.py,sha256=
|
|
50
|
+
spforge/performance_transformers/_performance_manager.py,sha256=WmjmlMEnq7y75MiI_s9Y-9eMXIyhPTUKrwsXRtgYp0k,9620
|
|
51
51
|
spforge/performance_transformers/_performances_transformers.py,sha256=0lxuWjAfWBRXRgQsNJHjw3P-nlTtHBu4_bOVdoy7hq4,15536
|
|
52
52
|
spforge/ratings/__init__.py,sha256=OZVH2Lo6END3n1X8qi4QcyAPlThIwAYwVKCiIuOQSQU,576
|
|
53
53
|
spforge/ratings/_base.py,sha256=dRMkIGj5-2zKddygaEA4g16WCyXon7v8Xa1ymm7IuoM,14335
|
|
54
|
-
spforge/ratings/_player_rating.py,sha256=
|
|
54
|
+
spforge/ratings/_player_rating.py,sha256=6OFINaJPM2CXgcwHEjMx9v6uMjv5HyLZVo2qYECX7ek,52744
|
|
55
55
|
spforge/ratings/_team_rating.py,sha256=T0kFiv3ykYSrVGGsVRa8ZxLB0WMnagxqdFDzl9yZ_9g,24813
|
|
56
56
|
spforge/ratings/enums.py,sha256=s7z_RcZS6Nlgfa_6tasO8_IABZJwywexe7sep9DJBgo,1739
|
|
57
57
|
spforge/ratings/league_identifier.py,sha256=_KDUKOwoNU6RNFKE5jju4eYFGVNGBdJsv5mhNvMakfc,6019
|
|
@@ -71,7 +71,7 @@ spforge/transformers/_other_transformer.py,sha256=xLfaFIhkFsigAoitB4x3F8An2j9ymd
|
|
|
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.10.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
75
75
|
tests/test_autopipeline.py,sha256=WXHeqBdjQD6xaXVkzvS8ocz0WVP9R7lN0PiHJ2iD8nA,16911
|
|
76
76
|
tests/test_autopipeline_context.py,sha256=IuRUY4IA6uMObvbl2pXSaXO2_tl3qX6wEbTZY0dkTMI,1240
|
|
77
77
|
tests/test_feature_generator_pipeline.py,sha256=CK0zVL8PfTncy3RmG9i-YpgwjOIV7yJhV7Q44tbetI8,19020
|
|
@@ -92,9 +92,9 @@ tests/feature_generator/test_rolling_mean_days.py,sha256=EyOvdJDnmgPfe13uQBOkwo7
|
|
|
92
92
|
tests/feature_generator/test_rolling_window.py,sha256=YBJo36OK3ILYeXrH06ylXqviUcCaGYaVQaK5RJzwM7Y,23239
|
|
93
93
|
tests/hyperparameter_tuning/test_estimator_tuner.py,sha256=iewME41d6LR2aQ0OtohGFtN_ocJUwTeqvs6L0QDmfG4,4413
|
|
94
94
|
tests/hyperparameter_tuning/test_rating_tuner.py,sha256=PyCFP3KPc4Iy9E_X9stCVxra14uMgC1tuRwuQ30rO_o,13195
|
|
95
|
-
tests/performance_transformers/test_performance_manager.py,sha256=
|
|
95
|
+
tests/performance_transformers/test_performance_manager.py,sha256=gjuuV_hb27kCo_kUecPKG3Cbot2Gqis1W3kw2A4ovS4,10690
|
|
96
96
|
tests/performance_transformers/test_performances_transformers.py,sha256=A-tGiCx7kXrj1cVj03Bc7prOeZ1_Ryz8YFx9uj3eK6w,11064
|
|
97
|
-
tests/ratings/test_player_rating_generator.py,sha256=
|
|
97
|
+
tests/ratings/test_player_rating_generator.py,sha256=NRD5OtCGBpdWjyup6YCKrt0sIz_GiIxB3l149gr44ts,61543
|
|
98
98
|
tests/ratings/test_ratings_property.py,sha256=ckyfGILXa4tfQvsgyXEzBDNr2DUmHwFRV13N60w66iE,6561
|
|
99
99
|
tests/ratings/test_team_rating_generator.py,sha256=cDnf1zHiYC7pkgydE3MYr8wSTJIq-bPfSqhIRI_4Tic,95357
|
|
100
100
|
tests/scorer/test_score.py,sha256=_Vd6tKpy_1GeOxU7Omxci4CFf7PvRGMefEI0gv2gV6A,74688
|
|
@@ -105,7 +105,7 @@ tests/transformers/test_other_transformer.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
|
|
|
105
105
|
tests/transformers/test_predictor_transformer.py,sha256=N1aBYLjN3ldpYZLwjih_gTFYSMitrZu-PNK78W6RHaQ,6877
|
|
106
106
|
tests/transformers/test_simple_transformer.py,sha256=wWR0qjLb_uS4HXrJgGdiqugOY1X7kwd1_OPS02IT2b8,4676
|
|
107
107
|
tests/transformers/test_team_ratio_predictor.py,sha256=fOUP_JvNJi-3kom3ZOs1EdG0I6Z8hpLpYKNHu1eWtOw,8562
|
|
108
|
-
spforge-0.8.
|
|
109
|
-
spforge-0.8.
|
|
110
|
-
spforge-0.8.
|
|
111
|
-
spforge-0.8.
|
|
108
|
+
spforge-0.8.10.dist-info/METADATA,sha256=k4X8MbDNbryeV2jKH4wPODprRVLFlzrNKCDqdMZjYok,20048
|
|
109
|
+
spforge-0.8.10.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
110
|
+
spforge-0.8.10.dist-info/top_level.txt,sha256=6UW2M5a7WKOeaAi900qQmRKNj5-HZzE8-eUD9Y9LTq0,23
|
|
111
|
+
spforge-0.8.10.dist-info/RECORD,,
|
|
@@ -56,6 +56,21 @@ def test_performance_weights_manager_basic_flow(sample_data):
|
|
|
56
56
|
assert output_df["weighted_performance"].iloc[0] == pytest.approx(0.6)
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
def test_performance_weights_manager_keeps_mean_when_weights_not_normalized():
|
|
60
|
+
df = pd.DataFrame(
|
|
61
|
+
{
|
|
62
|
+
"feat_a": [0.0, 1.0, 2.0, 3.0],
|
|
63
|
+
"feat_b": [3.0, 2.0, 1.0, 0.0],
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
weights = [ColumnWeight(name="feat_a", weight=0.9), ColumnWeight(name="feat_b", weight=0.5)]
|
|
67
|
+
|
|
68
|
+
manager = PerformanceWeightsManager(weights=weights, transformer_names=["min_max"], prefix="")
|
|
69
|
+
output_df = nw.from_native(manager.fit_transform(df)).to_pandas()
|
|
70
|
+
|
|
71
|
+
assert output_df["weighted_performance"].mean() == pytest.approx(0.5, abs=1e-6)
|
|
72
|
+
|
|
73
|
+
|
|
59
74
|
def test_lower_is_better_logic():
|
|
60
75
|
df = pd.DataFrame({"feat_a": [1.0, 0.0]})
|
|
61
76
|
weights = [ColumnWeight(name="feat_a", weight=1.0, lower_is_better=True)]
|
|
@@ -551,6 +551,63 @@ def test_fit_transform_scales_participation_weight_by_fit_quantile(base_cn):
|
|
|
551
551
|
assert p1_change / p2_change == pytest.approx(expected_ratio, rel=1e-6)
|
|
552
552
|
|
|
553
553
|
|
|
554
|
+
def test_fit_transform_auto_scales_participation_weight_when_out_of_bounds(base_cn):
|
|
555
|
+
"""Automatically enable scaling when participation weights exceed [0, 1]."""
|
|
556
|
+
df = pl.DataFrame(
|
|
557
|
+
{
|
|
558
|
+
"pid": ["P1", "P2", "O1", "O2"],
|
|
559
|
+
"tid": ["T1", "T1", "T2", "T2"],
|
|
560
|
+
"mid": ["M1", "M1", "M1", "M1"],
|
|
561
|
+
"dt": ["2024-01-01"] * 4,
|
|
562
|
+
"perf": [0.9, 0.9, 0.1, 0.1],
|
|
563
|
+
"pw": [10.0, 20.0, 10.0, 10.0],
|
|
564
|
+
}
|
|
565
|
+
)
|
|
566
|
+
gen = PlayerRatingGenerator(
|
|
567
|
+
performance_column="perf",
|
|
568
|
+
column_names=base_cn,
|
|
569
|
+
auto_scale_performance=True,
|
|
570
|
+
start_harcoded_start_rating=1000.0,
|
|
571
|
+
)
|
|
572
|
+
gen.fit_transform(df)
|
|
573
|
+
|
|
574
|
+
start_rating = 1000.0
|
|
575
|
+
p1_change = gen._player_off_ratings["P1"].rating_value - start_rating
|
|
576
|
+
p2_change = gen._player_off_ratings["P2"].rating_value - start_rating
|
|
577
|
+
|
|
578
|
+
q = df["pw"].quantile(0.99, "linear")
|
|
579
|
+
expected_ratio = min(1.0, 10.0 / q) / min(1.0, 20.0 / q)
|
|
580
|
+
|
|
581
|
+
assert gen.scale_participation_weights is True
|
|
582
|
+
assert p1_change / p2_change == pytest.approx(expected_ratio, rel=1e-6)
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def test_fit_transform_auto_scale_logs_warning_when_out_of_bounds(base_cn, caplog):
|
|
586
|
+
"""Auto-scaling should emit a warning when participation weights exceed [0, 1]."""
|
|
587
|
+
df = pl.DataFrame(
|
|
588
|
+
{
|
|
589
|
+
"pid": ["P1", "P2", "O1", "O2"],
|
|
590
|
+
"tid": ["T1", "T1", "T2", "T2"],
|
|
591
|
+
"mid": ["M1", "M1", "M1", "M1"],
|
|
592
|
+
"dt": ["2024-01-01"] * 4,
|
|
593
|
+
"perf": [0.9, 0.9, 0.1, 0.1],
|
|
594
|
+
"pw": [10.0, 20.0, 10.0, 10.0],
|
|
595
|
+
}
|
|
596
|
+
)
|
|
597
|
+
gen = PlayerRatingGenerator(
|
|
598
|
+
performance_column="perf",
|
|
599
|
+
column_names=base_cn,
|
|
600
|
+
auto_scale_performance=True,
|
|
601
|
+
start_harcoded_start_rating=1000.0,
|
|
602
|
+
)
|
|
603
|
+
with caplog.at_level("WARNING"):
|
|
604
|
+
gen.fit_transform(df)
|
|
605
|
+
|
|
606
|
+
assert any(
|
|
607
|
+
"Auto-scaling participation weights" in record.message for record in caplog.records
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
|
|
554
611
|
def test_future_transform_scales_projected_participation_weight_by_fit_quantile():
|
|
555
612
|
"""Future projected participation weights should scale with fit quantile and be clipped."""
|
|
556
613
|
cn = ColumnNames(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|