Numpy:x和y数组的笛卡尔积指向单个2D点阵列

Ric*_*ich 134 python numpy cartesian-product

我有两个numpy数组,定义网格的x和y轴.例如:

x = numpy.array([1,2,3])
y = numpy.array([4,5])
Run Code Online (Sandbox Code Playgroud)

我想生成这些数组的笛卡尔积来生成:

array([[1,4],[2,4],[3,4],[1,5],[2,5],[3,5]])
Run Code Online (Sandbox Code Playgroud)

在某种程度上,由于我需要在循环中多次执行此操作,因此效率不高.我假设将它们转换为Python列表并使用itertools.product并返回到numpy数组并不是最有效的形式.

sen*_*rle 132

规范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)
Run Code Online (Sandbox Code Playgroud)

值得一提的是,这个功能ix_以不寻常的方式使用; 而记录的使用ix_是为了生成索引 一个数组,它只是恰巧,具有相同形状的阵列可用于广播的分配.非常感谢mgilson,他鼓励我尝试使用ix_这种方式,并且unutbu,谁提供了一些非常有用的反馈,包括建议使用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
Run Code Online (Sandbox Code Playgroud)

在了解了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
Run Code Online (Sandbox Code Playgroud)

这似乎有一些恒定时间开销,使得它比小输入的Panzer更慢.但是对于更大的输入,在我运行的所有测试中,它的表现与他最快的实现(cartesian_product_transpose_pp)相同.

在以下部分中,我将介绍其他替代方案的一些测试.这些现在已经过时了,但是我决定将它们留在这里,而不是重复的努力.有关最新测试,请参阅Panzer的答案,以及NicoSchlömer的答案.

测试替代品

以下是一系列测试,显示其中一些功能相对于许多替代方案提供的性能提升.此处显示的所有测试均在四核机器上执行,运行Mac OS 10.12.5,Python 3.6.1和numpy1.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)]
Run Code Online (Sandbox Code Playgroud)

检测结果:

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)
Run Code Online (Sandbox Code Playgroud)

在所有情况下,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)
Run Code Online (Sandbox Code Playgroud)

正如这些测试所显示的那样,cartesian_product在输入数组的数量超过(大约)四个之前,仍然具有竞争力.在那之后,cartesian_product_transpose确实有一点点优势.

值得重申的是,使用其他硬件和操作系统的用户可能会看到不同的结果.例如,unutbu报告使用Ubuntu 14.04,Python 3.4.3和numpy1.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
Run Code Online (Sandbox Code Playgroud)

下面,我将详细介绍我在这些方面运行的早期测试.对于不同的硬件和不同版本的Python和Python,这些方法的相对性能随着时间的推移而发生了变化numpy.虽然对于使用最新版本的人来说它并不是立即有用numpy,但它说明了自这个答案的第一个版本以来事情发生了怎样的变化.

一个简单的选择:meshgrid+dstack

当前接受的答案使用tilerepeat一起广播两个阵列.但该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])]
Run Code Online (Sandbox Code Playgroud)

以下是输出meshgrid:

In [4]: numpy.meshgrid(x, y)
Out[4]: 
[array([[1, 2, 3],
        [1, 2, 3]]), array([[4, 4, 4],
        [5, 5, 5]])]
Run Code Online (Sandbox Code Playgroud)

如你所见,它几乎完全相同.我们只需要重塑结果以获得完全相同的结果.

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])]
Run Code Online (Sandbox Code Playgroud)

但是,不是在此时重新塑造,我们可以将输出传递meshgriddstack并重新整形,这节省了一些工作:

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]])
Run Code Online (Sandbox Code Playgroud)

此评论中的说法相反,我没有看到任何证据表明不同的输入会产生不同形状的输出,并且正如上面所示,它们做了非常相似的事情,所以如果他们这样做会很奇怪.如果您找到反例,请告诉我.

测试meshgrid+ dstackvs. repeat+transpose

这两种方法的相对表现随着时间的推移而发生了变化.在早期版本的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)
...     
Run Code Online (Sandbox Code Playgroud)

对于中等大小的输入,我看到了显着的加速.但是我numpy在更新的机器上用更新版本的Python(3.6.1)和(1.12.1)重试了这些测试.这两种方法现在几乎相同.

旧测试

>>> 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
Run Code Online (Sandbox Code Playgroud)

新测试

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)
Run Code Online (Sandbox Code Playgroud)

和往常一样,YMMV,但这表明在Python和numpy的最新版本中,这些是可以互换的.

广义的产品功能

通常,我们可能期望使用内置函数对于小输入更快,而对于大输入,专用函数可能更快.此外,对于广义的n维产品,tilerepeat没有帮助,因为它们没有明确的高维类似物.因此,值得调查专用函数的行为也是值得的.

