Подробное руководство с использованием python, pandas, scikit-learn и RFM-анализа.

Введение

У вас когда-нибудь было много клиентов, но вы не знали, что с ними делать?

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

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

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

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

  • Соберите и объедините наши данные.
  • Преобразуйте наш набор данных в богатые функции, используя метод, известный как рекурсивный RFM (Recency-Frequency-Monetary Value).
  • Соответствуйте модели, которая может предсказать на этих данных.

Блокнот, который я использовал для урока, можно найти здесь.

Шаг 1. Сбор данных

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

import pandas as pd
# Load transaction data from CSV
df = pd.read_csv(data_path) # path to your data
# Convert Date column to date-time object
df.Date = pd.to_datetime(df.Date)
df.head(10)

Вывод

Шаг 2: Разработка функций

Новизна, частота и денежная стоимость (RFM)

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

Давность, Частота и Денежная стоимость фиксируют, когда клиент совершил свою последнюю транзакцию, как часто он возвращался по делам и каков был средний объем продаж. для каждого клиента. Мы можем добавить к этому любые другие доступные функции (такие как GrossMargin, Age, CostToRetain) или другие прогнозируемые функции (риск оттока или анализ настроений). Ниже мы рассмотрим, как рассчитать каждый из них.

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

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

# Data before cut off
observed = df[df[date_col] < cut_off
# Data after cut off
future = df[
(df[date_col] > cut_off) &
(df[date_col] < cut_off + pd.Timedelta(label_period_days, unit='D'))]

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

  • Давность: время с момента последней транзакции (часы/дни/недели). Нам нужно установить отсечку, чтобы рассчитать давность. Например: сколько дней с момента отсечки они совершили транзакцию?
# Copy transactions
cut_off = df.Date.max()
recency = df[df.Date < cut_off].copy()
# Group customers by latest transaction
recency = recency.groupby(customer_id_column)[date_column].max()
recency = (max_date - recency).dt.days).reset_index().rename(
columns={date_column:'recency'})
  • Частота. Количество различных периодов времени, в течение которых клиент совершал транзакцию. Это позволит нам отслеживать, сколько транзакций совершил клиент и когда они произошли. Мы также можем сохранить практику расчета этих метрик по предельной дате, так как это будет удобно позже.
# Copy transactions
cut_off = df.Date.max()
frequency = df[df.Date < cut_off].copy()
# Set date column as index
frequency.set_index(date_column, inplace=True)
frequency.index = pd.DatetimeIndex(frequency.index)
# Group transactions by customer key and by distinct period
# and count transactions in each period
frequency = frequency.groupby([customer_id_column, pd.Grouper(freq="M", level=date_column)]).count()
# (Optional) Only count the number of distinct periods a transaction # occurred. Else, we will be calculating total transactions in each # period instead.
frequency[value_column] = 1 # Store all distinct transactions
# Sum transactions
frequency = frequency.groupby(customer_id_column).sum().reset_index().rename(
columns={value_column : 'frequency'})
  • Денежная ценность: средняя сумма продаж. Здесь мы просто вычисляем среднюю сумму продаж по всем транзакциям для каждого клиента. Мы можем дополнительно добавить функцию «TotalAmountSpent», взяв сумму вместо среднего на последнем шаге.
