|
| 1 | +import math |
| 2 | + |
1 | 3 | from scipy.stats import binomtest |
2 | 4 |
|
3 | 5 | from codeclash.constants import RESULT_TIE |
4 | 6 |
|
5 | 7 |
|
6 | | -def calculate_p_value(scores: dict[str, int]) -> float: |
| 8 | +def calculate_p_value(scores: dict[str, int | float]) -> float: |
7 | 9 | """Calculate the p-value for the statistical significance of the winner. |
8 | 10 |
|
9 | 11 | Input: scores as a dictionary of player names and number of games won. |
10 | 12 | Special case: 'Tie' (constants.RESULT_TIE) is a tie between all players. |
11 | 13 |
|
| 14 | + Score values must be whole numbers (integers or floats close to integers). |
| 15 | +
|
12 | 16 | Ties (RESULT_TIE) are excluded by conditioning on decisive games. |
13 | 17 |
|
14 | 18 | Uses an exact one-sided binomial test for the top player's share vs 1/K, |
15 | 19 | Bonferroni-corrected for choosing the winner post hoc among K players. |
16 | 20 | Returns 1.0 if there is no unique winner or no decisive games. |
17 | 21 | """ |
18 | | - player_wins = {p: c for p, c in scores.items() if p != RESULT_TIE} |
| 22 | + # Convert scores to integers, but only if they're close to whole numbers |
| 23 | + player_wins = {} |
| 24 | + for p, c in scores.items(): |
| 25 | + if p == RESULT_TIE: |
| 26 | + continue |
| 27 | + if isinstance(c, float) and not math.isclose(c, round(c)): |
| 28 | + raise ValueError(f"Score for player '{p}' is {c}, but game wins must be whole numbers") |
| 29 | + player_wins[p] = int(round(c)) |
19 | 30 | decisive_games = sum(player_wins.values()) |
20 | 31 | n_players = len(player_wins) |
21 | 32 | assert n_players > 1, "At least two players are required to calculate significance" |
|
0 commit comments