X ve y dizi noktalarının kartezyen çarpımı tek bir 2D nokta dizisine dönüşür


147

Bir ızgaranın x ve y eksenlerini tanımlayan iki numpy dizim var. Örneğin:

x = numpy.array([1,2,3])
y = numpy.array([4,5])

Bu dizilerin Kartezyen ürününü oluşturmak istiyorum:

array([[1,4],[2,4],[3,4],[1,5],[2,5],[3,5]])

Bir döngüde birçok kez yapmam gerektiğinden, bu çok korkunç bir şekilde verimsiz. Onları bir Python listesine dönüştürmek itertools.productve geri numpy dizi kullanarak ve en verimli formu olmadığını varsayalım.


Itertools yaklaşımındaki en pahalı adımın listeden diziye son dönüşüm olduğunu fark ettim. Bu son adım olmadan Ken'in örneğinden iki kat daha hızlıdır.
Alexey Lebedev

Yanıtlar:


88
>>> numpy.transpose([numpy.tile(x, len(y)), numpy.repeat(y, len(x))])
array([[1, 4],
       [2, 4],
       [3, 4],
       [1, 5],
       [2, 5],
       [3, 5]])

N dizilerinin Kartezyen ürününün hesaplanması için genel bir çözüm için iki dizinin tüm kombinasyonlarından oluşan bir dizi oluşturmak için Numpy kullanımı konusuna bakın .


1
Bu yaklaşımın bir avantajı, aynı büyüklükteki diziler için tutarlı çıktı üretmesidir. meshgrid+ dstackEğer kartezyen çarpım aynı boyuttaki diziler için aynı sırayla inşa edilecek bekliyorsanız yaklaşım, daha hızlı bazı durumlarda ise, hatalar yol açabilir.
tlnagy

3
@tlnagy, bu yaklaşımın meshgrid+ tarafından üretilenlerden farklı sonuçlar verdiği hiçbir durumu fark etmedim dstack. Bir örnek gönderebilir misiniz?
senderle

148

Standart cartesian_product(neredeyse)

Bu soruna farklı özelliklere sahip birçok yaklaşım vardır. Bazıları diğerlerinden daha hızlı, bazıları ise daha genel amaçlıdır. Çok fazla test ve ayar yaptıktan sonra, bir n-boyutlu hesaplayan aşağıdaki fonksiyonun cartesian_productbirçok girdi için diğerlerinden daha hızlı olduğunu gördüm . Biraz daha karmaşık, ancak çoğu durumda biraz daha hızlı olan bir çift yaklaşım için Paul Panzer'in cevabına bakın .

Bu cevap göz önüne alındığında, artık bildiğim kartezyen ürünün en hızlı uygulaması değil numpy. Ancak, basitliğinin gelecekteki iyileştirmeler için yararlı bir kriter haline getirmeye devam edeceğini düşünüyorum:

def cartesian_product(*arrays):
    la = len(arrays)
    dtype = numpy.result_type(*arrays)
    arr = numpy.empty([len(a) for a in arrays] + [la], dtype=dtype)
    for i, a in enumerate(numpy.ix_(*arrays)):
        arr[...,i] = a
    return arr.reshape(-1, la)

Bu işlevin ix_alışılmadık bir şekilde kullanıldığını belirtmek gerekir ; belgelenmiş kullanımı ise ix_olan endeksler oluşturmak içine bir dizi, sadece çok aynı şekle sahip diziler yayınlanan atama için kullanılabilir olur. Bu şekilde kullanmayı denemem için bana ilham veren mgilson'a ve bu öneriyi kullanma önerisi de dahil olmak üzere son derece yararlı geri bildirim sağlayan unutbu'ya çok teşekkürler .ix_numpy.result_type

Dikkate değer alternatifler

Bazen Fortran düzeninde bitişik bellek blokları yazmak daha hızlıdır. cartesian_product_transposeBazı donanımlarda daha hızlı olduğu kanıtlanan bu alternatifin temeli budur cartesian_product(aşağıya bakınız). Ancak Paul Panzer'in aynı prensibi kullanan cevabı daha da hızlı. Yine de, bunu ilgilenen okuyucular için buraya ekliyorum:

def cartesian_product_transpose(*arrays):
    broadcastable = numpy.ix_(*arrays)
    broadcasted = numpy.broadcast_arrays(*broadcastable)
    rows, cols = numpy.prod(broadcasted[0].shape), len(broadcasted)
    dtype = numpy.result_type(*arrays)

    out = numpy.empty(rows * cols, dtype=dtype)
    start, end = 0, rows
    for a in broadcasted:
        out[start:end] = a.reshape(-1)
        start, end = end, end + rows
    return out.reshape(cols, rows).T

Panzer'in yaklaşımını anlamaya başladıktan sonra, neredeyse onun kadar hızlı ve neredeyse basit olan yeni bir sürüm yazdım cartesian_product:

