produto cartesiano em pandas

108

Eu tenho dois dataframes do pandas:

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

Qual é a prática recomendada para obter seu produto cartesiano (claro, sem escrever explicitamente como eu)?

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

Respostas:

88

Se você tiver uma chave que é repetida para cada linha, poderá produzir um produto cartesiano usando merge (como faria no 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']]

Resultado:

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

Veja aqui a documentação: http://pandas.pydata.org/pandas-docs/stable/merging.html#brief-primer-on-merge-methods-relational-algebra

Matti John
fonte
6
Portanto, para fazer isso corretamente, é necessário primeiro encontrar um nome de coluna não utilizado, depois adicionar colunas fictícias com esse nome, mesclar e, finalmente, eliminar a coluna no resultado. Criar, ao contrário de ler, dados com pandas é uma dor
Bananach
68

Use pd.MultiIndex.from_productcomo um índice em um dataframe vazio, em seguida, redefina seu índice e pronto.

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

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

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

Fora:

   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
fonte
6
Eu acredito que esta é a maneira mais parecida com os pandas atualmente para pandas> = 0,21
shadi
6
Você tem votos negativos porque não mostrou como isso vai generalizar para qualquer coisa com mais de 1 coluna.
cs95
Esta função ( stackoverflow.com/a/58242079/1840471 ) generaliza para um número arbitrário de listas usando um dicionário de argumentos. É um pouco diferente da pergunta aqui, que pega o produto cartesiano de dois DataFrames (ou seja, não pega o produto de df1.col1e df.col2).
Max Ghenis
Na verdade, não acho que from_productpossa ser usado para esse problema.
Max Ghenis
34

Isso não vencerá uma competição de golfe de código e se baseia nas respostas anteriores - mas mostra claramente como a chave é adicionada e como funciona a junção. Isso cria 2 novos quadros de dados de listas e adiciona a chave para fazer o produto cartesiano.

Meu caso de uso era que eu precisava de uma lista de todos os IDs de loja para cada semana em minha lista. Então, criei uma lista de todas as semanas que queria ter e, em seguida, uma lista de todos os IDs de loja com os quais queria mapeá-los.

A mesclagem que escolhi à esquerda, mas seria semanticamente igual à interna nesta configuração. Você pode ver isso na documentação sobre fusão , que afirma que é um produto cartesiano se a combinação de teclas aparecer mais de uma vez em ambas as tabelas - que é o que configuramos.

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
fonte
25
Versão um pouco mais curta:days_and_stores = pd.merge(days.assign(key=0), stores.assign(key=0), on='key').drop('key', axis=1)
Eugene Pakhomov
Você mencionou crossJoin, mas está usando um dataframe pandas, não um dataframe spark.
Bryce Guinta de
Droga. Não estava pensando. Eu uso o Spark + Pandas juntos com tanta frequência, que quando vi a atualização do Spark, pensei neste post. Obrigado Bryce.
Rob Guderian de
32

Código mínimo necessário para este. Crie uma 'chave' comum para mesclar os dois cartesianos:

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

df_cartesian = df1.merge(df2, how='outer')
A.Kot
fonte
8
+ df_cartesian = df_cartesian.drop(columns=['key'])para limpar no final
StackG
22

Com encadeamento de métodos:

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

Como alternativa, pode-se contar com o produto cartesiano proporcionado pelos itertools:, itertools.productque evita criar uma chave temporária ou modificar o índice:

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)

Teste rápido:

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
fonte
4
Eu testei isso e funciona, mas é muito mais lento do que as respostas de mesclagem acima para grandes conjuntos de dados.
MrJ de
2

Se você não tem colunas sobrepostas, não deseja adicionar uma, e os índices dos frames de dados podem ser descartados, isso pode ser mais fácil:

df1.index[:] = df2.index[:] = 0
df_cartesian = df1.join(df2, how='outer')
df_cartesian.index[:] = range(len(df_cartesian))
Sergeyk
fonte
1
Isso parece promissor - mas recebo o erro na primeira linha: TypeError: '<class 'pandas.core.index.Int64Index'>' does not support mutable operations. posso contornar isso adicionando , index=[0,0]à definição de dataframe.
Racing Tadpole
2
Ou usando df1 = df1.set_index([[0]*len(df1)]))(e da mesma forma para df2).
Racing Tadpole
As edições de Racing Tadpole fizeram isso funcionar para mim - obrigado!
Sevyns
2

Aqui está uma função auxiliar para realizar um produto cartesiano simples com dois quadros de dados. A lógica interna lida com uma chave interna e evita mutilar quaisquer colunas que sejam chamadas de "chave" de qualquer um dos lados.

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)

mostra:

   number  key  digit
0       1    3      5
1       1    3      6
2       2    4      5
3       2    4      6
Mike T
fonte
Fiquei surpreso quando vi que uma pergunta de 7 anos tinha uma resposta de 4 horas - muito obrigado por isso :)
Bruno E
0

Você poderia começar pegando o produto cartesiano de df1.col1e df2.col3, em seguida, mesclar novamente df1para obter col2.

Aqui está uma função geral do produto cartesiano que possui um dicionário de listas:

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

Aplicar como:

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

Você pode usar numpy, pois poderia ser mais rápido. Suponha que você tenha duas séries como segue,

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

Você só precisa,

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

Acho que usar o pandas MultiIndex é a melhor ferramenta para o trabalho. Se você tiver uma lista de listas lists_list, chame pd.MultiIndex.from_product(lists_list)e itere sobre o resultado (ou use-o no índice DataFrame).

Ankur Kanoria
fonte