UG_Special Topics_Formula Syntax and Expressions - GoldenCheetah/GoldenCheetah GitHub Wiki

CONTENTS

The Basics

Working with Vectors

Working with Dates

Working with Strings

Working with Sports Data

Working with Models and Model Fitting

Alphabetical Function Reference

Activity Data Reference

Alphabetical List of Metrics

RELATED TOPICS

Creating User Metrics

Working with User Charts

Introduction

Around version 3.3 in 2015 the old filtering code was extended to allow full blown programs and analyses to be written. Largely focused on preparing data to plot, the expressions became more sophisticated over time. Now with version 3.6 development, the expressions have been extended to support a new user chart with syntax and functions heavily borrowed from the R Programming Language.

These expressions are still intended to be a gentle introduction into more sophisticated analytics, they allow you to wrangle data, work with vectors, work with sports models, and data managed in GC. For richer analytics, perhaps involving machine learning, matrices and statistics we would recommend using either the R Chart or the Python Chart.

Where can these expressions be used ?

Expressions can be used in a number of places, from just searching and filtering through to full blow user defined charts and metrics:

  • Expressions can be used in the search/filter box at the top right of the screen using very simple logical expressions e.g. Average_Power > 200 will filter activities with an average power of the entire activity that is over 200 watts.

  • Many charts also have a filter box that can be used to limit what data is shown on the chart. For example a trends chart might only show run type activities with isRun.

  • Working with the trends chart and performance (ride) chart you can create a formula to prepare data to plot; on trends the formula is applied for every activity, on the performance chart it is applied once for every data point (sample) in the ride.

  • User Metrics are written using expressions, a small program consisting of a number of functions that control when a metric is computed and a small piece of code to calculate the metric for an interval or activity.

  • KPIs can be calculated to measure progress to a goal and displayed on an overview dashboard. The program to calculate the KPI is a datafilter. This is useful for tracking training load, performance changes as well as weight.

  • Data can be prepared to display as a data table on an overview dashboard. The program to prepare and annotate the data is a set of data filter functions. This is useful for collecting tabular data.

  • User Charts are comprised of curves which each have a small program which again has a number of functions that return different aspects such as the x-values, y-values.

Why should I learn about this ?

In some respects you don't need to. Our users are developing charts all the time and uploading them to the cloud DB so you can download and use them. There are a number of builtin charts that can be configured without the need to write code.

But if you are interested in exploring your data and generating new insights, or metrics, then you will find these expressions very useful. Additionally, they are very close to the R programming language, so time spent to understand and learn this will help as a stepping stone to a more mature and sophisticated programming language.

The Basics

You can mix a functional style of programming where functions are the only building blocks alongside imperative programming styles where logical conditions control the execution of code blocks. As always, its easier to show by example:

Imperative Style

# an imperative style with logic and code blocks
if (a = 10) {
   # code goes here
} else {
   # other code goes here
}

Functional Style

# inside-out cascade of functions to compute work from meanmax data
work <- sapply(head(meanmax(POWER),3600),(x*i)/1000)[seq(0,3600,30)]

Don't worry if at this point you don't fully follow these examples, they are to highlight the fundamental compromise we have made (following R): both styles are valid. We want the expression language to be useful and also usable.

As a result it is really comprised of three things;

  1. Imperative style constructs: loops, conditions, code blocks
  2. Functional style constructs: built-in functions, user-defined functions and lambda functions
  3. Built-in metrics and models: access to pre-computed metrics and mmp alongside banister, pmc, CP models

In this tutorial we are going to work through the language in that order.

Conditional and Logical expressions

You can control execution of code based upon a conditional expression. A conditional expression evaluates to zero or non-zero, where zero indicates False and non-zero indicates True. Some examples;

x > 5

This expression evaluates to True if the variable x has any value greater than 5. There are a wide range of conditional operators you can use:

  • < and <= less than and less than or equal to
  • > and >= greater than and greater than or equal to
  • = equal to (not this is not the assignment operator)
  • <> not equal to (we do not support != as an operator)

As well as operators used to compare numbers there are a number of other operators for comparing strings:

  • matches performs a regular expression match on strings
  • endsWith if a string ends with another string
  • beginsWith if a string begins with another string
  • contains if a string contains another string

It is also possible to combine conditions using logical operators && and || for logical AND and OR. Here are some examples:

if (x > 5 && x < 10) {
    # do this if x is between 5 and 10
}

if (x > 5 || y > 10) {
   # do this if either x is greater than 5 or y is greater than 10
}

In some instances you may want to combine logical operators in which case expressions can be enclosed by ( and ) to indicate grouping and order of expression evaluation, e.g.:

if ((x > 5 && x <10) || y > 0) {
   # do this if x between 5 and 10 -or- if y is greater than zero
}

Unary and Binary Operators

The usual operators are available for multiplication, subtraction etc, these are vectorized and will work element wise when operating on a vector, or can be used when working on a single value. Operators are:

  • * multiplication eg. 2*3 (is 6)
  • / division eg. 10/5 (is 2)
  • + addition eg. 3+4 (is 7)
  • - subtraction eg. 19-16 (is 3)
  • ^ raise to the power eg. 2^2 (is 4)
  • ! not, eg. !0 (is 1) and !1 (is 0), also see bool()

Conditionals and Looping

We limited the conditional constructs to just while, if-then-else and declaring a function. In the future we might add for loops, but for now we believe most use-cases are covered.

while

The while statement will execute a block of code while a condition is evaluated to true. For example:

while (xx < 5) {
    # do something, but only whilst xx is less than 5
}

Obviously the block of code will need to modify the variable xx otherwise the loop will run forever and the code will never complete (actually we limit while loops to 1 million iterations as a fail-safe).

if-else

As you have seen already if clauses support if and also else and else if blocks. These can be combined to select code-blocks to execute according to any condition:

if (xx >= 5) {
    # do this when xx >= 5
} else if (xx<0) {
   # do this when xx < 0
} else {
   # only get here if xx>0 and xx< 5
}

Code Blocks

A code blocks is a sequence of expressions ending in semicolons and enclosed in curly brackets, which are evaluated in order producing side effects (i.e. changing values of variables), and generating a value corresponding to the last expression in the sequence. For example the following block of code:

{ x<-x+2; x^2; }

increments the variable x by 2 (variables are automatically initialized to zero on first use) and evaluates to the new value of x squared.

User functions

User defined functions can be declared and called, to declare a function:

fname {
   a <- samples(CADENCE);
   b <- mean(a);
   b; # this is the value the function returns, the last expression in the block
}

And to call the new function, assigning average cadence to c:

c <- fname(); # c now contains average cadence

Note the result of a code block is always the numeric value of the last expression evaluated (like lisp).

A function can only be declared at global scope, and cannot be declared within any other code block. it is possible to encapsulate functionality as a lambda function (an unnamed function), see sapply below.

Tertiary operator ?, : and Elvis

Often we want to assign a value based upon a binary condition, the ? operator can be used as a shorthand. It evaluates a conditional expression to decide which of two possible expressions to evaluate next. As ever, an example helps:

# set val to xx if xx is greater than 5, otherwise set to 0
val <- xx > 5 ? xx : 0;

There is a shorthand version of the above, the so-called Elvis operator. It is a shorthand of the tertiary operator above. For example it is very common to do the following:

# set val to x if it is non-zero otherwise set to zz
val <- xx ? xx : zz;

The ?: elvis operator allows us to do this with a shorthand. Every place where you would use A ? A : B you can use A ?: B, so the example above can be re-written as:

val <- xx ?: zz

Variables and Assignment

No doubt you will have noticed that to assign a value to a variable we use the <- operator. This is borrowed from R, where the = operator is used for other purposes (named function parameters). We have retained it for readability to those familiar with R.

You do not need to declare variables before you assign a value to them, the simple act of performing an assignment will ensure the variable enters scope. But you must do this before you attempt to use a variable in an expression.

This is correct:

xx <- 1;
if (xx > 0) {
  # do something
}

This is incorrect, xx is not known when checking if greater that 0:

if (xx > 0) {
   # do something, but the line above is a syntax error
}
xx <- 1;

Scripting with Python

From version 3.5 onwards it is possible to embed code using Python. This is done by inserting code between %%python and %% .

A couple of things to note:

  • the code is passed to the interpreter unchanged, so for Python scripts you should take care with indentation since the python interpreter is sensitive to this.
  • the code block is evaluated as an expression, so can be included into one.

The script itself will need to call GC.result(x) to return the value computed. At present only numeric values are supported.

Here is a simple example for Average Power in Python to demonstrate:

%%python
# calculate average power
import numpy as np
data = np.asarray(GC.series(GC.SERIES_WATTS))
GC.result(data.mean())
%%

Data types

There are four basic data types;

  • a string such as "this is a string"
  • a numeric value such as 55
  • a date (expressed as a string "yyyy/mm/dd") such as "2020/04/25"
  • a vector returned by built-in functions or created with the c() function

Dates are stored as number of days since 1900/01/01 allowing for lots of possible date arithmetic (see working with dates below).

Vectors and numeric values are the main use of variables in code and can be used interchangeably; wherever a number can be used a vector can be used; however note that when performing a comparative operation with a vector the sum of the vectors elements are used.

Working with vectors

A vector is a fancy name for a 1 dimensional array, a list of numbers or strings. It originates from mathematics, which defines a vector as an ordered set of numbers.

In GoldenCheetah we use vectors mostly to work with time-series data and metadata. So for example we might work with a vector that contains the heartrate readings for an entire ride, or the values for CTL for an entire season or breakdown of workouts by their sport.

NOTE: Some functions will return a vector of dates. In this case the dates are returned as numerical values calculated as the number of days since 1900/01/01. When working with dates you can express them using a string such as "1815/06/15" which will evaluate as -30881. The Working with Dates section below describes how you can manipulate dates and perform simple arithmetic with them.

Creating a vector

The c() function can be used to create a vector, it takes 1 or more parameters, which are numbers or names of other vectors and creates a vector containing them all.

a<-c(1,2,3,4,5);

Creates a vector of 5 entries and assigns it to a. a would now contain:

[1,2,3,4,5]

NOTE: If you create a vector which contains a string, all values will be coerced to string, e.g:

a <-c(1,2,3,4,"tiger",6);

Creates a vector of 6 strings, a would contain:

[ "1", "2", "3", "4", "tiger", "6"]

Vector arithmetic

All operations are vectorized, this means that you can add, divide, perform trig functions with them. All elements in a vector are processed together. if one vector is smaller than the other (or is just a single value) then it is repeated to match the other. Here are some examples:

a<-c(1,2,3,4,5);
b<-a+2;
# b now contains [3,4,5,6,7]
d<-a+c(1,2);
# d now contains [2,4,4,6,6]
b<-10/a;
# b now contains [10,5,3.333,2.25,2]

Most mathematical operators don't apply to strings and so the result is undefined, but it is possible to concatenate strings using the + operator:

a<- "hello" + ", world!";
# a now contains "hello, world!"

Creating a sequence or repeated set of numbers

The seq() function can be used to create a sequence of numbers, and takes three arguments; start, stop, step. Here are some examples:

a <- seq (0, 10, 2);
# a now contains [0,2,4,6,8,10]

a <- seq(10, 0, -2);
# a now contains [10,8,6,4,2,0]

The rep() function can be used to create a repeated set of numbers and takes two arguments; value, count. Here is an example:

a<-rep(1,10);
# a now contains [1,1,1,1,1,1,1,1,1,1]

Indexing into a vector

You can use the [ and ] operators to index into a vector, where the argument in the brackets lists the members of the vector by position that you want. Here are some examples:

a <- c(1,2,3,4,5);
b <- a[0];
# b will now contain the value 1, since indexes start from 0

d <- a[c(1,2,3)];
# d will now contain the values [2,3,4]

e <- c(3,4);
f <- a[e];
# f will now contain the values [4,5]

g <- seq(1,10,1);
# g contains [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
h <- c(22, 23);
index <- c(4,5,6);
g[index] <- h;
# g now contains [ 1, 2, 3, 4, 22, 23, 22, 8, 9, 10 ]
# remember indexes start at 0, vector assignment will repeat

Selecting and filtering a vector

It is also possible to use the [ and ] operators with a logical expression that returns true or false, in order to select entries from a vector. Within the logical expression the values x and i are available and contain the element being evaluated and their position in the vector. Here are some examples:

a<-c(1,2,3,4,5,6,7,8,9,10);
b<-a[x<5];
# b now contains [1,2,3,4]

c<-a[i>4]
# c now contains [6,7,8,9,10] remember index starts at 0

Nozero using filtering

A really common need is to filter out zero values, you can already do this (or more complex filtering) using the selection filtering described above. For example:

# get metric and date vectors
dates <- metric(date);
values <- metric(BikeStress);

# use x and i to reference across vectors 
# so we can remove zero values
dates <- dates[values[i] > 0];
values <- values[x>0]

Filtering out zeroes with the nonzero()

Although it's relatively simple to filter out zero values using sapply there is also a convenience function called nonzero(v1) that will return an index of all non-zero values in v1. e.g.

a <- c(1,2,3,0,5,6,0,8,9,10);
nz <- nonzero(a)
# nz contains [ 0, 1, 2, 4, 5, 7, 8, 9 ] remember indexes start at 0

Matching vectors with match()

To check if elements in one vector are present in another we can use match(vector1, vector2). This returns an index of values; for each value in vector1 it will return the index into vector2 if there. if the element in vector1 is not found in vector2 no entry will be added to the returned index.

a <- c(4,6,9);
b <- c(1,2,3,4,5,6,7,8,9);
d <- match(a,b);
# d now contains 3,5,8 since indexes start from 0

Chopping vectors with mid(), head() and tail()

To get a section of a vector you can use mid(v, pos, count), head(v, count) and tail(v, count), where v is a vector. Of course these could easily be replaced with a selection above, but for more readable code, and ultimately convenience, these functions help you get sections of a vector. Here are some examples:

a<-c(1,2,3,4,5,6,7,8,9,10);
b<-head(a,5);
# b now contains [1,2,3,4,5]

d<-tail(a,5);
# d now contains [6,7,8,9,10]

e<-mid(a,2,3);
# e now contains [3,4,5]