def cartesian_product_simple_transpose(arrays):
    la = len(arrays)
    dtype = numpy.result_type(*arrays)
    arr = numpy.empty([la] + [len(a) for a in arrays], dtype=dtype)
    for i, a in enumerate(numpy.ix_(*arrays)):
        arr[i, ...] = a
    return arr.reshape(la, -1).T

Bu, sabit girişler için küçük girişler için Panzer'lerden daha yavaş çalışmasını sağlıyor. Ancak daha büyük girdiler için, koştuğum tüm testlerde, en hızlı uygulama kadar iyi performans sergiliyor ( cartesian_product_transpose_pp).

İlerleyen bölümlerde, diğer alternatiflerin bazı testlerini de ekliyorum. Bunlar şimdi biraz güncel değil, ancak yinelenen çabalardan ziyade, onları tarihsel çıkarların dışında bırakmaya karar verdim. Güncel testler için Panzer'in yanı sıra Nico Schlömer'in cevabına bakınız .

Alternatiflere karşı testler

İşte bu işlevlerin bazılarının bir dizi alternatife göre sağladığı performans artışını gösteren bir test pili. Burada gösterilen tüm testler, Mac OS 10.12.5, Python 3.6.1 ve numpy1.12.1 çalıştıran dört çekirdekli bir makinede gerçekleştirildi . Donanım ve yazılımdaki varyasyonların farklı sonuçlar verdiği bilinmektedir, bu nedenle YMMV. Emin olmak için bu testleri yapın!

Tanımlar:

import numpy
import itertools
from functools import reduce

### Two-dimensional products ###

def repeat_product(x, y):
    return numpy.transpose([numpy.tile(x, len(y)), 
                            numpy.repeat(y, len(x))])

def dstack_product(x, y):
    return numpy.dstack(numpy.meshgrid(x, y)).reshape(-1, 2)

### Generalized N-dimensional products ###

def cartesian_product(*arrays):
    la = len(arrays)
    dtype = numpy.result_type(*arrays)
    arr = numpy.empty([len(a) for a in arrays] + [la], dtype=dtype)
    for i, a in enumerate(numpy.ix_(*arrays)):
        arr[...,i] = a
    return arr.reshape(-1, la)

def cartesian_product_transpose(*arrays):
    broadcastable = numpy.ix_(*arrays)
    broadcasted = numpy.broadcast_arrays(*broadcastable)
    rows, cols = numpy.prod(broadcasted[0].shape), len(broadcasted)
    dtype = numpy.result_type(*arrays)

    out = numpy.empty(rows * cols, dtype=dtype)
    start, end = 0, rows
    for a in broadcasted:
        out[start:end] = a.reshape(-1)
        start, end = end, end + rows
    return out.reshape(cols, rows).T

# from https://stackoverflow.com/a/1235363/577088

def cartesian_product_recursive(*arrays, out=None):
    arrays = [numpy.asarray(x) for x in arrays]
    dtype = arrays[0].dtype

    n = numpy.prod([x.size for x in arrays])
    if out is None:
        out = numpy.zeros([n, len(arrays)], dtype=dtype)

    m = n // arrays[0].size
    out[:,0] = numpy.repeat(arrays[0], m)
    if arrays[1:]:
        cartesian_product_recursive(arrays[1:], out=out[0:m,1:])
        for j in range(1, arrays[0].size):
            out[j*m:(j+1)*m,1:] = out[0:m,1:]
    return out

def cartesian_product_itertools(*arrays):
    return numpy.array(list(itertools.product(*arrays)))

### Test code ###

name_func = [('repeat_product',                                                 
              repeat_product),                                                  
             ('dstack_product',                                                 
              dstack_product),                                                  
             ('cartesian_product',                                              
              cartesian_product),                                               
             ('cartesian_product_transpose',                                    
              cartesian_product_transpose),                                     
             ('cartesian_product_recursive',                           
              cartesian_product_recursive),                            
             ('cartesian_product_itertools',                                    
              cartesian_product_itertools)]

def test(in_arrays, test_funcs):
    global func
    global arrays
    arrays = in_arrays
    for name, func in test_funcs:
        print('{}:'.format(name))
        %timeit func(*arrays)

def test_all(*in_arrays):
    test(in_arrays, name_func)

# `cartesian_product_recursive` throws an 
# unexpected error when used on more than
# two input arrays, so for now I've removed
# it from these tests.

def test_cartesian(*in_arrays):
    test(in_arrays, name_func[2:4] + name_func[-1:])

x10 = [numpy.arange(10)]
x50 = [numpy.arange(50)]
x100 = [numpy.arange(100)]
x500 = [numpy.arange(500)]
x1000 = [numpy.arange(1000)]

Test sonuçları:

