# numpy array quirks: does A.T return a view?

I am trying to understand the numpy array object, and a little mystified by the following:

A = np.array([[1,2,3],[4,5,6]]) # A is a 2x3 matrix
B = A

A = A.T # A is now the matrix [[1,4],[2,5],[3,6]]
A[0,1]= -1 # A is now the matrix [[1,-1],[2,5],[3,6]]

print(B)


I get for B:

[[1,2,3],[-1,5,6]]


So changing A using the assignment A[0,1]= -1 changed B (which is to be expected since numpy arrays are mutable objects, and python assignments are by reference). But what is mystifying is that A = A.T did not transpose B. Furthermore, if I ask (after the A = A.T statement):

A is B

I get False. What exactly is A.T returning? It seems to return a view of the data, but a copy of the alignment of data into a matrix.

You are right that B points to the original array and A (at the end) is a transposed view of the array.

The key point here is something that trips up a lot of people: whenever you assign a new value to a variable by using = with no indexing (e.g., A = <anything>), it causes that variable to point to a new location, but it doesn't alter the data previously held in that variable.

Here is an annotated version of what is happening:

A = np.array([[1,2,3],[4,5,6]])
# create the array [[1,2,3],[4,5,6]] at some location in memory
# (let's call it loc 1)
# point variable A to loc 1

B = A
# point variable B to the same location as A (loc 1)

A = A.T
# right-hand side:
#    - get the array that A currently points to (from loc 1)
#    - create a transposed view of this array and store the view
#      somewhere in memory (let's call it loc 2)
# left-hand side: set variable A to point to loc 2
# Note: the = assignment does not alter the object at loc 1; instead,
# it points A to the new object that was created on the right-hand side

A[0,1]= -1
# Push the value -1 into position (0, 1) of the object that A
# currently points to, i.e., the view at loc 2. This updates
# the A view, but also propagates back to the original,
# un-transposed array at loc 1 (which B still points to).

# Note that the last two lines are equivalent to this:
X = A.T     # doesn't transpose B
X[0,1]= -1  # propagates through view back to B
# i.e., the A.T could have been assigned to anything, e.g. a new
# variable X. When you do an assignment with =, it creates a new
# variable every time, even if it happens to have the same name as
# an old one, as in your case.


This is the general issue of changing mutable data and getting unexpected side effects. The easiest way to think about it is that a bare variable assignment (A = <something>) never alters the underlying data that A currently points to. Instead, it just causes A to point somewhere new. On the other hand, assigning A[index] = <something> or A.attribute = <something> or using A.data_altering_method(...) will alter the underlying object that A points to, in place, so anything else that points to the same object will also see the alteration.

In your case, the only thing you did that changed the underlying data was A[0,1]= -1, and that did indeed propagate to B. A = A.T did not alter the underlying data, it just pointed A to something new (which happened to be a new view of the original data). So the change to the A variable didn't propagate to B. However, it is a little surprising that A = A.T creates a view rather than a copy, so that later changes to A affect B. Your middle two lines are equivalent to B.T[0,1] = -1.

You get a view of the matrix, with only the the axes transposed. So your B doesn't get transposed.

More nuances depend on the shape of your original array:

In  : import numpy as np

In  : np.ndarray.transpose??
Out :

Returns a view of the array with axes transposed.
For a 1-D array, this has no effect. (To change between column and
row vectors, first cast the 1-D array into a matrix object.)
For a 2-D array, this is the usual matrix transpose.
For an n-D array, if axes are given, their order indicates how the
axes are permuted (see Examples). [truncated]



NOTE: there is a small difference between, a.T and a.transpose():

Same as self.transpose(), except that self is returned if self.ndim < 2.

There are other methods, such as np.ndarray.reshape(), which might or might not return a copy of the data, also depending on things like the shape of the array.