Menggunakan numpy untuk membangun array dari semua kombinasi dari dua array


143

Saya mencoba menjalankan ruang parameter fungsi 6 parameter untuk mempelajari perilaku numeriknya sebelum mencoba melakukan sesuatu yang rumit dengannya jadi saya mencari cara yang efisien untuk melakukan ini.

Fungsi saya mengambil nilai float yang diberikan array numpy 6-dim sebagai input. Apa yang saya coba lakukan pada awalnya adalah ini:

Pertama, saya membuat fungsi yang mengambil 2 array dan menghasilkan array dengan semua kombinasi nilai dari dua array

from numpy import *
def comb(a,b):
    c = []
    for i in a:
        for j in b:
            c.append(r_[i,j])
    return c

Lalu saya biasa reduce()menerapkannya pada m salinan array yang sama:

def combs(a,m):
    return reduce(comb,[a]*m)

Dan kemudian saya mengevaluasi fungsi saya seperti ini:

values = combs(np.arange(0,1,0.1),6)
for val in values:
    print F(val)

Ini bekerja tetapi waaaay terlalu lambat. Saya tahu ruang parameter sangat besar, tetapi ini seharusnya tidak terlalu lambat. Saya hanya mencicipi 10 6 (sejuta) poin dalam contoh ini dan butuh lebih dari 15 detik hanya untuk membuat array values.

Apakah Anda tahu cara yang lebih efisien untuk melakukan ini dengan numpy?

Saya bisa memodifikasi cara fungsi Fmengambil argumen itu jika perlu.


Untuk produk kartesius tercepat yang saya temukan, lihat jawaban ini . (Karena pertanyaannya diutarakan dengan sangat berbeda dari yang ini, saya menganggap bahwa pertanyaan itu bukan duplikat, tetapi solusi terbaik untuk kedua pertanyaan itu adalah sama.)
pengirim

Jawaban:


127

Dalam versi yang lebih baru numpy(> 1.8.x), numpy.meshgrid()memberikan implementasi yang jauh lebih cepat:

solusi @ 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()dulu hanya 2D, sekarang sudah bisa ND. Dalam hal ini, 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]])

Perhatikan bahwa urutan hasil akhir sedikit berbeda.


14
np.stack(np.meshgrid([1, 2, 3], [4, 5], [6, 7]), -1).reshape(-1, 3)akan memberikan pesanan yang tepat
Eric

@ Ct Zhu Apakah ada cara mudah untuk mengubah ini sehingga sebuah matriks memegang array yang berbeda sebagai kolom digunakan sebagai input?
Sedekah

2
Perlu dicatat bahwa meshgrid hanya berfungsi untuk set rentang yang lebih kecil, saya punya yang besar dan saya mendapatkan kesalahan: ValueError: dimensi maksimum yang didukung untuk ndarray adalah 32, ditemukan 69
mikkom

157

Inilah implementasi murni-numpy. Ini sekitar 5 × lebih cepat daripada menggunakan 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

46
pernah mempertimbangkan mengirimkan ini untuk dimasukkan dalam numpy? ini bukan pertama kalinya saya mencari fungsi ini dan menemukan posting Anda.
endolith

1
Ada bug dalam implementasi ini. Untuk array string misalnya: array [0] .dtype = "| S3" dan array [1] .dtype = "| S5". Jadi ada kebutuhan dalam menemukan string terpanjang dalam input dan menggunakan tipenya di out = np.zeros ([n, len (array)], dtype = dtype)
norecces

38
FYI: tampaknya telah berhasil masuk ke paket scikit-learn atfrom sklearn.utils.extmath import cartesian
Gus

2
Saya baru sadar: ini sedikit berbeda dari itertools.combinations, karena fungsi ini menghormati urutan nilai sedangkan kombinasi tidak, jadi fungsi ini mengembalikan lebih banyak nilai daripada kombinasi. Masih sangat mengesankan, tapi sayangnya bukan yang saya cari :(
David Marx

6
TypeError: slice indices must be integers or None or have an __index__ methoddilemparkan olehcartesian(arrays[1:], out=out[0:m,1:])
Boern

36

itertools.combinations umumnya adalah cara tercepat untuk mendapatkan kombinasi dari wadah Python (jika Anda memang menginginkan kombinasi, yaitu, pengaturan TANPA pengulangan dan tanpa urutan; bukan itu yang tampaknya dilakukan kode Anda, tetapi saya tidak bisa katakan apakah itu karena kode Anda bermasalah atau karena Anda menggunakan terminologi yang salah).

Jika Anda menginginkan sesuatu yang berbeda dari kombinasi mungkin iterator lain di itertools, productatau permutations, mungkin lebih bermanfaat bagi Anda. Misalnya, sepertinya kode Anda kira-kira sama dengan:

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

Semua iterator ini menghasilkan tupel, bukan daftar atau array numpy, jadi jika F Anda pilih-pilih tentang array yang numpy, Anda harus menerima overhead tambahan untuk membangun atau membersihkan dan mengisi ulang satu di setiap langkah.


8

Anda dapat melakukan sesuatu seperti ini

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

pemberian yang mana

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
Apakah ada cara untuk membuat NumPy menerima lebih dari 32 array untuk meshgrid? Metode ini berfungsi untuk saya selama saya tidak melewati lebih dari 32 array.
Joelmob

8

Implementasi numpy berikut ini harus sekitar. 2x kecepatan jawaban yang diberikan:

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
Kelihatan bagus. Dengan tes dasar saya, ini terlihat lebih cepat daripada jawaban asli untuk semua pasangan, tiga kali lipat, dan 4-tupel dari {1,2, ..., 100}. Setelah itu, jawaban asli menang. Juga, untuk pembaca di masa depan yang ingin menghasilkan semua k-tuple dari {1, ..., n}, np.indices((n,...,n)).reshape(k,-1).Takan dilakukan.
jme

Ini hanya berfungsi untuk bilangan bulat, sedangkan jawaban yang diterima juga berfungsi untuk float.
FJC

7

Sepertinya Anda ingin kisi untuk mengevaluasi fungsi Anda, dalam hal ini Anda dapat menggunakan numpy.ogrid(terbuka) atau numpy.mgrid(menyempurnakan):

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


4

Inilah cara lain, menggunakan NumPy murni, tidak ada rekursi, tidak ada pemahaman daftar, dan tidak ada eksplisit untuk loop. Itu sekitar 20% lebih lambat dari jawaban aslinya, dan ini didasarkan pada 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

Sebagai contoh,

x = np.arange(3)
a = cartesian(x, x, x, x, x)
print(a)

memberi

[[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

Untuk implementasi murni produk Cartesian dari array 1D (atau daftar python datar), cukup gunakan meshgrid(), putar sumbu transpose(), dan bentuk ulang ke ouput yang diinginkan:

 def cartprod(*arrays):
     N = len(arrays)
     return transpose(meshgrid(*arrays, indexing='ij'), 
                      roll(arange(N + 1), -1)).reshape(-1, N)

Perhatikan ini memiliki konvensi perubahan sumbu terakhir tercepat ("gaya C" atau "baris-utama").

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

Jika Anda ingin mengubah sumbu tercepat pertama ("gaya FORTRAN" atau "kolom-utama"), cukup ubah orderparameter reshape()seperti ini:reshape((-1, N), order='F')


1

Panda mergemenawarkan solusi cepat dan naif untuk masalah ini:

# 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)
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.