In [2]: test_all(*(x100 * 2))
repeat_product:
67.5 µs ± 633 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
dstack_product:
67.7 µs ± 1.09 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
cartesian_product:
33.4 µs ± 558 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
cartesian_product_transpose:
67.7 µs ± 932 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
cartesian_product_recursive:
215 µs ± 6.01 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
cartesian_product_itertools:
3.65 ms ± 38.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [3]: test_all(*(x500 * 2))
repeat_product:
1.31 ms ± 9.28 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
dstack_product:
1.27 ms ± 7.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
cartesian_product:
375 µs ± 4.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
cartesian_product_transpose:
488 µs ± 8.88 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
cartesian_product_recursive:
2.21 ms ± 38.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_itertools:
105 ms ± 1.17 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [4]: test_all(*(x1000 * 2))
repeat_product:
10.2 ms ± 132 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
dstack_product:
12 ms ± 120 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product:
4.75 ms ± 57.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_transpose:
7.76 ms ± 52.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_recursive:
13 ms ± 209 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_itertools:
422 ms ± 7.77 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Her durumda, cartesian_productbu cevabın başında tanımlandığı gibi en hızlıdır.

Rastgele sayıda giriş dizisini kabul eden işlevler için, performansı da denetlemeye değer len(arrays) > 2. ( cartesian_product_recursiveBu durumda neden bir hata atılacağını belirleyene kadar , bu testlerden kaldırdım.)

In [5]: test_cartesian(*(x100 * 3))
cartesian_product:
8.8 ms ± 138 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_transpose:
7.87 ms ± 91.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_itertools:
518 ms ± 5.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [6]: test_cartesian(*(x50 * 4))
cartesian_product:
169 ms ± 5.1 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
cartesian_product_transpose:
184 ms ± 4.32 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
cartesian_product_itertools:
3.69 s ± 73.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [7]: test_cartesian(*(x10 * 6))
cartesian_product:
26.5 ms ± 449 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
cartesian_product_transpose:
16 ms ± 133 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_itertools:
728 ms ± 16 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [8]: test_cartesian(*(x10 * 7))
cartesian_product:
650 ms ± 8.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
cartesian_product_transpose:
518 ms ± 7.09 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
cartesian_product_itertools:
8.13 s ± 122 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Bu testlerin gösterdiği gibi, cartesian_productgirdi dizisi sayısı dördün üzerine (kabaca) yükselinceye kadar rekabetçi kalır. Bundan sonra, cartesian_product_transposehafif bir kenarı var.

Diğer donanım ve işletim sistemlerine sahip kullanıcıların farklı sonuçlar görebileceğini yinelemeye değer. Örneğin unutbu, Ubuntu numpy14.04 , Python 3.4.3 ve 1.14.0.dev0 + b7050a9 kullanarak bu testler için aşağıdaki sonuçları gördüğünü bildiriyor:

>>> %timeit cartesian_product_transpose(x500, y500) 
1000 loops, best of 3: 682 µs per loop
>>> %timeit cartesian_product(x500, y500)
1000 loops, best of 3: 1.55 ms per loop

Aşağıda, bu çizgiler boyunca yürüttüğüm önceki testler hakkında birkaç ayrıntıya giriyorum. Bu yaklaşımların göreceli performansı, farklı donanım ve farklı Python ve sürümleri için zamanla değişmiştir numpy. Güncel sürümlerini kullanan kişiler için hemen kullanışlı olmasa da numpy, bu cevabın ilk versiyonundan bu yana durumların nasıl değiştiğini göstermektedir.

Basit bir alternatif: meshgrid+dstack

Şu anda kabul edilen cevap tileve repeatiki diziyi birlikte yayınlamak için kullanılır. Ancak meshgridişlev pratik olarak aynı şeyi yapar. İşte çıktısı var tileve repeatdevrik geçirilmeden önce:

In [1]: import numpy
In [2]: x = numpy.array([1,2,3])
   ...: y = numpy.array([4,5])
   ...: 

In [3]: [numpy.tile(x, len(y)), numpy.repeat(y, len(x))]
Out[3]: [array([1, 2, 3, 1, 2, 3]), array([4, 4, 4, 5, 5, 5])]

Ve işte çıktısı meshgrid:

In [4]: numpy.meshgrid(x, y)
Out[4]: 
[array([[1, 2, 3],
        [1, 2, 3]]), array([[4, 4, 4],
        [5, 5, 5]])]

Gördüğünüz gibi, neredeyse aynı. Tam olarak aynı sonucu elde etmek için yalnızca sonucu yeniden şekillendirmemiz gerekir.

In [5]: xt, xr = numpy.meshgrid(x, y)
   ...: [xt.ravel(), xr.ravel()]
Out[5]: [array([1, 2, 3, 1, 2, 3]), array([4, 4, 4, 5, 5, 5])]

Aksine bu noktada yeniden şekillendirilmesi daha olsa da, biz çıktısını geçebileceği meshgridiçin dstackbazı işler kaydeder ve daha sonra yeniden biçimlendirilmesi,:

In [6]: numpy.dstack(numpy.meshgrid(x, y)).reshape(-1, 2)
Out[6]: 
array([[1, 4],
       [2, 4],
       [3, 4],
       [1, 5],
       [2, 5],
       [3, 5]])

İstem aksine bu yorumun , farklı girişler farklı şekilli çıktılar üretecek hiçbir kanıt gördüm ve yukarıda ortaya koyduğu gibi yaptılar eğer oldukça garip olurdu ararlar, bu nedenle çok benzer şeyler yaparlar. Karşı örnek bulursanız lütfen bize bildirin.

Test meshgrid+ dstackile repeat+ karşılaştırmasıtranspose

Bu iki yaklaşımın göreceli performansı zaman içinde değişmiştir. Python'un (2.7) daha önceki bir sürümünde, küçük girdiler için meshgrid+ dstackkullanımı önemli ölçüde daha hızlıydı. (Bu testlerin bu cevabın eski bir versiyonuna ait olduğunu unutmayın.) Tanımlar:

>>> def repeat_product(x, y):
...     return numpy.transpose([numpy.tile(x, len(y)), 
                                numpy.repeat(y, len(x))])
...
>>> def dstack_product(x, y):
...     return numpy.dstack(numpy.meshgrid(x, y)).reshape(-1, 2)
...     

Orta boyutlu giriş için önemli bir hız gördüm. Ancak bu testleri daha yeni numpybir makinede Python'un (3.6.1) ve (1.12.1) daha yeni sürümleriyle tekrar denedim. İki yaklaşım şimdi neredeyse aynı.

Eski Test

>>> x, y = numpy.arange(500), numpy.arange(500)
>>> %timeit repeat_product(x, y)
10 loops, best of 3: 62 ms per loop
>>> %timeit dstack_product(x, y)
100 loops, best of 3: 12.2 ms per loop

Yeni Test

In [7]: x, y = numpy.arange(500), numpy.arange(500)
In [8]: %timeit repeat_product(x, y)
1.32 ms ± 24.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [9]: %timeit dstack_product(x, y)
1.26 ms ± 8.47 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Her zaman olduğu gibi, YMMV, ancak bu, Python ve numpy'nin son sürümlerinde bunların değiştirilebilir olduğunu göstermektedir.

Genelleştirilmiş ürün fonksiyonları

Genel olarak, küçük girişler için yerleşik işlevlerin kullanılmasının daha hızlı olacağını beklerken, büyük girişler için özel amaçlı bir işlev daha hızlı olabilir. Dahası, genelleştirilmiş n boyutlu bir ürün için tileve repeatyardımcı olmaz, çünkü net yüksek boyutlu analogları yoktur. Bu nedenle, amaca yönelik oluşturulan işlevlerin davranışını da araştırmaya değer.

İlgili testlerin çoğu bu cevabın başında görünür, ancak burada Python'un önceki sürümlerinde ve numpykarşılaştırma için yapılan testlerden birkaçı .

cartesianTanımlanan fonksiyon başka bir yanıt daha büyük girişler için oldukça iyi bir performans kullandı. ( cartesian_product_recursiveYukarıda belirtilen işlevle aynıdır .) Karşılaştırmak cartesianiçin dstack_prodctsadece iki boyut kullanıyoruz.

Burada yine, eski test önemli bir fark gösterirken, yeni test neredeyse hiç göstermiyor.

Eski Test

>>> x, y = numpy.arange(1000), numpy.arange(1000)
>>> %timeit cartesian([x, y])
10 loops, best of 3: 25.4 ms per loop
>>> %timeit dstack_product(x, y)
10 loops, best of 3: 66.6 ms per loop

Yeni Test

In [10]: x, y = numpy.arange(1000), numpy.arange(1000)
In [11]: %timeit cartesian([x, y])
12.1 ms ± 199 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [12]: %timeit dstack_product(x, y)
12.7 ms ± 334 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Daha önce olduğu gibi, dstack_producthala cartesiandaha küçük ölçeklerde atıyor .

Yeni Test ( gereksiz eski test gösterilmemiştir )

In [13]: x, y = numpy.arange(100), numpy.arange(100)
In [14]: %timeit cartesian([x, y])
215 µs ± 4.75 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [15]: %timeit dstack_product(x, y)
65.7 µs ± 1.15 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Bu ayrımlar bence ilginç ve kayda değer; ama sonunda akademikler. Bu cevabın başlangıcındaki testlerin gösterdiği gibi, bu sürümlerin hepsi neredeyse her zaman cartesian_productbu cevabın en başında tanımlanandan daha yavaştır - ki bu da bu sorunun cevapları arasındaki en hızlı uygulamalardan biraz daha yavaştır.


1
ve dtype=objectiçine ekleme arr = np.empty( ), üründe farklı tiplerin kullanılmasına izin verecektir, örn arrays = [np.array([1,2,3]), ['str1', 'str2']].
user3820991

