ผลิตภัณฑ์คาร์ทีเซียนของจุด x และ y เป็นจุดสองมิติในอาร์เรย์เดียว


147

ฉันมีสองอาร์เรย์ numpy ที่กำหนดแกน x และ y ของตาราง ตัวอย่างเช่น:

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 ไม่ใช่รูปแบบที่มีประสิทธิภาพที่สุด


ฉันสังเกตเห็นว่าขั้นตอนที่แพงที่สุดในแนวทาง itertools คือการแปลงขั้นสุดท้ายจากรายการไปยังอาร์เรย์ หากไม่มีขั้นตอนสุดท้ายมันจะเร็วเป็นสองเท่าตามตัวอย่างของ Ken
Alexey Lebedev

คำตอบ:


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

ดูการใช้ numpy เพื่อสร้างอาร์เรย์ของการรวมกันทั้งหมดของสองอาร์เรย์สำหรับโซลูชันทั่วไปสำหรับการคำนวณผลิตภัณฑ์ Cartesian ของ N อาร์เรย์


1
ข้อดีของวิธีนี้คือมันสร้างเอาต์พุตที่สอดคล้องกันสำหรับอาร์เรย์ที่มีขนาดเท่ากัน meshgrid+ dstackวิธีการในขณะที่เร็วขึ้นในบางกรณีสามารถนำไปสู่ข้อผิดพลาดถ้าคุณคาดหวังว่าผลิตภัณฑ์ Cartesian จะถูกสร้างขึ้นในลำดับเดียวกันสำหรับอาร์เรย์ที่มีขนาดเดียวกัน
tlnagy

3
@tlnagy ผมไม่ได้สังเกตเห็นกรณีที่วิธีการนี้ก่อให้เกิดผลลัพธ์ที่แตกต่างจากที่ผลิตโดย+meshgrid dstackคุณช่วยโพสต์ตัวอย่างได้ไหม?
senderle

148

บัญญัติcartesian_product(เกือบ)

มีหลายวิธีในการแก้ไขปัญหานี้ด้วยคุณสมบัติที่แตกต่างกัน บางตัวเร็วกว่าตัวอื่นและบางตัวก็มีจุดประสงค์ทั่วไปมากกว่า หลังจากการทดสอบและปรับแต่งมากมายฉันพบว่าฟังก์ชั่นต่อไปนี้ซึ่งคำนวณ n-dimension cartesian_productนั้นเร็วกว่าฟังก์ชั่นอื่น ๆ ส่วนใหญ่สำหรับอินพุตจำนวนมาก สำหรับคู่ของวิธีการที่มีความซับซ้อนมากขึ้นเล็กน้อย แต่แม้กระทั่งบิตเร็วขึ้นในหลายกรณีให้ดูคำตอบโดยพอลยานเกราะ

จากคำตอบนั้นนี่ไม่ใช่การใช้งานผลิตภัณฑ์คาร์ทีเซียนที่เร็วที่สุดอีกต่อไป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_คือการสร้างดัชนี ลงในอาร์เรย์มันเกิดขึ้นเพียงเพื่อที่อาร์เรย์ที่มีรูปร่างเหมือนกันสามารถนำมาใช้สำหรับการกำหนดออกอากาศ ขอบคุณมากที่mgilsonที่เป็นแรงบันดาลใจให้ผมลองใช้ix_วิธีนี้และจะunutbunumpy.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

หลังจากที่เข้ามาจะเข้าใจวิธีการที่ยานเกราะของผมเขียนรุ่นใหม่ที่เกือบเร็วที่สุดเท่าที่เขาและเกือบจะเป็นง่ายๆเป็น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)

ในส่วนต่อไปนี้ฉันรวมการทดสอบทางเลือกอื่น ๆ ตอนนี้สิ่งเหล่านี้ค่อนข้างล้าสมัย แต่แทนที่จะพยายามทำซ้ำฉันตัดสินใจทิ้งพวกเขาไว้ที่นี่เพื่อผลประโยชน์ทางประวัติศาสตร์ สำหรับการทดสอบ up-to-date, ดูคำตอบของยานเกราะเช่นเดียวกับนิโก้Schlömer 's

ทดสอบกับทางเลือกอื่น

นี่คือแบตเตอรี่ของการทดสอบที่แสดงการเพิ่มประสิทธิภาพซึ่งฟังก์ชั่นบางอย่างเหล่านี้ให้ความสัมพันธ์กับทางเลือกอื่น ๆ การทดสอบทั้งหมดที่แสดงในที่นี้ได้ดำเนินการบนเครื่อง quad-core โดยใช้ 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)]

ผลการทดสอบ:

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ยังคงสามารถแข่งขันได้จนกว่าจะมีจำนวนอินพุตอาร์เรย์เพิ่มขึ้นด้านบน (ประมาณ) สี่ชุด หลังจากนั้น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

