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]})

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


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

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)

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

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

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

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

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

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

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
)

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.