Прогнозирование стоимости продолжительности жизни клиента с использованием PyMC-Marketing

Прогнозирование продолжительности жизни клиента с использованием PyMC-Marketing Какие улучшения ожидаются?

Изучите глубины моделирования Buy-till-You-Die (BTYD) и практические кодировочные техники

Изображение от Boxed Water Is Better на Unsplash

TL; DR: Модель Значимости в Течение Жизни Клиента (CLV) – это ключевая техника аналитики клиентов, которая помогает компаниям определить, кто является ценным клиентом. Пренебрежение CLV может привести к излишним вложениям в клиентов на короткий срок, которые могут совершить только одну покупку. Моделирование ‘Покупай До Смерти’, использующее модели BG/NBD и Гамма-Гамма, может оценить CLV. Несмотря на то, что лучшие практики зависят от размера данных и приоритетов моделирования, рекомендуется использовать Python-библиотеку PyMC-Marketing тем, кто хочет быстро реализовать моделирование CLV.

1. Что такое CLV?

Определение CLV это общая чистая выручка, которую компания может ожидать от одного клиента на протяжении всего отношения. Некоторые из вас могут быть более знакомы с термином “LTV” (lifetime value). Да, CLV и LTV взаимозаменяемы.

Изображение от автора
  • Первая цель – рассчитать и предсказать будущий CLV, что поможет вам выяснить, сколько денег можно ожидать от каждого клиента.
  • Вторая цель – определить прибыльных клиентов. Модель скажет вам, кто из этих ценных клиентов по анализу характеристик высокоуровневых клиентов CLV.
  • Третья цель – принимать маркетинговые меры на основе анализа и оттуда вы сможете оптимизировать распределение бюджета маркетинга соответствующим образом.
Изображение от автора

2. Бизнес-контекст

Давайте, к примеру, возьмем интернет-магазин модного бренда, такого как Nike, который может использовать рекламу и купоны, чтобы привлечь новых клиентов. Теперь предположим, что две главные важные группы клиентов – это студенты колледжа и работающие профессионалы. Для первого заказа компания тратит $10 на рекламу для студентов колледжей и $20 для работающих профессионалов. И обе группы совершают покупки на сумму около $100.

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

Изображение от автора, с использованием фотографий из Pixabay

Итак, что, если бы вы знали эту информацию?

Сегмент студентов колледжа имеет высокий уровень оттока, что означает, что они не совершают больше покупок после этой одной покупки, в результате чего в среднем тратится $100. С другой стороны, у сегмента работающих профессионалов высокий уровень повторных покупок, что дает среднюю сумму в $400 на одного клиента.

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

Изображение от автора, с использованием фотографий из Pixabay

Путем настройки “стоимости получения клиента”, CPA, мы можем привлечь больше клиентов высокой стоимости и улучшить наш ROI. График слева представляет подход без учета CLV. Красная линия представляет CPA, то есть максимальные затраты, которые мы можем потратить на привлечение нового клиента. Использование одного и того же маркетингового бюджета для каждого клиента приводит к излишним инвестициям в клиентов низкой стоимости и недоинвестированию в клиентов высокой стоимости.

Теперь график справа показывает идеальное распределение расходов при использовании CLV. Мы устанавливаем более высокий CPA для клиентов высокой стоимости и более низкий CPA для клиентов низкой стоимости.

Изображение автора, с использованием фотографий из Pixabay

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

3. Необходимые данные

Модель CLV, которую я представляю, использует только данные о транзакциях продаж. Как видите, у нас есть три столбца данных: идентификатор клиента, дата транзакции и значение транзакции. Что касается объема данных, CLV обычно требует двух-трехлетней истории транзакций.

Изображение автора

4. Традиционная формула CLV

4.1 Подходы для моделирования CLV

Давайте начнем с понимания двух общих типов расчета CLV: исторического подхода и предиктивного подхода. В рамках предиктивного подхода существуют две модели. Вероятностная модель и модели машинного обучения.

