2

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

4 Answers 4

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)

Sign up to request clarification or add additional context in comments.

Comments

2

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 Comments

This is good to know! However, it won't work in my scenario because the input numbers are all over the place. They aren't 'regular' like 1, 2, 3, 4. I just used that as an example.
What do you mean by "all over the place"?
I mean not predictable, bad choice of words on my part. I looked into iron because I'd never seen it before and it seems like it will work. I previously thought your solution was tied to the numbers being 0 and 1, I was wrong.
This solution might not be the best, but I think it is one possible idea.
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 Comments

Ah, I never thought about using multiplication and addition. Seems obvious now that I see it. Thanks!
This approach and the ufunc approach with outer seem equivalent but I'll go with this one since it was first. I've tested both and they seem the same in terms of performance.
1

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:

3 Comments

Thanks for the link. This looks ike it will work and seems the most straight-forward so far.
For this example looks like using the calls to outer are direct equivalents to the broadcasting approach below from @divakar. I wonder if there's a convention for using one over the other?
Yes they are equivalent np.multiply and np.add are "ufuncs" which can take care of broadcasting for you - see docs.scipy.org/doc/numpy/reference/ufuncs.html

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.