Skip to main content

Searching, sorting and selection

Stable exact ordering and tolerant matching, classification and grouping.
import rustymath as rm
import rustymath.mathish as mm

Ordering

rm.search provides stable sort, argsort and lexsort, searchsorted, integer bincount, class counts and group_indices. These functions are also available directly on rm. Sorts work along the last axis unless you give axis. Ordering and insertion searches compare exactly and equate signed zeros. NaNs come last in ascending order and first with sort(..., descending=True).

scores = rm.array([8., 2, 5, 1, 9, 3])
rm.sort(scores), rm.argsort(scores)
(Array([1.0, 2.0, 3.0, 5.0, 8.0, 9.0], dtype=float64),
 Array([3, 1, 5, 2, 0, 4], dtype=int64))

Partial selection

partition(x, k) puts the item of rank k in its sorted position without sorting either side. argpartition gives the positions instead of the values. A negative rank counts from the end. Median and quantiles use selection rather than fully sorting the array; percentile takes probabilities in percent. Quantiles interpolate linearly between neighbouring ranks:

rm.partition(scores, 2), rm.median(scores), rm.quantile(scores, [0.25, 0.5, 0.75])
(Array([1.0, 2.0, 3.0, 5.0, 8.0, 9.0], dtype=float64),
 4.0,
 Array([2.25, 4.0, 7.25], dtype=float64))

For the smallest three scores, partition their positions and gather the selected values. Their internal order is unspecified. argmin and argmax give a single extreme’s position:

chosen = rm.argpartition(scores, 2)[:3]
chosen, scores[chosen], rm.argmin(scores), rm.argmax(scores)
(Array([3, 1, 5], dtype=int64), Array([1.0, 2.0, 3.0], dtype=float64), 3, 4)

Matching and classification

unique, index and isin compare floats exactly. Their Mathish namespace versions select tolerant matching. Booleans and integers always match exactly. unique keeps values in order of first occurrence, with each one’s first position, each item’s class number and each class’s size. index(x, y) gives where in x each item of y first matches, or the size of x where nothing does:

exact = rm.index([0.3, 1.0], 0.1 + 0.2)
matched = mm.unique([0.3, 1.0, 0.1 + 0.2]), mm.index([0.3, 1.0], 0.1 + 0.2)
exact, matched
(2,
 (Unique(values=Array([0.3, 1.0], dtype=float64), firsts=Array([0, 1], dtype=int64), numbers=Array([0, 1, 0], dtype=int64), counts=Array([2, 1], dtype=int64)),
  0))

Set operations match items as isin does, with the same tolerance, and retain first-occurrence order, rather than sorting their results. Their inputs are flattened.

rm.union1d([3, 1, 3, 2], [2, 4]), rm.intersect1d([3, 1, 3, 2], [2, 4]), rm.setdiff1d([3, 1, 3, 2], [2, 4])
(Array([3, 1, 2, 4], dtype=int64),
 Array([2], dtype=int64),
 Array([3, 1], dtype=int64))

Grouped analysis

Use the class numbers from unique as destinations for indexed addition. Repeated destinations accumulate every measurement. counts(labels) counts classes in first-occurrence order; bincount requires nonnegative integer labels and returns a count at each integer position:

labels = rm.array('babab')
sales = rm.array([12, 7, 5, 10, 8])
classes = rm.unique(labels)
totals = rm.at(rm.add, rm.zeros(len(classes.values)), classes.numbers, sales)
classes.values, classes.counts, totals, rm.bincount([0, 2, 2])
(Array(['b', 'a'], dtype=character),
 Array([3, 2], dtype=int64),
 Array([25.0, 17.0], dtype=float64),
 Array([1, 0, 2], dtype=int64))

group_indices gives the positions for each class when the groups themselves are needed. lexsort accepts multiple keys with the last key primary. Sort these records by label, then by descending sales within each label:

order = rm.lexsort((-sales, labels))
rm.group_indices(labels), labels[order], sales[order]
([Array([0, 2, 4], dtype=int64), Array([1, 3], dtype=int64)],
 Array(['a', 'a', 'b', 'b', 'b'], dtype=character),
 Array([10, 7, 12, 8, 5], dtype=int64))

searchsorted returns insertion positions in ascending data. Its side chooses before or after equal items. bins is the right-side spelling for bin boundaries:

boundaries = [0, 10, 20]
rm.searchsorted(boundaries, [0, 9, 10, 25]), rm.bins(boundaries, [0, 9, 10, 25])
(Array([0, 1, 1, 3], dtype=int64), Array([1, 1, 2, 3], dtype=int64))