Created
December 4, 2017 12:42
-
-
Save alex-hhh/5743eb50e792aae6c9859c50f62b5517 to your computer and use it in GitHub Desktop.
fatigue and running form scripts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#lang racket | |
;; You will need to clone https://github.com/alex-hhh/ActivityLog2 and fix the | |
;; require below to point to the al-interactive.rkt file. You will also need | |
;; to update the session ids and season names as they reference activities in | |
;; my own database. | |
(require "../../ActivityLog2/etc/al-interactive.rkt" | |
"../../ActivityLog2/rkt/data-frame.rkt" | |
"../../ActivityLog2/rkt/fmt-util.rkt") | |
(require plot racket/draw math/statistics) | |
;; Running sessions from the 2017 marathon season | |
(define q | |
" | |
select S.id | |
from A_SESSION S, SEASON N | |
where S.sport_id = 1 | |
and S.start_time between N.start_date and N.end_date | |
and N.name = 'Tri 2017 / Marathon' | |
order by S.start_time") | |
(define candidate-sessions | |
(query-list (current-database) q)) | |
(define max-pace 450) ; 7:30 / km | |
(define min-pace (+ 180 30)) ; 3:30 / km | |
;; Width and height of images we generate | |
(define img-width 800) | |
(define img-height 400) | |
(define density-kernel-size 3) ; passed to the `density` renderers | |
(define (add-heartbeats-series df) | |
(define hb 0) ; holds accumulated heart beats | |
;; Called by the `data-frame%/map` method with two adjacent data points, | |
;; PREV and NEXT. The value returned by this function will be the data point | |
;; in the new series at the same index as NEXT | |
(define (accumulate-heart-beats prev next) | |
(when prev ; first time PREV is #f | |
(match-let (((vector e1 hr1) prev) | |
((vector e2 hr2) next)) | |
(when (and e1 hr1 e2 hr2) | |
(let ((beats (/ (* 0.5 (+ hr1 hr2)) 60.0))) | |
(set! hb (+ hb (* beats (- e2 e1)))))))) | |
(exact-truncate hb)) | |
(unless (send df contains? "hb") ; might already be present due to caching! | |
(send df add-derived-series | |
"hb" ; new series name | |
'("elapsed" "hr") ; based on these series | |
accumulate-heart-beats ; generator function for the new series | |
))) | |
(define (heartbeats->fatigue hb) | |
;; 30 min @ 160bpm (z2 lower limit) increments, max out at 5. | |
(exact-truncate (min (+ 1 (/ hb (* 30 160))) 5.0))) | |
(define (fatigue-name f) | |
(cond ((<= f 1.0) "no fatigue") | |
((<= f 2.0) "mild fatigue") | |
((<= f 3.0) "moderate fatigue") | |
((<= f 4.0) "heavy fatigue") | |
(#t "severe fatigue"))) | |
;; Calculate a pace value (Min/Km) from cadence (Steps / Minute) and Stride | |
;; (meters). We use this function to make sure the cadence and stride | |
;; parameters are correlated with pace directly rather than use the possibly | |
;; filtered "pace" series in the data frame. | |
(define (cad+stride->pace cad stride) | |
(/ 1000 (/ (* stride 2.0 cad) 60.0))) | |
(define (collect-samples df hgct hvosc hcad hstride hcad+stride) | |
(send df for-each | |
'("hb" "gct" "vosc" "cad" "stride") | |
(lambda (data) | |
(match-define (vector hb gct vosc cad stride) data) | |
(when (and hb gct cad stride) | |
(let ((pace (cad+stride->pace cad stride)) | |
(fatigue (heartbeats->fatigue hb))) | |
(when (and (<= pace max-pace) (>= pace min-pace)) | |
(hash-update! hgct fatigue (lambda (l) (cons gct l)) '()) | |
(hash-update! hvosc fatigue (lambda (l) (cons vosc l)) '()) | |
(hash-update! hcad fatigue (lambda (l) (cons cad l)) '()) | |
(hash-update! hstride fatigue (lambda (l) (cons stride l)) '()) | |
(hash-update! hcad+stride fatigue (lambda (l) (cons (vector cad stride) l)) '()) | |
)))))) | |
(define gct-vs-fatigue (make-hash)) | |
(define vosc-vs-fatigue (make-hash)) | |
(define cad-vs-fatigue (make-hash)) | |
(define stride-vs-fatigue (make-hash)) | |
(define cad+stride-vs-fatigue (make-hash)) | |
(define (populate) | |
(define total (length candidate-sessions)) | |
(for ([(sid index) (in-indexed candidate-sessions)]) | |
(printf ".") (flush-output) | |
(let ((df (sid->df sid))) | |
(add-heartbeats-series df) | |
(collect-samples df | |
gct-vs-fatigue | |
vosc-vs-fatigue | |
cad-vs-fatigue | |
stride-vs-fatigue | |
cad+stride-vs-fatigue)))) | |
(define zone-colors | |
`((0 ,(make-object color% 165 165 165)) | |
(1 ,(make-object color% 102 188 255)) | |
(2 ,(make-object color% 68 114 196)) | |
(3 ,(make-object color% 84 130 53)) | |
(4 ,(make-object color% 237 125 49)) | |
(5 ,(make-object color% 192 0 0)) | |
(6 ,(make-object color% 128 46 5)) | |
(7 ,(make-object color% 178 0 255)) | |
(8 ,(make-object color% 102 0 255)) | |
(9 ,(make-object color% 12 0 255)))) | |
(define (make-scatter-group-renderer/1 group #:color color #:label [label #f] #:size [size 1.5]) | |
(define keys (sort (hash-keys group) <)) | |
;; (define color-map (make-key-colors keys color)) | |
(define first-time? #t) | |
(for/list ([key (in-list keys)]) | |
(define data (hash-ref group key)) | |
(begin0 | |
(points | |
data | |
#:sym 'fullcircle | |
#:fill-color color | |
#:color color | |
#:size (* (point-size) size) | |
#:x-jitter 0.5 | |
#:label (if first-time? label #f)) | |
(set! first-time? #f)))) | |
(define (cadence->stride speed cadence) | |
(if (> cadence 0) | |
(let ((step/sec (/ (* cadence 2.0) 60.0))) | |
(/ speed step/sec)) | |
+inf.0)) | |
(define (cad+stride-scatter-plot) | |
(parameterize ((plot-legend-anchor 'top-right) | |
(plot-title "Cadence + Stride") | |
(plot-x-label "Cadence (SPM)") | |
(plot-y-label "Stride (meters)")) | |
(plot-file | |
(append | |
(tick-grid) | |
(for/list ([k (sort (hash-keys cad+stride-vs-fatigue) <)]) | |
(let ((g (group-samples (hash-ref cad+stride-vs-fatigue k) 0 2)) | |
(c (car (cdr (assoc k zone-colors))))) | |
(make-scatter-group-renderer/1 g #:color c #:label (fatigue-name k)))) | |
;; Add constant pace lines | |
(for/list ([pace (in-range min-pace (add1 max-pace) 30)]) | |
(define speed (convert-pace->m/s pace)) | |
(define yval-fn (curry cadence->stride speed)) | |
(list | |
(function yval-fn #:color '(#x2f #x4f #x4f) #:width 1.5) | |
(point-label (vector 105 (yval-fn 105)) (pace->string speed #t) | |
#:point-size 0 #:anchor 'bottom-left)) | |
)) | |
"cad+stride-fatigue.svg" | |
#:x-min 70 | |
#:width img-width | |
#:height img-height))) | |
(define (gct-density-plot) | |
(parameterize ((plot-legend-anchor 'top-right) | |
(plot-title "Ground Contact Time Density") | |
(plot-x-label "Ground Contact Time (ms)") | |
(plot-y-label "")) | |
(plot-file | |
(for/list ([k (sort (hash-keys gct-vs-fatigue) <)]) | |
(density (hash-ref gct-vs-fatigue k) | |
density-kernel-size | |
#:width 2.0 | |
#:color (car (cdr (assoc k zone-colors))) | |
#:label (fatigue-name k))) | |
"gct-density.svg" | |
#:x-max 400 | |
#:width img-width | |
#:height img-height))) | |
(define (vosc-density-plot) | |
(parameterize ((plot-legend-anchor 'top-right) | |
(plot-title "Vertical Oscillation Density") | |
(plot-x-label "Vertical Oscillation (mm)") | |
(plot-y-label "")) | |
(plot-file | |
(for/list ([k (sort (hash-keys vosc-vs-fatigue) <)]) | |
(density (hash-ref vosc-vs-fatigue k) | |
density-kernel-size | |
#:width 2.0 | |
#:color (car (cdr (assoc k zone-colors))) | |
#:label (fatigue-name k))) | |
"vosc-density.svg" | |
#:x-max 100 | |
#:width img-width | |
#:height img-height))) | |
(define (cad-density-plot) | |
(parameterize ((plot-legend-anchor 'top-right) | |
(plot-title "Cadence Density") | |
(plot-x-label "Cadence SPM") | |
(plot-y-label "")) | |
(plot-file | |
(for/list ([k (sort (hash-keys cad-vs-fatigue) <)]) | |
(density (hash-ref cad-vs-fatigue k) | |
density-kernel-size | |
#:width 2 | |
#:color (car (cdr (assoc k zone-colors))) | |
#:label (fatigue-name k))) | |
"cad-density.svg" | |
#:x-min 75 | |
#:x-max 100 | |
#:width img-width | |
#:height img-height))) | |
(define (stride-density-plot) | |
(parameterize ((plot-legend-anchor 'top-right) | |
(plot-title "Stride Density") | |
(plot-x-label "Stride (meters)") | |
(plot-y-label "")) | |
(plot-file | |
(for/list ([k (sort (hash-keys stride-vs-fatigue) <)]) | |
(density (hash-ref stride-vs-fatigue k) | |
density-kernel-size | |
#:width 2 | |
#:color (car (cdr (assoc k zone-colors))) | |
#:label (fatigue-name k))) | |
"stride-density.svg" | |
#:x-min 0.8 | |
#:x-max 1.4 | |
#:width img-width | |
#:height img-height))) | |
(define (generate-all) | |
(populate) | |
(cad+stride-scatter-plot) | |
(gct-density-plot) | |
(vosc-density-plot) | |
(cad-density-plot) | |
(stride-density-plot)) | |
(define (pp-numerics data) | |
(for ((l (sort (hash-keys data) <))) | |
(define s (update-statistics* empty-statistics (hash-ref data l))) | |
(printf "~a -- ~a ~a~%" (fatigue-name l) | |
(statistics-mean s) | |
(statistics-stddev s)))) | |
;; (for ((l (sort (hash-keys cad-vs-fatigue) <))) (printf "~a -- ~a~%" l (length (hash-ref cad-vs-fatigue l)))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- find the number of sessions, total distance and total running time in the | |
-- marathon season | |
select count(S.id) as session_count, | |
round(total(SS.total_distance) / 1000.0, 1) as total_distance, | |
round(total(SS.total_timer_time) / 3600.0, 1) as total_time | |
from A_SESSION S, SEASON N, SECTION_SUMMARY SS | |
where S.sport_id = 1 | |
and S.summary_id = SS.id | |
and S.start_time between N.start_date and N.end_date | |
and N.name = 'Tri 2017 / Marathon'; | |
-- find the total number of data points collected in the marathon season | |
select count(T.id) as datapoint_count | |
from A_SESSION S, SEASON N, A_LAP L, A_LENGTH G, A_TRACKPOINT T | |
where S.sport_id = 1 | |
and T.length_id = G.id | |
and G.lap_id = L.id | |
and L.session_id = S.id | |
and S.start_time between N.start_date and N.end_date | |
and N.name = 'Tri 2017 / Marathon'; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment