Introduction

Apriori Algorithm is a foundational method in data mining used for discovering frequent itemsets and generating association rules. Its significance lies in its ability to identify relationships between items in large datasets which is particularly valuable in market basket analysis. https://www.geeksforgeeks.org/apriori-algorithm/ For example, if a grocery store finds that customers who buy bread often also buy butter, it can use this information to optimize product placement or marketing strategies.

Data

The Groceries data set contains 1 month (30 days) of real-world point-of-sale transaction data from a typical local grocery outlet. The data set contains 9835 transactions and the items are aggregated to 169 categories.

Reference: Michael Hahsler, Kurt Hornik, and Thomas Reutterer (2006) Implications of probabilistic data modeling for mining association rules. In M. Spiliopoulou, R. Kruse, C. Borgelt, A. Nuernberger, and W. Gaul, editors, From Data and Information Analysis to Knowledge Engineering, Studies in Classification, Data Analysis, and Knowledge Organization, pages 598–605. Springer-Verlag.

library(arules)
library(arulesViz)
library(networkD3)
library(dplyr)
library(tidyr)
library(plotly)
library(igraph)
library(ggraph)
library(tidyverse)
data("Groceries")

summary(Groceries)
## transactions as itemMatrix in sparse format with
##  9835 rows (elements/itemsets/transactions) and
##  169 columns (items) and a density of 0.02609146 
## 
## most frequent items:
##       whole milk other vegetables       rolls/buns             soda 
##             2513             1903             1809             1715 
##           yogurt          (Other) 
##             1372            34055 
## 
## element (itemset/transaction) length distribution:
## sizes
##    1    2    3    4    5    6    7    8    9   10   11   12   13   14   15   16 
## 2159 1643 1299 1005  855  645  545  438  350  246  182  117   78   77   55   46 
##   17   18   19   20   21   22   23   24   26   27   28   29   32 
##   29   14   14    9   11    4    6    1    1    1    1    3    1 
## 
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.000   2.000   3.000   4.409   6.000  32.000 
## 
## includes extended item information - examples:
##        labels  level2           level1
## 1 frankfurter sausage meat and sausage
## 2     sausage sausage meat and sausage
## 3  liver loaf sausage meat and sausage
itemFreqPlot <- itemFrequencyPlot(Groceries, topN = 10, type = "relative", main = "Top 10 Groceries")

rules <- apriori(Groceries, parameter = list(supp = 0.01, conf = 0.5, minlen = 2))
## Apriori
## 
## Parameter specification:
##  confidence minval smax arem  aval originalSupport maxtime support minlen
##         0.5    0.1    1 none FALSE            TRUE       5    0.01      2
##  maxlen target  ext
##      10  rules TRUE
## 
## Algorithmic control:
##  filter tree heap memopt load sort verbose
##     0.1 TRUE TRUE  FALSE TRUE    2    TRUE
## 
## Absolute minimum support count: 98 
## 
## set item appearances ...[0 item(s)] done [0.00s].
## set transactions ...[169 item(s), 9835 transaction(s)] done [0.00s].
## sorting and recoding items ... [88 item(s)] done [0.00s].
## creating transaction tree ... done [0.00s].
## checking subsets of size 1 2 3 4 done [0.00s].
## writing ... [15 rule(s)] done [0.00s].
## creating S4 object  ... done [0.00s].
rules.sorted <- sort(rules, by = "lift", decreasing = TRUE)

rules.clean <- rules.sorted[!is.redundant(rules.sorted)]

inspect(rules.clean[1:5])
##     lhs                                  rhs                support   
## [1] {citrus fruit, root vegetables}   => {other vegetables} 0.01037112
## [2] {tropical fruit, root vegetables} => {other vegetables} 0.01230300
## [3] {root vegetables, rolls/buns}     => {other vegetables} 0.01220132
## [4] {root vegetables, yogurt}         => {other vegetables} 0.01291307
## [5] {curd, yogurt}                    => {whole milk}       0.01006609
##     confidence coverage   lift     count
## [1] 0.5862069  0.01769192 3.029608 102  
## [2] 0.5845411  0.02104728 3.020999 121  
## [3] 0.5020921  0.02430097 2.594890 120  
## [4] 0.5000000  0.02582613 2.584078 127  
## [5] 0.5823529  0.01728521 2.279125  99
rules_df <- as(rules.clean, "data.frame")

