Русский
Русский
English
Статистика
Реклама

Поговорим о RFM-анализе

Добрый день уважаемые читатели! О данном методе сегментации клиентов по давности покупок, частоте и сумме сделок написано довольно много материалов. На просторах Интернета вы без труда найдете публикации с описанием теории и практики rfm-анализа. Он может выполняться как на платформе табличного редактора (при небольшом количестве данных), так и с помощью sql-запросов или силами тематических библиотек Python/R. Методология всех примеров одна и та же, расхождение будет только в деталях. Например, порядок присвоения номеров сегментам или принцип деления на группы. Ввиду всего вышеизложенного мне будет трудно привнести новизну в эту тему. В статье я лишь постараюсь заострить ваше внимание на некоторых моментах, которые могут помочь начинающим аналитикам данных.

Для демонстрации работы скриптов я выбрал базу данных PostgreSQL и JupyterLab из комплекта Anaconda. Все примеры кода, которые вы увидите в публикации, можно найти на GitHub (ссылка). Данные для анализа взяты с портала Kaggle (ссылка).

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

import pandas as pdimport numpy as npimport datetime as dtpd.set_option('display.max_columns', 10)pd.set_option('display.expand_frame_repr', False)df = pd.read_csv('dataset.csv', sep=',', index_col=[0])#Приводим названия столбцов датасета к нижнему региструdf.columns = [_.lower() for _ in df.columns.values]#Трансформируем строку-дату в правильный формат и избавляемся от времениdf['invoicedate'] = pd.to_datetime(df['invoicedate'], format='%m/%d/%Y %H:%M')df['invoicedate'] = df['invoicedate'].dt.normalize()#Удаляем строки с пропусками и возвратамиdf_for_report = df.loc[(~df['description'].isnull()) &                       (~df['customerid'].isnull()) &                       (~df['invoiceno'].str.contains('C', case=False))]#Назначаем всем числовым столбцам правильные форматыconvert_dict = {'invoiceno': int, 'customerid': int, 'quantity': int, 'unitprice': float}df_for_report = df_for_report.astype(convert_dict)#Контроль проведенных преобразований# print(df_for_report.head(3))# print(df_for_report.dtypes)# print(df_for_report.isnull().sum())# print(df_for_report.info())#Выгружаем датасет в новый файл формата csvdf_for_report.to_csv('dataset_for_report.csv', sep=";", index=False)

На следующем этапе необходимо создать в БД новую таблицу. Сделать это можно как в режиме графического редактора с помощью утилиты pgAdmin, так и с помощью кода Python.

import psycopg2# Подключение к БДconn = psycopg2.connect("dbname='db' user='postgres' password='gfhjkm' host='localhost' port='5432'")print("Database opened successfully")# Создаем курсорcursor = conn.cursor()with conn:    cursor.execute("""            DROP TABLE IF EXISTS dataset;        """)    cursor.execute("""            CREATE TABLE IF NOT EXISTS dataset (              invoiceno INTEGER NOT NULL,               stockcode TEXT NOT NULL,              description TEXT NOT NULL,              quantity INTEGER NOT NULL,              invoicedate DATE NOT NULL,              unitprice REAL NOT NULL,              customerid INTEGER NOT NULL,              country TEXT NOT NULL);        """)print("Operation done successfully")# Закрываем соединение и курсорcursor.close()conn.close()

Скрипт прост, поэтому останавливаться подробно на нем я не буду. Еще шаг и мы загружаем данные в PostgreSQL. Я уже обсуждал этот момент в своих предыдущих публикациях, здесь скажу лишь суть. Для ускорения добавления большого количества данных в БД лучше отказаться от услуг библиотеки Pandas.

import psycopg2from datetime import datetimestart_time = datetime.now()# Подключение к БДconn = psycopg2.connect("dbname='db' user='postgres' password='gfhjkm' host='localhost' port='5432'")print("Database opened successfully")# Создаем курсорcursor = conn.cursor()# Открываем файл. Считываем его построчно с записью в БДwith open('dataset_for_report.csv', 'r') as f:    next(f)    cursor.copy_from(f, 'dataset',sep=';', columns=('invoiceno', 'stockcode', 'description', 'quantity',                                                    'invoicedate','unitprice', 'customerid', 'country'))    conn.commit()f.close()print("Operation done successfully")# Закрываем соединение и курсорcursor.close()conn.close()end_time = datetime.now()print('Duration: {}'.format(end_time - start_time))

