spforge 0.8.19__tar.gz → 0.8.23__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.

Files changed (119) hide show
  1. {spforge-0.8.19/spforge.egg-info → spforge-0.8.23}/PKG-INFO +1 -1
  2. {spforge-0.8.19 → spforge-0.8.23}/pyproject.toml +1 -1
  3. {spforge-0.8.19 → spforge-0.8.23}/spforge/ratings/_base.py +6 -0
  4. {spforge-0.8.19 → spforge-0.8.23}/spforge/ratings/_player_rating.py +73 -45
  5. {spforge-0.8.19 → spforge-0.8.23}/spforge/ratings/_team_rating.py +23 -20
  6. {spforge-0.8.19 → spforge-0.8.23/spforge.egg-info}/PKG-INFO +1 -1
  7. {spforge-0.8.19 → spforge-0.8.23}/tests/ratings/test_player_rating_generator.py +341 -118
  8. {spforge-0.8.19 → spforge-0.8.23}/tests/ratings/test_team_rating_generator.py +153 -11
  9. {spforge-0.8.19 → spforge-0.8.23}/LICENSE +0 -0
  10. {spforge-0.8.19 → spforge-0.8.23}/MANIFEST.in +0 -0
  11. {spforge-0.8.19 → spforge-0.8.23}/README.md +0 -0
  12. {spforge-0.8.19 → spforge-0.8.23}/examples/__init__.py +0 -0
  13. {spforge-0.8.19 → spforge-0.8.23}/examples/game_level_example.py +0 -0
  14. {spforge-0.8.19 → spforge-0.8.23}/examples/lol/__init__.py +0 -0
  15. {spforge-0.8.19 → spforge-0.8.23}/examples/lol/data/__init__.py +0 -0
  16. {spforge-0.8.19 → spforge-0.8.23}/examples/lol/data/subsample_lol_data.parquet +0 -0
  17. {spforge-0.8.19 → spforge-0.8.23}/examples/lol/data/utils.py +0 -0
  18. {spforge-0.8.19 → spforge-0.8.23}/examples/lol/pipeline_transformer_example.py +0 -0
  19. {spforge-0.8.19 → spforge-0.8.23}/examples/nba/__init__.py +0 -0
  20. {spforge-0.8.19 → spforge-0.8.23}/examples/nba/cross_validation_example.py +0 -0
  21. {spforge-0.8.19 → spforge-0.8.23}/examples/nba/data/__init__.py +0 -0
  22. {spforge-0.8.19 → spforge-0.8.23}/examples/nba/data/game_player_subsample.parquet +0 -0
  23. {spforge-0.8.19 → spforge-0.8.23}/examples/nba/data/utils.py +0 -0
  24. {spforge-0.8.19 → spforge-0.8.23}/examples/nba/feature_engineering_example.py +0 -0
  25. {spforge-0.8.19 → spforge-0.8.23}/examples/nba/game_winner_example.py +0 -0
  26. {spforge-0.8.19 → spforge-0.8.23}/examples/nba/predictor_transformers_example.py +0 -0
  27. {spforge-0.8.19 → spforge-0.8.23}/setup.cfg +0 -0
  28. {spforge-0.8.19 → spforge-0.8.23}/spforge/__init__.py +0 -0
  29. {spforge-0.8.19 → spforge-0.8.23}/spforge/autopipeline.py +0 -0
  30. {spforge-0.8.19 → spforge-0.8.23}/spforge/base_feature_generator.py +0 -0
  31. {spforge-0.8.19 → spforge-0.8.23}/spforge/cross_validator/__init__.py +0 -0
  32. {spforge-0.8.19 → spforge-0.8.23}/spforge/cross_validator/_base.py +0 -0
  33. {spforge-0.8.19 → spforge-0.8.23}/spforge/cross_validator/cross_validator.py +0 -0
  34. {spforge-0.8.19 → spforge-0.8.23}/spforge/data_structures.py +0 -0
  35. {spforge-0.8.19 → spforge-0.8.23}/spforge/distributions/__init__.py +0 -0
  36. {spforge-0.8.19 → spforge-0.8.23}/spforge/distributions/_negative_binomial_estimator.py +0 -0
  37. {spforge-0.8.19 → spforge-0.8.23}/spforge/distributions/_normal_distribution_predictor.py +0 -0
  38. {spforge-0.8.19 → spforge-0.8.23}/spforge/distributions/_student_t_distribution_estimator.py +0 -0
  39. {spforge-0.8.19 → spforge-0.8.23}/spforge/estimator/__init__.py +0 -0
  40. {spforge-0.8.19 → spforge-0.8.23}/spforge/estimator/_conditional_estimator.py +0 -0
  41. {spforge-0.8.19 → spforge-0.8.23}/spforge/estimator/_frequency_bucketing_classifier.py +0 -0
  42. {spforge-0.8.19 → spforge-0.8.23}/spforge/estimator/_granularity_estimator.py +0 -0
  43. {spforge-0.8.19 → spforge-0.8.23}/spforge/estimator/_group_by_estimator.py +0 -0
  44. {spforge-0.8.19 → spforge-0.8.23}/spforge/estimator/_ordinal_classifier.py +0 -0
  45. {spforge-0.8.19 → spforge-0.8.23}/spforge/estimator/_sklearn_enhancer_estimator.py +0 -0
  46. {spforge-0.8.19 → spforge-0.8.23}/spforge/feature_generator/__init__.py +0 -0
  47. {spforge-0.8.19 → spforge-0.8.23}/spforge/feature_generator/_base.py +0 -0
  48. {spforge-0.8.19 → spforge-0.8.23}/spforge/feature_generator/_lag.py +0 -0
  49. {spforge-0.8.19 → spforge-0.8.23}/spforge/feature_generator/_net_over_predicted.py +0 -0
  50. {spforge-0.8.19 → spforge-0.8.23}/spforge/feature_generator/_regressor_feature_generator.py +0 -0
  51. {spforge-0.8.19 → spforge-0.8.23}/spforge/feature_generator/_rolling_against_opponent.py +0 -0
  52. {spforge-0.8.19 → spforge-0.8.23}/spforge/feature_generator/_rolling_mean_binary.py +0 -0
  53. {spforge-0.8.19 → spforge-0.8.23}/spforge/feature_generator/_rolling_mean_days.py +0 -0
  54. {spforge-0.8.19 → spforge-0.8.23}/spforge/feature_generator/_rolling_window.py +0 -0
  55. {spforge-0.8.19 → spforge-0.8.23}/spforge/feature_generator/_utils.py +0 -0
  56. {spforge-0.8.19 → spforge-0.8.23}/spforge/features_generator_pipeline.py +0 -0
  57. {spforge-0.8.19 → spforge-0.8.23}/spforge/hyperparameter_tuning/__init__.py +0 -0
  58. {spforge-0.8.19 → spforge-0.8.23}/spforge/hyperparameter_tuning/_default_search_spaces.py +0 -0
  59. {spforge-0.8.19 → spforge-0.8.23}/spforge/hyperparameter_tuning/_tuner.py +0 -0
  60. {spforge-0.8.19 → spforge-0.8.23}/spforge/performance_transformers/__init__.py +0 -0
  61. {spforge-0.8.19 → spforge-0.8.23}/spforge/performance_transformers/_performance_manager.py +0 -0
  62. {spforge-0.8.19 → spforge-0.8.23}/spforge/performance_transformers/_performances_transformers.py +0 -0
  63. {spforge-0.8.19 → spforge-0.8.23}/spforge/ratings/__init__.py +0 -0
  64. {spforge-0.8.19 → spforge-0.8.23}/spforge/ratings/enums.py +0 -0
  65. {spforge-0.8.19 → spforge-0.8.23}/spforge/ratings/league_identifier.py +0 -0
  66. {spforge-0.8.19 → spforge-0.8.23}/spforge/ratings/league_start_rating_optimizer.py +0 -0
  67. {spforge-0.8.19 → spforge-0.8.23}/spforge/ratings/player_performance_predictor.py +0 -0
  68. {spforge-0.8.19 → spforge-0.8.23}/spforge/ratings/start_rating_generator.py +0 -0
  69. {spforge-0.8.19 → spforge-0.8.23}/spforge/ratings/team_performance_predictor.py +0 -0
  70. {spforge-0.8.19 → spforge-0.8.23}/spforge/ratings/team_start_rating_generator.py +0 -0
  71. {spforge-0.8.19 → spforge-0.8.23}/spforge/ratings/utils.py +0 -0
  72. {spforge-0.8.19 → spforge-0.8.23}/spforge/scorer/__init__.py +0 -0
  73. {spforge-0.8.19 → spforge-0.8.23}/spforge/scorer/_score.py +0 -0
  74. {spforge-0.8.19 → spforge-0.8.23}/spforge/transformers/__init__.py +0 -0
  75. {spforge-0.8.19 → spforge-0.8.23}/spforge/transformers/_base.py +0 -0
  76. {spforge-0.8.19 → spforge-0.8.23}/spforge/transformers/_net_over_predicted.py +0 -0
  77. {spforge-0.8.19 → spforge-0.8.23}/spforge/transformers/_operator.py +0 -0
  78. {spforge-0.8.19 → spforge-0.8.23}/spforge/transformers/_other_transformer.py +0 -0
  79. {spforge-0.8.19 → spforge-0.8.23}/spforge/transformers/_predictor.py +0 -0
  80. {spforge-0.8.19 → spforge-0.8.23}/spforge/transformers/_simple_transformer.py +0 -0
  81. {spforge-0.8.19 → spforge-0.8.23}/spforge/transformers/_team_ratio_predictor.py +0 -0
  82. {spforge-0.8.19 → spforge-0.8.23}/spforge/utils.py +0 -0
  83. {spforge-0.8.19 → spforge-0.8.23}/spforge.egg-info/SOURCES.txt +0 -0
  84. {spforge-0.8.19 → spforge-0.8.23}/spforge.egg-info/dependency_links.txt +0 -0
  85. {spforge-0.8.19 → spforge-0.8.23}/spforge.egg-info/requires.txt +0 -0
  86. {spforge-0.8.19 → spforge-0.8.23}/spforge.egg-info/top_level.txt +0 -0
  87. {spforge-0.8.19 → spforge-0.8.23}/tests/cross_validator/test_cross_validator.py +0 -0
  88. {spforge-0.8.19 → spforge-0.8.23}/tests/distributions/test_distribution.py +0 -0
  89. {spforge-0.8.19 → spforge-0.8.23}/tests/end_to_end/test_estimator_hyperparameter_tuning.py +0 -0
  90. {spforge-0.8.19 → spforge-0.8.23}/tests/end_to_end/test_league_start_rating_optimizer.py +0 -0
  91. {spforge-0.8.19 → spforge-0.8.23}/tests/end_to_end/test_lol_player_kills.py +0 -0
  92. {spforge-0.8.19 → spforge-0.8.23}/tests/end_to_end/test_nba_player_points.py +0 -0
  93. {spforge-0.8.19 → spforge-0.8.23}/tests/end_to_end/test_nba_player_ratings_hyperparameter_tuning.py +0 -0
  94. {spforge-0.8.19 → spforge-0.8.23}/tests/end_to_end/test_nba_prediction_consistency.py +0 -0
  95. {spforge-0.8.19 → spforge-0.8.23}/tests/estimator/test_sklearn_estimator.py +0 -0
  96. {spforge-0.8.19 → spforge-0.8.23}/tests/feature_generator/test_lag.py +0 -0
  97. {spforge-0.8.19 → spforge-0.8.23}/tests/feature_generator/test_regressor_feature_generator.py +0 -0
  98. {spforge-0.8.19 → spforge-0.8.23}/tests/feature_generator/test_rolling_against_opponent.py +0 -0
  99. {spforge-0.8.19 → spforge-0.8.23}/tests/feature_generator/test_rolling_mean_binary.py +0 -0
  100. {spforge-0.8.19 → spforge-0.8.23}/tests/feature_generator/test_rolling_mean_days.py +0 -0
  101. {spforge-0.8.19 → spforge-0.8.23}/tests/feature_generator/test_rolling_window.py +0 -0
  102. {spforge-0.8.19 → spforge-0.8.23}/tests/hyperparameter_tuning/test_estimator_tuner.py +0 -0
  103. {spforge-0.8.19 → spforge-0.8.23}/tests/hyperparameter_tuning/test_rating_tuner.py +0 -0
  104. {spforge-0.8.19 → spforge-0.8.23}/tests/performance_transformers/test_performance_manager.py +0 -0
  105. {spforge-0.8.19 → spforge-0.8.23}/tests/performance_transformers/test_performances_transformers.py +0 -0
  106. {spforge-0.8.19 → spforge-0.8.23}/tests/ratings/test_player_rating_no_mutation.py +0 -0
  107. {spforge-0.8.19 → spforge-0.8.23}/tests/ratings/test_ratings_property.py +0 -0
  108. {spforge-0.8.19 → spforge-0.8.23}/tests/ratings/test_utils_scaled_weights.py +0 -0
  109. {spforge-0.8.19 → spforge-0.8.23}/tests/scorer/test_score.py +0 -0
  110. {spforge-0.8.19 → spforge-0.8.23}/tests/scorer/test_score_aggregation_granularity.py +0 -0
  111. {spforge-0.8.19 → spforge-0.8.23}/tests/test_autopipeline.py +0 -0
  112. {spforge-0.8.19 → spforge-0.8.23}/tests/test_autopipeline_context.py +0 -0
  113. {spforge-0.8.19 → spforge-0.8.23}/tests/test_feature_generator_pipeline.py +0 -0
  114. {spforge-0.8.19 → spforge-0.8.23}/tests/transformers/test_estimator_transformer_context.py +0 -0
  115. {spforge-0.8.19 → spforge-0.8.23}/tests/transformers/test_net_over_predicted.py +0 -0
  116. {spforge-0.8.19 → spforge-0.8.23}/tests/transformers/test_other_transformer.py +0 -0
  117. {spforge-0.8.19 → spforge-0.8.23}/tests/transformers/test_predictor_transformer.py +0 -0
  118. {spforge-0.8.19 → spforge-0.8.23}/tests/transformers/test_simple_transformer.py +0 -0
  119. {spforge-0.8.19 → spforge-0.8.23}/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.19
