Why i cannot replace a column in NumPy array by a single-column matrix?

Viewed 183

I have encountered a weird behaviour of NumPy array. I am working on some matrix algebra examples and i found out that you can easily replace an array column (or a row for that matter) with various types of data but not with an array that has the correct number of rows and single column.

Let's have an array:

>>> import numpy as np
>>>
>>> A = np.zeros([4,4])
>>> A
array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])

Now let's assign some values to rows/columns:

>>> A[0,:] = [1,1,1,1]
>>> A
array([[1., 1., 1., 1.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])
>>>
>>> A[:,0] = np.array([2,2,2,2])
>>> A
array([[2., 1., 1., 1.],
       [2., 0., 0., 0.],
       [2., 0., 0., 0.],
       [2., 0., 0., 0.]])
>>>
>>> A[2,:] = np.array([[3,3,3,3]])
>>> A
array([[2., 1., 1., 1.],
       [2., 0., 0., 0.],
       [3., 3., 3., 3.],
       [2., 0., 0., 0.]])
>>>
>>> A[:,2] = np.array([[4,4,4,4]])
>>> A
array([[2., 1., 4., 1.],
       [2., 0., 4., 0.],
       [3., 3., 4., 3.],
       [2., 0., 4., 0.]])

In the last two replacements I assigned a 1x4 array as a new row and a 1x4 array as a new column. But for reason unknown to me i cannot assign an array 4x1 to a column or a row of an existing array:

>>> A[:,3] = np.array([[5],[5],[5],[5]])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: could not broadcast input array from shape (4,1) into shape (4,)

I googled the error and I still think this might be a design flaw.

Is there an actual reason to not permit an assignment of an Nx1 array as a column to a NxM array?

2 Answers

You need broadcasting, use:

>>> A
array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])

>>> A[:,3:4] = np.array([[5],[5],[5],[5]])
>>> A
array([[0., 0., 0., 5.],
       [0., 0., 0., 5.],
       [0., 0., 0., 5.],
       [0., 0., 0., 5.]])

A[:, 3] is of shape (n,), i.e. a 1-D array, whereas A[:, 3:4] is of shape (n, 1), which is the same as the array you are trying to assign.

>>> A[:, 3]
array([0., 0., 0., 0.])
>>> A[:, 3].shape
(4, )

>>> A[:, 3:4]
array([[0.],
       [0.],
       [0.],
       [0.]])

>>> A[:, 3:4].shape
(4, 1)

The difference is, while 3 is just an integer, rather a single index, 3:4 is a slice object, slice(3, 4, None), you can think of it like, you are slicing a part along axis 1, as opposed to just picking one index.

EDIT:

Or, you can pass the index along axis 1, in a list:

>>> A[:, [3]] = np.array([[5],[5],[5],[5]])
>>> A
array([[0., 0., 0., 5.],
       [0., 0., 0., 5.],
       [0., 0., 0., 5.],
       [0., 0., 0., 5.]])

To see more, check out Broadcasting Rules

As an addendum to the accepted answer:

Make 2 arrays, one 1d, the other 2d (but with the same number of elements):

In [73]: x = np.ones((4,),int); y = np.ones((4,1),int)*10
In [74]: x
Out[74]: array([1, 1, 1, 1])
In [75]: y
Out[75]: 
array([[10],
       [10],
       [10],
       [10]])

When we add them we get a (4,4) result:

In [76]: x+y
Out[76]: 
array([[11, 11, 11, 11],
       [11, 11, 11, 11],
       [11, 11, 11, 11],
       [11, 11, 11, 11]])

By rules of broadcasting, the 2 arrays have to have the same number of dimensions. It can automatically add a leading dimension, turning the (4,) to (1,4):

In [77]: x[None,:]+y
Out[77]: 
array([[11, 11, 11, 11],
       [11, 11, 11, 11],
       [11, 11, 11, 11],
       [11, 11, 11, 11]])

And by the 2nd rule, size 1 dimensions are adjusted, so (1,4)=>(4,4) and (4,1)=>(4,4), resulting in a (4,4) array.

With assignment, the RHS can have added leading dimensions and adjusted 1s. But the LHS is basically unchanged:

In [78]: x[:] = y
Traceback (most recent call last):
  File "<ipython-input-78-4f264a106454>", line 1, in <module>
    x[:] = y
ValueError: could not broadcast input array from shape (4,1) into shape (4)

Changing the RHS to (1,4) works:

In [79]: x[:] = y.T
In [80]: x
Out[80]: array([10, 10, 10, 10])

or changing it to (4,):

In [81]: x[:] = y.ravel()

or changing the LHS to (4,1):

In [83]: x[:,None] = y

The accepted answer has effectively done this last A[:, [3]]

I was going to say the LHS can't be changed, but the ability to assign a (1,4) to (4,) makes me hesitate. When assigning (1,4) to (4,), is the LHS adjusted to (1,4) (automatic leading dimension), or is the RHS (1,4) changed to (4,). The details probably don't matter. The key is that trailing dimensions can't be added (or dropped).

The difference between leading and trailing dimensions is required to avoid ambiguity. Imagine trying to add a (3,) and (4,). Could that result in a (3,4) or (4,3)? (3,1)+(1,4) or (1,3)+(4,1). With the leading rule, you have to be explicit, add a (3,1)+(4,) or (3,)+(4,1).

Related