NumPy - 13 Fancy Indexing

Fancy indexing refers to using integer arrays or lists to index and select specific rows, columns, or elements in a NumPy array. This can be done to select a subset of rows in a particular order or create complex indexing patterns.


Creating and Indexing a Basic Array

An 8 x 4 array initialized with zeros

>>> arr = np.zeros((8, 4))
>>> for i in range(8):
...     arr[i] = i
...

>>> arr
array([[0., 0., 0., 0.],
       [1., 1., 1., 1.],
       [2., 2., 2., 2.],
       [3., 3., 3., 3.],
       [4., 4., 4., 4.],
       [5., 5., 5., 5.],
       [6., 6., 6., 6.],
       [7., 7., 7., 7.]])

Rows can be selected in any order by passing a list or ndarray of indices to the array:

>>> arr[[4, 3, 0, 6]]
array([[4., 4., 4., 4.],
       [3., 3., 3., 3.],
       [0., 0., 0., 0.],
       [6., 6., 6., 6.]])
  • The rows are selected in the specified order: 4, 3, 0, 6.

Using negative indexing to access rows starting from the end:

>>> arr[[-3, -5, -7]]
array([[5., 5., 5., 5.],
       [3., 3., 3., 3.],
       [1., 1., 1., 1.]])
  • Negative indices count from the last row backwards.

Fancy Indexing with Multiple Arrays

Passing multiple index arrays selects elements in a one-dimensional array corresponding to each tuple of indices.

>>> arr = np.arange(32).reshape((8, 4))
>>> arr
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15],
       [16, 17, 18, 19],
       [20, 21, 22, 23],
       [24, 25, 26, 27],
       [28, 29, 30, 31]])

>>> arr[[1], [0]]
array([4])

>>> arr[[1, 5], [0, 3]]
array([ 4, 23])

>>> arr[[1, 5, 7, 2], [0, 3, 1, 2]]
array([ 4, 23, 29, 10])
  • 4 is from position (1, 0),
  • 23 is from position (5, 3),
  • 29 is from position (7, 1),
  • 10 is from position (2, 2).

Selecting a Rectangular Region

Selecting a rectangular region of a matrix by specifying rows and then columns:

>>> arr
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15],
       [16, 17, 18, 19],
       [20, 21, 22, 23],
       [24, 25, 26, 27],
       [28, 29, 30, 31]])

>>> arr[ [1,5,7,2]]  [ : , [0,3,1,2] ] 
array([[ 4,  7,  5,  6],
       [20, 23, 21, 22],
       [28, 31, 29, 30],
       [ 8, 11,  9, 10]])
  • The first index array [[1, 5, 7, 2]] selects the rows.
  • The second slice [:, ] selects all columns,
  • The third index array [0, 3, 1, 2] selects specific columns for the output.

Fancy Indexing and Data Copying

Important: Fancy indexing always returns a copy of the data, not a view. This means that changes made to the result of a fancy indexing operation will not affect the original array. However, if you assign values with fancy indexing, the values in the original array will be modified.

>>> arr[[1, 5, 7, 2], :] = 99
>>> arr
array([[ 0,  1,  2,  3],
       [99, 99, 99, 99],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15],
       [16, 17, 18, 19],
       [99, 99, 99, 99],
       [24, 25, 26, 27],
       [99, 99, 99, 99]])
  • In this case, the rows selected by [[1, 5, 7, 2], :] are replaced with 99.

Key Points About Fancy Indexing

  • Fancy indexing allows you to select specific rows, columns, or individual elements using integer arrays or lists.
  • Unlike slicing, fancy indexing always creates a new array. The original array is not modified unless explicitly assigned.
  • Negative indexing works similarly to standard Python negative indexing, allowing you to count from the end of the array.
  • Multiple index arrays can be used to select individual elements from different locations in the array.