Условие
Дан датасет логов поведения пользователей онлайн-магазина: 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 (оформление заказа), проверьте статистическую значимость различий, предложите варианты персонализации.
Решение
Подход
Кластеризация принимает на вход векторы, у нас же — последовательности событий. Шаги:
- Векторизовать пользователя по его поведению.
- Кластеризовать (KMeans, HDBSCAN).
- Подобрать число кластеров (silhouette, elbow, BIC).
- Описать группы, сравнить конверсии, проверить значимость, предложить действия.
1. Векторизация
Несколько подходов:
(a) Bag of events — для каждого пользователя частоты mapped_event-ов. Простой baseline.
(b) N-gram bag — биграммы переходов (OpenProductPage → AddTo_Cart). Учитывает порядок.
(c) Markov-вероятности переходов — для каждого пользователя матрица вероятностей P[event_i → event_j], развёрнутая в вектор.
(d) Embeddings — event2vec (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 |
Подводные камни
- Размерность. Если у тебя 30 типов событий + биграммы — это 900+ фич. Перед KMeans — PCA/UMAP до 10–20 измерений.
- Скейлинг. Без
StandardScalerKMeans даст плохой результат: фичи с большой шкалой (число событий) задавят остальные. - K=число кластеров — не догма. Silhouette даёт оптимум, но интерпретируемость важнее. Если 5 кластеров неотличимы по бизнесу — берите 3.
- Один пользователь = один вектор. Если у пользователя несколько сессий — придётся выбрать: усреднять, брать последнюю, или кластеризовать сессии.
- Multiple comparisons. При попарных тестах конверсий обязательно поправка (Bonferroni, BH).
- Кластеры — не абсолютная истина. При другом random_state могут немного смещаться. Проверяйте на стабильность через несколько запусков.
- Selection bias. Кластеризация on-site бихевиора не видит «никогда не пришедших». Если задача про привлечение — нужна другая выборка.
Эталонный ответ
Пайплайн: bag-of-events / N-gram → StandardScaler → KMeans с подбором k через silhouette → описание профилей → конверсия в AddTo_Cart/pass по кластерам → попарные хи-квадрат тесты с Bonferroni → персонализация. Главное — выбрать число кластеров не по метрике, а по интерпретируемости и бизнес-различию между группами.