Продуктовая аналитика на практике при помощи R

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Проблематика

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

Все хотят аналитического подхода и способности принимать решения на куче данных о продукте которые есть. Но в реальности есть недостаток информации о том, как именно это делать. Какие инструменты использовать, как принимать решения и как исследовать данные. Я бы хотел поделится именно практическим аспектом этого вопроса в формате одного кейса.

Вводная часть

Начало 2020 года, вы - обычный продукт-менеджер, которому предложили развивать кредитный продукт в другой стране. Оффер принят, документы оформлены, пора за работу. 

Первое, что приходит в голову посмотреть, что там с экономикой продукта. И как вообще ведет себя продукт.

Еще через пару дней совещание, на котором вас просят ответить на несколько вопросов:

  1. Сейчас мы платим за привлечение клиента 695494. Какая стоимость привлечения для нас является приемлемой? Имеет ли смысл повысить стоимость привлечения на клиента, чтобы получить больший объем?

  2. Насколько здоровой выглядит экономика портфеля и какая динамика здесь и сейчас?

  3. Мы недавно изменили подход к размеру выдачи и стали в первые кредиты выдавать меньшие чеки. Как это сказалось на продукте?

В общем-то понятные вопросы, на которые стоит уметь отвечать любому владельцу продукта. 

Но есть проблема. Метрики, конечно, все посчитаны: LTV, CAC и прочее. Есть финансовая отчетность, где видны все расходы и доходы, и видно, что продукт в небольшом операционном плюсе.

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

Ответить на вопросы можно быстро и однозначно, четко посмотрев, что там в сырых данных. Большинство процессов в продукте оставляют следы в базах данных.

А ядро почти любого финансового продукта - транзакции, в них и предлагается смотреть ниже.

Анализируй это

Пример взят из реальной практики. То есть вот прям все взаправду, данные подшумлены, но это реальный портфель. И анализируются данные примерно таким образом.

Для того, чтобы анализировать, понадобятся следующие инструменты: R ,Rstudio,dbeaver(или аналог). Далее рассматривается реальный пример, как могут выглядеть эти данные и что с ними можно сделать.

Итак, транзакции. Напишем в базу нечто вроде  select * from transactions t и посмотрим результат.

Rows: 2,226,532
Columns: 10
$ borrower_id          2, 2, 2, 6, 6, 12, 12, 12, 12, 16, 20, 20, 20, 20, 22, 23, 23, 33, 33, 39, 39, 36, 36…
$ con_id               1, 1, 1, 2, 2, 4, 4, 4, 4, 5, 7, 7, 7, 7, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, 13…
$ disbursement_date    2017-11-23, 2017-11-23, 2017-11-23, 2017-11-24, 2017-11-24, 2017-11-27, 2017-11-27, …
$ prolongations_count  1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1…
$ loan_type            "pdl", "pdl", "pdl", "pdl", "pdl", "pdl", "pdl", "pdl", "pdl", "pdl", "pdl", "pdl", "…
$ date                 2017-12-15, 2017-11-23, 2017-12-06, 2017-11-24, 2017-12-25, 2017-12-29, 2017-11-27, …
$ type                 "Payments::Transaction::ContractAddTransaction", "Payments::Transaction::DisburseTran…
$ amount               250000, 1000000, 1200000, 2500000, 3500000, 1040000, 1500000, 10000, 1470000, 1500000…
$ id                   325, 2, 127, 5, 587, 557500, 557499, 557504, 557507, 17, 182865, 182874, 182869, 1828…
$ deleted_at           NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…

Видим всю историю транзакций по всем клиентами. Проанализируем состав полей(обычно можно посмотреть в документации, но кое-что понять можно и так).

У нас есть идентификаторы заемщика, контракта, и транзакции. Дата выдачи кредита (disbursment_date). Отметка о пролонгации кредита (prolongations_count) - перенос даты выплаты за отдельный платеж. Размер и дата транзакции. Отметка о "мягком" удалении.

Главное - тип транзакции(ContractAdd- возврат денег заемщиком, DisburseTransaction - выплата денег заемщику).

