Utilizzo di numpy per creare un array di tutte le combinazioni di due array


143

Sto cercando di scorrere lo spazio dei parametri di una funzione a 6 parametri per studiarne il comportamento numerico prima di provare a fare qualcosa di complesso con esso, quindi sto cercando un modo efficiente per farlo.

La mia funzione accetta valori float dati come array numpy 6 dim come input. Quello che ho provato a fare inizialmente era questo:

Innanzitutto ho creato una funzione che accetta 2 array e genera un array con tutte le combinazioni di valori dei due array

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

Quindi lo reduce()applicavo a m copie dello stesso array:

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

E quindi valuto la mia funzione in questo modo:

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

Funziona ma è troppo lento. So che lo spazio dei parametri è enorme, ma non dovrebbe essere così lento. In questo esempio ho campionato solo 10 6 (un milione) di punti e ci sono voluti più di 15 secondi solo per creare l'array values.

Conosci un modo più efficace di farlo con numpy?

Posso modificare il modo in cui la funzione Faccetta i suoi argomenti se è necessario.


Per il prodotto cartesiano più veloce che ho trovato, vedi questa risposta . (Poiché la domanda è formulata in modo abbastanza diverso da questa, ritengo che le domande non siano duplicati, ma la soluzione migliore alle due domande è la stessa.)
mittente

Risposte:


128

Nella versione più recente di numpy(> 1.8.x), numpy.meshgrid()fornisce un'implementazione molto più veloce:

La soluzione di @ 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()usa solo 2D, ora è in grado di ND. In questo 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]])

Si noti che l'ordine del risultato finale è leggermente diverso.


16
np.stack(np.meshgrid([1, 2, 3], [4, 5], [6, 7]), -1).reshape(-1, 3)darà il giusto ordine
Eric

@CT Zhu C'è un modo semplice per trasformare questo in modo che la matrice a che tiene le diverse matrici come colonne sia usata come input?
Dole,

2
Va notato che meshgrid funziona solo per set di intervalli più piccoli, ne ho uno grande e ricevo un errore: ValueError: la dimensione massima supportata per un ndarray è 32, trovata 69
mikkom

158

Ecco un'implementazione puramente insensibile. È circa 5 volte più veloce dell'uso di itertools.


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

47
hai mai pensato di inviarlo per essere incluso in numpy? questa non è la prima volta che cerco questa funzionalità e trovo il tuo post.
endolith

1
C'è un bug in questa implementazione. Ad esempio per le matrici di stringhe: matrici [0] .dtype = "| S3" e matrici [1] .dtype = "| S5". Quindi è necessario trovare la stringa più lunga in input e usare il suo tipo in out = np.zeros ([n, len (array)], dtype = dtype)
norecces

38
Cordiali saluti: sembra aver fatto il pacchetto scikit-learn afrom sklearn.utils.extmath import cartesian
Gus

2
Ho appena capito: questo è leggermente diverso da itertools.combinations, poiché questa funzione rispetta l'ordinamento dei valori mentre le combinazioni non lo fanno, quindi questa funzione restituisce più valori delle combinazioni. Ancora molto impressionante, ma purtroppo non è quello che cercavo :(
David Marx,

6
TypeError: slice indices must be integers or None or have an __index__ methodlanciato dacartesian(arrays[1:], out=out[0:m,1:])
Boern il

36

itertools.combinations è in genere il modo più veloce per ottenere combinazioni da un contenitore Python (se in realtà vuoi combinazioni, cioè accordi SENZA ripetizioni e indipendenti dall'ordine; non è quello che il tuo codice sembra fare, ma non posso indica se il codice è errato o se stai utilizzando una terminologia errata).

Se vuoi qualcosa di diverso dalle combinazioni, forse altri iteratori in itertools, producto permutations, potrebbero servirti meglio. Ad esempio, sembra che il tuo codice sia approssimativamente lo stesso di:

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

Tutti questi iteratori producono tuple, non elenchi o array intorpiditi, quindi se la tua F è esigente nell'ottenere specificamente un array intorpidito, dovrai accettare l'overhead aggiuntivo di costruire o cancellare e riempire di nuovo uno ad ogni passaggio.


8

Puoi fare qualcosa del genere

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

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

2
C'è un modo per convincere NumPy ad accettare più di 32 matrici per meshgrid? Questo metodo funziona per me purché non passi più di 32 matrici.
Joelmob,

8

La seguente implementazione numpy dovrebbe essere di ca. 2x la velocità della risposta data:

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

1
Sembra buono. Secondo i miei test rudimentali, questo sembra più veloce della risposta originale per tutte le coppie, le triple e le 4 tuple di {1,2, ..., 100}. Successivamente, vince la risposta originale. Inoltre, per i futuri lettori che desiderano generare tutte le k-tuple di {1, ..., n}, np.indices((n,...,n)).reshape(k,-1).Tlo faranno.
jme

Funziona solo con numeri interi, mentre la risposta accettata funziona anche con float.
FJC,

7

Sembra che tu voglia che una griglia valuti la tua funzione, nel qual caso puoi usare numpy.ogrid(aperto) o numpy.mgrid(arricchito):

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


4

Ecco ancora un altro modo, usando NumPy puro, nessuna ricorsione, nessuna comprensione dell'elenco e nessun esplicito per i loop. È circa il 20% più lento della risposta originale ed è basato su 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

Per esempio,

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

3

Per un'implementazione intorpidita pura del prodotto cartesiano di array 1D (o elenchi di pitone piatti), basta usare meshgrid(), ruotare gli assi con transpose()e rimodellare fino all'uscita desiderata:

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

Si noti che questa ha la convenzione dell'ultimo asse che cambia più velocemente ("stile C" o "riga maggiore").

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 vuoi cambiare il primo asse più velocemente ("FORTRAN style" o "column-major"), modifica il orderparametro in reshape()questo modo:reshape((-1, N), order='F')


1

Pandas mergeoffre una soluzione ingenua e rapida al 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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.