Изображение автора

4.2 Традиционная формула CLV

Давайте начнем с рассмотрения традиционной формулы CLV. Здесь CLV можно разбить на три компонента: средняя стоимость заказа, частота покупок и срок жизни клиента.

Изображение автора

Давайте, например, рассмотрим компанию моды:

  • Клиенты тратят $100 за заказ
  • Они делают покупки 4 раза в год
  • Они остаются верными в течение 3 лет

В этом случае CLV рассчитывается как 100 умножить 4 умножить 3, что равно $1,200 на клиента. Формула очень проста и выглядит прямолинейно, не так ли? Однако у нее есть некоторые ограничения.

4.3 Ограничения традиционной формулы CLV

Изображение автора

Ограничение №1: Не все клиенты одинаковые

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

Ограничение №2: Различия во времени первой покупки

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

Изображение автора, с использованием фотографий из Pixabay

Этот человек сделал свою первую покупку примерно год назад. В этом случае мы точно можем рассчитать его частоту покупок в год. Она составляет 8.

Как насчет двух покупателей? Один начал покупать полгода назад, а другой начал три месяца назад. У всех покупателей одинаковая скорость покупок. Однако, когда мы смотрим на общее количество покупок за последний год, они отличаются. Главным моментом здесь является то, что мы должны учитывать стаж покупателя, то есть время с момента его первой покупки.

Ограничение №3: Мертвы или живы?

Определение того, когда покупатель считается “ушедшим”, является проблематичным. Для подписных услуг, таких как Netflix, мы можем считать, что покупатель ушел, как только отписался. Однако в случае розничной торговли или электронной коммерции, неясно, является ли покупатель “Живым” или “Мертвым”.

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

Изображение автора, с использованием фотографий с Pixabay

5. Модель “Купи, пока не умрешь” (Buy-Till-You-Die)

Для решения этих проблем мы часто обращаемся к моделированию “Buy Till You Die” (BTYD). Этот подход включает две подмодели:

  1. Модель BG-NBD: Прогнозирует вероятность активности покупателя и частоту его транзакций.

2. Модель Гамма-Гамма: Позволяет оценить среднюю стоимость заказа.

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

Изображение автора

5.1 Модель BG/NBD

Мы считаем, что статус покупателя проходит через два процесса: “Процесс покупки”, когда покупатели активно совершают покупки, и “Процесс отказа”, когда покупатели прекращают покупки.

Во время активного процесса покупки модель прогнозирует частоту покупок покупателя с помощью “Пуассоновского процесса”.

Всегда существует вероятность того, что покупатель может отказаться после каждой покупки. Модель BG/NBD присваивает этой возможности вероятность “p”.

Рассмотрим нижеприведенное изображение для пояснения. Данные указывают, что этот покупатель совершил пять покупок. Однако, согласно предположению модели, если бы покупатель оставался активным, он совершил бы в общей сложности восемь покупок. Но, поскольку вероятность оставания в живых упала в какой-то момент, мы видим только пять фактических покупок.

Изображение автора

Частота покупок следует Пуассоновскому процессу, пока покупатель считается “активным”. Пуассоновское распределение обычно представляет счет случайных событий. Здесь “λ” символизирует частоту покупок для каждого покупателя. Однако частота покупок покупателя может меняться. Пуассоновское распределение учитывает такую изменчивость.

Изображение автора; график взят из Википедии

Нижеприведенный график иллюстрирует, как меняется “p” со временем. По мере увеличения времени с момента последней покупки (T=31), вероятность того, что покупатель “жив”, уменьшается. Когда происходит повторная покупка (в окрестности T=36), можно заметить, что “p” вновь увеличивается.

Изображение от автора

