Використання numpy для створення масиву всіх комбінацій двох масивів


143

Я намагаюсь перебрати простір параметрів функції 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

Відповіді:


127

У новій версії 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]])

Зауважимо, що порядок кінцевого результату дещо інший.


14
np.stack(np.meshgrid([1, 2, 3], [4, 5], [6, 7]), -1).reshape(-1, 3)надасть правильне замовлення
Ерік

@CT Zhu Чи є простий спосіб перетворити це так, щоб матриця, що містить різні масиви як стовпці, використовувалась як вхід?
Доле

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

158

Ось суто нумерована реалізація. Це приблизно на 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

47
Ви коли-небудь розглядаєте можливість подання цього запису включеним у рядок? це вже не перший раз, коли я шукаю цю функціональність і знайшов вашу посаду.
ендоліт

1
У цій реалізації є помилка. Наприклад, для масивів рядків: масиви [0] .dtype = "| S3" та масиви [1] .dtype = "| S5". Отже, потрібно знайти найдовший рядок у введенні та використовувати його тип у out = np.zeros ([n, len (масиви)], dtype = dtype)
norecces

38
FYI: здається, зробили це в scikit освоєння пакета наfrom sklearn.utils.extmath import cartesian
Гас

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

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

Усі ці ітератори дають кортежі, а не списки чи нумерові масиви, тож якщо ваш F прискіпливий до отримання конкретно нумерованого масиву, вам доведеться прийняти додаткові накладні витрати на побудову чи очищення та повторне заповнення одного на кожному кроці.


8

Можна зробити щось подібне

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

Наступна нумерована реалізація повинна становити бл. 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

1
Виглядає добре. За моїми рудиментарними тестами, це виглядає швидше, ніж оригінальна відповідь на всі пари, трійки та 4-кортежі {1,2, ..., 100}. Після цього оригінальна відповідь виграє. Також майбутні читачі, які хочуть створити всі k-кортежі {1, ..., n}, np.indices((n,...,n)).reshape(k,-1).Tзроблять.
jme

Це працює лише для цілих чисел, тоді як прийнята відповідь також працює для плавців.
FJC

7

Схоже, ви хочете, щоб сітка оцінювала вашу функцію, і в цьому випадку ви можете використовувати numpy.ogrid(відкрити) або numpy.mgrid(розгорнути):

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


4

Ось ще один спосіб, використовуючи чистий 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]]

3

Для чистої нутрійної реалізації декартового продукту масивів 1D (або плоских списків пітонів) просто використовуйте 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.