Private
Public Access
1
0

adding cpoverlay (world class)

This commit is contained in:
Sander Roosendaal
2021-01-19 07:31:44 +01:00
parent d3985c139d
commit 0cda0255ce
4 changed files with 245 additions and 9 deletions

View File

@@ -1218,6 +1218,10 @@ class AnalysisChoiceForm(forms.Form):
cpfit = forms.ChoiceField(choices=cpfitchoices, cpfit = forms.ChoiceField(choices=cpfitchoices,
label = 'Model Fit',initial='data',required=False) label = 'Model Fit',initial='data',required=False)
cpoverlay = forms.BooleanField(initial=False,
label='Overlay World Record Performance',
required=False)
piece = forms.IntegerField(initial=4,label='Ranking Piece (minutes)', piece = forms.IntegerField(initial=4,label='Ranking Piece (minutes)',
required=False) required=False)

View File

@@ -3304,7 +3304,8 @@ def interactive_agegroupcpchart(age,normalized=False):
def interactive_otwcpchart(powerdf,promember=0,rowername="",r=None,cpfit='data', def interactive_otwcpchart(powerdf,promember=0,rowername="",r=None,cpfit='data',
title='',type='water'): title='',type='water',
wcpower=[],wcdurations=[],cpoverlay=False):
powerdf = powerdf[~(powerdf == 0).any(axis=1)] powerdf = powerdf[~(powerdf == 0).any(axis=1)]
# plot tools # plot tools
@@ -3356,6 +3357,34 @@ def interactive_otwcpchart(powerdf,promember=0,rowername="",r=None,cpfit='data',
workouts = powerdf['workout'] workouts = powerdf['workout']
urls = powerdf['url'] urls = powerdf['url']
# add world class
wcpower = pd.Series(wcpower)
wcdurations = pd.Series(wcdurations)
# fitting WC data to three parameter CP model
if len(wcdurations)>=4:
fitfunc = lambda pars,x: pars[0]/(1+(x/pars[2])) + pars[1]/(1+(x/pars[3]))
errfunc = lambda pars,x,y: fitfunc(pars,x)-y
p1wc, success = optimize.leastsq(errfunc, p0[:],
args = (wcdurations,wcpower))
else:
p1wc = None
if p1wc is not None and cpoverlay:
fitpowerwc = fitfunc(p1wc,fitt)
fitpowerexcellent = 0.7*fitfunc(p1wc,fitt)
fitpowergood = 0.6*fitfunc(p1wc,fitt)
fitpowerfair = 0.5*fitfunc(p1wc,fitt)
fitpoweraverage = 0.4*fitfunc(p1wc,fitt)
else:
fitpowerwc = 0*fitpower
fitpowerexcellent = 0*fitpower
fitpowergood = 0*fitpower
fitpowerfair = 0*fitpower
fitpoweraverage = 0*fitpower
sourcecomplex = ColumnDataSource( sourcecomplex = ColumnDataSource(
data = dict( data = dict(
@@ -3364,6 +3393,11 @@ def interactive_otwcpchart(powerdf,promember=0,rowername="",r=None,cpfit='data',
duration = fitt/60., duration = fitt/60.,
ftime = ftime, ftime = ftime,
workout = workouts, workout = workouts,
fitpowerwc = fitpowerwc,
fitpowerexcellent = fitpowerexcellent,
fitpowergood = fitpowergood,
fitpowerfair = fitpowerfair,
fitpoweraverage = fitpoweraverage,
url = urls, url = urls,
) )
) )
@@ -3424,6 +3458,7 @@ def interactive_otwcpchart(powerdf,promember=0,rowername="",r=None,cpfit='data',
('Power (W)','@CP{int}'), ('Power (W)','@CP{int}'),
('Power (W) upper','@CPmax{int}'), ('Power (W) upper','@CPmax{int}'),
('Workout','@workout'), ('Workout','@workout'),
('World Class','@fitpowerwc{int}')
]) ])
hover.mode = 'mouse' hover.mode = 'mouse'
@@ -3437,6 +3472,27 @@ def interactive_otwcpchart(powerdf,promember=0,rowername="",r=None,cpfit='data',
plot.line('duration','CPmax',source=sourcecomplex,legend_label="CP Model", plot.line('duration','CPmax',source=sourcecomplex,legend_label="CP Model",
color='red') color='red')
if p1wc is not None:
plot.line('duration','fitpowerwc',source=sourcecomplex,
legend_label="World Class",
color='darkgoldenrod',line_dash='dotted')
plot.line('duration','fitpowerexcellent',source=sourcecomplex,
legend_label="90% percentile",
color='goldenrod',line_dash='dotted')
plot.line('duration','fitpowergood',source=sourcecomplex,
legend_label="75% percentile",
color='sandybrown',line_dash='dotted')
plot.line('duration','fitpowerfair',source=sourcecomplex,
legend_label="50% percentile",
color='rosybrown',line_dash='dotted')
plot.line('duration','fitpoweraverage',source=sourcecomplex,
legend_label="25% percentile",
color='tan',line_dash='dotted')
script, div = components(plot) script, div = components(plot)
return [script,div,p1,ratio,message] return [script,div,p1,ratio,message]

View File

@@ -91,6 +91,7 @@
var reststrokes = $("#id_includereststrokes").parent().parent(); var reststrokes = $("#id_includereststrokes").parent().parent();
var piece = $("#id_piece").parent().parent(); var piece = $("#id_piece").parent().parent();
var cpfit = $("#id_cpfit").parent().parent(); var cpfit = $("#id_cpfit").parent().parent();
var cpoverlay = $("#id_cpoverlay").parent().parent();
// Hide the fields. // Hide the fields.
@@ -113,6 +114,7 @@
spmmin.hide(); spmmin.hide();
spmmax.hide(); spmmax.hide();
cpfit.hide(); cpfit.hide();
cpoverlay.hide();
piece.hide(); piece.hide();
if (functionfield.val() == 'boxplot') { if (functionfield.val() == 'boxplot') {
@@ -153,6 +155,7 @@
if (functionfield.val() == 'cp') { if (functionfield.val() == 'cp') {
cpfit.show(); cpfit.show();
piece.show(); piece.show();
cpoverlay.show();
} }
@@ -182,6 +185,7 @@
plottype.hide(); plottype.hide();
reststrokes.show(); reststrokes.show();
cpfit.hide(); cpfit.hide();
cpoverlay.hide();
piece.hide(); piece.hide();
} }
else if (Value=='histo') { else if (Value=='histo') {
@@ -202,6 +206,7 @@
plottype.hide(); plottype.hide();
reststrokes.show(); reststrokes.show();
cpfit.hide(); cpfit.hide();
cpoverlay.hide();
piece.hide(); piece.hide();
} }
@@ -223,6 +228,7 @@
plottype.hide(); plottype.hide();
reststrokes.show(); reststrokes.show();
cpfit.hide(); cpfit.hide();
cpoverlay.hide();
piece.hide(); piece.hide();
} }
@@ -244,6 +250,7 @@
errorbars.hide(); errorbars.hide();
reststrokes.show(); reststrokes.show();
cpfit.hide(); cpfit.hide();
cpoverlay.hide();
piece.hide(); piece.hide();
} }
else if (Value=='stats') { else if (Value=='stats') {
@@ -260,6 +267,7 @@
plottype.hide(); plottype.hide();
reststrokes.show(); reststrokes.show();
cpfit.hide(); cpfit.hide();
cpoverlay.hide();
piece.hide(); piece.hide();
} }
else if (Value=='compare') { else if (Value=='compare') {
@@ -280,6 +288,7 @@
errorbars.hide(); errorbars.hide();
piece.hide(); piece.hide();
cpfit.hide(); cpfit.hide();
cpoverlay.hide();
reststrokes.show(); reststrokes.show();
@@ -302,6 +311,7 @@
plottype.hide(); plottype.hide();
reststrokes.hide(); reststrokes.hide();
cpfit.show(); cpfit.show();
cpoverlay.hide();
piece.show(); piece.show();
} }
}); });
@@ -372,6 +382,126 @@
<input name='optionsform' type="submit" value="Submit"> <input name='optionsform' type="submit" value="Submit">
</form> </form>
</li> </li>
{% if worldclass %}
{% if age and sex != 'not specified' %}
<li class="grid_4">
<h2>World Records</h2>
<p>The dashed lines are based on the
<a href="https://log.concept2.com/rankings">Concept2</a>
rankings for your age ({{ age }}), gender ({{ sex }})
and weight category ({{ weightcategory }}). World class means within 5% of
<a href="http://www.concept2.com/indoor-rowers/racing/records/world">
World Record</a> in terms
of power.
The percentile lines are estimates of where the percentiles
of the Concept2 rankings historically are for those of exactly
your age, gender and weight class.
</p>
<p>
For rowing on the water, the results are corrected for the expected
difference in power between the Concept2 indoor rower and power
values on the water.
</p>
</li>
<li>
<table width="100%" class="listtable">
<tbody>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/100m/">100m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/500m/">500m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/1000m/">1000m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/2000m/">2000m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/5000m/">5000m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/6000m/">6000m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/10000m/">10000m
</a>
</td>
</tr>
<tr>
<td>
<a target="_"
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/21097m/">Half Marathon
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/42195m/">Full Marathon
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/1min/">1 minute
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/4min/">4 minutes
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/30min/">30 minutes
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/60min/">1 hour
</a>
</td>
</tr>
</tbody>
</table>
</li>
{% endif %}
{% endif %}
</ul> </ul>

