Правило ассоциации (Association Rule) - Лена Капаца
Правило ассоциации (Association Rule) by Лена Капаца May 15, 2021

Правило ассоциации (ассоциация) – это метод определения взаимосвязи элементов друг с другом. В отличие от Корреляции (Correlation), где отыскиваются взаимосвязи между Признаками (Feature) Датасета (Dataset), здесь идет речь о взаимосвязях внутри признака, например, дополняющие друг друга покупки в гипермаркете.

Ассоциацию можно измерить тремя распространенными способами. Для простоты адаптируем концепцию к тем же покупкам в гипермаркете.

$$S_M = \frac{Число\space{корзин}\space{с}\space{набором}\space{M}}{Общее\space{число}\space{корзин}}\space{, где}$$
$$S_M\space{–}\space{Поддержка}$$

$$С_{M_1 -> M_2} = \frac{Число\space{корзин}\space{с}\space{наборами}\space{M_1}\space{и}\space{M_2}}{Число\space{корзин}\space{с}\space{набором}\space{M_1}}\space{, где}$$
C_M_1 -> M_2\space{–}\space{Степень}\space{уверенности}

$$L_{M_1 -> M_2} = \frac{С_{M_1 -> M_2}}{S_{M_2}}\space{, где}$$
$$L_{M_1 -> M_2}\space{–}\space{Степень}\space{подъема}$$

Ассоциация и Apriori

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

!pip install apyori

import pandas as pd 
import numpy as np
import apyori
from apyori import apriori

Мы используем готовый датасет с историей покупок, содержащий идентификатор покупателя, дату покупки и купленные товары. Посмотрим, какого размера наш набор:

groceries = pd.read_csv('https://www.dropbox.com/s/qoo8k66kxke52cw/Groceries_dataset.csv?dl=1')
groceries.shape

Датасет состоит из 38+ тысяч записей с тремя признаками:

(38765, 3)

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

# Разобъем записи на группы по дате и подсчитаем количество уникальных товаров
groceries_time = pd.DataFrame(groceries.groupby('Date')['itemDescription'].nunique().index)

# Разобъем записи на группы по дате и подсчитаем количество уникальных посетителей
groceries_time['members_count'] = groceries.groupby('Date')['Member_number'].nunique().values

# Разобъем записи на группы по дате и запишем все купленные отдельным посетителем товары в один список
groceries_time['items_count'] = groceries.groupby('Date')['itemDescription'].nunique().values

groceries_time['items'] = groceries.groupby('Date')['itemDescription'].unique().values

# Зададим новый индекс
groceries_time.set_index('Date', inplace = True)
groceries_time.head()

Теперь понятно, сколько за раз купил каждый пришедший:

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

transactions = groceries_time['items'].tolist()

Теперь применим функцию apriori. Зададим функции параметры по умолчанию – какой объект рассматривать (transactions), минимальный уровень поддержки и уверенности, форма выдачи результата. Ко всему прочему нам понадобится вспомогательная функция inspect(), которая сформирует Датафрейм (DataFrame) из обнаруженных ассоциативных пар:

rules = apriori(transactions = transactions, min_support = 0.00030, min_confidance = 0.01, min_lift = 3, min_length = 2, max_length = 2)
results = list(rules)

def inspect(results):
    lhs = [tuple(result[2][0][0])[0] for result in results]
    rhs = [tuple(result[2][0][1])[0] for result in results]
    supports = [result[1] for result in results]
    confidences = [result[2][0][2] for result in results]
    lifts = [result[2][0][3] for result in results]
    return list(zip(lhs, rhs, supports, confidences, lifts))

resultsinDataFrame = pd.DataFrame(inspect(results), columns = ['Предмет #1', 'Предмет #2', 'Поддержка', 'Уверенность', 'Подъем'])
resultsinDataFrame.head()

Мы получили обширный список из 242 ассоциаций – настоящая кладезь для маркетолога! Посетитель гипермаркета и не поймет, почему в отделе с кухонными принадлежностями звучит ненавящевая реклама мужской косметики:

Посмотрим, какие ассоциации самые ярковыраженные, отсортировав результирующий датафрейм resultsDataFrame от большего к меньшему:

resultsinDataFrame.nlargest(n = 10, columns = 'Lift')

Наш Топ-10 выглядит весьма и весьма неожиданно:

Ликер и консервы! Вот это сочетание. Кухонные принадлежности и просекко – весьма вероятно и обыденно, впрочем, на перебор таких "очевидных" сочетаний может уйти много времени и рекламного бюджета, не так ли? В топ-3 попала пара "Кухонные принадлежности и Мужская косметика". Да, удивительное рядом. Так реклама становится гораздо точнее и эффективнее, а ведь это сокращает и баннерную нагрузку на конечных пользователей ритейла, что само по себе замечательно.

Ноутбук, не требующий дополнительной настройки на момент написания статьи, можно скачать здесь.

Автор оригинальной статьи: Gaurav Chopra

Фото: @montylov

© Лена Капаца. Все права защищены.