Sản phẩm của Cartesian của các mảng x và y thành một mảng các điểm 2D


147

Tôi có hai mảng numpy xác định trục x và y của lưới. Ví dụ:

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

Tôi muốn tạo sản phẩm Cartesian của các mảng này để tạo:

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

Theo một cách không hiệu quả khủng khiếp vì tôi cần phải làm điều này nhiều lần trong một vòng lặp. Tôi giả định rằng việc chuyển đổi chúng thành một danh sách Python và sử dụng itertools.productvà quay lại một mảng numpy không phải là hình thức hiệu quả nhất.


Tôi nhận thấy rằng bước đắt nhất trong cách tiếp cận itertools là chuyển đổi cuối cùng từ danh sách sang mảng. Không có bước cuối cùng này, nó nhanh gấp đôi ví dụ của Ken.
Alexey Lebedev

Câu trả lời:


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

Xem Sử dụng numpy để xây dựng một mảng gồm tất cả các kết hợp của hai mảng cho một giải pháp chung để tính toán sản phẩm Cartesian của N mảng.


1
Một lợi thế của phương pháp này là nó tạo ra đầu ra nhất quán cho các mảng có cùng kích thước. Cách tiếp cận meshgrid+ dstack, trong khi nhanh hơn trong một số trường hợp, có thể dẫn đến lỗi nếu bạn mong muốn sản phẩm cartesian được xây dựng theo cùng thứ tự cho các mảng có cùng kích thước.
tlnagy

3
@tlnagy, tôi không nhận thấy bất kỳ trường hợp nào phương pháp này tạo ra kết quả khác với kết quả được tạo bởi meshgrid+ dstack. Bạn có thể gửi một ví dụ?
gửi

148

Một kinh điển cartesian_product(gần như)

Có nhiều cách tiếp cận vấn đề này với các tính chất khác nhau. Một số nhanh hơn những cái khác, và một số có mục đích chung hơn. Sau rất nhiều thử nghiệm và điều chỉnh, tôi đã thấy rằng hàm sau, tính toán một chiều n cartesian_product, nhanh hơn hầu hết các hàm khác cho nhiều đầu vào. Đối với một cặp cách tiếp cận phức tạp hơn một chút, nhưng thậm chí nhanh hơn một chút trong nhiều trường hợp, xem câu trả lời của Paul Panzer .

Với câu trả lời đó, đây không còn là triển khai nhanh nhất của sản phẩm cartesian numpymà tôi biết. Tuy nhiên, tôi nghĩ rằng sự đơn giản của nó sẽ tiếp tục làm cho nó trở thành một chuẩn mực hữu ích để cải thiện trong tương lai:

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)

Điều đáng nói là chức năng này sử dụng ix_một cách bất thường; trong khi việc sử dụng tài liệu ix_là để tạo các chỉ mục thành một mảng, điều đó xảy ra là các mảng có cùng hình dạng có thể được sử dụng để gán truyền phát. Rất cám ơn mgilson , người đã truyền cảm hứng cho tôi để thử sử dụng cách ix_này và unutbu , người đã cung cấp một số phản hồi cực kỳ hữu ích cho câu trả lời này, bao gồm cả gợi ý sử dụng numpy.result_type.

Các lựa chọn thay thế đáng chú ý

Đôi khi nhanh hơn để viết các khối bộ nhớ liền kề theo thứ tự Fortran. Đó là cơ sở của sự thay thế này, cartesian_product_transposeđã được chứng minh nhanh hơn trên một số phần cứng so với cartesian_product(xem bên dưới). Tuy nhiên, câu trả lời của Paul Panzer, sử dụng cùng một nguyên tắc, thậm chí còn nhanh hơn. Tuy nhiên, tôi bao gồm điều này ở đây cho độc giả quan tâm:

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

Sau khi hiểu được cách tiếp cận của Panzer, tôi đã viết một phiên bản mới gần như nhanh như của anh ấy, và gần như đơn giản như 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

Điều này dường như có một số chi phí không đổi thời gian liên tục làm cho nó chạy chậm hơn so với Panzer cho các đầu vào nhỏ. Nhưng đối với các đầu vào lớn hơn, trong tất cả các thử nghiệm tôi đã chạy, nó thực hiện tốt như việc thực hiện nhanh nhất của anh ấy ( cartesian_product_transpose_pp).

Trong các phần sau, tôi bao gồm một số thử nghiệm của các lựa chọn thay thế khác. Hiện tại chúng đã lỗi thời, nhưng thay vì nỗ lực trùng lặp, tôi đã quyết định để chúng ở đây vì lợi ích lịch sử. Đối với các bài kiểm tra cập nhật, xem câu trả lời của Panzer, cũng như của Nico Schlömer .

Thử nghiệm thay thế

Dưới đây là một loạt các bài kiểm tra cho thấy hiệu suất tăng mà một số chức năng này cung cấp liên quan đến một số lựa chọn thay thế. Tất cả các thử nghiệm hiển thị ở đây được thực hiện trên máy lõi tứ, chạy Mac OS 10.12.5, Python 3.6.1 và numpy1.12.1. Biến thể trên phần cứng và phần mềm được biết là tạo ra kết quả khác nhau, vì vậy YMMV. Chạy thử nghiệm cho chính mình để chắc chắn!

Định nghĩa:

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

Kết quả kiểm tra:

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)

Trong mọi trường hợp, cartesian_productnhư được định nghĩa ở đầu câu trả lời này là nhanh nhất.

Đối với những hàm chấp nhận số lượng mảng đầu vào tùy ý, thì cũng đáng để kiểm tra hiệu năng len(arrays) > 2. (Cho đến khi tôi có thể xác định lý do tại sao cartesian_product_recursiveném lỗi trong trường hợp này, tôi đã xóa nó khỏi các thử nghiệm này.)

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)

Như các thử nghiệm này cho thấy, cartesian_productvẫn cạnh tranh cho đến khi số lượng mảng đầu vào tăng lên trên (khoảng) bốn. Sau đó, cartesian_product_transposecó một cạnh nhẹ.

Cần nhắc lại rằng người dùng có phần cứng và hệ điều hành khác có thể thấy các kết quả khác nhau. Ví dụ: các báo cáo unutbu nhìn thấy các kết quả sau cho các thử nghiệm này bằng Ubuntu 14.04, Python 3.4.3 và 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

Dưới đây, tôi đi vào một vài chi tiết về các thử nghiệm trước đây tôi đã chạy dọc theo các dòng này. Hiệu suất tương đối của các phương pháp này đã thay đổi theo thời gian, đối với phần cứng khác nhau và các phiên bản khác nhau của Python và numpy. Mặc dù nó không hữu ích ngay lập tức cho những người sử dụng các phiên bản cập nhật của numpynó, nhưng nó minh họa cách mọi thứ đã thay đổi kể từ phiên bản đầu tiên của câu trả lời này.

Một thay thế đơn giản: meshgrid+dstack

Câu trả lời hiện được chấp nhận sử dụng tilerepeatđể phát hai mảng với nhau. Nhưng meshgridchức năng thực tế là điều tương tự. Đây là đầu ra của tilerepeattrước khi được chuyển sang transpose:

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

Và đây là đầu ra của meshgrid:

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

Như bạn có thể thấy, nó gần như giống hệt nhau. Chúng tôi chỉ cần định hình lại kết quả để có được kết quả chính xác như vậy.

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

Thay vì tái định hình vào thời điểm này, tuy nhiên, chúng ta có thể vượt qua sản lượng meshgridđể dstackvà Reshape sau đó, giúp tiết kiệm một số công việc:

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

Trái ngược với tuyên bố trong nhận xét này , tôi không thấy bằng chứng nào cho thấy các đầu vào khác nhau sẽ tạo ra các đầu ra có hình dạng khác nhau, và như đã chứng minh ở trên, chúng làm những việc rất giống nhau, vì vậy sẽ rất lạ nếu chúng làm như vậy. Xin vui lòng cho tôi biết nếu bạn tìm thấy một ví dụ mẫu.

Kiểm tra meshgrid+ dstackso với repeat+transpose

Hiệu suất tương đối của hai phương pháp này đã thay đổi theo thời gian. Trong phiên bản trước của Python (2.7), kết quả sử dụng meshgrid+ dstacknhanh hơn đáng kể cho các đầu vào nhỏ. (Lưu ý rằng các bài kiểm tra này là từ một phiên bản cũ của câu trả lời này.) Định nghĩa:

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

Đối với đầu vào có kích thước vừa phải, tôi đã thấy một sự tăng tốc đáng kể. Nhưng tôi đã thử lại các thử nghiệm này với các phiên bản Python mới hơn (3.6.1) và numpy(1.12.1), trên một máy mới hơn. Hai cách tiếp cận gần như giống hệt nhau bây giờ.

Kiểm tra cũ

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

Thử nghiệm mới

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)

Như mọi khi, YMMV, nhưng điều này cho thấy rằng trong các phiên bản gần đây của Python và numpy, chúng có thể hoán đổi cho nhau.

Chức năng sản phẩm tổng quát

Nói chung, chúng tôi có thể hy vọng rằng việc sử dụng các hàm tích hợp sẽ nhanh hơn cho các đầu vào nhỏ, trong khi đối với các đầu vào lớn, một hàm xây dựng có mục đích có thể nhanh hơn. Hơn nữa, đối với một sản phẩm n chiều tổng quát, tilerepeatsẽ không giúp ích gì, vì chúng không có các chất tương tự chiều cao rõ ràng hơn. Vì vậy, nó cũng đáng để điều tra hành vi của các chức năng được xây dựng có mục đích là tốt.

Hầu hết các thử nghiệm có liên quan xuất hiện ở đầu câu trả lời này, nhưng đây là một vài thử nghiệm được thực hiện trên các phiên bản trước của Python và numpyđể so sánh.

Các cartesianchức năng được định nghĩa trong câu trả lời khác sử dụng để thực hiện khá tốt cho đầu vào lớn hơn. (Nó giống như hàm được gọi cartesian_product_recursiveở trên.) Để so sánh cartesianvới dstack_prodct, chúng tôi chỉ sử dụng hai chiều.

Ở đây một lần nữa, thử nghiệm cũ cho thấy một sự khác biệt đáng kể, trong khi thử nghiệm mới cho thấy hầu như không có.

Kiểm tra cũ

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

Thử nghiệm mới

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)

Như trước, dstack_product vẫn đập cartesianở quy mô nhỏ hơn.

Thử nghiệm mới ( thử nghiệm cũ dư thừa không được hiển thị )

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)

Những sự phân biệt này, tôi nghĩ, thú vị và đáng ghi lại; nhưng cuối cùng họ học tập Như các bài kiểm tra ở đầu câu trả lời này cho thấy, tất cả các phiên bản này hầu như luôn chậm hơn cartesian_product, được xác định ngay từ đầu câu trả lời này - bản thân nó chậm hơn một chút so với các câu trả lời nhanh nhất trong số các câu trả lời cho câu hỏi này.


1
và thêm dtype=objectvào arr = np.empty( )sẽ cho phép sử dụng các loại khác nhau trong sản phẩm, ví dụ arrays = [np.array([1,2,3]), ['str1', 'str2']].
dùng3820991

Cảm ơn rất nhiều cho các giải pháp sáng tạo của bạn. Chỉ cần nghĩ rằng bạn muốn biết một số người dùng có thể tìm thấy cartesian_product_tranposenhanh hơn cartesian_producttùy thuộc vào hệ điều hành máy, phiên bản python hoặc numpy của họ. Ví dụ: trên Ubuntu 14.04, python3.4.3, numpy 1.14.0.dev0 + b7050a9, %timeit cartesian_product_transpose(x500,y500)mang lại hiệu quả 1000 loops, best of 3: 682 µs per looptrong khi %timeit cartesian_product(x500,y500)năng suất 1000 loops, best of 3: 1.55 ms per loop. Tôi cũng đang tìm kiếm cartesian_product_transposecó thể nhanh hơn khi len(arrays) > 2.
unutbu

Ngoài ra, cartesian_producttrả về một mảng của dtype dấu phẩy động trong khi cartesian_product_transposetrả về một mảng của dtype giống như mảng đầu tiên (được phát sóng). Khả năng bảo toàn dtype khi làm việc với mảng số nguyên có thể là một lý do để người dùng ủng hộ cartesian_product_transpose.
unutbu

@unutbu cảm ơn một lần nữa - như tôi đã biết, nhân bản dtype không chỉ thêm tiện lợi; nó tăng tốc mã thêm 20-30% trong một số trường hợp.
gửi

1
@senderle: Wow, thật tuyệt! Ngoài ra, điều đó xảy ra với tôi rằng một cái gì đó giống như dtype = np.find_common_type([arr.dtype for arr in arrays], [])có thể được sử dụng để tìm ra loại dtype chung của tất cả các mảng, thay vì buộc người dùng phải đặt mảng điều khiển dtype trước.
unutbu

44

Bạn chỉ có thể làm việc hiểu danh sách bình thường trong python

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

cái nào sẽ cho bạn

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

28

Tôi cũng quan tâm đến điều này và đã làm một so sánh hiệu suất nhỏ, có lẽ rõ ràng hơn câu trả lời của @ senderle.

Đối với hai mảng (trường hợp cổ điển):

nhập mô tả hình ảnh ở đây

Đối với bốn mảng:

nhập mô tả hình ảnh ở đây

(Lưu ý rằng độ dài của các mảng chỉ có vài chục mục ở đây.)


Mã để tái tạo các ô:

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

Xây dựng trên nền tảng công việc mẫu mực của @ senderle Tôi đã đưa ra hai phiên bản - một cho C và một cho bố cục Fortran - thường nhanh hơn một chút.

  • cartesian_product_transpose_pplà - không giống như @ senderle cartesian_product_transposesử dụng chiến lược hoàn toàn khác - một phiên bản cartesion_productsử dụng bố cục bộ nhớ chuyển đổi thuận lợi hơn + một số tối ưu hóa rất nhỏ.
  • cartesian_product_ppdính với bố trí bộ nhớ ban đầu. Điều làm cho nó nhanh là sử dụng sao chép liên tục. Các bản sao liền kề hóa ra nhanh hơn nhiều đến mức sao chép toàn bộ khối bộ nhớ mặc dù chỉ một phần trong đó chứa dữ liệu hợp lệ là tốt nhất chỉ nên sao chép các bit hợp lệ.

Một số nước hoa. Tôi đã tạo những cái riêng cho bố cục C và Fortran, vì đây là những nhiệm vụ khác nhau IMO.

Tên kết thúc bằng 'pp' là cách tiếp cận của tôi.

1) nhiều yếu tố nhỏ (mỗi yếu tố 2)

nhập mô tả hình ảnh ở đâynhập mô tả hình ảnh ở đây

2) nhiều yếu tố nhỏ (mỗi yếu tố 4)

nhập mô tả hình ảnh ở đâynhập mô tả hình ảnh ở đây

3) ba yếu tố có độ dài bằng nhau

nhập mô tả hình ảnh ở đâynhập mô tả hình ảnh ở đây

4) hai yếu tố có độ dài bằng nhau

nhập mô tả hình ảnh ở đâynhập mô tả hình ảnh ở đây

Mã (cần thực hiện các lần chạy riêng biệt cho từng ô b / c Tôi không thể tìm ra cách đặt lại; cũng cần chỉnh sửa / nhận xét vào / ra một cách thích hợp):

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
    )

Cảm ơn bạn đã chia sẻ câu trả lời tuyệt vời này. Khi kích thước của arrayscartesian_product_transpose_pp (mảng) vượt quá một kích thước nhất định, MemoryErrorsẽ xảy ra. Trong tình huống này, tôi muốn chức năng này mang lại kết quả nhỏ hơn. Tôi đã đăng một câu hỏi về vấn đề này. Bạn có thể giải quyết câu hỏi của tôi? Cảm ơn.
Gấu chó

13

Kể từ tháng 10 năm 2017, numpy hiện có một np.stackhàm chung có tham số trục. Sử dụng nó, chúng ta có thể có một "sản phẩm cartesian tổng quát" bằng cách sử dụng kỹ thuật "dstack và lướigrid":

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

Lưu ý về axis=-1thông số. Đây là trục cuối cùng (bên trong nhất) trong kết quả. Nó tương đương với việc sử dụng axis=ndim.

Một nhận xét khác, vì các sản phẩm của Cartesian nổ tung rất nhanh, trừ khi chúng ta cần nhận ra mảng trong bộ nhớ vì một số lý do, nếu sản phẩm rất lớn, chúng ta có thể muốn sử dụng itertoolsvà sử dụng các giá trị một cách nhanh chóng.


