produit cartésien chez les pandas

109

J'ai deux dataframes pandas:

from pandas import DataFrame
df1 = DataFrame({'col1':[1,2],'col2':[3,4]})
df2 = DataFrame({'col3':[5,6]})     

Quelle est la meilleure pratique pour obtenir leur produit cartésien (bien sûr sans l'écrire explicitement comme moi)?

#df1, df2 cartesian product
df_cartesian = DataFrame({'col1':[1,2,1,2],'col2':[3,4,3,4],'col3':[5,5,6,6]})
Idok
la source

Réponses:

88

Si vous avez une clé qui est répétée pour chaque ligne, vous pouvez alors produire un produit cartésien en utilisant la fusion (comme vous le feriez en SQL).

from pandas import DataFrame, merge
df1 = DataFrame({'key':[1,1], 'col1':[1,2],'col2':[3,4]})
df2 = DataFrame({'key':[1,1], 'col3':[5,6]})

merge(df1, df2,on='key')[['col1', 'col2', 'col3']]

Production:

   col1  col2  col3
0     1     3     5
1     1     3     6
2     2     4     5
3     2     4     6

Voir ici pour la documentation: http://pandas.pydata.org/pandas-docs/stable/merging.html#brief-primer-on-merge-methods-relational-algebra

Matti John
la source
6
Donc, pour faire cela correctement, il faut d'abord trouver un nom de colonne inutilisé, puis ajouter des colonnes factices avec ce nom, fusionner et enfin déposer la colonne sur le résultat? Créer, par opposition à la lecture, des données avec des pandas est juste une douleur
Bananach
69

Utilisez pd.MultiIndex.from_productcomme index dans un dataframe autrement vide, puis réinitialisez son index, et vous avez terminé.

a = [1, 2, 3]
b = ["a", "b", "c"]

index = pd.MultiIndex.from_product([a, b], names = ["a", "b"])

pd.DataFrame(index = index).reset_index()

en dehors:

   a  b
0  1  a
1  1  b
2  1  c
3  2  a
4  2  b
5  2  c
6  3  a
7  3  b
8  3  c
Gijs
la source
6
Je crois que c'est le moyen le plus semblable aux pandas ces jours-ci pour les pandas> = 0,21
shadi
6
Vous avez des votes négatifs parce que vous n'avez pas montré comment cela va se généraliser pour tout ce qui a plus d'une colonne.
cs95
Cette fonction ( stackoverflow.com/a/58242079/1840471 ) le généralise à un nombre arbitraire de listes en utilisant un dict d'arguments. C'est un peu différent de la question ici, qui prend le produit cartésien de deux DataFrames (c'est-à-dire qu'il ne prend pas le produit de df1.col1et df.col2).
Max Ghenis
En fait, je ne pense pas qu'il from_productpuisse être utilisé pour ce problème.
Max Ghenis
34

Cela ne gagnera pas une compétition de golf de code et emprunte aux réponses précédentes - mais montre clairement comment la clé est ajoutée et comment la jointure fonctionne. Cela crée 2 nouvelles trames de données à partir de listes, puis ajoute la clé sur laquelle effectuer le produit cartésien.

Mon cas d'utilisation était que j'avais besoin d'une liste de tous les identifiants de magasin pour chaque semaine de ma liste. J'ai donc créé une liste de toutes les semaines que je voulais avoir, puis une liste de tous les identifiants de magasin avec lesquels je voulais les mapper.

La fusion que j'ai choisie est gauche, mais serait sémantiquement la même que celle interne dans cette configuration. Vous pouvez le voir dans la documentation sur la fusion , qui indique qu'il fait un produit cartésien si la combinaison de touches apparaît plus d'une fois dans les deux tables - ce que nous avons configuré.

days = pd.DataFrame({'date':list_of_days})
stores = pd.DataFrame({'store_id':list_of_stores})
stores['key'] = 0
days['key'] = 0
days_and_stores = days.merge(stores, how='left', on = 'key')
days_and_stores.drop('key',1, inplace=True)
Rob Guderian
la source
25
Version un peu plus courte:days_and_stores = pd.merge(days.assign(key=0), stores.assign(key=0), on='key').drop('key', axis=1)
Eugene Pakhomov
Vous mentionnez crossJoin, mais vous utilisez un dataframe pandas, pas un dataframe spark.
Bryce Guinta
Dang. Je ne pensais pas. J'utilise si souvent spark + pandas ensemble que quand j'ai vu la mise à jour déclencher, j'ai pensé à ce post. Merci Bryce.
Rob Guderian
32

Code minimal nécessaire pour celui-ci. Créez une `` clé '' commune pour fusionner les deux cartésiens:

df1['key'] = 0
df2['key'] = 0

df_cartesian = df1.merge(df2, how='outer')
A.Kot
la source
8
+ df_cartesian = df_cartesian.drop(columns=['key'])pour nettoyer à la fin
StackG
22

Avec chaînage de méthodes:

product = (
    df1.assign(key=1)
    .merge(df2.assign(key=1), on="key")
    .drop("key", axis=1)
)
pomber
la source
14

En alternative, on peut s'appuyer sur le produit cartésien fourni par itertools:, itertools.productce qui évite de créer une clé temporaire ou de modifier l'index:

import numpy as np 
import pandas as pd 
import itertools

def cartesian(df1, df2):
    rows = itertools.product(df1.iterrows(), df2.iterrows())

    df = pd.DataFrame(left.append(right) for (_, left), (_, right) in rows)
    return df.reset_index(drop=True)

Test rapide:

In [46]: a = pd.DataFrame(np.random.rand(5, 3), columns=["a", "b", "c"])

In [47]: b = pd.DataFrame(np.random.rand(5, 3), columns=["d", "e", "f"])    

In [48]: cartesian(a,b)
Out[48]:
           a         b         c         d         e         f
0   0.436480  0.068491  0.260292  0.991311  0.064167  0.715142
1   0.436480  0.068491  0.260292  0.101777  0.840464  0.760616
2   0.436480  0.068491  0.260292  0.655391  0.289537  0.391893
3   0.436480  0.068491  0.260292  0.383729  0.061811  0.773627
4   0.436480  0.068491  0.260292  0.575711  0.995151  0.804567
5   0.469578  0.052932  0.633394  0.991311  0.064167  0.715142
6   0.469578  0.052932  0.633394  0.101777  0.840464  0.760616
7   0.469578  0.052932  0.633394  0.655391  0.289537  0.391893
8   0.469578  0.052932  0.633394  0.383729  0.061811  0.773627
9   0.469578  0.052932  0.633394  0.575711  0.995151  0.804567
10  0.466813  0.224062  0.218994  0.991311  0.064167  0.715142
11  0.466813  0.224062  0.218994  0.101777  0.840464  0.760616
12  0.466813  0.224062  0.218994  0.655391  0.289537  0.391893
13  0.466813  0.224062  0.218994  0.383729  0.061811  0.773627
14  0.466813  0.224062  0.218994  0.575711  0.995151  0.804567
15  0.831365  0.273890  0.130410  0.991311  0.064167  0.715142
16  0.831365  0.273890  0.130410  0.101777  0.840464  0.760616
17  0.831365  0.273890  0.130410  0.655391  0.289537  0.391893
18  0.831365  0.273890  0.130410  0.383729  0.061811  0.773627
19  0.831365  0.273890  0.130410  0.575711  0.995151  0.804567
20  0.447640  0.848283  0.627224  0.991311  0.064167  0.715142
21  0.447640  0.848283  0.627224  0.101777  0.840464  0.760616
22  0.447640  0.848283  0.627224  0.655391  0.289537  0.391893
23  0.447640  0.848283  0.627224  0.383729  0.061811  0.773627
24  0.447640  0.848283  0.627224  0.575711  0.995151  0.804567
Svend
la source
4
J'ai testé cela et cela fonctionne, mais c'est beaucoup plus lent que les réponses de fusion ci-dessus pour les grands ensembles de données.
MrJ
2

Si vous n'avez pas de colonnes qui se chevauchent, que vous ne voulez pas en ajouter et que les index des trames de données peuvent être supprimés, cela peut être plus facile:

df1.index[:] = df2.index[:] = 0
df_cartesian = df1.join(df2, how='outer')
df_cartesian.index[:] = range(len(df_cartesian))
Sergeyk
la source
1
Cela semble prometteur - mais j'obtiens l'erreur sur la première ligne: TypeError: '<class 'pandas.core.index.Int64Index'>' does not support mutable operations. je peux contourner cela en ajoutant , index=[0,0]à la définition du dataframe.
Racing Tadpole
2
Ou en utilisant df1 = df1.set_index([[0]*len(df1)]))(et de même pour df2).
Racing Tadpole
Les modifications de Racing Tadpole ont fait ce travail pour moi - merci!
Sevyns
2

