Использование алгоритма FP-growth для анализа ассоциаций между продуктами супермаркета

Супермаркеты по всему миру используют методы интеллектуального анализа данных для анализа модели покупок пользователей, чтобы сделать свой бизнес более эффективным, тем самым увеличивая свой бизнес и в то же время удовлетворяя потребности клиентов. Одно из таких приложений определяет, какие товары потребитель склонен покупать вместе, и анализирует модели «если-то», чтобы понять, покупает ли потребитель А, которые являются элементами Б, чтобы рекомендовать потребителю. Это помогает им размещать нужные продукты в нужном проходе, тем самым помогая потребителю вспомнить о своих потребностях. Это помогает потребителям запасать свои холодильники и дома в соответствии с их потребностями и в то же время сокращает время покупки.

В этом проекте я буду использовать реальный набор данных Instacart от Kaggle, который содержит данные о 3 миллионах заказов продуктов от 200 000 пользователей. Для каждого пользователя существует около 4–100 различных заказов в зависимости от того, сколько раз они совершали покупки в Instacart. Вот ссылка на набор данных:

Https://www.kaggle.com/c/instacart-market-basket-analysis/data

Я покажу реализацию на Spark из-за того, что она работает очень быстро на Databricks, поскольку использует распределенные внутрипроцессные вычисления и занимает гораздо меньше времени даже на таком большом наборе данных. К счастью, у нас уже есть библиотека FP (поиск часто-шаблонов) на spark, и поэтому я буду использовать ее для понимания шаблонов. Давайте начнем.

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

Начнем с импорта всех файлов во фрейм данных Spark:

#checking the files we have in the databricks file system

%fs ls /FileStore/tables

#enabling arrow just for a backup if pandas is needed at any point of time
import numpy as np
import pandas as pd

#Importing all the available files into the spark dataframe
aisles = spark.read.csv("/FileStore/tables/aisles.csv", header=True, inferSchema=True)
departments = spark.read.csv("/FileStore/tables/departments.csv", header=True, inferSchema=True)
order_products_prior = spark.read.csv("/FileStore/tables/order_products__prior.csv", header=True, inferSchema=True)
order_products_train = spark.read.csv("/FileStore/tables/order_products__train.csv", header=True, inferSchema=True)
orders = spark.read.csv("/FileStore/tables/orders.csv", header=True, inferSchema=True)
products = spark.read.csv("/FileStore/tables/products.csv", header=True, inferSchema=True)

# Create Temporary Tables to work using sql like commands
aisles.createOrReplaceTempView("aisles")
departments.createOrReplaceTempView("departments")
order_products_prior.createOrReplaceTempView("order_products_prior")
order_products_train.createOrReplaceTempView("order_products_train")
orders.createOrReplaceTempView("orders")
products.createOrReplaceTempView("products")

Вот словарь данных для всех файлов:

1) заказы (3,4 млн строк, 206 тыс пользователей):

order_id: идентификатор заказа

user_id: идентификатор клиента

eval_set: к какому набору оценок принадлежит этот порядок (см. SET, описанный ниже)

order_number: порядковый номер заказа для этого пользователя (1 = первый, n = n-й)

order_dow: день недели, когда был размещен заказ

order_hour_of_day: час дня, когда был размещен заказ

days_since_prior: количество дней с момента последнего заказа, максимум 30 (с НП для order_number = 1)

2) изделия (50к рядов):

product_id: идентификатор продукта

product_name: название продукта

aisle_id: внешний ключ

Department_id: внешний ключ

3) междурядья (134 ряда):

aisle_id: идентификатор прохода

проход: название прохода

4) отделы (21 ряд):

Department_id: идентификатор отдела

отдел: название отдела

5) order_products__SET (30м + рядов):

order_id: внешний ключ

product_id: внешний ключ

add_to_cart_order: порядок, в котором каждый товар был добавлен в корзину

reordered: 1, если этот продукт был заказан этим пользователем в прошлом, 0 в противном случае, где SET - один из четырех следующих оценочных наборов (eval_set в заказах):

«Предыдущий»: заказы, предшествующие последнему заказу этого пользователя (~ 3,2 млн заказов).

«Поезд»: данные по обучению, предоставленные участникам (~ 131 тыс. Заказов)

«Тест»: тестовые данные, зарезервированные для соревнований по машинному обучению (~ 75 тыс. Заказов)

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

#Top 5 orders in the orders dataframe
orders.show(n=5)

products.show(n=5)

order_products_train.show(n=5)

order_products_prior.show(n=5)

departments.show(n=5)

aisles.show(n=5)

Давайте теперь посмотрим, как общее количество заказов различается для разных дней недели.

%sql
select 
  count(order_id) as total_orders, 
  (case 
     when order_dow = '0' then 'Sunday'
     when order_dow = '1' then 'Monday'
     when order_dow = '2' then 'Tuesday'
     when order_dow = '3' then 'Wednesday'
     when order_dow = '4' then 'Thursday'
     when order_dow = '5' then 'Friday'
     when order_dow = '6' then 'Saturday'              
   end) as day_of_week 
  from orders  
 group by order_dow 
 order by total_orders desc

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