ด้านล่างนี้เป็นรายละเอียดเล็ก ๆ น้อย ๆ เกี่ยวกับการทดสอบก่อนหน้านี้ที่ฉันใช้ในบรรทัดเหล่านี้ ประสิทธิภาพสัมพัทธ์ของวิธีการเหล่านี้มีการเปลี่ยนแปลงเมื่อเวลาผ่านไปสำหรับฮาร์ดแวร์ที่แตกต่างกันและ Python และเวอร์ชั่นnumpyต่างๆ แม้ว่ามันจะไม่เป็นประโยชน์ทันทีสำหรับผู้ที่ใช้เวอร์ชันที่ทันสมัยnumpyแต่มันแสดงให้เห็นว่าสิ่งต่าง ๆ มีการเปลี่ยนแปลงอย่างไรตั้งแต่คำตอบแรกของคำตอบนี้

ทางเลือกง่ายๆ: meshgrid+dstack

คำตอบที่ยอมรับในปัจจุบันใช้tileและrepeatเพื่อออกอากาศสองอาร์เรย์ด้วยกัน แต่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กับ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)
...     

สำหรับอินพุตขนาดปานกลางฉันเห็นการเร่งความเร็วที่สำคัญ แต่ฉันลองทดสอบเหล่านี้อีกครั้งด้วย Python (3.6.1) และnumpy(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

ทดสอบใหม่

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-dimension ทั่วไปtileและrepeatจะไม่ช่วยเพราะพวกเขาไม่มี analogues มิติที่สูงขึ้นชัดเจน ดังนั้นจึงควรตรวจสอบพฤติกรรมของฟังก์ชันที่สร้างขึ้นด้วยวัตถุประสงค์เช่นกัน

การทดสอบที่เกี่ยวข้องส่วนใหญ่จะปรากฏขึ้นที่จุดเริ่มต้นของคำตอบนี้ แต่นี่คือการทดสอบเล็กน้อยที่ดำเนินการกับ Python รุ่นก่อนหน้าและnumpyเพื่อเปรียบเทียบ

cartesianฟังก์ชั่นที่กำหนดไว้ในคำตอบอื่นใช้ในการดำเนินสวยดีสำหรับปัจจัยการผลิตที่มีขนาดใหญ่ (มันเป็นเช่นเดียวกับฟังก์ชั่นที่เรียกว่าcartesian_product_recursiveข้างต้น.) เพื่อที่จะเปรียบเทียบcartesianไปdstack_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

ทดสอบใหม่

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กำหนดไว้ที่จุดเริ่มต้นของคำตอบนี้ - ซึ่งเป็นตัวเองช้ากว่าการใช้งานที่เร็วที่สุดในบรรดาคำตอบสำหรับคำถามนี้


1
และการเพิ่มdtype=objectเข้าไปจะช่วยให้การใช้ที่แตกต่างกันในผลิตภัณฑ์เช่นarr = np.empty( ) arrays = [np.array([1,2,3]), ['str1', 'str2']]
user3820991

ขอบคุณมากสำหรับนวัตกรรมของคุณ แค่คิดว่าคุณต้องการที่จะรู้ว่าผู้ใช้บางคนอาจพบcartesian_product_tranposeได้เร็วกว่าcartesian_productขึ้นอยู่กับระบบปฏิบัติการของพวกเขา, หลามหรือรุ่นที่ใช้งานไม่ได้ ยกตัวอย่างเช่นบน Ubuntu 14.04, python3.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ฉันยังหาอาจจะเร็วขึ้นเมื่อcartesian_product_transpose len(arrays) > 2
unutbu

นอกจากนี้cartesian_productส่งกลับอาร์เรย์ของ dtype ทศนิยมขณะที่cartesian_product_transposeส่งกลับอาร์เรย์ของ dtype เดียวกันเป็นอาร์เรย์แรก (ออกอากาศ) ความสามารถในการรักษา dtype cartesian_product_transposeเมื่อทำงานกับอาร์เรย์จำนวนเต็มอาจจะเป็นเหตุผลสำหรับผู้ใช้เพื่อความโปรดปราน
unutbu

@unutbu ขอบคุณอีกครั้ง - อย่างที่ฉันควรจะรู้การโคลนนิ่ง dtype ไม่เพียงเพิ่มความสะดวกสบายเท่านั้น มันเพิ่มความเร็วโค้ดอีก 20-30% ในบางกรณี
senderle

1
@senderle: ว้าวเยี่ยมเลย! นอกจากนี้มันเพิ่งเกิดขึ้นกับฉันที่บางสิ่งบางอย่างเช่นdtype = np.find_common_type([arr.dtype for arr in arrays], [])สามารถใช้ในการค้นหา dtype ทั่วไปของอาร์เรย์ทั้งหมดแทนที่จะบังคับให้ผู้ใช้วางอาร์เรย์ที่ควบคุม dtype ก่อน
unutbu

44

คุณสามารถทำรายการความเข้าใจปกติในไพ ธ อนได้

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

28

ฉันสนใจสิ่งนี้เช่นกันและทำการเปรียบเทียบประสิทธิภาพเล็กน้อยอาจจะค่อนข้างชัดเจนกว่าในคำตอบของ @ 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: 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 ฉันมีสองเวอร์ชัน - รุ่นหนึ่งสำหรับ C และอีกรุ่นสำหรับเค้าโครง Fortran - ซึ่งมักจะเร็วกว่าเล็กน้อย

  • cartesian_product_transpose_ppคือ - ต่างจาก @ senderle 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
    )

