cumstats
This commit is contained in:
@@ -114,7 +114,7 @@ def timedeltaconv(x):
|
||||
return dt
|
||||
|
||||
# Create new workout from file and store it in the database
|
||||
# This routine should be used everywhere in views.py and mailprocessing.pu
|
||||
# This routine should be used everywhere in views.py and mailprocessing.py
|
||||
# Currently there is code duplication
|
||||
def new_workout_from_file(r,f2,
|
||||
workouttype='rower',
|
||||
|
||||
@@ -38,35 +38,39 @@
|
||||
</div>
|
||||
|
||||
<div class="grid_6 omega">
|
||||
<h2>Pro</h2>
|
||||
<div class="grid_2 alpha">
|
||||
<p>
|
||||
{% if user.rower.rowerplan == 'pro' %}
|
||||
<a class="button blue small" href="/rowers/histo">Power Histogram</a>
|
||||
{% else %}
|
||||
<a class="button blue small" href="/rowers/promembership">Power Histogram</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
Plot a power histogram of all your strokes over a date range.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_2">
|
||||
<p class="button white small">
|
||||
Pro Feature 2
|
||||
</p>
|
||||
<p>
|
||||
Reserved for future functionality.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_2 omega">
|
||||
<p class="button white small">
|
||||
Pro Feature 3
|
||||
</p>
|
||||
<p>
|
||||
Reserved for future functionality.
|
||||
</p>
|
||||
</div>
|
||||
<h2>Pro</h2>
|
||||
<div class="grid_2 alpha">
|
||||
<p>
|
||||
{% if user.rower.rowerplan == 'pro' %}
|
||||
<a class="button blue small" href="/rowers/histo">Power Histogram</a>
|
||||
{% else %}
|
||||
<a class="button blue small" href="/rowers/promembership">Power Histogram</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
Plot a power histogram of all your strokes over a date range.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_2">
|
||||
<p>
|
||||
{% if user.rower.rowerplan == 'pro' %}
|
||||
<a class="button blue small" href="/rowers/cumstats">Statistics</a>
|
||||
{% else %}
|
||||
<a class="button blue small" href="/rowers/promembership">Statistics</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
BETA: Statistics of stroke metrics over a date range
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_2 omega">
|
||||
<p class="button white small">
|
||||
Pro Feature 3
|
||||
</p>
|
||||
<p>
|
||||
Reserved for future functionality.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
139
rowers/templates/cumstats.html
Normal file
139
rowers/templates/cumstats.html
Normal file
@@ -0,0 +1,139 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
{% block title %}Workout Statistics{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid_12 alpha">
|
||||
<h1>Workout Statistics for</h1>
|
||||
<p>
|
||||
This is an experimental page which just lists a bunch of statistics for
|
||||
your workouts. This page is under rapid development.
|
||||
</p>
|
||||
</div>
|
||||
<div id="summary" class="grid_6 alpha">
|
||||
|
||||
|
||||
|
||||
<p>Summary for {{ theuser.first_name }} {{ theuser.last_name }}
|
||||
between {{ startdate|date }} and {{ enddate|date }}</p>
|
||||
|
||||
<p>Direct link for other Pro users:
|
||||
<a href="/rowers/{{ id }}/cumstats/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}">https://rowsandall.com/rowers/{{ id }}/cumstats/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}</a>
|
||||
</p>
|
||||
<div class="grid_2 omega suffix_4 tooltip">
|
||||
<form enctype="multipart/form-data" action="{{ formloc }}" method="post">
|
||||
{% csrf_token %}
|
||||
{% if workstrokesonly == True %}
|
||||
<input class="grid_2 alpha button blue small" type="hidden" name="workstrokesonly" value="False">
|
||||
<input class="grid_2 alpha button blue small" value="Include Rest Strokes" type="Submit">
|
||||
{% else %}
|
||||
<input class="grid_2 alpha button blue small" type="hidden" name="workstrokesonly" value="True">
|
||||
<input class="grid_2 alpha button blue small" value="Remove Rest Strokes" type="Submit">
|
||||
{% endif %}
|
||||
</form>
|
||||
<span class="tooltiptext">If your data source allows, this will show or hide strokes taken during rest intervals.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="form" class="grid_6 omega">
|
||||
<p>Use this form to select a different date range:</p>
|
||||
<p>
|
||||
Select start and end date for a date range:
|
||||
<div class="grid_4 alpha">
|
||||
|
||||
<form enctype="multipart/form-data" action="" method="post">
|
||||
|
||||
<table>
|
||||
{{ form.as_table }}
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
</div>
|
||||
<div class="grid_2 omega">
|
||||
<input name='daterange' class="button green" type="submit" value="Submit"> </form>
|
||||
</div>
|
||||
<div class="grid_4 alpha">
|
||||
<form enctype="multipart/form-data" action="" method="post">
|
||||
Or use the last {{ deltaform }} days.
|
||||
</div>
|
||||
<div class="grid_2 omega">
|
||||
{% csrf_token %}
|
||||
<input name='datedelta' class="button green" type="submit" value="Submit">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid_4 alpha">
|
||||
{% if stats %}
|
||||
{% for key, value in stats.items %}
|
||||
<h2>{{ value.verbosename }}</h2>
|
||||
<table width="100%" class="listtable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Metric</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Mean</td><td>{{ value.mean|floatformat:-2 }}</td>
|
||||
</tr><tr>
|
||||
<td>Minimum</td><td>{{ value.min|floatformat:-2 }}</td>
|
||||
</tr><tr>
|
||||
<td>25%</td><td>{{ value.firstq|floatformat:-2 }}</td>
|
||||
</tr><tr>
|
||||
<td>Median</td><td>{{ value.median|floatformat:-2 }}</td>
|
||||
</tr><tr>
|
||||
<td>75%</td><td>{{ value.thirdq|floatformat:-2 }}</td>
|
||||
</tr><tr>
|
||||
<td>Maximum</td><td>{{ value.max|floatformat:-2 }}</td>
|
||||
</tr><tr>
|
||||
<td>Standard Deviation</td><td>{{ value.std|floatformat:-2 }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="grid_8 omega">
|
||||
{% if cordict %}
|
||||
<h2> Correlation table</h2>
|
||||
<p>This table indicates a positive (+) or negative (-) correlation between two parameters. The strong correlations are indicated with ++ and --.
|
||||
</p>
|
||||
<table width="90%" class="cortable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
{% for key,value in cordict.items %}
|
||||
<th class="rotate"><div><span>{{ key }}</span></div></th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for key, thedict in cordict.items %}
|
||||
<tr>
|
||||
<th> {{ key }}</th>
|
||||
{% for key2,value in thedict.items %}
|
||||
<td>
|
||||
{% if value > 0.5 %}
|
||||
<div class="poscor">++</div>
|
||||
{% elif value > 0.1 %}
|
||||
<div class="weakposcor">+</div>
|
||||
{% elif value < -0.5 %}
|
||||
<div class="negcor">--</div>
|
||||
{% elif value < -0.1 %}
|
||||
<div class="weaknegcor">-</div>
|
||||
{% else %}
|
||||
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -39,6 +39,7 @@
|
||||
<li>SpeedCoach GPS and SpeedCoach GPS 2 CSV export</li>
|
||||
<li>ErgData CSV export</li>
|
||||
<li>ErgStick CSV export</li>
|
||||
<li>BoatCoach CSV export</li>
|
||||
<li>A FIT file with location data (experimental)</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
@@ -128,6 +128,11 @@ urlpatterns = [
|
||||
url(r'^histo/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.histo),
|
||||
url(r'^histo/(?P<deltadays>\d+)$',views.histo),
|
||||
url(r'^histo/$',views.histo),
|
||||
url(r'^(?P<theuser>\d+)/cumstats/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.cumstats),
|
||||
url(r'^(?P<theuser>\d+)/cumstats/(?P<deltadays>\d+)$',views.cumstats),
|
||||
url(r'^cumstats/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.cumstats),
|
||||
url(r'^cumstats/(?P<deltadays>\d+)$',views.cumstats),
|
||||
url(r'^cumstats/$',views.cumstats),
|
||||
url(r'^histo-all/$',views.histo_all),
|
||||
url(r'^(?P<theuser>\d+)/histo-all/$',views.histo_all),
|
||||
url(r'^graph/(\d+)/$',views.graph_show_view),
|
||||
|
||||
189
rowers/views.py
189
rowers/views.py
@@ -1526,6 +1526,8 @@ def workout_histo_view(request,id=0):
|
||||
'mayedit':mayedit,
|
||||
})
|
||||
|
||||
|
||||
|
||||
# Histogram for a date/time range
|
||||
@login_required()
|
||||
def histo(request,theuser=0,
|
||||
@@ -2604,6 +2606,193 @@ def workout_geeky_view(request,id=0,message="",successmessage=""):
|
||||
'successmessage': successmessage,
|
||||
'interactiveplot':script,
|
||||
'the_div':div})
|
||||
|
||||
# Cumulative stats page
|
||||
@login_required()
|
||||
def cumstats(request,theuser=0,
|
||||
startdate=timezone.now()-datetime.timedelta(days=365),
|
||||
enddate=timezone.now(),
|
||||
deltadays=-1,
|
||||
startdatestring="",
|
||||
enddatestring="",
|
||||
workstrokesonly=True):
|
||||
|
||||
workstrokesonly = True
|
||||
if request.method == 'POST' and 'workstrokesonly' in request.POST:
|
||||
workstrokesonly = request.POST['workstrokesonly']
|
||||
|
||||
if deltadays>0:
|
||||
startdate = enddate-datetime.timedelta(days=int(deltadays))
|
||||
|
||||
if startdatestring != "":
|
||||
startdate = iso8601.parse_date(startdatestring)
|
||||
|
||||
if enddatestring != "":
|
||||
enddate = iso8601.parse_date(enddatestring)
|
||||
|
||||
if enddate < startdate:
|
||||
s = enddate
|
||||
enddate = startdate
|
||||
startdate = s
|
||||
|
||||
promember=0
|
||||
if theuser == 0:
|
||||
theuser = request.user.id
|
||||
|
||||
if not request.user.is_anonymous():
|
||||
r = Rower.objects.get(user=request.user)
|
||||
result = request.user.is_authenticated() and r.rowerplan=='pro'
|
||||
if result:
|
||||
promember=1
|
||||
|
||||
if not promember:
|
||||
return HttpResponseRedirect("/rowers/promembership/")
|
||||
|
||||
# get all indoor rows of in date range
|
||||
# process form
|
||||
if request.method == 'POST' and "daterange" in request.POST:
|
||||
form = DateRangeForm(request.POST)
|
||||
deltaform = DeltaDaysForm(request.POST)
|
||||
if form.is_valid():
|
||||
startdate = form.cleaned_data['startdate']
|
||||
enddate = form.cleaned_data['enddate']
|
||||
if startdate > enddate:
|
||||
s = enddate
|
||||
enddate = startdate
|
||||
startdate = s
|
||||
elif request.method == 'POST' and "datedelta" in request.POST:
|
||||
deltaform = DeltaDaysForm(request.POST)
|
||||
if deltaform.is_valid():
|
||||
deltadays = deltaform.cleaned_data['deltadays']
|
||||
if deltadays != 0:
|
||||
enddate = timezone.now()
|
||||
startdate = enddate-datetime.timedelta(days=deltadays)
|
||||
if startdate > enddate:
|
||||
s = enddate
|
||||
enddate = startdate
|
||||
startdate = s
|
||||
form = DateRangeForm(initial={
|
||||
'startdate': startdate,
|
||||
'enddate': enddate,
|
||||
})
|
||||
|
||||
else:
|
||||
form = DateRangeForm(initial={
|
||||
'startdate': startdate,
|
||||
'enddate': enddate,
|
||||
})
|
||||
deltaform = DeltaDaysForm()
|
||||
|
||||
try:
|
||||
r2 = Rower.objects.get(user=theuser)
|
||||
allergworkouts = Workout.objects.filter(user=r2,
|
||||
workouttype__in=['rower','dynamic','slides'],
|
||||
startdatetime__gte=startdate,
|
||||
startdatetime__lte=enddate)
|
||||
|
||||
except Rower.DoesNotExist:
|
||||
allergworkouts = []
|
||||
r2=0
|
||||
|
||||
try:
|
||||
u = User.objects.get(id=theuser)
|
||||
except User.DoesNotExist:
|
||||
u = ''
|
||||
|
||||
ids = [workout.id for workout in allergworkouts]
|
||||
|
||||
# Get field names and remove those that are not useful in stats
|
||||
fields = StrokeData._meta.get_fields()
|
||||
|
||||
fielddict = {field.name:field.verbose_name for field in fields}
|
||||
|
||||
|
||||
fielddict.pop('workoutid')
|
||||
fielddict.pop('ergpace')
|
||||
fielddict.pop('hr_an')
|
||||
fielddict.pop('hr_tr')
|
||||
fielddict.pop('hr_at')
|
||||
fielddict.pop('hr_ut2')
|
||||
fielddict.pop('hr_ut1')
|
||||
fielddict.pop('time')
|
||||
fielddict.pop('distance')
|
||||
fielddict.pop('nowindpace')
|
||||
fielddict.pop('fnowindpace')
|
||||
fielddict.pop('fergpace')
|
||||
fielddict.pop('equivergpower')
|
||||
fielddict.pop('workoutstate')
|
||||
fielddict.pop('fpace')
|
||||
fielddict.pop('pace')
|
||||
fielddict.pop('id')
|
||||
fielddict.pop('ftime')
|
||||
fielddict.pop('x_right')
|
||||
fielddict.pop('hr_max')
|
||||
fielddict.pop('hr_bottom')
|
||||
fielddict.pop('cumdist')
|
||||
|
||||
fieldlist = [field for field,value in fielddict.iteritems()]
|
||||
|
||||
# prepare data frame
|
||||
datadf = dataprep.read_cols_df_sql(ids,fieldlist)
|
||||
|
||||
if datadf.empty:
|
||||
return HttpResponse("No data found")
|
||||
|
||||
workoutstateswork = [1,4,5,8,9,6,7]
|
||||
workoutstatesrest = [3]
|
||||
workoutstatetransition = [0,2,10,11,12,13]
|
||||
|
||||
if workstrokesonly=='True' or workstrokesonly==True:
|
||||
try:
|
||||
datadf = datadf[~datadf['workoutstate'].isin(workoutstatesrest)]
|
||||
except:
|
||||
pass
|
||||
workstrokesonly = True
|
||||
|
||||
# Create stats
|
||||
stats = {}
|
||||
|
||||
|
||||
for field,verbosename in fielddict.iteritems():
|
||||
thedict = {
|
||||
'mean':datadf[field].mean(),
|
||||
'min': datadf[field].min(),
|
||||
'std': datadf[field].std(),
|
||||
'max': datadf[field].max(),
|
||||
'median': datadf[field].median(),
|
||||
'firstq':datadf[field].quantile(q=0.25),
|
||||
'thirdq':datadf[field].quantile(q=0.75),
|
||||
'verbosename':verbosename,
|
||||
}
|
||||
stats[field] = thedict
|
||||
|
||||
# Create a dict with correlation values
|
||||
cor = datadf.corr()
|
||||
cor.fillna(value=0,inplace=True)
|
||||
cordict = {}
|
||||
for field1,verbosename in fielddict.iteritems():
|
||||
thedict = {}
|
||||
for field2,verbosename in fielddict.iteritems():
|
||||
try:
|
||||
thedict[field2] = cor.ix[field1,field2]
|
||||
except KeyError:
|
||||
thedict[field2] = 0
|
||||
|
||||
cordict[field1] = thedict
|
||||
|
||||
return render(request,
|
||||
'cumstats.html',
|
||||
{
|
||||
'stats':stats,
|
||||
'workstrokesonly':workstrokesonly,
|
||||
'id':theuser,
|
||||
'theuser':u,
|
||||
'startdate':startdate,
|
||||
'enddate':enddate,
|
||||
'form':form,
|
||||
'deltaform':deltaform,
|
||||
'cordict':cordict,
|
||||
})
|
||||
|
||||
# Stats page
|
||||
@login_required()
|
||||
|
||||
Reference in New Issue
Block a user