Понимание текущего поведения клиентов в отношении различных типов предложений.
Код, используемый для выполнения этого анализа, а также для обучения модели, можно найти на моем GitHub.
Введение
В этом проекте мы хотим проанализировать данные из реальной клиентской базы Starbucks, включая транзакции и данные, связанные с предложениями, которые компания разослала указанным клиентам.
Конечная цель — понять, как клиенты реагируют на различные предложения, в зависимости от их ключевых характеристик, и иметь возможность предсказать, какое предложение лучше всего подходит для каждого нового клиента.
Чтобы достичь всего этого, мы подготавливаем все имеющиеся у нас данные, анализируем их (а также понимаем их) и, наконец, строим и обучаем модель, чтобы делать прогнозы для новых клиентов. Давайте погрузимся!
Данные, которые нам предоставляются
Через Udacity Starbucks предоставила три основных набора данных, которые были объединены и очищены, поэтому все готово для анализа и построения модели. Ключевые особенности этого набора данных включают в себя:
- идентификатор клиента: идентификатор каждого уникального клиента в наборе данных.
- когда каждый клиент стал участником
- личная информация (необязательно), такая как возраст, пол и доход
- совершенные транзакции, привязанные к соответствующему пользователю
- тип предоставляемого предложения: «Купи один, получи другой», скидки или информация
- дополнительные данные о предложениях: вознаграждения и необходимые расходы, а также каналы, через которые было отправлено предложение
Далее мы анализируем предоставленные данные относительно некоторых интересных особенностей набора данных. Поскольку демографические характеристики кажутся интересными, особенно в отношении таргетинга клиентов с предложениями или рекламой. Поэтому мы сосредоточимся на некоторых ключевых моментах для высокоуровневого анализа в этом посте.
Анализ данных: Анон? Докс? Есть ли различия?
В наши дни люди лучше осознают, насколько драгоценны и ценны их личные данные и от чего они могут отказаться, если просто раздадут их другим, в результате чего наш набор данных о клиентах уже разделен на две группы:
- Группа 1: Клиенты, добровольно предоставившие нам свои личные данные
- Группа 2: Клиенты, которые не предоставили нам свои личные данные
Эти личные данные включают их индивидуальный возраст, пол и доход.
В дальнейшем клиенты, предоставившие свои личные данные, называются закрытыми клиентами, а те, кто этого не сделал, называются анонимными клиентами.
Итак, давайте выясним, что отличает их, а затем, как они ведут себя в отношении различных типов предложений, которые им представлены. Поэтому давайте взглянем на самую интересную особенность: транзакционное поведение обеих групп (рис. 1).
Как видно на графике выше, существенных различий в сделках обеих групп нет. Чтобы быть более конкретным: в предоставленных данных есть 6,89 транзакций на одного клиента и 8,36 транзакций на одного доксированного клиента. Таким образом, транзакционное поведение клиентов, предоставивших личную информацию, несколько снизилось.
Давайте продолжим и посмотрим, как эти две группы реагируют на три разных типа предложений, которые Starbucks разослала своим клиентам (см. рис. 2).
Как мы видим, есть некоторые примечательные аспекты, касающиеся графика выше. Мы видим заметное снижение откликов клиентов на просмотр отправленных им предложений.
Количество доксированных клиентов уменьшилось на 25%, если сравнить, сколько из них получили предложение и сколько из них действительно просмотрели его. С другой стороны, у анонимных клиентов наблюдается аналогичное снижение примерно на 20%.
Если мы пойдем дальше, все станет еще интереснее. Существует дальнейшее снижение примерно на 35% для доксированных клиентов, которые также завершают предложение, независимо от типа предложения. Если мы посмотрим на анонимных клиентов, мы увидим дальнейшее снижение на 85%, когда дело доходит до завершения предложения.
Это существенная разница, и она может быть вызвана многими факторами. На этом этапе было бы интересно узнать, требуется ли от клиента предоставление личной информации для завершения предложения, например. предоставление адреса или электронной почты для получения ваучера. Это было бы контрпродуктивно, поскольку эта группа клиентов уже доказала, что хочет защитить свою личную информацию.
Анализ данных: возраст и доход. Как они влияют на реакцию клиентов на предложения?
На данный момент для анализа можно использовать только доксированные данные, так как не все клиенты указали свой возраст и доход (а также пол). Поскольку в наборе данных есть несколько десятков тысяч доксированных точек данных, это не должно быть проблемой. Давайте посмотрим на соответствующие типы предложений, «купи один, получи один» и предложения со скидками, и посмотрим, есть ли различия в отношении возраста и распределения доходов клиентов.
- Распределение по возрасту для предложений "купи один, получи другой" (рис. 3):
Распределение выглядит нормально распределенным по всем «стадиям» предложения, однако есть некоторые моменты, которые мы можем сделать. Как видно на диаграмме, а также на последующих, отдел маркетинга Starbucks, кажется, имеет четкие «корзины» для своих клиентов в зависимости от их возраста и дохода, когда дело доходит до нацеливания на них предложений. На этой диаграмме мы видим заметное преимущество где-то в конце 40-летнего возраста, а также в середине 30-х годов, когда клиентам отправляется значительно больше предложений. Чтобы получить некоторое представление и, возможно, выяснить, почему это так, давайте возьмем в качестве примеров возраст 25, 40 и 50 лет и рассмотрим их более внимательно:
- 25 лет:
233 человека получили предложение | 185 просмотрели (~ 80%) | 100 завершили его (~ 43%) - 40 лет:
369 получили предложение | 314 просмотрели его (~ 85%) | 196 завершили его (~ 53%) - 50 лет:
494 человека получили предложение | 410 просмотрели его (~ 83%) | 320 завершили его (~ 65%)
Кажется, Starbucks уже сделала свою домашнюю работу и нацелена на клиентов с более высокими шансами завершить предложение больше, чем другие, по крайней мере, с учетом их возраста и предложения «купи один, получи один».
2. Распределение по возрасту для дисконтных предложений (рис. 4):
Для сравнения давайте также посмотрим на предложение скидки. Здесь «закрома» еще более очевидны. Давайте посмотрим на тот же возраст, что и раньше:
- 25 лет:
244 человека получили предложение | 152 просмотрели (~ 62%) | 138 завершили его (~ 57%) - 40 лет:
356 получили предложение | 269 просмотрели его (~ 76%) | 228 завершили его (~ 64%) - 50 лет:
488 человек получили предложение | 359 просмотрели (~ 74%) | 351 завершили его (~ 72%)
Как и раньше, Starbucks, кажется, знает возрастные группы, которые больше всего реагируют на их предложения, и больше ориентируется на них.
3. Доход распределение по предложениям "купи один, получи один" (рис. 5):
Теперь мы попытаемся понять, как Starbucks учитывает различные доходы своих доксированных клиентов, когда дело доходит до рассылки предложений. Прежде всего: четкие границы для разных групп доходов еще более четко отличимы друг от друга. Здесь группы, кажется, от 0 до 50 тысяч в год, от 50 тысяч до где-то около 75 тысяч, от 75 тысяч до 100 тысяч и оттуда вверх. Однако людям с доходом от 50 до 75 тысяч в год отправляется гораздо больше предложений, чем людям с другим доходом. Может быть, потому, что в эту группу попадает больше клиентов, а может быть, потому, что Starbucks по какой-то причине фокусируется на них. Тем не менее, мы можем взглянуть на ответы на предложения. Как и прежде, мы выбираем для сравнения три разных диапазона доходов: 40к — 50к, 60к — 70к и 80к — 90к.
- Доход 40к — 50к:
3210 получили предложение | 2569 просмотрели его (~ 80%) |
1289 завершили его (~40%) - Доход 60к — 70к:
4106 получили предложение | 3537 просмотрели его (~ 86%) |
2332 прошли его (~ 57%) - Доход 80к — 90к:
2537 получили предложение | 2149 просмотрели его (~ 85%) |
1868 завершили его (~74%)
В этом случае может быть некоторый потенциал, чтобы немного скорректировать ситуацию, поскольку группа с доходом 80–90 тысяч одинаково хорошо реагирует на предложения «купи один, получи один» по сравнению с людьми с доходом от 60 до 70 тысяч. Однако могут быть причины, по которым людям с более высоким доходом рассылается меньше предложений. Возможно, только потому, что их меньше.
4. Доход распределение по скидкам (рис. 6):
Теперь давайте сравним группы с одним и тем же доходом друг с другом по предложениям скидок, которые им были отправлены.
- Доход 40к — 50к:
3336 получили предложение | 1947 просмотрели его (~ 58%) |
1725 завершили его (~ 52%) - Доход 60к — 70к:
4190 получили предложение | 3047 просмотрели его (~ 73%) |
2687 завершили его (~ 64%) - Доход 80к — 90к:
2543 получили предложение | 2005 просмотрели его (~ 79%) |
1913 г. завершил его (~ 75%)
Как и прежде, мы видим, что состоятельные люди, похоже, знают о предложениях и умеют экономить. Ответы на предложения скидок очень похожи на предложения «купи один, получи один».
Построение модели
В этой части проекта модель обучается, чтобы предсказать, какой вид рекламы лучше всего подходит для данного клиента с определенными чертами и особенностями. Поэтому необходимы несколько шагов:
- Общая подготовка данных в виде дальнейшей очистки, обработки отсутствующих данных и удаления непригодных/нерелевантных точек данных.
- Выбор всех соответствующих функций из набора данных для прогнозирования целевого значения, в данном случае идентификатора предложения.
- Для обработки категориальных значений необходимо кодирование данных.
- Поскольку мы хотим предсказать категориальное целевое значение, масштабирование признаков используется для дальнейшего повышения производительности модели.
- Разделение данных на обучающий и тестовый наборы.
- Решите, какую метрику производительности использовать.
- Выбор подходящей модели/классификатора.
- Обучите модель и предскажите целевые значения, используя обучающий и тестовый наборы.
- Оцените производительность модели, используя выбранную метрику производительности.
- Дальнейшее повышение производительности выбранной модели путем настройки гиперпараметров.
Подготовка данных
Чтобы иметь возможность обучать нашу модель, нам нужно подготовить данные и сделать их пригодными для их использования. Поскольку мы уже выполнили эту задачу, или, по крайней мере, большую ее часть, это не должно быть слишком большой задачей. Остался главный момент — обработать недостающие данные, что в данном случае относительно просто, потому что нас в основном интересует демография и, следовательно, только поведение клиентов.
Выбор функции
Во-первых: что мы вообще хотим предсказать? Цель состоит в том, чтобы предсказать наиболее подходящее предложение для нового клиента, представленное в наших данных как «offer_id». Всего есть 10 разных, что также означает, что мы хотим предсказать категориальное значение. Позже это повлияет на наш выбор классификаторов.
Теперь мы выбираем те функции, которые нас интересуют и которые, по нашему мнению, помогут модели предсказать целевое значение. Поэтому мы формируем наш фрейм данных, чтобы включить следующие функции:
'время', 'offer_completed', 'offer_received', 'offer_viewed', 'транзакция', 'offer_id', 'всего', 'пол', 'возраст' , 'доход', 'электронная почта', 'мобильный', 'социальный', 'Интернет', 'became_member_in'.
data_model.columns.tolist() ['time', 'offer_completed', 'offer_received', 'offer_viewed', 'transaction', 'offer_id', 'total', 'gender', 'age', 'income', 'email', 'mobile', 'social', 'web', 'became_member_in']
Категорические признаки
Категориальные признаки должны быть закодированы в числовые значения, чтобы модель могла соответствующим образом их обрабатывать. Поэтому сопоставление значений используется для простого сопоставления категориальных значений с целыми числами. Это влияет только на функцию offer_id и гендер в наборе данных.
offer_id_mapping = { 0:0, '0b1e1539f2cc45b7b9fa7c272da2e1d7':1, '2298d6c36e964ae4a3e7e9706d1fb8c2':2, '2906b810c7d4411798c6938adc9daaa5':3, '3f207df678b143eea3cee63160fa8bed':4, '4d5c57ea9a6940dd891ad53e9dbe8da0':5, '5a8bc65990b245e5a138643cd4eb9837':6, '9b98b8c7a33c4b65b9aebfe6a799e6d9':7, 'ae264e3637204a6fb9bb56bc8210ddfd':8, 'f19421c1d4aa40978ebb69ca19b0e20d':9, 'fafdcd668e3743c1bb461111dcafc2a4':10, }
Масштабирование функций
Для прогнозирования категориальных значений также рекомендуется каким-либо образом масштабировать/нормализовать функции для дальнейшего повышения производительности моделей. Здесь мы использовали подход масштабирования Min-Max из модуля sklearn:
cols = X.columns.tolist() scaler = MinMaxScaler() X[cols] = scaler.fit_transform(X[cols])
Разделение набора данных
Если данные готовы и полностью подготовлены для моделирования, набор данных разбивается на два набора: обучающий набор и тестовый набор, где 2/3 данных используются для обучения и 1/3 для тестирования модели.
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42) print(f'Length of X.train: {X_train.shape[0]} and X.test: {X_test.shape[0]}') Length of X.train: 182750 and X.test: 90012
Показатель эффективности
Чтобы выяснить, какая модель работает лучше всего, нам нужна метрика производительности, чтобы сделать их сопоставимыми. В программе Data Science довольно часто используется F1-Score, так как он достаточно сбалансирован и в некоторой степени компенсирует несбалансированные наборы данных. Для этой модели мы будем использовать F1-Score в качестве основной метрики производительности. Кроме того, мы рассчитываем показатели точности и полноты для каждой рассматриваемой нами модели.
Модель/классификатор
Поскольку все модели, алгоритмы и классификаторы различаются по своей производительности в зависимости от набора данных, на котором они обучаются, мы тестируем пару из них и вычисляем их производительность для сравнения:
- Случайный лесной классификатор
Чтобы начать с классификатора случайного леса из модуля sklearn, он обучается с n_estimators, установленным на 100. Ниже отчета о производительности:
print("Precision-score:",precision_score(y_true=y_test, y_pred=y_pred_rfc, average='weighted')) print("F1-score:",f1_score(y_true=y_test, y_pred=y_pred_rfc, average='weighted')) print("Recall-score:",recall_score(y_true=y_test, y_pred=y_pred_rfc, average='weighted')) print("------------------------------------------------------") print("In depth classification report:\n",classification_report(y_true=y_test, y_pred=y_pred_rfc)) Precision-score: 0.7367081382900627 F1-score: 0.7367724273466818 Recall-score: 0.7368795271741545 ------------------------------------------------------ In depth classification report: precision recall f1-score support 0 1.00 1.00 1.00 40963 1 1.00 1.00 1.00 4082 2 0.45 0.45 0.45 5856 3 0.52 0.52 0.52 4686 4 0.33 0.33 0.33 3380 5 0.41 0.39 0.40 5434 6 0.51 0.50 0.51 4177 7 0.52 0.52 0.52 4798 8 0.62 0.63 0.62 5379 9 0.41 0.42 0.42 5404 10 0.45 0.46 0.46 5853 accuracy 0.74 90012 macro avg 0.57 0.57 0.57 90012 weighted avg 0.74 0.74 0.74 90012
- Классификатор повышения градиента
Как и классификатор случайного леса, классификатор повышения градиента обучается с n_estimators = 100.
print("Precision-score:",precision_score(y_true=y_test, y_pred=y_pred_gbc, average='weighted')) print("F1-score:",f1_score(y_true=y_test, y_pred=y_pred_gbc, average='weighted')) print("Recall-score:",recall_score(y_true=y_test, y_pred=y_pred_gbc, average='weighted')) print("------------------------------------------------------") print("In depth classification report:\n",classification_report(y_true=y_test, y_pred=y_pred_gbc)) Precision-score: 0.741202800687273 F1-score: 0.7396625125975725 Recall-score: 0.7418122028174021 ------------------------------------------------------ In depth classification report: precision recall f1-score support 0 1.00 1.00 1.00 40963 1 1.00 1.00 1.00 4082 2 0.45 0.47 0.46 5856 3 0.53 0.52 0.53 4686 4 0.35 0.35 0.35 3380 5 0.45 0.39 0.42 5434 6 0.52 0.36 0.43 4177 7 0.54 0.55 0.54 4798 8 0.60 0.73 0.66 5379 9 0.47 0.37 0.41 5404 10 0.43 0.53 0.47 5853 accuracy 0.74 90012 macro avg 0.57 0.57 0.57 90012 weighted avg 0.74 0.74 0.74 90012
- Логистическая регрессия
И последнее, но не менее важное: классификатор логистической регрессии:
print("Precision-score:",precision_score(y_true=y_test, y_pred=y_pred_lrc, average='weighted')) print("F1-score:",f1_score(y_true=y_test, y_pred=y_pred_lrc, average='weighted')) print("Recall-score:",recall_score(y_true=y_test, y_pred=y_pred_lrc, average='weighted')) print("------------------------------------------------------") print("In depth classification report:\n",classification_report(y_true=y_test, y_pred=y_pred_lrc)) Precision-score: 0.6778460676206762 F1-score: 0.6747262443057118 Recall-score: 0.6804981558014487 ------------------------------------------------------ In depth classification report: precision recall f1-score support 0 1.00 1.00 1.00 40963 1 1.00 1.00 1.00 4082 2 0.27 0.35 0.30 5856 3 0.33 0.29 0.31 4686 4 0.32 0.28 0.30 3380 5 0.26 0.21 0.23 5434 6 0.51 0.41 0.46 4177 7 0.42 0.51 0.46 4798 8 0.60 0.69 0.64 5379 9 0.26 0.09 0.13 5404 10 0.27 0.40 0.32 5853 accuracy 0.68 90012 macro avg 0.48 0.48 0.47 90012 weighted avg 0.68 0.68 0.67 90012
Учитывая оценки каждого из вышеперечисленных классификаторов, мы продолжаем использовать классификатор случайного леса, учитывая тот факт, что он лишь незначительно хуже, чем классификатор повышения градиента, но требует меньше ресурсов для обучения модели.
Настройка гиперпараметров
Производительность модели может быть дополнительно увеличена путем настройки гиперпараметров. Чтобы добиться этого, мы используем подход поиска по сетке, чтобы найти оптимальные параметры для классификатора случайного леса, чтобы он работал наилучшим образом. На данный момент настраиваются только два параметра («n_estimators» и «max_depth»), учитывая тот факт, что во время настройки модели были доступны только ограниченные вычислительные ресурсы.
params = { 'n_estimators': [50, 100, 150], 'max_depth': [None, 10, 20, 30], } grid_search = GridSearchCV(estimator=rfc, param_grid=params, cv=5, scoring='f1_macro') grid_search.fit(X_train, y_train) rfc_tuned = grid_search.best_estimator_ y_pred_rfc_tuned = rfc_tuned.predict(X_test) print("Precision-score:",precision_score(y_true=y_test, y_pred=y_pred_rfc_tuned, average='weighted')) print("F1-score:",f1_score(y_true=y_test, y_pred=y_pred_rfc_tuned, average='weighted')) print("Recall-score:",recall_score(y_true=y_test, y_pred=y_pred_rfc_tuned, average='weighted')) print("------------------------------------------------------") print("In depth classification report:\n",classification_report(y_true=y_test, y_pred=y_pred_rfc_tuned)) Precision-score: 0.7379764376951514 F1-score: 0.7379395546275304 Recall-score: 0.7381460249744478 ------------------------------------------------------ In depth classification report: precision recall f1-score support 0 1.00 1.00 1.00 40963 1 1.00 1.00 1.00 4082 2 0.45 0.45 0.45 5856 3 0.53 0.52 0.52 4686 4 0.34 0.35 0.34 3380 5 0.42 0.38 0.40 5434 6 0.51 0.49 0.50 4177 7 0.53 0.53 0.53 4798 8 0.62 0.64 0.63 5379 9 0.41 0.43 0.42 5404 10 0.45 0.47 0.46 5853 accuracy 0.74 90012 macro avg 0.57 0.57 0.57 90012 weighted avg 0.74 0.74 0.74 90012
Выполнение этого короткого поиска по сетке немного улучшило производительность модели. Однако полный потенциал не достигается, поскольку фактические гиперпараметры могут быть настроены намного дальше.
И последнее, но не менее важное: мы можем кратко взглянуть на важность функций нашего набора данных, который мы использовали для обучения нашей окончательной модели:
feature_importances = rfc_tuned.feature_importances_ *100 print("Feature Importance:") for name, importance in zip(X_train.columns, feature_importances): print(f"{name}: {importance:.4f}") Feature Importance: time: 3.5651 offer_completed: 1.2726 offer_received: 1.5736 offer_viewed: 1.2490 transaction: 10.3583 total: 11.3432 gender: 0.4937 age: 5.9089 income: 5.7877 email: 15.8875 mobile: 15.1400 social: 12.4466 web: 13.6440 became_member_in: 1.3299
Как мы видим, демографические данные играют значительную роль в эффективности нашей модели, но наиболее важными характеристиками, по-видимому, являются каналы связи для отправки предложения клиентам. Это может быть еще одним хорошим оценочным пунктом для дальнейшего анализа.
Заключение и дальнейшие шаги
Заключение
Теперь, когда мы сравнили некоторые модели с точки зрения их производительности и настроили лучшую из них, чтобы получить немного лучший показатель F1 (0,74), мы можем предсказать наиболее подходящий тип предложения для новых клиентов в заданной степени. Сюда не входят клиенты, которые не поделились своими личными данными о возрасте, поле и доходе. Принимая это во внимание, вероятно, будет хорошей идеей разделить эти два типа клиентов на две разные модели, поскольку демографическая информация играет большую роль в улучшении моделей F1-Score.
Тем не менее, этот проект показывает, что мы можем построить модель, которая прогнозирует подходящее предложение для новых клиентов, используя ограниченный набор функций. Начав с нескольких небольших наборов данных, мы могли обрабатывать и очищать все данные и объединять их в один набор данных, подходящий для обучения модели, а также для построения диаграмм для визуализации и выделения определенных аспектов клиентов Starbucks.
Дальнейшие шаги
- Учитывая ограниченные ресурсы, сравнивались только ограниченное количество моделей. Кроме того, количество точно настроенных параметров довольно мало, что также можно решить с помощью большего количества вычислительных ресурсов.
- Общий анализ также можно расширить, чтобы учесть больше характеристик, особенно различные способы коммуникации, поскольку они имеют большое значение для производительности модели.
- Отдельная модель для анонимных клиентов может стать дополнительным источником информации для нацеливания на новых клиентов наилучшего возможного предложения.