View File

@@ -39,6 +39,7 @@ def analysis_new(request,userid=0,function='boxplot',teamid=0,id=''):
r = getrequestrower(request, userid=userid) r = getrequestrower(request, userid=userid)
user = r.user user = r.user
userid = user.id userid = user.id
worldclass = False
firstworkout = None firstworkout = None
if id: if id:
@@ -71,6 +72,10 @@ def analysis_new(request,userid=0,function='boxplot',teamid=0,id=''):
modalities = [m[0] for m in mytypes.workouttypes_ordered.items()] modalities = [m[0] for m in mytypes.workouttypes_ordered.items()]
modality = 'all' modality = 'all'
try:
worldclass = options['cpoverlay']
except KeyError:
worldclass = False
try: try:
@@ -132,6 +137,12 @@ def analysis_new(request,userid=0,function='boxplot',teamid=0,id=''):
options['modalities'] = modalities options['modalities'] = modalities
options['waterboattype'] = waterboattype options['waterboattype'] = waterboattype
try:
worldclass = options['cpoverlay']
except KeyError:
worldclass = False
options['cpoverlay'] = worldclass
chartform = AnalysisChoiceForm(request.POST) chartform = AnalysisChoiceForm(request.POST)
if chartform.is_valid(): if chartform.is_valid():
@@ -240,6 +251,10 @@ def analysis_new(request,userid=0,function='boxplot',teamid=0,id=''):
}) })
if r.birthdate:
age = calculate_age(r.birthdate)
else:
age = 0
startdatestring = startdate.strftime('%Y-%m-%d') startdatestring = startdate.strftime('%Y-%m-%d')
enddatestring = enddate.strftime('%Y-%m-%d') enddatestring = enddate.strftime('%Y-%m-%d')
@@ -272,6 +287,10 @@ def analysis_new(request,userid=0,function='boxplot',teamid=0,id=''):
'chartform':chartform, 'chartform':chartform,
'searchform':searchform, 'searchform':searchform,
'optionsform':optionsform, 'optionsform':optionsform,
'worldclass':worldclass,
'age':age,
'sex':r.sex,
'weightcategory':r.weightcategory,
'teams':get_my_teams(request.user), 'teams':get_my_teams(request.user),
}) })
@@ -519,6 +538,7 @@ def histodata(workouts, options):
def cpdata(workouts, options): def cpdata(workouts, options):
userid = options['userid'] userid = options['userid']
cpfit = options['cpfit'] cpfit = options['cpfit']
cpoverlay = options['cpoverlay']
u = User.objects.get(id=userid) u = User.objects.get(id=userid)
r = u.rower r = u.rower
@@ -546,6 +566,9 @@ def cpdata(workouts, options):
rowername = r.user.first_name+" "+r.user.last_name rowername = r.user.first_name+" "+r.user.last_name
wcdurations = []
wcpower = []
if len(powerdf) !=0 : if len(powerdf) !=0 :
datefirst = pd.Series(w.date for w in workouts).min() datefirst = pd.Series(w.date for w in workouts).min()
datelast = pd.Series(w.date for w in workouts).max() datelast = pd.Series(w.date for w in workouts).max()
@@ -561,19 +584,42 @@ def cpdata(workouts, options):
# for Mike # for Mike
wtype = 'erg' wtype = 'erg'
if cpoverlay:
if r.birthdate:
age = calculate_age(r.birthdate)
else:
worldclasspower = None
age = 0
agerecords = CalcAgePerformance.objects.filter(
age = age,
sex = r.sex,
weightcategory = r.weightcategory
)
if len(agerecords) == 0:
wcpower = []
wcdurations = []
else:
wcdurations = []
wcpower = []
for record in agerecords:
recordpower = record.power
if wtype == 'water':
recordpower = record.power*(100.-r.otwslack)/100.
wcdurations.append(record.duration)
wcpower.append(recordpower)
res = interactive_otwcpchart(powerdf,promember=True,rowername=rowername,r=r, res = interactive_otwcpchart(powerdf,promember=True,rowername=rowername,r=r,
cpfit=cpfit,title=title,type=wtype) cpfit=cpfit,title=title,type=wtype,
cpoverlay=cpoverlay,
wcdurations=wcdurations,wcpower=wcpower)
script = res[0] script = res[0]
div = res[1] div = res[1]
p1 = res[2] p1 = res[2]
ratio = res[3] ratio = res[3]
#r.p0 = p1[0]
#r.p1 = p1[1]
#r.p2 = p1[2]
#r.p3 = p1[3]
#r.cpratio = ratio
#r.save()
paulslope = 1 paulslope = 1
paulintercept = 1 paulintercept = 1
message = res[4] message = res[4]
@@ -2194,7 +2240,7 @@ def rankings_view2(request,userid=0,
if len(agerecords) == 0: if len(agerecords) == 0:
recalc = True recalc = True
wcpower = [] wcpower = []
wcduration = [] wcdurations = []
else: else:
wcdurations = [] wcdurations = []
wcpower = [] wcpower = []