Yenilikçi çözümleriniz için çok teşekkürler. Bazı kullanıcıların makine işletim sistemlerine, python veya numpy sürümlerine bağlı olarak cartesian_product_tranposedaha hızlı bulabileceğini bilmek istediğinizi düşündüm cartesian_product. Örneğin, Ubuntu 14.04, python3.4.3, numpy 1.14.0.dev0 + b7050a9'da %timeit cartesian_product_transpose(x500,y500)verim 1000 loops, best of 3: 682 µs per loopverilirken %timeit cartesian_product(x500,y500)elde edilir 1000 loops, best of 3: 1.55 ms per loop. Ben de bulmak cartesian_product_transposedaha hızlı olabilir zaman len(arrays) > 2.
unutbu

Ayrıca, cartesian_productkayan noktalı dtype cartesian_product_transposedizisini döndürürken, ilk (yayınlanan) diziyle aynı dtype dizisini döndürür. Tamsayı dizilerle çalışırken dtype'i koruma yeteneği, kullanıcıların tercih etmesinin bir nedeni olabilir cartesian_product_transpose.
unutbu

@ unutbu tekrar teşekkürler - bildiğim gibi, dtype klonlama sadece kolaylık eklemek değildir; bazı durumlarda kodu% 20-30 oranında hızlandırır.
senderle

1
@senderle: Vay canına, bu çok hoş! Ayrıca, sadece dtype = np.find_common_type([arr.dtype for arr in arrays], [])kullanıcıyı ilk dtype'yi kontrol eden diziyi yerleştirmeye zorlamak yerine, tüm dizilerin ortak dtype'ini bulmak için kullanılabilecek bir şey oldu .
unutbu

44

Python'da normal liste kavraması yapabilirsiniz

x = numpy.array([1,2,3])
y = numpy.array([4,5])
[[x0, y0] for x0 in x for y0 in y]

hangisi sana vermeli

[[1, 4], [1, 5], [2, 4], [2, 5], [3, 4], [3, 5]]

28

Ben de bununla ilgileniyordum ve belki de @ senderle'ın cevabından biraz daha net bir performans karşılaştırması yaptım.

İki dizi için (klasik durum):

resim açıklamasını buraya girin

Dört dizi için:

resim açıklamasını buraya girin

(Dizilerin uzunluğunun burada sadece birkaç düzine giriş olduğunu unutmayın.)


Grafikleri yeniden oluşturmak için kod:

from functools import reduce
import itertools
import numpy
import perfplot


def dstack_product(arrays):
    return numpy.dstack(numpy.meshgrid(*arrays, indexing="ij")).reshape(-1, len(arrays))


# Generalized N-dimensional products
def cartesian_product(arrays):
    la = len(arrays)
    dtype = numpy.find_common_type([a.dtype for a in arrays], [])
    arr = numpy.empty([len(a) for a in arrays] + [la], dtype=dtype)
    for i, a in enumerate(numpy.ix_(*arrays)):
        arr[..., i] = a
    return arr.reshape(-1, la)


def cartesian_product_transpose(arrays):
    broadcastable = numpy.ix_(*arrays)
    broadcasted = numpy.broadcast_arrays(*broadcastable)
    rows, cols = reduce(numpy.multiply, broadcasted[0].shape), len(broadcasted)
    dtype = numpy.find_common_type([a.dtype for a in arrays], [])

    out = numpy.empty(rows * cols, dtype=dtype)
    start, end = 0, rows
    for a in broadcasted:
        out[start:end] = a.reshape(-1)
        start, end = end, end + rows
    return out.reshape(cols, rows).T


# from https://stackoverflow.com/a/1235363/577088
def cartesian_product_recursive(arrays, out=None):
    arrays = [numpy.asarray(x) for x in arrays]
    dtype = arrays[0].dtype

    n = numpy.prod([x.size for x in arrays])
    if out is None:
        out = numpy.zeros([n, len(arrays)], dtype=dtype)

    m = n // arrays[0].size
    out[:, 0] = numpy.repeat(arrays[0], m)
    if arrays[1:]:
        cartesian_product_recursive(arrays[1:], out=out[0:m, 1:])
        for j in range(1, arrays[0].size):
            out[j * m : (j + 1) * m, 1:] = out[0:m, 1:]
    return out


def cartesian_product_itertools(arrays):
    return numpy.array(list(itertools.product(*arrays)))


perfplot.show(
    setup=lambda n: 2 * (numpy.arange(n, dtype=float),),
    n_range=[2 ** k for k in range(13)],
    # setup=lambda n: 4 * (numpy.arange(n, dtype=float),),
    # n_range=[2 ** k for k in range(6)],
    kernels=[
        dstack_product,
        cartesian_product,
        cartesian_product_transpose,
        cartesian_product_recursive,
        cartesian_product_itertools,
    ],
    logx=True,
    logy=True,
    xlabel="len(a), len(b)",
    equality_check=None,
)

