diff options
author | emkael <emkael@tlen.pl> | 2014-11-07 16:49:24 +0100 |
---|---|---|
committer | emkael <emkael@tlen.pl> | 2014-11-07 16:49:24 +0100 |
commit | efcf12f3e6bb437f5089215fd81b2c924961e4d2 (patch) | |
tree | 16a877357c36b54f3a3f60ad63f880f4459e0167 | |
parent | 50eda8dcf4c764493efe3cae4cf81df916ea2e7d (diff) |
* disparity variation algorithm
-rw-r--r-- | config/elo.json | 2 | ||||
-rw-r--r-- | doc/ideas.txt | 1 | ||||
-rw-r--r-- | doc/results.txt | 9 | ||||
-rw-r--r-- | f1elo/elo.py | 32 | ||||
-rw-r--r-- | f1elo/interface.py | 11 |
5 files changed, 44 insertions, 11 deletions
diff --git a/config/elo.json b/config/elo.json index 38a7796..6b08ee8 100644 --- a/config/elo.json +++ b/config/elo.json @@ -1,6 +1,6 @@ { "initial_ranking": 2000.0, - "disparity": 400, + "disparity": 600, "importance": { "NC_Q": 1.6, "NC_R": 16, diff --git a/doc/ideas.txt b/doc/ideas.txt index ec8f999..3c7f773 100644 --- a/doc/ideas.txt +++ b/doc/ideas.txt @@ -7,6 +7,7 @@ Tables: Charts: - rolling average and deviation of rankings - average ranking at the beginning of each championship season + - average ranking at the end of each championship season - peak season ranking (MAX, MIN, AVG, STDDEV GROUP BY YEAR(date)) - top 5 of every season (on average) by decade diff --git a/doc/results.txt b/doc/results.txt index c353368..13a56f3 100644 --- a/doc/results.txt +++ b/doc/results.txt @@ -21,14 +21,21 @@ Specific rules regarding Elo implementation for this application's purposes: * no minimum rating limit for driver is enforced * drivers are rated from their first entry (there's no initial grace period) * shared drive entries have the effective ranking equal to the average ranking of drivers sharing the drive; pending ranking points from duels of such entries are divided equally between drivers sharing the drive + * driver group disparity is varied to accommodate for dynamic shifts of relative performance within the F1 field (caused by technical changes) - see below Other than that, standard Elo rating conventions apply: * drivers start with identical initial rating * duels between high-ranked drivers change their rankings by fewer points than duels between low-ranked drivers +Field disparity change: + * higher disparity leads to more rating inflation - as disparity is a measure of rating difference that yields a certian probability of driver's victory, so higher disparity leads to more attribution for a victory to a driver (and less attribution to the shift of performance, e.g. car performance change) + * higher rating deviation in the months prior to a race suggests a shift in relative performance (some drivers gain a lot, some divers lose a lot), so there's a need for damping further changes a bit - by lowering disparity + * once the ratings are stabilized (meaning relative performance within the field had settled), disparity can be increased + * this helps with the initial "rolling start" phase of the ranking (1946-1949 races) + Specific parameters which are configurable: * initial driver ranking - * disparity factor (ranking difference which drops the possibility of lower-ranked driver's win by the factor of 10) + * initial disparity factor (ranking difference which drops the possibility of lower-ranked driver's win by the factor of 10) * duel importance (base, i.e. for drivers ranked below importance thresholds) for all race types * importance thresholds (for 50% of base importance and 75% of base importance) diff --git a/f1elo/elo.py b/f1elo/elo.py index 51c9e23..8b3c806 100644 --- a/f1elo/elo.py +++ b/f1elo/elo.py @@ -1,4 +1,5 @@ -import json +import json, dateutil +from sqlalchemy import func from itertools import combinations from os import path @@ -23,6 +24,27 @@ class Elo: return sum([self.get_ranking(d, date) for d in entry.drivers]) / len(entry.drivers) def rank_race(self, race): + recent_date = race.date - dateutil.relativedelta.relativedelta(months=9) + recent_ratings = self.session.query( + func.min(Ranking.ranking).label('min'), + func.max(Ranking.ranking).label('max') + ).filter( + Ranking.rank_date >= recent_date + ).group_by( + Ranking._driver + ) + changes_query = self.session.query( + func.avg( + func.abs( + recent_ratings.subquery().columns.min - recent_ratings.subquery().columns.max + ) + ) + ) + recent_rank_changes = changes_query.scalar() + race_disparity = self.config['disparity'] + if recent_rank_changes: + race_disparity -= recent_rank_changes + print(race_disparity, str(race.date)) entries = race.entries entries_to_compare = [] rankings = {} @@ -38,7 +60,9 @@ class Elo: self.get_outcome(c), self.get_importance(race, [rankings[c[0]], - rankings[c[1]]])) + rankings[c[1]]]), + race_disparity + ) new_rankings[c[0]] += score new_rankings[c[1]] -= score return new_rankings @@ -59,5 +83,5 @@ class Elo: return 0 return 0.5 - def get_score(self, difference, outcome, importance): - return importance * (outcome - 1 / (1 + (10 ** (-difference / self.config['disparity'])))) + def get_score(self, difference, outcome, importance, disparity): + return importance * (outcome - 1 / (1 + (10 ** (-difference / disparity)))) diff --git a/f1elo/interface.py b/f1elo/interface.py index 173cfa7..3768f8a 100644 --- a/f1elo/interface.py +++ b/f1elo/interface.py @@ -111,11 +111,12 @@ class Interface: one_year = dateutil.relativedelta.relativedelta(years=1) rankings = self.session.query( - Ranking).filter( - Ranking.rank_date > ( - date - one_year)).filter( - Ranking.rank_date <= date).all( - ) + Ranking + ).filter( + Ranking.rank_date > (date - one_year) + ).filter( + Ranking.rank_date <= date + ).all() drivers = {} for ranking in rankings: |