ใช้ numpy เพื่อสร้างอาร์เรย์ของการรวมกันทั้งหมดของสองอาร์เรย์


143

ฉันพยายามเรียกใช้พื้นที่พารามิเตอร์ของฟังก์ชันพารามิเตอร์ 6 ตัวเพื่อศึกษาพฤติกรรมเชิงตัวเลขก่อนพยายามทำสิ่งที่ซับซ้อนด้วยดังนั้นฉันจึงค้นหาวิธีที่มีประสิทธิภาพในการทำสิ่งนี้

ฟังก์ชั่นของฉันใช้ค่าทศนิยมที่กำหนดให้อาร์เรย์ 6-dim numpy เป็นอินพุต สิ่งที่ฉันพยายามทำในตอนแรกคือ:

ก่อนอื่นฉันสร้างฟังก์ชั่นที่ใช้ 2 อาร์เรย์และสร้างอาร์เรย์ที่มีการรวมค่าทั้งหมดจากสองอาร์เรย์

from numpy import *
def comb(a,b):
    c = []
    for i in a:
        for j in b:
            c.append(r_[i,j])
    return c

จากนั้นฉันก็ใช้reduce()กับสิ่งนั้นกับสำเนาของอาร์เรย์เดียวกัน:

def combs(a,m):
    return reduce(comb,[a]*m)

จากนั้นฉันประเมินฟังก์ชันของฉันดังนี้:

values = combs(np.arange(0,1,0.1),6)
for val in values:
    print F(val)

ใช้งานได้ แต่มันช้าเกินไป ฉันรู้ว่าพื้นที่ของพารามิเตอร์มีขนาดใหญ่มาก แต่ไม่ควรช้าขนาดนี้ ผมได้ชิมเพียง 10 6 (ล้าน) จุดในตัวอย่างนี้และมันก็ใช้เวลากว่า 15 valuesวินาทีเพียงเพื่อสร้างอาร์เรย์

คุณรู้วิธีที่มีประสิทธิภาพมากขึ้นในการทำเช่นนี้ด้วย numpy?

ฉันสามารถปรับเปลี่ยนวิธีที่ฟังก์ชั่นFใช้เป็นอาร์กิวเมนต์ถ้าจำเป็น


สำหรับผลิตภัณฑ์คาร์ทีเซียนที่เร็วที่สุดที่ฉันพบให้ดูคำตอบนี้ (เนื่องจากคำถามที่ถูกเขียนค่อนข้างแตกต่างจากคำถามนี้ฉันเห็นว่าคำถามนั้นไม่ซ้ำกัน แต่ทางออกที่ดีที่สุดสำหรับคำถามทั้งสองนั้นเหมือนกัน)
senderle

คำตอบ:


127

ในเวอร์ชันที่ใหม่กว่าของnumpy(> 1.8.x) numpy.meshgrid()ให้การปรับใช้ที่เร็วขึ้นมาก:

@ โซลูชั่นของ pv

In [113]:

%timeit cartesian(([1, 2, 3], [4, 5], [6, 7]))
10000 loops, best of 3: 135 µs per loop
In [114]:

cartesian(([1, 2, 3], [4, 5], [6, 7]))

Out[114]:
array([[1, 4, 6],
       [1, 4, 7],
       [1, 5, 6],
       [1, 5, 7],
       [2, 4, 6],
       [2, 4, 7],
       [2, 5, 6],
       [2, 5, 7],
       [3, 4, 6],
       [3, 4, 7],
       [3, 5, 6],
       [3, 5, 7]])

numpy.meshgrid()ใช้เป็น 2D เท่านั้นตอนนี้สามารถ ND ได้แล้ว ในกรณีนี้ 3D:

In [115]:

%timeit np.array(np.meshgrid([1, 2, 3], [4, 5], [6, 7])).T.reshape(-1,3)
10000 loops, best of 3: 74.1 µs per loop
In [116]:

np.array(np.meshgrid([1, 2, 3], [4, 5], [6, 7])).T.reshape(-1,3)

Out[116]:
array([[1, 4, 6],
       [1, 5, 6],
       [2, 4, 6],
       [2, 5, 6],
       [3, 4, 6],
       [3, 5, 6],
       [1, 4, 7],
       [1, 5, 7],
       [2, 4, 7],
       [2, 5, 7],
       [3, 4, 7],
       [3, 5, 7]])