%sql
select 
  count(order_id) as total_orders, 
  order_hour_of_day as hour 
  from orders 
 group by order_hour_of_day 
 order by order_hour_of_day

Как видно из приведенного выше графика, большинство заказов размещается между 10:00 и 16:00. Для меня это было немного неожиданно, так как я ожидал, что пик приходиться на нерабочее время. Теперь давайте посмотрим, какой отдел лидирует по количеству предлагаемых продуктов.

select countbydept.*
  from (
  -- from product table, let's count number of records per dept
  -- and then sort it by count (highest to lowest) 
  select department_id, count(1) as counter
    from products
   group by department_id
   order by counter asc 
  ) as maxcount
inner join (
  -- let's repeat the exercise, but this time let's join
  -- products and departments tables to get a full list of dept and 
  -- prod count
  select
    d.department_id,
    d.department,
    count(1) as products
    from departments d
      inner join products p
         on p.department_id = d.department_id
   group by d.department_id, d.department 
   order by products desc
  ) countbydept 
  -- combine the two queries's results by matching the product count
  on countbydept.products = maxcount.counter

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

%sql
select count(opp.order_id) as orders, p.product_name as popular_product
  from order_products_prior opp, products p
 where p.product_id = opp.product_id 
 group by popular_product 
 order by orders desc 
 limit 10

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

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

# Organize the data by shopping basket
from pyspark.sql.functions import collect_set, col, count
rawData = spark.sql("select p.product_name, o.order_id from products p inner join order_products_train o where o.product_id = p.product_id")
baskets = rawData.groupBy('order_id').agg(collect_set('product_name').alias('items'))
baskets.createOrReplaceTempView('baskets')
rawData.show(5)
baskets.show(5)
display(baskets)

print((baskets.count(), len(baskets.columns)))
(131209, 2)

Всего у нас порядка 1,31,209 корзины товаров. Теперь давайте введем эти данные в алгоритм FPGrowth, доступный в Spark. Прежде чем сделать это, позвольте нам прояснить некоторые термины -

1) Поддержка:

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

2) Уверенность:

Эта мера определяет вероятность появления консеквента в тележке при условии, что в тележке уже есть антецеденты. Интуитивно предположим, что есть корзина {a, b, c}, имеющая поддержку 's', тогда, если мы анализируем ({a} подразумевает {b, c}), достоверность - это% транзакций, имеющих {a, b, c}, содержащий {b, c}.

3) Лифт:

Поднимите элементы управления для поддержки (частоты) консеквента при вычислении условной вероятности появления {Y} данного {X}. Подъем - самый важный параметр, который супермаркеты используют для размещения продуктов. Думайте об этом как о подъеме, который {X} дает для нашей уверенности при наличии {Y} в тележке. Перефразируя, подъем - это повышение вероятности наличия {Y} в тележке со знанием наличия {X} по сравнению с вероятностью наличия {Y} в тележке без каких-либо сведений о наличии {X}.

Ссылка для понимания этих терминов:

Https://towardsdatascience.com/association-rules-2-aa9a77241654

Давайте установим минимальную поддержку на 0,001, это означает, что для нашего анализа любая корзина, которую мы будем анализировать, должна встречаться по крайней мере 0,001 * 1,31,209 (131) раз, чтобы она учитывалась в нашем анализе частых паттернов.

%scala
import org.apache.spark.ml.fpm.FPGrowth

// Extract out the items 
val baskets_ds = spark.sql("select items from baskets").as[Array[String]].toDF("items")

// Use FPGrowth
val fpgrowth = new FPGrowth().setItemsCol("items").setMinSupport(0.001).setMinConfidence(0)
val model = fpgrowth.fit(baskets_ds)
%scala
// Display frequent itemsets
val mostPopularItemInABasket = model.freqItemsets
mostPopularItemInABasket.createOrReplaceTempView("mostPopularItemInABasket")

А теперь давайте посмотрим, какая корзина с товарами чаще всего встречается.

%sql
select items, freq from mostPopularItemInABasket where size(items) > 2 order by freq desc limit 20

Теперь давайте воспользуемся атрибутом правил ассоциации алгоритма роста FP, чтобы проанализировать ассоциации «если-то» и увидеть значения достоверности и подъема для различных элементов. Чтобы правило было полезным для Instacart, значение подъемной силы должно быть ›1.

%scala
// Display generated association rules.
val ifThen = model.associationRules
ifThen.createOrReplaceTempView("ifThen")
%sql
select * from ifThen where lift > 1 order by lift desc

Как видно из приведенной выше таблицы, в которой указаны правила уменьшения значений подъемной силы, если кто-то покупает [«Йогурт из клубники и ревеня»], очень высока вероятность покупки [«Йогурта из черники»]

Отображение в порядке уверенности приводит к следующему.

Примечание. Рост количественно определяет силу ассоциации, которая уникальна из-за антецедента, тогда как уверенность - это просто вероятность появления следствия при наличии антецедента.

%sql
select antecedent as `antecedent (if)`, consequent as `consequent (then)`, confidence from ifThen order by confidence desc limit 20

Ссылка: https://s3.us-east-2.amazonaws.com/databricks-dennylee/notebooks/Market+Basket+Analysis+using+Instacart+Online+Grocery+Dataset.html