Частая пара продуктов с учетом транзакции в R

У меня есть следующий набор данных:

df <- data.frame(transaction = c("A1","A1","A1","B1","B1","C1","C1","C1",
                                 "C1","C1","D1","E1","E1","E1","E1","F1",
                                 "G1","G1","G1","H2","H2","H2","I3","I3"),
                 product = c("milk","eggs","butter",
                             "cocoa","tea",
                             "eggs","cookies","tea","toothpaste","water",
                             "cocoa",
                             "eggs","oil","tea","milk",
                             "ham",
                             "sugar","oil","milk",
                             "tea","milk","sugar",
                             "tea","milk"),
                 place_order = c(1,2,3,1,2,1,2,3,4,5,1,
                                 1,2,3,4,1,1,2,3,1,2,3,1,2),
                 client_id = c("x1","x1","x1","x2","x2",
                               "x3","x3","x3","x3","x3",
                               "x2","x4","x4","x4","x4",
                               "x2","x5","x5","x5",
                               "x3","x3","x3","x4","x4"))
df
   transaction    product place_order client_id
1           A1       milk           1        x1
2           A1       eggs           2        x1
3           A1     butter           3        x1
4           B1      cocoa           1        x2
5           B1        tea           2        x2
6           C1       eggs           1        x3
7           C1    cookies           2        x3
8           C1        tea           3        x3
9           C1 toothpaste           4        x3
10          C1      water           5        x3
11          D1      cocoa           1        x2
12          E1       eggs           1        x4
13          E1        oil           2        x4
14          E1        tea           3        x4
15          E1       milk           4        x4
16          F1        ham           1        x2
17          G1      sugar           1        x5
18          G1        oil           2        x5
19          G1       milk           3        x5
20          H2        tea           1        x3
21          H2       milk           2        x3
22          H2      sugar           3        x3
23          I3        tea           1        x4
24          I3       milk           2        x4

И я хочу самые частые пары продуктов. Поэтому я думаю, что группировка по транзакциям является правильным подходом, поскольку для каждой группы я могу сочетать комбинации. Например, транзакция A1 будет иметь такую ​​комбинацию:

> t(combn(sort(c("milk","eggs","butter")),2))
     [,1]     [,2]  
[1,] "butter" "eggs"
[2,] "butter" "milk"
[3,] "eggs"   "milk"

И я мог бы присоединиться к этим парам:

t(combn(sort(c("milk","eggs","butter")),2)) %>% as.data.frame() %>% unite("pair",V1,V2,sep = "_")
         pair
1 butter_eggs
2 butter_milk
3   eggs_milk

Но я застрял здесь, как я могу сделать это для каждой транзакции? Если бы у меня были пары для каждой группы, я мог бы подсчитать наиболее часто встречающиеся пары, поэтому ожидаемые пары (например, первые три):

pair      count
tea_milk  3
eggs_milk 2
milk_oil 2   

Есть ли функция в dplyr, которую мне не хватает? Возможно, мой метод сложен, вы знаете лучший подход?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
0
68
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Возможное решение crossproduct в таблице transaction и product. Я не уверен, насколько хорошо он будет масштабироваться, но, похоже, он работает:

tab <- crossprod(table(df$transaction, df$product))
tab[upper.tri(tab, diag=TRUE)] <- 0
tab <- as.data.frame.table(tab)
tab <- tab[tab$Freq > 0,]
tab <- tab[order(tab$Freq, decreasing=TRUE),]

##          Var1       Var2 Freq
##64         tea       milk    3
##39        milk       eggs    2
##42         tea       eggs    2
##62         oil       milk    2
##63       sugar       milk    2
##4         eggs     butter    1
## ...

С редким Matrix в случае, если это повышает эффективность:

tab <- crossprod(xtabs(value ~ transaction + product,
                data=cbind(df,value=1), sparse=TRUE))
