Create Cumulative Metrics
A grasp of these concepts will help you understand this documentation better:
Introduction
In business analytics, aside from typical aggregation metrics, cumulative metric is an indispensable tool to understand the performance of your business over time.
In this guide, we will try to answer the following questions about a fictional ecommerce business:
- How does the company’s cumulative revenue look like over months?
- How does the cumulative average order value of each category look like?
We will work with the familiar ecommerce
dataset, and use the running_total()
function to answer these questions.
Example 1: Cumulative Total Revenue over Months
Setup
In this example, the ecommerce
dataset will contain only the orders
, order_items
, and products
models.
- Initial Setup
- Final Setup
// orders.model.aml
Model orders {
...
dimension id {...}
dimension user_id {...}
dimension created_at {...}
}
// order_items.model.aml
Model order_items.model.aml {
...
dimension order_id {...}
dimension product_id {...}
dimension quantity {...}
}
// products.model.aml
Model products {
...
dimension id {...}
dimension price {...}
dimension category_id {...}
}
// e_commerce.dataset.aml
Dataset e_commerce {
...
models: [orders, order_items, products]
relationships: [
relationship(order_items.order_id > orders.id, true),
relationship(order_items.product_id > products.id, true)
]
}
// orders.model.aml
Model orders {
...
dimension id {...}
dimension user_id {...}
dimension created_at {...}
dimension order_month {
label: 'Created At Month'
type: 'date'
definition: @aql date_trunc(orders.created_at, 'month') ;;
}
}
// order_items.model.aml
Model order_items.model.aml {
...
dimension order_id {...}
dimension product_id {...}
dimension quantity {...}
}
// products.model.aml
Model products {
...
dimension id {...}
dimension price {...}
dimension category_id {...}
}
// e_commerce.dataset.aml
Dataset e_commerce {
...
models: [orders, order_items, products]
relationships: [
relationship(order_items.order_id > orders.id, true),
relationship(order_items.product_id > products.id, true)
]
metric revenue {
label: 'Revenue'
type: 'number'
hidden: false
definition: @aql order_items | sum(order_items.quantity * products.price);;
}
metric cumulative_revenue {
label: 'Cumulative Revenue by Month'
type: 'number'
definition: @aql running_total(revenue, orders.created_at_month) ;;
}
}
High-level flow
- Create the normal aggregation metric: In the
ecommerce
dataset, we create a simplerevenue
metric that calculate order value by summing overorder_items.quantity
multiplied byproducts.price
. - Prepare the running dimension: we will create a
created_at_month
dimension in theorders
model. This will be used in the final running metric. - Create the cumulative metric: We use the
running_total()
to run therevenue
metric along the dimension.
Implementation
1. Create the normal sum metric
In the definition file of the ecommerce
dataset, we can easily create a revenue
metric that combines fields from two different models (order_items
and products
) without the need to pre-join them:
Dataset ecommerce {
...
metric revenue {
label: 'Revenue'
type: 'number'
definition: @aql order_items | sum(order_items.quantity * products.price) ;;
}
}
2. Prepare the running dimension
When we first created the orders
table model, Holistics automatically scanned the table and pre-create model fields representing the physical table columns.
We already have created_at
as the time dimension at the lowest grain (seconds), but we want a time dimension with a coarser grain (month). We can use the AQL function date_trunc()
to truncate the timestamp values to month grain:
Model orders {
type: 'table'
label: 'Orders'
...
dimension created_at {
label: 'Created At'
type: 'datetime'
hidden: false
definition: @sql {{ #SOURCE.created_at }};;
}
dimension created_at_month {
label: 'Created At Month'
type: 'datetime'
definition: @aql date_trunc(orders.created_at, 'month') ;;
}
}
3. Create the cumulative metric
Finally, we can use the running_total()
function to create a Cumulative Revenue metric that will gradually add up revenue after each month:
Dataset ecommerce {
...
metric revenue {
label: 'Revenue'
type: 'number'
definition: @aql order_items | sum(order_items.quantity * products.price) ;;
}
metric cumulative_revenue {
label: 'Cumulative Revenue by Month'
type: 'number'
definition: @aql running_total(revenue, orders.created_at_month) ;;
}
}
Example 2: Cumulative Average Order Value over Months of each Category
Setup
Sometimes, simply looking at how a metric change over time is not enough - you may want to see how the metric differs between different groups. For example, you may ask: “How does the monthly cumulative average order value differs between categories?”
In this example, we will add the categories
model to the ecommerce
dataset.
- Initial Setup
- Final Setup
// orders.model.aml
Model orders {
...
dimension id {...}
dimension user_id {...}
dimension created_at {..}
}
// order_items.model.aml
Model order_items.model.aml {
...
dimension order_id {...}
dimension product_id {...}
dimension quantity {...}
}
// products.model.aml
Model products {
...
dimension id {...}
dimension price {...}
dimension category_id {...}
}
// categories.model.aml
Model categories {
...
dimension id {...}
dimension name {...}
dimension parent_id {...}
}
// e_commerce.dataset.aml
Dataset e_commerce {
...
models: [orders, order_items, products, categories]
relationships: [
relationship(order_items.order_id > orders.id, true),
relationship(order_items.product_id > products.id, true),
relationship(products.category_id > categories.id, true)
]
metric revenue {
label: 'Revenue'
type: 'number'
definition: @aql order_items | sum(order_items.quantity * products.price) ;;
}
}
// orders.model.aml
Model orders {
...
dimension id {...}
dimension user_id {}
dimension created_at {}
// note that metrics defined in model are called measures
measure total_orders {
label: 'Total Orders'
type: 'number'
definition: @aql count(orders.id) ;;
}
}
// order_items.model.aml
Model order_items.model.aml {
...
dimension order_id {...}
dimension product_id {...}
dimension quantity {...}
}
// products.model.aml
Model products {
...
dimension id {...}
dimension price {...}
dimension category_id {...}
}
// categories.model.aml
Model categories {
...
dimension id {...}
dimension name {...}
dimension parent_id {...}
}
// e_commerce.dataset.aml
Dataset e_commerce {
...
models: [orders, order_items, products, categories]
relationships: [
relationship(order_items.order_id > orders.id, true),
relationship(order_items.product_id > products.id, true),
relationship(products.category_id > categories.id, true)
]
metric revenue {
label: 'Revenue'
type: 'number'
definition: @aql order_items | sum(order_items.quantity * products.price) ;;
}
metric average_order_value {
label: 'AOV'
type: 'number'
definition: @aql safe_divide(revenue, orders.total_orders) ;;
}
metric cumulative_aov {
label: 'Cumulative AOV'
type: 'number'
definition: @aql running_total(average_order_value, orders.created_at_month) ;;
}
}
High-level Flow
- Create the normal average metric: In the
ecommerce
dataset, we create a simpleaverage_order_value
by dividing the previously createdrevenue
metric with theorders.count_orders
metric. - Create the cumulative metric: We use the
running_total()
to run theaverage_order_value
metric along theorders.created_at_month
dimension. In the final analysis, we will also include thecategories.name
dimension to see the effect.
Implementation
1. Create the normal average metric
In the orders
model, we create a total_orders
metric that count all the orders:
Model orders {
...
// note that metrics defined in model are called measures
measure total_orders {
label: 'Total Orders'
type: 'number'
definition: @aql count(orders.id) ;;
}
}
To create the average_order_values
metric, we can actually reuse the revenue
metrics we created earlier instead of writing the full calculations again:
Dataset ecommerce {
...
metric revenue {
label: 'Revenue'
type: 'number'
definition: @aql order_items | sum(order_items.quantity * products.price) ;;
}
metric average_order_value {
label: 'AOV'
type: 'number'
definition: @aql safe_divide(revenue, orders.total_orders) ;;
}
}
2. Create the cumulative metric
Similar to how we created the cumulative_revenue
metric, we use the running_total()
function to run the average_order_value
metric along orders.created_at_month
dimension.
Dataset ecommerce {
...
metric average_order_value {
label: 'AOV'
type: 'number'
definition: @aql safe_divide(revenue, orders.total_orders) ;;
}
metric cumulative_aov {
label: 'Cumulative AOV'
type: 'number'
definition: @aql running_total(average_order_value, orders.created_at_month) ;;
}
}
Since the average_order_value
metric is only run against the created_at_month
dimension, the cumulative_aov
metric is partitioned by the categories.name
dimension, which is what we wanted:
Producing the cumulative average this way is similar to dividing the cumulative_revenue
metric by a cumulative_orders
metric: