- What is expreval and what do I use it for ?
- Syntax
- Functions
- Examples
- Example 1: TSS from activity data
- Example 2: Performance Management Chart
- Example 3: Week Duration Analysis
- Example 4: Change laps according to changepoint detection
- Example 5: Extract FTP from an activity
- Example 6: Quadrant analysis
- Example 7: User-defined functions and loops
- Example 8: Monthly duration
- Example 9: PMC with TSS=IF^4*duration

Expreval gives you the possibility to access your data in an API-like fashion and process it with predifined functions. Resulting data is then formatted in a way that makes it easily importable into an analysis or plotting software such as MATLAB or Octave.

There are 5 main types of access that you have to choose to begin with:

- Activity
- One single activity refered by its id. You can then process its streams of data or its properties.
- Activities
- All activities in a date range. You can then process their properties (but streams are not available).
- PMC
- Access daily TSS, CTL, ATL, TSB metrics in a date range.
- Week_summaries
- Access weekly summarized data (Monday through Sunday) such as activity count, summed duration, or total distance.
- Metrics
- Access metric data in a date range for the metric selected in the dropdown list.

Choosing none of the about gives you a basic access to the functions defines for all types.

In the field called expression, you can put any number of expressions with each one on its own line. Expression parsing is not sensitive to spaces.

Line ending with a semi-colon (;) will be evaluated but not produce any output. Everything after the semi-colon is considered a comment and not evaluated. On the contrary, lines without a semi-colon will have their output displayed in the output section.

The functions plot_line and plot_scatter can be used to quickly visualize a data set. They expect a name which is the name of each series separated by underscores. One x series is to be provided while any number of y series can be used for that same x serie.

Quadrant analysis example below shows how to do this. Note that plotting can be relatively slow when a lot of points are to be handled. These functions are provided for quick debug but data export combined with plotting softwares should be prefered for more complex analysis.

Users can define their own functions on a single line using a MATLAB-like inline definition. The definition is done similarly to the definition of a variable (`name = ...`

) but with an @(...) listing the arguments after the equal sign. User-defined functions can then be used as any other functions, or by referencing to their name with an @. See for instance the loop example below.

In your expressions, you can use tokens of the following types:

