Add Clusters

Cluster paths using ML and add a new segment column with cluster_0, cluster_1, etc. cluster labels.

Per-path metrics are computed from features, optionally scaled, then passed to the chosen clustering algorithm. The resulting cluster label is broadcast to every row of the corresponding path.

Usage

stream.add_clusters(
    name="cluster",
    features=[
        {"metric": "length"},
        {"metric": "event_count", "metric_args": {"event": "purchase"}},
    ],
    method="kmeans",
    method_args={"n_clusters": 4},
    scaler="minmax",
)

# the same clustering the headless analysis settled on
result = stream.cluster_analysis_data(features=[{"metric": "length"}])
stream.add_clusters(name="cluster", features=[{"metric": "length"}], **result["best_params"])

How it works

add_clusters is the non-interactive half of Cluster Analysis: same features, same scaler, same algorithms, no UI. It computes the features metrics per path, scales them, clusters the result, and writes the label back onto every row of the corresponding path as a new segment column — after which the clusters behave like any other segment, in diff mode, Segment Overview, or the in_segment metric.

Two things are worth knowing before you call it.

method_args={"n_clusters": ...} is required for k-means, and that is not an oversight. The widget searches a range and picks a winner by silhouette score, because it can show you the result and let you disagree. A processor writing a column into your data has no such conversation, so it asks for the exact number rather than guessing one. The intended flow is to settle the question in the widget first — "Save Clusters" emits the matching call, and headlessly cluster_analysis_data()["best_params"] carries the same value — already shaped as the arguments of this call, so it splats straight in:

features = [{"metric": "length"}, {"metric": "active_days"}]

result = stream.cluster_analysis_data(features=features)
stream = stream.add_clusters("behavior", features=features, **result["best_params"])

Clustering is not deterministic across feature sets. Labels are positional (cluster_0, cluster_1, …) and carry no meaning of their own — adding a feature or changing the scaler can renumber every group. Rename them to something you can read once the split is settled, with rename_segment_levels:

stream = stream.rename_segment_levels("behavior", {"cluster_0": "browsers", "cluster_1": "buyers"})

See Path Metrics for what you can cluster on, and Cluster Analysis for why the choice of features is the analysis.

Parameters

ParameterTypeDescription
namestrName of the new segment column to add.
featureslist of dictMetric configurations used as clustering features. Each dict has a "metric" key (str) and an optional "metric_args" key (dict). Available metrics: "length", "duration", "event_count", "has_event", "event_count_bulk", "has_event_bulk", "has_all_events", "has_any_event", "time_between", "first_event_time", "active_days", "matches_pattern", "in_segment", "in_segment_bulk". See the Path Metrics documentation page for the full metric reference.
methodstr, default "kmeans"Clustering algorithm. One of "kmeans" or "hdbscan".
method_argsdict, optionalParameters of the chosen method: n_clusters for "kmeans" (required), min_cluster_size and cluster_selection_epsilon for "hdbscan" (both optional). This is the metric / metric_args shape applied to algorithms, so a key that does not belong to method raises rather than being ignored. scaler and nmf_components are not method arguments — they are pipeline steps applied before clustering, so they stay outside this dict.
scalerstr or None, default "minmax"Feature scaler applied before clustering. One of "minmax", "std", or None. ("standard" is accepted as a legacy alias of "std".)
nmf_componentsint, optionalWhen set, reduces features to this many NMF components before clustering.
path_colstr, optionalPath ID column override; defaults to schema.path_col.