大多数相关测试都出现在本答案的开头,但这里有一些在早期版本的Python上进行的测试并numpy进行比较.

cartesian另一个答案中定义的函数用于较大的输入执行.(这是一样调用该函数cartesian_product_recursive上面).为了比较cartesiandstack_prodct,我们只使用两个维度.

同样,旧测试显示出显着差异,而新测试几乎没有.

旧测试

>>> 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
Run Code Online (Sandbox Code Playgroud)

新测试

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)
Run Code Online (Sandbox Code Playgroud)

和以前一样,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)
Run Code Online (Sandbox Code Playgroud)

我认为,这些区别很有意思,值得记录; 但他们最终是学术性的.正如本答案开头的测试所示,所有这些版本几乎总是比cartesian_product在本答案开头时定义的慢- 这本身比这个问题的答案中最快的实现要慢一些.


ken*_*ytm 80

>>> 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]])
Run Code Online (Sandbox Code Playgroud)

请参阅使用numpy构建两个数组的所有组合的数组,以获得用于计算N个数组的笛卡尔乘积的通用解决方案.

  • @tlnagy,我没有注意到这种方法产生的结果与`meshgrid` +`dstack`产生的结果不同.你能发一个例子吗? (3认同)

ozo*_*oxo 41

你可以在python中进行正常的列表理解

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

哪个应该给你

[[1, 4], [1, 5], [2, 4], [2, 5], [3, 4], [3, 5]]
Run Code Online (Sandbox Code Playgroud)

  • 当当!如果您需要长度为 n × m 的二维数组,只需将一个循环包装在一个单独的推导式中:而不是 `[(x0, y0) for x0 in x for y0 in y]` 而是 `[[(x0, y0) for x0在 x] 中对于 y0 在 y]` (3认同)

Nic*_*mer 22

我也对此感兴趣并进行了一些性能比较,可能比@ senderle的答案更清晰.

对于两个数组(经典案例):

在此输入图像描述

对于四个阵列:

在此输入图像描述

(注意,数组的长度在这里只有几十个条目.)


重现图的代码:

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
    )
Run Code Online (Sandbox Code Playgroud)


Pau*_*zer 13

在@ senderle的示范性基础工作的基础上,我提出了两个版本 - 一个用于C,一个用于Fortran布局 - 通常更快一些.

  • cartesian_product_transpose_pp是 - 不像@ senderle's cartesian_product_transpose使用不同的策略 - 它的一个版本cartesion_product使用更有利的转置内存布局+一些非常小的优化.
  • cartesian_product_pp坚持原有的内存布局.它的快速之处在于它使用连续复制.连续拷贝变得非常快,以至于复制整个内存块即使只有部分内存包含有效数据也比仅复制有效位更好.

一些perfplots.我为C和Fortran布局分别制作了,因为这些是IMO的不同任务.

以'pp'结尾的名字是我的方法.

1)许多微小因素(每个2个元素)

在此输入图像描述在此输入图像描述

2)许多小因素(每个4个元素)

在此输入图像描述在此输入图像描述

3)长度相等的三个因素

在此输入图像描述在此输入图像描述

4)两个等长的因素

在此输入图像描述在此输入图像描述

代码(需要为每个绘图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
    )
Run Code Online (Sandbox Code Playgroud)


MrD*_*ner 9

截至2017年10月,numpy现在有一个np.stack带有轴参数的通用函数.使用它,我们可以使用"dstack和meshgrid"技术获得"通用笛卡尔积":

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

关于axis=-1参数的注意事项.这是结果中的最后一个(最内部)轴.它相当于使用axis=ndim.

另外一条评论,因为笛卡儿产品爆炸非常快,除非我们出于某种原因需要在内存中实现阵列,如果产品非常大,我们可能想要itertools即时使用和使用这些值.


Sea*_*igh 8

我使用@kennytm 答案了一段时间,但是当尝试在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)
Run Code Online (Sandbox Code Playgroud)

对于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)
Run Code Online (Sandbox Code Playgroud)


jmd*_*_dk 5

Scikit学习封装具有快速实施的正是这一点:

from sklearn.utils.extmath import cartesian
product = cartesian((x,y))
Run Code Online (Sandbox Code Playgroud)

请注意,如果您关心输出的顺序,此实现的约定与您想要的不同.对于您的确切订购,您可以这样做

product = cartesian((y,x))[:, ::-1]
Run Code Online (Sandbox Code Playgroud)

  • @cᴏʟᴅsᴘᴇᴇᴅ我还没有测试过。我希望这是用 C 或 Fortran 等语言实现的,因此几乎是无与伦比的,但是[看起来](https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/utils/extmath. py) 使用 NumPy 编写。因此,这个函数很方便,但不应该比使用 NumPy 自己构建的函数快得多。 (2认同)