8
votes

Comment créer une matrice de co-occurrence des commandes de produits en python?

Supposons que nous ayons le dataframe suivant qui inclut les commandes des clients (order_id) et les produits contenus dans la commande individuelle (product_id):

       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

Il serait intéressant de savoir à quelle fréquence les paires de produits sont apparues ensemble dans le même panier.

Comment créer une matrice de cooccurrence en python qui ressemble à ceci:

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

Je serais très reconnaissant de votre aide!


2 commentaires

Il serait utile que vous énumériez ce que vous avez essayé jusqu'à présent.


@clarked In RI aurait pu le faire, mais je suis nouveau dans Python et je ne savais même pas par où commencer. Désolé pour le dérangement.


3 Réponses :


5
votes

Nous commençons par regrouper le df par order_id, et dans chaque groupe, calculons toutes les paires possibles. Notez que nous trions d'abord par product_id afin que les mêmes paires dans différents groupes soient toujours dans le même ordre

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

nous obtenons une liste de toutes les paires de toutes les commandes

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()

Maintenant, nous comptons les doublons

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

nous obtenons donc le nombre de chaque paire, essentiellement ce que vous recherchez

(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))

Remettre cela dans une table multi-produits est un peu de travail, le bit clé est de diviser les tuples en colonnes en appelant .apply(pd.Series) et en déplaçant éventuellement l'une des colonnes vers les noms de colonne via unstack :

{('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}

cela produit une forme `` compacte '' de la table que vous recherchez, qui ne comprend que les produits apparaissant dans au moins une paire

from collections import Counter

count_dict = dict(Counter(all_pairs))
count_dict

MISE À JOUR Voici une version plutôt simplifiée de ce qui précède, suite à diverses discussions dans les commentaires

[('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')]

et nous avons terminé. df_cross ci-dessous

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


8 commentaires

Solution intelligente!


@piterbarg Merci beaucoup pour votre réponse! J'ai pu faire fonctionner votre approche. La table count_dict est déjà très utile. Malheureusement, la table finale est un peu déroutante. Par exemple, la valeur (365, 3333) est 0, alors que (3333, 365) est la bonne 3. Existe-t-il un moyen de créer une matrice nxn, avec n étant le nombre de produits, qui est reflétée sur la diagonale?


heureux que cela ait été d'une certaine aide! Et désolé, je n'ai pas obtenu la réponse dans la forme finale. J'y réfléchirai un peu plus


@piterbarg J'ai pu y arriver en utilisant itertools.permutations au lieu de itertools.combinations, ce qui est logique si vous y réfléchissez. Il compte les 365 deux fois, contrairement à mon tableau, mais c'est en fait une bonne chose, et vous n'avez pas besoin de trier par product_id. Merci beaucoup encore, vous m'avez beaucoup aidé!


Génial, et merci de me l'avoir fait savoir, très apprécié


J'ai mis à jour la réponse avec une solution beaucoup plus propre et qui repose sur votre idée de permutations. Je pense que je vais supprimer la première partie désordonnée si cela ne vous dérange pas


@piterbarg N'hésitez pas à le faire


Bien que je viens de le tester et que votre nouvelle solution consomme beaucoup plus de mémoire, vous voudrez peut-être laisser l'ancienne ou créer un nouveau commentaire



0
votes

Cela devrait être un bon point de départ et peut être utile

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


0 commentaires

0
votes

Pivotez pour que chaque ligne corresponde à un produit, puis (df * row > 0).sum(1) chaque ligne sur (df * row > 0).sum(1) , qui indique le nombre de commandes dans lesquelles le produit co-produit avec chacun des autres produits.

>>> 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

La diagonale peut être modifiée selon la convention impliquée par la sortie de l'échantillon (qu'un produit coexiste avec lui-même s'il y en a au moins deux dans le même ordre) avec np.fill_diagonal(co_occ.values, (df - 1).sum(1)) .


0 commentaires