एक्स और वाई सरणी का कार्टेशियन उत्पाद 2 डी पॉइंट के एकल सरणी में इंगित करता है


147

मेरे पास दो संख्यात्मक सरणियां हैं जो ग्रिड के 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]])

एक तरह से यह बहुत अक्षम नहीं है क्योंकि मुझे एक लूप में कई बार ऐसा करने की आवश्यकता है। मैं मान रहा हूं कि उन्हें पायथन सूची में परिवर्तित करना itertools.productऔर एक सुव्यवस्थित सरणी का उपयोग करना और वापस करना सबसे कुशल रूप नहीं है।


मैंने देखा कि इटर्स्टूलस एप्रोच में सबसे महंगा कदम सूची से सरणी में अंतिम रूपांतरण है। इस अंतिम चरण के बिना यह केन के उदाहरण से दोगुना तेज़ है।
एलेक्सी लेब्देव

जवाबों:


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

N सरणियों के कार्टेशियन उत्पाद की गणना के लिए एक सामान्य समाधान के लिए दो सरणियों के सभी संयोजनों की एक सरणी बनाने के लिए numpy का उपयोग करना देखें ।


1
इस दृष्टिकोण का एक फायदा यह है कि यह एक ही आकार के सरणियों के लिए लगातार आउटपुट का उत्पादन करता है। meshgrid+ dstackदृष्टिकोण, जबकि तेजी से कुछ मामलों में, कीड़े को जन्म दे सकता है, तो आप उम्मीद कर कार्तीय उत्पाद एक ही आकार के सरणियों के लिए एक ही क्रम में निर्माण किया जाना है।
tlnagy

3
@tlnagy, मैंने किसी भी मामले पर ध्यान नहीं दिया है जहां यह दृष्टिकोण meshgrid+ द्वारा उत्पादित लोगों से अलग-अलग परिणाम उत्पन्न करता है dstack। क्या आप एक उदाहरण पोस्ट कर सकते हैं?
प्रेषक

148

एक विहित cartesian_product (लगभग)

विभिन्न गुणों के साथ इस समस्या के कई दृष्टिकोण हैं। कुछ दूसरों की तुलना में तेज़ हैं, और कुछ अधिक सामान्य-उद्देश्य हैं। बहुत सारे परीक्षण और ट्विकिंग के बाद, मैंने पाया है कि निम्नलिखित फ़ंक्शन, जो एन-डायमेंशनल की गणना करता है, 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_ करने के लिए है , यह सिर्फ इतना होता है कि एक ही आकार के साथ सरणियों को प्रसारण असाइनमेंट के लिए उपयोग किया जा सकता है। एमगिलसन के लिए बहुत धन्यवाद , जिन्होंने मुझे इस तरह का उपयोग करने की कोशिश करने के लिए प्रेरित किया , और अनटुब को , जिन्होंने इस उत्तर पर कुछ बेहद उपयोगी प्रतिक्रिया प्रदान की, जिसमें उपयोग करने का सुझाव भी शामिल हैix_numpy.result_type

उल्लेखनीय विकल्प

यह कभी-कभी फोरट्रान क्रम में स्मृति के सन्निहित ब्लॉक लिखने के लिए तेज़ होता है। यह इस विकल्प का आधार है cartesian_product_transpose, जो कुछ हार्डवेयर की तुलना में तेजी से साबित हुआ है cartesian_product(नीचे देखें)। हालांकि, पॉल पैंजर का जवाब, जो एक ही सिद्धांत का उपयोग करता है, और भी तेज है। फिर भी, मैं इसमें रुचि रखने वाले पाठकों के लिए इसे शामिल करता हूं:

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

ऐसा प्रतीत होता है कि कुछ निरंतर ओवरहेड है जो इसे पैंजर की तुलना में छोटे इनपुट के लिए धीमी गति से चलाता है। लेकिन बड़े आदानों के लिए, मेरे द्वारा चलाए गए सभी परीक्षणों में, यह केवल सबसे तेज़ कार्यान्वयन ( cartesian_product_transpose_pp) के रूप में अच्छा प्रदर्शन करता है ।

निम्नलिखित वर्गों में, मैं अन्य विकल्पों के कुछ परीक्षणों को शामिल करता हूं। ये अब कुछ हद तक पुराने हैं, लेकिन डुप्लिकेट प्रयास के बजाय, मैंने उन्हें ऐतिहासिक हित से बाहर निकलने का फैसला किया है। अप-टू-डेट परीक्षणों के लिए, पैंजर का उत्तर, साथ ही निको श्लोमर देखें के बारे में भी देखें।

