Source code for athlib.wma.agegrader

import json
import re
import os

from collections import namedtuple

from athlib.utils import str2num, normalize_gender, parse_hms
from athlib.codes import PAT_THROWS, PAT_JUMPS, PAT_TRACK, PAT_ROAD

__all__ = ('AgeGrader',)

road_info = namedtuple('road_info', 'code distance standard factors')


[docs]class AgeGrader(object): """ We implement an object to cache the data used for lookups. end users will appear to be calling a function. """ min_age = 35 max_age = 100 data_year = "2015" data_file_name = "wma-data.json" text_columns = 0, event_column = 0 _data = None
[docs] def get_data(self): """Defer this until the first call, so we can bubble a function up to the top of the package """ if not self._data: codedir = os.path.dirname(__file__) self.data_path = os.path.join(codedir, self.data_file_name) with open(self.data_path, 'rb') as f: self._data = json.load(f) return self._data
@property def _all_event_codes(self): data = self.get_data() return list(set([t[self.event_column] for T in ( data[self.data_year]['m'], data[self.data_year]['f']) for t in T])) @staticmethod def _check_table_column(table, x, func): for row in table: func(row[x]) def _check_patterns(self): """ >>> from athlib.wma.agegrader import AgeGrader >>> AgeGrader()._check_patterns() """ for ec in self._all_event_codes: for p in (PAT_THROWS, PAT_JUMPS, PAT_TRACK, PAT_ROAD): if p.match(ec): break else: print 'could not match %s' % ec nuc = [] def ccase(x): if x.upper() != x: nuc.append(x) data = self.get_data()[self.data_year] for j in self.text_columns: self._check_table_column(data['m'], j, ccase) self._check_table_column(data['f'], j, ccase) if nuc: print ('these strings need uppercasing in the json %s' % ' '.join(repr(t) for t in list(set(nuc)))) @staticmethod def event_code_to_kind(code): for n, p in (('throw', PAT_THROWS), ('jump', PAT_JUMPS), ('track', PAT_TRACK), ('road', PAT_ROAD)): if p.match(code): return n raise ValueError('could not find event kind for code %R' % code) @staticmethod def normalize_gender(gender): g = gender.lower() if g: g = g[0] if g not in 'mf': raise ValueError('cannot normalize gender = %s' % repr(gender)) return g
[docs] def calculate_factor(self, gender, age, event, distance=None): """Work out 'slowdown factor' for a geezer of this age taking part in this event e.g. >>> from athlib.wma.agegrader import AgeGrader >>> ag=AgeGrader() >>> ag.calculate_factor('M',68,'5k') 0.7592 >>> ag.calculate_factor('M',68,'200K') 0.7561 >>> ag.calculate_factor('M',68.5,'200K') 0.7522 >>> ag.calculate_factor('f',35,'5k') 0.9935 >>> ag.calculate_factor('f',35,'200K') 0.9926 >>> ag.calculate_factor('F',35.5,'200K') 0.99095 >>> ag.calculate_factor('M',65,'10000') 0.7691 >>> ag.calculate_factor('M',69,'10000') 0.7402 >>> ag.calculate_factor('F',35,'1500') 0.9822 >>> ag.calculate_factor('f',39,'1500') 0.9547 >>> ag.calculate_factor('f',35,'SH') 0.9791 >>> ag.calculate_factor('f',39,'SH') 0.9576 >>> ag.calculate_factor('m',35,'LH') 0.9647 >>> ag.calculate_factor('m',39,'LH') 0.9254 """ kind = self.event_code_to_kind(event) event = event.upper() gender = self.normalize_gender(gender) # Which table we're using data = self.get_data()[self.data_year] table = data[gender] ages = data['ages'] nt = len(table) if distance is None: # We must match an event exactly self.find_row_by_event(event, table, x=self.event_column, label='wma.%s.%s' % (self.data_year, gender)) else: self.find_row_by_distance(distance, table, x=1, label='wma.%s.%s' % (self.data_year, gender)) self.find_age(age, ages) fx = self._fx fx1 = self._fx1 pfac = self._pfac ax = self._ax ax1 = self._ax1 page = self._page FX = table[fx][3:] FX1 = table[fx1][3:] fac = FX[ax] faca = FX[ax1] fac1 = FX1[ax] fac1a = FX1[ax1] fac = ((1 - pfac) * ((page * faca) + ((1 - page) * fac)) + (pfac * ((page * fac1a) + ((1 - page) * fac1)))) return fac
def find_row_by_event(self, event, table, x=0, label=''): for i, row in enumerate(table): if row[x] == event: self._fx = self._fx1 = i self._pfac = 0 return i raise ValueError('cannot locate event %s in %s' % (repr(event), label)) def find_row_by_distance(self, d, table, x=0, label=''): i = 0 nt = len(table) while i < nt and table[i][x] < d: i += 1 if i == 0: pfac = fx = fx1 = 0 elif i and i < nt: # Within the data fx = i - 1 fx1 = i pfac = float(dist - table[fx][x]) / (table[fx1][x] - table[fx][x]) else: fx = fx1 = nt - 1 pfac = 0 self._fx = fx self._fx1 = fx1 self._pfac = pfac def find_age(self, age, ages, interpolate=True): if not age: age = 29 na = len(ages) i = 0 while i < na and ages[i] < age: i += 1 if i == 0: page = ax = ax1 = 0 elif i and i < na: ax1 = i ax = ax1 - 1 page = (float(age - ages[ax]) / (ages[ax1] - ages[ax])) if interpolate else 0 else: ax = ax1 = nt - 1 page = 0 self._ax = ax self._ax1 = ax1 self._page = page
[docs] def world_best(self, gender, event): "The relevant world-record performance on the date stats were compiled" kind = self.event_code_to_kind(event) kind = self.event_code_to_kind(event) data = self.get_data()[self.data_year] table = data[gender] row = self.find_row_by_event(event, table, x=0) world_best = table[row][2] return world_best
[docs] def calculate_age_grade(self, gender, age, event, performance, verbose=False): """Return the age grade score (0 to 100ish) for this result. >>> from athlib.wma.agegrader import AgeGrader >>> ag=AgeGrader() >>> "%0.4f" % ag.calculate_age_grade('m',50,'5K', '16:23') '0.9004' >>> "%0.4f" % ag.calculate_age_grade('f',50,'5K', '18:00') '0.9179' >>> """ # This works for jumps/throws too, as they are floats float_performance = parse_hms(performance) world_best = self.world_best(gender, event) age_factor = self.calculate_factor(gender, age, event) age_group_best = world_best * 1.0 / age_factor if verbose: print "performance = %0.2f" % float_performance print "world best for %s %s = %0.2f" % (gender, event, world_best) print "factor %s %s = %0.4f" % (gender, event, age_factor) print "age group best would be", age_group_best kind = self.event_code_to_kind(event) if kind in ['road', 'track']: # Performance is a float. # Older people get lower values (shorter/lower) age_grade = age_group_best / float_performance else: age_grade = float_performance / age_group_best return age_grade
class AthlonsAgeGrader(AgeGrader): data_file_name = "wma-athlons-data.json" text_columns = 0, 2 event_column = 2 def calculate_factor(self, gender, age, event): """ >>> from athlib.wma.agegrader import AthlonsAgeGrader >>> ag=AthlonsAgeGrader() >>> ag.calculate_factor('M',65,'100H') 0.8637 >>> ag.calculate_factor('M',69,'100H') 0.8637 >>> ag.calculate_factor('M',65,'10000') 0.7858 >>> ag.calculate_factor('M',69,'10000') 0.7858 >>> ag.calculate_factor('f',35,'100H') 0.9852 >>> ag.calculate_factor('F',39,'100H') 0.9852 >>> ag.calculate_factor('F',35,'1500') 0.9872 >>> ag.calculate_factor('f',39,'1500') 0.9872 """ event = event.upper() gender = self.normalize_gender(gender) # Which table we're using data = self.get_data()[self.data_year] table = data[gender] ages = data['ages'] # We must match an event exactly self.find_row_by_event(event, table, x=2, label='wma-athlons.%s.%s' % (self.data_year, gender)) self.find_age(int(age / 5) * 5, ages, interpolate=False) fac = table[self._fx][3:][self._ax1] return fac