2

I create a normal python list

a = [[[1,2],[3,4]],[[5,6],[7,8]],[[9,10],[11,12]]]

Now I want to shift the first row of each 2x2 array to the previous 2x2 array, wrapping the first back to the last. I use the following unpacking assignment statement:

a[0][0],a[1][0],a[2][0] = a[1][0],a[2][0],a[0][0]

I get the following, which is what I want

print(a)
[[[5, 6], [3, 4]], [[9, 10], [7, 8]], [[1, 2], [11, 12]]]

Now, I do the same thing, this time, using numpy arrays

b = np.arange(1,13).reshape((3,2,2))
print(b)
[[[ 1  2]
  [ 3  4]]

 [[ 5  6]
  [ 7  8]]

 [[ 9  10]
  [11 12]]]

The shift is the same as before but using numpy indexing syntax

b[0,0],b[1,0],b[2,0] = b[1,0],b[2,0],b[0,0]
print(b)
[[[ 5  6]
  [ 3  4]]

 [[ 9 10]
  [ 7  8]]

 [[ 5  6]
  [11 12]]]

As you can see, this assignment left the first and last 2x2 arrays with the same first row.

Would you expect this behavior difference? Why does numpy do this but not the regular list type? How could the numpy assignment be done to result in the same result as the list?

sizzzzlerz
  • 4,277
  • 3
  • 27
  • 35
  • To answer your question of how I would have done it: `b[:,0] = b[[1,2,0],0]`. As to why you're seeing what you're seeing, I'll have to think about it more, but it seems to me that swap is being done in two parts. First, it swaps the first two terms. Second, it looks at the result from the last swap and uses that for the final swap, resulting in it copying the already swapped values. – jared Jun 11 '23 at 22:27

2 Answers2

4

The way I reason this out is that since b is a (3,2,2) array

each of

b[1,0],b[2,0],b[0,0]

is a view of b. That is, 3 (2,) arrays, but each uses a different part of the b data_buffer.

During the assignment, b[0,0] is set to the values at b[1,0]. By the time it assigns to b[2,0], B[0,0] now has the new values, [5,6].

I've seen this on occasion before. It's another case where arrays aren't exactly the same as lists. Lists contain pointers to lists, where as array indexing either produces copies or views. Views are convenient, but can mess with list/reference based intuitions.

Fortunately arrays can assign several values at once, so we can do:

b[[0,1,2],0] = b[[1,2,0],0]

or the equivalent with slices b[:,0]=b[::-1,0]

Earlier questions focus more on the correct way of doing the swap, and less on why there's a difference. So won't mark this as duplicate

Swap slices of Numpy arrays

Row exchange in Numpy

hpaulj
  • 221,503
  • 14
  • 230
  • 353
  • That's beautiful! I'd forgotten the ability to do multiple assignments since I'd never used it before. With a slight tweak to the indexing, I can do shifts to the columns just as easily. – sizzzzlerz Jun 11 '23 at 23:33
3

Simple: What is a python list under the hood? A bunch of pointers to objects! When you say a[1][0],a[2][0],a[0][0], you are (basically) creating a tuple of three objects - a pointer to the list [5,6], a pointer to the list [9,10], and a pointer to the list [1,2]. You then assign these pointers to spots in a with a[0][0],a[1][0],a[2][0] = ...

Numpy is different. Slices represent locations in memory, not pointers to objects. The statement below is identical to your code: when you assign b[0,0] to 5,6, the value in memory referenced by b[0,0] has changed. Therefore, when you assign b[2,0] to what's in b[0,0] you get 5,6.

b = np.arange(1,13).reshape((3,2,2))
rhs = b[1,0],b[2,0],b[0,0]
print("before first assign", rhs[2])
b[0,0] = rhs[0]
print("after first assign", rhs[2])
b[1,0] = rhs[1]
b[2,0] = rhs[2]
print(b)
before first assign [1 2]
after first assign [5 6]
[[[ 5  6]
  [ 3  4]]

 [[ 9 10]
  [ 7  8]]

 [[ 5  6]
  [11 12]]]

Carbon
  • 3,828
  • 3
  • 24
  • 51
  • 1
    I couldn't think of why this was happening, but your answer definitely makes sense to me. – jared Jun 11 '23 at 22:32