На этом вся предварительная работа подошла к концу и можно непосредственно приступить к построению rfm-отчета. Как вы уже, наверное, успели догадаться главенствующую роль здесь будут играть запросы sql. Для первичной аналитики я решил выбрать именно этот инструмент, так как по большому счету при обработке больших массивов неагрегированных данных у специалистов нет особенного выбора (обсуждение экосистемы Hadoop я оставляю за скобками разговора). Прежде чем писать основной запрос для rfm-анализа необходимо подготовить три вспомогательные функции: сегментирование давности, частоты и суммы покупок.

Деление диапазона данных на сегменты это процесс простой и сложный одновременно. Его простота заключается в том, что вы можете использовать встроенные возможности (пример функции Pandas cut и qcut) или слепо перенять чужой опыт. Но сложность заключается в том, что получить оптимальные результаты таким путем не получится. Максимальный эффект может быть достигнут при учете сферы бизнеса, сложившейся маркетинговой политики, целей конкретного рекламно-маркетингового мероприятия. Если акция относительно затратная и отдел маркетинга хочет точно идентифицировать бизнес-партнеров, то следует использовать максимально допустимое число сегментов. Если нужно провести массовый контакт с покупателями, то можно размыть границы сократив это число. Какой бы вы не выбрали путь, всегда задавайте сами себе несколько вопросов: удалось ли захватить всех потенциально интересных клиентов в нужные группы, какова будет себестоимость взаимодействия для компании и возможно ли будет при таком разделении максимизировать прибыль. В публикации я привожу лишь шаблонную заготовку кода функций, чтобы при работе вы уже концентрировались лишь на разработке бизнес-логики.

--Функции для rfm-анализаcreate function func_recency(days integer) returns integer as $$    select case when days<90 then 1           when (days>=90) and (days<=180) then 2          else 3          end;$$ language sql;create function func_frequency(transactions integer) returns integer as $$    select case when transactions>50 then 1           when (transactions>=10) and (transactions<=50) then 2          else 3          end;$$ language sql;create function func_monetary(amount integer) returns integer as $$    select case when amount>10000 then 1           when (amount>=1000) and (amount<=10000) then 2          else 3          end;$$ language sql;

Теперь можно перейти к разбору скрипта, который строит rfm-отчет. Сначала мы накладываем ограничение на столбец с датами. Точка отсчета выбрана произвольно и задана в виде константы. Вы же в своем решении можете обойти этот недостаток и, отловив актуальную дату составления отчета, сдвинуться относительно нее на любой временной диапазон назад. Далее следует составление, по сути, сводной таблицы, где в первом поле мы считаем разницу между выбранной временной константой и последней датой покупки для каждого клиента, второе поле отвечает за частоту покупок, последнее за общую сумму покупок за период. Если применить к указанным полям наши функции, в итоге мы получим искомый rfm-отчет. Сохраняем sql-скрипт в отдельное представление и получаем возможность подключаться к нему из BI системы компании или использовать его в расчетах в ноутбуках JupyterLab.

-- rfm-анализselect d3.*, concat(d3.rfm_recency,d3.rfm_frequency,d3.rfm_monetary) as rfmfrom (select d2.customerid,date('2011-11-01')- max(d2.invoicedate) as recency,cast(count(distinct(d2.invoiceno)) as integer) as frequency,cast(sum(d2.amount) as integer) as monetary,func_recency(date('2011-11-01')- max(d2.invoicedate)) as rfm_recency,func_frequency(cast(count(distinct(d2.invoiceno))as integer)) as rfm_frequency,func_monetary(cast(sum(d2.amount)as integer)) as rfm_monetaryfrom    (select d.*, d.quantity * d.unitprice as amount     from public.dataset as d      where d.invoicedate < date('2011-11-01')) as d2 group by d2.customeridorder by d2.customerid) as d3;

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

Чем еще можно дополнить отчет? Для начала хотелось бы получить средний чек для каждого сегмента. Почему данный показатель может быть важен. Дело в том, что любое рекламно-маркетинговое взаимодействие с клиентом небесплатно. Допустим, я вам скажу, что мы хотим потрать 50 рублей, чтобы мотивировать покупателя сделать очередную покупку в нашем магазине. Это много или мало? Ответить на этот вопрос очень сложно, если вы не знаете к какому сегменту относиться этот клиент и с какой вероятностью результативного действия обычно работают такие маркетинговые мероприятия. Но также важно иметь под рукой средний чек, как некую цель, за которую будет бороться рекламный отдел. Если компания ждет, что покупатель, пришедший в магазин, совершит операций на 5000 рублей, то это одно дело. Если в среднем чек не выше 500 рублей, то это совершенно другой расклад. Sql-запрос для данной метрики прикладываю ниже. Данные расчеты уже носят вспомогательный характер, поэтому удобнее всего их проводить в JupyterLab с подключением к БД.

