web-dev-qa-db-ja.com

xおよびy配列点のデカルト積から2D点の単一配列へ

グリッドのx軸とy軸を定義する2つのnumpy配列があります。例えば:

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

これらの配列のデカルト積を生成して生成します:

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

ループで何度もこれを行う必要があるため、ひどく非効率的ではありません。それらをPythonリストに変換し、itertools.productを使用してnumpy配列に戻すことは、最も効率的な形式ではないと想定しています。

129
Rich
>>> 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配列のデカルト積を計算するための一般的なソリューションについては、 numpyを使用して2つの配列のすべての組み合わせの配列を構築する を参照してください。

76
kennytm

正規のcartesian_product(ほぼ)

さまざまなプロパティを使用して、この問題に対する多くのアプローチがあります。他のものよりも高速なものもあれば、より汎用的なものもあります。多くのテストと微調整の後、n次元のcartesian_productを計算する次の関数は、多くの入力で他のほとんどの関数よりも高速であることがわかりました。少し複雑ですが、多くの場合は少し高速なアプローチのペアについては、 Paul Panzer による回答を参照してください。

その答えを考えると、これはもはや私が知っているnumpyのデカルト積の最速実装ではありません。ただし、そのシンプルさは今後の改善のための有用なベンチマークであり続けると思います。

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)

この関数がix_を異常な方法で使用することに言及する価値があります。文書化されたix_の使用法は 生成インデックスinto配列ですが、同じ形の配列をブロードキャスト割り当てに使用できるのはまさにそのためです。 mgilson 、この方法でix_の使用を試みたインスピレーション、および nutb に感謝します。 numpy.result_type

注目すべき代替案

連続したメモリブロックをFortranの順序で書き込む方が速い場合があります。これが、この代替案の基礎であるcartesian_product_transposeであり、一部のハードウェアではcartesian_productよりも高速であることが証明されています(以下を参照)。ただし、同じ原則を使用するPaul Panzerの回答はさらに高速です。それでも、興味のある読者のためにここにこれを含めます:

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のアプローチを理解した後、私は彼とほぼ同じ速度で、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

これは、一定の時間のオーバーヘッドがあるため、小さな入力の場合はPanzerよりも遅くなります。しかし、大規模な入力の場合、私が実行したすべてのテストで、彼の最速の実装(cartesian_product_transpose_pp)と同様に機能します。

次のセクションでは、他の選択肢のいくつかのテストを含めます。これらは現在多少古くなっていますが、重複した作業ではなく、ここで歴史的な興味から除外することにしました。最新のテストについては、Panzerの回答と NicoSchlömer を参照してください。

代替案に対するテスト

以下は、これらの機能のいくつかがいくつかの選択肢と比較して提供するパフォーマンスの向上を示す一連のテストです。ここに示すすべてのテストは、Mac OS 10.12.5、Python 3.6.1、およびnumpy 1.12.1を実行しているクアッドコアマシンで実行されました。ハードウェアとソフトウェアのバリエーションは異なる結果をもたらすことが知られているため、YMMV。確認するために、これらのテストを自分で実行してください!

定義:

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

試験結果:

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)

すべての場合において、この回答の冒頭で定義されているcartesian_productが最も高速です。

任意の数の入力配列を受け入れる関数の場合、len(arrays) > 2の場合もパフォーマンスをチェックする価値があります。 (この場合 cartesian_product_recursive がエラーをスローする理由を特定できるまで、これらのテストから削除しました。)

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)

これらのテストが示すように、cartesian_productは、入力配列の数が(およそ)4を超えるまで競争力を維持します。その後、cartesian_product_transposeにはわずかなエッジがあります。

他のハードウェアおよびオペレーティングシステムを使用しているユーザーは異なる結果を見る可能性があることを繰り返し説明する価値があります。たとえば、unutbuのレポートでは、Ubuntu 14.04、Python 3.4.3、およびnumpy 1.14.0.dev0 + b7050a9を使用したこれらのテストについて、次の結果が表示されます。

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