Теперь поговорим об экономике продукта. Принципиально кредитный бизнес работает просто(как и большинство бизнесов): заплатили, чтобы привести пользователя -> выдали кредит -> собрали деньги -> выдали еще кредит.

Выручку по продукту формируют два финансовых потока: от нас к пользователю и от пользователя к нам.

И в нашей табличке есть все данные, чтобы это посчитать.

Также узнаем курс местной валюты к доллару(для примера возьмем, что курс без динамики и составляет 0.00003 доллара за местную валюту).

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

  • для любителей R, данных в табличке прилично, больше 2 млн. Для таких датасетов предпочитаю data table. Но можно и на tidyverse(по запросу покажу как)

Теперь посмотрим на структуру данных и займемся обработкой.

Чуть обработаем их и приведем данные в нужный нам формат. Назовем транзакции удобно и сделаем отрицательными транзакции выдачи кредитов (поля z_type и am):

lk %>% data.table()->lk1 #Создаем отдельный объект вида data table

lk1[,':='
(z_type=z_type<-fifelse(
#  создаем отдельную переменную - которая отвечает за тип транзакций
 type=='Payments::Transaction::ContractAddTransaction','add','disb'),
am=amount*fifelse(z_type=='add',1,-1))][1:20,c(-1,-11,-8,-6)] #убираем ненужные колонки
lk1[disbursement_date<'2020-01-01' & date<='2020-03-01',c(-1,-11,-8,-6)]->lk1
glimpse(lk1) #посмотрим на получившийся фаил
$ borrower_id          2, 2, 2, 6, 6, 12, 12, 12, 12, 16, 20, 20, 20, 20, 22, 23, 23, 33, 33, 39, 39, 36, 36…
$ con_id               1, 1, 1, 2, 2, 4, 4, 4, 4, 5, 7, 7, 7, 7, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, 13…
$ disbursement_date    2017-11-23, 2017-11-23, 2017-11-23, 2017-11-24, 2017-11-24, 2017-11-27, 2017-11-27, …
$ prolongations_count  1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1…
$ date                 2017-12-15, 2017-11-23, 2017-12-06, 2017-11-24, 2017-12-25, 2017-12-29, 2017-11-27, …
$ amount               250000, 1000000, 1200000, 2500000, 3500000, 1040000, 1500000, 10000, 1470000, 1500000…
$ id                   325, 2, 127, 5, 587, 557500, 557499, 557504, 557507, 17, 182865, 182874, 182869, 1828…
$ z_type               "add", "disb", "add", "disb", "add", "add", "disb", "add", "add", "disb", "disb", "ad…
$ am                   250000, -1000000, 1200000, -2500000, 3500000, 1040000, -1500000, 10000, 1470000, -150…

Давайте посмотрим на главное и посчитаем, прибыльный ли портфель.

Для этого посмотрим транзакции за все время. И так как цифры крупные, посмотрим сразу в миллионах:

lk1[,.(total_in_mln=sum(am*for_ex)/1e6),.(z_type)]

total_in_mln

add

58.33176

disb

-45.49114

Видим ясный и понятный результат: транзакции от заемщиков (add) значительно превышают транзакции от выдачи кредита (disb). А ведь часть кредитов наверняка только выдана и по ним даже не подошла дата выплаты.

И здесь же разберемся с темпом, с которым выдавали кредиты:

lk1[!is.na(disbursement_date)&z_type=='disb',
.(sum=sum(am*for_ex*-1,na.rm = T)/1e6),
.(date=floor_date(disbursement_date,'month',))][,
ggplot(.SD,aes(date,sum,label=round(sum,2)))+
geom_col(fill=polar_night[2])+ff+tt+
  geom_text(aes(y=sum+0.1),col=aurora[1])+
  labs(x='месяц выдачи',y='выдача в миллионах долларов',
  title='Выдача кредитов в месяц')]

Аналитически видно, что масштабирование продукта в последние несколько месяцев остановилось. Выдаем столько же сколько и выдавали.

Вернемся к Юнит экономике

Глобально мы занимаемся масштабированием пользователей, ведь кредит без человека выдать сложно.

