- Overview
- Literals
- Zero values
- Operators
- Operator precedence reference
- Built-in functions
- Regular expressions
Overview
Sperta has its own expression language called SEL
. You can write expressions with SEL combining features, literals, and operators to output a value.
If you have worked with SQL, Python, or any programming language, the syntax should look very familiar to you.
Unlike SQL or Python, SEL is strongly typed. This means Sperta can detect data type mismatches in addition to syntax errors as soon as you save the expression. Take a look at Features to learn what features can be used in an expression and their data types.
SEL is fast, orders of magnitude faster than alternative implementations using sandboxed JavaScript.
Literals
Literals are values written exactly as it's meant to be interpreted. For example, if you have an expression fraud_score > 0.9
, then fraud_score
is a feature and 0.9
is a literal.
Here are some examples:
- Integer literals
1
-2
- Double literals
-1.5
1.
7.3e4
7.3E4
- String literals
"john@example.com"
'john@example.com'
"""Some comments"""
'''Some comments'''
- Boolean literals
true
false
- List literals. The values must be of the same data type:
[1, 2, 3]
["John", "Jessie", "Mark"]
- Map literals. The keys must be of the same data type (
Integer
,Boolean
,String
). The values must be of the same data type too: {"US": 0.95, "MX": 0.85}
Zero values
Sperta automatically assigns a zero value to missing input features based on the data type. This way, expressions are less likely to fail since there are no null values. The zero values for each data type are:
0
forInteger
0.0
forDouble
""
(empty string) forString
false
forBoolean
timestamp("1970-01-01T00:00:00Z")
(Unix epoch) forTimestamp
{}
(empty JSON) forJSON
[]
(empty list) forList
Operators
Operators in SEL perform mathematical, relational, or logical operations. The following operators are supported:
- Logical operators:
not
,and
,or
- When used together in an expression,
not
has the highest precedence andor
has the lowest precedence. - For example, in
3 > 2 and not 2 > 1 or 4 > 3
,not 2 > 1
is evaluated first. - If you are not sure about the precedence, you can use
(
or)
to override the precedence and make it more clear. You can rewrite the previous expression to(3 > 2 and (not 2 > 1)) or 4 > 3
- Negation operator:
-
- For example, if a feature
score
has a value of0.9
, then-score
outputs a value of-0.9
- Arithmetic operators (precedence from high to low):
*
,/
,%
,+
,-
- For example, the result of
18 / 2 * 3 + 1
is4
- If you are not sure about the precedence, you can use
(
or)
to override the precedence and make it more clear. You can rewrite the previous expression to(18 / (2 * 3)) + 1
%
performs a modulo operation. For example, the result of10 % 3
is1
.- Since SEL is strongly typed, you can’t mix
Integer
andDouble
in arithmetic operations. Sperta will report an error for this expression:4.0 * 3
. Instead, first convert them to the same data type:4.0 * double(3)
- You can use
+
to concatenate strings, e.g.first_name + " " + last_name
- You can also use
+
and-
between timestamps and durations: timestamp("2024-02-16T05:13:45Z") - timestamp("2024-02-15T05:13:45Z") > duration("23h")
timestamp("2024-02-16T05:13:45Z") + duration("2h") < timestamp("2024-02-16T08:13:45Z")
duration("2h") - duration("30m")
- Relational operators:
==
,!=
,>
,>=
,<
,<=
- You can use
==
and!=
to compare booleans and strings. You can use all relational operators to compare numbers. - Compare booleans:
device_spoofed == true
- Compare strings:
country_code != "US"
- Compare numbers:
fraud_score >= 0.9
- Compare timestamps:
timestamp("2024-02-16T00:00:00Z") < timestamp("2024-02-16T01:00:00Z")
- Compare durations:
duration("2h") > duration("80m")
- Inclusion operators:
in
- Use inclusion operators to test the inclusion of a value in a
List
or aMap
- Example for
in
:billing_country_code in ["US", "MX"]
- Note that
not in
is not yet supported, so instead, you can writenot (email_domain in ["111.com", "freemail.com"])
- You can’t use
in
to check if any elements of a list exists in another list. Instead, use theexists()
function on the list. For example, ifcountries
is a list, the expression will look likecountries.exists(country, country in ["US", "MX"])
- Conditional operators:
?:
- Similar to conditional steps, the conditional operators allow you to output a feature based on a condition.
- For example,
score > 700 ? 200.0 : 100.0
will return200.0
if the score is higher than700
and return100.0
otherwise. - You can even output a feature using the conditional operator, which makes it more flexible than a conditional step. For example,
score > 700 ? requested_amount : 100.0
. - You can also use the conditional operators to assign default values to features. For example:
model_score == 0.0 ? 0.5 : model_score
If the precedence rules are not clear to you, take a look at the technical reference of operator precedence.
Operator precedence reference
Precedence | Operator | Description | Associativity | Example Usage |
---|---|---|---|---|
1 |
| Functions | Left-to-right |
|
2 |
| Negation | Right-to-left |
|
| Logical NOT | Right-to-left |
| |
3 |
| Multiplication | Left-to-right |
|
| Division | Left-to-right |
| |
| Modulo | Left-to-right |
| |
4 |
| Addition | Left-to-right |
|
| Subtraction | Left-to-right |
| |
5 |
| Relations | Left-to-right |
|
| Inclusion test | Left-to-right |
| |
6 |
| Logical AND | Left-to-right |
|
7 |
| Logical OR | Left-to-right |
|
8 |
| Conditional | Right-to-left |
|
Built-in functions
Name | Type | Description | Example Usage | Example Result |
---|---|---|---|---|
size | (string) -> integer string.() -> integer | String size |
|
|
(list(A)) -> integer list(A).() -> integer | List size |
|
| |
(map(A, B)) -> integer map(A, B).() -> integer | Map size |
|
| |
contains | string.(string) -> boolean | Tests whether the string operand contains the substring. |
|
|
lower | string.(string) -> string | Converts a string to lower case |
|
|
upper | string.(string) -> string | Converts a string to upper case |
|
|
startsWith | string.(string) -> boolean | Tests whether the string operand starts with the substring. |
|
|
endsWith | string.(string) -> boolean | Tests whether the string operand ends with the substring |
|
|
substring | string.(start, end) -> string | Returns a substring with an inclusive start range and exclusive end range. The index starts at 0. |
|
|
format | string.(list) -> string | Formats the specified value(s) and insert them inside the string's placeholder. |
|
|
matches | string.(string) -> boolean | Tests whether the string matches the regular expression. |
|
|
bool | (string) -> boolean | Converts a string to a boolean |
|
|
double | (integer) -> double | Converts an integer to a double |
|
|
(string) -> double | Converts a string to a double |
|
| |
int | (double) -> integer | Converts a double to an integer. Rounds toward zero. Errors if the result is out of range. |
|
|
(string) -> integer | Converts a string to an integer |
|
| |
(timestamp) -> integer | Converts a timestamp to an integer in seconds since Unix epoch. |
|
| |
string | (integer) -> string | Converts an integer to a string |
|
|
(double) -> string | Converts a double to a string |
|
| |
(boolean) -> string | Converts a boolean to a string |
|
| |
(timestamp) -> string | Converts a timestamp to a string |
|
| |
(duration) -> string | Converts a duration to a string |
|
| |
timestamp | (string) -> timestamp | Converts a string to a timestamp |
| - |
duration | (string) -> duration | Converts a string to a duration; Supports the following suffixes: "h" (hour), "m" (minute), "s" (second), "ms" (millisecond), "us" (microsecond), and "ns" (nanosecond). Duration strings may be zero, negative, fractional, and/or compound. Examples: "0", "-1.5h", "1m6s” |
| - |
time.now | () -> timestamp | Get the current timestamp |
|
|
getDate | timestamp.() -> integer | Get day of month from the timestamp in UTC, one-based indexing |
|
|
Get day of month from the date with timezone, one-based indexing |
|
| ||
getDayOfWeek | timestamp.() -> integer | Get day of week from the timestamp in UTC, zero-based, zero for Sunday |
|
|
Get day of week from the date with timezone, zero-based, zero for Sunday |
|
| ||
all | list.(element, predicate) -> boolean | Tests whether a predicate holds for all elements of a list |
|
|
exists | list.(element, predicate) -> boolean | Tests whether a predicate holds for any elements of a list |
|
|
exists_one | list.(element, predicate) -> boolean | Tests whether a predicate holds for exactly one element of a list |
|
|
map | list.(element, expression) -> list | Transforms a list by taking each element and transforming it with an expression |
|
|
filter | list.(element, predicate) -> list | Returns a sublist where the predicate evaluates to |
|
|
numeric.round | (double) -> integer | Rounds the double to the nearest integer |
|
|
numeric.pow | (double, double) -> double | Calculate x to the power y |
|
|
math.least | (integer…) -> integer | Find the smallest integer among the arguments or a list |
|
|
(double…) -> double | Find the smallest double among the arguments or a list |
|
| |
math.greatest | (integer…) -> integer | Find the biggest integer among the arguments or a list |
|
|
(double…) -> double | Find the biggest double among the arguments or a list |
|
|
Regular expressions
The matches()
function allows you to test a regular expression, and it returns true
if the test passes.
There are two ways to use backslash as the escape character:
- Use
\\
instead of\
. For example, instead ofs.matches("^trashymail\.(com|net)$")
, you need to uses.matches("^trashymail\\.(com|net)$")
- Use a raw string with an
r
prefix for the regular expression and use\
. For example,s.matches(r"^trashymail\.(com|net)$")