以下では、これらのラインに沿って実行した以前のテストに関するいくつかの詳細を説明します。これらのアプローチの相対的なパフォーマンスは、異なるハードウェアと異なるバージョンのPythonおよびnumpyで経時的に変化しています。 numpyの最新バージョンを使用しているユーザーにとってすぐに役立つわけではありませんが、この回答の最初のバージョン以降の状況の変化を示しています。

簡単な代替手段:meshgrid + dstack

現在受け入れられている回答では、tilerepeatを使用して、2つのアレイを一緒にブロードキャストします。ただし、meshgrid関数は実質的に同じことを行います。転置に渡される前のtileおよびrepeatの出力は次のとおりです。

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

そして、これがmeshgridの出力です:

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

ご覧のとおり、ほぼ同じです。まったく同じ結果を得るために、結果を再形成するだけです。

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

ただし、この時点で形状を変更するのではなく、meshgridの出力をdstackに渡し、後で形状を変更することで作業を節約できます。

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

このコメント の主張に反して、異なる入力が異なる形状の出力を生成するという証拠は見ていません。上記の例が示すように、それらは非常に似たようなことをするので、した。反例を見つけたら教えてください。

テストmeshgrid + dstack vs. repeat + transpose

これら2つのアプローチの相対的なパフォーマンスは、時間の経過とともに変化しています。 Python(2.7)の以前のバージョンでは、meshgrid + dstackを使用した結果は、小さな入力に対して著しく高速でした。 (これらのテストはこの回答の古いバージョンからのものであることに注意してください。)定義:

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

中程度のサイズの入力では、大幅な高速化が見られました。しかし、新しいマシンで、Python(3.6.1)およびnumpy(1.12.1)のより新しいバージョンでこれらのテストを再試行しました。 2つのアプローチは現在ほとんど同じです。

古いテスト

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

新しいテスト

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)

いつものように、YMMVですが、これはPythonとnumpyの最近のバージョンでは、これらは互換性があることを示唆しています。

一般化された製品機能

一般に、小さな入力の場合は組み込み関数を使用した方が速くなりますが、大きな入力の場合は専用の関数を使用した方が速くなることが予想されます。さらに、一般化されたn次元製品の場合、tilerepeatは、高次元の類似物が明確にないため、役に立ちません。そのため、専用の関数の動作も調査する価値があります。

関連するテストのほとんどはこの回答の最初にありますが、比較のためにPythonおよびnumpyの以前のバージョンで実行されたテストのいくつかを次に示します。

another answer で定義されているcartesian関数は、大きな入力に対して非常にうまく機能するために使用されます。 (上記のcartesian_product_recursiveという関数と同じです。)cartesiandstack_prodctと比較するために、2つのディメンションのみを使用します。

ここでも、古いテストでは大きな違いが見られましたが、新しいテストではほとんど違いが見られませんでした。

古いテスト

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

新しいテスト

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)

前と同じように、dstack_productはまだ小さいスケールでcartesianに勝っています。

新しいテスト冗長な古いテストは表示されません

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)

これらの違いは、興味深いと記録する価値があると思います。しかし、最終的には学術的です。この回答の冒頭のテストが示したように、これらのバージョンはすべて、この回答の冒頭で定義されたcartesian_productよりもほとんど常に遅いです。

128
senderle

あなたはPythonで通常のリストの理解を行うことができます

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

あなたに与えるはずです

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

私もこれに興味があり、パフォーマンスの比較を少し行いました。おそらく@senderleの答えよりもいくらか明確です。

2つの配列の場合(古典的な場合):

enter image description here

4つの配列の場合:

enter image description here

(配列の長さは、ここでは数ダースのエントリにすぎないことに注意してください。)


プロットを再現するコード:

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: 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
    )
22
Nico Schlömer

