Как меняются клиентские сегменты: матрица переходов в pandas

Число лояльных клиентов не изменилось: в прошлом месяце их было тысяча, в этом тоже тысяча. Но внутри могли уйти сотни прежних покупателей и появиться новые. Матрица переходов показывает движение между сегментами. Соберём её в pandas и разделим сохранение лояльности, рост клиентов и исчезновение из наблюдения.

Снимки должны использовать одни правила

Вход — две таблицы с одной строкой на клиента: идентификатор и сегмент на конец месяца. Сегменты уже рассчитаны по согласованному правилу, например по давности и частоте покупок. В этой задаче мы не подбираем их границы, а анализируем переходы. Если между месяцами изменились пороги, движение может отражать новую классификацию, а не поведение клиента.

Для сравнения важно одинаковое окно расчёта. «Активный за последние 30 дней» и «активный в календарном месяце» дают разные границы. Короткий февраль и длинный март также могут влиять на частоту покупок. Перед объединением сохраните версию правила сегмента и дату снимка, чтобы последующий отчёт можно было воспроизвести.

Не следует считать отсутствующую строку автоматически оттоком. Клиент мог исчезнуть из выгрузки из-за фильтра, смены идентификатора или неполной поставки. В примере считаем снимки полными и называем отсутствие во втором «вне снимка». Бизнес-смысл этой категории уточняется до решения о кампании возврата клиентов.

Объединяем всю аудиторию

Используем outer merge по customer и validate="one_to_one". Так сохраняются клиенты только из старого и только из нового снимка. Для отсутствующего старого сегмента ставим «новый в снимке», для отсутствующего нового — «вне снимка». Эти технические категории не должны совпадать с настоящими названиями сегментов.

Затем crosstab считает количество клиентов по паре старый–новый сегмент. Диагональ означает сохранение сегмента. Строка показывает, куда перешли клиенты исходной группы. Чтобы получить доли, делим каждую строку на её сумму. Для вопроса «откуда пришли нынешние лояльные» понадобилась бы нормировка по столбцу. Направление знаменателя меняет смысл процента.

В примере у клиента 1 active сменился на loyal, клиент 2 остался loyal, клиент 3 остался sleeping. Клиент 4 выпал из нового снимка, а клиент 5 появился. Небольшой набор позволяет проверить все пять переходов вручную и увидеть, почему внутреннее объединение потеряло бы две важные группы.

import pandas as pd
old = pd.DataFrame({"customer":[1,2,3,4],"segment":["active","loyal","sleeping","active"]})
new = pd.DataFrame({"customer":[1,2,3,5],"segment":["loyal","loyal","sleeping","active"]})
d = old.merge(new,on="customer",how="outer",suffixes=("_old","_new"),validate="one_to_one",indicator=True)
d["segment_old"] = d.segment_old.fillna("new_in_snapshot")
d["segment_new"] = d.segment_new.fillna("outside_snapshot")
counts = pd.crosstab(d.segment_old,d.segment_new)
shares = counts.div(counts.sum(axis=1),axis=0)
print("counts",counts.to_string())
print("row shares",shares.to_string())
assert counts.to_numpy().sum() == 5
assert counts.loc["active","loyal"] == 1
assert shares.loc["active","outside_snapshot"] == 0.5
assert counts.loc["new_in_snapshot","active"] == 1

Как превратить переходы в действие

Из двух прежних active один стал loyal, один оказался вне нового снимка. Поэтому рост лояльности и потеря активной аудитории происходят одновременно. Если смотреть только итоговые размеры сегментов, такой обмен легко пропустить. Для кампании сохраните клиентский список каждой нужной ячейки, а не только сводную частоту.

Начните с переходов, по которым есть управляемое действие: active→sleeping, loyal→active, новый→loyal. Для каждого проверьте историю покупок и возможные причины. Перед подготовкой следующего списка исключите клиентов, по которым команда уже завершила разбор. Сохраните дату и результат обработки: так повторный расчёт не вернёт те же случаи в рабочую очередь без нового изменения сегмента.

Сверяем результаты с исходными снимками

Суммы строк, кроме новой категории, должны совпасть с численностью старых сегментов. Суммы столбцов, кроме выпавшей категории, — с новым снимком. Общая численность матрицы равна объединению клиентских идентификаторов двух месяцев. Эти равенства помогают обнаружить дубли или потерю части аудитории.

Для следующего периода сохраните ту же последовательность категорий и направление нормировки. Тогда матрицы можно сравнивать визуально. Не смешивайте процент перехода и количество клиентов: высокая доля по маленькому сегменту может быть менее важна для бюджета, чем небольшое ухудшение массовой группы. В управленческом обзоре показывайте оба значения рядом.

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

Переход и его причина — разные поля

Клиент может перейти из loyal в active просто потому, что давняя крупная покупка вышла из скользящего окна. Это закономерное следствие определения сегмента. Перед предложением скидки проверьте дату последнего заказа и состав покупок. Если поведение не ухудшилось, реактивационная акция может оказаться лишней.

Для приоритетов полезно добавить к клиентскому списку маржу и канал приобретения. Тогда можно оценить масштаб перехода в деньгах и выбрать группу для отдельной проверки. Сначала зафиксируйте исходный список и дату снимка: динамический пересчёт во время подготовки кампании иначе изменит аудиторию. Эффект самого предложения оценивают отдельным сравнением, потому что переход в сегмент не доказывает, что скидка вернёт клиента.

Продолжить практику

В «Практической аналитике данных» клиентский кейс связывает метрики с удержанием и выводом для бизнеса. Вы проверяете исходные таблицы, считаете показатели через pandas или SQL и создаёте графики в DataLens. Итоговая работа включает повторный анализ нового месяца и короткую аналитическую записку. Откройте программу курса.

Продолжить на практике

  • Практическая аналитика данных ↗

    Превратите знания SQL или Python в аналитическую работу: проверяйте данные, считайте бизнес-метрики, стройте дашборды и объясняйте выводы на трёх прикладных кейсах.