8

Tôi đã sử dụng câu trả lời @kennytm trong một thời gian, nhưng khi cố gắng làm điều tương tự trong TensorFlow, nhưng tôi thấy rằng TensorFlow không có tương đương vớinumpy.repeat() . Sau một thử nghiệm nhỏ, tôi nghĩ rằng tôi đã tìm thấy một giải pháp tổng quát hơn cho các vectơ tùy ý của các điểm.

Đối với 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)

và cho 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)

6

Gói Scikit-learn có triển khai nhanh chính xác điều này:

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

Lưu ý rằng quy ước của việc thực hiện này khác với những gì bạn muốn, nếu bạn quan tâm đến thứ tự của đầu ra. Để đặt hàng chính xác, bạn có thể làm

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

Đây có phải là nhanh hơn chức năng của @ senderle?
cs95

@ cᴏʟᴅsᴘᴇᴇᴅ Tôi chưa kiểm tra. Tôi đã hy vọng rằng điều này đã được thực hiện trong ví dụ C hoặc Fortran và do đó khá nhiều bất khả chiến bại, nhưng nó dường như được viết bằng NumPy. Như vậy, chức năng này thuận tiện nhưng không nên nhanh hơn đáng kể so với những gì người ta có thể xây dựng bằng NumPy tự xây dựng.
jmd_dk

4

Tổng quát hơn, nếu bạn có hai mảng numpy 2 và a và b, và bạn muốn nối từng hàng của a với mỗi hàng b (Một sản phẩm của cartesian, giống như nối trong cơ sở dữ liệu), bạn có thể sử dụng phương pháp này :

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

Cách nhanh nhất bạn có thể nhận được là bằng cách kết hợp biểu thức trình tạo với chức năng bản đồ:

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

Đầu ra (thực ra toàn bộ danh sách kết quả được in):

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

hoặc bằng cách sử dụng biểu thức tạo kép:

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

Đầu ra (toàn bộ danh sách được in):

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

Hãy xem xét rằng hầu hết thời gian tính toán đi vào lệnh in. Các tính toán máy phát điện là hiệu quả khác. Không in thời gian tính toán là:

execution time: 0.079208 s

cho biểu thức máy phát + chức năng bản đồ và:

execution time: 0.007093 s

cho biểu thức tạo kép.

Nếu những gì bạn thực sự muốn là tính toán sản phẩm thực tế của từng cặp tọa độ, thì cách nhanh nhất là giải quyết nó như một sản phẩm ma trận khó hiểu:

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

Đầu ra:

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

và không in (trong trường hợp này nó không tiết kiệm được nhiều vì chỉ có một phần nhỏ của ma trận thực sự được in ra):

execution time: 0.003083 s

Đối với tính toán sản phẩm, phát sóng sản phẩm bên ngoài foo = a[:,None]*blà nhanh hơn. Sử dụng phương pháp thời gian của bạn mà không có print(foo), đó là 0,001103 giây so với 0,002225 giây. Sử dụng thời gian, nó là 304 so với 1,6 ms. Ma trận được biết là chậm hơn ndarray, vì vậy tôi đã thử mã của bạn với np.array nhưng nó vẫn chậm hơn (1,57 ms) so với phát sóng.
syockit

2

Điều này cũng có thể được thực hiện dễ dàng bằng cách sử dụng phương thức 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')

Kết quả: mảng ([
[1, 4],
[1, 5],
[2, 4],
[2, 5],
[3, 4],
[3, 5]], dtype = int32)

Thời gian thực hiện: 0,000155 s


1
bạn không cần phải gọi numpy. mảng python cũ đơn giản cũng làm việc với điều này.
Coddy

0

Trong trường hợp cụ thể mà bạn cần thực hiện các thao tác đơn giản như thêm vào mỗi cặp, bạn có thể giới thiệu một thứ nguyên phụ và để phát sóng thực hiện công việc:

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

Tôi không chắc có cách nào tương tự để thực sự có được các cặp hay không.


Nếu dtypefloatbạn có thể làm (a[:, None, None] + 1j * b[None, :, None]).view(float)đó là đáng ngạc nhiên nhanh.
Paul Panzer
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.