How grouping changes every calculation in the package
Author
Alejandro Hagan
Grouping is the main dial
Every time-intelligence and segmentation function in ti respects dplyr::group_by(). There is no .by argument and no group parameter — you group the table before you call the function, and the calculation follows.
Ungrouped, mtd() gives you one month-to-date series for the whole company:
The grouping columns are carried through into the result, so they are available to filter and join on afterwards.
The cumulative sum resets per group
This is the behaviour that makes grouping worth doing. Each group’s running total is independent — store 10’s month-to-date figure never picks up store 20’s revenue.
Drop the zero-filled calendar rows so we are looking at real trading days.
2
Always sort before taking head(). A collected result has no guaranteed row order — the database returns rows in whatever order the query plan produced, so an unsorted head() gives you arbitrary rows that can change between runs.
Be aware of the cost. Because ti completes the calendar for each group (see Calendar Completion and Missing Dates), the row count is roughly number of groups × number of days in that group’s active range. Grouping by two high-cardinality columns can multiply your result set considerably — one reason to keep the pipeline lazy and filter in the database.
Each group gets its own calendar range
A subtle and important point: ti does not force every group onto the same date range. Each group’s calendar is completed only within that group’s own first and last observed date.
Consider two stores, where store B opens two months after store A:
# A tibble: 2 × 4
store first_date last_date n_rows
<chr> <dttm> <dttm> <int>
1 A 2024-01-01 00:00:00 2024-03-01 00:00:00 61
2 B 2024-03-01 00:00:00 2024-03-05 00:00:00 5
Store B’s series starts in March, not January. It is not back-filled with two months of zeroes just because store A had data then.
This is the right default for retail and subscription analysis. A store that opened in March did not have a bad January — it had no January. Padding it with zeroes would drag down every average and make year-over-year comparisons meaningless.
Checking your grouping before you run
Because a ti object is a blueprint, you can inspect what it is about to do. Printing it reports the group count:
This creates a daily `cumsum()` of the current month gross_margin from the
start of the standard calendar month to the end of the month
── Calendar: ──
• The calendar aggregated order_date to the day time unit
• A standard calendar is created with 1 groups
• Calendar ranges from 2021-05-18 to 2024-04-20
• 222 days were missing and replaced with 0
• New date column date, year, quarter, month was created from order_date
If the group count is not what you expected, you have caught the mistake before running a query over the full table.
Grouping and segmentation
The segmentation functions treat grouping differently: for abc(), the grouping columns define what is being ranked. Grouping by store ranks stores against each other by their contribution to the total.