Usando numpy para criar uma matriz de todas as combinações de duas matrizes

143

Estou tentando percorrer o espaço de parâmetros de uma função de 6 parâmetros para estudar seu comportamento numérico antes de tentar fazer algo complexo com ela, então estou procurando uma maneira eficiente de fazer isso.

Minha função usa valores de flutuação dados uma matriz numpy de 6 dim como entrada. O que eu tentei fazer inicialmente foi o seguinte:

Primeiro, criei uma função que pega 2 matrizes e gera uma matriz com todas as combinações de valores das duas matrizes

from numpy import *
def comb(a,b):
    c = []
    for i in a:
        for j in b:
            c.append(r_[i,j])
    return c

Então eu costumava reduce()aplicar isso a m cópias da mesma matriz:

def combs(a,m):
    return reduce(comb,[a]*m)

E então eu avalio minha função assim:

values = combs(np.arange(0,1,0.1),6)
for val in values:
    print F(val)

Isso funciona, mas é muito lento. Eu sei que o espaço dos parâmetros é enorme, mas isso não deve ser tão lento. Eu apenas amostramos 10 6 (um milhão) de pontos neste exemplo e levou mais de 15 segundos apenas para criar a matriz values.

Você conhece alguma maneira mais eficiente de fazer isso com numpy?

Posso modificar a maneira como a função Fleva seus argumentos, se for necessário.

Rafael S. Calsaverini
fonte
Para o produto cartesiano mais rápido que encontrei, veja esta resposta . (Uma vez que a questão é formulada de forma bastante diferente de um presente, considero que as questões não são duplicados, mas a melhor solução para as duas perguntas é a mesma.)
senderle

Respostas:

127

Na versão mais recente do numpy(> 1.8.x), numpy.meshgrid()fornece uma implementação muito mais rápida:

solução de @ pv

In [113]:

%timeit cartesian(([1, 2, 3], [4, 5], [6, 7]))
10000 loops, best of 3: 135 µs per loop
In [114]:

cartesian(([1, 2, 3], [4, 5], [6, 7]))

Out[114]:
array([[1, 4, 6],
       [1, 4, 7],
       [1, 5, 6],
       [1, 5, 7],
       [2, 4, 6],
       [2, 4, 7],
       [2, 5, 6],
       [2, 5, 7],
       [3, 4, 6],
       [3, 4, 7],
       [3, 5, 6],
       [3, 5, 7]])

numpy.meshgrid()use apenas para 2D, agora é capaz de ND. Nesse caso, 3D:

In [115]:

%timeit np.array(np.meshgrid([1, 2, 3], [4, 5], [6, 7])).T.reshape(-1,3)
10000 loops, best of 3: 74.1 µs per loop
In [116]:

np.array(np.meshgrid([1, 2, 3], [4, 5], [6, 7])).T.reshape(-1,3)

Out[116]:
array([[1, 4, 6],
       [1, 5, 6],
       [2, 4, 6],
       [2, 5, 6],
       [3, 4, 6],
       [3, 5, 6],
       [1, 4, 7],
       [1, 5, 7],
       [2, 4, 7],
       [2, 5, 7],
       [3, 4, 7],
       [3, 5, 7]])

Observe que a ordem do resultante final é um pouco diferente.

CT Zhu
fonte
14
np.stack(np.meshgrid([1, 2, 3], [4, 5], [6, 7]), -1).reshape(-1, 3)vai dar a ordem certa
Eric
@CT Zhu Existe uma maneira fácil de transformar isso para que a matriz a que contém as diferentes matrizes como colunas seja usada como entrada?
Dole
2
Deve-se notar que meshgrid só funciona para conjuntos de intervalo menor, eu tenho um grande e eu recebo erro: ValueError: dimensão máxima suportada para uma ndarray é de 32, encontrou 69
mikkom
157

Aqui está uma implementação numpy pura. É cerca de 5 vezes mais rápido que o uso de ferramentas.


import numpy as np

def cartesian(arrays, out=None):
    """
    Generate a cartesian product of input arrays.

    Parameters
    ----------
    arrays : list of array-like
        1-D arrays to form the cartesian product of.
    out : ndarray
        Array to place the cartesian product in.

    Returns
    -------
    out : ndarray
        2-D array of shape (M, len(arrays)) containing cartesian products
        formed of input arrays.

    Examples
    --------
    >>> cartesian(([1, 2, 3], [4, 5], [6, 7]))
    array([[1, 4, 6],
           [1, 4, 7],
           [1, 5, 6],
           [1, 5, 7],
           [2, 4, 6],
           [2, 4, 7],
           [2, 5, 6],
           [2, 5, 7],
           [3, 4, 6],
           [3, 4, 7],
           [3, 5, 6],
           [3, 5, 7]])

    """

    arrays = [np.asarray(x) for x in arrays]
    dtype = arrays[0].dtype

    n = np.prod([x.size for x in arrays])
    if out is None:
        out = np.zeros([n, len(arrays)], dtype=dtype)

    m = n / arrays[0].size
    out[:,0] = np.repeat(arrays[0], m)
    if arrays[1:]:
        cartesian(arrays[1:], out=out[0:m, 1:])
        for j in xrange(1, arrays[0].size):
            out[j*m:(j+1)*m, 1:] = out[0:m, 1:]
    return out
