-1

I'm trying to figure out a good way of doing the following addition operation without using np.repeat to create a large dimension. If using np.repeat and adding is the best solution let me know.

I'm also confused about what broadcasting is doing in this case. Essentially I have a 4d matrix, and I want to add a 2d matrix in the 1st and 2nd index, while and doing this across index 0 and index 3.

This works correctly

a = np.arange(64).reshape((2,4,4,2)).astype(float)
b = np.ones((2,2))
a[:, 0:2, 0:2, : ] += b

This throws an error. What is a good way of doing this?

a[:, 0:3, 0:3, :] += np.ones((3,3))

This works but is not what I'm looking to do

c = np.arange(144).reshape(3,4,4,3).astype(float)
c[:, 0:3, 0:3, :] += np.ones((3,3))
MSeifert
  • 145,886
  • 38
  • 333
  • 352
Kevin Zen
  • 111
  • 1
  • 7
  • The "works correctly" code isn't actually doing what you think it is. It only looks like it's working because all elements of `b` are equal. – user2357112 Jan 25 '17 at 23:50

2 Answers2

3

You could include an empty axis from the start:

a[:, 0:3, 0:3, :] += np.ones((3,3,1))  # 1 broadcasts against any axis

Similar you should have used:

a[:, 0:2, 0:2, : ] += np.ones((2,2,1))

because you (probably inadvertently) broadcasted these against the third and fourth axes. I think you wanted it to broadcast to the second and third, right?


Also you can always add dimensions with np.expand_dims and axis=-1:

>>> np.expand_dims(np.ones((2, 2)), axis=-1).shape
(2, 2, 1)

or slicing with None or np.newaxis (they are equivalent!):

>>> np.ones((2, 2))[None, :, :, np.newaxis].shape
(1, 2, 2, 1)

The first None is not necessary for correct broadcasting but the last one is!


In this context it is important to mention that numpy broadcasts starting with the last dimension. So if you have two arrays each dimension starting by the last one must have equal shape or one of them has to be 1 (if one is 1 then it broadcasts along this axis!). That's why a[:, 0:2, 0:2, : ] worked:

>>> a[:, 0:2, 0:2, : ].shape
(2, 2, 2, 2)
>>> b.shape
(2, 2)

So the last dimension is equal (both 2) and the second-last one is equal (both 2). However with:

>>> np.ones((2,2,1)).shape
(2, 2, 1)

The last one is 2 and 1 so the last axis of np.ones((2,2,1)) is broadcast while the second and third dimension are equal (all 2) so numpy uses element-wise operations there.

MSeifert
  • 145,886
  • 38
  • 333
  • 352
  • 2
    I like the `[None, :, :, None]` because it makes it clear(er) that we are expanding dimensions at both ends (even through the first None is automatic). – hpaulj Jan 26 '17 at 00:00
2

To align the axes of the array to be added, we need to insert a new axis at the end, like so -

a[:, 0:3, 0:3, :] += np.ones((3,3))[...,None]

Let's study the shapes here :

In [356]: a[:, 0:3, 0:3, :].shape
Out[356]: (2, 3, 3, 2)

In [357]: np.ones((3,3)).shape
Out[357]: (3, 3)

In [358]: np.ones((3,3))[...,None].shape
Out[358]: (3, 3, 1)


Input1 (a[:, 0:3, 0:3, :])        :     (2, 3, 3, 2) 
Input2 (np.ones((3,3))[...,None]) :        (3, 3, 1)

Remember that the broadcasting rules state that the singleton dimensions (dimensions with lengths = 1) would broadcast to the match up with the lengths of the other non-singleton dimensions. Also, the dimensions that are not listed actually have lengths of 1 by default.

So, this is broadcastable and would work now.


Part 2: Why the following works?

c = np.arange(144).reshape(3,4,4,3).astype(float)
c[:, 0:3, 0:3, :] += np.ones((3,3))

Studying shapes again -

In [363]: c[:, 0:3, 0:3, :].shape
Out[363]: (3, 3, 3, 3)

In [364]: np.ones((3,3)).shape
Out[364]: (3, 3)

Input1 (c[:, 0:3, 0:3, :])  :     (3, 3, 3, 3) 
Input2 (np.ones((3,3)))     :           (3, 3)

Again going by the broadcastable rules this is fine, so no error here, but the result isn't the expected one.

Divakar
  • 218,885
  • 19
  • 262
  • 358