Мира · маркетолог магазина настолок «Кубик»
Сегмент «уходящие» хорош, но он про тех, кто уже ушёл. Я хочу ловить их раньше: отправить подарок, пока человек ещё с нами. Подарок с курьером стоит 500 ₽, всем подряд слать дорого. Кому отправлять?
В уроке про RFM «уходящих» нашло правило: давно не покупал, хотя раньше возвращался. Это взгляд назад. Мире нужен взгляд вперёд: кто уйдёт в ближайшие месяцы. В уроке про линейную регрессию модель предсказывала число. Сегодня она будет предсказывать вероятность события «ушёл», а мы научимся проверять такую модель и переводить её ответ в рубли.
Что значит «ушёл»
Модель не поможет, пока не договорились о словах. Определение берём из урока про отток: клиент «Кубика» ушёл, если 135 дней не сделал ни одного неотменённого заказа. 135 дней это 90-й перцентиль паузы между заказами: 90% повторных заказов приходят быстрее.
Теперь выбираем точку прогноза: 16 февраля 2026. От неё до конца данных ровно 135 дней: столько и нужно, чтобы узнать ответ. Смотрим на неё так, будто сейчас утро 16 февраля и будущего мы ещё не знаем:
- кого прогнозируем: клиентов, у которых был неотменённый заказ за 135 дней до 16 февраля, то есть ещё не ушедших. Таких 1547;
- признаки: всё, что известно к 16 февраля. Сколько дней с регистрации и с последнего заказа, сколько заказов, сколько денег за вычетом возвратов, сколько заказов с промокодом, сколько возвратов, канал привлечения;
- ответ:
churned = 1, если с 16 февраля по 30 июня у клиента нет ни одного неотменённого заказа. Июньский заказ может быть ещё в пути, поэтому, как и в уроке про отток, считаем любой неотменённый.
Ушли 946 клиентов из 1547, 61,2%. Магазин настолок не подписка: большинство покупает один-два раза.
Таблицу собрал один SQL-запрос по orders, order_items и returns, в задачах она лежит в customers.csv. Разбиение на обучающую и тестовую выборки из введения в ML уже сделано: 1082 клиента для обучения, 465 для проверки, доля ушедших в обеих одинаковая.
Признаки только из прошлого
Самая дорогая ошибка в модели оттока это признак, который подсматривает в будущее. Посчитай давность не от 16 февраля, а от 1 июля, дня после конца данных, и модель на одном этом признаке получит идеальную оценку: ROC-AUC 1,0 (что это, разберём ниже). Модель не умная, она видит ответ: если последний заказ был в марте, клиент точно не ушёл.
Это утечка из введения в ML в чистом виде. Правило: у каждого признака должна быть дата, и эта дата раньше точки прогноза. Если признак слишком хорошо предсказывает, первым делом ищи, не из будущего ли он.
От прямой к вероятности
Линейная регрессия складывает признаки с весами и выдаёт любое число, хоть −3, хоть 140. Вероятность так не устроена: она живёт от 0 до 1. Логистическая регрессия берёт ту же взвешенную сумму z = b0 + b1 * x1 + b2 * x2 + ... и сжимает её в вероятность:
p = 1 / (1 + exp(-z))
При z = 0 получается 0,5. Большой плюс даёт вероятность около 1, большой минус около 0. В scikit-learn модель собирается так же, как в прошлом уроке, только класс другой:
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
customers = pd.read_csv("customers.csv")
features = ["tenure_days", "recency_days", "orders", "revenue",
"promo_orders", "returns", "channel"]
X = pd.get_dummies(customers[features], columns=["channel"], drop_first=True, dtype=float)
train = customers["part"] == "train"
test = customers["part"] == "test"
model = make_pipeline(StandardScaler(), LogisticRegression())
model.fit(X[train], customers.loc[train, "churned"])
model.predict(X[test])[:5] # array([0, 1, 0, 0, 1])
model.predict_proba(X[test])[:5, 1] # array([0.4041, 0.7775, 0.405 , 0.2153, 0.5658])
make_pipeline склеивает StandardScaler и модель в один объект: fit считает средние и отклонения только по обучающей выборке, а на тестовой применяет их же. Так масштабирование не подсматривает в тест.
predict отвечает классом, predict_proba вероятностью. Класс получается из вероятности по порогу 0,5, и этот порог scikit-learn выбрал за тебя. Мира же платит за каждую посылку, так что порог мы выберем сами, а для этого нужна вероятность.
Крайние клиенты теста выглядят так. У клиента 254 шесть заказов на 27 650 ₽, последний 29 дней назад: вероятность ухода 0,17. У клиента 764 один заказ на 650 ₽ с маркетплейса 129 дней назад: 0,90. Он и правда ушёл.
Веса модели после StandardScaler можно сравнивать между собой, как в прошлом уроке. Самый большой у давности, +0,55: чем дольше клиент не покупал, тем вероятнее уход. Дальше канал маркетплейс, +0,37 (оттуда уходят 74,6% клиентов против 50,3% у пришедших по приглашению; в уроке про отток мы видели, что маркетплейс испортила акция MEGA30), число заказов, −0,23, и сумма, −0,18. Знак веса говорит, в какую сторону признак двигает вероятность.
урок из курса
Урок из курса «Аналитика данных с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.