Skip to content

Instantly share code, notes, and snippets.

@alex-hhh
Created December 4, 2017 12:42
Show Gist options
  • Save alex-hhh/5743eb50e792aae6c9859c50f62b5517 to your computer and use it in GitHub Desktop.
Save alex-hhh/5743eb50e792aae6c9859c50f62b5517 to your computer and use it in GitHub Desktop.
fatigue and running form scripts
#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))))
-- 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