import numpy as np # Line 01
y = np.zeros((3,3)) # Line 02
y = y.astype(np.int16) # Line 03
y[1,1] = 1 # Line 04
x = np.ones((3,3)) # Line 05
t = (1-y).astype(np.int16) # Line 06
print(t) # Line 07
print(x[t]) # Line 08
x[(1-y).astype(np.int16)] = 0 # Line 09
print(x) # Line 10
Line 02:
Creates a two-dimensional 3 x 3 ndarray of zeros. y is a name that is made to point to this ndarray.
Line 03:
Sets the data-type of each element of y, to 16-bit integer.
Line 04:
Sets the element of y at the intersection of the middle row and middle column, to 1.
Line 05:
Creates a two-dimensional 3 x 3 ndarray of ones. x is a name that is made to point to this ndarray.
Line 06:
The subtraction (1-t) results in several scalar subtractions (1- elem), where elem is each element of t. The result will be another ndarray, having the same shape as t, and having the result of the subtraction (1- elem), as its values. That is, the values of the ndarray (1-t) will be:
[[1-t[0,0], 1-t[0,1], 1-t[0,2]],
[1-t[1,0], 1-t[1,1], 1-t[1,2]],
[1-t[2,0], 1-t[2,1], 1-t[2,2]]]
Since t is full of zeros, and a lone 1 at the intersection of the middle row and middle column, (1-t) will be a two-dimensional ndarray full of ones, with a lone 0 at the intersection of the middle row and middle column.
Line 07:
Prints t
Line 08:
Things get a little tricky from here. What is happening here is called "Combined Advanced and Basic Indexing" (https://docs.scipy.org/doc/numpy-1.13.0/reference/arrays.indexing.html#combining-advanced-and-basic-indexing). Let's go through the specifics, step-by-step.
First, notice that x, is a two-dimensional ndarray, taking another integer ndarray t as an index. Since x needs two indices to be supplied, t will be taken to be the first of those two indices, and the second index will be implicitly assumed to be :. So, x[t] is first interpreted as x[t,:]. The presence of these two indices, where one index is an array of integers t, and the other index is a slice :, results in the situation that is called "Combined Advanced and Basic Indexing".
Now, what exactly happens in this "Combined" scenario? Here goes:
First, the shape of the result will get contributions from the first index t, as well as from the second index :. Now t has the shape (3,3), and hence the contribution of t to the shape of the result of x[t,:], is to supply the outermost (leftmost) dimensions of the result shape. Hence the result shape will begin with (3,3,). Now, the contribution of : to the shape of x[t,:] is based on the answer to the question: On which dimension of x is the : being applied ? The answer is -- the second dimension (since : is the second index within x[t,:]). Hence the contribution of : to the result shape of x[t,:] is 3 (since 3 is the length of the second dimension of x). To recap, we have deduced that the result shape of x[t] will be that of x[t,:], which in turn will be (3,3,3). This means x[t] will be a three-dimensional array, even though x itself is only a two-dimensional array.
Note that in the shape (3,3,3) of the result, the first two 3s were contributed by the advanced index t, and the last 3 was contributed by the implicit basic index :. These two indexes t and : also use different ways to arrive at their respective contributions. The 3,3, contribution that came from the index t is just the shape of t itself. It doesn't care about the position of t among the indexes, in the expression x[t,:] (it doesn't care whether t occurs before : or : appears before t). The 3 contribution that came from the index : is the length of the second dimension of x, and we consider the second dimension of x because : is the second index in the expression x[t,:]. If x had the shape (3,5) instead of (3,3), then the shape of x[t,:] would have been (3,3,5) instead of (3,3,3).
Now that we've deduced the shape of the result of x[t] to be (3,3,3), let us move on to understand how the values themselves will get determined, in the result. The values in the result are obviously the values at positions [0,0,0],
[0,0,1], [0,1,2], [0,1,0], [0,1,1], [0,1,2], [0,2,0], [0,2,1], [0,2,2], and so on. Let's walk through one example of these positions, and you will hopefully get the drift. For our example, let's look at the position [0,1,2] in the result. To get the value for this position, we will first index into the t array using the 0 and the 1. That is, we find out t[0,1], which will be 1 (refer to the output of print(t)). This 1, which was obtained at t[0,1], shall be taken to be our first index into x. The second index into x will be 2 (remember that we are discussing the position [0,1,2] within the result, and trying to determine the value at that position). Now, given these first and second indices into x, we get from x the value to be populated at position [0,1,2] of x[t].
Now, x is just full of ones. So, x[t] will only consist of ones, even though the shape of x[t] is (3,3,3). To really test your understanding of what I've said so far, you need to fill x with diverse values:
So, temporarily, comment out Line 05, and have the following line in its place:
x = np.arange(9).reshape((3,3)) # New version of Line 05
Now, you will find that print(x[t]) at Line 08 gives you:
[[[3 4 5]
[3 4 5]
[3 4 5]]
[[3 4 5]
[0 1 2]
[3 4 5]]
[[3 4 5]
[3 4 5]
[3 4 5]]]
Against this output, test your understanding of what I've described above, about how the values in the result will get determined. (That is, if you've understood the above explanation of x[t], you should be able to manually re-construct this same output as above, for print (x[t]).
Line 09:
Given the definition of t on Line 06, Line 09 is equivalent to x[t], which, as we saw above, is equivalent to x[t, :] = 0.
And the effect of the assignment x[t, :] = 0 is the same as the effect of x[0:2, :] = 0.
Why is this so? Simply because, in x[t, :]:
- The index values generated by the index
t are 0s and 1s (since t is an integer index array consisting of only 0s and 1s)
- The index values generated by the index
: are 0, 1, and 2.
- We are referring only to positions within
x that correspond to combinations of these index values. That is, x[t, :] relates only to the those positions x[i,j], where i takes values 0 or 1, and j takes values 0,1, or 2. That is, x[t, :] relates only to the positions x[0,0], x[0,1], x[0,2], x[1,0], x[1,1], x[1,2], within the array x.
- So, the assignment statement
x[t, :] = 0 assigns the value 0 at these positions in x. Effectively, we are assigning the value 0 into all three columns in the first two rows of x, and we are leaving the third row of x unchanged.
Line 10:
Prints the value of x after the above assignment.
x[t]indexes a (3,3) on the first dimension with a (3,3) array; the result is (3,3,3), pickingx[0]for one term,x[1]for others. Sincexis all 1s we don't see any pattern in the display, just the shape.x[t] = 0assigns0to some elements ofx. The result is the same shape asx, not the (3,3,3) ofx[t].tcontains just 0 and 1, the result is thatx[0,:]is set to 0. So isx[1,:], butx[2,:]is untouched.y = np.zeros((3,3)); x = np.ones((3,3)); x[1-y] = 0and you will see[[1,1,1],[0,0,0],[1,1,1]]