- Numbers
- Integers or fractional numbers with the dot (.) as decimal separator (eg 1.25).
- Arrays
- Arrays created by separating numbers with commas (,) and placing the whole inside squared brackets (eg [1, 2]).
- Associative arrays
- Arrays created by separating key and numbers pairs (using colon, :) with commas (,) and placing the whole inside curly brackets (eg {"CP":300,"Wbal":20e3}}).
- Strings
- Text characters enclosed inside double quotes (") (eg "String").
- Operators
- Math operators (addition +, substraction -, multiplication *, division /, power ^, modulo %) and comparison operators (greater >, smaller <, greater or equal >=, smaller or equal <=, equal ==, not equal !=).
- Parenthesis
- Parenthesis with matching left and right pairs (eg 1*(1+2)).
- Constants
- Math or PHP constants beginning with M_ or PHP_ (see http://php.net/manual/en/math.constants.php) (eg M_PI or M_E).
- Functions
- Functions with operands in parenthesis (see functions list below) (eg max([1, 2])). Can also be user defined as
`f=@(i,j) i+j`

. - Function references
- Functions referenced to be used inside another function. Function name preceeded by @ and without argument (eg @max).

Available functions are:

Search:Name | Signature | Available | Description | Notes |
---|---|---|---|---|

plot_line | Array plot_line(String chart_name, Array x_values, Array y_values) | All | plots x_values vs y_values | |

plot_dateline | Array plot_line(String chart_name, Array x_values, Array y_values) | All | plots x_values (as dates) vs y_values | |

plot_scatter | Array plot_scatter(String chart_name, Array x_values, Array y_values) | All | plots x_values vs y_values | |

input | Mixed input(String param) | All | returns value of the parameter selected at the input | Param: id | start (timestamp) | end (timestamp) | metric |

save | Array save(String var_name, Mixed value) | All | saves the value allowing later recalls | see also = operator |

recall | Array recall(String var_name) | All | recalls value saved by the save function | or simply write the variable name |

round | Array round(Array number [, Number precision]) | All | rounds a number to the nearest integer or precision if given | |

ceil | Array ceil(Array number) | All | rounds up a number to the next integer | |

floor | Array floor(Array number) | All | rounds down a number to the previous integer | |

abs | Array abs(Array number) | All | returns the absolute value of a number | |

sin | Array sin(Array angle_rad) | All | returns the sine of an angle in radian | |

cos | Array cos(Array angle_rad) | All | returns the cosine of an angle in radian | |

tan | Array tan(Array angle_rad) | All | returns the tangent of an angle in radian | |

asin | Array asin(Array number) | All | returns the arc sine in radian | |

acos | Array acos(Array number) | All | returns the arc cosine in radian | |

atan | Array atan(Array number) | All | returns the arc tangent in radian | |

sinh | Array sinh(Array number) | All | returns the hyperbolic sine | |

cosh | Array cosh(Array number) | All | returns the hyperbolic cosine | |

tanh | Array tanh(Array number) | All | returns the hyperbolic tangent | |

asinh | Array asinh(Array number) | All | returns the hyperbolic arc sine | |

acosh | Array acosh(Array number) | All | returns the hyperbolic arc cosine | |

atanh | Array atanh(Array number) | All | returns the hyperbolic arc tangent | |

deg2rad | Array deg2rad(Array deg) | All | converts degrees to radians | |

rad2deg | Array rad2deg(Array rad) | All | converts radians to degrees | |

exp | Array exp(Array number) | All | returns the exponential of a number | |

log | Array log(Array number [, Number base]) | All | returns the log of a number in base (default M_E corresponding to ln) | |

mean_max | Array mean_max(Array equallyspaced_stream, Array steps[, Number step]) | All | returns mean maximal values of the equallyspaced_stream (with points spaced by step, or 1 second by default) at steps (negative values in stream are treated as zero) | |

max_filter | Array max_filter(Array number, Number max) | All | returns the original array with values higher than max truncated at max | |

min_filter | Array min_filter(Array number, Number min) | All | returns the original array with values lower than min truncated at min | |

sort | Array sort(Array number) | All | sorts an array (smallest to largest) | |

rsort | Array rsort(Array number) | All | reverse-sorts array (largest to smallest) | |

asort | Array asort(Array number) | All | sorts array keeping key-value relationships (smallest to largest values) | |

arsort | Array arsort(Array number) | All | reverse-sorts array keeping key-value relationships (largest to smallest values) | display of associative array is always sorted (but for internal computations it will be reverse-sorted) |

ksort | Array ksort(Array number) | All | sorts an array (smallest to largest keys) | |

krsort | Array krsort(Array number) | All | reverse-sorts array (largest to smallest keys) | display of associative array is always sorted (but for internal computations it will be reverse-sorted) |

max | Number max(Array array) | All | max number in array | |

min | Number min(Array array) | All | min number in array | |

sum | Number sum(Array array) | All | sum of numbers in array | |

count | Number count(Array array) | All | number of elemets in array | |

median | Number median(Array array) | All | median of numbers in array | |

avg | Number avg(Array array) | All | average of numbers in array | |

std_dev | Number std_dev(Array array [, Number sample]) | All | standard deviation of numbers in array (sample=1 to divide by N-1) | |

index | Array index(Array array, Mixed indexes) | All | array of the elements at indexes (associative index, positive from 0 to N-1, or negative from -N to -1) | |

select | Array select(Array array, Array boolean) | All | array of the elements with corresponding boolean to 1 | |

array_diff | Array array_diff(Array complete_set, Array exclude_set) | All | returns the array with elements whose value is in complete_set but not in exclude_set | |

array_diff_key | Array array_diff(Array complete_set, Array exclude_set) | All | returns the array with elements whose key is in complete_set but not in exclude_set | |

array_intersect | Array array_intersect(Array set1, Array set2) | All | returns the array with elements whose value is in set1 and set2 | |

array_intersect_key | Array array_intersect_key(Array set1, Array set2) | All | returns the array with elements whose key is in set1 and set2 | |

array_reverse | Array array_reverse(Array array) | All | returns the array with elements in reversed order | |

array_unique | Array array_unique(Array array) | All | returns the array with duplicates removed | |

array_merge | Array array_merge(Array array1, Array array2) | All | array resulting of concatenating array1 and array2 | |

array_combine | Array array_combine(Array keys, Array values) | All | associative array from 2 non-associative arrays | |

array_keys | Array array_keys(Array array) | All | returns the keys of an associative array | |

array_values | Array unique(Array array) | All | returns the values of an associative array | |

array_flip | Array unique(Array array) | All | flipes an associative array (keys become values, and values become keys) | |

array_groupby | Array array_groupby(FuncRef @func, Array array) | All | applies function func to all keys in array and group those having the same result | |

array_reduce | Array array_reduce(FuncRef @func, Array array) | All | applies reduce function to array values | |

array_map | Array array_map(FuncRef @func, Array params) | All | applies function func to all elements in params | |

delay | Array delay(Array array, Number delay) | All | delay/shift array (positive towards the right, negative towards the left; padded with zeros) | |

integ | Array integ(Array array[, Number step]) | All | partial integral from beginning up to index i-1 (ouput of length N+1; also known as cumsum) | |

diff | Array diff(Array array[, Number step]) | All | finite difference from beginning up to index i-1 (ouput of length N-1) | |

interp1 | Array sum(Array timepoints, Array known, Number step) | All | linear interpolation of knwon values (equally spaced by step) at defined timepoints | |

sma_filter | Array sma_filter(Array array, Number n_step) | All | simple moving average of n_step on array (equally spaced) | |

expweighted_sma_filter | Array expweighted_sma_filter(Array array, Number n_step, Number attenuation) | All | exponentially-weighted simple moving average of n_step on array (equally spaced) | |

ema_filter | Array ema_filter(Array array, Number n_step) | All | exponential moving average of n_step on array (equally spaced) | |

smm_filter | Array sma_filter(Array array, Number n_step) | All | simple moving median of n_step on array (equally spaced) | |

changepoints | Array changepoints(Array stream, Number min_mean) | All | changepoints detection in stream | |

distribution | Array distribution(Array stream, Number bin_size) | All | splits stream into distribution with bins of size bin_size | |

linrange | Array linrange(Number start, Number end [, Number step]) | All | generates array from start to stop with step (default +1/-1 depending on direction) | see "range" implementing PHP range function. linrange kept for backward compatibility |

range | Array range(Number start, Number end [, Number step]) | All | generates array from start to stop with step (default +1/-1 depending on direction) | |

time_range | Array time_range(Number start, Number end [, String step]) | All | generates array from start to stop timestamps with step (default +1/-1 day depending on direction) | start and stop are timestamps, use strtotime if needed. step is everything accepted as first parameter of strtotime |

ones | Array ones(Number length) | All | generates array of ones of a certain length | |

rand | Array rand([Number min, Number max [, Number array_length]]) | All | generates array of random values (defaults: 1 element between min=0 and max=1) | |

date | Array date(String format, Array timestamps) | All | formats timestamp according to PHP date function | |

strtotime | Array strtotime(Array date_strings [, Array now]) | All | retrieve timestamps from date strings according to PHP strtotime function | |

if | Mixed if(Mixed condition, Mixed if_true, Mixed if_false) | All | chooses between if_true and if_false values depending on condition | when condition, if_true, if_false are arrays of same length, condition is evaluated on each element separately |

config | Mixed config(String var_part1 [, String var_part2 [, String var_part3]]) | All | gets config property | Possible calls: 1:alert_types|activity_types|BT_TSS_thresholds|metric_types|training_zones | 1:activity_types|metric_types => 2:<type> | 1:MMP_steps | 1:MMP_worldrecord => 2:men|women => 3:swim|bike|run | 1:BT_TSS_thresholds => 2:<threshold_name> | 1:training_zones => 2:<zone_name> => 3:FTP|HR|TRIMP | 1:alert_types => 2:<alert_name> => 3:threshold|short_desc|long_desc |

PMC_const | Number PMC_const(String name) | All | retrieves PMC constant | Constant list: CTL_ema_tau | ATL_ema_tau |

PD_model_parameters | Array PD_model_parameters(String model, String activity_type, Array MMP_steps, Array MMP) | All | retrieves PD parameters from a model for MMP values at MMP_steps in an activity of sport activity_type | Model list: Monod_2P | Monod_3P | Morton | Alvarez | WardSmith | Veloclinic | ExtendedCP |

PD_model_values | Array PD_model_values(String model, String activity_type, Array parameters, Array timepoints) | All | retrieves NP values at timepoints from a PD model in an activity of sport activity_type | Model list: Monod_2P | Monod_3P | Morton | Alvarez | WardSmith | Veloclinic | ExtendedCP |

activity | Number activity([String name]) | Activity | retrieves activity property | Properties list: title | type | subtype | race | metrics_origin_auto | metrics_origin | TSS | IF | VI | NP | AvgP | TRIMP | duration | moving_time | distance | elevation | timestamp | RPE | calories | HR_max | HR_avg | speed_max | speed_avg | description | comment | FTP | FTPower | Wprime | LTHR | athlete_height | athlete_weight | TSS_coeff |

activity_stream | Array activity_stream(String name[, Array times]) | Activity | retrieves activity stream (optionally for defined timepoints only) | Stream list (if available): time | distance | altitude | velocity_smooth | heartrate | cadence | watts | grade_smooth |

alter_activity_stream | Array alter_activity_stream(String name, Array timepoints, Array new_vals) | Activity | alters (already defined) activity stream | Stream list (if available): distance | altitude | velocity_smooth | heartrate | cadence | watts | grade_smooth |

alter_new_activity_stream | Array alter_new_activity_stream(String name, Array new_vals) | Activity | creates non-existing activity stream | Stream list (if available): distance | altitude | velocity_smooth | heartrate | cadence | watts | grade_smooth |

equallyspaced_streams | Array equallyspaced_streams(String stream, Number step [, Number start [, Number end [, String x_stream]]]) | Activity | retrieves equally spaced stream by step from start to end for x_stream (default is time with step in seconds, set start=0 and end=0 to take full stream) | |

activity_laps | Array activity_laps() | Activity | retrieves activity laps (start time) | |

alter_activity_laps | Array alter_activity_laps(Array new_starts) | Activity | alters activity laps with new start times | |

NP_on_interval | Array NP_on_interval(Number start_time, Number, end_time) | Activity | retrieves metrics on interval | returned array contains [NP, IF, AvgP, VI] |

NP_from_power | Array NP_from_power(Array power) | Activity | converts power to NP units (power or pace) | |

power_from_NP | Array power_from_NP(Array NP) | Activity | converts NP units (power or pace) to power | |

power_from_speed | Array power_from_speed(Array speed [, Array slope [, Array distance [, Array inital_speed]]]) | Activity | estimates power from speed and slope | |

activity | Array activity([String name]) | Activities | retrieves activity property | Properties list: id | title | type | subtype | race | metric_origin_auto | metrics_origin | TSS | IF | VI | NP | AvgP | TRIMP | duration | moving_time | distance | elevation | timestamp | RPE | calories | HR_max | HR_avg | speed_max | speed_avg | description | comment |

daily_sum | Array activity(String name[, String equals]) | Activities | retrieves daily sum of property (count of activities equal if name = type | subtype) | Properties list: type | subtype | TSS | IF | VI | NP | AvgP | TRIMP | duration | moving_time | distance | elevation | RPE | calories | HR_max | HR_avg | speed_max | speed_avg |

PMC | Array PMC(String name) | PMC | retrieves PMC data | Properties list: day | raceday | TSS | ATL | CTL | TSB | TMonotony | TStrain | TSS_<type> | ATL_<type> | CTL_<type> | TSB_<type> | TMonotony_<type> | TStrain_<type> |

week_summary | Array week_summary(String name) | Week_summaries | retrieves week_summary data | Properties list: monday | sunday | count | duration | distance | count_<type> | duration_<type> | distance_<type> | MMP_<type> |

metric | Array metric(String name) | Metrics | retrieves metric data | Properties list: timestamp | value |

Note: This results to slightly different values than the reported ones as it does not account for non-moving time. It also calls power_fom_speed for all seconds and not at the original timepoints. Nevertheless, values are very close in practice.

vel = smm_filter(ema_filter(smm_filter(equallyspaced_streams("velocity_smooth", 1), 50), 30), 50); save filtered velocity vel_del = delay(vel, 1); delay velocity for acceleration and distance in power_from_speed grade = smm_filter(ema_filter(smm_filter(equallyspaced_streams("grade_smooth", 1) / 100, 50), 90), 50); save filtered grade (note: convert percents by dividing by 100) power = power_from_speed(vel, grade, vel_del, vel_del); estimate power NP = avg(min_filter(sma_filter(power, 30), 0)^4)^(1/4) IF = NP/athlete("FTPower") TSS = IF^2 * activity("moving_time") / 3600 * activity("TSS_coeff")

TSS = PMC("TSS"); ATL = ema_filter(TSS, PMC_const("ATL_ema_tau")) CTL = ema_filter(TSS, PMC_const("CTL_ema_tau")) TSB = CTL - ATL

durations = week_summary("duration") / 3600; conversion from seconds to hours avg_dur = avg(durations) median_dur = median(durations) stddev_dur = std_dev(durations)

watts = equallyspaced_streams("watts", 1); retrieve watts changes = changepoints(watts, avg(watts)); find changepoints ; apply changes (!WARNING! this will change laps data without any undo possible) alter_activity_laps(changes)

MMP = activity("MMP"); retrieve activity MMP MMP_steps = index(config("MMP_steps"), linrange(0, count(MMP) - 1)); find MMP steps and crop to actual length PD_model_parameters("Morton", "bike", MMP_steps, MMP)

sel = activity_stream("cadence") > 0; CPV = select(activity_stream("cadence"), sel) / 60 * 0.1725 * 2 * M_PI; Circumferential Pedal Velocity (CPV, m/s) AEPF = select(activity_stream("watts"), sel) / CPV; Average Effective Pedal Force (AEPF, N) plot_scatter("CPV_AEPF", CPV, AEPF)

Note: Multiple y series would look like: `plot_scatter("x_y1_y2", x, [y1, y2])`

.

f = @(i,j) i+2*j; ; f(i=1,j=2)=1+2*2=5 f(1,2) ; loop over pairs [(i=1,j=2), (i=10,j=20)], f(i,j) = [5, 50] array_map(@f, [[1,10], [2,20]])

time_array = time_range(input("start"), input("end")); get input time range (1 point per day) duration_array = daily_sum("duration")/3600; daily sum (in seconds, converted to hours) ; define group and reduce functions (see comments below) group_func = @(t) date("Ym",t); group function: Year+month => grouped per month reduc_func = @(a) sum(a); reduce function: sum => total duration in the period (eg month) ; group and reduce (see comments below) period_sum = array_reduce(@reduc_func, array_groupby(@group_func, array_combine(time_array, duration_array))); plot_line("periodIdx_duration", range(1,count(period_sum)), array_values(period_sum))

The most complicated looking line of code performs the following functions in order:

- array_combine: creates an array looking like {timestamp: duration}={1478896495:2.3444,...} with one pair for each day
- array_groupby: groups all that are in the same month transforming it to {"201601":[2.3444,3.45,...],"201602":[5.3,0,...],...}
- array_reduce: sums all sub-array that correspond to the same month, finally getting {"201601": 15.2, "201602": 23.335, ...}

Other variations of this can include:

- group_func = @(t) date("Y",t); sum over periods of years
- group_func = @(t) date("N",t); sum all same days of the week (find out which day of the week you are training the most, use ksort probably before plotting)
- reduc_func = @(a) avg(a)*7; make an average per week instead of a sum
- ... or combinations of those ... or many others of your imagination ...

One can show mathematically that he TSS as defined by Coggan originally is not additive, by which it is meant that cutting your activity in 2 will lead to a different inpact on your PMC than leaving it as one (TSS(activity1+activity2) /= TSS(activity1)+TSS(activity2)). A short proof by Daniel even ended in the GoldenCheetah github repository after a discussion with Mark Liversedge (responsible of GoldenCheetah). Some people refer to it as "free TSS while coasting" or have complicated rules regarding when you should cut activities or not (look for burrito rule on the wattage forum if interested). One other approach is to look at what it would take to make the TSS additive. It is simple to show that it can be made by actually taking the same exponent for the IF in the TSS formula as the one used for the power in the NP formula.

For an exponent of 1, one gets simply that the NP is equal to the average power and the TSS is proportional to the expended energy. If you think however that the exponent 4 of the NP formula correctly models the body stresses and still want additivity, you would go to an exponent of 4. Whether this makes sense or not is up your judgment and using the original TSS formula is perhaps easier to compare with litterature. But if you want to try and compare, use the script below (selecting activities and a date range)...

; compute TSS with different IF exponents ifs4 = array_combine(activity("timestamp"), activity("IF") ^ 4 * activity("duration") / 3600 * 100); ifs2 = array_combine(activity("timestamp"), activity("IF") ^ 2 * activity("duration") / 3600 * 100); ; combine with zeros for days that have no activities (assumes no activity at midnight) time = time_range(input("start"), input("end")); act4 = array_combine(array_merge(time, array_keys(ifs4)), array_merge(0 * time, ifs4)); act2 = array_combine(array_merge(time, array_keys(ifs2)), array_merge(0 * time, ifs2)); ; sum over each days group_func = @(t) date("Ymd",t); reduc_func = @(a) sum(a); TSS4 = array_values(array_reduce(@reduc_func, array_groupby(@group_func, act4))); TSS2 = array_values(array_reduce(@reduc_func, array_groupby(@group_func, act2))); ; compute PMC starting from TSS ATL4 = ema_filter(TSS4, PMC_const("ATL_ema_tau")); CTL4 = ema_filter(TSS4, PMC_const("CTL_ema_tau")); TSB4 = CTL4 - ATL4; ATL2 = ema_filter(TSS2, PMC_const("ATL_ema_tau")); CTL2 = ema_filter(TSS2, PMC_const("CTL_ema_tau")); TSB2 = CTL2 - ATL2; ; plots for comparison plot_dateline("date_tss4_tss2", time * 1000, [TSS4, TSS2]) plot_dateline("date_atl4_ctl4_tsb4", time * 1000, [ATL4, CTL4, TSB4]) plot_dateline("date_atl2_ctl2_tsb2", time * 1000, [ATL2, CTL2, TSB2]) plot_dateline("date_tsb4_tsb2", time * 1000, [TSB4, TSB2])