Sorting and searching
argsort is the one that matters — it returns the order, so you can apply it anywhere.
np.sortnp.argsortnp.argpartitionnp.searchsortednp.argmaxnp.digitizeWatch it happen
Play it through, or step back and forth yourself.
scoresnp.sort(scores)np.sort(scores)np.sort(scores) returns a sorted copy, ascending. scores.sort() — the method — sorts in place and returns None, which catches people out about once each.
The idea
np.sort(x) returns a sorted copy, ascending. x.sort() — the method — sorts in place and returns None, which everyone assigns to a variable exactly once before learning not to.
Sorting one array is usually a bug
Real data comes in parallel arrays: names and scores, timestamps and readings, ids and labels. Sort the scores on their own and you've destroyed the correspondence — the values are in order and nobody knows whose they are.
np.argsort answers the better question: what order would sort this?
scores = np.array([72, 45, 90, 58])
order = np.argsort(scores) # [1 3 0 2]Those are positions, not values: "the smallest is at index 1, then index 3, then 0, then 2". And a list of positions is exactly what fancy indexing eats, so you can apply that one permutation to every array that lines up:
names[order] # ['Bilal' 'Dev' 'Asha' 'Chen']
scores[order] # [45 58 72 90]That's "sort the table by this column" in two lines, and it generalises to any number of parallel arrays. Whenever you catch yourself wanting to sort a table, reach for argsort first.

Descending
There is no reverse= argument. Reverse the result instead:
np.sort(x)[::-1] # values, descending
np.argsort(x)[::-1] # the descending permutation
x[np.argsort(x)[::-1]] # the array itself, descendingWhen you only need the top few
Fully sorting a million values to find the largest ten is wasteful. np.argpartition does a partial sort — it guarantees the k-th element is in its final place with everything smaller before it, and doesn't bother ordering the rest:
top3 = np.argpartition(x, -3)[-3:] # the three largest, unordered
top3 = top3[np.argsort(x[top3])[::-1]] # now sort just those threeSearching a sorted array
Once an array is sorted, you get binary search — logarithmic rather than linear:
a = np.array([10, 20, 30, 40])
np.searchsorted(a, 25) # 2 — where 25 would be inserted
np.searchsorted(a, [5, 25, 45]) # [0 2 4] — several at once
np.searchsorted(a, 30, side='right') # 3 — after the equal value, not beforeThe classic use is bucketing without a loop: given bin edges, find which bin each value belongs to. np.digitize is a friendlier wrapper around the same idea.

Positions of extremes
np.argmax(cups) # position of the largest, in the flattened array
np.argmax(cups, axis=0) # per column
np.unravel_index(np.argmax(cups), cups.shape) # as a (row, col) pairargmax on a 2-D array with no axis flattens first, so you get a single number that indexes the flat array. np.unravel_index converts it back into coordinates, which is usually what you wanted.
And on 2-D, np.sort(m, axis=0) sorts each column independently — it scrambles rows relative to one another, which is almost always wrong for a table. Sort with argsort on the key column instead.
Practice
Write it yourself. The answer is there when you want it.
Putting the kettle on…
Starting up…
Write it yourself
not gradedGet the sort order with np.argsort(scores), then use it to print names and scores together, still matching. Print them descending as well. Then use np.searchsorted to find where 5, 25 and 45 would go in [10, 20, 30, 40]. Finish with the row and column of the best figure in cups, via np.unravel_index.
Your turn
4 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.
Return scores sorted ascending, as a new array.
Return names ordered by ascending score.
Now names ordered by descending score — best first.
Given np.array([10, 20, 30, 40]), where would 25 be inserted to keep it sorted? Return the position.
