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.

@@ -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) / sum_weight * (1 - nw.col(feature_name)))
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) / sum_weight * nw.col(feature_name))
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.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=KwAga6dGhNkXi-MDW6LPjwk6VZwCcjo5L--jnk9aio8,9706
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=MyqsyLSY6d7_bxDSnF8eWOyXpSCADWGdepdFSGM4cHw,51365
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.8.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
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=bfC5GiBuzHw-mLmKeEzBUUPuKm0ayax2bsF1j88W8L0,10120
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=FGH3Tq0uFoSlkS_XMldsUKhsovBRBvzH9EbqjKvg2O0,59601
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.8.dist-info/METADATA,sha256=fO2JHqnnqOrjkWZ1Zh4rgYg58bi4YzxhSa8I72wqDs4,20047
109
- spforge-0.8.8.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
110
- spforge-0.8.8.dist-info/top_level.txt,sha256=6UW2M5a7WKOeaAi900qQmRKNj5-HZzE8-eUD9Y9LTq0,23
111
- spforge-0.8.8.dist-info/RECORD,,
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(