17

@ Senderle'ın örnek zemin çalışmasına dayanarak, biri C ve diğeri Fortran düzenleri için olmak üzere, genellikle biraz daha hızlı olan iki sürüm buldum.

  • cartesian_product_transpose_pp- cartesian_product_transposetamamen farklı bir strateji kullanan @ senderle'nin aksine - cartesion_productbunun daha uygun devrik bellek düzenini + bazı çok küçük optimizasyonları kullanan bir versiyonu .
  • cartesian_product_pporijinal bellek düzenine yapışır. Bunu hızlı yapan şey bitişik kopyalama kullanmaktır. Bitişik kopyalar o kadar hızlı olur ki, yalnızca bir kısmı geçerli veriler içeriyor olsa bile, tam bir bellek bloğunu kopyalamak yalnızca geçerli bitleri kopyalamaktan daha çok tercih edilir.

Bazı perflotlar. C ve Fortran düzenleri için ayrı olanlar yaptım, çünkü bunlar IMO'nun farklı görevleri.

'Pp' ile biten isimler benim yaklaşımlarım.

1) birçok küçük faktör (her biri 2 eleman)

resim açıklamasını buraya girinresim açıklamasını buraya girin

2) birçok küçük faktör (her biri 4 eleman)

resim açıklamasını buraya girinresim açıklamasını buraya girin

3) eşit uzunlukta üç faktör

resim açıklamasını buraya girinresim açıklamasını buraya girin

4) eşit uzunlukta iki faktör

resim açıklamasını buraya girinresim açıklamasını buraya girin

Kod (her arsa b / c için ayrı çalışır yapmak gerekir nasıl sıfırlamak için anlayamadım; ayrıca uygun şekilde düzenlemek / yorumlamak / yorumlamak gerekir):

import numpy
import numpy as np
from functools import reduce
import itertools
import timeit
import perfplot

def dstack_product(arrays):
    return numpy.dstack(
        numpy.meshgrid(*arrays, indexing='ij')
        ).reshape(-1, len(arrays))

def cartesian_product_transpose_pp(arrays):
    la = len(arrays)
    dtype = numpy.result_type(*arrays)
    arr = numpy.empty((la, *map(len, arrays)), dtype=dtype)
    idx = slice(None), *itertools.repeat(None, la)
    for i, a in enumerate(arrays):
        arr[i, ...] = a[idx[:la-i]]
    return arr.reshape(la, -1).T

def cartesian_product(arrays):
    la = len(arrays)
    dtype = numpy.result_type(*arrays)
    arr = numpy.empty([len(a) for a in arrays] + [la], dtype=dtype)
    for i, a in enumerate(numpy.ix_(*arrays)):
        arr[...,i] = a
    return arr.reshape(-1, la)

def cartesian_product_transpose(arrays):
    broadcastable = numpy.ix_(*arrays)
    broadcasted = numpy.broadcast_arrays(*broadcastable)
    rows, cols = numpy.prod(broadcasted[0].shape), len(broadcasted)
    dtype = numpy.result_type(*arrays)

    out = numpy.empty(rows * cols, dtype=dtype)
    start, end = 0, rows
    for a in broadcasted:
        out[start:end] = a.reshape(-1)
        start, end = end, end + rows
    return out.reshape(cols, rows).T

from itertools import accumulate, repeat, chain

def cartesian_product_pp(arrays, out=None):
    la = len(arrays)
    L = *map(len, arrays), la
    dtype = numpy.result_type(*arrays)
    arr = numpy.empty(L, dtype=dtype)
    arrs = *accumulate(chain((arr,), repeat(0, la-1)), np.ndarray.__getitem__),
    idx = slice(None), *itertools.repeat(None, la-1)
    for i in range(la-1, 0, -1):
        arrs[i][..., i] = arrays[i][idx[:la-i]]
        arrs[i-1][1:] = arrs[i]
    arr[..., 0] = arrays[0][idx]
    return arr.reshape(-1, la)

def cartesian_product_itertools(arrays):
    return numpy.array(list(itertools.product(*arrays)))


# from https://stackoverflow.com/a/1235363/577088
def cartesian_product_recursive(arrays, out=None):
    arrays = [numpy.asarray(x) for x in arrays]
    dtype = arrays[0].dtype

    n = numpy.prod([x.size for x in arrays])
    if out is None:
        out = numpy.zeros([n, len(arrays)], dtype=dtype)

    m = n // arrays[0].size
    out[:, 0] = numpy.repeat(arrays[0], m)
    if arrays[1:]:
        cartesian_product_recursive(arrays[1:], out=out[0:m, 1:])
        for j in range(1, arrays[0].size):
            out[j*m:(j+1)*m, 1:] = out[0:m, 1:]
    return out

