Sorting, searching and unique

NumPy · Practical · 12 min

Ranking, top-k, binning and deduplication are everyday tasks with fast dedicated NumPy routines.

np.sort(a) returns a sorted copy; a.sort() sorts in place. Both sort along the last axis by default; pass axis=0 to sort each column, or axis=None to flatten first. NaNs are sorted to the end. There is no reverse flag: use np.sort(a)[::-1] for descending order.

`np.argsort(a)` returns the indices that would sort a. That is the tool for sorting one array by another (names[np.argsort(scores)]) and for ranking. Use kind="stable" when ties must keep their original order, for example when sorting by a secondary key after a primary one. np.lexsort sorts by several keys at once.

For the top or bottom k values you don't need a full sort. np.partition(a, k) rearranges so that the element at position k is where it would be in sorted order, smaller ones before it and larger ones after, in O(n) time. np.argpartition gives the indices.

`np.searchsorted(sorted_arr, values)` does a binary search: for each value it returns the index where it would be inserted to keep the order. It's how you bin values into intervals or look up many keys in a sorted table in O(log n) each.

`np.unique(a)` returns the sorted distinct values; with return_counts=True or return_inverse=True it also gives frequencies or a mapping from each element to its unique value. NumPy 2 adds the clearer np.unique_values, np.unique_counts and np.unique_inverse, returning named tuples.

Also handy: np.argmax/np.argmin for positions of extremes, np.nonzero for positions of matches, and np.isin(a, values) for vectorized membership tests.

Example

import numpy as np

scores = np.array([72, 95, 61, 88, 95])
names = np.array(["ann", "bo", "cy", "di", "ed"])

order = np.argsort(-scores, kind="stable")   # descending, ties stable
print(names[order])          # ['bo' 'ed' 'di' 'ann' 'cy']

top2 = np.argpartition(scores, -2)[-2:]      # top 2, unordered
print(np.sort(names[top2]))  # ['bo' 'ed']

edges = np.array([60, 70, 80, 90])           # grade boundaries
print(np.searchsorted(edges, scores, side="right"))   # [2 4 1 3 4]

vals, counts = np.unique(scores, return_counts=True)
print(vals, counts)          # [61 72 88 95] [1 1 1 2]
res = np.unique_counts(scores)
print(res.values, res.counts)

print(np.isin(names, ["bo", "zz"]))   # [False  True False False False]

Remember

  • np.sort copies, a.sort() is in place; reverse with [::-1]
  • argsort gives ordering indices; use kind='stable' when ties matter
  • partition/argpartition find top-k in linear time
  • searchsorted binary-searches a sorted array; unique deduplicates and counts

Watch out for

  • Using searchsorted on an unsorted array: it won't complain, but the answers are wrong.

Check yourself

  1. What is the most efficient way to get the 10 largest values out of 10 million?
  2. `np.searchsorted([10, 20, 30], 25)` returns?

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

Official docs: numpy.org/doc/stable/reference/routines.sort.html