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!
3 Réponses :
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
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
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
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))
.
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.