Это графическая модель. Как уже упоминалось ранее, она включает лямбда (λ) и p. Здесь лямбда и p различаются от человека к человеку. Чтобы учесть это разнообразие, мы предполагаем, что гетерогенность в λ следует гамма-распределению, а гетерогенность в p следует “бета-распределению”. Другими словами, эта модель использует слоистый подход, основанный на теореме Байеса, который также называется байесовским иерархическим моделированием.

Изображение от автора

5.2 Гамма-гамма модель

Мы предполагаем, что гамма-распределение моделирует средний заказ. Гамма-распределение формируется двумя параметрами: параметром формы и параметром масштаба. Как показывает этот график, форма гамма-распределения может сильно изменяться при изменении этих двух параметров.

Изображение от автора; График из Википедии

Эта диаграмма иллюстрирует использование графической модели. Модель использует два гамма-распределения в рамках байесовского иерархического подхода. Первое гамма-распределение представляет “среднюю стоимость заказа” для каждого клиента. Поскольку эта стоимость различается у разных клиентов, второе гамма-распределение улавливает изменчивость средней стоимости заказа по всей клиентской базе. Параметры p, q и γ (гамма) для априорных распределений определяются с использованием Half-flat priors.

Изображение от автора

6. Пример кода

Полезные библиотеки CLV

Давайте познакомимся с двумя отличными библиотеками OSS для моделирования CLV. Первая – PyMC-Marketing, а вторая – CLVTools. Обе библиотеки включают моделирование “Покупай, пока не умрешь”. Самое значительное отличие заключается в том, что PyMC-Marketing – это библиотека на основе Python, в то время как CLVTools – на основе R. PyMC-Marketing построена на PyMC, популярной байесовской библиотеке. Ранее существовала известная библиотека под названием Lifetimes. Однако Lifetimes сейчас находится в режиме обслуживания, поэтому она перешла в состав PyMC-Marketing.

Полный код

Полный код можно найти на моем Github ниже. Мой пример кода основан на официальном быстром старте PyMC-Marketing.

GitHub – takechanman1228/Effective-CLV-Modeling

Внесите свой вклад в развитие takechanman1228/Effective-CLV-Modeling, создав учетную запись на GitHub.

github.com

Пошаговое описание кода

Сначала вам нужно импортировать pymc_marketing и другие библиотеки.

import arviz as az
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import pymc as pm
from arviz.labels import MapLabeller
from IPython.display import Image
from pymc_marketing import clv

Вам понадобится загрузить набор данных “Online Retail” из “UCI Machine Learning Repository”. Этот набор данных содержит транзакционные данные от британского интернет-ритейлера и имеет лицензию Creative Commons Attribution 4.0 International (CC BY 4.0).

import requests
import zipfile
import os

# Загрузка zip-файла
url = "https://archive.ics.uci.edu/static/public/352/online+retail.zip"
response = requests.get(url)
filename = "online_retail.zip"
with open(filename, 'wb') as file:
    file.write(response.content)

# Разархивация файла
with zipfile.ZipFile(filename, 'r') as zip_ref:
    zip_ref.extractall("online_retail_data")

# Поиск имени файла Excel
for file in os.listdir("online_retail_data"):
    if file.endswith(".xlsx"):
        excel_file = os.path.join("online_retail_data", file)
        break

# Преобразование из Excel в CSV
data_raw = pd.read_excel(excel_file)
data_raw.head()

Очистка данных

Необходимо выполнить быструю очистку данных. Например, нам нужно обработать возвратные заказы, отфильтровать записи без идентификатора клиента и создать столбец “общие продажи”, умножив количество и цену за единицу.