pv.
fonte
46
Já pensou em enviar isso para ser incluído em numpy? esta não é a primeira vez que procurei essa funcionalidade e encontrei sua postagem.
Endolith 12/04
1
Há um erro nesta implementação. Para matrizes de seqüências de caracteres, por exemplo: matrizes [0] .dtype = "| S3" e matrizes [1] .dtype = "| S5". Portanto, há uma necessidade em encontrar maior string de entrada e use seu tipo na out = np.zeros ([N, len (matrizes)], dtipo = dtipo)
norecces
38
FYI: parece ter entrado no pacote scikit-learn emfrom sklearn.utils.extmath import cartesian
Gus
2
Acabei de perceber: isso é um pouco diferente das combinações itertools.com, pois essa função respeita a ordem dos valores, enquanto as combinações não, portanto, essa função retorna mais valores do que as combinações. Ainda muito impressionante, mas infelizmente não o que eu estava procurando :(
David Marx
6
TypeError: slice indices must be integers or None or have an __index__ methodjogado porcartesian(arrays[1:], out=out[0:m,1:])
Boern 25/09
36

Em geral, itertools.combinations é a maneira mais rápida de obter combinações de um contêiner Python (se você realmente deseja combinações, isto é, arranjos SEM repetições e independentes da ordem; não é isso que seu código parece estar fazendo, mas não posso diga se é porque seu código está com defeito ou porque você está usando a terminologia errada).

Se você quiser algo diferente de combinações, talvez outros iteradores em iterols, productou permutations, possam atendê-lo melhor. Por exemplo, parece que seu código é aproximadamente o mesmo que:

for val in itertools.product(np.arange(0, 1, 0.1), repeat=6):
    print F(val)

Todos esses iteradores produzem tuplas, não listas ou matrizes numpy, portanto, se seu F for exigente em obter especificamente uma matriz numpy, você terá que aceitar a sobrecarga extra de construir ou limpar e preencher novamente uma a cada etapa.

Alex Martelli
fonte
8

Você pode fazer algo assim

import numpy as np

def cartesian_coord(*arrays):
    grid = np.meshgrid(*arrays)        
    coord_list = [entry.ravel() for entry in grid]
    points = np.vstack(coord_list).T
    return points

a = np.arange(4)  # fake data
print(cartesian_coord(*6*[a])

que dá

array([[0, 0, 0, 0, 0, 0],
   [0, 0, 0, 0, 0, 1],
   [0, 0, 0, 0, 0, 2],
   ..., 
   [3, 3, 3, 3, 3, 1],
   [3, 3, 3, 3, 3, 2],
   [3, 3, 3, 3, 3, 3]])
felippe
fonte
2
Existe uma maneira de o NumPy aceitar mais de 32 matrizes para o meshgrid? Este método funciona para mim desde que eu não passe mais de 32 matrizes.
Joelmob 29/09/14
8

A seguinte implementação numpy deve ser de aprox. 2x a velocidade da resposta dada:

def cartesian2(arrays):
    arrays = [np.asarray(a) for a in arrays]
    shape = (len(x) for x in arrays)

    ix = np.indices(shape, dtype=int)
    ix = ix.reshape(len(arrays), -1).T

    for n, arr in enumerate(arrays):
        ix[:, n] = arrays[n][ix[:, n]]

    return ix
Stefan van der Walt
fonte
1
Parece bom. Pelos meus testes rudimentares, isso parece mais rápido que a resposta original para todos os pares, triplos e 4-tuplas de {1,2, ..., 100}. Depois disso, a resposta original vence. Além disso, para futuros leitores que desejam gerar todas as k-tuplas de {1, ..., n}, o np.indices((n,...,n)).reshape(k,-1).Tfará.
jme
Isso funciona apenas para números inteiros, enquanto a resposta aceita também funciona para carros alegóricos.
FJC
7

Parece que você deseja uma grade para avaliar sua função; nesse caso, você pode usar numpy.ogrid(aberto) ou numpy.mgrid(detalhado):

import numpy
my_grid = numpy.mgrid[[slice(0,1,0.1)]*6]
Steabert
fonte
6

você pode usar np.array(itertools.product(a, b))

William Song
fonte
np.array (list (itertools.product (l, l2))))
ZirconCode
4

Aqui está outra maneira, usando o NumPy puro, sem recursão, sem compreensão de lista e sem explicações para loops. É cerca de 20% mais lento que a resposta original e é baseado em np.meshgrid.

def cartesian(*arrays):
    mesh = np.meshgrid(*arrays)  # standard numpy meshgrid
    dim = len(mesh)  # number of dimensions
    elements = mesh[0].size  # number of elements, any index will do
    flat = np.concatenate(mesh).ravel()  # flatten the whole meshgrid
    reshape = np.reshape(flat, (dim, elements)).T  # reshape and transpose
    return reshape

Por exemplo,

x = np.arange(3)
a = cartesian(x, x, x, x, x)
print(a)

[[0 0 0 0 0]
 [0 0 0 0 1]
 [0 0 0 0 2]
 ..., 
 [2 2 2 2 0]
 [2 2 2 2 1]
 [2 2 2 2 2]]
étale-cohomology
fonte
3

Para uma implementação numpy pura do produto cartesiano de matrizes 1D (ou listas planas de python), basta usar meshgrid(), rolar os eixos com transpose()e remodelar para a saída desejada:

 def cartprod(*arrays):
     N = len(arrays)
     return transpose(meshgrid(*arrays, indexing='ij'), 
                      roll(arange(N + 1), -1)).reshape(-1, N)

Observe que a convenção do último eixo muda mais rapidamente ("estilo C" ou "linha principal").

In [88]: cartprod([1,2,3], [4,8], [100, 200, 300, 400], [-5, -4])
Out[88]: 
array([[  1,   4, 100,  -5],
       [  1,   4, 100,  -4],
       [  1,   4, 200,  -5],
       [  1,   4, 200,  -4],
       [  1,   4, 300,  -5],
       [  1,   4, 300,  -4],
       [  1,   4, 400,  -5],
       [  1,   4, 400,  -4],
       [  1,   8, 100,  -5],
       [  1,   8, 100,  -4],
       [  1,   8, 200,  -5],
       [  1,   8, 200,  -4],
       [  1,   8, 300,  -5],
       [  1,   8, 300,  -4],
       [  1,   8, 400,  -5],
       [  1,   8, 400,  -4],
       [  2,   4, 100,  -5],
       [  2,   4, 100,  -4],
       [  2,   4, 200,  -5],
       [  2,   4, 200,  -4],
       [  2,   4, 300,  -5],
       [  2,   4, 300,  -4],
       [  2,   4, 400,  -5],
       [  2,   4, 400,  -4],
       [  2,   8, 100,  -5],
       [  2,   8, 100,  -4],
       [  2,   8, 200,  -5],
       [  2,   8, 200,  -4],
       [  2,   8, 300,  -5],
       [  2,   8, 300,  -4],
       [  2,   8, 400,  -5],
       [  2,   8, 400,  -4],
       [  3,   4, 100,  -5],
       [  3,   4, 100,  -4],
       [  3,   4, 200,  -5],
       [  3,   4, 200,  -4],
       [  3,   4, 300,  -5],
       [  3,   4, 300,  -4],
       [  3,   4, 400,  -5],
       [  3,   4, 400,  -4],
       [  3,   8, 100,  -5],
       [  3,   8, 100,  -4],
       [  3,   8, 200,  -5],
       [  3,   8, 200,  -4],
       [  3,   8, 300,  -5],
       [  3,   8, 300,  -4],
       [  3,   8, 400,  -5],
       [  3,   8, 400,  -4]])

Se você deseja alterar o primeiro eixo mais rapidamente ("estilo FORTRAN" ou "coluna principal"), basta alterar o orderparâmetro da reshape()seguinte maneira:reshape((-1, N), order='F')

RBF06
fonte
1

O Pandas mergeoferece uma solução rápida e ingênua para o problema:

# given the lists
x, y, z = [1, 2, 3], [4, 5], [6, 7]

# get dfs with same, constant index 
x = pd.DataFrame({'x': x}, index=np.repeat(0, len(x))
y = pd.DataFrame({'y': y}, index=np.repeat(0, len(y))
z = pd.DataFrame({'z': z}, index=np.repeat(0, len(z))

# get all permutations stored in a new df
df = pd.merge(x, pd.merge(y, z, left_index=True, righ_index=True),
              left_index=True, right_index=True)
simone
fonte