А юнит экономика в таком контексте- это сколько мы зарабатываем или теряем на одном пользователе, заплатив за его привлечение. 

Давайте посчитаем доходную часть. Чтобы это сделать, нужно для среднего пользователя взять кумулятивный баланс его доходов и расходов(сумму транзакций выдач и возвратов кредитов) и наложить на время его жизни. 

Удобнее работать с относительным временем, так сможем сравнивать пользователей, пришедших в разное время:

lk1[][,':='( min_date=min(disbursement_date)),.(borrower_id)][,#найдем дату начала работы каждого заемщика
c("gen",'dif'):=.(floor_date(min_date,'1 month'),as.numeric(date-min_date))][, #месяц в котором появился заемщик и растояние в днях между датой его рождения и датой транзакции
n:=uniqueN(borrower_id),][, #количество уникальных клиентов
  .(sum=sum(am*for_ex),n=unique(n)),
  .(dif)
][order(dif)][,":="(bal=bal<-sum/n,cum=cumsum(bal))][,
    ggplot(.SD,aes(dif,cum))+ #строим график
   geom_line()+
   tt+ff+
        labs(x='дни жизни заемщика',y='доход в USD',col='поколение',
        title='LTV клиента по поколениям и по годам')+
   scale_y_continuous(breaks = seq(-200,200,20),
   labels =paste0('$',seq(-200,200,20),'k' ))+
   scale_x_continuous(breaks = seq(0,1000,20))  ]

Получившаяся кривая показывает его LTV c динамикой по времени. И это уже близко к ответу на первый вопрос о стоимости привлечения пользователя.

Но перед тем как пустится в эти рассуждения, стоит вспомнить, кто такой, этот средний пользователь. Мало ли, может, лучшие времена продукта давно позади.

Поэтому стоит посмотреть этот же график, но разбив по поколениям и посмотрев последние 24 поколения. Сразу же наложим сюда стоимость привлечения и разобьем по годам(чисто для удобства сравнения):

lk1[,':='( min_date=min(disbursement_date)),.(borrower_id)][,
c("gen",'dif'):=.(floor_date(min_date,'1 month'),as.numeric(date-min_date))][,
n:=uniqueN(borrower_id),.(gen)][,
  .(sum=sum(am*0.00003),n=unique(n)),
  .(gen,dif)# сгруппируем данные  по поколениями
][order(gen,dif)][,
":="(bal=bal<-sum/n)][,':='(cum=cumsum(bal),m_dif=max(dif)),
.(gen)][dif<=m_dif-30 & gen %between% c('2018-01-01','2021-03-31')][,
 ggplot(.SD,aes(dif,cum,col=factor(gen)))+
 geom_line()+
 facet_wrap(~factor(year(gen),levels = c(2019,2018)),nrow=2)+tt+ff+
 labs(x='дни жизни заемщика',y='доход в USD',col='поколение',
 title = 'LTV клиента по поколениям и по годам')+
 scale_y_continuous(breaks = seq(-200,200,20),
 labels =paste0('$',seq(-200,200,20),'k' ))+
 scale_x_continuous(breaks = seq(0,1000,20))+
 geom_hline(yintercept = 695494*for_ex,color='red',size=1)+
 geom_hline(yintercept = 0,color='dark red',linetype='dashed')+
 geom_text(inherit.aes = F,aes(x=as.Date(600),
 y=695494*for_ex+3,group=1),label='Стоимость привлечения клиента = $20.86',
 col='red',size=6)+tt+ff+
 theme(legend.text = element_text(size=20),
       legend.title = element_text(size=25))+
  guides(colour = guide_legend(override.aes = list(size=10)))]

Если каждая траектория - это поколение пользователей (месяц в котором выдан первый кредит), то мы можем сделать несколько важных выводов исходя из следующего:

  1. Точка, из которой начинаются линии - размер первого кредита.

  2. Чем выше траектория, тем больше денег на одного пользователя мы заработали в этом поколении.

  3. Чем раньше траектория поколения пересекает 0, тем быстрее мы начинаем зарабатывать на привлеченном пользователе.

  4. Чем раньше линия пересекает линию стоимости привлечения, тем раньше мы зарабатываем достаточно, чтобы окупать его в данном поколении.