# Обработка возвратных заказов# Извлечение строк, где InvoiceNo начинается с "C"cancelled_orders = data_raw[data_raw['InvoiceNo'].astype(str).str.startswith("C")]# Создание временного DataFrame со столбцами, которые мы хотим сравнить, и также отрицание столбца 'Quantity'cancelled_orders['Quantity'] = -cancelled_orders['Quantity']# Слияние исходного DataFrame с временным DataFrame по столбцам, которые мы хотим сравнитьmerged_data = pd.merge(data_raw, cancelled_orders[['CustomerID', 'StockCode', 'Quantity', 'UnitPrice']],                        on=['CustomerID', 'StockCode', 'Quantity', 'UnitPrice'],                        how='left', indicator=True)# Фильтрация строк, где объединение нашло совпадение, а также фильтрация исходных возвратных заказовdata_raw = merged_data[(merged_data['_merge'] == 'left_only') & (~merged_data['InvoiceNo'].astype(str).str.startswith("C"))]# Удаление столбца индикатораdata_raw = data_raw.drop(columns=['_merge'])# Выборка соответствующих характеристик и расчет общих продажfeatures = ['CustomerID', 'InvoiceNo', 'InvoiceDate', 'Quantity', 'UnitPrice', 'Country']data = data_raw[features]data['TotalSales'] = data['Quantity'].multiply(data['UnitPrice'])# Удаление транзакций с отсутствующими идентификаторами клиентов, так как они не вносят вклад в поведение отдельного клиентаdata = data[data['CustomerID'].notna()]data['CustomerID'] = data['CustomerID'].astype(int).astype(str)data.head()
Изображение от автора

Затем нам необходимо создать сводную таблицу, используя функцию ‘clv_summary’. Функция возвращает датафрейм в формате RFM-T. RFM-T означает Recency (недавность), Frequency (частотность), Monetary (денежность) и Tenure (длительность) для каждого клиента. Эти метрики популярны в анализе покупателей.

data_summary_rfm = clv.utils.clv_summary(data, 'CustomerID', 'InvoiceDate', 'TotalSales')data_summary_rfm = data_summary_rfm.rename(columns={'CustomerID': 'customer_id'})data_summary_rfm.index = data_summary_rfm['customer_id']data_summary_rfm.head()

Модель BG/NBD

Модель BG/NBD доступна в виде функции BetaGeoModel в этой библиотеке. Когда вы выполняете bgm.fit(), ваша модель начинает обучение.

Когда вы выполняете bgm.fit_summary(), система предоставляет статистическое резюме процесса обучения. Например, эта таблица показывает среднее значение, стандартное отклонение, интервал высокой плотности (HDI), и т.д. для параметров. Мы также можем проверить значение r_hat, которое помогает оценить, сойдется ли симуляция методом Монте-Карло в цепях Маркова (MCMC). Значение R-hat считается приемлемым, если оно меньше или равно 1.1.

bgm = clv.BetaGeoModel(    data = data_summary_rfm,)bgm.build_model()bgm.fit()bgm.fit_summary()

Матрица ниже называется Матрицей вероятности жизни. С ее помощью мы можем установить, вероятно ли, что пользователи вернутся или не вернутся. По оси X представлена историческая частота покупок клиента, а по оси y представлена недавность клиентов. Цвет показывает вероятность оставаться активным. Наши новые клиенты находятся в нижнем левом углу: низкая частота и высокая недавность. У этих клиентов высокая вероятность оставаться активными. Наши постоянные клиенты – это те, кто находится в нижнем правом углу: клиенты с высокой частотой и высокой недавностью. Если они долго не покупают, постоянные клиенты становятся потенциально угрожаемыми клиентами, у которых низкая вероятность оставаться активными.

clv.plot_probability_alive_matrix(bgm);
Image by Author

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

num_purchases = bgm.expected_num_purchases(    customer_id=data_summary_rfm["customer_id"],    t=365,    frequency=data_summary_rfm["frequency"],    recency=data_summary_rfm["recency"],    T=data_summary_rfm["T"])sdata = data_summary_rfm.copy()sdata["expected_purchases"] = num_purchases.mean(("chain", "draw")).valuessdata.sort_values(by="expected_purchases").tail(4)

Модель Gamma-Gamma

