Fancy (integer array) indexing

NumPy · Practical · 12 min

Lookups, reordering by argsort, sampling rows and one-hot style encodings all rely on integer array indexing.

When the index is an array (or list) of integers, NumPy performs advanced indexing, often called fancy indexing: it gathers the elements at those positions. a[[3, 0, 3]] returns a new array with elements 3, 0 and 3, in that order, repeats allowed.

The result's shape is the shape of the index array, not of a. So a[np.array([[0, 1], [2, 3]])] returns a 2x2 array. This makes fancy indexing a vectorized lookup table: palette[image] turns an array of colour ids of shape (h, w) into RGB pixels of shape (h, w, 3).

With several index arrays, they are paired up element by element: m[[0, 1, 2], [2, 0, 1]] returns m[0, 2], m[1, 0], m[2, 1]. That is not the same as selecting a sub-block. For "rows 0 and 2, columns 1 and 3" use m[np.ix_([0, 2], [1, 3])], or index in two steps m[[0, 2]][:, [1, 3]].

Mixing a slice with an index array works per axis: m[:, [2, 0]] reorders columns.

Two important behaviours:

  • Fancy indexing always returns a copy, unlike slicing.
  • When assigning with repeated indices, each repeat simply overwrites: a[[0, 0]] += 1 increments a[0] only once. To accumulate repeats, use the unbuffered ufunc method np.add.at(a, idx, 1), or np.bincount for counts.

A typical pairing is order = np.argsort(keys) followed by values[order], which sorts one array by another.

Example

import numpy as np

a = np.array([10, 20, 30, 40, 50])
print(a[[3, 0, 3]])                 # [40 10 40]
print(a[np.array([[0, 1], [4, 4]])])   # [[10 20] [50 50]]

m = np.arange(12).reshape(3, 4)
print(m[[0, 1, 2], [2, 0, 1]])      # [2 4 9]  paired positions
print(m[np.ix_([0, 2], [1, 3])])    # [[ 1  3] [ 9 11]]  a sub-block
print(m[:, [3, 0]])                 # columns reordered

palette = np.array([[0, 0, 0], [255, 0, 0], [0, 255, 0]])
img = np.array([[0, 1], [2, 1]])
print(palette[img].shape)           # (2, 2, 3)

counts = np.zeros(3, dtype=int)
idx = np.array([0, 2, 2, 2])
counts[idx] += 1
print(counts)                       # [1 0 1]  repeats not accumulated
counts[:] = 0
np.add.at(counts, idx, 1)
print(counts)                       # [1 0 3]

Remember

  • An integer array index gathers elements; result shape = index shape
  • Multiple index arrays pair up element-wise; use np.ix_ for sub-blocks
  • Fancy indexing returns a copy
  • Use np.add.at or np.bincount when indices repeat and must accumulate

Watch out for

  • Writing m[[0, 2], [1, 3]] expecting a 2x2 block and getting two elements.

Check yourself

  1. `m = np.arange(9).reshape(3, 3)`. What is `m[[0, 2], [0, 2]]`?
  2. `a = np.zeros(3); a[[1, 1, 1]] += 1`. What is `a`?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: numpy.org/doc/stable/user/basics.indexing.html#integer-array-indexing