Add Segment
Add a new categorical segment column to the eventstream.
Exactly one of rules, func, sql, funnel_events, or time_range
must be provided — unless name is already listed in
schema.custom_cols, in which case passing none of them promotes that
existing column to a segment in place, without recomputing its values.
Usage
# ordered CASE-WHEN rules over an existing column
stream.add_segment("region", rules=[
["country", "=", "US", "domestic"],
["country", "in", "('GB', 'DE', 'FR')", "europe"],
["other"],
])
# the deepest funnel step each path completed in order
stream.add_segment("funnel", funnel_events=["add_to_cart", "shipping_details", "purchase"])
# "inside" / "outside" a time window
stream.add_segment("incident", time_range=("2024-03-10", "2024-03-17"))
# a DuckDB SELECT returning one label per row
stream.add_segment("device", sql="SELECT CASE WHEN platform = 'mobile' THEN 'mobile' ELSE 'web' END FROM eventstream")
# promote a column that is already in the eventstream, keeping its values
stream.add_segment("returned")
How it works
A segment column stores one label per event row, which is what lets a segment be either static (the same label along a whole path) or dynamic (changing as the path goes on). See Segments for what that distinction buys you.
rules — CASE-WHEN over existing columns
Conditions are tried in order; the last entry is the fallback label.
Before — a country column that came along with the source data:
| user_id | event | country |
|---|---|---|
| u1 | home | US |
| u1 | cart | US |
| u2 | home | DE |
| u2 | cart | DE |
stream.add_segment("region", rules=[
["country", "=", "US", "domestic"],
["international"],
])
After:
| user_id | event | country | region |
|---|---|---|---|
| u1 | home | US | domestic |
| u1 | cart | US | domestic |
| u2 | home | DE | international |
| u2 | cart | DE | international |
Write string values unquoted — "US", not "'US'" — since they get quoted
for you. The one exception is op="in", whose value is passed through to SQL
as-is and so must be a complete tuple:
["country", "in", "('GB', 'DE', 'FR')", "europe"].
funnel_events — how deep did this path get
Labels each path with the deepest funnel step it completed in order. This is the mode that turns a funnel drop-off into a comparable group.
Before — four paths against an add_to_cart → checkout → purchase funnel:
- u1:
home → add_to_cart → checkout → purchase - u2:
home → add_to_cart → checkout - u3:
home → checkout → purchase(skippedadd_to_cart) - u4:
home
stream.add_segment("funnel", funnel_events=["add_to_cart", "checkout", "purchase"])
After — one label per path, written onto every one of its events:
| path | funnel |
|---|---|
| u1 | purchase |
| u2 | checkout |
| u3 | out_of_funnel |
| u4 | out_of_funnel |
Note u3: it reached both checkout and purchase, but never completed
add_to_cart first, so the strictly ordered funnel gives it no credit at all.
Paths that never complete even the first step are labelled out_of_funnel.
time_range — inside vs outside a window
stream.add_segment("incident", time_range=("2024-03-10", "2024-03-17"))
Every event is labelled inside or outside by its own timestamp, so a user
active both during and after an incident contributes to both groups. That is
what makes this the right tool for "how did behaviour change during X?" — see
Segments.
func and sql — anything else
func receives the raw DataFrame and returns one label per row, in row order;
sql is a DuckDB SELECT over the eventstream alias returning a single
column, also one value per row in order.
Promoting an existing column
If the column is already in the eventstream — it rode along from the source data
as a custom column — call add_segment with just the name and no mode argument.
Its values are kept as they are and the column becomes a segment.
stream.add_segment("returned")
Parameters
| Parameter | Type | Description |
|---|---|---|
name | str | Name of the new segment column. |
rules | list, optional | CASE-WHEN rules. A list of conditions plus a final else entry: Example: [["country", "=", "US", "domestic"], ["international"]]. |
func | callable, optional | A function that accepts the raw pandas DataFrame and returns a collection of segment labels with the same length and order as the eventstream rows. |
sql | str, optional | DuckDB SQL SELECT statement that reads from the eventstream table alias and returns exactly one column — the segment label for each row. Row count and order must match the eventstream. Example: "SELECT CASE WHEN platform = 'mobile' THEN 'mobile' ELSE 'web' END FROM eventstream". |
funnel_events | list of str, optional | Ordered list of at least 2 event names defining a strict, ordered ("closed") funnel. A path is assigned funnel_events[k] if there exists an increasing sequence of event occurrences matching funnel_events[0], funnel_events[1], ..., funnel_events[k] in that order (later steps may be reached via any qualifying occurrence, not necessarily the first or last one — earlier events occurring again after a later step was reached don't un-complete it). A path is assigned the highest such k; if it never completes even funnel_events[0], it is labeled out_of_funnel. Segment values (in ascending funnel order): out_of_funnel, then each event name from funnel_events[0] to funnel_events[-1]. |
time_range | tuple or list, optional | (start, end) — two timestamps (string or pd.Timestamp) bounding an inclusive interval over schema.timestamp_col. Each event is labeled inside if its timestamp falls within [start, end], otherwise outside. |
path_col | str, optional | Path ID column override for funnel_events mode; defaults to schema.path_col. |
Values rules
- Each condition entry is
[column, op, value, label]— translates toWHEN <column> <op> <value> THEN <label>in SQL. A stringvalueis quoted for you, so write"US", not"'US'"— the exception isop="in", whose value is inserted raw and must therefore be a complete SQL tuple:"('GB', 'DE', 'FR')". - The last entry is
[else_label]— the ELSE branch label.