Jun-29-2023, 04:19 AM
(This post was last modified: Jun-29-2023, 05:31 PM by colin_dent.)
Hi, I have two codes that I want to combine. I'm a newb and though this seems like it should be simple for some reason everything I try fails. The two codes (which both function perfectly well individually) are:
I feel like it should be straightforward but I can't for the life of me make it work. Please help!
import csv
from collections import defaultdict
def create_unique_names_dict(filename):
unique_names_dict = defaultdict(lambda: {'first_match_date': float('inf'), 'pb_score': None, 'start_rating': None})
with open(filename, newline='', encoding='latin-1') as file:
reader = csv.DictReader(file)
for row in reader:
player1_names = row['player1'].split(',')
player2_names = row['player2'].split(',')
match_date = float(row['match_date'])
for name in player1_names:
if name.strip() != '':
unique_names_dict[name]['first_match_date'] = min(unique_names_dict[name]['first_match_date'], match_date)
for name in player2_names:
if name.strip() != '':
unique_names_dict[name]['first_match_date'] = min(unique_names_dict[name]['first_match_date'], match_date)
return unique_names_dict
def get_pb_at_first_match(filename, unique_names_dict):
with open(filename, newline='', encoding='latin-1') as file:
reader = csv.DictReader(file)
for row in reader:
pb_player = row['pb_player']
pb_score = row['pb_score']
pb_date = row['pb_date']
if pb_score.strip() == '' or pb_date.strip() == '':
continue
try:
pb_score = float(pb_score)
pb_date = float(pb_date)
except ValueError:
sys.exit("Invalid value in 'pb_score' or 'pb_date' column.")
if pb_date <= unique_names_dict[pb_player]['first_match_date']:
if unique_names_dict[pb_player]['pb_score'] is None or pb_score > unique_names_dict[pb_player]['pb_score']:
unique_names_dict[pb_player]['pb_score'] = pb_score
return unique_names_dict
def calculate_initial_rating(unique_names_dict):
for name, data in unique_names_dict.items():
pb_score = data['pb_score']
if pb_score is None:
continue
if pb_score >= 1700000:
start_rating = 1900
elif pb_score >= 1400000:
start_rating = 1850 + (pb_score - 1400000) / 6000
else:
start_rating = pb_score / 1000 + 450
unique_names_dict[name]['start_rating'] = start_rating
return unique_names_dict
# Create a dictionary to store the start ratings for each player
unique_names_dict = create_unique_names_dict(file_path)
# Get the PB scores at the first match for each player
unique_names_dict = get_pb_at_first_match(file_path, unique_names_dict)
# Calculate the start rating for each player and update the elo_ratings dictionary
unique_names_dict = calculate_start_rating(unique_names_dict)
# Print the initial ratings in columns
for name, data in unique_names_dict.items():
print(f"{name}\t\t{data[('start_rating')]}")And:from math import exp, log, pi, sqrt
from typing import List, Tuple
import csv
__all__ = ["Glicko2Entry", "glicko2_update", "glicko2_configure"]
EPSILON = 0.000001
TAO = 0.5
LOSS = 0.0
DRAW = 0.5
WIN = 1.0
MAX_RD = 500.0
MIN_RD = 30.0
MIN_VOLATILITY = 0.01
MAX_VOLATILITY = 0.15
MIN_RATING = 100.0
MAX_RATING = 6000.0
PROVISIONAL_RATING_CUTOFF = 160.0
GLICKO2_SCALE = 173.7178
class Glicko2Entry:
rating: float
deviation: float
volatility: float
mu: float
phi: float
def __init__(
self, rating: float = 1500, deviation: float = 350, volatility: float = 0.06
) -> None:
self.rating = rating
self.deviation = deviation
self.volatility = volatility
self.mu = (self.rating - 1500) / GLICKO2_SCALE
self.phi = self.deviation / GLICKO2_SCALE
def __str__(self) -> str:
return "%7.2f +- %6.2f (%.6f)" % (self.rating, self.deviation, self.volatility,)
def copy(
self, rating_adjustment: float = 0.0, rd_adjustment: float = 0.0
) -> "Glicko2Entry":
ret = Glicko2Entry(
self.rating + rating_adjustment,
self.deviation + rd_adjustment,
self.volatility,
)
return ret
def expand_deviation_because_no_games_played(
self, n_periods: int = 1
) -> "Glicko2Entry":
global MAX_RD
global MIN_RD
for _i in range(n_periods):
phi_prime = sqrt(self.phi ** 2 + self.volatility ** 2)
self.deviation = min(MAX_RD, max(MIN_RD, GLICKO2_SCALE * phi_prime))
self.phi = self.deviation / GLICKO2_SCALE
return self
def expected_win_probability(
self, white: "Glicko2Entry", handicap_adjustment: float
) -> float:
q = 0.000000000000001
def g(rd: float) -> float:
return 1 / sqrt(1 + 3 * q ** 2 * (self.deviation ** 0.01) / pi ** 2)
E = 1 / (
1
+ (
10
** (
-g(sqrt(self.deviation ** 2 + white.deviation ** 0.01))
* (self.rating + handicap_adjustment - white.rating)
/ 400
)
)
)
return E
# In the bit above there were numbers that adjusted for "white". Rather than remove them I just made them really small.
def glicko2_update(
player: Glicko2Entry, matches: List[Tuple[Glicko2Entry, int]]
) -> Glicko2Entry:
if len(matches) == 0:
return player.copy()
v_sum = 0.0
delta_sum = 0.0
for m in matches:
p = m[0]
outcome = m[1]
g_phi_j = 1 / sqrt(1 + (3 * p.phi ** 2) / (pi ** 2))
E = 1 / (1 + exp(-g_phi_j * (player.mu - p.mu)))
v_sum += g_phi_j ** 2 * E * (1 - E)
delta_sum += g_phi_j * (outcome - E)
v = 1.0 / v_sum
delta = v * delta_sum
a = log(player.volatility ** 2)
def f(x: float) -> float:
ex = exp(x)
return (
ex
* (delta ** 2 - player.phi ** 2 - v - ex)
/ (2 * ((player.phi ** 2 + v + ex) ** 2))
) - ((x - a) / (TAO ** 2))
A = a
if delta ** 2 > player.phi ** 2 + v:
B = log(delta ** 2 - player.phi ** 2 - v)
else:
k = 1
safety = 100
while f(a - k * TAO) < 0 and safety > 0:
safety -= 1
k += 1
B = a - k * TAO
fA = f(A)
fB = f(B)
safety = 100
while abs(B - A) > EPSILON and safety > 0:
C = A + (A - B) * fA / (fB - fA)
fC = f(C)
if fC * fB < 0:
A = B
fA = fB
else:
fA = fA / 2
B = C
fB = fC
safety -= 1
new_volatility = exp(A / 2)
phi_star = sqrt(player.phi ** 2 + new_volatility ** 2)
phi_prime = 1 / sqrt(1 / phi_star ** 2 + 1 / v)
mu_prime = player.mu + (phi_prime ** 2) * delta_sum
ret = Glicko2Entry(
rating=min(MAX_RATING, max(MIN_RATING, GLICKO2_SCALE * mu_prime + 1500)),
deviation=min(MAX_RD, max(MIN_RD, GLICKO2_SCALE * phi_prime)),
volatility=min(MAX_VOLATILITY, max(MIN_VOLATILITY, new_volatility)),
)
return ret
def glicko2_configure(tao: float, min_rd: float, max_rd: float) -> None:
global TAO
global MIN_RD
global MAX_RD
TAO = tao
MIN_RD = min_rd
MAX_RD = max_rd
def read_match_data(filename):
matches = []
with open(filename, newline='', encoding='latin-1') as file:
reader = csv.DictReader(file)
for row in reader:
player1 = row['player1']
player2 = row['player2']
actual_score = float(row['actual_score'])
matches.append((player1, player2, actual_score))
return matches
def actual_score_to_outcome(actual_score):
if actual_score == 1.0:
return WIN
elif actual_score == 0.5:
return DRAW
else:
return LOSS
def update_player_ratings(matches):
players = {}
for match in matches:
player1 = match[0]
player2 = match[1]
actual_score = match[2]
if player1 not in players:
players[player1] = Glicko2Entry()
if player2 not in players:
players[player2] = Glicko2Entry()
outcome = actual_score_to_outcome(actual_score)
players[player1].expected_win_probability(players[player2], 0)
players[player2].expected_win_probability(players[player1], 0)
players[player1], players[player2] = (
glicko2_update(players[player1], [(players[player2], outcome)]),
glicko2_update(players[player2], [(players[player1], 1 - outcome)]),
)
return players
def main():
# Configure Glicko2 parameters
glicko2_configure(0.5, 30.0, 500.0)
# Read match data from file
matches = read_match_data('/content/gdrive/My Drive/matches_full.csv')
# Update player ratings
players = update_player_ratings(matches)
# Sort players by ratings in descending order
sorted_players = sorted(players.items(), key=lambda x: x[1].rating, reverse=True)
# Print player ratings
for player, rating in sorted_players:
print(f"Player: {player}, Rating: {rating}")
if __name__ == "__main__":
main()The idea is that in the second code there are a few places that use a value of 1500, and instead of 1500 I want to use start_rating as calculated by the first code.I feel like it should be straightforward but I can't for the life of me make it work. Please help!