Simple vectorised functions

There are a few functions that will be useful when working with vectors and they do as you would expect; these include sum(v), mean(v), mode(v), median(v), max(v), min(v), count(v) and length(v) which respectively do pretty much what you would expect. Here are some examples:

a<-c(1,2,3,4,5);
b<-length(a);
# b now contains 5
d<-sum(a);
# d now contains 15
e<-max(a);
# e now contains 5
f<-min(a);
# f now contains 1

NOTE: For all of these functions except length() you can pass multiple vectors or numbers and it will operate on them all. e.g.

a<-c(1,2,3,4,5)
b<-sum(a,6,c(7,8,9));
# b now contains 45

The length() function will return 0 if the variable is not a vector of values. e.g.

a<-10
b<-length(a);
# b contains 0
a<-c(10)
b<-length(a)
# b contains 1

Append and Remove elements in-situ

By now you will no doubt have noticed that these operations generally return copies of the data. From a functional programming perspective this is very useful, but from a performance perspective it can be rather expensive. Especially if we are performing operations over and over.

For this reason, we have implemented append(v, expr [, pos]) and remove(v, pos, count) functions. In both cases v must reference a variable, since we are applying directly rather than working on copies.

Here are some examples:

a<-c(1,2,3,4,5)
append(a, c(6,7));
# a now contains [1,2,3,4,5,6,7]
append(a, 3.5, 3);
# a now contains [1,2,3,3.5,4,5,6,7]
remove(a, 3, 1);
# a now contains [1,2,3,4,5,6,7]
remove(a, 3, 3);
# a now contains [1,2,3,7]

Iterating using sapply

The sapply() function allows us to iterate over a vector applying an expression as we go, the results of which are returned as a vector. The expression can refer to the variables x and i for the element value and position respectively. Additionally, the expression can refer to other variables. E.g:

a<- c(1,2,3,4,5);
b<- c(10,20,30,40,50);
d<- sapply(a, { b[i] / x; });
# d contains [10, 10, 10, 10, 10]
e <- sapply(a, { x<3 ? i : -1; });
# e contains [ 0, 1, -1, -1, -1 ]

Sorting using multisort and argsort

You can sort a vector using the multisort() function, it will be sorted in situ. multisort does NOT return a vector of sorted values, it sorts the items in the vector supplied, returning a count of items sorted (to just get a sorted vector use plain sort).

a<-c(3,6,2,6,1,99,-2);
b<-sort(ascend, a);
# a contains [-2, 1, 2, 3, 6, 6, 99]
# b is 7

But when we are working with x,y data we need to sort the x and y values together, so multisort() will also accept a list of vectors to sort. Each entry will be resorted in the same way as the first list. E.g.

a<-c(5,4,3,2,1);
b<-c(9,8,6,7,2);
multisort(ascend, a, b);
# a is now [1,2,3,4,5]
# b is now [2,7,6,8,9]

There is no limit to the number of vectors you can pass to sort, so that helps if we want to work with lots of aligned vectors for say returning x,y,z,d and t.

One other use case might be for getting the top n values, and sorting the x,y pair for those together. For this we also have argsort(). It will return an order index into the vector, with each entry being a reference into the original vector. Lets look at an example;

a<-c(5,7,2,-2,100,0);
b<-argsort(ascend, a);
# b now contains [3,5,2,0,1,4] 

So position 0 in b refers to position 3 in a, position 1 in b refers to position 5 in a and so on. We could dereference using this index to get a sorted list:

d<-a[b]; # remember we can index into a vector with another vector
# d now contains [-2,0,2,5,7,100]

So, if we wanted to be efficient, we could use this to get the top n values from two aligned vectors:

a <- c(9, -2, 100, 3, 6);
b <- c(1,  2,   3, 4, 5);
top2 <- head(argsort(ascend, a),2); # only want the 2 smallest values
e <- a[top2];
f <- b[top2];
# e contains [-2, 3]
# f contains [ 2, 4]

We could obviously achieve the same with multisort and multiple lists (and internally the multisort code uses an argsort to do this anyway), but when working with large numbers of data points this can be more efficient and we may also want to do other things besides working out the top n.

SIDE NOTE: From a purist, functional programming perspective, that multisort updates the vector passed rather than returning a vector is considered very bad practice (the same can also be said for append and remove above). All vectors should be immutable. If you are an 'fp purist' feel free to combine argsort and dereferencing to sort multiple lists in a pure manner, the rest of us will cheat and write code that confuses others. Lastly, the argsort function is modelled on the Python numpy function with the same name, there are a lot of tutorials on-line explaining how it might be used, if you are struggling to follow they may be of use.

Binary search with lowerbound

Occasionally you will want to find a value in a vector. The lowerbound(list, lower) function will search the list (it MUST be sorted ascending) for the first value that is not less than (i.e. greater or equal to) lower. Under the covers this uses a binary search so is very fast.

One use-case for this might be looking at PD data to find how long you can last at a particular wattage:

## time to exhaustion at 300w, where model prediction is in yy
## and time is in secs. we copy and sort then use lowerbound
## to find TTE point at 300 watts
lbyy <-yy; lbsecs<-secs; multisort(ascend, lbyy,lbsecs);
annotate(label, "300W TTE=", lbsecs[lowerbound(lbyy,300)]);

ASIDE: lowerbound is modelled after the standard C function std::lower_bound. Internally it uses this function to perform the search.

De-duplicating with multiuniq and arguniq

In a similar vein to the multisort and argsort functions above, arguniq will return an index into the unique entries for a vector, it does not require the list to be sorted and is stable; the returned index will always reference the first element by position when there are duplicates. multiuniq will de-duplicate a vector by removing duplicates, you can synchronise lists by passing more than one as parameters, in which case the same element by index will be removed in accordance with the first list.

Here are some examples:

a<-c(1,3,5,3,6,7,4,5,7);
b<-arguniq(a);
# b now contains [0,1,2,4,5,6]
multiuniq(a);
# a now contains [1,3,5,6,7,4]
a<-c(3,4,3,6,7,6,2);
b<-c(1,2,3,4,5,6,7);
multiuniq(a,b);
# a now contains [3,4,6,7,2]
# b now contains [1,2,4,5,7]

ASIDE: When fitting models to full MMP data it is highly recommended to run multiuniq() on the input. There are large numbers of duplicated y values at longer durations that can be removed to avoid bias at longer durations and also to improve performance when fitting the model. This is less of a problem if you choose to truncate to the first 20 minutes of data. For example:

mmp <- meanmax(POWER);
secs <- seq(1,length(mmp),1);
multiuniq(mmp,secs); # remove duplicate y values

Smoothing noisy data

The smooth() function will return a vector of data with a smoothing algorithm applied. At present there are two types:

  • sma is the simple moving average algorithm and takes two parameters, the first defines the windowing policy: forward, backward or centered, and the second is the window size. We recommend always using an odd number for the window size when centered.

  • ewma is the exponentially weighted moving average algorithm and takes a single pararameter alpha which is the weighting to use, and must be between 0 and 1. The default is 0.3 if the value supplied is out of bounds.

Some examples:

power <- smooth(samples(POWER), sma, centered, 15);
# power contains sample data smoothed
bikestress <- smooth(metrics(BikeStress), ewma, 0.01);
# bikestress contains a smoothed trend of bikestress for the date range

Trend lines can also be created using the lr() function, which is described below (or by just selecting the area on a chart, since a linear trend line is always added for the selection).

Interpolating data points

You can interpolate points between data points to create a smoother curve, several algorithms exist to do this, from cubic, akima and steffen or just plain linear. The interpolate() function is available to do this.

For example, to produce a smooth curve between points, you could:

xx <- samples(SECS);
yy <- samples(POWER);
    
first <- head(xx,1);
last <- tail(xx,1);

# use 10ths of a second resolution
zxx <- seq(first, last, 0.1);
zyy <- interpolate(cubic, xx, yy, zxx);

Bear in mind that the curve produced will pass through all the points passed, so you will need to work at a higher resolution to see the interpolation results.

Also bear in mind that this does not smooth the data, so if it is particularly noisy the cubic method may fail to find a solution.

Resampling data points

You may also want to resample data, where the recording interval the data us recorded in needs to be adapted to a recording rate you can use in a calculation. A classic example would be taking data from an original powertap where the recording rate was set at 1.26s. This is far easier to work with if resampled to 1s intervals.

Crucially when we resample data we do not want to lose precision- which we will do if we use interpolation. But also crucially we need to make sure the data we resample does not contain gaps in recording; so we would typically interpolate data before we resample. Here is an example, resampling from above:

# fetch power and time
xx <- samples(SECS); yy <- samples(POWER);
first <- head(xx,1); last <- tail(xx,1);

# lets interpolate keeping same sample rate
zxx <- seq(first, last, RECINTSECS);
zyy <- interpolate(linear, xx, yy, zxx);

# now lets resample to 1s intervals, if they are
# the same, the data is returned unchanged, so no 
# harm can be done...
zyy <- resample(RECINTSECS, 1, zyy);

Working with dates and times

Date arithmetic

All dates are stored as numbers, where the value is days since 1900/01/01. These values can be negative (and this is supported), but is generally not likely since most activity data tends to be recent and certainly since the advent of portable sensors and bike computers which is well after 1st January 1900.

This scheme has been adopted to make it easy to perform arithmetic, dates can be subtracted from and added to in order to calculate ranges and day counts. Additionally, you can represent a date using a string based format "yyyy/mm/dd".

It is perfectly valid to perform arithmetic on dates:

a<- "2020/01/01";
# a now contains 43830
b<- a-365;
# b now contains 43465

Today, Date, Time and Current

When working with an activity or perhaps when writing a user metric, its useful to know about the date of the activity or what today's date is. The two built-in variables Today and Date provide this. Today unsurprisingly gives today's date, whilst Date is the date and Time is the start time of the activity. So in activity view and when writing a user metric Date and Time are available to you. If you are on trends view then you will instead be working with a date range (see below). As an edge case you may occasionally want to know the date of the currently selected activity, this is available in the Current built-in variable.

# when working with user metrics, get last 90 days values
bs <- metrics(BikeStress, Date-90);

Season daterange

The function daterange(start|stop) will return the currently selected date range. When calling functions like measures or metrics the currently selected date range is automatically honored. But if you want to work differently or need to calculate the length of the current season then this function will help. e.g.

# calculate season duration in days
days <- daterange(stop) - daterange(start) + 1;

using daterange(...) as a closure

One other use for daterange() is to set a date range for a specific expression; daterange(start, stop, expression) where start and stop are dates.

For example, the measures function doesn't support providing a date range to use, instead it uses the currently selected date range. In this case we can override this:

# override the date range selection
weight <- daterange("2020/01/01", "2020/05/01", measures("Body", "WEIGHTKG"));

In a similar way, using the pmc function you can get TSB for today:

daterange(Today, Today, pmc(BikeStress, sb))

Days since, Weeks since, Months since

Dates are stored internally as days since 1900/01/01. We can use week(date) to convert a date from days since 1900/01/01 to weeks since 1900/01/01. This is useful for grouping by weeks. To convert back to a date we can use weekdate(week) to get the date for the start of that week.

Similarly month(date) will convert a date to the number of months since 1900/01/01 and monthdate(month) will revert back to a date for the first day of the month. Here are some examples;

a<-"2020/04/20";
# a now contains 43939, the number of days since 1900/01/01
b<-month(a);
# b now contains 1443, the number of months since 1900/01/01
d<-monthdate(b);
# d now contains 43920, which is "2020/04/01" as days since 1900/01/01

Converting date and time values to strings

datestring(date) and timestring(time) can do the conversion for a single number or a vector elementwise.

Working with strings

It is possible to create and manipulate strings and vectors of strings. String constants are delimited by "", if you need to include " they has to be escaped as \". There are a number of operators that only apply to strings, such as contains, matches, beginsWith and endsWith. Additionally there are some functions for manipulating strings.

Trimming, Joining and splitting strings

You can join a group of strings into a single string using the join function, providing and separator to use when joining, and in reverse you can split strings based upon a separator.

a<- c("the", "quick", "brown", "fox");
b<- join(a, " ");
# b now contains "the quick brown fox"
d<- split(b, " ");
# d now contains [ "the", "quick", "brown", "fox" ]

It may also be useful to remove unwanted whitespace when splitting, the trim function will remove spaces at the beginning and end of a string. For example;

a<- "   this is a string   ";
b<- trim(a);
# b now contains "this is a string"

Changing case and replacing values

You can convert a string to upper or lower case only using the tolower and toupper functions:

a<- "This is a Sentence";
b<- toupper(a);
# b now contains "THIS IS A SENTENCE"
d<- tolower(a);
# d now contains "this is a sentence"

Occasionally you may want to manipulate a string replacing one value with another:

a<- "this is a string"
b<- replace(a, "this", "that");
# b now contains "that is a string"

All built-in functions work with both strings and numbers, or vectors of strings and vectors of numbers. Where the function clearly applies only to numeric values (e.g. sum()) any string or string array will be converted to numbers (coerced), otherwise you can use double() for explicit conversion. Some functions only work with strings, in which case the numbers are coerced to strings, these include: tolower, toupper, replace, join, split, trim, replace; otherwise you can use string() for explicit conversion.

Working with sports data and models

Accessing metadata

The metadata() function will return the string value for a ride or a list of metadata values for a season or date range. E.g:

wcode <- metadata("Workout Code");
# will return a single string on analysis view, or vector of values on trends view

Just using the metadata field name, with spaces replaced by underscores (like Workout_Code), allows to access the field for the current activity, provided the field is defined in Preferences > Data Fields with a non blank Screen Tab. Some technical fields, s.t. Start Date and Start Time, are not available this way and can be accessed via builtin functions s.t. Date and Time instead.

Accessing Activity samples

The samples() function will return a vector of the channel or data series from all of the activity data points (except when computing metrics when it will be for the activity or the interval being worked with). It has a single parameter, the data series name. There are a wide range of data series that can be present in each sample, these are:

General Data Series

  • INDEX - positional index of data point in ride
  • SECS - time of data point in ride
  • CADENCE - in rpm
  • HEARTRATE - in bpm
  • DISTANCE - in km
  • SPEED - in kph
  • TORQUE - in Nm
  • POWER - in watts
  • ALTITUDE - in meters
  • LON - gps longitude
  • LAT - gps latitidue
  • HEADWIND - in meters per second
  • SLOPE - in degrees
  • TEMPERATURE - in centigrade
  • BALANCE - as a percentage
  • WBAL - in Joules only supported in samples()
  • WBALSECS - seconds, only supported in samples()

Garmin specific series

  • LEFTEFFECTIVENESS RIGHTEFFECTIVENESS LEFTSMOOTHNESS RIGHTSMOOTHNESS SMO2 THB
  • RUNVERT RUNCADENCE RUNCONTACT LEFTPCO RIGHTPCO LEFTPPB RIGHTPPB LEFTPPE
  • RIGHTPPE LEFTPPPB RIGHTPPPB LEFTPPPE RIGHTPPPE

As well as the samples() function these symbols will also resolve to the current data point value when iterating using the sample function in the series program. In the example below, the xx and exex vector will be identical.

{
    init { xx <- c(); }

    sample {
       append(xx, TEMPERATURE);
    }

    finalise {
       exex <- samples(TEMPERATURE);
    }
}

NOTE: The samples() function will only return samples for the currently selected activity (or more specifically, only for activities that have been opened). This is to avoid accidentally opening every activity if it is used when processing a date range on the trends view. One exception to this is during metric calculation where the activity is explicitly opened to compute, but also in this case when interval metrics are being computed samples() will only return values in the interval.

W'bal