Затем мы перейдем к модели Gamma-Gamma для прогнозирования средней стоимости заказа. Мы можем предсказать ожидаемую “среднюю стоимость заказов” с помощью функции ‘Expected_customer_spend’.

nonzero_data = data_summary_rfm.query("frequency>0")dataset = pd.DataFrame({    'customer_id': nonzero_data.customer_id,    'mean_transaction_value': nonzero_data["monetary_value"],    'frequency': nonzero_data["frequency"],})gg = clv.GammaGammaModel(    data = dataset)gg.build_model()gg.fit();expected_spend = gg.expected_customer_spend(    customer_id=data_summary_rfm["customer_id"],    mean_transaction_value=data_summary_rfm["monetary_value"],    frequency=data_summary_rfm["frequency"],)

На графике ниже показана ожидаемая средняя стоимость заказа для 5 клиентов. Средняя стоимость заказа этих двух клиентов превышает $500, в то время как средняя стоимость заказа этих трех клиентов около $350.

labeller = MapLabeller(var_name_map={"x": "customer"})az.plot_forest(expected_spend.isel(customer_id=(range(5))), combined=True, labeller=labeller)plt.xlabel("Ожидаемая средняя стоимость заказа");
Image by Author

Результаты

Наконец, мы можем объединить две подмодели, чтобы оценить CLV каждого клиента. Одна вещь, которую я хотел бы упомянуть здесь, это параметр: Discount_rate. Эта функция использует метод DCF, сокращение от “дисконтированный денежный поток”. Когда ежемесячная ставка дисконта составляет 1%, $100 через месяц стоит $99 сегодня.