Выводы

  1. Продукт становится лучше. Траектории более поздних поколений пересекают ноль и стоимость привлечения раньше, чем траектории старых поколений (150 дней против 180).

  2. Разница в темпах окупаемости объясняется скорее стартовой точкой и первой парой кредитов. Например, поколения конца 2018 года окупались лучше, имея меньшую сумму первого кредита и большую сумму второго. Это можно понять по “провалу” на траектории на 10-40 днях. Так же в 2019 году: поколения с меньшей средней суммой первого кредита окупали себя намного быстрее.

  3. На дистанции в один год мы заработаем на одном пользователе от 40 до 60 долларов. Максимальный размер заработка неизвестен. Ни одно поколение пока не вышло на плато, но экстраполируя, оценка в 120-150 долларов на дистанции в 3 года выглядит разумной

  4. Наиболее существенное влияние на траекторию оказывают события первых 90 дней, после этого темп роста плюс минус одинаковый.

Мы тут ради ответов, а не картинки смотреть

Воспользуемся полученными знаниями и порассуждаем об ответах на вопросы к предстоящему совещанию

1. Сейчас мы платим 695494. Какая стоимость привлечения для нас является приемлемой? Имеет ли смысл повысить стоимость привлечения на клиента, чтобы получить больший объем?

Ситуация выглядит следующим образом - у нас есть успешный продукт, который мы не масштабируем - и это проблема. Ожидания по доходности от клиента на дистанции год - 40-60 долларов. Текущие затраты на привлечение $20.8 (695494* 0.00003)

В принципе - увеличение стоимости привлечения наша экономика выдержит, портфель не станет убыточным. Но в продукте - длинный срок окупаемости клиента. 

Если мы можем позволить себе тратить сейчас - чтобы заработать потом - эксперименты оправданы. 

2. Насколько здоровой выглядит экономика портфеля и какая динамика здесь и сейчас?

Портфель устойчиво положительный, с динамикой к росту за счет практик развития клиентов в первые дни жизни. Главное пространство для идей - механика работы с клиентами возраста 100+ дней. 

Там существенных прорывов пока не было.

3. Мы недавно изменили подход к размеру выдачи и стали в первые кредиты выдавать меньшие чеки. Как это сказалось на продукте? 

Вообще - видно что поколения в которых первый кредит был меньшим по размерам окупали себя чуть лучше. Четкий ответ - только через АБ тест.

Итог

Работа продукт менеджера это принимать решения. 2.2 млн строк рассказали о том, что происходит. Такой анализ занимает от 30 минут до пары часов, в зависимости от знания предметной области и грязи в данных. Для такого анализа не нужно ничего, кроме сырых данных и открытого ПО.

Несколько десятков строк кода, чуть здравых размышлений и экономика продукта ясна, посчитана и выводы сделаны.

Из этих же данных несложно собирается еще несколько важных оценок и выводов , но о них в дуговой раз.

Источник: https://habr.com/ru/post/544338/


Интересные статьи

Интересные статьи

Кто бы что ни говорил, но я считаю, что изобретение велосипедов — штука полезная. Использование готовых библиотек и фреймворков, конечно, хорошо, но порой стоит их отложить и создать ...
Предыстория Когда-то у меня возникла необходимость проверять наличие неотправленных сообщений в «1С-Битрикс: Управление сайтом» (далее Битрикс) и получать уведомления об этом. Пробле...
Меня всегда занимали отказы систем и странности их поведения, в особенности когда те работают в нормальных для себя условиях. Недавно я видел один из слайдов презентации Йена Гудф...
Многие не любят психологов и психологию, но немногие отрицают наличие у людей [вообще, а не у себя лично] психологических «заморочек». На этом фоне идея разобраться с этими сложностями самостояте...
Не так давно мне было необходимо написать несколько ansible playbooks для подготовки сервера к деплою rails приложения. И, на удивление, я не нашел простого пошагового мануала. Копировать чужой п...