โปรดทราบว่าลำดับของผลลัพธ์สุดท้ายนั้นแตกต่างกันเล็กน้อย


14
np.stack(np.meshgrid([1, 2, 3], [4, 5], [6, 7]), -1).reshape(-1, 3)จะให้คำสั่งที่ถูกต้อง
Eric

@CT Zhu มีวิธีง่ายๆในการแปลงค่านี้เพื่อให้เมทริกซ์ที่เก็บอาร์เรย์ที่แตกต่างกันเป็นคอลัมน์ที่ใช้เป็นอินพุตแทนหรือไม่
Dole

2
มันควรจะตั้งข้อสังเกตว่า meshgrid ทำงานเฉพาะสำหรับชุดช่วงที่มีขนาดเล็กก็จะมีขนาดใหญ่และฉันได้รับข้อผิดพลาด: ValueError: สูงสุดมิติการสนับสนุนสำหรับ ndarray เป็น 32 พบ 69
mikkom

158

นี่คือการนำไปปฏิบัติที่บริสุทธิ์ เร็วกว่าการใช้ itertools ประมาณ 5 ×


import numpy as np

def cartesian(arrays, out=None):
    """
    Generate a cartesian product of input arrays.

    Parameters
    ----------
    arrays : list of array-like
        1-D arrays to form the cartesian product of.
    out : ndarray
        Array to place the cartesian product in.

    Returns
    -------
    out : ndarray
        2-D array of shape (M, len(arrays)) containing cartesian products
        formed of input arrays.

    Examples
    --------
    >>> cartesian(([1, 2, 3], [4, 5], [6, 7]))
    array([[1, 4, 6],
           [1, 4, 7],
           [1, 5, 6],
           [1, 5, 7],
           [2, 4, 6],
           [2, 4, 7],
           [2, 5, 6],
           [2, 5, 7],
           [3, 4, 6],
           [3, 4, 7],
           [3, 5, 6],
           [3, 5, 7]])

    """

    arrays = [np.asarray(x) for x in arrays]
    dtype = arrays[0].dtype

    n = np.prod([x.size for x in arrays])
    if out is None:
        out = np.zeros([n, len(arrays)], dtype=dtype)

    m = n / arrays[0].size
    out[:,0] = np.repeat(arrays[0], m)
    if arrays[1:]:
        cartesian(arrays[1:], out=out[0:m, 1:])
        for j in xrange(1, arrays[0].size):
            out[j*m:(j+1)*m, 1:] = out[0:m, 1:]
    return out

47
เคยพิจารณาส่งสิ่งนี้เพื่อรวมอยู่ในจำนวน numpy? นี่ไม่ใช่ครั้งแรกที่ฉันไปหาฟังก์ชั่นนี้และพบโพสต์ของคุณ
endolith

1
มีข้อบกพร่องในการใช้งานนี้ สำหรับอาร์เรย์ของสตริงตัวอย่างเช่น: อาร์เรย์ [0] .dtype = "| S3" และอาร์เรย์ [1] .dtype = "| S5" ดังนั้นจึงมีความจำเป็นในการค้นหาสตริงที่ยาวที่สุดในอินพุตและใช้ชนิดของมันใน out = np.zeros ([n, len (อาร์เรย์)), dtype = dtype)
norecces

38
FYI: ดูเหมือนจะทำให้มันกลายเป็นแพ็คเกจเรียนรู้ Scikit ที่from sklearn.utils.extmath import cartesian
Gus