@senderleの模範的な地上作業に基づいて、C版とFortranレイアウト用の2つのバージョンを作成しました。

  • cartesian_product_transpose_ppは-まったく異なる戦略を使用する@senderleのcartesian_product_transposeとは異なり-より有利な転置メモリレイアウトを使用するcartesion_productのバージョン+いくつかの非常に小さな最適化。
  • cartesian_product_ppは元のメモリレイアウトを維持します。高速化するのは、連続コピーの使用です。連続コピーは非常に高速であることが判明しているため、有効なデータのみが有効なデータを含む場合でも、有効なビットのみをコピーするよりもメモリの完全なブロックをコピーする方が望ましい場合があります。

いくつかのperfplots。 IMOとは異なるタスクであるため、CレイアウトとFortranレイアウト用に別々のものを作成しました。

「pp」で終わる名前は私のアプローチです。

1)多くの小さな要素(各2要素)

enter image description hereenter image description here

2)多くの小さな要素(各4要素)

enter image description hereenter image description here

3)等しい長さの3つの要因

enter image description hereenter image description here

4)長さが等しい2つの要因

enter image description hereenter image description here

コード(各プロットb/cごとに個別の実行を行う必要がありますリセットする方法がわかりませんでした;適切に編集/コメントアウトする必要があります):

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
    )
13
Paul Panzer

2017年10月の時点で、numpyには軸パラメーターを受け取る汎用np.stack関数があります。これを使用して、「dstack and meshgrid」手法を使用した「一般化されたデカルト積」を作成できます。

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

axis=-1パラメーターに関する注意。これは、結果の最後の(最も内側の)軸です。 axis=ndimを使用するのと同等です。

もう1つのコメント、デカルト積は非常に急速に爆発するため、何らかの理由でメモリ内の配列を実現するためにneedを除き、製品が非常に大きい場合は、itertoolsを使用して、オンザフライの値。

9
MrDrFenner

@kennytm answer をしばらく使用しましたが、TensorFlowで同じことをしようとしたときに、TensorFlowにはnumpy.repeat()に相当するものがないことがわかりました。少し実験した後、任意の点のベクトルについてより一般的な解決策を見つけたと思います。

Numpyの場合:

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)

tensorFlowの場合:

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)
8
Sean McVeigh

Scikit-learn パッケージには、まさにこれの高速実装があります。

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

出力の順序に注意を払う場合、この実装の規則は必要なものとは異なることに注意してください。正確な注文については、次のことができます

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

より一般的には、2つの2D numpy配列aとbがあり、aのすべての行をbのすべての行に連結したい場合(行のデカルト積、データベースの結合のようなもの)、このメソッドを使用できます:

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))
4
Jonathan

取得できる最速の方法は、ジェネレーター式とマップ関数を組み合わせることです。

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

出力(実際には結果リスト全体が印刷されます):

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

または、二重ジェネレーター式を使用して:

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

出力(リスト全体の印刷):

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

計算時間のほとんどが印刷コマンドに費やされることを考慮してください。それ以外の場合、ジェネレーターの計算はまともな効率です。印刷しない場合、計算時間は次のとおりです。

execution time: 0.079208 s

ジェネレーター式+マップ関数、および:

execution time: 0.007093 s

ダブルジェネレーター式用。

実際に必要なのが各座標ペアの実際の積を計算することである場合、最も速いのはそれをnumpy行列積として解くことです:

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

出力:

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

そして、印刷せず(この場合、マトリックスのごく一部のみが実際に印刷されるため、あまり節約されません):

execution time: 0.003083 s
3
mosegui

各ペアの追加などの単純な操作を実行する必要がある特定のケースでは、余分な次元を導入して、ブロードキャストでジョブを実行できます。

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

実際にペアを取得する同様の方法があるかどうかはわかりません。

0
Caagr98

これはitertools.productメソッドを使用して簡単に行うこともできます

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

結果:array([
[1、4]、
[1、5]、
[2、4]、
[2、5]、
[3、4]、
[3、5]]、dtype = int32)

実行時間:0.000155秒