summ <- summary(tab)
summ <- summ[summ$i != summ$j,]
summ <- summ[order(summ$x, decreasing=TRUE),]
data.frame(product1 = rownames(tab)[summ$i],
           product2 = colnames(tab)[summ$j],
           count    = summ$x)

##     product1   product2 count
##1        milk        tea     3
##2        eggs       milk     2
##3        milk        oil     2
##4        milk      sugar     2
##5        eggs        tea     2
##6      butter       eggs     1
## ...

Интересный подход! Пожалуйста, позвольте мне проверить мой набор данных, у меня 400 тысяч транзакций, поэтому, когда вы упомянули «масштаб», я понял, что размер набора данных может быть проблемой ... Я вернусь с результатами!

Alexis 17.03.2022 05:03

@Alexis - добавил разреженную версию Matrix на случай, если это тоже поможет.

thelatemail 17.03.2022 05:31

Только что провел тест с ~400000 транзакций и таким же количеством товаров. Сработал быстро. Однако он может зависнуть, если у вас много продуктов и много транзакций.

thelatemail 17.03.2022 05:54

Спасибо за ваше время и ответ @thelatemail, не знал о разреженном варианте !, для моего набора данных это было быстро! хорошего отличного дня!

Alexis 17.03.2022 23:31

Применяя ту же логику, которую вы используете, я думаю, что это может быть аккуратным способом сделать это.

get_combs <- function(x) {
  if (length(x$product)>1){
  t(combn(sort(x$product),2)) %>% as.data.frame() %>% unite("prods",V1,V2,sep = "_") %>% pull(prods)
  }
  else return(NULL)
}

df %>% 
  group_by(transaction) %>% 
  nest() %>% 
  mutate(comb = map(data, get_combs)) %>% 
  unnest(comb) %>% 
  ungroup() %>% 
  count(comb, sort = TRUE)

# A tibble: 21 × 2
   comb             n
   <chr>        <int>
 1 milk_tea         3
 2 eggs_milk        2
 3 eggs_tea         2
 4 milk_oil         2
 5 milk_sugar       2
 6 butter_eggs      1
 7 butter_milk      1
 8 cocoa_tea        1
 9 cookies_eggs     1
10 cookies_tea      1
# … with 11 more rows

Спасибо @ Matias1905, это подход с использованием tidyverse! Позвольте мне протестировать мой набор данных транзакций 400 000, под словом «масштаб» было ключевое слово для этого вопроса.

Alexis 17.03.2022 05:08

спасибо, это было отличное решение и отлично работает для набора данных 400 тыс. Хорошего отличного дня!

Alexis 17.03.2022 23:32

Спасибо, что дали мне знать! На этот вопрос много отличных ответов, приятно знать, что все они хорошо масштабируются для вашего набора данных.

Matias1905 18.03.2022 00:16

Еще одно решение с использованием функций из igraph:

library(tidyverse)
library(igraph)

df %>% 
  group_by(transaction) %>%
  summarise(new = ifelse(n() > 1, paste(combn(product, 2), collapse = "-"), product)) %>% 
  separate_rows(new, sep = "-") %>% 
  pull(new) %>% 
  make_undirected_graph() %>% 
  get.data.frame() %>% 
  group_by(from, to) %>% 
  count(sort = T)

выход

# A tibble: 23 x 3
# Groups:   from, to [23]
   from    to             n
   <chr>   <chr>      <int>
 1 milk    tea            3
 2 eggs    tea            2
 3 milk    oil            2
 4 milk    sugar          2
 5 cocoa   tea            1
 6 cookies toothpaste     1
 7 cookies water          1
 8 eggs    butter         1
 9 eggs    cocoa          1
10 eggs    cookies        1
# ... with 13 more rows

Здравствуйте @Maël, я протестировал ваше решение и отлично сработало. Я даже сделал ggraph для этого, это очень здорово!. Спасибо за ваше время и ответ.

Alexis 17.03.2022 23:30

Другие вопросы по теме