Voici une fonction d'aide pour réaliser un produit cartésien simple avec deux blocs de données. La logique interne gère l'utilisation d'une clé interne et évite de modifier les colonnes qui se trouvent être nommées «clé» de chaque côté.

import pandas as pd

def cartesian(df1, df2):
    """Determine Cartesian product of two data frames."""
    key = 'key'
    while key in df1.columns or key in df2.columns:
        key = '_' + key
    key_d = {key: 0}
    return pd.merge(
        df1.assign(**key_d), df2.assign(**key_d), on=key).drop(key, axis=1)

# Two data frames, where the first happens to have a 'key' column
df1 = pd.DataFrame({'number':[1, 2], 'key':[3, 4]})
df2 = pd.DataFrame({'digit': [5, 6]})
cartesian(df1, df2)

spectacles:

   number  key  digit
0       1    3      5
1       1    3      6
2       2    4      5
3       2    4      6
Mike T
la source
a fait une double prise quand j'ai vu qu'une question de 7 ans avait une réponse de 4 heures - merci beaucoup pour cela :)
Bruno E
0

Vous pouvez commencer par prendre le produit cartésien de df1.col1et df2.col3, puis fusionner à nouveau df1pour obtenir col2.

Voici une fonction produit cartésienne générale qui prend un dictionnaire de listes:

def cartesian_product(d):
    index = pd.MultiIndex.from_product(d.values(), names=d.keys())
    return pd.DataFrame(index=index).reset_index()

Postulez comme:

res = cartesian_product({'col1': df1.col1, 'col3': df2.col3})
pd.merge(res, df1, on='col1')
#  col1 col3 col2
# 0   1    5    3
# 1   1    6    3
# 2   2    5    4
# 3   2    6    4
Max Ghenis
la source
0

Vous pouvez utiliser numpy car cela pourrait être plus rapide. Supposons que vous ayez deux séries comme suit,

s1 = pd.Series(np.random.randn(100,))
s2 = pd.Series(np.random.randn(100,))

Vous avez juste besoin,

pd.DataFrame(
    s1[:, None] @ s2[None, :], 
    index = s1.index, columns = s2.index
)
Yanqi Huang
la source
-1

Je trouve que l'utilisation de pandas MultiIndex est le meilleur outil pour le travail. Si vous avez une liste de listes lists_list, appelez pd.MultiIndex.from_product(lists_list)et parcourez le résultat (ou utilisez-le dans l'index DataFrame).

Ankur Kanoria
la source