Vectorize numpy array expansion

2020-03-30 00:03发布

问题:

I'm trying to find a way to vectorize an operation where I take 1 numpy array and expand each element into 4 new points. I'm currently doing it with Python loop. First let me explain the algorithm.

input_array = numpy.array([1, 2, 3, 4])

I want to 'expand' or 'extend' each element in this array to 4 points. So, element zero (value 1) would be expanded to these 4 points:

[0, 1, 1, 0]

This would happen for each element to end up with a final array of:

[0, 1, 1, 0, 0, 2, 2, 0, 0, 3, 3, 0, 0, 4, 4, 0]

I'd like to make the code slightly generic so that I could also perform this 'expansion' in a different way. For example:

input_array = numpy.array([1, 2, 3, 4])

This time each point is expanded by adding += .2 to each point. So, the final array would be:

[.8, .8, 1.2, 1.2, 1.8, 1.8, 2.2, 2.2, 2.8, 2.8, 3.2, 3.2, 3.8, 3.8, 4.2, 4.2]

The code I'm currently using looks like this. It's a pretty naive approach that works but seems like there would be a way to speed it up for large arrays:

output = []
for x in input_array:
    output.append(expandPoint(x))

output = numpy.concatenate(output)

def expandPoint(x):
    return numpy.array([0, x, x, 0])

def expandPointAlternativeStyle(x):
    return numpy.array([x - .2, x - .2, x + .2, x + .2])

回答1:

It seems you want to want to do elementwise operations between input_array and the array that contains the extending elements. For these, you can use broadcasting.

For the first example, it seems you are performing elementwise multiplication -

In [424]: input_array = np.array([1, 2, 3, 4])
     ...: extend_array = np.array([0, 1, 1, 0])
     ...: 

In [425]: (input_array[:,None] * extend_array).ravel()
Out[425]: array([0, 1, 1, 0, 0, 2, 2, 0, 0, 3, 3, 0, 0, 4, 4, 0])

For the second example, it seems you are performing elementwise addition -

In [422]: input_array = np.array([1, 2, 3, 4])
     ...: extend_array = np.array([-0.2, -0.2, 0.2, 0.2])
     ...: 

In [423]: (input_array[:,None] + extend_array).ravel()
Out[423]: 
array([ 0.8,  0.8,  1.2,  1.2,  1.8,  1.8,  2.2,  2.2,  2.8,  2.8,  3.2,
        3.2,  3.8,  3.8,  4.2,  4.2])


回答2:

I'm not certain of the logic of your algorithm, but I think if you want each point to extend around it, and then queue them all together, your best approach would be to increase the dimension, and then take the flattened version; for your first example:

>>> x = np.array([1,2,3,4])
>>> x
array([1, 2, 3, 4])
>>> y = np.empty((len(x), 4))
>>> y[:, [0, 3]] = 0
>>> y[:, 1:3] = x[:, None]
>>> y
array([[ 0.,  1.,  1.,  0.],
       [ 0.,  2.,  2.,  0.],
       [ 0.,  3.,  3.,  0.],
       [ 0.,  4.,  4.,  0.]])
>>> y.reshape((4*len(x),))  # Flatten it back
array([ 0.,  1.,  1.,  0.,  0.,  2.,  2.,  0.,  0.,  3.,  3.,  0.,  0.,
    4.,  4.,  0.])

How you then go about making that generic depends on your algorithm, which I'm not entirely sure to follow... But this should give you some pointers to get started.

Edit: As others have stated, you can actually do all that with outer products in a much more concise way, which will probably match your algorithm more closely, e.g., shamelessely making YXD answer a one-liner:

>>> (x[:, None] * np.array([0,1,1,0])[None, :]).flatten()
array([0, 1, 1, 0, 0, 2, 2, 0, 0, 3, 3, 0, 0, 4, 4, 0])

However, the principle is still to go in a higher dimension (2) before expanding it in you original dimension (1)



回答3:

For the first example, you can use np.kron

>>> a = np.array([0, 1, 1, 0])
>>> np.kron(input_array, a)
array([0, 1, 1, 0, 0, 2, 2, 0, 0, 3, 3, 0, 0, 4, 4, 0])

For the second example, you can use np.repeat and np.tile

>>> b = np.array([-0.2, -0.2, 0.2, 0.2])
>>> np.repeat(input_array, b.size) + np.tile(b, input_array.size)
array([ 0.8,  0.8,  1.2,  1.2,  1.8,  1.8,  2.2,  2.2,  2.8,  2.8,  3.2,
        3.2,  3.8,  3.8,  4.2,  4.2])


回答4:

For the first example, you can do an outer product of the input and the template and reshape the result:

input_array = np.array([1, 2, 3, 4])
template = np.array([0, 1, 1, 0])

np.multiply.outer(input_array, template)
# array([[0, 1, 1, 0],
#        [0, 2, 2, 0],
#        [0, 3, 3, 0],
#        [0, 4, 4, 0]])

result = np.multiply.outer(input_array, template).ravel()
# array([0, 1, 1, 0, 0, 2, 2, 0, 0, 3, 3, 0, 0, 4, 4, 0])

Similarly for your second example you can use np.add.outer

np.add.outer(input_array, [-0.2, -0.2, 0.2, 0.2]).ravel()
# array([ 0.8,  0.8,  1.2,  1.2,  1.8,  1.8,  2.2,  2.2,  2.8,  2.8,  3.2,
        3.2,  3.8,  3.8,  4.2,  4.2])

See:

  • http://docs.scipy.org/doc/numpy/reference/generated/numpy.ufunc.outer.html