Использование numpy для построения массива из всех комбинаций двух массивов
Я пытаюсь запустить пространство параметров 6-параметрической функции, чтобы изучить ее численное поведение, прежде чем пытаться сделать что-либо сложное с ней, поэтому я ищу эффективный способ сделать это.
моя функция принимает значения с плавающей запятой, заданные массивом 6-dim numpy в качестве входных данных. То, что я пытался сделать изначально было так:
сначала я создал функцию, которая принимает 2 массива а создать массив всех комбинаций значений из двух массивы
from numpy import *
def comb(a,b):
c = []
for i in a:
for j in b:
c.append(r_[i,j])
return c
тогда я использовал reduce()
чтобы применить это к m копиям одного и того же массива:
def combs(a,m):
return reduce(comb,[a]*m)
и затем я оцениваю свою функцию следующим образом:
values = combs(np.arange(0,1,0.1),6)
for val in values:
print F(val)
это работает, но это waaaay слишком медленно. Я знаю, что пространство параметров огромно, но это не должно быть так медленно. Я только попробовал 106 (миллион) точек в этом примере, и потребовалось более 15 секунд, чтобы создать массив values
.
знаете ли вы более эффективный как это сделать с numpy?
Я могу изменить функцию F
нужны аргументы, если это необходимо.
9 ответов:
в новой версии
numpy
(>1.8.x),np.meshgrid
обеспечивает гораздо более быструю реализацию:решение@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
используйте только 2D, теперь он способен ND. В этом случае 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]])
обратите внимание, что порядок конечного результата немного отличается.
вот реализация pure-numpy. Это ок. 5× быстрее, чем с помощью 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.комбинации это вообще самый быстрый способ получить комбинации из контейнера Python (если вы действительно хотите комбинаций, т. е. договоренностей без повторений и независимо от порядка; это не то, что делает ваш код, но я не могу сказать, потому что ваш код глючит или потому что вы используете неправильную терминологию).
Если вы хотите что-то другое, чем комбинации, возможно, другие итераторы в модуле itertools,
product
илиpermutations
, может служить вам лучше. Например, похоже, что ваш код примерно такой же, как:for val in itertools.product(np.arange(0, 1, 0.1), repeat=6): print F(val)
все эти итераторы дают кортежи, а не списки или массивы numpy, поэтому, если ваш F разборчив в получении конкретно массива numpy, вам придется принять дополнительные накладные расходы на построение или очистку и повторное заполнение одного на каждом шаге.
следующая реализация numpy должна быть ок. 2x скорость данного ответа:
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
похоже, вы хотите, чтобы сетка оценивала вашу функцию, и в этом случае вы можете использовать
numpy.ogrid
(открытое) илиnumpy.mgrid
(конкретизированы):import numpy my_grid = numpy.mgrid[[slice(0,1,0.1)]*6]
Вы можете сделать что-то подобное
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])
что дает
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]])
вот еще один способ, используя чистый NumPy, без рекурсии,без понимания списка и без явных циклов. Это примерно на 20% медленнее, чем исходный ответ, и он основан на 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
например,
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]]
для чисто реализацию и NumPy из декартова произведения 1-мерных массивов (или плоские списки в Python), просто использовать
meshgrid()
, крен оси сtranspose()
, и изменить форму до желаемого выхода:def cartprod(*arrays): N = len(arrays) return transpose(meshgrid(*arrays, indexing='ij'), roll(arange(N + 1), -1)).reshape(-1, N)
обратите внимание, что это имеет соглашение о последнем изменении оси быстрее всего ("C style" или "row-major").
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]])
если вы хотите изменить первый axis fastest ("FORTRAN style" или "column-major"), просто измените