chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
# Expressions
|
||||
|
||||
The `expressions` property specifies _new_ columns in Perspective that are
|
||||
created using existing column values or arbitrary scalar values defined within
|
||||
the expression. In `<perspective-viewer>`, expressions are added using the "New
|
||||
Column" button in the side panel.
|
||||
|
||||
Expressions are strings parsed by Perspective's expression engine (based on
|
||||
[ExprTK](https://github.com/ArashPartow/exprtk)). Column names are referenced by
|
||||
wrapping them in double quotes, e.g. `"Sales"`:
|
||||
|
||||
<div class="javascript">
|
||||
|
||||
```javascript
|
||||
const view = await table.view({
|
||||
expressions: {
|
||||
"Profit Ratio": '"Profit" / "Sales"',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</div>
|
||||
<div class="python">
|
||||
|
||||
```python
|
||||
view = table.view(expressions={'Profit Ratio': '"Profit" / "Sales"'})
|
||||
```
|
||||
|
||||
</div>
|
||||
<div class="rust">
|
||||
|
||||
```rust
|
||||
let view = table.view(Some(ViewConfigUpdate {
|
||||
expressions: Some(Expressions([
|
||||
("Profit Ratio", "\"Profit\" / \"Sales\"".into())
|
||||
].into_iter().collect())),
|
||||
..ViewConfigUpdate::default()
|
||||
})).await?;
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Type Conversion and Coercion
|
||||
|
||||
Perspective expressions are strongly typed — each column and literal has a fixed
|
||||
type, and most operators require matching types on both sides. To work across
|
||||
types, use the conversion functions:
|
||||
|
||||
| Function | Description |
|
||||
| --------------- | ------------------------------------------------------------ |
|
||||
| `to_string(x)` | Convert any type to string |
|
||||
| `to_integer(x)` | Convert to integer (null if not parsable) |
|
||||
| `to_float(x)` | Convert to float (null if not parsable) |
|
||||
| `to_boolean(x)` | Convert to boolean (truthy/falsy) |
|
||||
| `integer(x)` | Alias for `to_integer(x)` |
|
||||
| `float(x)` | Alias for `to_float(x)` |
|
||||
| `datetime(x)` | Construct a datetime from a POSIX timestamp (ms since epoch) |
|
||||
| `date(y, m, d)` | Construct a date from year, month, day |
|
||||
|
||||
### How coercion works
|
||||
|
||||
Perspective does not implicitly coerce types. For example, you cannot directly
|
||||
add an `integer` to a `float` — you must cast one side explicitly. Similarly,
|
||||
`datetime` and `date` values are not numeric: to perform arithmetic on them, you
|
||||
must first convert to a numeric representation, do the math, then convert back.
|
||||
|
||||
Internally, `datetime` values are stored as milliseconds since the Unix epoch
|
||||
(1970-01-01T00:00:00Z). Converting a `datetime` to a `float` yields this
|
||||
millisecond timestamp, and `datetime()` accepts a millisecond timestamp to
|
||||
produce a `datetime`.
|
||||
|
||||
### Example: offsetting a datetime by 7 days
|
||||
|
||||
This expression takes a `"Shipped Date"` column, converts it to its
|
||||
millisecond-epoch representation, adds 7 days worth of milliseconds (7 ×
|
||||
24 × 60 × 60 × 1000 = 604800000), and converts the result back
|
||||
to a `datetime`:
|
||||
|
||||
```
|
||||
// Due Date
|
||||
datetime(float("Shipped Date") + 604800000)
|
||||
```
|
||||
|
||||
## Operators
|
||||
|
||||
Standard arithmetic and comparison operators are supported:
|
||||
|
||||
| Operator | Description |
|
||||
| -------------------------------- | ----------- |
|
||||
| `+`, `-`, `*`, `/` | Arithmetic |
|
||||
| `%` | Modulo |
|
||||
| `==`, `!=`, `<`, `>`, `<=`, `>=` | Comparison |
|
||||
| `and`, `or`, `not` | Logical |
|
||||
| `if ... else ...` | Conditional |
|
||||
|
||||
## Numeric Functions
|
||||
|
||||
ExprTK provides a rich set of built-in numeric functions including `abs`,
|
||||
`ceil`, `floor`, `round`, `exp`, `log`, `log10`, `sqrt`, `min`, `max`, `pow`,
|
||||
`clamp`, `iclamp`, `inrange`, and trigonometric functions (`sin`, `cos`, `tan`,
|
||||
`asin`, `acos`, `atan`).
|
||||
|
||||
## String Functions
|
||||
|
||||
| Function | Description |
|
||||
| ------------------------------- | ------------------------------------------------------- |
|
||||
| `concat(a, b, ...)` | Concatenate strings |
|
||||
| `upper(s)` | Convert to uppercase |
|
||||
| `lower(s)` | Convert to lowercase |
|
||||
| `length(s)` | String length |
|
||||
| `contains(s, substr)` | Whether `s` contains `substr` |
|
||||
| `order(col, 'B', 'C', 'A')` | Custom sort order for a string column |
|
||||
| `match(s, pattern)` | Regex partial match (returns boolean) |
|
||||
| `match_all(s, pattern)` | Regex full match (returns boolean) |
|
||||
| `search(s, pattern)` | First capturing group match |
|
||||
| `indexof(s, pattern)` | Start index of first regex match |
|
||||
| `substring(s, start, end)` | Substring from `start` (inclusive) to `end` (exclusive) |
|
||||
| `replace(s, repl, pattern)` | Replace first regex match |
|
||||
| `replace_all(s, repl, pattern)` | Replace all regex matches |
|
||||
|
||||
## Date/Datetime Functions
|
||||
|
||||
| Function | Description |
|
||||
| ------------------------ | ------------------------------------------------------------------------ |
|
||||
| `today()` | Current date |
|
||||
| `now()` | Current datetime |
|
||||
| `date(year, month, day)` | Construct a date |
|
||||
| `datetime(timestamp_ms)` | Construct a datetime from a POSIX timestamp (ms since epoch) |
|
||||
| `hour_of_day(dt)` | Hour component (0-23) |
|
||||
| `day_of_week(dt)` | Day of the week as a string |
|
||||
| `month_of_year(dt)` | Month of the year as a string |
|
||||
| `bucket(dt, unit)` | Bucket datetime by unit: `'s'`, `'m'`, `'h'`, `'D'`, `'W'`, `'M'`, `'Y'` |
|
||||
|
||||
`bucket` also works on numeric columns: `bucket("Price", 10)` rounds values down
|
||||
to the nearest multiple of 10.
|
||||
|
||||
## Other Functions
|
||||
|
||||
| Function | Description |
|
||||
| ------------------------- | ----------------------------------------------------- |
|
||||
| `is_null(x)` | Whether the value is null |
|
||||
| `is_not_null(x)` | Whether the value is not null |
|
||||
| `percent_of(a, b)` | `a` as a percentage of `b` |
|
||||
| `inrange(low, val, high)` | Whether `val` is between `low` and `high` (inclusive) |
|
||||
| `min(a, b, ...)` | Minimum of inputs |
|
||||
| `max(a, b, ...)` | Maximum of inputs |
|
||||
| `random()` | Random float between 0.0 and 1.0 |
|
||||
| `col(name)` | Look up a column by string name at runtime |
|
||||
| `vlookup(col, key)` | Look up a value in another column by row key |
|
||||
@@ -0,0 +1,147 @@
|
||||
# Grouping and Pivots
|
||||
|
||||
## Group By
|
||||
|
||||
A group by _groups_ the dataset by the unique values of each column used as a
|
||||
group by - a close analogue in SQL to the `GROUP BY` statement. The underlying
|
||||
dataset is aggregated to show the values belonging to each group, and a total
|
||||
row is calculated for each group, showing the currently selected aggregated
|
||||
value (e.g. `sum`) of the column. Group by are useful for hierarchies,
|
||||
categorizing data and attributing values, i.e. showing the number of units sold
|
||||
based on State and City. In Perspective, group by are represented as an array of
|
||||
string column names to pivot, are applied in the order provided; For example, a
|
||||
group by of `["State", "City", "Postal Code"]` shows the values for each Postal
|
||||
Code, which are grouped by City, which are in turn grouped by State.
|
||||
|
||||
<div class="javascript">
|
||||
|
||||
```javascript
|
||||
const view = await table.view({ group_by: ["a", "c"] });
|
||||
```
|
||||
|
||||
</div>
|
||||
<div class="python">
|
||||
|
||||
```python
|
||||
view = table.view(group_by=["a", "c"])
|
||||
```
|
||||
|
||||
</div>
|
||||
<div class="rust">
|
||||
|
||||
```rust
|
||||
let view = table.view(Some(ViewConfigUpdate {
|
||||
group_by: Some(vec!["a".into(), "c".into()]),
|
||||
..ViewConfigUpdate::default()
|
||||
})).await?;
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Split By
|
||||
|
||||
A split by _splits_ the dataset by the unique values of each column used as a
|
||||
split by. The underlying dataset is not aggregated, and a new column is created
|
||||
for each unique value of the split by. Each newly created column contains the
|
||||
parts of the dataset that correspond to the column header, i.e. a `View` that
|
||||
has `["State"]` as its split by will have a new column for each state. In
|
||||
Perspective, Split By are represented as an array of string column names to
|
||||
pivot:
|
||||
|
||||
<div class="javascript">
|
||||
|
||||
```javascript
|
||||
const view = await table.view({ split_by: ["a", "c"] });
|
||||
```
|
||||
|
||||
</div>
|
||||
<div class="python">
|
||||
|
||||
```python
|
||||
view = table.view(split_by=["a", "c"])
|
||||
```
|
||||
|
||||
</div>
|
||||
<div class="rust">
|
||||
|
||||
```rust
|
||||
let view = table.view(Some(ViewConfigUpdate {
|
||||
split_by: Some(vec!["a".into(), "c".into()]),
|
||||
..ViewConfigUpdate::default()
|
||||
})).await?;
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Aggregates
|
||||
|
||||
Aggregates perform a calculation over an entire column, and are displayed when
|
||||
one or more [Group By](#group-by) are applied to the `View`. Aggregates can be
|
||||
specified by the user, or Perspective will use the following sensible default
|
||||
aggregates based on column type:
|
||||
|
||||
- "sum" for `integer` and `float` columns
|
||||
- "count" for all other columns
|
||||
|
||||
Perspective provides a selection of aggregate functions that can be applied to
|
||||
columns in the `View` constructor using a dictionary of column name to aggregate
|
||||
function name.
|
||||
|
||||
<div class="javascript">
|
||||
|
||||
```javascript
|
||||
const view = await table.view({
|
||||
aggregates: {
|
||||
a: "avg",
|
||||
b: "distinct count",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</div>
|
||||
<div class="python">
|
||||
|
||||
```python
|
||||
view = table.view(
|
||||
aggregates={
|
||||
"a": "avg",
|
||||
"b": "distinct count"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</div>
|
||||
<div class="rust">
|
||||
|
||||
```rust
|
||||
use std::collections::HashMap;
|
||||
let view = table.view(Some(ViewConfigUpdate {
|
||||
aggregates: Some(HashMap::from([
|
||||
("a".into(), "avg".into()),
|
||||
("b".into(), "distinct count".into()),
|
||||
])),
|
||||
..ViewConfigUpdate::default()
|
||||
})).await?;
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
The available aggregate functions depend on the column type:
|
||||
|
||||
**Numeric columns** (`integer`, `float`): `sum`, `abs sum`, `sum abs`,
|
||||
`sum not null`, `any`, `avg`, `mean`, `count`, `distinct count`, `dominant`,
|
||||
`first`, `last`, `last by index`, `high`, `low`, `max`, `min`,
|
||||
`high minus low`, `last minus first`, `median`, `q1`, `q3`,
|
||||
`pct sum parent`, `pct sum total`, `stddev`, `var`, `unique`,
|
||||
`weighted mean`, `min by`, `max by`.
|
||||
|
||||
**String columns**: `count`, `any`, `distinct count`, `dominant`, `first`,
|
||||
`last`, `last by index`, `join`, `median`, `q1`, `q3`, `unique`, `min by`,
|
||||
`max by`.
|
||||
|
||||
**Date/Datetime columns**: `count`, `any`, `avg`, `distinct count`, `dominant`,
|
||||
`first`, `last`, `last by index`, `high`, `low`, `max`, `min`, `median`,
|
||||
`q1`, `q3`, `unique`.
|
||||
|
||||
**Boolean columns**: `count`, `any`, `distinct count`, `dominant`, `first`,
|
||||
`last`, `last by index`, `unique`.
|
||||
@@ -0,0 +1,138 @@
|
||||
# Selection and Ordering
|
||||
|
||||
## Columns
|
||||
|
||||
The `columns` property specifies which columns should be included in the
|
||||
`View`'s output. This allows users to show or hide a specific subset of columns,
|
||||
as well as control the order in which columns appear to the user. This is
|
||||
represented in Perspective as an array of string column names:
|
||||
|
||||
<div class="javascript">
|
||||
|
||||
```javascript
|
||||
const view = await table.view({
|
||||
columns: ["a"],
|
||||
});
|
||||
```
|
||||
|
||||
</div>
|
||||
<div class="python">
|
||||
|
||||
```python
|
||||
view = table.view(columns=["a"])
|
||||
```
|
||||
|
||||
</div>
|
||||
<div class="rust">
|
||||
|
||||
```rust
|
||||
let view = table.view(Some(ViewConfigUpdate {
|
||||
columns: Some(vec![Some("a".into())]),
|
||||
..ViewConfigUpdate::default()
|
||||
})).await?;
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Sort
|
||||
|
||||
The `sort` property specifies columns on which the query should be sorted,
|
||||
analogous to `ORDER BY` in SQL. A column can be sorted regardless of its data
|
||||
type, and sorts can be applied in ascending or descending order. Perspective
|
||||
represents `sort` as an array of arrays, with the values of each inner array
|
||||
being a string column name and a string sort direction. When `split_by` are
|
||||
applied, the additional sort directions `"col asc"` and `"col desc"` will
|
||||
determine the order of pivot column groups.
|
||||
|
||||
<div class="javascript">
|
||||
|
||||
```javascript
|
||||
const view = await table.view({
|
||||
sort: [["a", "asc"]],
|
||||
});
|
||||
```
|
||||
|
||||
</div>
|
||||
<div class="python">
|
||||
|
||||
```python
|
||||
view = table.view(sort=[["a", "asc"]])
|
||||
```
|
||||
|
||||
</div>
|
||||
<div class="rust">
|
||||
|
||||
```rust
|
||||
let view = table.view(Some(ViewConfigUpdate {
|
||||
sort: Some(vec![Sort("a".into(), SortDir::Asc)]),
|
||||
..ViewConfigUpdate::default()
|
||||
})).await?;
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
The available sort directions are:
|
||||
|
||||
| Direction | Description |
|
||||
|---|---|
|
||||
| `"asc"` | Ascending order |
|
||||
| `"desc"` | Descending order |
|
||||
| `"asc abs"` | Ascending by absolute value |
|
||||
| `"desc abs"` | Descending by absolute value |
|
||||
| `"col asc"` | Ascending order for pivot column groups (requires `split_by`) |
|
||||
| `"col desc"` | Descending order for pivot column groups (requires `split_by`) |
|
||||
| `"col asc abs"` | Ascending by absolute value for pivot column groups |
|
||||
| `"col desc abs"` | Descending by absolute value for pivot column groups |
|
||||
|
||||
## Filter
|
||||
|
||||
The `filter` property specifies columns on which the query can be filtered,
|
||||
returning rows that pass the specified filter condition. This is analogous to
|
||||
the `WHERE` clause in SQL. There is no limit on the number of columns where
|
||||
`filter` is applied, but the resulting dataset is one that passes all the filter
|
||||
conditions, i.e. the filters are joined with an `AND` condition. The join
|
||||
condition can be changed to `OR` via the `filter_op` property.
|
||||
|
||||
Perspective represents `filter` as an array of arrays, with the values of each
|
||||
inner array being a string column name, a string filter operator, and a filter
|
||||
operand in the type of the column:
|
||||
|
||||
<div class="javascript">
|
||||
|
||||
```javascript
|
||||
const view = await table.view({
|
||||
filter: [["a", "<", 100]],
|
||||
});
|
||||
```
|
||||
|
||||
</div>
|
||||
<div class="python">
|
||||
|
||||
```python
|
||||
view = table.view(filter=[["a", "<", 100]])
|
||||
```
|
||||
|
||||
</div>
|
||||
<div class="rust">
|
||||
|
||||
```rust
|
||||
let view = table.view(Some(ViewConfigUpdate {
|
||||
filter: Some(vec![Filter::new("a", "<", FilterTerm::Scalar(Scalar::Float(100.0)))]),
|
||||
..ViewConfigUpdate::default()
|
||||
})).await?;
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
The available filter operators depend on the column type:
|
||||
|
||||
**String columns**: `==`, `!=`, `>`, `>=`, `<`, `<=`, `begins with`,
|
||||
`contains`, `ends with`, `in`, `not in`, `is not null`, `is null`.
|
||||
|
||||
**Numeric columns** (`integer`, `float`): `==`, `!=`, `>`, `>=`, `<`, `<=`,
|
||||
`is not null`, `is null`.
|
||||
|
||||
**Boolean columns**: `==`, `is not null`, `is null`.
|
||||
|
||||
**Date/Datetime columns**: `==`, `!=`, `>`, `>=`, `<`, `<=`, `is not null`,
|
||||
`is null`.
|
||||
Reference in New Issue
Block a user