2
ฉันเพิ่งรู้ว่า: นี่แตกต่างจาก itertools.combinations เล็กน้อยเนื่องจากฟังก์ชั่นนี้เคารพการเรียงลำดับของค่าในขณะที่ชุดค่าผสมไม่ทำงานดังนั้นฟังก์ชันนี้จึงคืนค่ามากกว่าชุดค่าผสม ยังคงน่าประทับใจมาก แต่น่าเสียดายที่ไม่ใช่สิ่งที่ฉันกำลังมองหา: (
เดวิดมาร์กซ์

6
TypeError: slice indices must be integers or None or have an __index__ methodถูกโยนโดยcartesian(arrays[1:], out=out[0:m,1:])
เบิร์น

36

itertools.combinationsโดยทั่วไปแล้วเป็นวิธีที่เร็วที่สุดในการรับชุดค่าผสมจากคอนเทนเนอร์ Python (หากคุณต้องการชุดค่าผสมเช่นการจัดเรียงโดยไม่มีการซ้ำซ้อนและเป็นอิสระจากการสั่งซื้อนั่นไม่ใช่สิ่งที่รหัสของคุณดูเหมือนจะทำ แต่ฉันไม่สามารถทำได้ บอกได้ว่าเป็นเพราะรหัสของคุณเป็นรถหรือเพราะคุณกำลังใช้คำศัพท์ที่ผิด)

หากคุณต้องการสิ่งที่แตกต่างจากชุดค่าผสมบางทีตัววนซ้ำอื่น ๆ ใน itertools productหรือpermutationsอาจให้บริการคุณได้ดี ตัวอย่างเช่นดูเหมือนว่ารหัสของคุณจะเหมือนกับ:

for val in itertools.product(np.arange(0, 1, 0.1), repeat=6):
    print F(val)

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


8

คุณสามารถทำอะไรเช่นนี้

import numpy as np

def cartesian_coord(*arrays):
    grid = np.meshgrid(*arrays)        
    coord_list = [entry.ravel() for entry in grid]
    points = np.vstack(coord_list).T
    return points

a = np.arange(4)  # fake data
print(cartesian_coord(*6*[a])

ซึ่งจะช่วยให้

array([[0, 0, 0, 0, 0, 0],
   [0, 0, 0, 0, 0, 1],
   [0, 0, 0, 0, 0, 2],
   ..., 
   [3, 3, 3, 3, 3, 1],
   [3, 3, 3, 3, 3, 2],
   [3, 3, 3, 3, 3, 3]])

2
มีวิธีรับ NumPy ที่จะยอมรับมากกว่า 32 อาร์เรย์สำหรับ meshgrid หรือไม่? วิธีนี้ใช้ได้กับฉันตราบใดที่ฉันไม่ผ่านเกิน 32 อาร์เรย์
Joelmob

8

การใช้งาน numpy ต่อไปนี้ควรจะประมาณ 2x ความเร็วของคำตอบที่ให้:

def cartesian2(arrays):
    arrays = [np.asarray(a) for a in arrays]
    shape = (len(x) for x in arrays)

    ix = np.indices(shape, dtype=int)
    ix = ix.reshape(len(arrays), -1).T

    for n, arr in enumerate(arrays):
        ix[:, n] = arrays[n][ix[:, n]]

    return ix

1
ดูดี. โดยการทดสอบพื้นฐานของฉันนี้ดูเร็วกว่าคำตอบเดิมสำหรับทุกคู่สามเท่าและ 4 tuples ของ {1,2, ... , 100} หลังจากนั้นคำตอบเดิมจะชนะ นอกจากนี้สำหรับผู้อ่านมองในอนาคตเพื่อสร้าง K-tuples ทั้งหมด {1, ... , n} np.indices((n,...,n)).reshape(k,-1).Tจะทำ
jme

วิธีนี้ใช้ได้กับจำนวนเต็มเท่านั้นในขณะที่คำตอบที่ยอมรับยังใช้ได้กับโฟลต
FJC

7

ดูเหมือนว่าคุณต้องการให้กริดประเมินฟังก์ชันของคุณซึ่งในกรณีนี้คุณสามารถใช้numpy.ogrid(เปิด) หรือnumpy.mgrid(ดึงออก):

import numpy
my_grid = numpy.mgrid[[slice(0,1,0.1)]*6]


4

นี่คืออีกวิธีหนึ่งโดยใช้ NumPy แท้ ๆ ไม่มีการเรียกซ้ำไม่มีความเข้าใจในรายการและไม่มีความชัดเจนในการวนซ้ำ มันช้ากว่าคำตอบเดิมประมาณ 20% และขึ้นอยู่กับ np.meshgrid

def cartesian(*arrays):
    mesh = np.meshgrid(*arrays)  # standard numpy meshgrid
    dim = len(mesh)  # number of dimensions
    elements = mesh[0].size  # number of elements, any index will do
    flat = np.concatenate(mesh).ravel()  # flatten the whole meshgrid
    reshape = np.reshape(flat, (dim, elements)).T  # reshape and transpose
    return reshape

ตัวอย่างเช่น,

x = np.arange(3)
a = cartesian(x, x, x, x, x)
print(a)

จะช่วยให้

[[0 0 0 0 0]
 [0 0 0 0 1]
 [0 0 0 0 2]
 ..., 
 [2 2 2 2 0]
 [2 2 2 2 1]
 [2 2 2 2 2]]

3

สำหรับการใช้งานแบบบริสุทธิ์ของผลิตภัณฑ์คาร์ทีเซียนของอาร์เรย์ 1D (หรือรายการงูเหลือมแบบเรียบ) เพียงแค่ใช้meshgrid()หมุนแกนด้วยtranspose()และเปลี่ยนรูปร่างให้เป็น ouput ที่ต้องการ:

 def cartprod(*arrays):
     N = len(arrays)
     return transpose(meshgrid(*arrays, indexing='ij'), 
                      roll(arange(N + 1), -1)).reshape(-1, N)

โปรดทราบว่านี่มีหลักการของการเปลี่ยนแปลงแกนสุดท้ายเร็วที่สุด ("สไตล์ C" หรือ "แถวหลัก")

In [88]: cartprod([1,2,3], [4,8], [100, 200, 300, 400], [-5, -4])
Out[88]: 
array([[  1,   4, 100,  -5],
       [  1,   4, 100,  -4],
       [  1,   4, 200,  -5],
       [  1,   4, 200,  -4],
       [  1,   4, 300,  -5],
       [  1,   4, 300,  -4],
       [  1,   4, 400,  -5],
       [  1,   4, 400,  -4],
       [  1,   8, 100,  -5],
       [  1,   8, 100,  -4],
       [  1,   8, 200,  -5],
       [  1,   8, 200,  -4],
       [  1,   8, 300,  -5],
       [  1,   8, 300,  -4],
       [  1,   8, 400,  -5],
       [  1,   8, 400,  -4],
       [  2,   4, 100,  -5],
       [  2,   4, 100,  -4],
       [  2,   4, 200,  -5],
       [  2,   4, 200,  -4],
       [  2,   4, 300,  -5],
       [  2,   4, 300,  -4],
       [  2,   4, 400,  -5],
       [  2,   4, 400,  -4],
       [  2,   8, 100,  -5],
       [  2,   8, 100,  -4],
       [  2,   8, 200,  -5],
       [  2,   8, 200,  -4],
       [  2,   8, 300,  -5],
       [  2,   8, 300,  -4],
       [  2,   8, 400,  -5],
       [  2,   8, 400,  -4],
       [  3,   4, 100,  -5],
       [  3,   4, 100,  -4],
       [  3,   4, 200,  -5],
       [  3,   4, 200,  -4],
       [  3,   4, 300,  -5],
       [  3,   4, 300,  -4],
       [  3,   4, 400,  -5],
       [  3,   4, 400,  -4],
       [  3,   8, 100,  -5],
       [  3,   8, 100,  -4],
       [  3,   8, 200,  -5],
       [  3,   8, 200,  -4],
       [  3,   8, 300,  -5],
       [  3,   8, 300,  -4],
       [  3,   8, 400,  -5],
       [  3,   8, 400,  -4]])

หากคุณต้องการเปลี่ยนแกนแรกที่เร็วที่สุด ("รูปแบบ FORTRAN" หรือ "คอลัมน์หลัก") เพียงแค่เปลี่ยนorderพารามิเตอร์reshape()ดังนี้:reshape((-1, N), order='F')


1

นุ่นmergeเสนอวิธีแก้ปัญหาที่ไร้เดียงสาและรวดเร็ว:

# given the lists
x, y, z = [1, 2, 3], [4, 5], [6, 7]

# get dfs with same, constant index 
x = pd.DataFrame({'x': x}, index=np.repeat(0, len(x))
y = pd.DataFrame({'y': y}, index=np.repeat(0, len(y))
z = pd.DataFrame({'z': z}, index=np.repeat(0, len(z))

# get all permutations stored in a new df
df = pd.merge(x, pd.merge(y, z, left_index=True, righ_index=True),
              left_index=True, right_index=True)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.