Data Science and Fitness Workouts

Analysing Workouts and Meals with Mathematica

I do a lot of sports. I go jogging, and swimming, do stretching, and love functional- and crossfit workouts. Recently I also attended my first Hyrox competition. I really enjoyed that challenge, and currently I prepare for the next season. Hence, I need a way to analyze the performance of my workouts, track my results and keep an eye on my nutrition. Most people would probably use an app for that. In fact, I do too. For my workouts I use Polar Flow and for my nutrition I use FDDB. I like the fact, that the Polar Flow lets me export my data in plain CSV or GPX format. Most of the information from FDDB is freely available anyway. So it’s basically just a matter of copy and pasting the data from the website into an Excel sheet or similar if I wanted to export it.

Many people claim that nowadays Jupyter and Python is the way to go when it comes to data analysis. I was curious how well Wolfram Mathematica would perform. So I put together some code that lets me analyze my workouts and that optimizes my meals in terms of macro nutriments such as protein, carbohydrates, fat, and fiber.

Getting the data into Mathematica was straightforward. There are built-in functions to read CSV, GPX, and Excel sheets. I converted most of them to Datasets. Getting the average heart rate of a workout or the number of calories consumed on a particular day was then usually done with 2 or 3 lines of code. Also, Mathematica provides a huge variety of visualizations and plots to explore the data.

The following code for example gives me a nice bar chart with the distribution of my workouts. The variable fullWorkoutData is a list with all workouts as Datasets.

SportsDistribution = 
  Counts[Map[#["metadata"]["Sport"] &, fullWorkoutData]];
BarChart[
 SportsDistribution,
 ColorFunction -> Function[{height}, ColorData["Rainbow"][height]],
 ChartLabels -> Automatic,
 PlotLabel -> 
  "Activities (Total: " <> ToString[Total[SportsDistribution]] <> 
   " Workouts)",
 PlotTheme -> "Detailed"
 ]

The result looks as follows.

workout distribution

Optimizing food quantities can be formulated as a linear program. Formulae for your total daily energy needs are easily implemented in Mathematica as well. Hence, also that worked really well.

The whole thing is work in progress, but the code is hosted on Gitlab. Feel free to look at it.

To give you a short demo what the code is currently capable of doing, let’s assume you have the following database with food nutriments, and you wanted to compute the optimal breakfast.

foodDB = <|
   "Banana" -> <|"Calories" -> Quantity[95, "Calories15C"], 
     "Carbs" -> Quantity[21, "Grams"], 
     "Sugar" -> Quantity[17.2, "Grams"], 
     "Protein" -> Quantity[1.2, "Grams"], 
     "Fat" -> Quantity[0.3, "Grams"]|>,
   "Oats" -> <|"Calories" -> Quantity[370, "Calories15C"], 
     "Carbs" -> Quantity[53.2, "Grams"], 
     "Sugar" -> Quantity[10.20, "Grams"], 
     "Protein" -> Quantity[10.90, "Grams"], 
     "Fat" -> Quantity[10.20, "Grams"]|>,
   "Whey" -> <|"Calories" -> Quantity[358, "Calories15C"], 
     "Carbs" -> Quantity[1.4, "Grams"], 
     "Sugar" -> Quantity[1.4, "Grams"], 
     "Protein" -> Quantity[87.0, "Grams"], 
     "Fat" -> Quantity[0.5, "Grams"]|>,
   "Egg" -> <|"Calories" -> Quantity[154, "Calories15C"], 
     "Carbs" -> Quantity[0.5, "Grams"], 
     "Sugar" -> Quantity[0.1, "Grams"], 
     "Protein" -> Quantity[12.90, "Grams"], 
     "Fat" -> Quantity[10.50, "Grams"]|>,
   "Soy Milk" -> <|"Calories" -> Quantity[39, "Calories15C"], 
     "Carbs" -> Quantity[2.5, "Grams"], 
     "Sugar" -> Quantity[2.5, "Grams"], 
     "Protein" -> Quantity[3.0, "Grams"], 
     "Fat" -> Quantity[1.8, "Grams"]|>,
   "Yoghurt" -> <|"Calories" -> Quantity[51, "Calories15C"], 
     "Carbs" -> Quantity[6.6, "Grams"], 
     "Sugar" -> Quantity[6.6, "Grams"], 
     "Protein" -> Quantity[5.3, "Grams"], 
     "Fat" -> Quantity[0.1, "Grams"]|>
   |>;

Note how you can easily add physical units to your computations. This makes it significantly easier to debug your code. If you expect calories but receive grams as unit, then something went wrong! All quantities are listed in per 100 gram here. I could have added that as well to the units above, but I am not sure if specifying something like Quantity[4,"Grams"/(100 "Grams")] is any better in terms of readability and interpretation.

With the Nutrition module from my repository, you get your optimal meal via the following code. The target ranges are taken from the website of the Deutsche Gesellschaft für Ernährung. For the sake of simplicity I evenly distributed the suggested values over breakfast, lunch and dinner.

macros = {"Calories", "Carbs", "Sugar", "Protein", "Fat"};
numberOfMealsPerDay = 3;
targetRange = <|
    "Calories" -> {Quantity[1750, "Grams"/"Day"], 
      Quantity[2800, "Grams"/"Day"]},
    "Carbs" -> {Quantity[340, "Grams"/"Day"], 
      Quantity[400, "Grams"/"Day"]},
    "Sugar" -> {Quantity[0, "Grams"/"Day"], 
      Quantity[90, "Grams"/"Day"]},
    "Protein" -> {Quantity[71, "Grams"/"Day"], 
      Quantity[213, "Grams"/"Day"]},
    "Fat" -> {Quantity[58, "Grams"/"Day"], Quantity[78, "Grams"/"Day"]}
    |>/numberOfMealsPerDay;

breakfast = {"Banana", "Oats", "Whey", "Egg", "Soy Milk"};
costBreakfast = ComputeCostFunction[breakfast, foodDB];
conditionMatrixBreakfast = 
  ComputeConditionMatrix[breakfast, macros, foodDB];
boundsBreakfast = ComputeBounds[macros, targetRange];
{costevalBreakfast, optimalBreakfast} = 
 ComputeOptimalMeal[breakfast, costBreakfast, 
  conditionMatrixBreakfast, boundsBreakfast, foodDB]

This code will tell you that your optimal breakfast should consist of

  • 62 grams of banana
  • 190 grams of oats
  • 3 grams of whey protein
  • sadly, no milk and no eggs

In this case you have a meal consisting of 765 calories with 113 grams of carbohydrates, 30 grams of sugar, 24 grams of protein and 19 grams of fat.

It’s interesting to note that sugar is the limiting factor in this example. If you reduce the maximal range of the allowed amount of sugar just slightly, then the above system is not solvable anymore. Hence, in this case, a better meal would require that we exchange the ingredients!

Notes

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License

Mathematician and
Software Engineer

Researcher, Engineer, Tinkerer, Scholar, Philosopher, and Hyrox afficionado