Eu tenho duas matrizes numpy que definem os eixos xey de uma grade. Por exemplo:
x = numpy.array([1,2,3])
y = numpy.array([4,5])
Eu gostaria de gerar o produto cartesiano dessas matrizes para gerar:
array([[1,4],[2,4],[3,4],[1,5],[2,5],[3,5]])
De uma maneira que não é terrivelmente ineficiente, pois preciso fazer isso várias vezes seguidas. Estou assumindo que convertê-los para uma lista Python e usar itertools.product
e voltar para uma matriz numpy não é a forma mais eficiente.
Respostas:
Consulte Usando numpy para criar uma matriz de todas as combinações de duas matrizes para obter uma solução geral para calcular o produto cartesiano de N matrizes.
fonte
meshgrid
+dstack
, embora mais rápida em alguns casos, pode levar a erros se você espera que o produto cartesiano seja construído na mesma ordem para matrizes do mesmo tamanho.meshgrid
+dstack
. Você poderia postar um exemplo?Um canônico
cartesian_product
(quase)Existem muitas abordagens para esse problema com propriedades diferentes. Alguns são mais rápidos que outros, e outros são de uso geral. Após muitos testes e ajustes, descobri que a função a seguir, que calcula uma dimensão n
cartesian_product
, é mais rápida que a maioria das outras para muitas entradas. Para um par de abordagens um pouco mais complexas, mas ainda mais rápidas em muitos casos, veja a resposta de Paul Panzer .Dada essa resposta, essa não é mais a implementação mais rápida do produto cartesiano em
numpy
que estou ciente. No entanto, acho que sua simplicidade continuará sendo uma referência útil para melhorias futuras:Vale ressaltar que essa função usa de
ix_
maneira incomum; enquanto o uso documentado deix_
é gerar índices em uma matriz, acontece que matrizes com a mesma forma podem ser usadas para atribuição transmitida. Muito obrigado a mgilson , que me inspirou a tentar usarix_
esse caminho, e a unutbu , que forneceu alguns comentários extremamente úteis sobre essa resposta, incluindo a sugestão de usonumpy.result_type
.Alternativas notáveis
Às vezes, é mais rápido gravar blocos de memória contíguos na ordem do Fortran. Essa é a base dessa alternativa,
cartesian_product_transpose
que se mostrou mais rápida em alguns hardwares do quecartesian_product
(veja abaixo). No entanto, a resposta de Paul Panzer, que usa o mesmo princípio, é ainda mais rápida. Ainda assim, incluo isso aqui para leitores interessados:Depois de entender a abordagem de Panzer, escrevi uma nova versão quase tão rápida quanto a dele e quase tão simples quanto
cartesian_product
:Parece haver alguma sobrecarga de tempo constante que a torna mais lenta que a do Panzer para pequenas entradas. Mas para entradas maiores, em todos os testes que eu executei, ele executa tão bem quanto sua implementação mais rápida (
cartesian_product_transpose_pp
).Nas seções a seguir, incluo alguns testes de outras alternativas. Agora eles estão um pouco desatualizados, mas, em vez de um esforço duplicado, decidi deixá-los aqui fora de interesse histórico. Para testes atualizados, consulte a resposta de Panzer, bem como a de Nico Schlömer .
Testes contra alternativas
Aqui está uma bateria de testes que mostram o aumento de desempenho que algumas dessas funções fornecem em relação a várias alternativas. Todos os testes mostrados aqui foram realizados em uma máquina quad-core, executando o Mac OS 10.12.5, Python 3.6.1 e
numpy
1.12.1. Sabe-se que variações no hardware e software produzem resultados diferentes, portanto, YMMV. Execute esses testes para ter certeza!Definições:
Resultado dos testes:
Em todos os casos,
cartesian_product
conforme definido no início desta resposta, é mais rápido.Para aquelas funções que aceitam um número arbitrário de matrizes de entrada, vale a pena verificar o desempenho
len(arrays) > 2
também. (Até que eu possa determinar por que causacartesian_product_recursive
um erro nesse caso, eu o removi desses testes.)Como esses testes mostram,
cartesian_product
permanece competitivo até que o número de matrizes de entrada ultrapasse (aproximadamente) quatro. Depois disso,cartesian_product_transpose
tem uma ligeira vantagem.Vale a pena reiterar que usuários com outros hardwares e sistemas operacionais podem ter resultados diferentes. Por exemplo, o unutbu reporta os seguintes resultados para esses testes usando o Ubuntu 14.04, Python 3.4.3 e
numpy
1.14.0.dev0 + b7050a9:Abaixo, mostro alguns detalhes sobre os testes anteriores que realizei nesse sentido. O desempenho relativo dessas abordagens mudou ao longo do tempo, para diferentes hardwares e diferentes versões do Python e
numpy
. Embora não seja imediatamente útil para pessoas que usam versões atualizadasnumpy
, ele ilustra como as coisas mudaram desde a primeira versão desta resposta.Uma alternativa simples:
meshgrid
+dstack
A resposta atualmente aceita usa
tile
erepeat
para transmitir duas matrizes juntas. Mas ameshgrid
função faz praticamente a mesma coisa. Aqui está a saídatile
erepeat
antes de ser passada para transposição:E aqui está a saída de
meshgrid
:Como você pode ver, é quase idêntico. Precisamos apenas remodelar o resultado para obter exatamente o mesmo resultado.
Em vez de remodelar neste ponto, porém, poderíamos passar a saída
meshgrid
paradstack
e remodelar depois, o que economiza algum trabalho:Ao contrário da afirmação deste comentário , não vi nenhuma evidência de que entradas diferentes produzirão saídas de formas diferentes e, como demonstrado acima, elas fazem coisas muito semelhantes, portanto seria muito estranho se o fizessem. Entre em contato se você encontrar um contra-exemplo.
Testando
meshgrid
+dstack
vs.repeat
+transpose
O desempenho relativo dessas duas abordagens mudou ao longo do tempo. Em uma versão anterior do Python (2.7), o resultado usando
meshgrid
+dstack
foi notavelmente mais rápido para pequenas entradas. (Observe que esses testes são de uma versão antiga desta resposta.) Definições:Para entrada de tamanho médio, vi uma aceleração significativa. Mas tentei novamente esses testes com versões mais recentes do Python (3.6.1) e
numpy
(1.12.1), em uma máquina mais nova. As duas abordagens são quase idênticas agora.Teste antigo
Novo teste
Como sempre, YMMV, mas isso sugere que nas versões recentes do Python e numpy, elas são intercambiáveis.
Funções generalizadas do produto
Em geral, podemos esperar que o uso de funções internas seja mais rápido para entradas pequenas, enquanto que para entradas grandes, uma função criada para fins específicos pode ser mais rápida. Além disso, para um produto n-dimensional generalizado,
tile
erepeat
não ajudará, porque eles não possuem análogos claros de alta dimensão. Portanto, vale a pena investigar o comportamento das funções criadas especificamente para esse fim.A maioria dos testes relevantes aparece no início desta resposta, mas aqui estão alguns dos testes realizados em versões anteriores do Python e
numpy
para comparação.A
cartesian
função definida em outra resposta costumava ter um desempenho muito bom para entradas maiores. (É o mesmo que a função chamadacartesian_product_recursive
acima.) A fim de compararcartesian
adstack_prodct
, usamos apenas duas dimensões.Aqui, novamente, o teste antigo mostrou uma diferença significativa, enquanto o novo teste mostra quase nenhum.
Teste antigo
Novo teste
Como antes,
dstack_product
ainda batecartesian
em escalas menores.Novo teste ( teste antigo redundante não mostrado )
Acho que essas distinções são interessantes e merecem ser registradas; mas eles são acadêmicos no final. Como mostraram os testes no início desta resposta, todas essas versões são quase sempre mais lentas que
cartesian_product
, definidas no início desta resposta - o que é um pouco mais lento que as implementações mais rápidas entre as respostas a essa pergunta.fonte
dtype=object
emarr = np.empty( )
permitiria a utilização de tipos diferentes no produto, por exemploarrays = [np.array([1,2,3]), ['str1', 'str2']]
.cartesian_product_tranpose
mais rápido do quecartesian_product
dependendo do SO da máquina, python ou versão numpy. Por exemplo, no Ubuntu 14.04, python3.4.3, numpy 1.14.0.dev0 + b7050a9,%timeit cartesian_product_transpose(x500,y500)
produz1000 loops, best of 3: 682 µs per loop
enquanto%timeit cartesian_product(x500,y500)
produz1000 loops, best of 3: 1.55 ms per loop
. Também estou descobrindo quecartesian_product_transpose
pode ser mais rápido quandolen(arrays) > 2
.cartesian_product
retorna uma matriz do tipo de ponto flutuante enquantocartesian_product_transpose
retorna uma matriz do mesmo tipo da primeira matriz (transmitida). A capacidade de preservar o dtype ao trabalhar com matrizes inteiras pode ser um motivo para os usuários favoreceremcartesian_product_transpose
.dtype = np.find_common_type([arr.dtype for arr in arrays], [])
poderia ser usado para encontrar o dtype comum de todas as matrizes, em vez de forçar o usuário a colocar a matriz que controla o dtype primeiro.Você pode apenas entender a lista normal em python
o que deve lhe dar
fonte
Eu também estava interessado nisso e fiz uma pequena comparação de desempenho, talvez um pouco mais clara do que na resposta do @ senderle.
Para duas matrizes (o caso clássico):
Para quatro matrizes:
(Observe que o comprimento das matrizes é de apenas algumas dezenas de entradas aqui.)
Código para reproduzir as parcelas:
fonte
Com base no trabalho exemplar do @ senderle, criei duas versões - uma para C e outra para layouts de Fortran - que geralmente são um pouco mais rápidas.
cartesian_product_transpose_pp
é - ao contrário do @ senderle,cartesian_product_transpose
que usa uma estratégia completamente diferente - uma versão docartesion_product
que usa o layout de memória de transposição mais favorável + algumas otimizações muito menores.cartesian_product_pp
fica com o layout de memória original. O que o torna mais rápido é o uso de cópias contíguas. As cópias contíguas acabam sendo muito mais rápidas que copiar um bloco inteiro de memória, embora apenas parte dela contenha dados válidos, é preferível a copiar apenas os bits válidos.Alguns perfplots. Criei separações para layouts C e Fortran, porque essas são tarefas diferentes da IMO.
Os nomes que terminam em 'pp' são as minhas abordagens.
1) muitos fatores minúsculos (2 elementos cada)
2) muitos fatores pequenos (4 elementos cada)
3) três fatores de igual comprimento
4) dois fatores de igual comprimento
Código (é necessário executar execuções separadas para cada gráfico b / c Não consegui descobrir como redefinir; também preciso editar / comentar in / out adequadamente):
fonte
arrays
em cartesian_product_transpose_pp (matrizes) exceder um determinado tamanho,MemoryError
ocorrerá. Nesta situação, eu gostaria que essa função produzisse pequenos pedaços de resultados. Eu postei uma pergunta sobre esse assunto. Você pode responder à minha pergunta? Obrigado.A partir de outubro de 2017, o numpy agora tem uma
np.stack
função genérica que usa um parâmetro de eixo. Utilizando-o, podemos ter um "produto cartesiano generalizado" usando a técnica "dstack and meshgrid":Nota sobre o
axis=-1
parâmetro Este é o último eixo (mais interno) do resultado. É equivalente a usaraxis=ndim
.Outro comentário: como os produtos cartesianos explodem muito rapidamente, a menos que precisemos realizar a matriz na memória por algum motivo, se o produto for muito grande, convém usar
itertools
e usar os valores imediatamente.fonte
Eu usei a resposta @kennytm por um tempo, mas ao tentar fazer o mesmo no TensorFlow, mas descobri que o TensorFlow não tem equivalente
numpy.repeat()
. Após um pouco de experimentação, acho que encontrei uma solução mais geral para vetores arbitrários de pontos.Para numpy:
e para o TensorFlow:
fonte
O pacote Scikit-learn possui uma rápida implementação exatamente disso:
Observe que a convenção desta implementação é diferente do que você deseja, se você se importa com a ordem da saída. Para o seu pedido exato, você pode fazer
fonte
De maneira mais geral, se você possui duas matrizes 2d numpy aeb, e deseja concatenar todas as linhas de a a cada linha de b (um produto cartesiano de linhas, como uma junção em um banco de dados), você pode usar esse método :
fonte
O mais rápido que você pode obter é combinando uma expressão de gerador com a função map:
Saídas (na verdade, toda a lista resultante é impressa):
ou usando uma expressão de gerador duplo:
Saídas (lista completa impressa):
Leve em consideração que a maior parte do tempo de computação é direcionada ao comando de impressão. Os cálculos do gerador são decentemente eficientes. Sem imprimir, os tempos de cálculo são:
para expressão de gerador + função de mapa e:
para a expressão de gerador duplo.
Se o que você realmente deseja é calcular o produto real de cada um dos pares de coordenadas, o mais rápido é resolvê-lo como um produto matricial numpy:
Saídas:
e sem impressão (neste caso, não economiza muito, pois apenas um pequeno pedaço da matriz é realmente impresso):
fonte
foo = a[:,None]*b
é mais rápida. Usando seu método de temporização semprint(foo)
, é 0,001103 s vs 0,002225 s. Usando timeit, são 304 μs vs 1,6 ms. Sabe-se que o Matrix é mais lento que o ndarray, então tentei seu código com o np.array, mas ainda é mais lento (1,57 ms) do que a transmissão.Isso também pode ser feito facilmente usando o método itertools.product
Resultado: matriz ([
[1, 4],
[1, 5],
[2, 4],
[2, 5],
[3, 4],
[3, 5]], dtype = int32)
Tempo de execução: 0.000155 s
fonte
No caso específico em que você precisa executar operações simples, como adição em cada par, é possível introduzir uma dimensão extra e permitir que a transmissão faça o trabalho:
Não tenho certeza se existe alguma maneira similar de obter os pares.
fonte
dtype
éfloat
que você pode fazer(a[:, None, None] + 1j * b[None, :, None]).view(float)
que é surpreendentemente rápido.