Utiliser numpy pour construire un tableau de toutes les combinaisons de deux tableaux
-
05-07-2019 - |
Question
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 pour un tableau numpy de 6 dim en entrée. Au départ, j’ai essayé de faire 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é reduction ()
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 trop lent. Je sais que l'espace des paramètres est énorme, mais cela ne devrait pas être si lent. Dans cet exemple, je n’ai échantillonné que 10 6 (un million) de points et il a fallu plus de 15 secondes pour créer le tableau valeurs
.
Connaissez-vous un moyen plus efficace de le faire avec numpy?
Je peux modifier la façon dont la fonction F
utilise ses arguments si nécessaire.
La solution
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 ()
utilisé 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.
Autres conseils
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
itertools.combinations est en général le moyen le plus rapide d'obtenir des combinaisons. depuis un conteneur Python (si vous voulez réellement des combinaisons, c'est-à-dire des arrangements SANS répétition et indépendants 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 ' en utilisant la mauvaise terminologie).
Si vous voulez quelque chose de différent des combinaisons, peut-être que d'autres itérateurs dans itertools, produit
ou permutations
, pourraient mieux vous servir. Par exemple, il semble que votre code soit à peu près identique à:
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 que représente la construction ou la suppression et le remplissage à chaque étape.
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
Il semble que vous souhaitiez une grille pour évaluer votre fonction. Dans ce cas, vous pouvez utiliser numpy.ogrid
(open) ou numpy.mgrid
(étoffé):
import numpy
my_grid = numpy.mgrid[[slice(0,1,0.1)]*6]
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]])
vous pouvez utiliser np.array (itertools.product (a, b))
Voici encore un autre moyen d’utiliser NumPy pur, aucune récursion, aucune compréhension de la liste et aucune boucle for explicite. Elle est environ 20% plus lente que la réponse initiale et est basée 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]]
Pour une implémentation purement numérique du produit cartésien de tableaux 1D (ou de listes python plates), 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 souhaitez modifier l’axe premier le plus rapidement ("Style FORTRAN" ou "Colonne-majeur"), il vous suffit de modifier le paramètre order
de . reshape ()
comme ceci: reshape ((- 1, N), commande = 'F')