-- Суммарные продажи в сегменте, количество клиентов, средний чекselect r.rfm,    sum(r.monetary) as total_amount,   count(r.rfm) as count_customer,   cast(avg(r.monetary/r.frequency) as integer) as avg_checkfrom public.report_rfm_analysis as r group by r.rfm;

Что еще можно узнать из предложенного датасета. Конечно, можно рассчитать структуру продаж по странам в абсолютном и относительном выражении. Особенно это важно для интернет-торговли, когда в один сегмент могут попасть люди со всех концов земного шара. Если проанализировать демо-данные по мы сразу получим, что в ряде групп 70% покупок в денежном выражении совершают жители одной страны. Неплохая подсказка сотрудникам отдела маркетинга.

-- Структура продаж по странам в абсолютном и относительном выраженииselect d2.rfm,d2.country,cast(sum(d2.amount) as integer) as amount_country,round(cast(sum(d2.amount)/sum(sum(d2.amount))over(partition by d2.rfm)*100 as numeric),2) as percent_total_amountfrom (select d.*, d.quantity * d.unitprice as amount, r.rfm      from public.dataset as d left join                                public.report_rfm_analysis as r on d.customerid = r.customerid      where d.invoicedate < date('2011-11-01')) as d2group by d2.rfm, d2.countryorder by d2.rfm, sum(d2.amount)desc;

Будущее всей торговли это строгая персонализация предложений и рекламы. У идеальной торговой площадки есть данные по каждому клиенту с момента его первой покупки: структура потребительской корзины за прошедший год, топ-7 товаров, топ-3 дня для совершения закупок, средний чек и т. д. Но для хранения всей этой информации требуется сначала много места в хранилище данных, а затем вычислительные мощности, чтобы состыковать людей и проводимые акции в режиме реального времени. В этом случае не будет ситуаций, что за всю историю бизнес-коммуникаций клиент покупал в вашем магазине только сыр, а вы постоянно шлете ему смс-сообщения о скидках на шоколад, бананы, охлажденную курицу. В наше время клиенты уже вряд ли оценят такую заботу о своем кошелке и скорее отнесут сообщения к категории спама. Если коммуникация с клиентом то обязательно максимально целевая. Для демонстрации данного подхода я реализовал расчет топ-3 дня по объему продаж в разрезе сегмент-страна.

-- Наименование дня месяцаcreate function func_day_of_week(number_day integer) returns text as $$select (string_to_array('sunday,monday,tuesday,wednesday,thursday,friday,saturday',','))[number_day];$$ language sql;-- Топ-3 дня по объему продаж в разрезе сегмент-странаselect d4.rfm, d4.country, max(d4.top) as top_3_daysfrom   (select d3.rfm, d3.country, string_agg(d3.day_of_week,', ')over(partition by d3.rfm, d3.country) as top   from (select d2.rfm, d2.country, d2.day_of_week,sum(d2.amount) as total_amount,     row_number ()over(partition by d2.rfm, d2.country order by d2.rfm, d2.country, sum(d2.amount)desc)from      (select r.rfm,              d.country,                          func_day_of_week(cast(to_char(d.invoicedate, 'D') as integer)) as day_of_week,             d.quantity * d.unitprice as amount      from public.dataset as d left join public.report_rfm_analysis as r on d.customerid = r.customerid      where d.invoicedate < date('2011-11-01')) as d2group by d2.rfm, d2.country, d2.day_of_weekorder by d2.rfm, d2.country, sum(d2.amount) desc) as d3  where d3.row_number <= 3) as d4group by d4.rfm, d4.country

Краткие выводы. RFM-анализ и вспомогательные расчеты для него удобнее всего выполнять путем комбинирования sql и ноутбуков Python. При сегментации клиентов важно учитывать сферу бизнеса, маркетинговую политику и цели рекламной акции. RFM-отчет не дает всей полноты картины, поэтому его лучше всего сопровождать вспомогательными расчетами.

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

Источник: habr.com
К списку статей
Опубликовано: 24.02.2021 16:18:54
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Python

Postgresql

Sql

Marketing

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru