Live-code the Banister impulse-response model in Stan. Difference equations for training load, physiological time constants from exercise science literature, Student-t likelihood for robustness, and posterior predictive checks on real cycling power data.
Scott brings domain expertise from cycling performance to this lesson. He personally tracks training load data and uses these models in practice. His BayeZian II course extends beyond standard regression into ODE solvers, Gaussian Processes, and difference equations applied to real physiological systems.
The mathematical framework from exercise physiology: training load drives fitness (positive adaptation) and fatigue (negative adaptation) through exponential decay. Performance = baseline + fitness effect - fatigue effect.
Setting up the Stan data block for time series with missing observations. Observation indices, workload arrays, initial conditions. Separating the full time grid from the observed data points.
Implementing the exponential decay recursion: G[t] = exp(-1/tau_g) * G[t-1] + W[t-1]. Initializing with boundary conditions, then looping through time to compute fitness, fatigue, and predicted performance at every point.
Time constants from Clark and Skiba (2013): tau_g ~ Normal(35, uncertainty) for fitness decay in days, tau_h ~ Normal(21, uncertainty) for fatigue decay. Half-normal priors on scaling factors. Domain knowledge directly shaping the model.
Why a normal likelihood fails when athletes have occasional outlier performance measurements. Using Student-t with 3 degrees of freedom to downweight extreme observations without discarding them.
Using the student_t_rng function in Stan's generated quantities block to simulate posterior predictive performance trajectories. Checking model fit against observed data across the full training timeline.
transformed parameters { array[T] real G; // fitness state array[T] real H; // fatigue state array[T] real P_hat; // predicted performance // Initialize from boundary conditions G[1] = exp(-1.0/tau[1]) * G0 + W0; H[1] = exp(-1.0/tau[2]) * H0 + W0; P_hat[1] = P0 + K[1]*G[1] - K[2]*H[1]; // Banister difference equations for (t in 2:T) { G[t] = exp(-1.0/tau[1]) * G[t-1] + W[t-1]; H[t] = exp(-1.0/tau[2]) * H[t-1] + W[t-1]; P_hat[t] = P0 + K[1]*G[t] - K[2]*H[t]; } }
The Banister model is a starting point. Here are four ways to extend it into production tools or research-quality work.
Extend from a single workload input to separate cardiovascular, strength, and sport-specific loads. Each component gets its own fitness/fatigue dynamics. Estimate how different training modalities contribute to race-day performance differently.
Use the posterior distributions of tau and K to simulate thousands of training schedules leading into a target event. Find the taper protocol that maximizes predicted performance. Deploy as a Shiny app where athletes input their training history and get a personalized taper plan.
Apply the hierarchical framework from BayeZian I to the fitness-fatigue model. Shared population-level time constants across a team of athletes, with individual-level K parameters. Partial pooling lets you estimate personalized responses even for athletes with limited testing data.
The fatigue state H is a proxy for accumulated physiological stress. Set threshold alerts when H exceeds historical norms for a given athlete. Combine with external load data (GPS, accelerometer) to build a Bayesian injury risk dashboard that updates daily.
Advanced Bayesian modeling in Stan. Gaussian Processes, ODE solvers, difference equations, non-centered parameterizations, and production deployment. Built on real athlete performance data, cycling power metrics, and sports physics.
Both BayeZian courses are included in the membership. All courses, 120+ live sessions per year, and enterprise Posit Workbench. Founding spots limited to 250.
Get Presale AccessNo spam. Just presale pricing and launch updates.