Created
December 9, 2024 20:54
-
-
Save cmrfrd/241bab6b105444d1812a5a2010b5d514 to your computer and use it in GitHub Desktop.
terry_bradley
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import random | |
| from functools import reduce | |
| import numpy as np | |
| from matplotlib import pyplot as plt | |
| player_names = ["Alice", "Bob", "Charlie", "David"] | |
| match_matrix = np.zeros((len(player_names), len(player_names))) | |
| # player in the first position is the prob of winning | |
| # this is for sampling | |
| pair_winning_prob = { | |
| ("Alice", "Bob"): 0.5, | |
| ("Alice", "Charlie"): 0.4, | |
| ("Alice", "David"): 0.6, | |
| ("Bob", "Charlie"): 0.5, | |
| ("Bob", "David"): 0.5, | |
| ("Charlie", "David"): 0.4, | |
| } | |
| ## add inverses | |
| for (p1, p2), prob in dict(pair_winning_prob).items(): | |
| pair_winning_prob[(p2, p1)] = 1 - prob | |
| def add_match() -> None: | |
| # create a random matchup | |
| p1, p2 = random.sample(list(player_names), 2) | |
| # sample the winner | |
| prob = pair_winning_prob[(p1, p2)] | |
| winner = p1 if random.random() < prob else p2 | |
| # update the match matrix | |
| p1_index = player_names.index(p1) | |
| p2_index = player_names.index(p2) | |
| if winner == p1: | |
| match_matrix[p1_index, p2_index] += 1 | |
| else: | |
| match_matrix[p2_index, p1_index] += 1 | |
| def terry_bradley_calc(iters: int = 10) -> dict[str, float]: | |
| """Calculate the Terry-Bradley values for each player.""" | |
| init_tb = {player: 1.0 for player in player_names} | |
| for _ in range(iters): | |
| for player in player_names: | |
| wins_by_player = match_matrix[player_names.index(player)] | |
| losses_from_opponents = match_matrix[:, player_names.index(player)] | |
| # wins normalized by the sum of wins and losses | |
| numerator = 0 | |
| for opponent in filter(lambda x: x != player, player_names): | |
| numerator += wins_by_player[player_names.index(opponent)] * ( | |
| init_tb[opponent] / (init_tb[player] + init_tb[opponent]) | |
| ) | |
| # losses normalized by the sum of wins and losses | |
| denominator = 0 | |
| for opponent in filter(lambda x: x != player, player_names): | |
| denominator += losses_from_opponents[player_names.index(opponent)] * ( | |
| init_tb[opponent] / (init_tb[player] + init_tb[opponent]) | |
| ) | |
| init_tb[player] = numerator / denominator | |
| geometric_mean = reduce(lambda x, y: x * y, init_tb.values()) ** (1 / len(init_tb)) | |
| init_tb = {player: init_tb[player] / geometric_mean for player in player_names} | |
| return init_tb | |
| # Step 3: Simulate matches | |
| def simulate(num_rounds: int = 100, matches_per_round: int = 1000) -> list[dict[str, float]]: | |
| results = [] | |
| for _ in range(num_rounds): | |
| for _ in range(matches_per_round): | |
| add_match() | |
| results.append(terry_bradley_calc()) | |
| return results | |
| # Run the simulation | |
| results = simulate(num_rounds=1_000) | |
| ## plot the measured values | |
| player_measures_over_time: dict[str, list[float]] = {player: [] for player in player_names} | |
| for measure in results: | |
| for player, stats in measure.items(): | |
| player_measures_over_time[player].append(stats) | |
| fig, ax = plt.subplots() | |
| for player, measures in player_measures_over_time.items(): | |
| ax.plot(measures, label=player, linestyle="-") | |
| ax.legend() | |
| plt.savefig("terry_bradley.png") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment