Skip to main content

Picking and ordering with integer arrays

A list of positions used as an index picks rows in any order, with repeats. Sort the days by hill temperature with np.argsort, shuffle features and targets with one seeded permutation as a train-test split does, and see why two position lists pick entries rather than a block.
P02.3.3L1 Core25 min
In review (what this means)

The maths and code on this page are checked by automated tests on every change, but an independent reader has not signed it off yet. Spot a mistake or a confusing step? Tell us.

Before this lesson: P02.3.2 Boolean masks

What you will be able to do
  • Pick rows or columns in any order, with repeats, using a list or array of positions.
  • Explain what np.argsort returns and use it to order the rows of a table by one column.
  • Build argsort in plain Python with sorted(range(n), key=…).
  • Shuffle features and targets with one seeded permutation so that every pair stays together.
  • Tell the entries picked by temps[[0, 1], [2, 0]] from the block picked by np.ix_.

The question

Sort the four days from the coolest hill reading to the warmest, keeping each day’s three readings together. Shuffle a dataset’s rows before splitting it into training and test sets, without separating any input from its target. Both jobs put rows into a new order. A slice can only walk forwards or backwards in steps, and a mask can only keep or drop. What kind of index names an arbitrary order?

From the last lesson

1. What does temps[temps > 27] return?

Show answer

The 1-D array [28, 30, 29, 32]: the readings above 27, in row-by-row order.

2. Why does warm and cool fail for two masks, and what is written instead?

Show answer

and needs one truth value from each side, and an array of four has none, so NumPy raises ValueError. Write warm & cool.

Intuition

The running array once more: noon temperatures (invented) with days as rows and the coast, hill and valley stations as columns.

import numpy as np

temps = np.array([[21, 19, 26],
                  [27, 23, 28],
                  [27, 18, 30],
                  [29, 20, 32]])

Put a list of positions inside the brackets and NumPy reads it as a shopping list: fetch the row at the first position, then the row at the second, and so on, and stack them in that order. Nothing says the positions must be in order or different from each other. This is called integer array indexing (some books say fancy indexing).

Two panels. Left, temps[[3, 0]]: the temps grid with rows mon and thu highlighted and positions 0 to 3 beside the rows; green arrows run from row 3 to the first row of a vermillion result and from row 0 to its second row, crossing; the result holds 29, 20, 32 labelled thu, then 21, 19, 26 labelled mon, shape (2, 3); caption: any order, Thursday first. Right, temps[[0, 0, 2]]: rows mon and wed highlighted; two arrows leave row 0 and one leaves row 2, giving a three-row result mon, mon, wed, shape (3, 3); caption: repeats allowed, Monday twice.
P02.3.3-F1

A list of positions picks rows in the order it names. The result has one row per entry of the list, so [3, 0] gives two rows, Thursday first, and [0, 0, 2] gives three, with Monday twice.

Build it up

Step 1. Rows in any order. temps[[3, 0]] has shape (2, 3): Thursday’s readings, then Monday’s. Note the two pairs of brackets: the outer ones index, the inner ones make the list. In plain Python the same rows come from a comprehension over the positions, [rows[i] for i in [3, 0]].

Step 2. Repeats. temps[[0, 0, 2]] has three rows, Monday twice and then Wednesday. Drawing the same example several times is exactly what a bootstrap sample does (S02.8).

Step 3. Columns too. A list in the column place reorders columns: temps[:, [2, 0]] gives the valley and then the coast reading for every day, shape (4, 2).

Step 4. The positions that would sort. np.argsort(a) does not return the sorted values. It returns the positions of the values in sorted order. For the hill column [19, 23, 18, 20] the smallest value, 18, is at position 2, then 19 at 0, 20 at 3 and 23 at 1, so np.argsort(hill) is [2, 0, 3, 1]. Feed those positions back in as an index and the rows come out ordered by hill temperature, each day’s readings travelling together.

Left, hill = temps[:, 1] drawn as a column of four cells 19, 23, 18, 20 beside the labels mon, tue, wed, thu and positions 0 to 3. Green arrows cross to a vermillion column of the sorted values 18, 19, 20, 23, and beside it an orange column order holding 2, 0, 3, 1, with the code order = np.argsort(hill). A green arrow leads to temps[order]: a vermillion grid with rows wed, mon, thu, tue holding 27, 18, 30; 21, 19, 26; 29, 20, 32; 27, 23, 28, with the hill column highlighted and rising 18, 19, 20, 23; caption: days by hill reading.
P02.3.3-F2

np.argsort answers “where is the smallest, where is the next?” Its answer, used as an index, reorders whole rows so that the hill column rises.

For the warmest first, reverse the positions: np.argsort(hill)[::-1] is [1, 3, 0, 2], Tuesday first. And the first k entries of the reversed order are the positions of the k largest values: np.argsort(temps[:, 2])[::-1][:2] is [3, 2], the two warmest valley days, Thursday and Wednesday.

Step 5. Shuffling. A permutation of 0 to \({n - 1}\) is those numbers in some order, each once. rng.permutation(4) returns a random one, drawn from a generator made with a seed so that it can be repeated (seeds are lesson P02.9.2). Use the same permutation on the inputs and on the targets, and every input keeps its own target. This is how ML01.2.2 shuffles before it splits.

Top, the code rng = np.random.default_rng(0); order = rng.permutation(4). Left, the four regression points as two rows, x holding 1, 2, 3, 4 in blue and y holding 2, 3, 5, 6 in grey, positions 0 to 3 above, captioned four (x, y) pairs. A green arrow leads right to an orange row order holding 2, 0, 1, 3, and under it x[order] holding 3, 1, 2, 4 and y[order] holding 5, 2, 3, 6. A blue bracket marks the first two columns as train and a grey bracket the last two as test. Caption: every column is still a true pair: (3, 5), (1, 2), (2, 3), (4, 6).
P02.3.3-F4

One permutation, applied to x and to y, moves each pair as a unit. Cutting the shuffled arrays then gives a random training set and a random test set.

The formal statement

NoteDefinition: integer array indexing

Let a have length \(n\) along axis 0, and let p be a list or array of whole numbers, each with \({-n \le p_k < n}\).

  • a[p] has one row for each entry of p: row \(k\) of the result is row \({p_k}\) of a. Its shape is the shape of p followed by the rest of a.shape.
  • The positions may come in any order and may repeat.
  • The result is always a new array, a copy, never a view of a (lesson P02.3.4).
  • With one list per axis, a[p, q], the lists are paired up: entry \(k\) of the result is a[p[k], q[k]]. (Lists of different shapes are first broadcast together by the rule of P02.2.3, so a list of one position pairs with every entry of the other, while lists of lengths 2 and 3 raise IndexError.)

Why temps[np.argsort(c)] orders the rows by c. Let o = np.argsort(c). By definition, the values c[o[0]], c[o[1]], ... are in increasing order. Now look at row \(k\) of temps[o]: it is row \({o_k}\) of temps, and its entry in the column c came from is \({c[o_k]}\). So, reading down the result, that column runs through \({c[o_0], c[o_1], \dots}\), which is increasing, and each row arrived whole, so no reading has moved to another day.

Ties. The coast column [21, 27, 27, 29] holds 27 twice. Either of the two 27s may come first in a sorted order. NumPy’s default sort does not promise which; np.argsort(c, kind="stable") promises that equal values keep their original order, here Tuesday before Wednesday.

Go deeper: undoing a shuffle

A permutation can be undone by another permutation, its inverse, and np.argsort finds it. Let order be a permutation of 0 to \({n - 1}\) and inv = np.argsort(order). Sorting the numbers 0 to \({n - 1}\) gives exactly \({0, 1, \dots, n - 1}\), so order[inv] is [0, 1, ..., n - 1]: position \(k\) of it says \({\text{order}[\text{inv}[k]] = k}\). Now take s = x[order] and index it with inv. Entry \(k\) of s[inv] is \({s[\text{inv}[k]] = x[\text{order}[\text{inv}[k]]] = x[k]}\). So s[inv] is x again: the shuffle is undone.

Worked example

Order the days by hill reading, coolest first: build the order in plain Python, check it against np.argsort, and apply it.

Python’s sorted takes a key: a function that gives the value to sort by. Sort the positions 0, 1, 2, 3, and let the key of position i be the hill reading hill[i]:

  1. Pair each position with its key: (0, 19), (1, 23), (2, 18), (3, 20).
  2. Sort the pairs by the key: (2, 18), (0, 19), (3, 20), (1, 23).
  3. Keep the positions: [2, 0, 3, 1].
At the top, the code order = sorted(range(4), key=lambda i: hill[i]). Left, a two-row table: the orange top row i holds 0, 1, 2, 3 and the blue bottom row hill[i] holds 19, 23, 18, 20; caption: 1, each position with its key. A green arrow labelled sort by key leads to the same pairs reordered: top row 2, 0, 3, 1 highlighted, bottom row 18, 19, 20, 23; caption: 2, columns sorted by the key. Below: 3, read the top row: order = [2, 0, 3, 1]; and np.argsort(hill) agrees.
P02.3.3-F3

The worked example as a trace. Sorting positions by their keys and keeping the positions is what np.argsort does in one call.

That is np.argsort(hill). Then temps[[2, 0, 3, 1]] stacks Wednesday, Monday, Thursday and Tuesday:

\[ \begin{aligned} &[[27, 18, 30], \\ &\;\;[21, 19, 26], \\ &\;\;[29, 20, 32], \\ &\;\;[27, 23, 28]] \end{aligned} \]

The middle column now reads 18, 19, 20, 23, increasing, and every row is a day’s real readings.

Code

Picking rows from scratch with a comprehension, then with an index:

rows = temps.tolist()
print([rows[i] for i in [3, 0]])
print(temps[[3, 0]])
print(temps[[0, 0, 2]].shape, temps[:, [2, 0]].shape)
[[29, 20, 32], [21, 19, 26]]
[[29 20 32]
 [21 19 26]]
(3, 3) (4, 2)

Argsort from scratch, then NumPy’s, then the sorted table:

hill = temps[:, 1]
order = sorted(range(4), key=lambda i: hill[i])
print(order, np.argsort(hill))
print(temps[np.argsort(hill)])
print(np.argsort(hill)[::-1], np.argsort(temps[:, 2])[::-1][:2])
print(np.argsort(temps[:, 0], kind="stable"))
[2, 0, 3, 1] [2 0 3 1]
[[27 18 30]
 [21 19 26]
 [29 20 32]
 [27 23 28]]
[1 3 0 2] [3 2]
[0 1 2 3]

Shuffling the regression points of ML01 with one seeded permutation, then cutting it into a training half and a test half:

x = np.array([1, 2, 3, 4])
y = np.array([2, 3, 5, 6])
rng = np.random.default_rng(0)
order = rng.permutation(4)
print(order, x[order], y[order])
train, test = order[:2], order[2:]
print("train:", list(zip(x[train].tolist(), y[train].tolist())),
      " test:", list(zip(x[test].tolist(), y[test].tolist())))
[2 0 1 3] [3 1 2 4] [5 2 3 6]
train: [(3, 5), (1, 2)]  test: [(2, 3), (4, 6)]

With the NumPy version this course pins, order comes out as [2 0 1 3], as in the figure. Another seed gives another order; whatever the order, every shuffled pair is one of the four true pairs, and the training and test positions never overlap. That is what the hidden checks test.

Explore

This module’s interactive, the index explorer, lives in lesson P02.3.4. Its list mode builds a list of row positions one press at a time: try [3, 0], then a list with repeats, and watch the result grow one row per entry.

Pitfalls

Two panels. Left, marked with a cross and headed two entries, not a block: the temps grid with positions, where only the cells at row 0, column 2 (26) and row 1, column 0 (27) are highlighted; an arrow leads to a vermillion row 26, 27 of shape (2,). Notes: temps[[0, 1], [2, 0]] pairs up the lists, (0, 2) and (1, 0): temps[0, 2] = 26, temps[1, 0] = 27. Right, marked with a tick and headed np.ix_ makes the block: four cells highlighted, rows 0 and 1 at columns 0 and 2; an arrow leads to a vermillion 2 by 2 block 26, 21; 28, 27 of shape (2, 2). Notes: temps[np.ix_([0, 1], [2, 0])], rows 0, 1 crossed with columns 2, 0; or in two steps, temps[[0, 1]][:, [2, 0]].
P02.3.3-F5

Wrong beside right. Two position lists are read in pairs, one entry per pair. To cross every chosen row with every chosen column, use np.ix_, or index the rows first and the columns second.

“temps[[0, 1], [2, 0]] gives rows 0 and 1 at columns 2 and 0.”

It gives two entries, temps[0, 2] and temps[1, 0], that is [26, 27]. The lists are zipped together, not crossed. For the 2 by 2 block, write temps[np.ix_([0, 1], [2, 0])] or temps[[0, 1]][:, [2, 0]].

“Shuffle the inputs and the targets, each with rng.permutation.”

Two calls draw two independent orders, which on a real dataset almost never agree, so input \(i\) ends up beside some other example’s target and the model learns from noise. Draw one order and index both arrays with it.

“np.argsort(hill) gives the hill readings in order.”

It gives their positions in order, [2, 0, 3, 1]. The sorted values are hill[np.argsort(hill)], or np.sort(hill), [18, 19, 20, 23]. Positions are what you need to move whole rows.

“Sorting puts tied values in their original order.”

Not unless you ask. With the default sort, the two coast readings of 27 may come out either way round. Pass kind="stable" when the order of ties matters, for example when a second sort must not undo a first.

Check yourself

Recall

1. What does temps[[3, 0]] return, and what is its shape?

Show answer

Thursday’s readings, then Monday’s: [[29, 20, 32], [21, 19, 26]], shape (2, 3).

2. What is np.argsort([19, 23, 18, 20])?

Show answer

[2, 0, 3, 1]: the smallest value is at position 2, then position 0, then 3, then 1.

3. How do you order the days by hill reading, warmest first, and which day comes first?

Show answer

temps[np.argsort(temps[:, 1])[::-1]]. Tuesday comes first, with 23.

4. What is temps[[0, 1], [2, 0]]?

Show answer

[26, 27]: the two entries temps[0, 2] and temps[1, 0], not a block.

5. Why must the inputs and the targets be shuffled with the same permutation?

Show answer

So that every input stays beside its own target. Two separate shuffles break the pairs.

Exercises

1. ★ List the days from the warmest valley reading to the coolest, and give the positions of the two warmest valley days.

Show solution

The valley column is [26, 28, 30, 32], already increasing, so np.argsort(temps[:, 2]) is [0, 1, 2, 3] and reversed it is [3, 2, 1, 0]: Thursday, Wednesday, Tuesday, Monday. The first two of the reversed order, [3, 2], are the positions of the two warmest valley days.

2. ★★ Predict temps[[1, 1, 3], [0, 2, 2]] and temps[[1, 1, 3]][:, [0, 2]], with their shapes.

Show solution

The first pairs the lists: temps[1, 0], temps[1, 2], temps[3, 2], that is [27, 28, 32], shape (3,). The second takes rows 1, 1 and 3 first, then columns 0 and 2 of each: [[27, 28], [27, 28], [29, 32]], shape (3, 2).

3. ★★★ After s = x[order] with order = [2, 0, 1, 3] and x = [1, 2, 3, 4], find a position list inv with s[inv] equal to x. Show that np.argsort(order) is such a list.

Show solution

s is [3, 1, 2, 4]. Value 1 sits at position 1 of s, value 2 at position 2, value 3 at position 0 and value 4 at position 3, so inv = [1, 2, 0, 3] and s[inv] is [1, 2, 3, 4].

np.argsort([2, 0, 1, 3]) lists where the smallest entry of order is (position 1, holding 0), then the next (position 2, holding 1), then position 0 (holding 2), then position 3 (holding 3): again [1, 2, 0, 3]. In general order[inv] is [0, 1, 2, 3], so entry \(k\) of s[inv] is \({x[\text{order}[\text{inv}[k]]] = x[k]}\), as the deeper block shows.

Summary card

A list of positions picks rows in the order it names.

The anchor figure again: temps[[3, 0]] gives Thursday then Monday, and temps[[0, 0, 2]] gives Monday twice then Wednesday.
P02.3.3-F1

Any order, repeats allowed.

  • a[[i, j, ...]]: rows \({i, j, \dots}\) in that order; always a copy.
  • np.argsort(c): positions that sort c; a[np.argsort(c)] orders rows by c.
  • One order = rng.permutation(n) for X and y keeps pairs together.
  • a[p, q] pairs the lists; a[np.ix_(p, q)] gives the block.

Sources and next steps

Sources. Each was opened and checked against this lesson on 15 November 2026.

  • NumPy developers, Indexing on ndarrays, NumPy user guide, section Advanced indexing, subsection Integer array indexing: index arrays are broadcast and iterated as one, so x[[0, 1, 2], [0, 1, 0]] picks three entries; the example with np.ix_, which without the call would select only the diagonal; advanced indexing always returns a copy.
  • NumPy developers, numpy.argsort, NumPy reference: returns the indices that would sort an array; the default kind is quicksort, and kind="stable" (or stable=True) keeps equal values in their relative order.
  • NumPy developers, numpy.random.Generator.permutation, NumPy reference: for an integer x it permutes np.arange(x); for an array it shuffles a copy along axis 0.
  • Jake VanderPlas, Python Data Science Handbook, Fancy Indexing: the shape of the result reflects the shape of the index arrays; X[row, col] pairs its index arrays; fancy indexing combined with slices; selecting a random subset of rows.

Next. P02.3.4 Views and copies asks what each kind of index gives back: a slice shares memory with the array, while a mask or a list of positions makes a copy.

This lesson unlocks

P02.3.4 Views and copies

Your progress is saved in this browser only. No account, no tracking.
Back to top