Использование numpy для построения массива всех комбинаций двух массивов


151

Я пытаюсь пробежаться по пространству параметров функции с 6 параметрами, чтобы изучить ее числовое поведение, прежде чем пытаться сделать с ней что-нибудь сложное, поэтому я ищу эффективный способ сделать это.

Моя функция принимает на вход значения с плавающей запятой, заданный 6-тусклым массивом 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)

Это работает, но слишком медленно. Я знаю, что пространство параметров огромно, но это не должно быть так медленно. В этом примере я отобрал только 10 6 (миллион) точек, и на создание массива ушло более 15 секунд values.

Знаете ли вы более эффективный способ сделать это с помощью numpy?

При необходимости я могу изменить способ получения Fаргументов функцией .


Чтобы узнать о самом быстром декартовом произведении, которое я нашел, см. Этот ответ . (Поскольку вопрос сформулирован совершенно иначе, чем этот, я считаю, что вопросы не повторяются, но лучшее решение для двух вопросов - одно и то же.)
senderle

Ответы:


137

В более новой версии numpy(> 1.8.x) numpy.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]])

Обратите внимание, что порядок конечного результата немного отличается.


19
np.stack(np.meshgrid([1, 2, 3], [4, 5], [6, 7]), -1).reshape(-1, 3)отдадим правильный приказ
Эрик

@CT Zhu Есть ли простой способ преобразовать это так, чтобы вместо этого в качестве входных данных использовалась матрица, содержащая различные массивы в качестве столбцов?
Dole

2
Следует отметить, что meshgrid работает только с меньшими наборами диапазонов, у меня большой, и я получаю сообщение об ошибке: ValueError: максимальный поддерживаемый размер для ndarray равен 32, найдено 69
mikkom

160

Вот чистая реализация. Это примерно в 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

48
когда-нибудь рассматривал возможность включения этого в numpy? это не первый раз, когда я ищу эту функцию и нашел ваш пост.
endolith

1
В этой реализации есть ошибка. Для массивов строк, например: array [0] .dtype = "| S3" и array [1] .dtype = "| S5". Таким образом, существует необходимость найти самую длинную строку во входных данных и использовать ее тип в out = np.zeros ([n, len (array)], dtype = dtype)
norecces

40
К вашему сведению: похоже, он попал в пакет scikit-learn наfrom sklearn.utils.extmath import cartesian
Гас,

2
Я только что понял: это немного отличается от itertools.combinations, поскольку эта функция учитывает порядок значений, а комбинации - нет, поэтому эта функция возвращает больше значений, чем комбинаций. Все еще очень впечатляюще, но, к сожалению, это не то, что я искал :(
Дэвид Маркс

7
TypeError: slice indices must be integers or None or have an __index__ methodброшеноcartesian(arrays[1:], out=out[0:m,1:])
Берн

36

itertools.combinations, как правило, является самым быстрым способом получения комбинаций из контейнера Python (если вам действительно нужны комбинации, т. е. схемы БЕЗ повторений и независимо от порядка; это не то, что ваш код делает, но я не могу скажите, потому ли это, что ваш код содержит ошибки, или потому, что вы используете неправильную терминологию).

Если вам нужно что-то отличное от комбинаций, возможно, другие итераторы в itertools productили permutationsмогут служить вам лучше. Например, похоже, что ваш код примерно такой же, как:

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

Все эти итераторы выдают кортежи, а не списки или массивы numpy, поэтому, если ваш F придирчив к получению конкретно массива numpy, вам придется принять дополнительные накладные расходы на создание или очистку и повторное заполнение одного на каждом шаге.


9

Вы можете сделать что-то вроде этого

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

2
Есть ли способ заставить NumPy принимать более 32 массивов для meshgrid? Этот метод у меня работает, пока я не передаю более 32 массивов.
Joelmob

8

Следующая реализация numpy должна быть прибл. В 2 раза быстрее полученного ответа:

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
Выглядит хорошо. Судя по моим элементарным тестам, это выглядит быстрее, чем исходный ответ для всех пар, троек и 4-кортежей {1,2, ..., 100}. После этого исходный ответ побеждает. Кроме того, для будущих читателей, которые хотят сгенерировать все k-кортежи из {1, ..., n}, np.indices((n,...,n)).reshape(k,-1).Tподойдет.
jme

Это работает только для целых чисел, в то время как принятый ответ также работает для чисел с плавающей запятой.
FJC

7

Похоже, вы хотите, чтобы сетка оценивала вашу функцию, и в этом случае вы можете использовать numpy.ogrid(open) или numpy.mgrid(fleshed out):

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


4

Вот еще один способ, использующий чистый NumPy, без рекурсии, без понимания списка и без явных циклов for. Это примерно на 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]]

4

Для чистой numpy реализации декартова произведения одномерных массивов (или плоских списков Python) просто используйте 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')


1

Pandas 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)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.