विकल्पों के खिलाफ परीक्षण

यहां परीक्षणों की एक बैटरी है जो प्रदर्शन को बढ़ावा देती है कि इनमें से कुछ फ़ंक्शन कई विकल्पों के सापेक्ष प्रदान करते हैं। यहां दिखाए गए सभी परीक्षण क्वाड-कोर मशीन पर किए गए थे, जो मैक ओएस 10.12.5, पायथन 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तब तक प्रतिस्पर्धी बना रहता है जब तक कि इनपुट सरणियों की संख्या चार से ऊपर नहीं बढ़ जाती (लगभग)। उसके बाद, cartesian_product_transposeएक मामूली बढ़त है।

यह दोहराने लायक है कि अन्य हार्डवेयर और ऑपरेटिंग सिस्टम वाले उपयोगकर्ता अलग-अलग परिणाम देख सकते हैं। उदाहरण के लिए, Ubuntu 14.04, पायथन 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

नीचे, मैं इन लाइनों के साथ चलाए गए पहले परीक्षणों के बारे में कुछ विवरणों में जाता हूं। इन तरीकों के सापेक्ष प्रदर्शन समय के साथ बदल गए हैं, विभिन्न हार्डवेयर और पायथन के विभिन्न संस्करणों के लिए और 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

इन दोनों तरीकों के सापेक्ष प्रदर्शन समय के साथ बदल गए हैं। पायथन (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)
...     

मध्यम आकार के इनपुट के लिए, मैंने एक महत्वपूर्ण स्पीडअप देखा। लेकिन मैंने पायथन (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, लेकिन यह बताता है कि पायथन और हाल के संस्करणों में, ये विनिमेय हैं।

सामान्यीकृत उत्पाद कार्य

सामान्य तौर पर, हम उम्मीद कर सकते हैं कि अंतर्निहित कार्यों का उपयोग छोटे आदानों के लिए तेज होगा, जबकि बड़े इनपुट के लिए, एक उद्देश्य-निर्मित फ़ंक्शन तेज हो सकता है। इसके अलावा एक सामान्यीकृत एन-आयामी उत्पाद के लिए, tileऔरrepeat मदद नहीं करेगा, क्योंकि उनके पास स्पष्ट उच्च-आयामी एनालॉग नहीं हैं। तो यह उद्देश्य-निर्मित कार्यों के व्यवहार की भी जांच करने के लायक है।

अधिकांश प्रासंगिक परीक्षण इस उत्तर की शुरुआत में दिखाई देते हैं, लेकिन यहां कुछ परीक्षण पायथन के पुराने संस्करणों और 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']]
14:38 पर 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
अनटुब

इसके अतिरिक्त, cartesian_productफ़्लोटिंग-पॉइंट dtype की cartesian_product_transposeएक सरणी देता है जबकि पहले (प्रसारण) सरणी के समान dtype की एक सरणी देता है। पूर्णांक सरणियों के साथ काम करते समय dtype को संरक्षित करने की क्षमता उपयोगकर्ताओं के पक्ष में एक कारण हो सकती है cartesian_product_transpose
अनटुब

@unutbu फिर से धन्यवाद - जैसा कि मैंने जाना है, dtype क्लोनिंग सिर्फ सुविधा नहीं जोड़ता है; यह कुछ मामलों में 20-30% तक कोड को गति देता है।
प्रेषक

1
@ सादर: वाह, यह अच्छा है! इसके अलावा, यह सिर्फ मेरे लिए हुआ है कि dtype = np.find_common_type([arr.dtype for arr in arrays], [])उपयोगकर्ता को सरणी को रखने के लिए मजबूर करने के बजाय सभी सरणियों के सामान्य dtype को खोजने के लिए कुछ का उपयोग किया जा सकता है, जो पहले dtype को नियंत्रित करता है।
अनटुब

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

मुझे इसमें भी रूचि थी और थोड़ी तुलनात्मक प्रदर्शन किया, शायद @ प्रेषक के उत्तर की तुलना में कुछ हद तक स्पष्ट था।

दो सरणियों (शास्त्रीय मामले) के लिए:

यहाँ छवि विवरण दर्ज करें

चार सरणियों के लिए:

यहाँ छवि विवरण दर्ज करें

(ध्यान दें कि एरे की लंबाई केवल कुछ दर्जन प्रविष्टियाँ हैं।)


भूखंडों को पुन: उत्पन्न करने के लिए कोड:

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

@ प्रेषक के अनुकरणीय जमीनी कार्य का निर्माण, मैं दो संस्करणों के साथ आया हूं - एक सी के लिए और एक फोरट्रान लेआउट के लिए - जो अक्सर थोड़ा तेज होता है।

  • cartesian_product_transpose_ppहै - @ भेजने वाले के विपरीत cartesian_product_transposeजो पूरी तरह से एक अलग रणनीति का उपयोग करता है - cartesion_productउस का एक संस्करण अधिक अनुकूल स्थानान्तरण स्मृति लेआउट का उपयोग करता है + कुछ बहुत ही मामूली बदलाव।
  • cartesian_product_ppमूल मेमोरी लेआउट के साथ चिपक जाती है। क्या यह तेजी से बनाता है इसकी नकल नकल का उपयोग कर रहा है। समसामयिक प्रतियां इतनी तेज़ी से निकलती हैं कि स्मृति के एक पूर्ण ब्लॉक की प्रतिलिपि बनाते हुए, भले ही इसके कुछ भाग में वैध डेटा हो, केवल मान्य बिट्स की प्रतिलिपि बनाना बेहतर होता है।

कुछ इत्र। मैंने C और फोरट्रान लेआउट के लिए अलग-अलग बना दिया, क्योंकि ये अलग-अलग कार्य IMO हैं।

'पीपी' में समाप्त होने वाले नाम मेरे दृष्टिकोण हैं।

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
    )

