## Formulas

Formulas are used at multiple places:

- Filters are formulas. These formulas must result in a Boolean value (True or False) that indicates whether a shot must be included or not.
- Objectives are defined using formulas. You indicate when a shot is good, fair, and poor. Again, these must result in a Boolean value.
- When defining your own columns you provide a formula to compute the value. The formula is applies to each shot to obtain the value for that column for the shot.
- When defining components in dashboard you can specify a filter to indicate what shots should be used.

Formulas are case-sensitive. Always make sure you use the correct case. We largely use the Python syntax for formulas.

### Basics

A formula contains numbers, strings (delimited either by " or by '), Boolean values (true or false) , operators and parenthesis, like you are used to. The following operators are supported:

- Numeric: + - * / // (integer division) ** (power) % (modulo)
- Comparison: < <= == != >= >
- Boolean: and or not

+ can also be used on strings to concatenate them. And you can multiply a string with a number to get that many copies.

So you can write something like: *(1+2)*"Hello"* to get "HelloHelloHello".

You can chain comparison operators. So you can for example write: *150 < TotalDistance < 170*.

A formula can cover multiple lines. This is strongly recommended to improve readability.

### Column values

In the formulas you can refer to the different values for the shot by using the column name without spaces. So for example *TotalDistance* is the value of the Total Distance and *AttackAngle* is the value of the Attack Angle. So you could for example compute your own Roll Distance using

*TotalDistance-CarryDistance*

Check out the list of all possible built-in columns. But there are of course also the user defined columns.

### Macros

Macros are named formulas. You can easily define such macros. Once defined, a macro can be used anywhere where you must provide a formula. Macros can use each other (be careful for recursive use) and you can use multiple macros in a single formula. When MissingClubAngles and MissingClubSpeed are macros, you can use as a formula: *MissingClubAngles or MissingClubSpeed*.

### Conditionals

If you want to return a value dependent on a condition you can use the construction: * result1 if condition else result2*. The condition is evaluated. If true, result1 is returned, else result2. Note that only the result that is returned is computed. Here is an example to indicate the quality of shots:

*"Great" if TotalDistance > 200 else "Poor"*

The result2 part can again be a condition if you want to check further. You can use parenthesis to make sure the order is correct.

### Aggregate values

In your formulas you can use aggregate values of all values in a column. For example, you can ask for the minimum value or the median value. You get these by calling functions for the columns, for example, *TotalDistance.minimum()* or *BallSpeed.median()*. The following functions are available for each column. Here CName is the name of the column.

This the value of the column for the current shot. So it is actually the same as**CName.value()***CName*.The minimum value in the column.**CName.minimum()**The maximum value in the column.**CName.maximum()**The average value in the column.**CName.average()**The mean (same as the average) value in the column.**CName.mean()**The median (middle) value in the column.**CName.median()**The 25% percentile value in the column.**CName.low()**The 75% percentile value in the column.**CName.high()**The given percentile value in the column. perc should lie between 0 and 100.**CName.percentile(perc)**The number of values in the column.**CName.count()**The number of unique (different) values in the column.**CName.uniques()**The sum of the values in the column.**CName.sum()**The index of the shot value among all the values in the column. 0 is the smallest value.**CName.index()**Returns the value at the given index. Index 0 is the smallest value. Negative numbers count from the top. So index -1 is the largest value.**CName.at_index(n)**Returns whether the shot value is among the smallest n values in the column. This is actually the same as**CName.among_smallest(n)***CName < CName.at_index(n)*.Returns whether the shot value is among the largest n values in the column.**CName.among_largest(n)**

Note that default zero values are not taken into account when computing the values, because they are considered missed values. You can though change that in the column definitions. See also the help on missing data.

These methods deal with all values in the column. But often you want to only consider the values of the shots that used the same club. To this end, put **club_** in front of the function name. So, for example, *TotalDistance.club_average()* is the average total distance of all shots with the same club name. As another example, if you want to compute for each shot the deviation in vertical launch angle w.r.t. the median launch angle for that club, you can use

*abs(VerticalLaunchAngle.club_median() - VerticalLaunchAngle)*

*abs* is a function that turns the argument into its absolute value.

You can also consider the values of the shots in the same session. To this end, put **session_** in front of the function name. So, for example, *ClubHeadSpeed.session_maximum()* is the largest club head speed in the session of the shot. As another example, assume the first 10 shots in your sessions are warm-up shots and you do not want to take them into account. You can then create a filter with formula:

*not Time.session_among_smallest(10)*

This will only select the shots for which the time of the shot is not among the smallest 10 in the session.

Note that when you use formulas to define columns, the aggregates are always over ALL shots. The selection is not taken into account (because the new columns are computed once at the start). In a filter though, they apply to the current selection as the filter only deals with shots in the current selection.

### Session and profile data

You can access data about the session of the shot and the profile of the user. For session information, use the variable: * session_name*,

*,*

**session_date***,*

**session_time***,*

**session_range***,*

**session_ball***, and*

**session_rating***. For example, you can create a filter that only shows shots with a Callaway ball by using*

**session_comments***contains(session_ball, "Callaway")*. (contains is a function to return whether the first argument contains the second one; see below).

Two special session related variables are **session_last*** *and * session_first*. These Boolean variables indicate whether the shot is in the last (most recent) session or the first (oldest) session in the current selection. They can for example be used to create a dashboard that shows information about the most recent session only.

For profile information, use the variables: * profile_name*,

*,*

**profile_gender***,*

**profile_handicap***,*

**profile_birthyear***and*

**profile_hand***. The profile variables are only useful when you want to define generic formulas. For example, you can take the hand into account to determine whether a shot is a fade or a draw.*

**profile_device**## Functions and constants

You can use many different functions in your formulas. Functions use only lowercase letters. Here is a list. Note that an argument between square brackets is optional.

**Selection functions**

These are rather special. They allow you to make a selection from different values.

This function evaluates the condition. If it is true result1 is returned, else result2. The result is only computed when needed. Example:**test(condition, result1, result2)***test(RollDistance == 0, 0, TotalDistance/RollDistance)*. Note that the division is only executed when the Roll Distance is not 0. Otherwise an error would occur.You can use the test function also to check multiple conditions. The first condition is evaluated. If it is true the first result is reported. Otherwise, condition2 is evaluated and, if true, result2 is reported, and so on. If no condition is true the default result is reported. default must be present if there is a chance none of the conditions are true. Example:**test(condition1, result1, condition2, result2, ..., [default])***test(contains(ClubType, "Iron"), "Iron Club", contains(ClubType, "Wood"), "Wood Club", "Other Club")*.This function evaluates the expression. When the result is value1, result1 is reported, when it is value2, result2 is reported, and so on. When no value matches the default result is reported. default must exist if there could be no correct value. Otherwise an error is reported. Example:**switch(expr, value1, result1, value2, result2, ..., [default])***switch(ClubType, "Driver", 1.1*TotalDistance, "3 Wood", 1.05*TotalDistance, TotalDistance)*. This will adapt the distance based on the club type.This function evaluates the expression. This should result in an integer. When the result is 0, result0 is reported, when it is 1, result1 is reported, and so on. When no value matches the default result is reported. Example:**choose(expr, result0, result1, ..., default)***choose(round(abs(HorizontalLaunchAngle)), "Straight", "Almost straight", "Not straight")*.

**Numeric functions**

Take the absolute value of the argument. Example:**abs(value)***abs(TotalDeviationAngle)*.Same as**absolute(value)***abs(value)*.Round the value to the nearest integer. The optional precision argument gives the precision (number of decimals).**round(value, [precision])**Returns the largest integer smaller than or equal to the value.**floor(value)**Returns the smallest integer larger than or equal to the value.**ceil(value)**Returns the smallest integer larger than or equal to the value.**ceiling(value)**Returns the sign of the value (1, -1, or 0)**sign(value)**Results the square of the value, so value**2.**square(value)**Returns the square root of the value, so value**0.5.**sqrt(value)**Returns value to the power n, so value**n.**pow(value, n)**Returns value to the power n, so value**n.**power(value, n)**Returns the minimum of the arguments.**min(val1, val2, ...)**Returns the minimum of the arguments.**minimum(val1, val2, ...)**Returns the maximum of the arguments.**max(val1, val2, ...)**Returns the maximum of the arguments.**maximum(val1, val2, ...)**Returns the sum of the arguments.**sum(val1, val2, ...)**Returns the mean (=average) of the arguments.**mean(val1, val2, ...)**Returns the average(=mean) of the arguments.**average(val1, val2, ...)**Convert distances, speeds, and angles. value is the value to convert, from the unit to convert it from, and to the unit to convert it to. For distances (and heights) you can use "m", "yds", and "ft". For speeds "km/h", "mph" and "m/s". For angles "degrees" and "radians".**convert(value, from, to)**The number e. (This is a constant, not a function.)**e**

**String constants and functions**

and**star, cross, checkmark, alert, arrow_up, arrow_down, arrow_left**These are special symbols you can use. You can include other symbols using their unicode and using a string "\uXXXX" where XXXX is the unicode for the character. You find all possible symbols for example here. As an example, if you want to provide a star rating based on distance you could write something like:**arrow_right***3*star if TotalDistance > 200 else 2*star if TotalDistance > 180 else star if TotalDistance > 160 else "-"*. This uses a nested conditional, as described above.Turns the value into a string. Example:**string(value)***string(TotalDeviationAngle) + " degrees"*Same as string(value).**str(value)**Turns the string into a number (float). Example:**float(string)***float("3.1415")*, which is of course the same as 3.1415.Returns the length of the string.**len(string)**Formats the arguments. The string will contain placeholders where the arguments must be filled in. These have the form {n} where n is the number of the argument (1, 2, 3, ...). You can also just use {} to get the next argument. You can add all sorts of formatting types to the placeholders. We use the Python format convention for this. Example:**format(string, arg1, arg2, ...)***format("Shot with club: {} has distance {}", ClubName, TotalDistance)*.**lower(string)**Converts the string to uppercase.*upper(string)*Makes first letter uppercase and all others lowercase.*capitalize(string)*Makes the first letter of each wordt uppercase.*title(string)*Removes leading and trailing white space. The optional chars argument gives the characters that must be removed.*strip(string, [chars])*Removes leading white space. The optional chars argument gives the characters that must be removed.*lstrip(string, [chars])*Removes trailing white space. The optional chars argument gives the characters that must be removed.*rstrip(string, [chars])*Returns whether the string starts with the sub string. When start and end are given the search is limited to that range.*startswith(string, substr, [start, end])*Returns whether the string ends with the sub string. When start and end are given the search is limited to that range.*endswith(string, substr, [start, end])*Returns whether the string contains the sub string. When start and end are given the search is limited to that range.*contains(string, substr, [start, end])*Returns the index of the first occurrences of substr in string. When start and end are given the search is limited to that range.*find(string, substr, [start, end])*Counts the number of occurrences of substr in string. When start and end are given the search is limited to that range.*count(string, substr, [start, end])*Replaces all occurrences of substr in the string with the replacement. When count is given, that is the maximum number of replacements.*replace(string, substr, replacement, [count])*. Splits the string in parts and returns the part with the given index (0 is first part). The optional second argument indicates what to split on. If it is omitted a space is used for splitting. Example part(ClubType,1), will e.g. return "Iron".**part(string, [string], index)**Create one string, joining the arguments, with the separator between them. Use "" as separator to just concatenate the arguments. Example**join(separator, arg1, arg2, ...)***join(": ", ClubHeadSpeed, BallSpeed)*

The following functions deal with lists of strings. A list is a number of strings separated by commas, like "great,bad,poor".

Return whether the list contains the string as one of its items.**list_contains(list, string)**Return whether the list contains one of more of the strings as items.**list_contains_some(list, str1, str2, ...)**Return whether the list contains all of the strings as items.**list_contains_all(list, str1, str2, ...)**Return whether the list contains none of the strings as items.**list_contains_none(list, str1, str2, ...)**

### Tags functions

Each shot has a column called Tags. This is used to store tags and keywords. The following functions deal with tags. Note that a tag is a string.

Returns whether the shot has the indicated tag.**has_tag(tag1)**Returns whether the shot has one or more of the indicated tags.**has_some_tags(tag1, tag2, ...)**Returns whether the shot has all of the indicated tags.**has_all_tags(tag1, tag2, ...)**Returns whether the shot has none of the indicated tags.**has_none_tags(tag1, tag2, ...)**

**Trigonometry function**

When you want to work with angles and distances you will need these functions. Note that angles are always represented in degrees (not radians). So also functions like *sin* and *cos* assume the argument to be in degrees.

Returns the sine of the argument.**sin(degrees)**Returns the cosine of the argument.**cos(degrees)**Returns the tangent of the argument.**tan(degrees)**Returns the arc sine of the argument in degrees.**asin(value)**Returns the arc cosine of the argument in degrees.**acos(value)**Returns the arc tangent of the argument in degrees.**atan(value)**Returns the arc tangent of y/x in degrees.**atan2(y,x)**Converts the degrees to radians.**radians(degrees)**Converts the radians to degrees.**degrees(radians)**Returns pi (3.1415...). Note that this is a constant, not a function.**pi**Returns the length of the vector (x,y).**length(x,y)**Returns the distance between (x1,y1) and (x2,y2).**distance(x1,y1,x2,y2)**

**Date and time functions**

Date are represented by string of the form YYYY-MM-DD, so for example "2022-12-25". Times are of the form "HH:MM:SS" so for example "05:32:06", and use a 24 hour notation. This is also the way the date and time of a shot are stored. It makes it possible to easily compare dates and times. For, example, you can write: *Date > "2022-08-16". *The following functions exist:

Creates the corresponding date string.**date(year, month, day)**Creates the date string for today.**today()**Returns the days in the given date (1-31)**day(date)**Returns the month in the given date (1-12)**month(date)**Returns the day of the week in the given date as a number (0 = Monday, 1= Tuesday, and so on)**weekday(date)**Returns the name of the month in the given date.**month_name(date)**Returns the name of the weekday in the given date.**weekday_name(date)**Formats the date according to the format string. We use the convention of Python here. For example**format_date(date, string)***format_date("2022-12-25", "%d %b, %Y")*returns "25 Dec, 2022". For details on the format see e.g. this page.Creates the corresponding time string.**time(hour, minute, second)**Creates the time string for this moment.**now()**Returns the hours in the given time (0-23)**hour(time)**Returns the minutes in the given time (0-59)**minute(time)**Returns the seconds in the given time (0-59)**second(time)**Formats the time according to the format string. We use the convention of Python here. For example**format_time(time, string)***format_time("15:12:23", ""%I:%M %p"")*returns "03:12 PM". For details on the format see e.g. this page.Returns whether the shot has a date in the given period. period is a one of the following strings: "today", "yesterday", "last 7 days", "previous 7 days", "last 30 days", "previous 30 days", "year to date", "last year", "previous year".**in_period(period)**

### Miscellaneous** functions**

The following functions deal with specific column values. These are in particular useful in filters.

Returns whether the shot has any tags.**has_tags()**Returns whether the shot has an associated video.**has_video()**Returns whether the shot is in the club set with the given name. (name is a string!)**in_clubset(name)**Returns the category of the current club as a string, e.g. "Driver", "Wood", "Hybrid", etc.**club_category()**Returns whether a column with the given name exists. Note that the name is a string and must contain the required spaces. This is typically used to create generic formulas that work for different devices that might have different columns. For example, to test whether there is a column with name Back Spin, use**column_exists(name)***column_exists("Back Spin")*.Similar to previous function, but only check for metrics, that is, columns that are numbers.**metric_exists(name)**