Руководство к созданию собственного когортного отчёта по возвратности
- воскресенье, 12 ноября 2017 г. в 03:12:07
Когортный анализ возвратности пользователей является мощным способом для понимания разных групп клиентов — их поведения и значимости для бизнеса. Однако итоговые таблицы бывает трудно понять с первого раза, а с ходу придумать, как их построить, ещё сложнее.
В статье будет описан относительно простой, но полезный алгоритм построения когортой таблицы, а также приведены наброски кода с Python/Pandas и SQL. Если Вам необходимо программно реализовать построение когортного отчёта или просто интересно узнать этот алгоритм — прошу под кат.
Одна из моих разработок — приложение для корпоративной аналитики. И когортный отчёт по возвратности вероятно является важнейшей функцией. Мне не раз приходилось его переписывать, делая более настраиваемым, гибким.
Во время разработки я не нашёл примеров реализации такого отчёта, поэтому изложенный ниже алгоритм был создан мной из понимания сути когортного анализа. Если кто-то обнаружит косяки или знает варианты получше, пожалуйста, сообщите об этом в комментариях.
Условные определения:
Когорты по возвратности — таблица когортного анализа, в которой каждая строка описывает отдельную когорту по дате её появление, а столбцы показывают время наблюдения за когортой.
На примере ниже видно, что из клиентов, пришедших в августе, на следующий месяц остались только 60% от их начального количества. А число активных клиентов в сентябре составляют: 100% от пришедших в сентябре + 60% от пришедших в августе + 30% от пришедших в июле. Аналогично, число активных клиентов в августе это 100% от новых клиентов в августе и 50% клиентов, пришедших в июле. То есть, сами когорты мы смотрим по строкам, а всех клиентов, активных в некоторый месяц, — по диагонали, по разным когортам.
Мы построим когортную таблицу за несколько шагов, на каждом из которых будем получать новую таблицу, каждой из которых я дал своё название.
Есть таблица заказов Orders, по которой нужно провести когортный анализ. Структура следующая:
// Дата заказа
date: DateTime,
// ID клиента
clientID: String,
// Стоимость заказа
price: Int
Нам нужно получить таблицу Clients с датой прихода клиента:
date: DateTime,
clientID: String
Возможно, у кого-то она уже есть (например, с датой регистрации пользователя или с датой установки приложения), но её также можно посчитать её как дату первого заказа.
Python:
Clients = pd.groupby(Orders, by=['clientID'], as_index=False)
Clients = Clients.agg({ 'dt' : {'date' : 'min' }})
Clients.columns = cli.columns.droplevel()
Clients.columns = ['clientID', 'date']
MySQL:
SELECT clientID, MIN(date) AS date
FROM Orders
GROUP BY clientID
Производим объединение таблиц Orders и Clients по типу Left через общее поле clientID. Дабы избежать путаницы, поле date из первой таблицы называем dateOr, а у второй — dateCl.
Теперь нам необходимо округлить даты, чтобы получились группы. Можно округлить до месяца, откинув число, можно посчитать число недель до какой-нибудь даты. В итоге, из даты должны получиться строки.
Структура таблицы Mix:
// Дата прихода
dateCl: String,
// Дата заказа
dateOr: String,
// ID клиента
clientID: String,
// Стоимость заказа
price: Int
Python:
Mix = pd.merge(Orders, Clients, how='left', on=['clientID'])
Mix.columns = ['dateOr', 'clientID', 'price', 'dateCl']
def cutDate(txt):
return txt[:7]
Mix['dateOr'] = Mix['dateOr'].apply(cutDate)
Mix['dateCl'] = Mix['dateCl'].apply(cutDate)
MySQL:
SELECT
STRFTIME_UTC_USEC(Clients.date, "%Y-%m") AS dateCl,
STRFTIME_UTC_USEC(Orders.date, "%Y-%m") AS dateOr,
clientID, price
FROM Clients INNER JOIN Orders ON (Clients.date = Orders.date)
Наконец приближаемся к когортам! Произведём группировку сразу по трём полям: dateCl, dateOr и clientID.
Без этого мы не сможем найти число активных клиентов в какой-либо промежуток времени. Количество строк (функция Count()
) дала бы нам лишь количество заказов в когорте в данный временной промежуток, а число активных клиентов найти было бы никак.
К этим полям добавляем следующие:
ordersCount = Count()
total = Sum(price)
Итоговая структура:
// Дата прихода
dateCl: String,
// Дата заказа
dateOr: String,
// ID клиента
clientID: String,
// Число заказов, которые сделал клиент в этот временной промежуток
ordersCount: Int,
// Сумма, на которую этот клиент сделал заказы в этот временной промежуток
total: Int,
Python:
Preresult = pd.groupby(Mix, by=['tel', 'dateOr', 'dateCl'], as_index=False)
Preresult = Preresult.agg({ 'price': { 'total': 'sum', 'ordersCount': 'count' } })
Preresult.columns = Preresult.columns.droplevel()
Preresult.columns = ['clientID', 'dateOr', 'dateCl', 'total', 'ordersCount']
MySQL:
SELECT
clientID, dateCl, dateOr,
COUNT(*) AS ordersCount,
SUM(price) AS total,
FROM Mix
GROUP BY dateCl, dateOr, clientID
Теперь уже можем обезличить наши данные, сгруппировав только по dateCl и dateOr. Добавляем другие поля:
clientsCount = Count()
ordersCount = Sum(ordersCount)
total = Sum(total)
Получается такая таблица:
// Дата прихода
dateCl: String,
// Дата заказа
dateOr: String,
// Число клиентов в когорте
clientsCount: Int,
// Число заказов, которые сделали все клиенты этой когорты в данный временной промежуток
ordersCount: Int,
// Сумма, на которую все клиенты этой когорты сделал заказы в данный временной промежуток
total: Int,
Python:
Result = pd.groupby(Preresult, by=['dateOr', 'dateCl'], as_index=False)
Result = Result.agg({ 'total': { 'total': 'sum' }, 'ordersCount': { 'ordersCount': 'sum', 'clientsCount': 'count' } })
Result.columns = Result.columns.droplevel()
Result.columns = ['dateOr', 'dateCl', 'total', 'ordersCount', 'clientsCount'])
MySQL:
SELECT
dateCl, dateOr,
COUNT(*) AS clientsCount,
SUM(ordersCount) AS ordersCount,
SUM(total) AS total
FROM Preresult
GROUP BY dateCl, dateOr
Теперь нам остаётся лишь преобразовать структуру таблицы: по строкам должны располагаться значения dateCl, по столбцам — dateOr, а в ячейках — желаемая величина (clientsCount, ordersCount, total или нечто иное).
Python:
# в качестве ячеек можно использовать clientsCount, ordersCount, total или другое поле
Cohort = Preresult.pivot(index='dateCl', columns='dateOr', values='Data')
# избавимся от null'ов для красоты
Cohort.fillna(0, inplace=True)
MySQL:
К сожалению, MySQL не умеет простым способом превращать ячейки в строки и столбцы, поэтому я с этой целью использовал другие языки. Но если кто-то знает такой способ — напишите в комментариях, буду признателен.
* 09 10 11 * 0 1 2
09 3 2 1 09 3 2 1
10 - 3 2 -> 10 3 2 -
11 - - 3 11 3 - -
Когортная таблица по возвратности — не единственное применение колоритного анализа, есть и другие, более наглядные применения. Например, разделение пользователей на две группы по некоторому признаку (когорты) и отображение их характеристик на графике как двух независимых линий.
Полезное по теме: когортный анализ в Google Analytics.
Всем успеха ;)