Exploring Fancy Indexing
# In[1]
rng=np.random.default_rng(seed=1701)
x=rng.integers(100,size=10)
print(rng)
print(x)
# Out[1]
Generator(PCG64)
[90 40 9 30 80 67 39 15 33 79]
- If we want to access three different elements. We could do it like this
# In[2]
[x[3],x[7],x[2]]
# Out[2]
[30, 15, 9]
- Alternatively, we can pass a single list or array of indices to obtain the same result.
# In[3]
ind=[3,7,4]
x[ind]
# Out[3]
array([30, 15, 80])
- When using arrays of indices, the shape of the result reflects the shape of the index arrays rather than the shape of the array being indexed.
# In[4]
ind=np.array([[3,7],[4,5]])
x[ind]
# Out[4]
array([[30, 15],
[80, 67]])
- Fancy indexing also works in multiple dimensions.
# In[5]
X=np.arange(12).reshape((3,4))
X
# Out[5]
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
# In[6]
row=np.array([0,1,2])
col=np.array([2,1,3])
X[row,col]
# Out[6]
array([ 2, 5, 11])
- The first value in the result is
X[0,2]
, the second is X[1,1]
, and the third is X[2,3]
- The pairing of indices in fancy indexing follows all the broadcasting rules.
# In[7]
X[row[:,np.newaxis],col]
# Out[7]
array([[ 2, 1, 3],
[ 6, 5, 7],
[10, 9, 11]])
- Fancy indexing that the return value reflects the broadcasted shape of the indices.
Combined Indexing
- We can combine fancy and simple indices
# In[8]
X
# Out[8]
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
# In[9]
X[2,[2,0,1]]
# Out[9]
array([10, 8, 9])
- We can also combine fancy indexing and slicing
# In[10]
X[1:,[2,0,1]]
# Out[10]
array([[ 6, 4, 5],
[10, 8, 9]])
- We can combine fancy indexing with masking
# In[11]
mask=np.array([1,0,1,0],dtype=bool)
X[row[:,np.newaxis],mask]
# Out[11]
array([[ 0, 2],
[ 4, 6],
[ 8, 10]])
Modifying Values with Fancy Indexing
# In[12]
x=np.arange(10)
i=np.array([2,1,8,4])
x[i]=99
print(x)
# Out[12]
[ 0 99 99 3 99 5 6 7 99 9]
# In[13]
x[i]-=10
print(x)
# Out[13]
[ 0 89 89 3 89 5 6 7 89 9]
- You have to notice that repeated indices with these operations can cause some potentially unexpected results.
# In[14]
x=np.zeros(10)
x[[0,0]]=[4,6]
print(x)
# Out[14]
[6. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
- This operation first assign
x[0]=4
, followed by x[0]=6
. So, x[0] contains the value 6
# In[15]
i=[2,3,3,4,4,4]
x[i]+=1
x
# Out[15]
array([6., 0., 1., 1., 1., 0., 0., 0., 0., 0.])
x[i]+=1
is meant as a shorthand of x[i]=x[i]+1
.
x[i]+1
is evaluated, and then the result is assigned to the indices in x
- With this in mind, it is not the augmentation that happens multiple times, but the assignment, which leads to the rather nonintuitive results.
- So, if you want the other behavior where the operation is repeated, you can use the
at
method of ufuncs.
# In[16]
x=np.zeros(10)
np.add.at(x,i,1)
print(x)
# Out[16]
[0. 0. 1. 2. 3. 0. 0. 0. 0. 0.]
at
method does an in-place application of the given operator at the specified indices (here,i) with the specified value (here,i)