Условие

Дан CSV с machine-generated данными пользователей сервиса как Wolt. Каждая строка — один клиент, который зарегистрировался в сентябре 2019. Между сентябрём 2019 и октябрём 2020 он либо делал заказы, либо нет.
Нужно:
- Сделать качественный EDA.
- Предложить сегментацию пользователей, которой воспользуется маркетинговая команда для реактивации разных типов пользователей.
- Обосновать выбранный подход (почему он лучше произвольного «slice & dice» немонитарного аналитика).
Ожидаемый формат: Jupyter notebook на Python/R + поддерживающая презентация. Время выполнения ~ 8 часов.
Решение
Шаг 1. EDA — что в данных
Минимальный набор колонок, которые ожидаемо есть в таком тестовом датасете (точные имена в задании зависят от файла):
user_id,registration_date(всё в сентябре 2019),country/city,device_type(iOS/Android),total_orders,total_revenue,last_purchase_date,avg_basket,discount_used_count,- (часто)
acquisition_channel.
Что проверяем:
- Распределение
total_orders. Скорее всего — heavy tail: 30–50% людей не сделали ни одного заказа, 5% сделали > 50. last_purchase_dateпротивregistration_date— сколько прошло до первой покупки (time_to_first_order).- Когорты по неделе регистрации (даже внутри сентября).
- Доли активных vs «никогда не покупавших».
- Сравнение по странам и платформам.
Главный нюанс: «не делал заказов» — это отдельный сегмент (registered, never ordered), который нельзя сегментировать классическим RFM.
Шаг 2. Логические предсегменты
Прежде чем кластеризовать, разделить пользователей на логические корзины (правилами):
- Never ordered —
total_orders == 0. Сегмент сам по себе. Реактивация ≠ оживление, скорее «активация в первую покупку». - One-time — сделал ровно 1 заказ давно.
- Lapsed — был активным, но last_order > 90 дней.
- Active — last_order < 30 дней.
- At risk — был активным, last_order 30–90 дней.
Это базовые правила. Дальше внутри них (кроме «never ordered») — RFM + численная кластеризация.
Шаг 3. Подход — RFM + K-Means
Для пользователей с заказами:
- R (Recency) = дни от
last_purchase_dateдо конца периода (октябрь 2020). - F (Frequency) =
total_orders. - M (Monetary) =
total_revenue.
Стандартизуем (StandardScaler) и применяем K-Means. Выбор K через локоть (elbow) и silhouette. Обычно получается 4–5 кластеров.
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
df = pd.read_csv("wolt_users.csv", parse_dates=["registration_date","last_purchase_date"])
df["recency"] = (pd.Timestamp("2020-10-31") - df["last_purchase_date"]).dt.days
df["frequency"] = df["total_orders"]
df["monetary"] = df["total_revenue"]
# Никогда не покупавшие — отдельный сегмент
buyers = df[df["frequency"] > 0].copy()
non_buyers = df[df["frequency"] == 0].copy()
# Лог-преобразование тяжёлых хвостов
X = buyers[["recency","frequency","monetary"]].copy()
X["frequency"] = np.log1p(X["frequency"])
X["monetary"] = np.log1p(X["monetary"])
X_scaled = StandardScaler().fit_transform(X)
# Подбор k
for k in range(2, 8):
km = KMeans(n_clusters=k, random_state=42, n_init=10).fit(X_scaled)
print(k, silhouette_score(X_scaled, km.labels_))
# Выбор k=4
km = KMeans(n_clusters=4, random_state=42, n_init=10).fit(X_scaled)
buyers["cluster"] = km.labels_
# Профилируем: средние R/F/M по кластерам
buyers.groupby("cluster")[["recency","frequency","monetary"]].mean().round(1)Шаг 4. Интерпретация и реактивация
Типичные кластеры (имена даём осмысленные):
| Кластер | Признаки | Реактивация |
|---|---|---|
| Champions | low R, high F, high M | Не реактивация — программа лояльности, реферал. |
| At-risk loyal | high R (60–120 дней), high F, high M | Персональная скидка, опрос «почему не возвращаетесь» |
| One-timers | F = 1, R высокий | Промокод на 2-й заказ + социальные доказательства |
| Low-value lapsed | низкий M, высокий R | Дешёвая активация: пуш «новые рестораны рядом» |
| Never ordered (отдельный сегмент вне k-means) | F = 0 | Onboarding: первый бесплатный заказ, объяснение ценности |
Шаг 5. Почему это лучше «slice and dice»
Что бы сделал не-аналитик?
- Поделил «купили / не купили» — 2 группы.
- Или «много заказов / мало заказов» по произвольному порогу.
Эти простые правила не учитывают взаимосвязи: пользователь может быть «пять заказов», но это всё было в первый месяц, а потом тишина — это «lapsed loyal», а не «active».
К-Means на стандартизованных R/F/M:
- учитывает все три измерения одновременно;
- сам находит границы — не аналитик придумывает порог «5 заказов»;
- выявляет неочевидные паттерны (например, «пользователи с 2 заказами и большим чеком отличаются от 2-заказников с маленьким чеком»).
Дополнительно можно добавить признаки: time_to_first_order, avg_days_between_orders, discount_dependency — это даёт «поведенческие» сегменты сверх RFM.
Шаг 6. Что ещё положить в презентацию
- Распределение R/F/M, обоснование лог-преобразования.
- Выбор K (elbow + silhouette).
- Профили кластеров (средние, доли).
- Стратегия реактивации для каждого кластера + ожидаемый KPI.
- Ограничения: данные только сентябрь-2019 регистрации, 1 страна и т.д.
- A/B-тест плана реактивации: для каждого сегмента — control + treatment с конкретным офером.
Подводные камни
- Reactivation vs Activation. Никогда не покупавших нельзя кластеризовать через RFM — у них нет M и F. Это отдельная задача (активация в первую покупку), её часто забывают.
- K-Means на хвостах. Без
log1pили RobustScaler 1–2 super-VIP перетянут центроиды. Кластеризация развалится. - Стабильность кластеров. K-Means с
n_init=1даёт случайные центры. Нуженn_init=10(или KMeans++). - Произвольное K. Только elbow или только silhouette — неполноценно. Сочетайте + логика бизнеса.
- Не-нормализованные R/F/M. Без
StandardScalerметрика расстояния доминируется monetary (тысячи евро) — recency (дни) теряется. - «Чисто алгоритм». Маркетингу нужны имена сегментов и actionable рекомендации, а не «cluster_3».
- Игнорировать новые/повторные. Новый пользователь со 2 заказами — другой сегмент, чем старый, у которого 2 заказа за год. Учитывайте
tenure. - Атрибуция к сентябрю-2019. Данные смещены: пользователи из одной когорты, поведение покрывает 13 месяцев. На свежих когортах горизонт меньше.
Эталонный ответ
- EDA + разделение на «buyers» и «never ordered» (последние — отдельный сегмент).
- Для buyers — RFM + K-Means (с лог-преобразованием M и F, StandardScaler, выбор K через elbow + silhouette).
- Получаем 4–5 интерпретируемых сегментов: champions, at-risk, one-timers, low-value lapsed.
- Для каждого сегмента — конкретная тактика реактивации и предлагаемый A/B-тест с метрикой успеха (доля повторного заказа за 30 дней).
- Никогда-не-покупавшим — onboarding-кампания с первым бесплатным заказом.
Главный аргумент против произвольного «slice and dice»: ручные пороги по одному измерению пропускают двумерные паттерны (high-F low-M или recent low-M отличаются от давних с тем же M), а K-Means на R/F/M находит их автоматически.