import rustymath as rm
import rustymath.mathish as mmSearching, sorting and selection
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))
Pattern search
find(pattern, target) marks each start where the whole pattern fits and matches. The Boolean mask has the target’s shape and includes overlapping matches. Floats match as they do for isin:
motifs = rm.find([1, 2, 1], [1, 2, 1, 2, 1])
motifs, rm.nonzero(motifs)(Array([True, False, True, False, False], dtype=bool),
(Array([0, 2], dtype=int64),))
A character patch works the same way in two dimensions. argwhere gives its start coordinates. Lower-rank patterns acquire leading singleton axes, making a vector pattern horizontal in a matrix. A pattern of higher rank than the target has no matches:
image = rm.array([list('ababa'), list('babab'), list('ababa')])
patches = rm.find([list('ab'), list('ba')], image)
patches, rm.argwhere(patches)(Array([[True, False, True, False, False], [False, True, False, True, False], [False, False, False, False, False]], dtype=bool),
Array([[0, 0], [0, 2], [1, 1], [1, 3]], dtype=int64))