इस बेहतरीन उत्तर को साझा करने के लिए धन्यवाद। जब arrayscartesian_product_transpose_pp (सरणियाँ) का आकार एक निश्चित आकार से अधिक MemoryErrorहो जाता है , तब होगा। इस स्थिति में, मैं चाहूंगा कि इस समारोह में परिणाम के छोटे हिस्से मिले। मैंने इस मामले पर एक प्रश्न पोस्ट किया है। क्या आप मेरे सवाल का जवाब दे सकते हैं? धन्यवाद।
सूर्य भालू

13

2017 के अक्टूबर के रूप में, सुपी में अब एक जेनेरिक 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()। थोड़े से प्रयोग के बाद, मुझे लगता है कि मैंने बिंदुओं के मनमाना वैक्टरों के लिए अधिक सामान्य समाधान पाया।

सुन्न के लिए:

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]

क्या यह @ प्रेषक के कार्य से तेज है?
1995 पर cs95

@ c @sᴏʟᴅ मैंने परीक्षण नहीं किया। मैं उम्मीद कर रहा था कि यह उदा C या फोरट्रान में लागू किया गया था और इस प्रकार बहुत अधिक अपराजेय है, लेकिन ऐसा लगता है कि यह NumPy का उपयोग करके लिखा गया है। इस प्रकार, यह फ़ंक्शन सुविधाजनक है, लेकिन किसी व्यक्ति द्वारा NumPy कंस्ट्रक्शन का उपयोग करके जो निर्माण किया जा सकता है, उससे अधिक तेज नहीं होना चाहिए।
jmd_dk

4

अधिक आम तौर पर, यदि आपके पास दो 2 डी का खस्ता एरे और बी है, और आप बी की हर पंक्ति को हर पंक्ति में बदलना चाहते हैं (पंक्तियों का एक कार्टेशियन उत्पाद, एक डेटाबेस में शामिल होने की तरह), तो आप इस विधि का उपयोग कर सकते हैं। :

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

डबल जनरेटर अभिव्यक्ति के लिए।

यदि आप वास्तव में क्या चाहते हैं, तो प्रत्येक समन्वय जोड़े के वास्तविक उत्पाद की गणना करना है, सबसे तेजी से इसे एक संख्यात्मक मैट्रिक्स के रूप में हल करना है:

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 है। समय का उपयोग करते हुए, यह 304 μ बनाम 1.6 एमएस है। मैट्रिक्स को 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
आपको सुन्न कॉल करने की आवश्यकता नहीं है। सादे पुराने अजगर सरणियाँ भी इसके साथ काम करती हैं।
कोडी

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)जो आश्चर्यजनक रूप से तेज है।
पॉल पैंजर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.