ขอบคุณสำหรับการแบ่งปันคำตอบที่ยอดเยี่ยมนี้ เมื่อขนาดของarraysin ใน cartesian_product_transpose_pp (อาร์เรย์) เกินขนาดที่กำหนดMemoryErrorจะเกิดขึ้น ในสถานการณ์เช่นนี้ฉันต้องการฟังก์ชั่นนี้เพื่อให้ได้ผลลัพธ์ที่เล็กลง ฉันได้โพสต์คำถามในเรื่องนี้ คุณสามารถตอบคำถามของฉันได้ไหม ขอบคุณ
Sun Bear

13

เมื่อวันที่ตุลาคม 2017 ตอนนี้ 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)

หมายเหตุเกี่ยวกับaxis=-1พารามิเตอร์ นี่คือแกนสุดท้าย (ภายในสุด) ในผลลัพธ์ axis=ndimมันเป็นเทียบเท่ากับการใช้

อีกความคิดเห็นหนึ่งเนื่องจากผลิตภัณฑ์คาร์ทีเซียนระเบิดอย่างรวดเร็วเว้นแต่เราจำเป็นต้องตระหนักถึงอาเรย์ในหน่วยความจำด้วยเหตุผลบางอย่างหากผลิตภัณฑ์มีขนาดใหญ่มากเราอาจต้องการใช้ประโยชน์itertoolsและใช้ค่าแบบทันที


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)

และสำหรับ 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

Scikit เรียนรู้แพคเกจที่มีการดำเนินงานอย่างรวดเร็วของตรงนี้:

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

โปรดทราบว่าข้อตกลงของการนำไปใช้งานนี้แตกต่างจากสิ่งที่คุณต้องการหากคุณสนใจลำดับของผลลัพธ์ สำหรับการสั่งซื้อที่แน่นอนของคุณคุณสามารถทำได้

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

นี่เร็วกว่าฟังก์ชั่นของ @ senderle หรือไม่
cs95

@ cᴏʟᴅsᴘᴇᴇᴅฉันไม่ได้ทดสอบ ฉันหวังว่าสิ่งนี้จะถูกนำไปใช้เช่น C หรือ Fortran และทำให้ไม่สามารถเอาชนะได้มากนัก แต่ดูเหมือนว่าจะเขียนโดยใช้ NumPy ฟังก์ชั่นนี้สะดวก แต่ไม่ควรเร็วกว่าสิ่งที่เราสามารถสร้างได้โดยใช้ NumPy สร้างตัวเอง
jmd_dk

4

โดยทั่วไปหากคุณมีอาร์เรย์ 2d aump 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))

3

เร็วที่สุดที่คุณจะได้รับคือการรวมนิพจน์เครื่องกำเนิดไฟฟ้ากับฟังก์ชั่นแผนที่:

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

สำหรับการคำนวณผลิตภัณฑ์การกระจายผลิตภัณฑ์ภายนอกfoo = a[:,None]*bจะเร็วกว่า ใช้วิธีการจับเวลาของคุณโดยไม่ใช้print(foo)มันคือ 0.001103 s กับ 0.002225 s ใช้ timeit มันคือ 304 μs vs 1.6 ms Matrix นั้นช้ากว่า ndarray ดังนั้นฉันจึงลองใช้โค้ดของคุณกับ np.array แต่ก็ยังช้ากว่า (1.57 ms) มากกว่าการออกอากาศ
syockit

2

นอกจากนี้ยังสามารถทำได้อย่างง่ายดายโดยใช้วิธี 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')

ผลลัพธ์: อาร์เรย์ ([
[1, 4],
[1, 5],
[2, 4],
[2, 5],
[3, 4],
[3, 5]], dtype = int32)

เวลาดำเนินการ: 0.000155 วิ


1
คุณไม่จำเป็นต้องโทรหาคนอ้วน หลามอาเรย์เก่าธรรมดายังใช้งานได้กับสิ่งนี้
Coddy

0

ในกรณีเฉพาะที่คุณต้องดำเนินการอย่างง่ายเช่นการเพิ่มในแต่ละคู่คุณสามารถแนะนำมิติเพิ่มเติมและให้การออกอากาศดำเนินงาน:

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

ฉันไม่แน่ใจว่ามีวิธีใดที่คล้ายกันในการรับคู่จริงๆ


ถ้าdtypeเป็นfloatเช่นนั้นคุณสามารถทำได้(a[:, None, None] + 1j * b[None, :, None]).view(float)อย่างรวดเร็วอย่างน่าประหลาดใจ
Paul Panzer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.