# Copy transactions
cut_off = df.Date.max()
value = df[df.Date < cut_off].copy()
# Set date column as index
value.set_index(date_column, inplace=True)
value.index = pd.DatetimeIndex(value.index)
# Get mean or total sales amount for each customer
value = value.groupby(customer_id_column[value_column].mean().reset_index()
.rename(columns={value_column : 'value'})
  • Возраст: время с момента первой транзакции. Для этой функции мы просто найдем количество дней с момента первой транзакции каждого клиента. Опять же, нам понадобится отсечка, чтобы рассчитать время между отсечкой и первой транзакцией.
cut_off = df.Date.max()
age = df[df.Date < cut_off].copy()
# Get date of first transaction
first_purchase = age.groupby(customer_id_column)[date_column].min().reset_index()
# Get number of days between cut off and first transaction
first_purchase['age'] = (cut_off - first_purchase[date_column]).dt.days

Мы можем обернуть все эти функции вместе со следующей функцией:

def customer_rfm(data, cut_off, date_column, customer_id_column, value_column, freq='M'):
cut_off = pd.to_datetime(cut_off)
# Compute Recency
recency = customer_recency(data, cut_off, date_column, customer_id_column)
# Compute Frequency
frequency = customer_frequency(data, cut_off, date_column, customer_id_column, value_column, freq=freq)
# Compute average value
value = customer_value(data, cut_off, date_column, customer_id_column, value_column)
# Compute age
age = customer_age(data, cut_off, date_column, customer_id_column)
# Merge all columns
return recency.merge(frequency, on=customer_id_column).merge(
on=customer_id_column).merge(age, on=customer_id_column)

В идеале это может собирать информацию об удержании клиентов в течение определенного периода времени. Это может выглядеть примерно так:

Customer_ID   recency    frequency            value       age                                     1                 131            1         8.145000       131                      2                  69            1         7.770000        69                       3                 121            1         3.640000       121                      5                   4            4        14.672500       100

Для ярлыков мы просто суммируем суммы, потраченные каждым клиентом в будущем периоде.

labels = future.groupby(id_col)[value_col].sum()

В некоторых случаях, выполняя это один раз для всего набора данных и подбирая модель для прогнозирования меток, можно получить приемлемую точность. Однако, если вы присмотритесь, вы можете спросить: а что, если в наблюдаемый период произошло что-то интересное? Какой правильный вопрос задать. Простое выполнение этого один раз для набора данных игнорирует всю сезонность в данных и рассматривает только один конкретный период метки. Здесь мы представляем то, что я называю рекурсивным RFM.

Рекурсивный RFM

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

Допустим, данные начинаются слева в начале года. Мы выберем периодичность (например, один месяц) и пройдемся по набору данных, вычислив наши функции на основе наблюдаемых (o) и сгенерировав наши метки на основе будущее (f). Идея состоит в том, чтобы рекурсивно вычислить эти функции, чтобы модель узнала, как поведение клиентов меняется с течением времени.

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

Для каждой даты прекращения (co):

  • Вычислить функции RFM из всех строк (i) до отсечения ( ico)
  • Вычислить метки в строках (i) между отсечкой и через месяц после отсечки (coico strong> + частота)
  • Внешнее объединение функций и меток на основе идентификатора клиента для создания набора данных для заполнения клиентов, которые не совершали никаких транзакций.

Объедините все наборы данных в цикле.

Это реализовано в коде ниже:

def recursive_rfm(data, date_col, id_col, value_col, freq='M', start_length=30, label_period_days=30):
# Resultant list of datasets
dset_list = []
# Get start and end dates of dataset
start_date = data[date_col].min() + pd.Timedelta(start_length, unit="D")
end_date = data[date_col].max() - pd.Timedelta(label_period_days, unit="D")
# Get dates at desired interval
dates = pd.date_range(
start=start_date, end=end_date, freq=freq
data[date_col] = pd.to_datetime(data[date_col]
)
for cut_off in dates:
   # split by observed / future
   observed = data[data[date_col] < cut_off
   future = data[
                  (data[date_col] > cut_off) &
                  (data[date_col] < cut_off + pd.Timedelta(
                   label_period_days,  unit='D'))
                ]
   # Get relevant columns
   rfm_columns = [date_col, id_col, value_col]
   _observed = observed[rfm_columns] 
   # Compute features from observed
   rfm_features = customer_rfm(
        _observed, cut_off, date_col, id_col, value_col
   )
   # Compute labels from future
   labels = future.groupby(id_col)[value_col].sum()
   # Outer join features with labels to ensure customers 
   # not in observed are still recorded with a label of 0
   dset = rfm_features.merge(
        labels, on=id_col, how='outer'
   ).fillna(0)
   dset_list.append(dset)
# Concatenate all datasets
full_dataset = pd.concat(dset_list, axis=0)
res = full_dataset[full_dataset.recency != 0].dropna(axis=1, how='any')
return res
rec_df = recursive_rfm(data_for_rfm, 'Date', 'Customer_ID', 'Sales_Amount')

Теперь, когда мы сгенерировали наш набор данных, мы готовы к моделированию и прогнозированию! Все, что нам нужно сделать сейчас, это перетасовать и выполнить разделение обучения/тестирования на наших результирующих данных. Мы будем использовать 80% для обучения и 20% для тестирования.

from sklearn.model_selection import train_test_split
rec_df = rec_df.sample(frac=1) # Shuffle
# Set X and y
X = rec_df[['recency', 'frequency', 'value', 'age']]
y = rec_df[['Sales_Amount']].values.reshape(-1)
# Set test ratio and perform train / test split
test_size = 0.2
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=test_size, random_state=42, shuffle=True)

Шаг 3. Модель

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

В этом примере мы попробуем Random Forest Regressor, так как они очень просты в своей реализации, и поэтому их очень легко попробовать сразу.

from sklearn.ensemble import RandomForestRegressor
# Initialize and fit model on train dataset
rf = RandomForestRegressor().fit(X_train, y_train)

После подгонки мы можем просмотреть наши прогнозы на тестовом наборе в кадре данных.

from sklearn.metrics import mean_squared_error
# Create Dataframe and populate with predictions and actuals
# Train set
predictions = pd.DataFrame()
predictions['true'] = y_train
predictions['preds'] = rf.predict(X_train)
# Test set
predictions_test = pd.DataFrame()
predictions_test['true'] = y_test
predictions_test['preds'] = rf.predict(X_test)
# Compute error
train_rmse = mean_squared_error(predictions.true, predictions.preds)**0.5
test_rmse = mean_squared_error(predictions_test.true, predictions_test.preds)**0.5
print(f"Train RMSE: {train_rmse}, Test RMSE: {test_rmse}")

Выход:

Train RMSE: 10.608368028113563, Test RMSE: 28.366171873961612

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

С тестовым среднеквадратичным отклонением ~ 28,4 это означает, что наши прогнозы отклоняются примерно на 28,40 долларов США на невидимых данных. Кроме того, RMSE нашего поезда значительно ниже, чем RMSE нашего теста, что указывает на переоснащение. Другими словами, он слишком зависит от обучающих данных, чтобы делать свои прогнозы, и изо всех сил пытается предсказать данные, которые он раньше не видел. Для человека это аналогично «закрытости», ему нужно научиться находить отношения в данных, которые можно обобщить на невидимые данные. Анализируя наши результаты, мы видим, что есть возможности для улучшения.

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

Заключение

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

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