web-dev-qa-db-fra.com

Utiliser numpy pour construire un tableau de toutes les combinaisons de deux tableaux

J'essaie de parcourir l'espace de paramètres d'une fonction à 6 paramètres pour étudier son comportement numérique avant d'essayer de faire quelque chose de complexe avec elle. Je recherche donc un moyen efficace de le faire.

Ma fonction prend des valeurs float avec un tableau numpy de 6 dim en entrée. Ce que j'ai essayé de faire initialement était ceci:

J'ai d'abord créé une fonction qui prend 2 tableaux et génère un tableau avec toutes les combinaisons de valeurs des deux tableaux

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

Ensuite, j'ai utilisé reduce() pour l'appliquer à m copies du même tableau:

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

Et puis j'évalue ma fonction comme ceci:

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

Cela fonctionne mais c'est waaaay trop lent. Je sais que l'espace des paramètres est énorme, mais cela ne devrait pas être si lent. J'ai seulement échantillonné 106 (un million) de points dans cet exemple et il a fallu plus de 15 secondes pour créer le tableau values.

Connaissez-vous un moyen plus efficace de le faire avec numpy?

Je peux modifier la façon dont la fonction F prend ses arguments si nécessaire.

122

Dans les versions plus récentes de numpy (> 1.8.x), numpy.meshgrid() fournit une implémentation beaucoup plus rapide:

La solution 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() utilisait pour être 2D seulement, maintenant il est capable de ND. Dans ce cas, 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]])

Notez que l'ordre de la résultante finale est légèrement différent.

92
CT Zhu

Voici une implémentation purement numérique. C'est ca. 5 × plus rapide que d'utiliser 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
150
pv.

itertools.combinations est en général le moyen le plus rapide d’obtenir des combinaisons d’un conteneur Python (si vous voulez en fait des combinaisons, c’est-à-dire des arrangements SANS répétitions et indépendantes de l’ordre). ; ce n'est pas ce que votre code semble faire, mais je ne peux pas dire si c'est parce que votre code est bogué ou parce que vous utilisez une terminologie incorrecte).

Si vous voulez quelque chose de différent des combinaisons, peut-être que d'autres itérateurs dans itertools, product ou permutations, pourraient mieux vous servir. Par exemple, il semble que votre code soit à peu près le même que:

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

Tous ces itérateurs génèrent des n-uplets, et non des listes ou des tableaux numpy. Par conséquent, si votre F est difficile à obtenir un tableau numpy spécifique, vous devrez accepter la surcharge supplémentaire liée à la construction ou la suppression et le remplissage à chaque étape.

32
Alex Martelli

L'implémentation numpy suivante devrait être d'env. 2x la vitesse de la réponse donnée:

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
8

Il semble que vous souhaitiez une grille pour évaluer votre fonction, auquel cas vous pouvez utiliser numpy.ogrid (ouvert) ou numpy.mgrid (étoffé):

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

Vous pouvez faire quelque chose comme ça

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

qui donne

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

vous pouvez utiliser np.array(itertools.product(a, b))

4
William Song

Voici encore un autre moyen d’utiliser NumPy pur, pas de récursion, pas de compréhension de liste et pas de boucles explicites. C'est environ 20% plus lent que la réponse d'origine, et c'est basé sur 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

Par exemple,

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

donne

[[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]]
2
étale-cohomology

Pour une implémentation numpy pure du produit cartésien de tableaux 1D (ou flat python listes)), utilisez simplement meshgrid(), faites rouler les axes avec transpose(), et remodeler à la sortie désirée:

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

Notez que cela a la convention du dernier axe qui change le plus rapidement ("style C" ou "ligne-majeur").

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

Si vous voulez changer le premier axe le plus rapide ("style FORTRAN" ou "colonne-majeur"), il suffit de changer le paramètre order de reshape() comme ceci: reshape((-1, N), order='F')

1
RBF06