clv_estimate = gg.expected_customer_lifetime_value(    transaction_model=bgm,    customer_id=data_summary_rfm['customer_id'],    mean_transaction_value=data_summary_rfm["monetary_value"],    frequency=data_summary_rfm["frequency"],    recency=data_summary_rfm["recency"],    T=data_summary_rfm["T"],    time=120, # 120 месяцев = 10 лет    discount_rate=0.01,    freq="D",)clv_df = az.summary(clv_estimate, kind="stats").reset_index()clv_df['customer_id'] = clv_df['index'].str.extract('(\d+)')[0]clv_df = clv_df[['customer_id', 'mean', 'hdi_3%', 'hdi_97%']]clv_df.rename(columns={'mean' : 'clv_estimate', 'hdi_3%': 'clv_estimate_hdi_3%', 'hdi_97%': 'clv_estimate_hdi_97%'}, inplace=True)# monetary_values = data_summary_rfm.loc[clv_df['customer_id'], 'monetary_value']monetary_values = data_summary_rfm.set_index('customer_id').loc[clv_df['customer_id'], 'monetary_value']clv_df['monetary_value'] = monetary_values.valuesclv_df.to_csv('clv_estimates_output.csv', index=False)

Теперь я покажу вам, как мы можем улучшить наши маркетинговые действия. График ниже показывает оценку CLV по странам.

# Расчет общих продаж за транзакцию
data['TotalSales'] = data['Quantity'] * data['UnitPrice']
customer_sales = data.groupby('CustomerID').agg({    'TotalSales': sum,    'Country': 'first'  # Предположим, что клиент ассоциируется только с одной страной})
customer_countries = customer_sales.reset_index()[['CustomerID', 'Country']]
clv_with_country = pd.merge(clv_df, customer_countries, left_on='customer_id', right_on='CustomerID', how='left')
average_clv_by_country = clv_with_country.groupby('Country')['clv_estimate'].mean()
customer_count_by_country = data.groupby('Country')['CustomerID'].nunique()
country_clv_summary = pd.DataFrame({    'AverageCLV': average_clv_by_country,    'CustomerCount': customer_count_by_country,})
# Расчет среднего нижнего и верхнего доверительного интервала оценок CLV по странам
average_clv_lower_by_country = clv_with_country.groupby('Country')['clv_estimate_hdi_3%'].mean()
average_clv_upper_by_country = clv_with_country.groupby('Country')['clv_estimate_hdi_97%'].mean()
# Добавление этих средних значений в датафрейм country_clv_summary
country_clv_summary['AverageCLVLower'] = average_clv_lower_by_country
country_clv_summary['AverageCLVUpper'] = average_clv_upper_by_country
# Фильтрация стран с более чем 20 клиентами
filtered_countries = country_clv_summary[country_clv_summary['CustomerCount'] >= 20]
# Сортировка в порядке убывания по CustomerCount
sorted_countries = filtered_countries.sort_values(by='AverageCLV', ascending=False)
# Подготовка данных для погрешностей
lower_error = sorted_countries['AverageCLV'] - sorted_countries['AverageCLVLower']
upper_error = sorted_countries['AverageCLVUpper'] - sorted_countries['AverageCLV']
asymmetric_error = [lower_error, upper_error]
# Создание новой фигуры с указанными размерами
plt.figure(figsize=(12,8))
# Создание графика среднего CLV с погрешностями, указывающими интервалы достоверности
plt.errorbar(x=sorted_countries['AverageCLV'], y=sorted_countries.index.tolist(),              xerr=asymmetric_error, fmt='o', color='black', ecolor='lightgray', capsize=5, markeredgewidth=2)
# Установка названий и заголовка
plt.xlabel('Average CLV')
plt.ylabel('Country')
plt.title('Средняя стоимость жизни клиента (CLV) по странам с доверительными интервалами')
# Инвертирование оси y
plt.gca().invert_yaxis()
# Отображение сетки
plt.grid(True, linestyle='--', alpha=0.7)
# Отображение графика
plt.show()
Image by Author

Клиенты во Франции имеют высокий CLV. С другой стороны, клиенты в Бельгии имеют более низкий CLV. Исходя из этого вывода, я рекомендую увеличить бюджет маркетинга для привлечения клиентов из Франции и снизить бюджет маркетинга для привлечения клиентов из Бельгии. Когда мы выполняем моделирование с данными, основанными в США, мы будем использовать штаты вместо страны.

7. Как можно повысить точность модели?

Возможно, вам интересно:

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

По сути, моделирование BTYD требует только данных о транзакциях. Если вы хотите использовать другие данные или функции, возможен подход с использованием машинного обучения. Затем вы можете оценить производительность байесовской и ML моделей, выбрав ту, которая обеспечивает лучшую точность и интерпретируемость.

Схема ниже показывает руководство по более точному моделированию CLV.

Image by Author

Сначала рассмотрите размер ваших данных. Если ваши данные недостаточно велики или у вас есть только данные о транзакциях, моделирование BTYD с использованием PyMC Marketing может быть лучшим выбором. Даже если у вас достаточно данных, я считаю, что хороший подход – начать с модели BTYD, и если она показывает плохие результаты, попробуйте другой подход. В частности, если ваш приоритет – точность перед интерпретируемостью, нейронные сети, XGboost, LightGBM или ансамблевые методы могут быть полезны. Если интерпретируемость по-прежнему важна для вас, рассмотрите методы, такие как Random Forest или подход объяснимого ИИ.

В заключение, я рекомендую начать с маркетинга PyMC – это хороший первый шаг в любом случае!

8. Заключение

Вот несколько ключевых выводов.

  • Ценность клиента на протяжении его жизненного цикла (CLV) – это чистая прибыль, которую компания может ожидать от одного клиента на протяжении всего их отношения.
  • Мы можем построить вероятностную модель (BTYD), используя модель BG/NBD и модель Gamma-Gamma.
  • Если вы знакомы с языком Python, то можете начать с PyMC-Marketing.

Спасибо за внимание! Если у вас есть какие-либо вопросы/предложения, не стесняйтесь связаться со мной на Linkedin! Также, я буду рад, если вы подпишетесь на меня в Towards Data Science.

9. Ссылки