У меня есть следующий набор данных:
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, которую мне не хватает? Возможно, мой метод сложен, вы знаете лучший подход?





Возможное решение 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
## ...
@Alexis - добавил разреженную версию Matrix на случай, если это тоже поможет.
Только что провел тест с ~400000 транзакций и таким же количеством товаров. Сработал быстро. Однако он может зависнуть, если у вас много продуктов и много транзакций.
Спасибо за ваше время и ответ @thelatemail, не знал о разреженном варианте !, для моего набора данных это было быстро! хорошего отличного дня!
Применяя ту же логику, которую вы используете, я думаю, что это может быть аккуратным способом сделать это.
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, под словом «масштаб» было ключевое слово для этого вопроса.
спасибо, это было отличное решение и отлично работает для набора данных 400 тыс. Хорошего отличного дня!
Спасибо, что дали мне знать! На этот вопрос много отличных ответов, приятно знать, что все они хорошо масштабируются для вашего набора данных.
Еще одно решение с использованием функций из 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 для этого, это очень здорово!. Спасибо за ваше время и ответ.
Интересный подход! Пожалуйста, позвольте мне проверить мой набор данных, у меня 400 тысяч транзакций, поэтому, когда вы упомянули «масштаб», я понял, что размер набора данных может быть проблемой ... Я вернусь с результатами!