如何在python中创建产品订单的共现矩阵?

How to create a co-occurence matrix of product orders in python?

假设我们有以下数据框,其中包括客户订单 (order_id) 和单个订单包含的产品 (product_id):

import pandas as pd

df = pd.DataFrame({'order_id' : [1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3],
                   'product_id' : [365, 48750, 3333, 9877, 48750, 32001, 3333, 3333, 365, 11202, 365]})
print(df)

   order_id product_id
0         1        365
1         1      48750
2         1       3333
3         1       9877
4         2      48750
5         2      32001
6         2       3333
7         3       3333
8         3        365
9         3      11202
10        3        365

了解产品对出现在同一个购物篮中的频率会很有趣。

如何在 python 中创建如下所示的共现矩阵:

       365  48750  3333  9877  32001  11202
365      1      1     2     1      0      1
48750    1      0     2     1      1      0
3333     2      2     0     1      1      1
9877     1      1     1     0      0      0
32001    0      1     1     0      0      0
11202    1      0     1     0      0      0

非常感谢您的帮助!

我们首先将 df 按 order_id 分组,然后在每个组内计算所有可能的对。请注意,我们首先按 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}

将其放回叉积 table 有点工作,关键是通过调用 .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))

这会生成 'compact' 形式的 table 你之后只包含至少一对出现的产品


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

更新 在评论

中进行了各种讨论之后,这是上述内容的一个相当简化的版本
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

这应该是一个很好的起点,也许会有用

pd.crosstab(df['order_id '], df['product_id'])

product_id  365    3333   9877   11202  32001  48750
order_id 
1            1      1      1      0      0      1
2            0      1      0      0      1      1
3            2      1      0      1      0      0

旋转使每一行对应一个产品,然后将每一行映射到 (df * row > 0).sum(1),这表示该产品与其他每个产品同时出现的订单数。

>>> df = df.pivot_table(index='product_id', columns='order_id', aggfunc='size')
>>> co_occ = df.apply(lambda row: (df * row > 0).sum(1), axis=1)
>>> co_occ
product_id  365    3333   9877   11202  32001  48750
product_id                                          
365             2      2      1      1      0      1
3333            2      3      1      1      1      2
9877            1      1      1      0      0      1
11202           1      1      0      1      0      0
32001           0      1      0      0      1      1
48750           1      2      1      0      1      2

可以使用 np.fill_diagonal(co_occ.values, (df - 1).sum(1)).[=13 将对角线修改为示例输出所隐含的约定(如果至少有两个产品以相同的顺序出现,则产品与自身同时出现) =]