The W'bal and W'bal time are only available in the samples function (not available in sample) because the calculation of W'bal accounts for gaps in recording and resamples to 1s samples. So, to plot W'bal you can only use the samples function (i.e. you cannot access W'bal within the sample function). Use WBAL and WBALSECS to access W'bal and W'bal secs respectively.

Here is a user series in a user chart:

{
    x { samples(WBALSECS); }
    y { samples(WBAL)/1000; }
}

Accessing Activity metrics

The metrics() function will return a vector containing the metric value for every activity in the currently selected date range, or you can also (optionally) pass a start and a stop date range.

When working with your own date range you will use the form: metrics(BikeStress, "2020/07/01", "2020/07/31"). This will return a vector of BikeStress for July 2020. Both the start and stop dates are optional, if only one date is provided it will return a vector from that day through to the current date.

There are over 300 metrics in version 3.5 of GoldenCheetah, so rather than list them all here, we will just point out that there is an autocompleter in the program editor. Some of the more popular metrics include;

  • BikeScore - Skiba's stress metric for cycling
  • BikeStress - Coggan's training stress score metric for cycling
  • Relative_Intensity - Skiba's intensity metric for cycling
  • BikeIntensity - Coggan's intensity factor for cycling
  • xPower - Skiba's normalised power for cycling
  • IsoPower - Coggan's normalised power for cycling
  • TriScore - All sport training score for triathlon
  • TRIMP - Banister stress score using HR

As well as the metrics() function these symbols will also resolve to the current data point value when iterating using the activity function in the series program. In the example below, the xx and exex vector will be identical (like in the samples example above).

{
    init { xx <- c(); }

    activity {
       append(xx, BikeScore);
    }

    finalise {
       exex <- samples(BikeScore);
    }
}

Aggregating Metrics

When working with metrics we have to take care that we aggregate correctly, depending on the type; peak metrics we keep the maximum, averages need to be aggregated taking into account the duration of the activities being combined and so on.

To make life easer there is a helper function aggmetrics which has the exact same parameters and usage as the metrics function, but returns a single aggregated value (applying the correct aggregation method for the metric in question). Note there is also an asaggstring function to complement the asstring function for working with metric formatting.

Aggregating, Averages and Totals

If you choose to aggregate values yourself rather than use the helper functions or perhaps you are not working with built-in metrics you will need to take care that you recognise how to aggregate their values. For example BikeStress is cumulative (it is accumulated over the course of a ride), so it makes sense to aggregate over a season through summation; equally the average BikeStress per ride might be interesting, and does not need to take into account the duration of the ride so can be calculated as a mean.

On the other hand, a metric like Average Power should be aggregated taking into account the length of each ride. For example, a ride of 1 hour at an average power of 200 watts, followed by a ride of 5 minutes with an average power of 400 watts should not be calculated as a mean value of 300 watts. Instead, the duration needs to be taken into account (in this case the Average Power is ((200 * 60) + (300 * 5)) / 65, which is 207.7).

Here is an example, calculating an season average for Average Power and a total for BikeStress over the course of a season:

finalise {
    # everage BikeStress for date range
    bs <- metrics(BikeStress);
    ap <- metrics(Average_Power);
    time <- metrics(Duration);
    avg <- sum(ap * time) / sum(time);
    annotate(label, "Power", round(avg));
    annotate(label, "BikeStress", round(sum(bs)));
}

Aggregating

The aggregate(v, by, sum|mean|max|min|count) function will return an aggregated vector for v grouped by values in vector by, the values in each group will be aggregated by applying the function supplied as the third parameter (sum, mean, max, min or count). It is not possible to supply your own aggregating function at this point (this may be added later). Vector by needs to be sorted, if that is not the case multisort function is handy to sort it preserving vectors alignment.

Here is an example:

by<-c(1,1,1,2,2,3,4); 
vals<-c(1,2,3,4,5,6,7); 
agg<-aggregate(vals,by,mean);
# agg now contains 2, 4.5, 6, 7

Here is a user chart program for a series that plots the mean monthly PeakPowerIndex:

{

    finalise {

        # all dates as month numbers
        by <- month(metrics(date));

        # get daily metrics and aggregate to monthly average
        yy <- aggregate(metrics(PeakPowerIndex)*2.61, by, mean);

        # aggregate dates to 1 per month (max/min return the same thing)
        xx <- monthdate(aggregate(by,by,max));

    }

    x { xx; }
    y { yy; }
}

Cumulative Summation

The cumsum(vector) function will return a cumulative sum for the vector. This is useful to keep running totals for series. This is particularly useful for watching how stress, load, work or distance type metrics are building over time.

Here is an example of accumulating BikeStress:

bs <- metrics(BikeStress);
# bs contains a vector of daily bikestress scores for the season
agg <- cumsum(bs);
# agg now contains a running total of bikestress accumulated for the season

Working with Mean-maximal data and efforts

The meanmax() function will return a vector of mean maximal data for every second, from 1s to the longest available. On the trends view it will return bests for the date range selected and on the analysis view it will return for the currently selected activity.

The returned vector starts at 1s, not 0s, so the very first element in the vector is the peak 1s value.

It accepts one parameter, naming the series you want to retrieve:

  • CADENCE - in rpm
  • HEARTRATE - in bpm
  • SPEED - in kph
  • TORQUE - in nm
  • POWER - in watts
  • WPK - in w/kg
  • APOWER - in watts
  • ISOPOWER - in watts

Additionally, if you are on the trends view

  • efforts - indexes into the POWER vector for the peak efforts, using the same approach for filtering that is currently used on the CP chart; a mixture of filtering averaging tails by date, removing submax from a linear regression and identifying true peak efforts using the PowerIndex for each point.

Here are some examples:

mmp <- meanmax(POWER);
# mmp now contains the peaks from 1s to longest
secs <- seq(1, length(mmp), 1);
# secs now contains the x value for plotting a mmp curve
efforts <- meanmax(efforts);
peakmmp <- mmp[efforts];
peaksecs <- secs[efforts];
# peakmmp and peaksecs now contain the x,y values for plotting peak efforts

Generating a mean maximal curve from other data

You can call the meanmax() function two vectors; time and value. This data will be used to generate a mean-maximal curve. The supplied data should be in 1s increments and the y values should not be negative. The data will be corrected if this is not the case; negative y values will be truncated to 0 and the data will linearly interpolated to guarantee 1s intervals.

Since the mean-max algorithm has been optimized for integer arithmetic, the y-values will only be calculated to 3 decimal places of precision. Here is an example activity chart, generating MMP data using this approach:

{
    finalise {
        xx <- samples(SECS);
        yy <- samples(POWER);
        yy <- meanmax(xx,yy);
        xx <- seq(1,length(yy),1);
    }

    x { xx; }
    y { yy; }
}

Working with peak values for a specific duration

Whilst the meanmax() function above will return a mean-maximal curve, we also want to look at specific durations and plot or track how the peak values are changing over time. To do this we use the best() and bests() functions.

The best() function returns a single value for the current ride, so useful in a user metric or when we are iterating in the activity() function on a user chart. We can get the peak 5min power for the current ride with best(POWER, 300).

Alternatively the bests() function returns a vector of values for the current date range, so is useful when we want to retrieve data to plot trends, or want to manipulate the data in some way. We can also (optionally) pass a date range to control the values returned independently of the currently selected date range.

Here is an example from a user chart that plots peak 5 minute power for 2020;

{

    finalise {
        # if we only wanted the current date range
        # xx <- bests(date);
        # yy <- bests(POWER, 300);

        # but we insist on working explicitly with data from 2020 only
        xx <- bests(date, "2020/01/01", "2020/12/31");
        yy <- bests(POWER, 300, "2020/01/01", "2020/12/31");
        z <- nonzero(yy);
    }

    x { xx[z]; }
    y { yy[z]; }
}

Working with performance tests

Users can mark intervals as performance tests (time to exhaustion tests) and at startup peak best efforts are identified in general data. Both of these are useful for PD model fitting. To access both the tests() function can be used.

Called with no parameters the tests() function will return the number of performance tests available in the currennt date range or activity.

Additionally, you can retrieve user marked tests, or system identified bests by passing as a parameter, additionally you need to specify if you want a vector of durations or power values for the tests/bests identified.

e.g:

# get the x,y pairs for all user defined test intervals
testx <- tests(user, duration);
testy <- tests(user, power);

# get the x,y pairs for best efforts
bestsx <- tests(bests, duration);
bestsy <- tests(bests, power);

NOTE: The bests are identified for each week, so during a season the bests will improve, it is recommended that you limit the date range you use when working with bests to avoid using data that does not represent current performance. e.g:

# get peak power duration for the last 21 days
bestsx <- daterange(Today-21, Today, tests(bests, duration));
bestsy <- daterange(Today-21, Today, tests(bests, power));

Working with distributions

Access to precomputed distributions of the main data series is made possible via the dist() function. It accepts two parameters dist(series, data|bins). series is one of the usual data series; HEARTRATE, POWER etc, whilst data will return the duration spent in a particular bin, and bins will use the actual bins. You could then plot a distribution like this, in user chart:

{
finalise {
    # basic distribution
    yy <- dist(POWER, data);
    xx <- dist(POWER, bins);

    # time spent at zero value
    yy[0] <- 0;

    # remove all empty bins
    nz <- nonzero(yy);
}

x { xx[nz]; }
y { smooth(yy[nz], ewma, 0.1); }

}

Accessing HRV and Body Measurements

Access to body measurements and HRV data is performed via the measure and measures functions.

  • measure("group",date, "field") access a measurement on the date of a specific activity
  • measures("group", "field" | "date") get a vector of measures for the current date selection

Typically the measure function is called in a user metric to get the value for the date of an activity, or in the activity function of a user chart series program. It just retrieves the value for the date of an activity.

In contrast, the measures function can be called to get measurements independent of any activities. This is useful to plot measurements alongside other data.

The group field will be one of "Hrv" or "Body" to access HRV or Body measurements, and the field value will include "RMSSD" for HRV measurements and "WEIGHTKG" for Body measurements respectively.

As new groups and fields are likely to be added over time you can use the autocompleter in the editor to get a list of the current values that are supported.

Here is an example user series program to plot weight over time:

{
    x { measures("Body", "date"); }
    y { measures("Body", "WEIGHTKG"); }
}

Working with the PMC

You can generate Coggan PMC data series using the pmc() function. It takes two parameters, the first is an expression to evaluate as the impulse metric, the second is the PMC curve you want returned.

Classically, the metric to use for the PMC is BikeStress (our name for the proprietary TSS metric). But you can use any metric. You could even calculate something by using an expression.

The curves supported are: stress - the daily impulse lts - long term stress (aka CTL) sts - short term stress (aka ATL) sb - stress balance (aka TSB) rr - ramp rate date - date of the data point.

Result is a vector with daily values of the requested curve for the selected date range in Trends sidebar.

Some examples:

ctl <- pmc(BikeStress, lts);
# classic pmc little blue line
tsb <- pmc((Work^2)/Relative_Intensity, sb);
# homebrew stress metric based on work and intensity

Working with the Banister model

You can generate Banister curves using any impulse metric and any performance metric. Traditionally the impulse metric might be TRIMP and the performance metric might be 8_min_Peak_Power but recently we have also added Power_Index as a performance metric, which works well. Banister fitting uses all athlete’s activities without filters.

The banister() function takes 3 parameters, the impulse metric, performance metric and curve that you want generated and returned, one of:

  • nte - negative training effect
  • pte - positive training effect
  • perf - performance curve (fit to performance)
  • cp - critical power estimate (esp. useful when working with Power_Index)
  • date - data of the data point.

Some examples:

yy <- smooth(banister(BikeStress, Power_Index, cp), sma, centered, 28);
# yy has a smoothed estimate of CP over time using Coggan's 
# stress score and fitting the performance curve to PowerIndex 

Model fitting

We support linear regression, multiple linear regression and least squares fitting of models. They provide a generalised method for working with any kind of model; from estimating TSS for rides without power, power duration modelling with Critical Power or fitting an IR model to performance tests.

Linear Regression with lr()

The lr(xdata, ydata) function performs a linear regression on the x and y data vectors passed. it will return a vector of 4 elements:

  • [0] - the slope of the fit
  • [1] - the intercept of the fit
  • [2] - the coefficient of determination (R2)
  • [3] - standard error of estimates (SEE)

Here is an example:

# get work
work <- sapply(meanmax(POWER), { x * (i+1); });
time <- seq(1, length(work), 1);
fit<- lr(time, work);
# fit[0] is now a rough estimate of Critical Power, fit[1] is an estimate of W'

Multiple linear regression with mlr()

The mlr(yvector, xvector1 .. xvectorn) function will perform a multiple linear regression and calculate the coefficients (beta) for each predictor (xvector1 thru xvectorn). The coefficients are returned as a vector. At present the chi-squared and covariance matrix is calculated internally and discarded, this may be fixed in future releases.

One use of mlr() when working with sports data is to use a combination of metrics to make predictions e.g. to estimate HR based TRIMP when HR data is not available. In this case the coefficients would be calculated from data where power and HR data is available and use to estimate TRIMP where power but no HR data is available.

e.g.

# get trimp and some predictors; work, duration and distance
trimp <- metrics(TRIMP);
constant <- rep(1, length(trimp)); # the constant term or intercept
work <- metrics(work);
duration <- metrics(Duration);
distance <- metrics(Distance);

# calculate coeff when trimp is available
coef <- mlr(trimp[x>0], constant[trimp[i]>0], work[trimp[i]>0], 
                        duration[trimp[i]>0], distance[trimp[i]>0]);

# now apply when trimp isn't available, remember the formula is of the form
# y = c1 + c2x1 + c3x2 + c4x3
ptrimp <- sapply(trimp, x > 0 ? x : coeff[0] + coeff[1]*work[i] + 
                                    coeff[2]*duration[i] + coeff[3] * distance[i]);

# ptrimp now contains a mix of metrics computed from HR and metrics predicted using coefficients

Another useful application of mlr() is to determine the coefficients for a quadratric trend line for some data points on a chart. The quadratic equation we all learned at school: y = a + bx + cx^2 can be solved quickly and easily using mlr() since a, b and c are the coefficients from a fit of 1, x and x*x.

Here is a trend curve calculated on a user chart:

{
    finalise {
        # Prepare the data
        xx <- metrics(date);
        yy <- metrics(AveragePowerAt145BPM);
        z<-nonzero(yy);
        xx <- xx[z];
        yy <- yy[z];
        one <- rep(1,length(yy));

        # calculate the coefficients from a multiple linear regression
        coeff <- mlr(yy,one,xx,xx*xx);

        # generate a prediction curve
        xx <- seq(daterange(start), daterange(stop), 1);
        yy <- sapply(xx, coeff[0] + coeff[1]*x + coeff[2] * (x*x));

    }

    x { xx; }
    y { yy; }
}

OLS Model Fitting with lm()

The lm(formula, xdata, ydata) function can be used to perform an ordinary least squares fit for any non-linear model using the Levenberg-Marqaurdt algorithm. It takes three parameters;

  • formula - an expression that calculates the y-value, within the expression x will be replaced with the x value. Unlike in R you can fore-go the "y~" part of the formula, it is just a normal expression.
  • x data - fitting data points x values
  • y data - fitting data points y values

The formula expression must reference existing variables and their values will be used as the starting values for the fit. Once the fit is completed they will contain estimates from the fit.

The lm() function returns a vector of 3 elements:

  • [0] - Success/Fail non-zero indicates success, zero indicates failure to converge.
  • [1] - root mean square of errors for the fit (RMSE)
  • [2] - coefficient of variation % (CV)

Example, fitting the Morton 3 parameter critical power model in a chart:

{
    finalise {
        # raw data to fit with, de-duped
        mmp <- meanmax(POWER);
        secs <- seq(1,length(mmp),1);
        multiuniq(mmp,secs);

        # set the fit starting values
        cp <- 200; W<-11000; pmax <- 1000;

        # LM fit the Morton 3p model to first 20 min data
        fit <- lm((W / (x - (W/(cp-pmax)))) + cp, secs[x<1200], mmp[secs[i]<1200]);

        # success, show user the parameter estimates, but to the right precision
        if (fit[0] <> 0) { 
            annotate(label, "CP", round(cp));
            annotate(label, "W'", round(W));
            annotate(label, "CV=", round(fit[2],2), "%");

            # generate prediction curve based on estimates
            yy <- sapply(secs, (W / (x - (W/(cp-pmax)))) + cp); 
        }
    }

    x { secs; }
    y { yy; }
}

ASIDE:

One last tip on working with model fitting; more recently with multi-domain power duration models it has become common to have cut-offs where a continuous function applies; e.g a different formulation might be used at durations over 30 minutes.

In this case you may want to include a logical expression into what is essentially a mathematical formula. To do this the function bool() allows you to embed a logical expression, it converts the result into 0 or 1.

For example the Omni-domain power duration model fit looks like this, note the use of the bool() function:

{

    finalise {
        # raw data to fit with, just first 2 hours maximum
        mmp <- head(meanmax(POWER),7200);
        secs <- seq(1,length(mmp),1);
        multiuniq(mmp,secs);

        # fit starting values
        cp <- 200; W<-11000; pmax <- 1000; a<-40;

        # Fit the ODPM model 
        fit <- lm(W/x*(1-exp(-x/(W/(pmax-cp)))) + cp - a*(log(x/1800))*(bool(x>1800)), 
                  secs, mmp);

        # success, generate prediction data
        if (fit[0] <> 0) { 
            annotate(label, "CP", round(cp));
            annotate(label, "W'", round(W));
            annotate(label, "CV=", round(fit[2],2), "%");
            yy <- sapply(secs, W/x*(1-exp(-x/(W/(pmax-cp)))) + cp - 
                               a*(log(x/1800))*(bool(x>1800))); 
        }

    }

    x { secs; }
    y { yy; }
}

Function Reference

Maths & Stats

acos acosh asin asinh atan atanh cdfbeta cdfnormal ceil cos cosh exp fabs floor interpolate isinf isnan kmeans lm log log10 lr max mean median min mlr mode normalize pdfbeta pdfnormal quantile random resample round sin sinh sqrt stddev sum tan tanh variance

Vectors

aggregate append argsort arguniq bin c count cumsum head length lowerbound nonzero match mid multisort multiuniq rank remove rep rev sapply seq sort tail uniq which

Date and Time

Current Date daterange month monthdate Time Today week weekdate

Strings

aggmetricstrings asaggstring asstring double isNumber isString join metricname metricstrings metricunit replace split string tolower toupper trim

Sports Data and Models

banister best bests besttime Device dist estimate estimates events intervals intervalstrings isRide isRun isSwim isXtrain meanmax measure measures metadata metrics and aggmetrics lts pmc powerindex rr samples sb sts tests vdottime xdata XDATA XDATA_UNITS zones

Charts and General

annotate autoprocess bool config constant curve exists fetch filename isset postprocess print set store smooth unset

Alphabetical

acos(v)

The arcus, inverse cosine operation is performed for the value or vector passed. Returning either a value or a vector of values.

acosh(v)

The arcus, inverse hyperbolic cosine operation is performed for the value or vector passed. Returning either a value or a vector of values.

activities("filter expression", expression) - since v3.6

Runs the expression for all activities that pass the "filter expression" (as a string, so " in the filter has to be escaped as \"). This provides a closure mechanism in the same way as the daterange() function.

e.g:

activities("tests() > 0", { yy <- metrics(BikeIntensity); xx <- metrics(date); } );

aggregate(vector, by, function) - since v3.6

Will aggregate vector, grouping by the vector by (which will be coerced to strings and repeated if not the same length as vector), the aggregate will be calculated using the passed function, which should be one of:

  • sum - the sum of all the values in the group
  • mean - the average of all the values in the group
  • max - the maximum value in the group
  • min - the minimum value in the group
  • count - the number of values in the group

annotate(type, parms) - since v3.6

Add an annotation to the chart.

annotate(label, "CP ", config(cp), " watts"); # add some labels to the top of the chart
annotate(lr, solid, "red"); # add a linear regression for the current series with style solid and color red
annotate(vline, "Average Cadence", solid, mean(xx)) # add a vertical line for mean xx
annotate(hline, "Average Power", solid, mean(yy)) # add a horizontal line for mean yy
annotate(voronoi, kmeans(centers, 50, xx, yy)); # overlay a voronoi diagram (make sure xx and yy have common dimensions/normalise them)

Note: in the above, the line styles can be one of; solid, dash, dot, dashdot and dashdotdot.

append(vector, v [,pos]) - since v3.6

Appends value v into the vector, by default the value is placed at the end of the vector but you can optionally supply the pos as an index (starting from 0).

It returns the number of items appended and updates the vector passed -- for this reason the vector passed must be a user symbol (you cannot pass it as an expression e.g. c(1,2,3)).

argsort(ascend|descend, vector) - since v3.6

Returns an index into the vector supplied sorted either ascend(ing) or descend(ing). The first element in the returned vector is an index to the first item in the sorted vector and the last element in the returned vector being an index to the last item in the sorted vector. This is modelled after numpy.argsort().

arguniq(vector) - since v3.6

Returns a vector into the unique items in the vector, with the first occurence of a duplicate always selected.

asin(v)

The arcus, inverse sine operation is performed for the value or vector passed. Returning either a value or a vector of values.

asinh(v)

The arcus, inverse hyperbolic sine operation is performed for the value or vector passed. Returning either a value or a vector of values.

asstring(metricname) and asaggstring(metricname)

Returns the metric but formatted as a string, this will honour number of decimal places and any special formatting e.g. pace or durations. The asaggstring will additional aggregate metrics for the currently selected date range.

atan(v)

The arcus, inverse tan operation is performed for the value or vector passed. Returning either a value or a vector of values.

atanh(v)

The arcus, inverse hyperbolic tan operation is performed for the value or vector passed. Returning either a value or a vector of values.

atl

A shorthand for sts(BikeStress)

autoprocess(expression)

This will typically be run from the search/filter box in the toolbar. The expression is used to select the activities we want to work with, and those that are selected will have the data processors re-run for them.

As part of global configuration, you can setup data processors to do things like fix gaps in recording, fix elevation and so on.

Since this function needs to open the ride and process it, the overall process will take some time if you apply to all your activities. So the expression helps to limit the scope of work. For example you might only want to reprocess runs since a specific data, so you might use a filter like; isRun && Date > "2020/04/01".

banister(stressmetric, performancemetric, nte|pte|perf|cp|date) - since v3.6

Returns a vector of values for the curve selected; one of nte, pte for negative and positive effect, perf or cp for the performance curve or related cp estimate or just the date for each data point. Banister fitting uses all athlete’s activities without filters.

Typically, the performance metric will be TRIMPS or BikeStress and the performance metric will be PowerIndex or 5_min_Peak_Power or similar. The CP estimate is only meaningful if the performance metric used is PowerIndex.

A worked example looks like this:

cp <- banister(BikeStress, PowerIndex, cp);

best(SERIES, duration)

Returns the peak value for the given SERIES and duration for the current activity.

bests(SERIES|date, [duration] [,start ,[stop] ]) - since v3.6

Returns a vector of the peak values for the given SERIES and duration (in seconds) for the currently selected date range, or optionally provided start and stop dates. When retreiving the dates no duration is required. An example from a user chart plotting peak 5minute power:

{

    finalise {
        xx <- bests(date);
        yy <- bests(POWER,300);
        z <- nonzero(yy);
    }

    x { xx[z]; }
    y { yy[z]; }
}

besttime(distance)

Returns the best time in seconds for covering the distance as passed (in kilometers). Particularly useful for running you might use besttime(10) to get your 10k PB, or for cycling TT perhaps besttime(40) for a 25 mile TT.

bin(data, bins) - since v3.6

Returns a vector of the data binned into bins, any data less than the first bin will be discarded, and data greater than the last bin will be included in the last bin. The returned bin is based upon counts, so will need to be scaled if want duration in seconds.

data <- samples(POWER);
b <- bin(data, quantile(data, c(0,0.25,5,0.75,1))) * RECINTSECS;

bool(expr) - since v3.6

Converts an expression to either 0 or 1. The expression can be a logical expression such as x>1800. It effectively truncates all non-zero values to 1.

c(elements) - since v3.6

Will return a vector containing the elements passed. There is no limit to the number of elements passed and they may be either vectors or numeric values or expressions. This is the standard way to create a vector.

An example:

a <- c(10,12,45);
# creates vector a with values [ 10, 12, 45 ]

cdfbeta(a,b, v) - since v3.6

Computes the cumulative density function of a beta distribution for v. This is the area of the long tail for the probability density function with a beta distribution with a alpha and b beta.

cdfnormal(sigma, v) - since v3.6

Computes the cumulative density function of a normal distribution for v. This is the area of the long tail for the probability density function with a Gaussian or normal distribution and a standard deviation of sigma.

ceil(v)

Computes the smallest integer value not less than v. This is effective for rounding values up to the nearest integer. See also floor below.

config(cranklength|cp|aetp|ftp|w'|pmax|cv|aetp|sex|dob|height|weight|lthr|aethr|maxhr|rhr|units)

Returns the configured value for the current athlete and time, in metric units for numerical values, while config(sex) returns “Female” or “Male” strings, and config(dob) returns the number of days from 1900/1/1 to date of birth.

constant(pi|e)

Returns hi-precision values for the well known mathematical constants.

cos(v)

The cosine operation is performed for the value or vector passed. Returning either a value or a vector of values.

cosh(v)

The hyperboliccosine operation is performed for the value or vector passed. Returning either a value or a vector of values.

count(v ..)

Returns the number of elements in a variable or list of variables, if it is a vector a count of the elements in the vector, if it is a numeric value it will return 1. See also length.

ctl

A shorthand for lts(BikeStress)

cumsum(v) - since v3.6

Returns a cumulative sum for the passed vector.

Current

Returns the date of the currently selected activity on the activity view. Depending upon the context this may or may not be available in Date. Use with caution. See also: Date, today and daterange.

curve(series, x|y|z|d|t) - since v3.6

Fetch the computed values for another curve on a user chart. The series will need to have already been computed. This is useful to avoid recomputing data in a user chart which may have been computationally expensive, e.g. fitting a model and producting a prediction.

Date

Is a built-in variable that holds the date of the current activity. This is only valid when working with activities in user metrics and the activity {} function when iterating. See also: daterange() and today.

daterange(start|stop)

Retrieves the start or stop of the currently selected date range. For the All Dates season it will return the date of the first activity available, or the last. See also: Date and today.

# calculate current season duration in days
days <- daterange(stop) - daterange(start);

daterange(from, to, expression) - since v3.6

Runs the expression setting the selected date range from from to to. Any function that works with the trend view date range selection will honour this.

e.g:

dist <- daterange(Today-30, Today, dist(POWER, data));
bins <- daterange(Today-30, Today, dist(POWER, bins));

datestring(date) - since v3.6

Converts the date parameter, number or vector elementwise, from days since 1900/01/01 to string date format.

Device

Returns a string describing the device the activity was recorded with. This is typically the bike computer or sports watch device rather than sensor details (such as power meter or foot-pod).

dist(SERIES, data|bins) - since v3.6

Returns vectors of the series data in bins (duration) or the actual bins. On activity view it will return the distribution for the currently selected activity, on the trends view it will return for the currently selected season.

double(string)

Convert a string or a vector of strings to a number or vector of numbers.

estimate(model, (cp|ftp|w'|pmax|x))

Relevant when iterating over activities, this will retrieve the interna GC estimate for a model parameter for the date of the activity being worked on. As at v3.5 the model can be one of: cp2, cp3, WS or Ext for the Critical Power 2 and 3 parameter models, Ward-Smith and the Extended CP model respectively. Extended CP model requires activities longer than 1hr to be fitted. In v3.6 model can be one of: cp2, cp3 or ext.

The model parameter to retrieve can be one of cp, ftp, pmax or w.

Estimates for sports with Power data are pre-computed weekly based on the last 6 weeks of data at startup and when new activities are imported. Maximal efforts in the 2-20’ are required for a good fit and extended CP model requires activities longer than 1hr.

estimates(model, cp|ftp|w'|pmax|date|x) - since v3.6

As per estimate above, will retrieve the PD model estimates for a date range without filters, model can be one of: cp2, cp3 or ext. For example, to plot a history of Bike CP estimates using the Morton 3 parameter model for the currently selected date range in a user chart, you can use the following program:

{

    finalise {
        # Morton 3P Bike model estimates for date range
        xx <- estimates(cp3, date);
        yy <- estimates(cp3, cp);
    }

    x { xx; }
    y { yy; }
}

If you filter by other sport, s.t. Run, the estimates corresponding to that sport will be selected.

Estimates for sports with power data are computed weekly based on the last 6 weeks of data at startup and when new activities are imported. Maximal efforts in the 2-20’ are required for a good fit and extended CP model requires activities longer than 1hr.

events(date|name|priority|description) - since v3.6

Will return a vector of dates, or the required string field, for each Event in the current Trends season / date range.

exists("symbol") - since v3.6

Will return non-zero if symbol is a known variable or function. Since variables do not have local or global scope it can be used to introduce and initialise a variable. This is particularly useful when working with user data formulas on the performance chart. E.g;

{
# accumulating work in a user data formula
if (!exists("work")) { work <- 0; }
work <- work + (POWER * RECINTSECS);
work/1000;
}

exp(v)

The base-e exponential of v is calculated, this is the same as constant(e)^v. Will return a value or a vector depending upon what was passed.

fabs(v)

Will return the absolute value for v; negative and positive numbers will always be positive in value.

fetch("name") - since v3.6

Will retrieve a named value previously stored with store() and returns the value 0 if not found.

store("cp", 260);
yy <- fetch("cp"); # yy now contains 260

filename() - since v3.6

Will return the filename for the curret ride on analysis view, or a vector of filenames for the currently selected date range in trends view.

floor(v)

Will compute the largest integer value that is not larger than v. It is effectively a way to round floating point numbers down to the nearest integer. See also ceil().

head(vector, count) - since v3.6

Returns the first count elements in the vector passed. Useful when looking for top n, or working with MMP data up to a particular time e.g:

# get first 20 minutes (1200 seconds)
mmpdata <- head(meanmax(POWER), 1200);

interpolate(linear|cubic|akima|steffen, x,y, xvalues) - since v3.6

Returns a vector of yvalues for each element in xvalues where the points in x,y are interpolated using the algorithm passed in the first parameter. See also: resample().

e.g:

xx <- samples(SECS);
yy <- samples(POWER);
    
first <- head(xx,1);
last <- tail(xx,1);
zxx <- seq(first, last, 0.1); # 10ths of a second
zyy <- interpolate(cubic, xx, yy, zxx);

intervals(metric|name|start|stop|type|test|color|route|selected|date|filename [,start [,stop]]) - since v3.6

intervalstrings(metric|name|start|stop|type|test|color|route|selected|date|filename [,start [,stop]]) - since v3.6

Returns a vector of the requested interval info, intervalstrings returns values as strings encoded correctly (for example time or metric values). Below is an example of an overview data table that displays intervals and selected metrics:

{

# column names, if using metrics then best
# to use metricname() to get correct name for locale
# otherwise it won't translate to other languages
names { 
    metricname(time,name,
              Average_Power,
              Distance,
              Duration,
              Fatigue_Index,
              Power_Index,
              W'_Work,
              Work);
}

# column units, if using metrics then best
# to use metricunit() function to get correct string
# for locale and metric/imperial
units { 
    metricunit(time,name,
              Average_Power,
              Distance,
              Duration,
              Fatigue_Index,
              Power_Index,
              W'_Work,
              Work);
}

# values to display as doubles or strings
# if using metrics always best to use asstring()
# to convert correctly with dp, metric/imperial
# or specific formats eg. rowing pace xx/500m
values { 
    c(intervalstrings(time),
      intervalstrings(name),
      intervalstrings(Average_Power),
      intervalstrings(Distance),
      intervalstrings(Duration),
      intervalstrings(Fatigue_Index),
      intervalstrings(Power_Index),
      intervalstrings(W'_Work),
      intervalstrings(Work));
}

# heatmap values are from 0-1 so we use the
# normalize() function to calculate it for the metrics
# in question. When we use normalize(0,0,metric) we
# will always get no heat, you can edit the min
# max values to set the range to set the heat color
heat { 
    c(normalize(0, 0, intervals(time)),
      normalize(0, 0, intervals(name)),
      normalize(0, config(cp), intervals(Average_Power)),
      normalize(0, 0, intervals(Distance)),
      normalize(0, 0, intervals(Duration)),
      normalize(0, 0, intervals(Fatigue_Index)),
      1-normalize(0, 150, intervals(Power_Index)),
      normalize(0, 0, intervals(W'_Work)),
      normalize(0, 0, intervals(Work)));
}

i {
    intervalstrings(name);
}

}

isNumber(v) - since v3.6

Will return 1 if v is numeric, 0 if not.

isString(v) - since v3.6

Will return 0 if v is numeric, 1 if not.

isRide

Will evaluation to true if the activity is a bike ride. Uses the Sport field and the presence of cycling related data series (e.g. Garmin Cycling Metrics).

isRun

This is a symbol, rather than a function. It returns non-zero if the activity is a Run. This is determined from the Sport metadata field and the presence of Garmin running metrics or data in the activity. See also: isSwim.

e.g:

if (isRun || isSwim) {
   # use speed not power values
   xx <- metrics(xPace);
} else {
   # use power values
   xx <- metrics(BikeScore);
}

isSwim

This is a symbol, rather than a function. It returns non-zero value if the activity is a Swim. This is determined from the Sport`` metadata field. See also: isRun```.

isXtrain

Returns true if the activity is not a Bike, Run or Swim.

isinf(v)

Returns non-zero if the value passed is infinite. Uses std::isinf internally.

isnan(v)

Returns non-zero if the value pass is not a number. Uses std::isnan internally.

isset(meta)

Returns non-zero if the metadata field meta has a value. See also unset() and set().

join(v, sep) - since v3.6

Joins v (a vector) as a string vector using sep as the separator.

kmeans(centers|assignments, k, dim1, dim2 .. dimn) - since v3.6

Returns the centers or assignments for k kmeans clusters across multiple dimensions. Typically you may choose two use two dimensions and plot on a scatter plot. The return values are ordered so they can be used directly in an overview data table.

# example for listing 5 clusters on an overview data table for some popular metrics
values {
    kmeans(centers, 5, metrics(TSS), metrics(Elevation_Gain), metrics(IF));
}

length(v) - since v3.6

Will return the number of elements in a vector. If the passed value is not a vector then a value of 0 will be returned. See also count().

lm(formula, xseries, yseries) - since v3.6

Will fit a formula using the Levenberg-Marquardt weighted least squares and return fit goodness. The formula is an expression involving existing user symbols, their current values will be used as the starting values by the fit.

Once the fit has been completed the user symbols will contain the estimated parameters and lm will return some diagnostics goodness of fit measures [ success, RMSE, CV ] where a non-zero value for success means the fit was successful. if success is false, RMSE and CV will be set to -1.

Here is an example fitting the 3 parameter Critical Power model:

# fit starting values, then LM fit the Morton 3p model to first 20 min data
cp <- 200; W<-11000; pmax <- 1000;
fit <- lm((W / (x - (W/(cp-pmax)))) + cp, secs[x<1200], mmp[secs[i]<1200]);

# success, generate prediction data
if (fit[0] <> 0) {

    annotate(label, "CP", round(cp));
    annotate(label, "W'", round(W));
    annotate(label, "CV=", round(fit[2],2), "%");

    # general model prediction using parameter estimates
    yy <- sapply(secs, (W / (x - (W/(cp-pmax)))) + cp); 
}

log(v)

Computes the natural (base e) logarithm of v, returning the computed value or values if a vector is passed.

log10(v)

Computes the base 10 logarithm of v, returning the computed value or values if a vector is passed.

lowerbound(vector, value) - since v3.6

Returns the index of the first entry in vector that does not compare less than value, analogous to std::lower_bound, it will return length(v) if no value found. The vector should be sorted first.

lr(xvector, yvector) - since v3.6

Performs a linear regression on x,y co-ords in xvector and yvector respectively. it returns a vector of results [slope, intercept, r2, see]. A worked example:

# looking at relationship between power and cadence
xx <- samples(CADENCE);
yy <- samples(POWER);

# linear regression
fit <- lr(xx,yy);

# using the lr results lets compute two
# points one at x=10, one at y=120 so we
# can draw a straight line
yy <- c((10*fit[0])+fit[1], (120*fit[0])+fit[1]);

lts(expression)

Returns the long term stress value (from the PMC concept called CTL) for the current activity. The input metric is passed in expression. It is typically just a metric, such as BikeStress (our name for TSS) but can be a formula to apply e.g. BikeStress/Duration.

match(v1, v2) - since v3.6

This returns an index of values; for each value in vector v1 it will return the index into the first element in vector v2 that matches. Elements that are not in v2 are ignored.

a <- c(4,6,11,9);
b <- c(1,2,3,4,5,6,7,8,9);
d <- match(a,b);
# d now contains 3,5,8 since indexes start from 0

max(v, ...)

Returns the maximum value for the values passed, you can pass multiple vectors or values. For example:

a<-max(1,2,3,c(4,5,6));
# a contains 6

mean(v ...)

Returns the mean value for the values passed, you can pass multiple vectors or values. For example:

a<-mean(1,2,3,c(4,5,6));
# a contains 3.5

meanmax(POWER|WPK|HR|CADENCE|SPEED|date|efforts [,start,stop]) - since v3.6

Will return a vector of mean maximal values from 1s onwards, in 1 second incremements. Element 0 represents the mean maximal value for 1 second. The parameter passed represents the data series we require or the dates for each maximal effort if using date.

The efforts series returns a set of indexes into the power mean maximal data for peaks filtered by date, removing averaging tails and filtering by power index. This is to remove data that is provably sub-maximal (to avoid low-bias when fitting power duration models).

When used on in the trends view it returns an array for the entire date range, on activities view it returns for the currently selected ride. If you want to retrieve a meanmaximal curve for a specific date range you can optionally pass that in start and stop.

Plotting the last 90 days MMP for the currently selected ride in activity view:

{
    finalise {
        yy <- meanmax(POWER, Date-90, Date);
        xx <- seq(1, length(yy), 1);
    }

    x { xx; }
    y { yy; }
}

meanmax(time, yvalue)

You can call the meanmax() function with two vectors as parameters. This data will be used to generate a mean-maximal curve. The supplied time vector should be in 1s increments and the yvalues should not be negative. The data will be corrected if this is not the case; negative y values will be truncated to 0 and the data will linearly interpolated to guarantee 1s time intervals.

The returned values will only be calculated to 3 decimal places of precision. Here is an example activity chart, generating MMP data using this approach:

{
    finalise {
        xx <- samples(SECS);
        yy <- samples(POWER);
        yy <- meanmax(xx,yy);
        xx <- seq(1,length(yy),1);
    }

    x { xx; }
    y { yy; }
}

measure(group, field)

Returns the value for the current activity; where group is one of "Body" or "Hrv" and the associated fields are "Weight" and "RMSSD" respectively. See also measures() below.

measures(group, field|"date") - since v3.6

As above but returns a vector of values for the measure for the current date range in trends view. You can additionally pass the field "date" to get the dates of the measures. At present it is no possible to distinguish between measures taken on a specific date with the continued (non-interpolated) values.

median(v ...) - since v3.6

Returns the median value for the values passed, you can pass multiple vectors or values. The values do not have to be sorted.

metadata(symbol) - since v3.6

Returns the metadata value for the current ride or date range.

workoutcode <- metadata("Workout Code");

metrics(symbol|date|time [,start [,stop]]) - since v3.6

metricstrings(symbol|date|time [,start [,stop]]) - since v3.6

aggmetrics(symbol|date|time [,start [,stop]]) - since v3.6

aggmetricstrings(symbol|date|time [,start [,stop]]) - since v3.6

Returns a vector of values for the symbol specified. metrics returns a vector of numbers, whilst metricstrings returns a vector of strings (applying formatting rules to the metric). If no start/stop is supplied it uses the currently selected date range, otherwise start thru today or start till stop. e.g.:

a<- metrics(BikeStress, "2019/01/01", "2020/01/01");
a contains the bikestress values for every ride in 2019

The agg variants of the functions will return the metrics as an aggregated value rather than a vector of values. The appropriate aggrgation method is applied when doing so (e.g. peaks get max values, averages take into account activity duration and so on).

metricname(metric1 ... metricn) - since v3.6

Will return the metric names as strings in the local language. Can be particularly useful when used with the overview data table, to supply column headings for metrics.

metricunit(metric1 ... metricn) - since v3.6

Will return a vector of unit descriptions for the metrics using the local language. Particularly useful when preparing data in an overview data table tile.

mid(vector, pos, count) - since v3.6

Returns a vector of elements taken from the passed vector starting at pos (indexes start from 0) and taking count number of items.

min(v ...)

Returns the minimum value for the values passed, you can pass multiple vectors or values. For example:

a<-min(1,2,3,c(4,5,6));
# a contains 1

mlr(yv, xv1 .. xvn) - since v3.6

The mlr(yvector, xvector1 .. xvectorn) function will perform a multiple linear regression and calculate the coefficients (beta) for each predictor (xvector1 thru xvectorn). The coefficients are returned as a vector. At present the chi-squared and covariance matrix is calculated internally and discarded, this may be fixed in future releases.

mode(v ...) - since v3.6

Returns the mode value for the values passed, you can pass multiple vectors or values. The values do not have to be sorted.

month(dates) - since v3.6

For the passed date value (vector or single value) converts from days since 1900/01/01 to months since 1900/01/01. See also monthdate().

monthdate - since v3.6

For the passed month value (vector or single value) converts from months since 1900/01/01 to days since 1900/01/01. See also month().

multisort(ascend|descend, vector1 .. vectorn) - since v3.6

You can sort a vector using the sort() function, it will be sorted in situ. sort does NOT return a vector of sorted values, it sorts the items in the vector supplied, returning a count of items sorted. e.g.

a<-c(3,6,2,6,1,99,-2);
b<-multisort(ascend, a);
# a contains [-2, 1, 2, 3, 6, 6, 99]
# b is 7

But when we are working with x,y data we need to sort the x and y values together, so sort() will also accept a list of vectors to sort. Each entry will be resorted in the same way as the first list. E.g.

a<-c(5,4,3,2,1);
b<-c(9,8,6,7,2);
multisort(ascend, a, b);
# a is now [1,2,3,4,5]
# b is now [2,7,6,8,9]

There is no limit to the number of vectors you can pass to multisort, so that helps if we want to work with lots of aligned vectors for say returning x,y,z,d and t sorted together in a user chart. See also sort.

multiuniq(vector1 .. vectorn) - since v3.6

Will modify vector1 by removing all duplicate values, retaining the first element found in sequence (from first to last). The vector does not need to be sorted and the function is stable (it sorts data internally and also uses arguniq). It removes in-situ and can remove the same elements from all other vectors passed. See also: arguniq(), multisort(), argsort(), sort and uniq.

nonzero(v) - since 3.6

nonzero(v1) returns a vector of indexes into v1 for non-zero values. Whilst it is possible to do this with sapply() this convenience function is faster - and is useful since this use-case is common.

a <- c(1,2,3,0,5,6,0,8,9,10);
nz <- nonzero(a)
# nz contains [ 0, 1, 2, 4, 7, 8, 9 ] remember indexes start at 0

normalize(min, max, v) - since v3.6

Returns a normalized vector for values in v, performing a unity based normalization using the values min and max. The returned values will be between 0 and 1.

tss <- c(75, 150, 180, 240, 300, 500);
nz <- normalize(0, 300, tss);
# nz now contains [ 0.25, 0.5, 0.6, 0.8, 1, 1 ]

Converting values to a fixed range between 0 and 1 is useful in lot of algorithms and for normalising dimensions between different units, but we use it at the moment in the heat function in an overview data table, where it is used to determine the color for the cell (based upon a heatmap that ranges from 0 to 1).

TIP: if you want to reverse the order ie range from 1 to 0 (in instances where a higher number is good and a lower number is bad) then just use 1-normalize(min, max, v). When used for a heatmap color this will reverse the colors used.

pdfbeta(a,b, v) - since v3.6

Computes the probability density function for v for a beta distribution using a alpha and b beta.

pdfnormal(sigma, v) - since v3.6

Computes the probability density function for v for a Gaussian distribution centered on 0 with standard deviation sigma.

pmc(symbol, stress|lts|sts|sb|rr|date) - since v3.6

Returns a vector of values for the current date range using the metric in symbol. As with the other pmc based functions symbol can be a formula expression e.g. BikeStress/Duration.

postprocess(processor, expression)

Runs the named data processor (see Edit menu for the available list) for activities that match the expression passed. See also: autoprocess().

powerindex(power, duration) - since v3.6

Returns a value (or vector) for power and duration expressed as a powerindex. Where power index is the percentage of the power the average athlete would do for the same duration. Over 100 is better than average, less than 100 is worse. The power and duration parameters can be values or vectors.

print(expression)

Outputs onto the debugging log (in release builds goldencheetah.log, unless --debug command line parameter is used to redirect to the launching console/terminal). Helpful for debugging programs and seeing what the contents of vectors, etc. contain.

quantile(vector, quantiles) - since v3.6

Returns quantile values for the vector passed. quantiles can be a single value, or vector of values. The quantile is expressed as a value between 0 and 1, where 0.5 would represent the median. Values outside this range are truncated to 0 or 1. For example:

pdata <- samples(POWER);
quartiles <- quantile(pdata, seq(0,1,0.25));
median <- quantile(pdata, 0.5);

random(n) - since v3.6

Generate a vector of n elements containing random numbers between 0 and 1. The user will need to scale to the range they require. For example:

{
    x { random(1024)*1024; }
    y { random(1024)*1024; }
}

rank(ascend|descend, list) - since v3.6

Will return rankings for each entry in the passed list in the order specified. Rankings range from 1 to n.

a<-c(4,2,1,3); 
b<-rank(descend, a);
# b now contains [ 1, 3, 4, 2 ]

remove(vector, pos, count) - since v3.6

Remove elements from the vector starting at position pos (indexes start at 0), removing count elements. See also: append().

rep(v, count) - since v3.6

Returns a vector of count values repeating the value passed in v. See also: seq().

replace(v, s1, s2) - since v3.6

Will replace string s1 with string s2 for value or vector v.

E.g.:

a<-"apple";
b<-replace(a, "pp", "nk");
# b now contains "ankle"

resample(old, new, yvector) - since v3.6

returns yvector resampled from old sample rate to new sample rate. Assumes yvector has already been interpolated or smoothed as needed (see the interpolate function). If the old and new sample rates are the same it will return yvector unchanged.

For example, resampling to 10s power samples in a user chart:

{
   finalise {
       t <- samples(SECS);
       xx <- seq(head(t,1),tail(t,1),10);
       yy <- resample(RECINTSECS, 10, samples(POWER));
   }
    
   x { xx; }
   y { yy; }
}

rev(vector) - since v3.6

Returns vector with its sequence reversed.

a<- rev(c(1,2,3,4));
# a now contains [ 4, 3, 2, 1 ]

round(v) or round(v, dp)

Returns v rounded to the nearest integer, or if decimal places are passed will round to that precision. See also: ceil() and floor().

rr(expression)

Returns the ramp rate value on the day of the current activity. The input metric is passed in expression. It is typically just a metric, such as BikeStress (our name for TSS) but can be a formula to apply e.g. BikeStress/Duration. See also: lts(), sts() and sb().

samples(POWER|HR etc) - since v3.6

Returns a vector of samples for the current ride, if the ride is not open (has never been selected by the user) it will return an empty vector. This is to avoid accidentally opening all activities by accident in an errant data filter. The full list of data series and their names are listed below in the Activity Data Reference section.

Additionally, when computing interval user metrics samples will only return values in the interval range.

sapply(vector, expression) - since v3.6

The sapply() function allows us to iterate over a vector applying an expression as we go, the results of which are returned as a vector. The expression can refer to the variables x and i for the element value and position respectively. Additionally, the expression can refer to other variables. E.g:

a<- c(1,2,3,4,5);
b<- c(10,20,30,40,50);
d<- sapply(a, { b[i] / x; });
# d contains [10, 10, 10, 10, 10]
e <- sapply(a, { x<3 ? i : -1; });
# e contains [ 0, 1, -1, -1, -1 ]

sb(expression)

Returns the stress balance (aka TSB) value on the day of the current activity. The input metric is passed in expression. It is typically just a metric, such as BikeStress (our name for TSS) but can be a formula to apply e.g. BikeStress/Duration. See also: lts(), sts() and rr().

seq(start, stop, step) - since v3.6

Returns a vector of values starting at value start and ending at stop in increments of step. Step can be positive or negative. See also: rep().

set(symbol, value, filter)

Set the metadata field symbol to value where the filter selects the activity. See also: unset() and isset().

sin(v)

The sine operation is performed for the value or vector passed. Returning either a value or a vector of values.

sinh

The hyperbolic sine operation is performed for the value or vector passed. Returning either a value or a vector of values.

smooth(algorithm, ...) - since v3.6

The smooth() function will return a vector of data with a smoothing algorithm applied. At present there are two types:

  • sma is the simple moving average algorithm and takes two parameters, the first defines the windowing policy: forward, backward or centered, and the second is the window size. We recommend always using an odd number for the window size when centered.

  • ewma is the exponentially weighted moving average algorithm and takes a single pararameter alpha which is the weighting to use, and must be between 0 and 1. The default is 0.3 if the value supplied is out of bounds.

Some examples:

power <- smooth(samples(POWER), sma, centered, 15);
# power contains sample data smoothed
bikestress <- smooth(metrics(BikeStress), ewma, 0.01);
# bikestress contains a smoothed trend of bikestress for the date range

sort(ascend|descend, list) - since v3.6

Will return a vector of the list sorted. See also multisort.

split(v, sep) - since v3.6

Splits a string v into a vector of strings, using separator sep.

sqrt(v) - since v3.6

Returns the square root of values passed, you can pass multiple vectors or values.

stddev(vector) - since v3.6

Returns the standard deviation for the values passed in the vector. See also: variance().

store("name", value) - since v3.6

Store a named value for an athlete so it can be re-used in other data filters. Values stored are retained until you restart or update them. They are lost when you restart GoldenCheetah. See also: fetch().

string(number)

Convert a number or a vector of numbers to a string or vector of strings.

sts(expression)

Returns the short term stress (aka ATL) value on the day of the current activity. The input metric is passed in expression. It is typically just a metric, such as BikeStress (our name for TSS) but can be a formula to apply e.g. BikeStress/Duration. See also: lts(), sb() and rr().

sum(v ...)

Returns the square root of values passed, you can pass multiple vectors or values.

tail(vector, count) - since v3.6

Returns a vector of the last count elements in vector.

tan(v)

The tan operation is performed for the value or vector passed. Returning either a value or a vector of values.

tanh(v)

The hyperbolic tan operation is performed for the value or vector passed. Returning either a value or a vector of values.

tests()

Returns the number of performance tests that have been marked in the activity. Useful for finding activities that are relevant for performace modelling.

tests(user|bests, power|duration) - since v3.6

Returns a vector of power or duration values for the user marked performance tests, or the discovered bests within training data. Used to fit pd models. See also: meanmax().

timestring(time) - since v3.6

Converts the time parameter, number or vector elementwise, from secs since midnight to a time string.

Time

Returns the start time of the current activity.

Today

Returns the current date as of now. Be cautious when using this since you are likely more interested in the date range or date of the activity. It is quite rare to actually need to know today's date. See also: Date and daterange().

tolower(v) - since v3.6

Converts to lower case.

toupper(v) - since 3.6

Converts to upper case.

trim(v) - since v3.6

Trims whitespace from both ends of a string.

tsb

A shorthand for sb(BikeStress)

uniq(list) - since v3.6

Will return the uniq values in list. it is stable and will return in the same order, retaining the first entry it finds. See also: arguniq(), multiuniq(), argsort() and sort.

unset(symbol, filter)

Remove the metadata field symbol where the filter selects the activity. See also: set() and isset().

variance(vector) - since v3.6

Returns the variance for the values passed in vector. See also: stddev().

vdottime(VDOT, distance)

Returns the equivalent time (in seconds) for VDOT at distance (in kilometers), estimated by Newton-Raphson method. See also: besttime().

week(date) - since v3.6

For the passed date value (vector or single value) converts from days since 1900/01/01 to weeks since 1900/01/01. See also weekdate().

weekdate(week) - since v3.6

For the passed week value (vector or single value) converts from weeks since 1900/01/01 to days since 1900/01/01. See also week().

which(expression, values)

Superceded either sapply() or selection using [lexpr], which returns a vector of elements from values (which can in turn be a number of parameter values) that pass the expression.

For example:

a<-which(x>5, 1,2,3,c(4,5,6,7),c(8,9,10))
# a now contains [6, 7, 8, 9, 10)

# this is equivalent to:
a<- c(1,2,3,c(4,5,6,7),c(8,9,10))[x>5]

xdata("XDATANAME", "XDATASERIES" | km | secs) - since v3.6

Returns a vector of values for xdata series in an activity or the km or secs values. Does not interpolate or resample the values, since the user can perform this with the relevant functions. Like the samples() function it will only return values for activities that have been opened to avoid accidentally iterating over every ride. See also: samples, interpolate and resample.

XDATA("XDATANAME", "XDATASERIES", sparse|repeat|interpolate|resample)

When iterating over an activity with the sample {} function it returns XDATA values for the associated XDATANAME and XDATASERIES passed. Since the XDATA can also have a different recording interval the third parameter controls how data will be interpolated, in other contexts s.t. filters and relevant function it indicates the presence of the XDATA series in activity. See Also: XDATA_UNITS().

XDATA_UNITS("XDATANAME", "XDATASERIES")

Returns a string of the units in which XDATA values are recorded. See also: XDATA().

zones(power|hr|pace|fatigue, name|description|low|high|units|time|percent) - since v3.6

Returns zone information, the first parameter is the series you want information for, whilst the second parameter is the zone info field you want to retrieve. It will return a vector of strings that are typically used in an overview data table.

The fields and their meanings;

  • name - the short codes for the zone e.g. THR, VO2, SPR
  • description - the long description for the zone, in the local language e.g "Active Recovery", "Threshold"
  • low and high - the lower and upper bound for the zone, based upon the date of the current ride.
  • time - time spent in zone as a time string
  • percent - percent of time spent in zone as a string

Here is an example of using the function to prepare an overview data table:

{

# column names for the table, no transation available
names { 
    c("Name","Description","Low","High","Time","%");
}

# column units - for the zone table it really is only
# the low and high values that need explaining, the rest
# are obvious from the name (or it doesn't apply) so 
# we just return a blank string for those.
units {

    c("",
      "",
      zones(power,units),
      zones(power,units), "", "");
}

# values to display - zones will return a vector
# of values (1 per zone) we call it multiple times
# to get each field we are interested in displaying
values { 

   c( zones(power,name),
      zones(power,description),
      zones(power,low),
      zones(power,high),
      zones(power,time),
      zones(power,percent));
} 

}

Activity Data Reference

The following data streams are available when working with activity data:

  • SECS
  • CADENCE
  • CADENCED
  • HEARTRATE
  • HEARTRATED
  • DISTANCE
  • SPEED
  • SPEEDD
  • TORQUE
  • TORQUED
  • POWER
  • POWERD
  • NP
  • ALTITUDE
  • LON
  • LAT
  • HEADWIND
  • SLOPE
  • TEMPERATURE
  • BALANCE
  • LEFTEFFECTIVENESS
  • RIGHTEFFECTIVENESS
  • LEFTSMOOTHNESS
  • RIGHTSMOOTHNESS
  • SMO2
  • THB
  • RUNVERT
  • RUNCADENCE
  • RUNCONTACT
  • LEFTPCO
  • RIGHTPCO
  • LEFTPPB
  • RIGHTPPB
  • LEFTPPE
  • RIGHTPPE
  • LEFTPPPB
  • RIGHTPPPB
  • LEFTPPPE
  • RIGHTPPPE
  • WBAL and WBALSECS - only made available in the samples() function

Metric Reference

The following metrics are available, computed for intervals and entire activities.

  • 1_min_Peak_Pace - 1 min Peak Pace
  • 1_min_Peak_Pace_Swim - 1 min Peak Pace Swim
  • 1_min_Peak_Power - 1 min Peak Power
  • 1_min_Peak_Power_HR - Average Heart Rate for 1 min Peak Power interval
  • 1_min_Peak_WPK - 1 min Peak Power relative to Athlete Weight.
  • 1_sec_Peak_Power - 1 sec Peak Power
  • 1_sec_Peak_WPK - 1 sec Peak Power relative to Athlete Weight.
  • 10_min_Peak_Pace - 10 min Peak Pace
  • 10_min_Peak_Pace_Swim - 10 min Peak Pace Swim
  • 10_min_Peak_Power - 10 min Peak Power
  • 10_min_Peak_Power_HR - Average Heart Rate for 10 min Peak Power interval
  • 10_min_Peak_WPK - 10 min Peak Power relative to Athlete Weight.
  • 10_sec_Peak_Pace - 10 sec Peak Pace
  • 10_sec_Peak_Pace_Swim - 10 sec Peak Pace Swim
  • 10_sec_Peak_Power - 10 sec Peak Power
  • 10_sec_Peak_WPK - 10 sec Peak Power relative to Athlete Weight.
  • 15_sec_Peak_Pace - 15 sec Peak Pace
  • 15_sec_Peak_Pace_Swim - 15 sec Peak Pace Swim
  • 15_sec_Peak_Power - 15 sec Peak Power
  • 15_sec_Peak_WPK - 15 sec Peak Power relative to Athlete Weight.
  • 2_min_Peak_Pace - 2 min Peak Pace
  • 2_min_Peak_Pace_Swim - 2 min Peak Pace Swim
  • 2_min_Peak_Power - 2 min Peak Power
  • 20_min_Peak_Pace - 20 min Peak Pace
  • 20_min_Peak_Pace_Swim - 20 min Peak Pace Swim
  • 20_min_Peak_Power - 20 min Peak Power
  • 20_min_Peak_Power_HR - Average Heart Rate for 20 min Peak Power interval
  • 20_min_Peak_WPK - 20 min Peak Power relative to Athlete Weight.
  • 20_sec_Peak_Pace - 20 sec Peak Pace
  • 20_sec_Peak_Pace_Swim - 20 sec Peak Pace Swim
  • 20_sec_Peak_Power - 20 sec Peak Power
  • 20_sec_Peak_WPK - 20 sec Peak Power relative to Athlete Weight.
  • 3_min_Peak_Pace - 3 min Peak Pace
  • 3_min_Peak_Pace_Swim - 3 min Peak Pace Swim
  • 3_min_Peak_Power - 3 min Peak Power
  • 30_min_Peak_Pace - 30 min Peak Pace
  • 30_min_Peak_Pace_Swim - 30 min Peak Pace Swim
  • 30_min_Peak_Power - 30 min Peak Power
  • 30_min_Peak_Power_HR - Average Heart Rate for 30 min Peak Power interval
  • 30_min_Peak_WPK - 30 min Peak Power relative to Athlete Weight.
  • 30_sec_Peak_Pace - 30 sec Peak Pace
  • 30_sec_Peak_Pace_Swim - 30 sec Peak Pace Swim
  • 30_sec_Peak_Power - 30 sec Peak Power
  • 30_sec_Peak_WPK - 30 sec Peak Power relative to Athlete Weight.
  • 5_min_Peak_Pace - 5 min Peak Pace
  • 5_min_Peak_Pace_Swim - 5 min Peak Pace Swim
  • 5_min_Peak_Power - 5 min Peak Power
  • 5_min_Peak_Power_HR - Average Heart Rate for 5 min Peak Power interval
  • 5_min_Peak_WPK - 5 min Peak Power relative to Athlete Weight.
  • 5_sec_Peak_Power - 5 sec Peak Power
  • 5_sec_Peak_WPK - 5 sec Peak Power relative to Athlete Weight.
  • 60_min_Peak_Pace - 60 min Peak Pace
  • 60_min_Peak_Pace_Swim - 60 min Peak Pace Swim
  • 60_min_Peak_Power - 60 min Peak Power
  • 60_min_Peak_Power_HR - Average Heart Rate for 60 min Peak Power interval
  • 60_min_Peak_WPK - 60 min Peak Power relative to Athlete Weight.
  • 8_min_Peak_Pace - 8 min Peak Pace
  • 8_min_Peak_Pace_Swim - 8 min Peak Pace Swim
  • 8_min_Peak_Power - 8 min Peak Power
  • 90_min_Peak_Pace - 90 min Peak Pace
  • 90_min_Peak_Pace_Swim - 90 min Peak Pace Swim
  • 90_min_Peak_Power - 90 min Peak Power
  • 95%_Heartrate - Heart Rate for which 95% of activity samples has lower HR values
  • aBikeScore - Skiba's altitude adjusted stress score taking into account both the intensity and the duration of the training session plus the altitude effect, similar to aTSS it can be computed as 100 * hours * (aPower Relative Intensity)^2
  • Activities - Activity Count
  • Activity Date - Activity Start Date
  • Aerobic_Decoupling - Aerobic decoupling is a measure of how much heart rate rises or how much power/pace falls off during the course of a long ride/run.
  • Aerobic_TISS - Aerobic Training Impact Scoring System. It's a metric to quantify the training strain or response on the aerobic system
  • aIF - Altitude Adjusted Intensity Factor is the ratio between aNP and the Critical Power (CP) configured in Power Zones.
  • Anaerobic_TISS - Anaerobic Training Impact Scoring System. It's a metric to quantify the training strain or response on the anaerobic system
  • aNP - Altitude Adjusted Normalized Power is an estimate of the power that you could have maintained for the same physiological 'cost' if your power output had been perfectly constant accounting for altitude.
  • aPower_Efficiency_Factor - The ratio between aNP and Average HR
  • aPower_Relative_Intensity - Altitude Adjusted Relative Intensity is the ratio between axPower and the Critical Power (CP) configured in Power Zones, similar to aIF.
  • aPower_Response_Index - The ratio between axPower and Average HR, similar to aPower Efficiency Factor
  • Athlete_Bodyfat - Bodyfat in kg or lbs from Withings data
  • Athlete_Bodyfat_Percent - Bodyfat Percent from Withings data
  • Athlete_Lean_Weight - Lean Weight in kg or lbs from Withings data
  • Athlete_Weight - Weight in kg or lbs: first from Withings data, then from Activity metadaa and last from Athlete configuration with 75kg default
  • aTSS - Altitude Adjusted Training Stress Score takes into account both the intensity and the duration of the training session plus the altitude effect, it can be computed as 100 * hours * aIF^2
  • aTSS_per_hour - Altitude Adjusted Training Stress Score divided by Duration in hours
  • Average_aPower - Average altitude power. Recorded power ajusted to take into account the effect of altitude on vo2max and thus power output.
  • Average_Cadence - Average Cadence, computed when Cadence > 0
  • Average_Core_Temperature - Average Core Temperature. The core body temperature estimate is based on HR data
  • Average_Ground_Contact_Time - Average Ground Contact Time
  • Average_Heart_Rate - Average Heart Rate computed for samples when hr is greater than zero
  • Average_Left_Peak_Power_Phase_End - It is the left pedal stroke angle where you end producing peak power, on average.
  • Average_Left_Peak_Power_Phase_Length - It is the left pedal stroke region length where you produce peak power, on average.
  • Average_Left_Peak_Power_Phase_Start - It is the left pedal stroke angle where you start producing peak power, on average.
  • Average_Left_Pedal_Center_Offset - Platform center offset is the location on the left pedal platform where you apply force, on average.
  • Average_Left_Pedal_Smoothness - It measures how smoothly power is delivered to the left pedal throughout the revolution, on average.
  • Average_Left_Power_Phase_End - It is the left pedal stroke angle where you end producing positive power, on average.
  • Average_Left_Power_Phase_Length - It is the left pedal stroke region length where you produce positive power, on average.
  • Average_Left_Power_Phase_Start - It is the left pedal stroke angle where you start producing positive power, on average.
  • Average_Left_Torque_Effectiveness - It measures how much of the power delivered to the left pedal is pushing it forward, on average.
  • Average_Power - Average Power from all samples with power greater than or equal to zero
  • Average_Power_Variance - Mean Power Deviation with respect to 30sec Moving Average
  • Average_Right_Peak_Power_Phase_End - It is the right pedal stroke angle where you end producing peak power, on average.
  • Average_Right_Peak_Power_Phase_Length - It is the right pedal stroke region length where you produce peak power, on average.
  • Average_Right_Peak_Power_Phase_Start - It is the right pedal stroke angle where you start producing peak power, on average.
  • Average_Right_Pedal_Center_Offset - Platform center offset is the location on the right pedal platform where you apply force, on average.
  • Average_Right_Pedal_Smoothness - It measures how smoothly power is delivered to the right pedal throughout the revolution, on average.
  • Average_Right_Power_Phase_End - It is the right pedal stroke angle where you end producing positive power, on average.
  • Average_Right_Power_Phase_Length - It is the right pedal stroke region length where you produce positive power, on average.
  • Average_Right_Power_Phase_Start - It is the right pedal stroke angle where you start producing positive power, on average.
  • Average_Right_Torque_Effectiveness - It measures how much of the power delivered to the right pedal is pushing it forward, on average.
  • Average_Running_Cadence - Average Running Cadence, computed when Cadence > 0
  • Average_SmO2 - Average Muscle Oxygen Saturation, the percentage of hemoglobin that is carrying oxygen.
  • Average_Speed - Average Speed in kph or mph, computed from distance over time when speed not zero
  • Average_Temp - Average Temp from activity data
  • Average_tHb - Average total hemoglobin concentration. The total grams of hemoglobin per deciliter.
  • Average_Vertical_Oscillation - Average Vertical Oscillation
  • aVI - Altitude Adjusted Variability Index is the ratio between aNP and Average aPower.
  • axPower - Altitude Adjusted xPower is an estimate of the power that you could have maintained for the same physiological 'cost' if your power output had been perfectly constant at altitude, similar to aNP.
  • Below_CP_Work - Below CP Work is the amount of kJ produced while power is below CP.
  • Best_1000m - Best 1000m
  • Best_100m - Best 100m
  • Best_10km - Best 10km
  • Best_1500m - Best 1500m
  • Best_15km - Best 15km
  • Best_2000m - Best 2000m
  • Best_200m - Best 200m
  • Best_20km - Best 20km
  • Best_3000m - Best 3000m
  • Best_30km - Best 30km
  • Best_4000m - Best 4000m
  • Best_400m - Best 400m
  • Best_40km - Best 40km
  • Best_5000m - Best 5000m
  • Best_500m - Best 500m
  • Best_50m - Best 50m
  • Best_800m - Best 800m
  • Best_Half_Marathon - Best Half Marathon
  • Best_Marathon - Best Marathon
  • BikeScore™ - Skiba's stress score taking into account both the intensity and the duration of the training session, similar to TSS it can be computed as 100 * hours * (Relative Intensity)^2
  • Calories_(HR) - Total Calories estimated from Time Moving, Heart Rate, Weight, Sex and Age
  • Checksum - A checksum for the activity, can be used to trigger cache refresh in R scripts.
  • Climb_Rating - According to Dan Conelly: Elevation Gain ^2 / Distance / 1000, 100 is HARD
  • Critical_Power - Critical Power (CP) configured in Power Zones.
  • Daniels_EqP - Daniels EqP is the constant power which would produce equivalent Daniels Points.
  • Daniels_Points - Daniels Points adapted for cycling using power instead of pace and assuming VO2max-power=1.2*CP, normalized to assign 100 points to 1 hour at CP.
  • Distance - Total Distance in kilometers or miles
  • Distance_Swim - Total Distance in meters or yards
  • Duration - Total Duration
  • Effect_of_Altitude - Relationship between altitude adjusted power and recorded power
  • Efficiency_Factor - The ratio between NP and Average HR for Cycling and xPace (in yd/min) and Average HR for Running
  • Efficiency_Index - Efficiency Index : average speed by average power
  • Elevation_Gain - Elevation Gain in meters or feet
  • Elevation_Gain_Carrying(Est)_ - Elevation gained at low speed with no power nor cadence
  • Elevation_Loss - Elevation Loss in meters or feet
  • Estimated_VO2MAX - Estimated VO2max from 5 min Peak Power relative to Athlete Weight using new ACSM formula: 10.8 * Watts / KG + 7 (3.5 per leg).
  • Exhaustion_Best_R - Best value for R in differential model for exhaustion point.
  • Fatigue_Index - Fatigue Index is power decay from Max Power to Min Power as a percent of Max Power.
  • GOVSS - Gravity Ordered Velocity Stress Score, the TSS like metric defined by Dr. Skiba for Running, accounts for variations in speed, slope and relative intensity and duration
  • Gradient - Elevation Gain to Total Distance percent ratio
  • H1_Percent_in_Zone - Percent of Time in Heart Rate Zone 1.
  • H1_Time_in_Zone - Time in Heart Rate Zone 1.
  • H10_Percent_in_Zone - Percent of Time in Heart Rate Zone 10.
  • H10_Time_in_Zone - Time in Heart Rate Zone 10.
  • H2_Percent_in_Zone - Percent of Time in Heart Rate Zone 2.
  • H2_Time_in_Zone - Time in Heart Rate Zone 2.
  • H3_Percent_in_Zone - Percent of Time in Heart Rate Zone 3.
  • H3_Time_in_Zone - Time in Heart Rate Zone 3.
  • H4_Percent_in_Zone - Percent of Time in Heart Rate Zone 4.
  • H4_Time_in_Zone - Time in Heart Rate Zone 4.
  • H5_Percent_in_Zone - Percent of Time in Heart Rate Zone 5.
  • H5_Time_in_Zone - Time in Heart Rate Zone 5.
  • H6_Percent_in_Zone - Percent of Time in Heart Rate Zone 6.
  • H6_Time_in_Zone - Time in Heart Rate Zone 6.
  • H7_Percent_in_Zone - Percent of Time in Heart Rate Zone 7.
  • H7_Time_in_Zone - Time in Heart Rate Zone 7.
  • H8_Percent_in_Zone - Percent of Time in Heart Rate Zone 8.
  • H8_Time_in_Zone - Time in Heart Rate Zone 8.
  • H9_Percent_in_Zone - Percent of Time in Heart Rate Zone 9.
  • H9_Time_in_Zone - Time in Heart Rate Zone 9.
  • Heartbeats - Total Heartbeats
  • HrNp_Ratio - Normalized Power to Average Heart Rate ratio in watts/bpm
  • HrPw_Ratio - Power to Heart Rate Ratio in watts/bpm
  • IF - Intensity Factor is the ratio between NP and the Functional Threshold Power (FTP) configured in Power Zones.
  • IsoPower - Normalized Power (aka NP) is an estimate of the power that you could have maintained for the same physiological 'cost' if your power output had been perfectly constant.
  • IWF - Intensity Weigthting Factor, part of GOVSS calculation, defined as LNP/RTP
  • L1_Percent_in_Zone - Percent of Time in Power Zone 1.
  • L1_Sustained_Time - Sustained Time in Power Zone 1, based on (sustained) EFFORT intervals.
  • L1_Time_in_Zone - Time in Power Zone 1.
  • L10_Percent_in_Zone - Percent of Time in Power Zone 10.
  • L10_Sustained_Time - Sustained Time in Power Zone 10, based on (sustained) EFFORT intervals.
  • L10_Time_in_Zone - Time in Power Zone 10.
  • L2_Percent_in_Zone - Percent of Time in Power Zone 2.
  • L2_Sustained_Time - Sustained Time in Power Zone 2, based on (sustained) EFFORT intervals.
  • L2_Time_in_Zone - Time in Power Zone 2.
  • L3_Percent_in_Zone - Percent of Time in Power Zone 3.
  • L3_Sustained_Time - Sustained Time in Power Zone 3, based on (sustained) EFFORT intervals.
  • L3_Time_in_Zone - Time in Power Zone 3.
  • L4_Percent_in_Zone - Percent of Time in Power Zone 4.
  • L4_Sustained_Time - Sustained Time in Power Zone 4, based on (sustained) EFFORT intervals.
  • L4_Time_in_Zone - Time in Power Zone 4.
  • L5_Percent_in_Zone - Percent of Time in Power Zone 5.
  • L5_Sustained_Time - Sustained Time in Power Zone 5, based on (sustained) EFFORT intervals.
  • L5_Time_in_Zone - Time in Power Zone 5.
  • L6_Percent_in_Zone - Percent of Time in Power Zone 6.
  • L6_Sustained_Time - Sustained Time in Power Zone 6, based on (sustained) EFFORT intervals.
  • L6_Time_in_Zone - Time in Power Zone 6.
  • L7_Percent_in_Zone - Percent of Time in Power Zone 7.
  • L7_Sustained_Time - Sustained Time in Power Zone 7, based on (sustained) EFFORT intervals.
  • L7_Time_in_Zone - Time in Power Zone 7.
  • L8_Percent_in_Zone - Percent of Time in Power Zone 8.
  • L8_Sustained_Time - Sustained Time in Power Zone 8, based on (sustained) EFFORT intervals.
  • L8_Time_in_Zone - Time in Power Zone 8.
  • L9_Percent_in_Zone - Percent of Time in Power Zone 9.
  • L9_Sustained_Time - Sustained Time in Power Zone 9, based on (sustained) EFFORT intervals.
  • L9_Time_in_Zone - Time in Power Zone 9.
  • Left/Right_Balance - Left/Right Balance shows the proportion of power coming from each pedal.
  • LNP - Lactate Normalized Power as defined by Dr. Skiba in GOVSS algorithm
  • Max_Cadence - Maximum Cadence
  • Max_Core_Temperature - Maximum Core Temperature. The core body temperature estimate is based on HR data
  • Max_Heartrate - Maximum Heart Rate.
  • Max_Power - Maximum Power
  • Max_Power_Variance - Maximum Power Deviation with respect to 30sec Moving Average
  • Max_Running_Cadence - Maximum Running Cadence
  • Max_SmO2 - Maximum Muscle Oxygen Saturation, the percentage of hemoglobin that is carrying oxygen.
  • Max_Speed - Maximum Speed
  • Max_Temp - Maximum Temperature
  • Max_tHb - Maximum total hemoglobin concentration. The total grams of hemoglobin per deciliter.
  • Max_W'_Expended - Maximum W' bal Expended expressed as percentage of W', W' bal tracks the level of W' according to CP model during intermitent exercise.
  • Maximum_W'bal_Match - Maximum W' bal Match, W' bal tracks the level of W' according to CP model during intermitent exercise.
  • Min_Heartrate - Minimum Heart Rate.
  • Min_SmO2 - Minimum Muscle Oxygen Saturation, the percentage of hemoglobin that is carrying oxygen.
  • Min_Temp - Minimum Temperature
  • Min_tHb - Minimum total hemoglobin concentration. The total grams of hemoglobin per deciliter.
  • Minimum_W'_bal - Minimum W' bal, W' bal tracks the level of W' according to CP model during intermitent exercise.
  • MMP_Percentage - Average Power as Percent of Mean Maximal Power for Duration.
  • Nonzero_Average_Power - Average Power without zero values, it gives inflated values when frecuent coasting is present
  • P1_Percent_in_Pace_Zone - Percent of Time in Pace Zone 1.
  • P1_Time_in_Pace_Zone - Time in Pace Zone 1.
  • P10_Percent_in_Pace_Zone - Percent of Time in Pace Zone 10.
  • P10_Time_in_Pace_Zone - Time in Pace Zone 10.
  • P2_Percent_in_Pace_Zone - Percent of Time in Pace Zone 2.
  • P2_Time_in_Pace_Zone - Time in Pace Zone 2.
  • P3_Percent_in_Pace_Zone - Percent of Time in Pace Zone 3.
  • P3_Time_in_Pace_Zone - Time in Pace Zone 3.
  • P4_Percent_in_Pace_Zone - Percent of Time in Pace Zone 4.
  • P4_Time_in_Pace_Zone - Time in Pace Zone 4.
  • P5_Percent_in_Pace_Zone - Percent of Time in Pace Zone 5.
  • P5_Time_in_Pace_Zone - Time in Pace Zone 5.
  • P6_Percent_in_Pace_Zone - Percent of Time in Pace Zone 6.
  • P6_Time_in_Pace_Zone - Time in Pace Zone 6.
  • P7_Percent_in_Pace_Zone - Percent of Time in Pace Zone 7.
  • P7_Time_in_Pace_Zone - Time in Pace Zone 7.
  • P8_Percent_in_Pace_Zone - Percent of Time in Pace Zone 8.
  • P8_Time_in_Pace_Zone - Time in Pace Zone 8.
  • P9_Percent_in_Pace_Zone - Percent of Time in Pace Zone 9.
  • P9_Time_in_Pace_Zone - Time in Pace Zone 9.
  • Pace - Average Speed expressed in pace units: min/km or min/mile
  • Pace_Swim - Average Speed expressed in swim pace units: min/100m or min/100yd
  • Pacing_Index - Pacing Index is Average Power as a percent of Maximal Power
  • Power_Percent_of_Max - Power as percent of Pmax according to Power Zones
  • Power_Zone - Power Zone fractional number determined from Average Power.
  • Relative_Intensity - Relative Intensity is the ratio between xPower and the Critical Power (CP) configured in Power Zones, similar to IF.
  • Response_Index - The ratio between xPower and Average HR, similar to Efficiency Factor
  • RTP - Run Threshold Power, computed from Critical Velocity using the GOVSS related algorithm
  • Session_RPE - Session RPE is the product of RPE * minutes, where RPE is the rate of perceived exercion (10 point modified borg scale) and minutes is Time Moving if available or Duration otherwise.
  • Skiba_aVI - Skiba Altitude Adjusted Variability Index is the ratio between axPower and Average aPower.
  • Skiba_VI - Skiba Variability Index is the ratio between xPower and Average Power.
  • SRI - Swimming Relative Intensity, used for SwimScore calculation, defined as xPowerSwim/STP
  • STP - Swimming Threshold Power based on Swimming Critical Velocity, used for SwimScore calculation
  • Stroke_Rate - Stroke Rate in strokes/min, counting both arms for freestyle/backstroke, corrected by 3m push-off length for pool swims
  • Strokes_Per_Length - Strokes per length, counting the arm using the watch, Pool Length defaults to 50m for open water swims
  • Swim_Pace - Average Swim Pace, computed only when Cadence > 0 to avoid kick/drill lengths
  • SwimScore - SwimScore swimming stress metric as defined by Dr. Skiba
  • SWolf - Strokes per length, counting the arm using the watch plus time in seconds, Pool Length defaults to 50m for open water swims
  • Time_Carrying_(Est) - Time with low speed and elevation gain but no power nor cadence
  • Time_Moving - Time with speed or cadence different from zero
  • TISS_Aerobicity - TISS Aerobicity is a percentage of Aerobic TISS of the total TISS
  • To_Exhaustion - Count of exhaustion points marked by the user in an activity
  • TPace - Daniels' TPace, computed as 90%vVDOT from VDOT metric, in min/km or min/mile
  • TRIMP_Points - Training Impulse according to Morton/Banister with Green et al coefficient.
  • TRIMP_Zonal_Points - Training Impulse with time in zones weighted according to coefficients defined in Heart Rate Zones.
  • TRIMP(100)_Points - TRIMP Points normalized to assign 100 points to 1 hour at threshold heart rate.
  • TriScore - TriScore combined stress metric based on Dr. Skiba stress metrics, defined as BikeScore for cycling, GOVSS for running and SwimScore for swimming
  • TSS - Training Stress Score takes into account both the intensity and the duration of the training session, it can be computed as 100 * hours * IF^2
  • TSS_per_hour - Training Stress Score divided by Duration in hours
  • VAM - Velocita Ascensionale Media, average ascent speed in vertical meters per hour
  • VDOT - Daniels' VDOT computed from best average pace for durations from 4 min 4 hr
  • VI - Variability Index is the ratio between NP and Average Power.
  • W'_Power - W' Power is the average power produce while power is above CP.
  • W'_Work - W' Work is the amount of kJ produced while power is above CP.
  • W'bal_Matches - Number of W' balance Matches higher than 2kJ, W' bal tracks the level of W' according to CP model during intermitent exercise.
  • W'bal_TAU - W' bal TAU is the recovery time constant for W' bal, W' bal tracks the level of W' according to CP model during intermitent exercise.
  • W1_Above_CP_W'bal_Low_Fatigue - Time expended when Power is above CP and W' bal is below 25% of W'.
  • W1_W'bal_Low_Fatigue - Time expended when W' bal is below 25% of W'.
  • W1_W'bal_Work_Low_Fatigue - Work produced when W' bal is below 25% of W'.
  • W2_Above_CP_W'bal_Moderate_Fatigue - Time expended when Power is above CP and W' bal is between 25% and 50% of W'.
  • W2_W'bal_Moderate_Fatigue - Time expended when W' bal is between 25% and 50% of W'.
  • W2_W'bal_Work_Moderate_Fatigue - Work produced when W' bal is between 25% and 50% of W'.
  • W3_Above_CP_W'bal_Heavy_Fatigue - Time expended when Power is above CP and W' bal is between 50% and 75% of W'.
  • W3_W'bal_Heavy_Fatigue - Time expended when W' bal is between 50% and 75% of W'.
  • W3_W'bal_Work_Heavy_Fatigue - Work produced when W' bal is between 50% and 75% of W'.
  • W4_W'bal_Severe_Fatigue - Time expended when W' bal is above 75% of W'.
  • W4_W'bal_Severe_Fatigue - Time expended when Power is above CP and W' bal is above 75% of W'.
  • W4_W'bal_Work_Severe_Fatigue - Work produced when Power is above CP and W' bal is above 75% of W'.
  • Watts_Per_Kilogram - Average Power relative to Athlete Weight.
  • Watts:RPE_Ratio - Watts to RPE ratio
  • Work - Total Work in kJ computed from power data
  • Workbeat_stress - Work * Heartbeats / 100000
  • xPace - Normalized pace in min/km or min/mile, defined as the constant pace in flat surface which requires the same LNP
  • xPace_Swim - Normalized Swim Pace in min/100m or min/100yd, defined as the constant pace which requires the same xPowerSwim
  • xPower - xPower is an estimate of the power that you could have maintained for the same physiological 'cost' if your power output had been perfectly constant, similar to NP.
  • xPower_Swim - Swimming power normalized for variations in speed as defined by Dr. Skiba in the SwimScore algorithm

BACK: Special Topics: Overview
BACK: Table of contents

⚠️ **GitHub.com Fallback** ⚠️