3
+ Version: 0.8.23
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.19"
7
+ version = "0.8.23"
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"
@@ -176,6 +176,9 @@ class RatingGenerator(FeatureGenerator):
176
176
  @to_polars
177
177
  @nw.narwhalify
178
178
  def transform(self, df: IntoFrameT) -> IntoFrameT:
179
+ if self.performance_manager and self.performance_manager.ori_performance_column in df.columns:
180
+ df = nw.from_native(self.performance_manager.transform(df))
181
+
179
182
  pl_df: pl.DataFrame
180
183
  pl_df = df.to_native() if df.implementation.is_polars() else df.to_polars().to_native()
181
184
  return self._historical_transform(pl_df)
@@ -188,6 +191,9 @@ class RatingGenerator(FeatureGenerator):
188
191
  - use existing ratings to compute pre-match ratings/features
189
192
  - do NOT update ratings
190
193
  """
194
+ if self.performance_manager and self.performance_manager.ori_performance_column in df.columns:
195
+ df = nw.from_native(self.performance_manager.transform(df))
196
+
191
197
  pl_df: pl.DataFrame
192
198
  pl_df = df.to_native() if df.implementation.is_polars() else df.to_polars().to_native()
193
199
  return self._future_transform(pl_df)
@@ -433,9 +433,16 @@ class PlayerRatingGenerator(RatingGenerator):
433
433
  team1_off_perf = self._team_off_perf_from_collection(c1)
434
434
  team2_off_perf = self._team_off_perf_from_collection(c2)
435
435
 
436
+ team1_def_perf: float | None = None
437
+ team2_def_perf: float | None = None
438
+
436
439
  if self.use_off_def_split:
437
- team1_def_perf = 1.0 - team2_off_perf
438
- team2_def_perf = 1.0 - team1_off_perf
440
+ team1_def_perf = (
441
+ 1.0 - team2_off_perf if team2_off_perf is not None else None
442
+ )
443
+ team2_def_perf = (
444
+ 1.0 - team1_off_perf if team1_off_perf is not None else None
445
+ )
439
446
  else:
440
447
  team1_def_perf = team1_off_perf
441
448
  team2_def_perf = team2_off_perf
@@ -484,26 +491,33 @@ class PlayerRatingGenerator(RatingGenerator):
484
491
  ),
485
492
  )
486
493
 
487
- off_perf = float(pre_player.match_performance.performance_value)
488
- def_perf = float(team1_def_perf) # same for all players on team1 (derived)
494
+ perf_value = pre_player.match_performance.performance_value
495
+ if perf_value is None:
496
+ off_change = 0.0
497
+ else:
498
+ off_perf = float(perf_value)
499
+ mult_off = self._applied_multiplier_off(off_state)
500
+ off_change = (
501
+ (off_perf - float(pred_off))
502
+ * mult_off
503
+ * float(pre_player.match_performance.participation_weight)
504
+ )
489
505
 
490
- if not self.use_off_def_split:
491
- pred_def = pred_off
492
- def_perf = off_perf
506
+ if perf_value is None or team1_def_perf is None:
507
+ def_change = 0.0
508
+ else:
509
+ def_perf = float(team1_def_perf)
493
510
 
494
- mult_off = self._applied_multiplier_off(off_state)
495
- mult_def = self._applied_multiplier_def(def_state)
511
+ if not self.use_off_def_split:
512
+ pred_def = pred_off
513
+ def_perf = float(perf_value)
496
514
 
497
- off_change = (
498
- (off_perf - float(pred_off))
499
- * mult_off
500
- * float(pre_player.match_performance.participation_weight)
501
- )
502
- def_change = (
503
- (def_perf - float(pred_def))
504
- * mult_def
505
- * float(pre_player.match_performance.participation_weight)
506
- )
515
+ mult_def = self._applied_multiplier_def(def_state)
516
+ def_change = (
517
+ (def_perf - float(pred_def))
518
+ * mult_def
519
+ * float(pre_player.match_performance.participation_weight)
520
+ )
507
521
 
508
522
  if math.isnan(off_change) or math.isnan(def_change):
509
523
  raise ValueError(
@@ -562,26 +576,33 @@ class PlayerRatingGenerator(RatingGenerator):
562
576
  ),
563
577
  )
564
578
 
565
- off_perf = float(pre_player.match_performance.performance_value)
566
- def_perf = float(team2_def_perf)
579
+ perf_value = pre_player.match_performance.performance_value
580
+ if perf_value is None:
581
+ off_change = 0.0
582
+ else:
583
+ off_perf = float(perf_value)
584
+ mult_off = self._applied_multiplier_off(off_state)
585
+ off_change = (
586
+ (off_perf - float(pred_off))
587
+ * mult_off
588
+ * float(pre_player.match_performance.participation_weight)
589
+ )
567
590
 
568
- if not self.use_off_def_split:
569
- pred_def = pred_off
570
- def_perf = off_perf
591
+ if perf_value is None or team2_def_perf is None:
592
+ def_change = 0.0
593
+ else:
594
+ def_perf = float(team2_def_perf)
571
595
 
572
- mult_off = self._applied_multiplier_off(off_state)
573
- mult_def = self._applied_multiplier_def(def_state)
596
+ if not self.use_off_def_split:
597
+ pred_def = pred_off
598
+ def_perf = float(perf_value)
574
599
 
575
- off_change = (
576
- (off_perf - float(pred_off))
577
- * mult_off
578
- * float(pre_player.match_performance.participation_weight)
579
- )
580
- def_change = (
581
- (def_perf - float(pred_def))
582
- * mult_def
583
- * float(pre_player.match_performance.participation_weight)
584
- )
600
+ mult_def = self._applied_multiplier_def(def_state)
601
+ def_change = (
602
+ (def_perf - float(pred_def))
603
+ * mult_def
604
+ * float(pre_player.match_performance.participation_weight)
605
+ )
585
606
 
586
607
  if math.isnan(off_change) or math.isnan(def_change):
587
608
  raise ValueError(
@@ -933,7 +954,7 @@ class PlayerRatingGenerator(RatingGenerator):
933
954
  self.performance_column in team_player
934
955
  and team_player[self.performance_column] is not None
935
956
  )
936
- else 0.0
957
+ else None
937
958
  )
938
959
 
939
960
  mp = MatchPerformance(
@@ -1021,22 +1042,28 @@ class PlayerRatingGenerator(RatingGenerator):
1021
1042
 
1022
1043
  return pre_match_player_ratings, pre_match_player_off_values
1023
1044
 
1024
- def _team_off_perf_from_collection(self, c: PreMatchPlayersCollection) -> float:
1045
+ def _team_off_perf_from_collection(
1046
+ self, c: PreMatchPlayersCollection
1047
+ ) -> float | None:
1025
1048
  # observed offense perf = weighted mean of player performance_value using participation_weight if present
1049
+ # skip players with null performance
1026
1050
  cn = self.column_names
1027
1051
  if not c.pre_match_player_ratings:
1028
- return 0.0
1052
+ return None
1029
1053
  wsum = 0.0
1030
1054
  psum = 0.0
1031
1055
  for pre in c.pre_match_player_ratings:
1056
+ perf_val = pre.match_performance.performance_value
1057
+ if perf_val is None:
1058
+ continue
1032
1059
  w = (
1033
1060
  float(pre.match_performance.participation_weight)
1034
1061
  if cn.participation_weight
1035
1062
  else 1.0
1036
1063
  )
1037
- psum += float(pre.match_performance.performance_value) * w
1064
+ psum += float(perf_val) * w
1038
1065
  wsum += w
1039
- return psum / wsum if wsum else 0.0
1066
+ return psum / wsum if wsum else None
1040
1067
 
1041
1068
  def _team_off_def_rating_from_collection(
1042
1069
  self, c: PreMatchPlayersCollection
@@ -1101,13 +1128,13 @@ class PlayerRatingGenerator(RatingGenerator):
1101
1128
  self.PLAYER_PRED_PERF_COL: [],
1102
1129
  }
1103
1130
 
1104
- def get_perf_value(team_player: dict) -> float:
1131
+ def get_perf_value(team_player: dict) -> float | None:
1105
1132
  if (
1106
1133
  self.performance_column in team_player
1107
1134
  and team_player[self.performance_column] is not None
1108
1135
  ):
1109
1136
  return float(team_player[self.performance_column])
1110
- return 0.0
1137
+ return None
1111
1138
 
1112
1139
  def ensure_new_player(
1113
1140
  pid: str,
@@ -1187,8 +1214,9 @@ class PlayerRatingGenerator(RatingGenerator):
1187
1214
  )
1188
1215
  off_vals.append(float(local_off[pid].rating_value))
1189
1216
 
1190
- psum += float(mp.performance_value) * float(pw)
1191
- wsum += float(pw)
1217
+ if mp.performance_value is not None:
1218
+ psum += float(mp.performance_value) * float(pw)
1219
+ wsum += float(pw)
1192
1220
 
1193
1221
  team_off_perf = psum / wsum if wsum else 0.0
1194
1222
  return pre_list, player_ids, off_vals, proj_w, team_off_perf
@@ -326,16 +326,7 @@ class TeamRatingGenerator(RatingGenerator):
326
326
  opp_off_pre = float(o_off.rating_value)
327
327
  opp_def_pre = float(o_def.rating_value)
328
328
 
329
- off_perf = (
330
- float(r[self.performance_column])
331
- if r.get(self.performance_column) is not None
332
- else 0.0
333
- )
334
- opp_off_perf = float(r[perf_opp_col]) if r.get(perf_opp_col) is not None else 0.0
335
- if self.use_off_def_split:
336
- def_perf = 1.0 - opp_off_perf
337
- else:
338
- def_perf = off_perf
329
+ off_perf_raw = r.get(self.performance_column)
339
330
 
340
331
  pred_off = self._performance_predictor.predict_performance(
341
332
  rating_value=s_off.rating_value, opponent_team_rating_value=o_def.rating_value
@@ -346,16 +337,28 @@ class TeamRatingGenerator(RatingGenerator):
346
337
  if not self.use_off_def_split:
347
338
  pred_def = pred_off
348
339
 
349
- mult_off = self._applied_multiplier(s_off, self.rating_change_multiplier_offense)
350
- mult_def = self._applied_multiplier(s_def, self.rating_change_multiplier_defense)
351
-
352
- off_change = (off_perf - pred_off) * mult_off
353
- def_change = (def_perf - pred_def) * mult_def
354
-
355
- if math.isnan(off_change) or math.isnan(def_change):
356
- raise ValueError(
357
- f"NaN rating change for team_id={team_id}, match_id={r[cn.match_id]}"
358
- )
340
+ # Null performance means no rating change
341
+ if off_perf_raw is None:
342
+ off_change = 0.0
343
+ def_change = 0.0
344
+ else:
345
+ off_perf = float(off_perf_raw)
346
+ opp_off_perf = float(r[perf_opp_col]) if r.get(perf_opp_col) is not None else 0.0
347
+ if self.use_off_def_split:
348
+ def_perf = 1.0 - opp_off_perf
349
+ else:
350
+ def_perf = off_perf
351
+
352
+ mult_off = self._applied_multiplier(s_off, self.rating_change_multiplier_offense)
353
+ mult_def = self._applied_multiplier(s_def, self.rating_change_multiplier_defense)
354
+
355
+ off_change = (off_perf - pred_off) * mult_off
356
+ def_change = (def_perf - pred_def) * mult_def
357
+
358
+ if math.isnan(off_change) or math.isnan(def_change):
359
+ raise ValueError(
360
+ f"NaN rating change for team_id={team_id}, match_id={r[cn.match_id]}"
361
+ )
359
362
 
360
363
  rows.append(
361
364
  {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spforge
3
- Version: 0.8.19
3
+ Version: 0.8.23
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