Собесов

Кейс — сегментация пользователей Wolt для реактивации

Кейсы и метрикиСегментация и кластеризацияСложнаяMiddle

Условие

Дан CSV с machine-generated данными пользователей сервиса как Wolt. Каждая строка — один клиент, который зарегистрировался в сентябре 2019. Между сентябрём 2019 и октябрём 2020 он либо делал заказы, либо нет.

Нужно:

  1. Сделать качественный EDA.
  2. Предложить сегментацию пользователей, которой воспользуется маркетинговая команда для реактивации разных типов пользователей.
  3. Обосновать выбранный подход (почему он лучше произвольного «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. Логические предсегменты

Прежде чем кластеризовать, разделить пользователей на логические корзины (правилами):

  1. Never orderedtotal_orders == 0. Сегмент сам по себе. Реактивация ≠ оживление, скорее «активация в первую покупку».
  2. One-time — сделал ровно 1 заказ давно.
  3. Lapsed — был активным, но last_order > 90 дней.
  4. Active — last_order < 30 дней.
  5. 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 с конкретным офером.

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

  1. Reactivation vs Activation. Никогда не покупавших нельзя кластеризовать через RFM — у них нет M и F. Это отдельная задача (активация в первую покупку), её часто забывают.
  2. K-Means на хвостах. Без log1p или RobustScaler 1–2 super-VIP перетянут центроиды. Кластеризация развалится.
  3. Стабильность кластеров. K-Means с n_init=1 даёт случайные центры. Нужен n_init=10 (или KMeans++).
  4. Произвольное K. Только elbow или только silhouette — неполноценно. Сочетайте + логика бизнеса.
  5. Не-нормализованные R/F/M. Без StandardScaler метрика расстояния доминируется monetary (тысячи евро) — recency (дни) теряется.
  6. «Чисто алгоритм». Маркетингу нужны имена сегментов и actionable рекомендации, а не «cluster_3».
  7. Игнорировать новые/повторные. Новый пользователь со 2 заказами — другой сегмент, чем старый, у которого 2 заказа за год. Учитывайте tenure.
  8. Атрибуция к сентябрю-2019. Данные смещены: пользователи из одной когорты, поведение покрывает 13 месяцев. На свежих когортах горизонт меньше.

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

  1. EDA + разделение на «buyers» и «never ordered» (последние — отдельный сегмент).
  2. Для buyers — RFM + K-Means (с лог-преобразованием M и F, StandardScaler, выбор K через elbow + silhouette).
  3. Получаем 4–5 интерпретируемых сегментов: champions, at-risk, one-timers, low-value lapsed.
  4. Для каждого сегмента — конкретная тактика реактивации и предлагаемый A/B-тест с метрикой успеха (доля повторного заказа за 30 дней).
  5. Никогда-не-покупавшим — onboarding-кампания с первым бесплатным заказом.

Главный аргумент против произвольного «slice and dice»: ручные пороги по одному измерению пропускают двумерные паттерны (high-F low-M или recent low-M отличаются от давних с тем же M), а K-Means на R/F/M находит их автоматически.

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

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

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