Использование numpy для построения массива всех комбинаций из двух массивов
Я пытаюсь пробежать пространство параметров 6-параметрической функции, чтобы изучить ее числовое поведение, прежде чем пытаться что-то сделать с ней сложным, поэтому я ищу эффективный способ сделать это.
Моя функция принимает значения с плавающей запятой, используя в качестве входных данных массив с 6-ю значениями. Сначала я попытался сделать следующее:
Сначала я создал функцию, которая принимает 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)
Это работает, но это слишком медленно. Я знаю, что пространство параметров огромно, но это не должно быть так медленно. В этом примере я выбрал только 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]])
Обратите внимание, что порядок конечного результата немного отличается.
Вот чистая реализация. Это ок. В 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.combination - это, как правило, самый быстрый способ получить комбинации из контейнера Python (если вы на самом деле хотите комбинации, т. е. механизмы без повторений и независимо от порядка; это не то, что, по-видимому, делает ваш код, но я не могу скажите, это потому, что ваш код содержит ошибки или вы используете неверную терминологию).
Если вы хотите что-то другое, чем комбинации, возможно, другие итераторы в itertools, product
или же permutations
, может служить вам лучше. Например, похоже, что ваш код примерно такой же, как:
for val in itertools.product(np.arange(0, 1, 0.1), repeat=6):
print F(val)
Все эти итераторы дают кортежи, а не списки или массивы, поэтому, если ваш F требователен к получению именно массива, вы должны будете принять дополнительные издержки на создание или очистку и повторное заполнение одного на каждом шаге.
Вы можете сделать что-то вроде этого
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 должна быть ок. 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]
Вот еще один способ, использующий чистый 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]]
Для чистой реализации кода с декартовым произведением одномерных массивов (или плоских списков питонов) просто используйте meshgrid()
свернуть топоры transpose()
и измените на желаемый результат:
def cartprod(*arrays):
N = len(arrays)
return transpose(meshgrid(*arrays, indexing='ij'),
roll(arange(N + 1), -1)).reshape(-1, N)
Обратите внимание, что в соответствии с соглашением последняя ось изменяется наиболее быстро ("стиль C" или "строка-мажор").
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]])
Если вы хотите быстро изменить первую ось ("стиль FORTRAN" или "основной столбец"), просто измените order
параметр reshape()
как это: reshape((-1, N), order='F')
Панды merge
предлагает наивное и быстрое решение проблемы:
# 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)