### Test code ###
if False:
  perfplot.save('cp_4el_high.png',
    setup=lambda n: n*(numpy.arange(4, dtype=float),),
                n_range=list(range(6, 11)),
    kernels=[
        dstack_product,
        cartesian_product_recursive,
        cartesian_product,
#        cartesian_product_transpose,
        cartesian_product_pp,
#        cartesian_product_transpose_pp,
        ],
    logx=False,
    logy=True,
    xlabel='#factors',
    equality_check=None
    )
else:
  perfplot.save('cp_2f_T.png',
    setup=lambda n: 2*(numpy.arange(n, dtype=float),),
    n_range=[2**k for k in range(5, 11)],
    kernels=[
#        dstack_product,
#        cartesian_product_recursive,
#        cartesian_product,
        cartesian_product_transpose,
#        cartesian_product_pp,
        cartesian_product_transpose_pp,
        ],
    logx=True,
    logy=True,
    xlabel='length of each factor',
    equality_check=None
    )

Bu mükemmel yanıtı paylaştığınız için teşekkür ederiz. Büyüklüğü zaman arrayscartesian_product_transpose_pp içinde (diziler) belirli bir boyutu aşan, MemoryErrorortaya çıkar. Bu durumda, bu fonksiyonun daha küçük sonuçlar vermesini istiyorum. Bu konuda bir soru gönderdim . Soruma cevap verebilir misiniz? Teşekkürler.
Sun Bear

13

Ekim 2017 itibarıyla numpy artık np.stackbir eksen parametresi alan genel bir işleve sahiptir. Bunu kullanarak, "dstack ve meshgrid" tekniğini kullanarak bir "genelleştirilmiş kartezyen ürün" elde edebiliriz:

import numpy as np
def cartesian_product(*arrays):
    ndim = len(arrays)
    return np.stack(np.meshgrid(*arrays), axis=-1).reshape(-1, ndim)

axis=-1Parametre ile ilgili not . Bu, sonuçtaki son (en içteki) eksendir. Kullanmaya eşdeğerdir axis=ndim.

Başka bir yorum, Kartezyen ürünler çok hızlı bir şekilde patladığı için, bir nedenden ötürü hafızadaki diziyi gerçekleştirmemiz gerekmiyorsa , ürün çok büyükse, itertoolsdeğerleri anında kullanmak ve kullanmak isteyebiliriz .


8

Bir süre @kennytm cevabı kullandım , ama aynı şeyi TensorFlow'da yapmaya çalışırken, ama TensorFlow'un eşdeğeri olmadığını buldum numpy.repeat(). Biraz deney yaptıktan sonra, rastgele nokta vektörleri için daha genel bir çözüm bulduğumu düşünüyorum.

Numpy için:

import numpy as np

def cartesian_product(*args: np.ndarray) -> np.ndarray:
    """
    Produce the cartesian product of arbitrary length vectors.

    Parameters
    ----------
    np.ndarray args
        vector of points of interest in each dimension

    Returns
    -------
    np.ndarray
        the cartesian product of size [m x n] wherein:
            m = prod([len(a) for a in args])
            n = len(args)
    """
    for i, a in enumerate(args):
        assert a.ndim == 1, "arg {:d} is not rank 1".format(i)
    return np.concatenate([np.reshape(xi, [-1, 1]) for xi in np.meshgrid(*args)], axis=1)

ve TensorFlow için:

import tensorflow as tf

def cartesian_product(*args: tf.Tensor) -> tf.Tensor:
    """
    Produce the cartesian product of arbitrary length vectors.

    Parameters
    ----------
    tf.Tensor args
        vector of points of interest in each dimension

    Returns
    -------
    tf.Tensor
        the cartesian product of size [m x n] wherein:
            m = prod([len(a) for a in args])
            n = len(args)
    """
    for i, a in enumerate(args):
        tf.assert_rank(a, 1, message="arg {:d} is not rank 1".format(i))
    return tf.concat([tf.reshape(xi, [-1, 1]) for xi in tf.meshgrid(*args)], axis=1)

6

Scikit-öğrenme paketi tam olarak bu hızlı bir uygulama vardır:

from sklearn.utils.extmath import cartesian
product = cartesian((x,y))

Çıktının sırasını önemsiyorsanız, bu uygulamanın kuralının istediğinizden farklı olduğunu unutmayın. Tam siparişiniz için şunları yapabilirsiniz

product = cartesian((y,x))[:, ::-1]

Bu @ gönderenin işlevinden daha mı hızlı?
cs95

@ cᴏʟᴅsᴘᴇᴇᴅ Test etmedim. Bunun örneğin C veya Fortran'da uygulanmasını umuyordum ve bu yüzden neredeyse rakipsizdi, ancak NumPy kullanılarak yazılmış gibi görünüyor . Bu nedenle, bu işlev kullanışlıdır, ancak kişinin NumPy yapılarını kullanarak inşa edebileceğinden önemli ölçüde daha hızlı olmamalıdır.
jmd_dk

