Verwenden von numpy, um ein Array aller Kombinationen von zwei Arrays zu erstellen


143

Ich versuche, den Parameterraum einer 6-Parameter-Funktion zu durchlaufen, um ihr numerisches Verhalten zu untersuchen, bevor ich versuche, etwas Komplexes damit zu tun, also suche ich nach einem effizienten Weg, dies zu tun.

Meine Funktion verwendet Float-Werte bei einem 6-Dim-Numpy-Array als Eingabe. Was ich anfangs versuchte, war Folgendes:

Zuerst habe ich eine Funktion erstellt, die 2 Arrays verwendet und aus den beiden Arrays ein Array mit allen Wertekombinationen generiert

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

Dann habe ich reduce()das auf m Kopien desselben Arrays angewendet :

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

Und dann bewerte ich meine Funktion folgendermaßen:

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

Das funktioniert, aber es ist zu langsam. Ich weiß, dass der Raum der Parameter riesig ist, aber das sollte nicht so langsam sein. Ich habe in diesem Beispiel nur 10 6 (eine Million) Punkte abgetastet und es dauerte mehr als 15 Sekunden, um das Array zu erstellen values.

Kennen Sie einen effizienteren Weg, dies mit Numpy zu tun?

Ich kann die Art und Weise ändern, wie die Funktion Fihre Argumente verwendet, wenn dies erforderlich ist.


Das schnellste kartesische Produkt, das ich gefunden habe, finden Sie in dieser Antwort . (Da die Frage ganz anders formuliert ist als diese, bin ich der Meinung, dass die Fragen keine Duplikate sind, aber die beste Lösung für die beiden Fragen ist dieselbe.)
senderle

Antworten:


127

numpyBietet in der neueren Version von (> 1.8.x) numpy.meshgrid()eine viel schnellere Implementierung:

@ pvs Lösung

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()Früher war es nur 2D, jetzt ist es ND-fähig. In diesem Fall 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]])

Beachten Sie, dass die Reihenfolge des Endergebnisses geringfügig abweicht.


14
np.stack(np.meshgrid([1, 2, 3], [4, 5], [6, 7]), -1).reshape(-1, 3)wird die richtige Bestellung geben
Eric

@CT Zhu Gibt es eine einfache Möglichkeit, dies so zu transformieren, dass stattdessen eine Matrix mit den verschiedenen Arrays als Spalten als Eingabe verwendet wird?
Dole

2
Es sollte beachtet werden, dass Meshgrid nur für kleinere Bereichssätze funktioniert. Ich habe einen großen und erhalte die Fehlermeldung: ValueError: Die maximal unterstützte Dimension für ein Ndarray beträgt 32, gefunden 69
mikkom

156

Hier ist eine reine Numpy-Implementierung. Es ist ungefähr 5 × schneller als die Verwendung von 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

45
Erwägen Sie jemals, dies einzureichen, um es in numpy aufzunehmen? Dies ist nicht das erste Mal, dass ich nach dieser Funktionalität gesucht und Ihren Beitrag gefunden habe.
Endolith

1
Diese Implementierung weist einen Fehler auf. Für Arrays von Strings zum Beispiel: Arrays [0] .dtype = "| S3" und Arrays [1] .dtype = "| S5". Es ist also notwendig, die längste Zeichenfolge in der Eingabe zu finden und ihren Typ in out = np.zeros ([n, len (Arrays)], dtype = dtype) zu verwenden
norecces

38
Zu Ihrer Information: scheint es in das Scikit-Lernpaket amfrom sklearn.utils.extmath import cartesian
Gus

2
Ich habe gerade festgestellt: Dies unterscheidet sich geringfügig von itertools.combinations, da diese Funktion die Reihenfolge der Werte berücksichtigt, während Kombinationen dies nicht tun. Daher gibt diese Funktion mehr Werte zurück als Kombinationen. Immer noch sehr beeindruckend, aber leider nicht das, wonach ich gesucht habe :(
David Marx

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

36

itertools.combinations ist im Allgemeinen der schnellste Weg, um Kombinationen aus einem Python-Container abzurufen (wenn Sie tatsächlich Kombinationen wünschen, dh Arrangements OHNE Wiederholungen und unabhängig von der Reihenfolge; das scheint Ihr Code nicht zu tun, aber ich kann nicht Sagen Sie, ob dies daran liegt, dass Ihr Code fehlerhaft ist oder dass Sie die falsche Terminologie verwenden.

Wenn Sie etwas anderes als Kombinationen möchten, können Ihnen andere Iteratoren in itertools productoder permutationsbesser dienen. Zum Beispiel sieht Ihr Code ungefähr so ​​aus wie:

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

Alle diese Iteratoren ergeben Tupel, keine Listen oder Numpy-Arrays. Wenn Ihr F also wählerisch ist, ein bestimmtes Numpy-Array zu erhalten, müssen Sie den zusätzlichen Aufwand akzeptieren, bei jedem Schritt eines zu erstellen oder zu löschen und neu zu füllen.


8

Sie können so etwas tun

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

was gibt

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
Gibt es eine Möglichkeit, NumPy dazu zu bringen, mehr als 32 Arrays für Meshgrid zu akzeptieren? Diese Methode funktioniert für mich, solange ich nicht mehr als 32 Arrays übergebe.
Joelmob

8

Die folgende Numpy-Implementierung sollte ca. 2x die Geschwindigkeit der gegebenen Antwort:

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
Sieht gut aus. Nach meinen rudimentären Tests sieht dies für alle Paare, Tripel und 4-Tupel von {1,2, ..., 100} schneller aus als die ursprüngliche Antwort. Danach gewinnt die ursprüngliche Antwort. Auch für zukünftige Leser, die alle k-Tupel von {1, ..., n} generieren möchten, np.indices((n,...,n)).reshape(k,-1).Tist dies ausreichend.
jme

Dies funktioniert nur für Ganzzahlen, während die akzeptierte Antwort auch für Floats funktioniert.
FJC

7

Es sieht so aus, als ob Sie möchten, dass ein Raster Ihre Funktion bewertet. In diesem Fall können Sie Folgendes verwenden numpy.ogrid(offen) oder numpy.mgrid(konkretisiert):

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


4

Hier ist noch eine andere Möglichkeit, reines NumPy zu verwenden, keine Rekursion, kein Listenverständnis und keine expliziten for-Schleifen. Es ist ungefähr 20% langsamer als die ursprüngliche Antwort und basiert auf 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

Beispielsweise,

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

gibt

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

Für eine reine Numpy-Implementierung des kartesischen Produkts von 1D-Arrays (oder flachen Python-Listen) verwenden Sie einfach meshgrid()die Achsen, rollen Sie mit transpose()und formen Sie die gewünschte Ausgabe neu:

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

Beachten Sie, dass sich die Konvention der letzten Achse am schnellsten ändert ("C-Stil" oder "Zeilenmajor").

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

Wenn Sie die erste Achse am schnellsten ändern möchten ("FORTRAN-Stil" oder "Spalten-Major"), ändern Sie einfach den orderParameter reshape()wie folgt:reshape((-1, N), order='F')


1

Pandas mergebietet eine naive, schnelle Lösung für das Problem:

# 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)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.