rules_df$rule <- as.character(rules_df$rules)

rules_df <- separate(rules_df, rule, into = c("lhs", "rhs"), sep = "=>")

rules_df <- rules_df %>%
  mutate(lhs = gsub("[{ }]", "", lhs), rhs = gsub("[{ }]", "", rhs))
head(rules_df)
##                                                     rules    support confidence
## 7    {citrus fruit,root vegetables} => {other vegetables} 0.01037112  0.5862069
## 8  {tropical fruit,root vegetables} => {other vegetables} 0.01230300  0.5845411
## 13     {root vegetables,rolls/buns} => {other vegetables} 0.01220132  0.5020921
## 11         {root vegetables,yogurt} => {other vegetables} 0.01291307  0.5000000
## 1                           {curd,yogurt} => {whole milk} 0.01006609  0.5823529
## 2               {other vegetables,butter} => {whole milk} 0.01148958  0.5736041
##      coverage     lift count                          lhs             rhs
## 7  0.01769192 3.029608   102   citrusfruit,rootvegetables othervegetables
## 8  0.02104728 3.020999   121 tropicalfruit,rootvegetables othervegetables
## 13 0.02430097 2.594890   120    rootvegetables,rolls/buns othervegetables
## 11 0.02582613 2.584078   127        rootvegetables,yogurt othervegetables
## 1  0.01728521 2.279125    99                  curd,yogurt       wholemilk
## 2  0.02003050 2.244885   113       othervegetables,butter       wholemilk
nodes <- data.frame(name = unique(c(rules_df$lhs, rules_df$rhs)), id = 0:(length(unique(c(rules_df$lhs, rules_df$rhs))) - 1))
links <- merge(nodes, rules_df, by.x = "name", by.y = "lhs")
links <- links %>% rename(source = id)
links <- merge(nodes, links, by.x = "name", by.y = "rhs")
links <- links %>% rename(target = id, value = lift)
links <- links[, c("source", "target", "value")]

nodes$group <- 1

forceNetwork(Links = links, Nodes = nodes,
             Source = "source", Target = "target",
             Value = "value", NodeID = "name",
             Group = "group",  
             opacity = 0.9, zoom = TRUE)
labels <- unique(c(rules_df$lhs, rules_df$rhs))


source_indices <- as.integer(factor(rules_df$lhs, levels = labels)) - 1
target_indices <- as.integer(factor(rules_df$rhs, levels = labels)) - 1

sankey_plot <- plot_ly(
  type = "sankey",
  node = list(
    pad = 15,
    thickness = 20,
    line = list(color = "black", width = 0.5),
    label = labels ),
  link = list(
    source = source_indices,
    target = target_indices,
    value = rules_df$lift)) %>%
  layout(
    title = "Sankey Diagram of Grocery Associations",
    font = list(size = 10),
    xaxis = list(showgrid = FALSE, zeroline = FALSE),
    yaxis = list(showgrid = FALSE, zeroline = FALSE))

sankey_plot
rules.milk <- apriori(Groceries, parameter = list(supp = 0.01, conf = 0.5),
                      appearance = list(default = "lhs", rhs = "whole milk"),
                      control = list(verbose = FALSE))
rules.milk.sorted <- sort(rules.milk, by = "confidence", decreasing = TRUE)

rules_milk_df <- as(rules.milk.sorted, "data.frame")

rules_milk_df <- separate(rules_milk_df, rules, into = c("lhs", "rhs"), sep = "=>") %>%
  mutate(lhs = gsub("[{ }]", "", lhs),
         rhs = gsub("[{ }]", "", rhs))

edges <- rules_milk_df %>%
  select(lhs, rhs, lift) %>%
  rename(from = lhs, to = rhs, weight = lift)

g <- graph_from_data_frame(edges, directed = TRUE)

ggraph(g, layout = "kk") + 
  geom_edge_link(aes(edge_width = weight), alpha = 0.3, color = "yellow") +
  geom_node_point(size = 5, color = "red") +
  geom_node_text(aes(label = name), repel = TRUE, size = 5, check_overlap = TRUE) +  
  theme_void() +
  ggtitle("Whole Milk")

Conclusion

The analysis of the Groceries dataset using the Apriori association rules revealed valuable insights into customer purchasing behavior. Whole milk, bread, butter, yogurt, and vegetables were identified as the most frequently purchased items, with strong associations between dairy products, bakery items, and vegetables.