4

Daha genel olarak, a ve b olmak üzere iki adet 2d numpy diziniz varsa ve a'nın her satırını b'nin her satırına birleştirmek istiyorsanız (Satırların kartezyen bir ürünü, veritabanına birleştirme gibi), bu yöntemi kullanabilirsiniz :

import numpy
def join_2d(a, b):
    assert a.dtype == b.dtype
    a_part = numpy.tile(a, (len(b), 1))
    b_part = numpy.repeat(b, len(a), axis=0)
    return numpy.hstack((a_part, b_part))

3

Alabileceğiniz en hızlı şey, bir jeneratör ifadesini harita işleviyle birleştirmektir:

import numpy
import datetime
a = np.arange(1000)
b = np.arange(200)

start = datetime.datetime.now()

foo = (item for sublist in [list(map(lambda x: (x,i),a)) for i in b] for item in sublist)

print (list(foo))

print ('execution time: {} s'.format((datetime.datetime.now() - start).total_seconds()))

Çıktılar (aslında sonuçta ortaya çıkan listenin tamamı yazdırılır):

[(0, 0), (1, 0), ...,(998, 199), (999, 199)]
execution time: 1.253567 s

veya bir çift jeneratör ifadesi kullanarak:

a = np.arange(1000)
b = np.arange(200)

start = datetime.datetime.now()

foo = ((x,y) for x in a for y in b)

print (list(foo))

print ('execution time: {} s'.format((datetime.datetime.now() - start).total_seconds()))

Çıktılar (tüm liste basılı):

[(0, 0), (1, 0), ...,(998, 199), (999, 199)]
execution time: 1.187415 s

Hesaplama süresinin çoğunun yazdırma komutuna girdiğini dikkate alın. Jeneratör hesaplamaları başka şekilde oldukça verimlidir. Baskı olmadan hesaplama süreleri:

execution time: 0.079208 s

jeneratör ifadesi + harita fonksiyonu için ve:

execution time: 0.007093 s

çift ​​jeneratör ifadesi için.

Aslında istediğiniz koordinat çiftlerinin her birinin gerçek ürününü hesaplamaksa, en hızlısı onu bir numpy matris ürünü olarak çözmektir:

a = np.arange(1000)
b = np.arange(200)

start = datetime.datetime.now()

foo = np.dot(np.asmatrix([[i,0] for i in a]), np.asmatrix([[i,0] for i in b]).T)

print (foo)

print ('execution time: {} s'.format((datetime.datetime.now() - start).total_seconds()))

Çıktılar:

 [[     0      0      0 ...,      0      0      0]
 [     0      1      2 ...,    197    198    199]
 [     0      2      4 ...,    394    396    398]
 ..., 
 [     0    997   1994 ..., 196409 197406 198403]
 [     0    998   1996 ..., 196606 197604 198602]
 [     0    999   1998 ..., 196803 197802 198801]]
execution time: 0.003869 s

ve yazdırmadan (bu durumda çok fazla tasarruf sağlamaz, çünkü matrisin sadece küçük bir parçası yazdırılır):

execution time: 0.003083 s

Ürün hesaplaması için dış ürün yayını foo = a[:,None]*bdaha hızlıdır. Zamanlama yönteminizi kullanmadan print(foo), 0.001103 s vs 0.002225 s. Timeit kullanarak, 304 ms vs 1.6 ms. Matrix ndarray daha yavaş olduğu bilinmektedir, bu yüzden np.array ile kodunuzu denedim ama hala yayın (1.57 ms) daha yavaş.
syockit

2

Bu, itertools.product yöntemi kullanılarak da kolayca yapılabilir

from itertools import product
import numpy as np

x = np.array([1, 2, 3])
y = np.array([4, 5])
cart_prod = np.array(list(product(*[x, y])),dtype='int32')

Sonuç: dizi ([
[1, 4],
[1, 5],
[2, 4],
[2, 5],
[3, 4],
[3, 5]], dtype = int32)

Yürütme süresi: 0.000155 s


1
numpy çağırmanıza gerek yok. düz eski python dizileri de bununla çalışır.
Coddy

0

Her bir çift için ekleme gibi basit işlemler yapmanız gereken özel bir durumda, ekstra bir boyut getirebilir ve yayınların işi yapmasına izin verebilirsiniz:

>>> a, b = np.array([1,2,3]), np.array([10,20,30])
>>> a[None,:] + b[:,None]
array([[11, 12, 13],
       [21, 22, 23],
       [31, 32, 33]])

Çiftleri kendileri elde etmenin benzer bir yolu olup olmadığından emin değilim.


Eğer dtypebir floatYapabileceğiniz (a[:, None, None] + 1j * b[None, :, None]).view(float)şaşırtıcı derecede hızlı olan.
Paul Panzer
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.