Собесов

Retentioneering: кластеризация пользователей по траекториям событий

ML / Data ScienceКластеризация и event-based аналитикаСложнаяMiddle

Условие

Дан датасет логов поведения пользователей онлайн-магазина: event_timestamp, current_path, next_path, user_id, session, os, browser, mapped_event (session_start, pass, lost, OpenProductPage, cart_page, AddTo_Cart, ...).

Кластеризуйте пользователей по поведению, опишите группы, сравните между ними конверсию в AddTo_Cart и pass (оформление заказа), проверьте статистическую значимость различий, предложите варианты персонализации.

Решение

Подход

Кластеризация принимает на вход векторы, у нас же — последовательности событий. Шаги:

  1. Векторизовать пользователя по его поведению.
  2. Кластеризовать (KMeans, HDBSCAN).
  3. Подобрать число кластеров (silhouette, elbow, BIC).
  4. Описать группы, сравнить конверсии, проверить значимость, предложить действия.

1. Векторизация

Несколько подходов:

(a) Bag of events — для каждого пользователя частоты mapped_event-ов. Простой baseline.

(b) N-gram bag — биграммы переходов (OpenProductPage → AddTo_Cart). Учитывает порядок.

(c) Markov-вероятности переходов — для каждого пользователя матрица вероятностей P[event_i → event_j], развёрнутая в вектор.

(d) Embeddingsevent2vec (skip-gram на событиях) усреднённый по сессиям пользователя.

В Retentioneering обычно начинают с (a) или (b), потом усложняют:

import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
 
df = pd.read_csv("test2.gz", compression="gzip")
 
# Аггрегация: bag of events на пользователя
ue = df.pivot_table(
    index="user_id",
    columns="mapped_event",
    values="session",
    aggfunc="count",
    fill_value=0,
)
 
# Дополним продуктовыми статистиками
sess_per_user = df.groupby("user_id")["session"].nunique()
ev_per_session = df.groupby(["user_id", "session"]).size().groupby("user_id").mean()
 
ue["sessions"] = sess_per_user
ue["events_per_session"] = ev_per_session
 
X = StandardScaler().fit_transform(ue.values)

2. Подбор числа кластеров

from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
 
scores = {}
for k in range(2, 9):
    km = KMeans(n_clusters=k, random_state=42, n_init=10).fit(X)
    scores[k] = silhouette_score(X, km.labels_, sample_size=10000)
print(scores)
 
# Выбираем максимум silhouette + здравый смысл
k_best = max(scores, key=scores.get)
labels = KMeans(n_clusters=k_best, random_state=42, n_init=10).fit_predict(X)
ue["cluster"] = labels

Альтернативные метрики: Calinski-Harabasz, Davies-Bouldin. Elbow на inertia — менее формально, но интуитивно. На «грязных» данных лучше HDBSCAN — он не требует фиксированного k.

3. Описание кластеров

profile = ue.groupby("cluster").agg(["mean", "median"]).round(2)
print(profile)

Типичные кластеры в e-commerce событийных данных:

  • «Window-shoppers»: много OpenProductPage, мало AddTo_Cart, ноль pass.
  • «Серьёзные покупатели»: глубокая воронка, сразу AddTo_Cart, конверсия в pass высокая.
  • «Корзинщики-абандонщики»: добавляют, но не оформляют.
  • «Поисковики»: много search_result_page, мало взаимодействия.

4. Конверсия + статистика

from scipy.stats import chi2_contingency
from itertools import combinations
 
ue["had_addtocart"] = (ue.get("AddTo_Cart", 0) > 0).astype(int)
ue["had_pass"] = (ue.get("pass", 0) > 0).astype(int)
 
# Конверсии по кластерам
conv = ue.groupby("cluster")[["had_addtocart", "had_pass"]].mean()
print(conv)
 
# Хи-квадрат для попарных сравнений (с поправкой Бонферрони)
clusters = ue["cluster"].unique()
for c1, c2 in combinations(clusters, 2):
    sub = ue[ue["cluster"].isin([c1, c2])]
    contingency = pd.crosstab(sub["cluster"], sub["had_pass"])
    chi2, p, *_ = chi2_contingency(contingency)
    print(f"{c1} vs {c2}: p={p:.4f}")

С поправкой на множественные сравнения (Bonferroni: умножаем p на число пар).

5. Персонализация

Из кластеров — рекомендации:

Кластер Персонализация
Window-shoppers Push «посмотрите похожие», скидка на просмотренный товар через 24h
Серьёзные покупатели Programa лояльности, рекомендации на основе истории
Корзинщики-абандонщики Email/push «корзина ждёт», free shipping, лимитированная скидка
Поисковики Улучшение поиска, фильтры, popular queries

Подводные камни

  1. Размерность. Если у тебя 30 типов событий + биграммы — это 900+ фич. Перед KMeans — PCA/UMAP до 10–20 измерений.
  2. Скейлинг. Без StandardScaler KMeans даст плохой результат: фичи с большой шкалой (число событий) задавят остальные.
  3. K=число кластеров — не догма. Silhouette даёт оптимум, но интерпретируемость важнее. Если 5 кластеров неотличимы по бизнесу — берите 3.
  4. Один пользователь = один вектор. Если у пользователя несколько сессий — придётся выбрать: усреднять, брать последнюю, или кластеризовать сессии.
  5. Multiple comparisons. При попарных тестах конверсий обязательно поправка (Bonferroni, BH).
  6. Кластеры — не абсолютная истина. При другом random_state могут немного смещаться. Проверяйте на стабильность через несколько запусков.
  7. Selection bias. Кластеризация on-site бихевиора не видит «никогда не пришедших». Если задача про привлечение — нужна другая выборка.

Эталонный ответ

Пайплайн: bag-of-events / N-gram → StandardScaler → KMeans с подбором k через silhouette → описание профилей → конверсия в AddTo_Cart/pass по кластерам → попарные хи-квадрат тесты с Bonferroni → персонализация. Главное — выбрать число кластеров не по метрике, а по интерпретируемости и бизнес-различию между группами.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти