我们首先按 order_id 对 df 进行分组,然后在每个组中计算所有可能的对。请注意,我们首先按 product_id 排序,因此不同组中的相同对始终按相同顺序排列
import itertools
all_pairs = []
for _, group in df.sort_values('product_id').groupby('order_id'):
all_pairs += list(itertools.combinations(group['product_id'],2))
all_pairs
我们从所有订单中获得所有对的列表
[('3333', '365'),
('3333', '48750'),
('3333', '9877'),
('365', '48750'),
('365', '9877'),
('48750', '9877'),
('32001', '3333'),
('32001', '48750'),
('3333', '48750'),
('11202', '3333'),
('11202', '365'),
('11202', '365'),
('3333', '365'),
('3333', '365'),
('365', '365')]
现在我们计算重复项
from collections import Counter
count_dict = dict(Counter(all_pairs))
count_dict
所以我们得到每对的计数,基本上是你想要的
{('3333', '365'): 3,
('3333', '48750'): 2,
('3333', '9877'): 1,
('365', '48750'): 1,
('365', '9877'): 1,
('48750', '9877'): 1,
('32001', '3333'): 1,
('32001', '48750'): 1,
('11202', '3333'): 1,
('11202', '365'): 2,
('365', '365'): 1}
将其放回叉积表中需要一些工作,关键是通过调用.apply(pd.Series) 将元组拆分为列,并最终通过unstack 将其中一列移动到列名:
(pd.DataFrame.from_dict(count_dict, orient='index')
.reset_index(0)
.set_index(0)['index']
.apply(pd.Series)
.rename(columns = {0:'pid1',1:'pid2'})
.reset_index()
.rename(columns = {0:'count'})
.set_index(['pid1', 'pid2'] )
.unstack()
.fillna(0))
这会生成一个“紧凑”形式的表格,其中仅包含至少出现一对的产品
count
pid2 3333 365 48750 9877
pid1
11202 1.0 2.0 0.0 0.0
32001 1.0 0.0 1.0 0.0
3333 0.0 3.0 2.0 1.0
365 0.0 1.0 1.0 1.0
48750 0.0 0.0 0.0 1.0
更新
这是上面的一个相当简化的版本,在 cmets 中进行了各种讨论
import numpy as np
import pandas as pd
from collections import Counter
# we start as in the original solution but use permutations not combinations
all_pairs = []
for _, group in df.sort_values('product_id').groupby('order_id'):
all_pairs += list(itertools.permutations(group['product_id'],2))
count_dict = dict(Counter(all_pairs))
# We create permutations for _all_ product_ids ... note we use unique() but also product(..) to allow for (365,265) combinations
total_pairs = list(itertools.product(df['product_id'].unique(),repeat = 2))
# pull out first and second elements separately
pid1 = [p[0] for p in total_pairs]
pid2 = [p[1] for p in total_pairs]
# and get the count for those permutations that exist from count_dict. Use 0
# for those that do not
count = [count_dict.get(p,0) for p in total_pairs]
# Now a bit of dataFrame magic
df_cross = pd.DataFrame({'pid1':pid1, 'pid2':pid2, 'count':count})
df_cross.set_index(['pid1','pid2']).unstack()
我们完成了。 df_cross下方
count
pid2 11202 32001 3333 365 48750 9877
pid1
11202 0 0 1 2 0 0
32001 0 0 1 0 1 0
3333 1 1 0 3 2 1
365 2 0 3 2 1 1
48750 0 1 2 1 0 1
9877 0 0 1 1 1 0